summaryrefslogtreecommitdiff
path: root/java
diff options
context:
space:
mode:
authortwyen <twyen@google.com>2017-10-10 12:15:08 -0700
committerEric Erfanian <erfanian@google.com>2017-10-11 07:06:24 -0700
commit00623aa60a7908b0709df38632cfa576cb15e33e (patch)
tree35bbae5a10d673a11eec6f20954305bc2eb9aa4f /java
parent7b96fb1690255f13591edba01770d2d82c197194 (diff)
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
Diffstat (limited to 'java')
-rw-r--r--java/com/android/dialer/telecom/TelecomUtil.java23
-rw-r--r--java/com/android/incallui/CallButtonPresenter.java28
-rw-r--r--java/com/android/incallui/callpending/CallPendingActivity.java3
-rw-r--r--java/com/android/incallui/commontheme/res/values/strings.xml2
-rw-r--r--java/com/android/incallui/incall/impl/ButtonChooserFactory.java1
-rw-r--r--java/com/android/incallui/incall/impl/ButtonController.java17
-rw-r--r--java/com/android/incallui/incall/impl/InCallFragment.java4
-rw-r--r--java/com/android/incallui/incall/impl/res/values/strings.xml4
-rw-r--r--java/com/android/incallui/incall/protocol/InCallButtonIds.java4
-rw-r--r--java/com/android/incallui/incall/protocol/InCallButtonIdsExtension.java2
-rw-r--r--java/com/android/incallui/incall/protocol/InCallButtonUiDelegate.java2
-rw-r--r--java/com/android/incallui/multisim/SwapSimWorker.java202
12 files changed, 290 insertions, 2 deletions
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
@@ -279,6 +279,9 @@ public class CallPendingActivity extends FragmentActivity
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 @@
<string name="incall_content_description_swap_calls">Swap calls</string>
+ <string name="incall_content_description_swap_sim">Change SIM</string>
+
<string name="incall_content_description_merge_calls">Merge calls</string>
<string name="incall_content_description_earpiece">Handset earpiece</string>
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 @@
<string name="incall_label_swap">Swap</string>
+ <!-- Button shown during a call with a multi-SIM device to hang up and call with the other SIM
+ instead. [CHAR LIMIT=12] -->
+ <string name="incall_label_swap_sim">Change SIM</string>
+
<!-- Used to inform the user that the note associated with an outgoing call has been sent.
[CHAR LIMIT=32] -->
<string name="incall_note_sent">Note sent</string>
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<Void, Void>, 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) {}
+}