From 7b96fb1690255f13591edba01770d2d82c197194 Mon Sep 17 00:00:00 2001 From: linyuh Date: Tue, 10 Oct 2017 11:03:33 -0700 Subject: Set an appropriate icon for the call detail UI's callback button. cl/170943038 groups calls in the call log according to their corresponding actions (Lightbringer, IMS, and voice). This way calls in the call detail UI are in the same category and an appropriate icon can be set. Bug: 66026167 Test: CallDetailsActivityTest.callbackButtonLoggedForLightbringerCall, CallDetailsActivityTest.callbackButtonLoggedForVideoCall, CallDetailsActivityTest.callbackButtonLoggedForVoiceCall PiperOrigin-RevId: 171703514 Change-Id: I534e1d22f1355f261105a6bde74285403fc9ed87 --- .../app/calllog/CallLogListItemViewHolder.java | 10 ++- .../android/dialer/app/calllog/IntentProvider.java | 9 ++- .../dialer/calldetails/CallDetailsActivity.java | 69 ++++++++++++++++++- .../dialer/calldetails/CallDetailsAdapter.java | 26 ++++++-- .../calldetails/CallDetailsHeaderViewHolder.java | 77 ++++++++++++++++------ .../android/dialer/logging/dialer_impression.proto | 7 +- 6 files changed, 165 insertions(+), 33 deletions(-) diff --git a/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java b/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java index adf7f1b4e..6067c4239 100644 --- a/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java +++ b/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java @@ -551,7 +551,7 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder if (mCallLogCache.isVoicemailNumber(accountHandle, number)) { // Call to generic voicemail number, in case there are multiple accounts primaryActionButtonView.setTag(IntentProvider.getReturnVoicemailCallIntentProvider()); - } else if (this.info != null && this.info.lookupKey != null) { + } else if (canSupportAssistedDialing()) { primaryActionButtonView.setTag( IntentProvider.getAssistedDialIntentProvider( number + postDialDigits, @@ -618,7 +618,7 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder ((TextView) callButtonView.findViewById(R.id.call_type_or_location_text)); if (canPlaceCallToNumber) { - if (this.info != null && this.info.lookupKey != null) { + if (canSupportAssistedDialing()) { callButtonView.setTag( IntentProvider.getAssistedDialIntentProvider( number, mContext, mContext.getSystemService(TelephonyManager.class))); @@ -685,7 +685,7 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder && mCachedNumberLookupService.canReportAsInvalid(info.sourceType, info.objectId); detailsButtonView.setTag( IntentProvider.getCallDetailIntentProvider( - callDetailsEntries, buildContact(), canReportCallerId)); + callDetailsEntries, buildContact(), canReportCallerId, canSupportAssistedDialing())); } boolean isBlockedOrSpam = blockId != null || (isSpamFeatureEnabled && isSpam); @@ -764,6 +764,10 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder return accountHandle.getComponentName().equals(mDefaultPhoneAccountHandle.getComponentName()); } + private boolean canSupportAssistedDialing() { + return info != null && info.lookupKey != null; + } + private boolean canSupportCarrierVideoCall() { return mCallLogCache.canRelyOnVideoPresence() && info != null diff --git a/java/com/android/dialer/app/calllog/IntentProvider.java b/java/com/android/dialer/app/calllog/IntentProvider.java index 52a7b0faf..5030de5f5 100644 --- a/java/com/android/dialer/app/calllog/IntentProvider.java +++ b/java/com/android/dialer/app/calllog/IntentProvider.java @@ -124,15 +124,20 @@ public abstract class IntentProvider { * * @param callDetailsEntries The call details of the other calls grouped together with the call. * @param contact The contact with which this call details intent pertains to. + * @param canReportCallerId Whether reporting a caller ID is supported. + * @param canSupportAssistedDialing Whether assisted dialing is supported. * @return The call details intent provider. */ public static IntentProvider getCallDetailIntentProvider( - CallDetailsEntries callDetailsEntries, DialerContact contact, boolean canReportCallerId) { + CallDetailsEntries callDetailsEntries, + DialerContact contact, + boolean canReportCallerId, + boolean canSupportAssistedDialing) { return new IntentProvider() { @Override public Intent getIntent(Context context) { return CallDetailsActivity.newInstance( - context, callDetailsEntries, contact, canReportCallerId); + context, callDetailsEntries, contact, canReportCallerId, canSupportAssistedDialing); } }; } diff --git a/java/com/android/dialer/calldetails/CallDetailsActivity.java b/java/com/android/dialer/calldetails/CallDetailsActivity.java index 569aaa5b5..d7f414bb2 100644 --- a/java/com/android/dialer/calldetails/CallDetailsActivity.java +++ b/java/com/android/dialer/calldetails/CallDetailsActivity.java @@ -16,6 +16,7 @@ package com.android.dialer.calldetails; +import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.os.AsyncTask; @@ -29,21 +30,30 @@ import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar.OnMenuItemClickListener; +import android.telephony.TelephonyManager; import android.view.MenuItem; +import android.widget.Toast; +import com.android.dialer.assisteddialing.ConcreteCreator; import com.android.dialer.calldetails.CallDetailsEntries.CallDetailsEntry; +import com.android.dialer.callintent.CallInitiationType; +import com.android.dialer.callintent.CallIntentBuilder; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.AsyncTaskExecutors; +import com.android.dialer.constants.ActivityRequestCodes; import com.android.dialer.dialercontact.DialerContact; import com.android.dialer.enrichedcall.EnrichedCallComponent; import com.android.dialer.enrichedcall.EnrichedCallManager.HistoricalDataChangedListener; import com.android.dialer.enrichedcall.historyquery.proto.HistoryResult; +import com.android.dialer.lightbringer.Lightbringer; +import com.android.dialer.lightbringer.LightbringerComponent; import com.android.dialer.logging.DialerImpression; import com.android.dialer.logging.Logger; import com.android.dialer.logging.UiAction; import com.android.dialer.performancereport.PerformanceReport; import com.android.dialer.postcall.PostCall; import com.android.dialer.protos.ProtoParsers; +import com.android.dialer.util.DialerUtils; import java.util.Collections; import java.util.List; import java.util.Map; @@ -51,6 +61,7 @@ import java.util.Map; /** Displays the details of a specific call log entry. */ public class CallDetailsActivity extends AppCompatActivity implements OnMenuItemClickListener, + CallDetailsHeaderViewHolder.CallbackActionListener, CallDetailsFooterViewHolder.ReportCallIdListener, HistoricalDataChangedListener { @@ -59,6 +70,7 @@ public class CallDetailsActivity extends AppCompatActivity private static final String EXTRA_CALL_DETAILS_ENTRIES = "call_details_entries"; private static final String EXTRA_CONTACT = "contact"; private static final String EXTRA_CAN_REPORT_CALLER_ID = "can_report_caller_id"; + private static final String EXTRA_CAN_SUPPORT_ASSISTED_DIALING = "can_support_assisted_dialing"; private static final String TASK_DELETE = "task_delete"; private CallDetailsEntries entries; @@ -74,7 +86,8 @@ public class CallDetailsActivity extends AppCompatActivity Context context, @NonNull CallDetailsEntries details, @NonNull DialerContact contact, - boolean canReportCallerId) { + boolean canReportCallerId, + boolean canSupportAssistedDialing) { Assert.isNotNull(details); Assert.isNotNull(contact); @@ -82,6 +95,7 @@ public class CallDetailsActivity extends AppCompatActivity ProtoParsers.put(intent, EXTRA_CONTACT, contact); ProtoParsers.put(intent, EXTRA_CALL_DETAILS_ENTRIES, details); intent.putExtra(EXTRA_CAN_REPORT_CALLER_ID, canReportCallerId); + intent.putExtra(EXTRA_CAN_SUPPORT_ASSISTED_DIALING, canSupportAssistedDialing); return intent; } @@ -142,7 +156,13 @@ public class CallDetailsActivity extends AppCompatActivity entries = ProtoParsers.getTrusted( intent, EXTRA_CALL_DETAILS_ENTRIES, CallDetailsEntries.getDefaultInstance()); - adapter = new CallDetailsAdapter(this, contact, entries.getEntriesList(), this); + adapter = + new CallDetailsAdapter( + this /* context */, + contact, + entries.getEntriesList(), + this /* callbackListener */, + this /* reportCallIdListener */); RecyclerView recyclerView = findViewById(R.id.recycler_view); recyclerView.setLayoutManager(new LinearLayoutManager(this)); @@ -188,6 +208,51 @@ public class CallDetailsActivity extends AppCompatActivity .getEntriesList()); } + @Override + public void placeImsVideoCall(String phoneNumber) { + Logger.get(this).logImpression(DialerImpression.Type.CALL_DETAILS_IMS_VIDEO_CALL_BACK); + DialerUtils.startActivityWithErrorToast( + this, + new CallIntentBuilder(phoneNumber, CallInitiationType.Type.CALL_DETAILS) + .setIsVideoCall(true) + .build()); + } + + @Override + public void placeLightbringerCall(String phoneNumber) { + Logger.get(this).logImpression(DialerImpression.Type.CALL_DETAILS_LIGHTBRINGER_CALL_BACK); + Lightbringer lightbringer = LightbringerComponent.get(this).getLightbringer(); + if (!lightbringer.isReachable(this, phoneNumber)) { + placeImsVideoCall(phoneNumber); + return; + } + + try { + startActivityForResult( + lightbringer.getIntent(this, phoneNumber), ActivityRequestCodes.DIALTACTS_LIGHTBRINGER); + } catch (ActivityNotFoundException e) { + Toast.makeText(this, R.string.activity_not_available, Toast.LENGTH_SHORT).show(); + } + } + + @Override + public void placeVoiceCall(String phoneNumber, String postDialDigits) { + Logger.get(this).logImpression(DialerImpression.Type.CALL_DETAILS_VOICE_CALL_BACK); + + boolean canSupportedAssistedDialing = + getIntent().getExtras().getBoolean(EXTRA_CAN_SUPPORT_ASSISTED_DIALING, false); + CallIntentBuilder callIntentBuilder = + new CallIntentBuilder(phoneNumber + postDialDigits, CallInitiationType.Type.CALL_DETAILS); + if (canSupportedAssistedDialing) { + callIntentBuilder.setAllowAssistedDial( + true, + ConcreteCreator.createNewAssistedDialingMediator( + getSystemService(TelephonyManager.class), this)); + } + + DialerUtils.startActivityWithErrorToast(this, callIntentBuilder.build()); + } + @NonNull private Map> getAllHistoricalData( @Nullable String number, @NonNull CallDetailsEntries entries) { diff --git a/java/com/android/dialer/calldetails/CallDetailsAdapter.java b/java/com/android/dialer/calldetails/CallDetailsAdapter.java index 645587461..1f00d9d9a 100644 --- a/java/com/android/dialer/calldetails/CallDetailsAdapter.java +++ b/java/com/android/dialer/calldetails/CallDetailsAdapter.java @@ -23,7 +23,10 @@ import android.support.v7.widget.RecyclerView.ViewHolder; import android.view.LayoutInflater; import android.view.ViewGroup; import com.android.dialer.calldetails.CallDetailsEntries.CallDetailsEntry; +import com.android.dialer.calldetails.CallDetailsHeaderViewHolder.CallbackActionListener; import com.android.dialer.calllogutils.CallTypeHelper; +import com.android.dialer.calllogutils.CallbackActionHelper; +import com.android.dialer.calllogutils.CallbackActionHelper.CallbackAction; import com.android.dialer.common.Assert; import com.android.dialer.dialercontact.DialerContact; import com.android.dialer.lightbringer.LightbringerComponent; @@ -37,7 +40,8 @@ final class CallDetailsAdapter extends RecyclerView.Adapter callDetailsEntries; @@ -45,10 +49,12 @@ final class CallDetailsAdapter extends RecyclerView.Adapter callDetailsEntries, - CallDetailsFooterViewHolder.ReportCallIdListener listener) { + CallbackActionListener callbackActionListener, + CallDetailsFooterViewHolder.ReportCallIdListener reportCallIdListener) { this.contact = Assert.isNotNull(contact); this.callDetailsEntries = callDetailsEntries; - this.listener = listener; + this.callbackActionListener = callbackActionListener; + this.reportCallIdListener = reportCallIdListener; callTypeHelper = new CallTypeHelper( context.getResources(), LightbringerComponent.get(context).getLightbringer()); @@ -60,13 +66,13 @@ final class CallDetailsAdapter extends RecyclerView.Adapter Date: Tue, 10 Oct 2017 12:15:08 -0700 Subject: Implement SIM swapping When the call is still ringing, a new button is added to allow to user to call with the other SIM. A background worker will be created to hang up the phone and redial with the other SIM. The in call UI will be prevented from ending during the process. Video: https://drive.google.com/a/google.com/file/d/0B2eYBUUznfyTSl9MdXQ0V1ZzQkE/view?usp=sharing UX has not been finalized, the icon and position are just placeholder. Bug: 64215256 Test: SwapSimWorkerTest PiperOrigin-RevId: 171715715 Change-Id: Idb3f486e9fc9a45d4c5e244af2d7d91b075bf0f2 --- java/com/android/dialer/telecom/TelecomUtil.java | 23 +++ java/com/android/incallui/CallButtonPresenter.java | 28 +++ .../incallui/callpending/CallPendingActivity.java | 3 + .../incallui/commontheme/res/values/strings.xml | 2 + .../incallui/incall/impl/ButtonChooserFactory.java | 1 + .../incallui/incall/impl/ButtonController.java | 17 ++ .../incallui/incall/impl/InCallFragment.java | 4 +- .../incallui/incall/impl/res/values/strings.xml | 4 + .../incallui/incall/protocol/InCallButtonIds.java | 4 +- .../incall/protocol/InCallButtonIdsExtension.java | 2 + .../incall/protocol/InCallButtonUiDelegate.java | 2 + .../android/incallui/multisim/SwapSimWorker.java | 202 +++++++++++++++++++++ 12 files changed, 290 insertions(+), 2 deletions(-) create mode 100644 java/com/android/incallui/multisim/SwapSimWorker.java diff --git a/java/com/android/dialer/telecom/TelecomUtil.java b/java/com/android/dialer/telecom/TelecomUtil.java index 8ff4b3967..3bf9b4666 100644 --- a/java/com/android/dialer/telecom/TelecomUtil.java +++ b/java/com/android/dialer/telecom/TelecomUtil.java @@ -17,6 +17,7 @@ package com.android.dialer.telecom; import android.Manifest; +import android.Manifest.permission; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -24,7 +25,9 @@ import android.net.Uri; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.provider.CallLog.Calls; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.annotation.RequiresPermission; import android.support.annotation.VisibleForTesting; import android.support.v4.content.ContextCompat; import android.telecom.PhoneAccount; @@ -234,6 +237,26 @@ public abstract class TelecomUtil { return instance.isDefaultDialer(context); } + /** @return the other SIM based PhoneAccountHandle that is not {@code currentAccount} */ + @Nullable + @RequiresPermission(permission.READ_PHONE_STATE) + @SuppressWarnings("MissingPermission") + public static PhoneAccountHandle getOtherAccount( + @NonNull Context context, @Nullable PhoneAccountHandle currentAccount) { + if (currentAccount == null) { + return null; + } + TelecomManager telecomManager = context.getSystemService(TelecomManager.class); + for (PhoneAccountHandle phoneAccountHandle : telecomManager.getCallCapablePhoneAccounts()) { + PhoneAccount phoneAccount = telecomManager.getPhoneAccount(phoneAccountHandle); + if (phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION) + && !phoneAccountHandle.equals(currentAccount)) { + return phoneAccountHandle; + } + } + return null; + } + /** Contains an implementation for {@link TelecomUtil} methods */ @VisibleForTesting() public static class TelecomUtilImpl { diff --git a/java/com/android/incallui/CallButtonPresenter.java b/java/com/android/incallui/CallButtonPresenter.java index bd5bb78c9..b3fb97fad 100644 --- a/java/com/android/incallui/CallButtonPresenter.java +++ b/java/com/android/incallui/CallButtonPresenter.java @@ -22,11 +22,14 @@ import android.os.Trace; import android.support.v4.app.Fragment; import android.support.v4.os.UserManagerCompat; import android.telecom.CallAudioState; +import android.telecom.PhoneAccountHandle; import com.android.contacts.common.compat.CallCompat; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; +import com.android.dialer.common.concurrent.DialerExecutorComponent; import com.android.dialer.logging.DialerImpression; import com.android.dialer.logging.Logger; +import com.android.dialer.telecom.TelecomUtil; import com.android.incallui.InCallCameraManager.Listener; import com.android.incallui.InCallPresenter.CanAddCallListener; import com.android.incallui.InCallPresenter.InCallDetailsListener; @@ -42,6 +45,7 @@ import com.android.incallui.call.TelecomAdapter; import com.android.incallui.incall.protocol.InCallButtonIds; import com.android.incallui.incall.protocol.InCallButtonUi; import com.android.incallui.incall.protocol.InCallButtonUiDelegate; +import com.android.incallui.multisim.SwapSimWorker; import com.android.incallui.videotech.utils.VideoUtils; /** Logic for call buttons. */ @@ -63,6 +67,7 @@ public class CallButtonPresenter private boolean mAutomaticallyMuted = false; private boolean mPreviousMuteState = false; private boolean isInCallButtonUiReady; + private PhoneAccountHandle mOtherAccount; public CallButtonPresenter(Context context) { mContext = context.getApplicationContext(); @@ -310,6 +315,23 @@ public class CallButtonPresenter mInCallButtonUi.showAudioRouteSelector(); } + @Override + public void swapSimClicked() { + LogUtil.enterBlock("CallButtonPresenter.swapSimClicked"); + SwapSimWorker worker = + new SwapSimWorker( + getContext(), + mCall, + InCallPresenter.getInstance().getCallList(), + mOtherAccount, + InCallPresenter.getInstance().acquireInCallUiLock("swapSim")); + DialerExecutorComponent.get(getContext()) + .dialerExecutorFactory() + .createNonUiTaskBuilder(worker) + .build() + .executeParallel(null); + } + /** * Switches the camera between the front-facing and back-facing camera. * @@ -409,6 +431,7 @@ public class CallButtonPresenter * * @param call The active call. */ + @SuppressWarnings("MissingPermission") private void updateButtonsState(DialerCall call) { LogUtil.v("CallButtonPresenter.updateButtonsState", ""); final boolean isVideo = call.isVideoCall(); @@ -439,11 +462,15 @@ public class CallButtonPresenter && call.getState() != DialerCall.State.DIALING && call.getState() != DialerCall.State.CONNECTING; + mOtherAccount = TelecomUtil.getOtherAccount(getContext(), call.getAccountHandle()); + boolean showSwapSim = mOtherAccount != null && DialerCall.State.isDialing(call.getState()); + mInCallButtonUi.showButton(InCallButtonIds.BUTTON_AUDIO, true); mInCallButtonUi.showButton(InCallButtonIds.BUTTON_SWAP, showSwap); mInCallButtonUi.showButton(InCallButtonIds.BUTTON_HOLD, showHold); mInCallButtonUi.setHold(isCallOnHold); mInCallButtonUi.showButton(InCallButtonIds.BUTTON_MUTE, showMute); + mInCallButtonUi.showButton(InCallButtonIds.BUTTON_SWAP_SIM, showSwapSim); mInCallButtonUi.showButton(InCallButtonIds.BUTTON_ADD_CALL, true); mInCallButtonUi.enableButton(InCallButtonIds.BUTTON_ADD_CALL, showAddCall); mInCallButtonUi.showButton(InCallButtonIds.BUTTON_UPGRADE_TO_VIDEO, showUpgradeToVideo); @@ -532,4 +559,5 @@ public class CallButtonPresenter } return null; } + } diff --git a/java/com/android/incallui/callpending/CallPendingActivity.java b/java/com/android/incallui/callpending/CallPendingActivity.java index 7fc4caf5a..47325d823 100644 --- a/java/com/android/incallui/callpending/CallPendingActivity.java +++ b/java/com/android/incallui/callpending/CallPendingActivity.java @@ -278,6 +278,9 @@ public class CallPendingActivity extends FragmentActivity @Override public void showAudioRouteSelector() {} + @Override + public void swapSimClicked() {} + @Override public Context getContext() { return CallPendingActivity.this; diff --git a/java/com/android/incallui/commontheme/res/values/strings.xml b/java/com/android/incallui/commontheme/res/values/strings.xml index 94a8c901b..f366a867f 100644 --- a/java/com/android/incallui/commontheme/res/values/strings.xml +++ b/java/com/android/incallui/commontheme/res/values/strings.xml @@ -24,6 +24,8 @@ Swap calls + Change SIM + Merge calls Handset earpiece diff --git a/java/com/android/incallui/incall/impl/ButtonChooserFactory.java b/java/com/android/incallui/incall/impl/ButtonChooserFactory.java index 0f4a95d38..0d0a93256 100644 --- a/java/com/android/incallui/incall/impl/ButtonChooserFactory.java +++ b/java/com/android/incallui/incall/impl/ButtonChooserFactory.java @@ -112,6 +112,7 @@ class ButtonChooserFactory { mapping.put(InCallButtonIds.BUTTON_AUDIO, MappingInfo.builder(2).build()); mapping.put(InCallButtonIds.BUTTON_MERGE, MappingInfo.builder(3).setSlotOrder(0).build()); mapping.put(InCallButtonIds.BUTTON_ADD_CALL, MappingInfo.builder(3).build()); + mapping.put(InCallButtonIds.BUTTON_SWAP_SIM, MappingInfo.builder(4).build()); return mapping; } } diff --git a/java/com/android/incallui/incall/impl/ButtonController.java b/java/com/android/incallui/incall/impl/ButtonController.java index b7a47f08e..cefbd723b 100644 --- a/java/com/android/incallui/incall/impl/ButtonController.java +++ b/java/com/android/incallui/incall/impl/ButtonController.java @@ -560,4 +560,21 @@ interface ButtonController { inCallScreenDelegate.onSecondaryInfoClicked(); } } + + class SwapSimButtonController extends SimpleNonCheckableButtonController { + + public SwapSimButtonController(InCallButtonUiDelegate delegate) { + super( + delegate, + InCallButtonIds.BUTTON_SWAP_SIM, + R.string.incall_content_description_swap_sim, + R.string.incall_label_swap_sim, + R.drawable.quantum_ic_swap_calls_white_36); + } + + @Override + public void onClick(View view) { + delegate.swapSimClicked(); + } + } } diff --git a/java/com/android/incallui/incall/impl/InCallFragment.java b/java/com/android/incallui/incall/impl/InCallFragment.java index 13175656d..f0504bc56 100644 --- a/java/com/android/incallui/incall/impl/InCallFragment.java +++ b/java/com/android/incallui/incall/impl/InCallFragment.java @@ -110,7 +110,8 @@ public class InCallFragment extends Fragment || id == InCallButtonIds.BUTTON_UPGRADE_TO_VIDEO || id == InCallButtonIds.BUTTON_ADD_CALL || id == InCallButtonIds.BUTTON_MERGE - || id == InCallButtonIds.BUTTON_MANAGE_VOICE_CONFERENCE; + || id == InCallButtonIds.BUTTON_MANAGE_VOICE_CONFERENCE + || id == InCallButtonIds.BUTTON_SWAP_SIM; } @Override @@ -198,6 +199,7 @@ public class InCallFragment extends Fragment buttonControllers.add(new ButtonController.AddCallButtonController(inCallButtonUiDelegate)); buttonControllers.add(new ButtonController.SwapButtonController(inCallButtonUiDelegate)); buttonControllers.add(new ButtonController.MergeButtonController(inCallButtonUiDelegate)); + buttonControllers.add(new ButtonController.SwapSimButtonController(inCallButtonUiDelegate)); buttonControllers.add( new ButtonController.UpgradeToVideoButtonController(inCallButtonUiDelegate)); buttonControllers.add( diff --git a/java/com/android/incallui/incall/impl/res/values/strings.xml b/java/com/android/incallui/incall/impl/res/values/strings.xml index 2b30dfa53..d0217566a 100644 --- a/java/com/android/incallui/incall/impl/res/values/strings.xml +++ b/java/com/android/incallui/incall/impl/res/values/strings.xml @@ -61,6 +61,10 @@ Swap + + Change SIM + Note sent diff --git a/java/com/android/incallui/incall/protocol/InCallButtonIds.java b/java/com/android/incallui/incall/protocol/InCallButtonIds.java index 50ebc6413..3de533519 100644 --- a/java/com/android/incallui/incall/protocol/InCallButtonIds.java +++ b/java/com/android/incallui/incall/protocol/InCallButtonIds.java @@ -37,6 +37,7 @@ import java.lang.annotation.RetentionPolicy; InCallButtonIds.BUTTON_MANAGE_VIDEO_CONFERENCE, InCallButtonIds.BUTTON_MANAGE_VOICE_CONFERENCE, InCallButtonIds.BUTTON_SWITCH_TO_SECONDARY, + InCallButtonIds.BUTTON_SWAP_SIM, InCallButtonIds.BUTTON_COUNT, }) public @interface InCallButtonIds { @@ -55,5 +56,6 @@ public @interface InCallButtonIds { int BUTTON_MANAGE_VIDEO_CONFERENCE = 11; int BUTTON_MANAGE_VOICE_CONFERENCE = 12; int BUTTON_SWITCH_TO_SECONDARY = 13; - int BUTTON_COUNT = 14; + int BUTTON_SWAP_SIM = 14; + int BUTTON_COUNT = 15; } diff --git a/java/com/android/incallui/incall/protocol/InCallButtonIdsExtension.java b/java/com/android/incallui/incall/protocol/InCallButtonIdsExtension.java index 6d802e346..db6e9009c 100644 --- a/java/com/android/incallui/incall/protocol/InCallButtonIdsExtension.java +++ b/java/com/android/incallui/incall/protocol/InCallButtonIdsExtension.java @@ -54,6 +54,8 @@ public class InCallButtonIdsExtension { return "MANAGE_VOICE_CONFERENCE"; } else if (id == InCallButtonIds.BUTTON_SWITCH_TO_SECONDARY) { return "SWITCH_TO_SECONDARY"; + } else if (id == InCallButtonIds.BUTTON_SWAP_SIM) { + return "SWAP_SIM"; } else { return "INVALID_BUTTON: " + id; } diff --git a/java/com/android/incallui/incall/protocol/InCallButtonUiDelegate.java b/java/com/android/incallui/incall/protocol/InCallButtonUiDelegate.java index e02ada96d..9f9c5fb03 100644 --- a/java/com/android/incallui/incall/protocol/InCallButtonUiDelegate.java +++ b/java/com/android/incallui/incall/protocol/InCallButtonUiDelegate.java @@ -63,5 +63,7 @@ public interface InCallButtonUiDelegate { void showAudioRouteSelector(); + void swapSimClicked(); + Context getContext(); } diff --git a/java/com/android/incallui/multisim/SwapSimWorker.java b/java/com/android/incallui/multisim/SwapSimWorker.java new file mode 100644 index 000000000..73c18c442 --- /dev/null +++ b/java/com/android/incallui/multisim/SwapSimWorker.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.incallui.multisim; + +import android.content.Context; +import android.net.Uri; +import android.os.Bundle; +import android.support.annotation.MainThread; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; +import android.support.annotation.WorkerThread; +import android.telecom.PhoneAccount; +import android.telecom.PhoneAccountHandle; +import android.telecom.TelecomManager; +import com.android.dialer.common.Assert; +import com.android.dialer.common.LogUtil; +import com.android.dialer.common.concurrent.DialerExecutor.Worker; +import com.android.dialer.common.concurrent.ThreadUtil; +import com.android.dialer.util.PermissionsUtil; +import com.android.incallui.call.CallList; +import com.android.incallui.call.DialerCall; +import com.android.incallui.call.DialerCallListener; +import com.android.incallui.incalluilock.InCallUiLock; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Hangs up the current call and redial the call using the {@code otherAccount} instead. the in call + * ui will be prevented from closing until the process has finished. + */ +public class SwapSimWorker implements Worker, DialerCallListener, CallList.Listener { + + // Timeout waiting for the call to hangup or redial. + private static final int DEFAULT_TIMEOUT_MILLIS = 5_000; + + private final Context context; + private final DialerCall call; + private final CallList callList; + private final InCallUiLock inCallUiLock; + + private final CountDownLatch disconnectLatch = new CountDownLatch(1); + private final CountDownLatch dialingLatch = new CountDownLatch(1); + + private final PhoneAccountHandle otherAccount; + private final String number; + + private final int timeoutMillis; + + private CountDownLatch latchForTest; + + @MainThread + public SwapSimWorker( + Context context, + DialerCall call, + CallList callList, + PhoneAccountHandle otherAccount, + InCallUiLock lock) { + this(context, call, callList, otherAccount, lock, DEFAULT_TIMEOUT_MILLIS); + } + + @VisibleForTesting + SwapSimWorker( + Context context, + DialerCall call, + CallList callList, + PhoneAccountHandle otherAccount, + InCallUiLock lock, + int timeoutMillis) { + Assert.isMainThread(); + this.context = context; + this.call = call; + this.callList = callList; + this.otherAccount = otherAccount; + inCallUiLock = lock; + this.timeoutMillis = timeoutMillis; + number = call.getNumber(); + call.addListener(this); + call.disconnect(); + } + + @WorkerThread + @Nullable + @Override + @SuppressWarnings("MissingPermission") + public Void doInBackground(Void unused) { + try { + if (!PermissionsUtil.hasPhonePermissions(context)) { + LogUtil.e("SwapSimWorker.doInBackground", "missing phone permission"); + return null; + } + if (!disconnectLatch.await(timeoutMillis, TimeUnit.MILLISECONDS)) { + LogUtil.e("SwapSimWorker.doInBackground", "timeout waiting for call to disconnect"); + return null; + } + LogUtil.i("SwapSimWorker.doInBackground", "call disconnected, redialing"); + TelecomManager telecomManager = context.getSystemService(TelecomManager.class); + Bundle extras = new Bundle(); + extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, otherAccount); + callList.addListener(this); + telecomManager.placeCall(Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null), extras); + if (latchForTest != null) { + latchForTest.countDown(); + } + if (!dialingLatch.await(timeoutMillis, TimeUnit.MILLISECONDS)) { + LogUtil.e("SwapSimWorker.doInBackground", "timeout waiting for call to dial"); + } + return null; + } catch (InterruptedException e) { + LogUtil.e("SwapSimWorker.doInBackground", "interrupted", e); + Thread.currentThread().interrupt(); + return null; + } finally { + ThreadUtil.postOnUiThread( + () -> { + call.removeListener(this); + callList.removeListener(this); + inCallUiLock.release(); + }); + } + } + + @MainThread + @Override + public void onDialerCallDisconnect() { + disconnectLatch.countDown(); + } + + @Override + public void onCallListChange(CallList callList) { + if (callList.getOutgoingCall() != null) { + dialingLatch.countDown(); + } + } + + @VisibleForTesting + void setLatchForTest(CountDownLatch latch) { + latchForTest = latch; + } + + @Override + public void onDialerCallUpdate() {} + + @Override + public void onDialerCallChildNumberChange() {} + + @Override + public void onDialerCallLastForwardedNumberChange() {} + + @Override + public void onDialerCallUpgradeToVideo() {} + + @Override + public void onDialerCallSessionModificationStateChange() {} + + @Override + public void onWiFiToLteHandover() {} + + @Override + public void onHandoverToWifiFailure() {} + + @Override + public void onInternationalCallOnWifi() {} + + @Override + public void onEnrichedCallSessionUpdate() {} + + @Override + public void onIncomingCall(DialerCall call) {} + + @Override + public void onUpgradeToVideo(DialerCall call) {} + + @Override + public void onSessionModificationStateChange(DialerCall call) {} + + @Override + public void onDisconnect(DialerCall call) {} + + @Override + public void onWiFiToLteHandover(DialerCall call) {} + + @Override + public void onHandoverToWifiFailed(DialerCall call) {} + + @Override + public void onInternationalCallOnWifi(@NonNull DialerCall call) {} +} -- cgit v1.2.3