[SIM Dialog Migration] Migrate SIM enable dialog from LPA to Settings
Migrates SIM enabling and DSDS dialog from LPA to Settings.
Design: https://docs.google.com/document/d/1wb5_hoBkZVbkXGNWHbx4Jf61swjfxsJzkytiTzJosYo/edit?usp=sharing
Bug: 160819390
Test: Manually tested eSIM profile enabling.
Change-Id: I9ce690a1594adfc90b62840819237acd54418469
diff --git a/res/values/strings.xml b/res/values/strings.xml
index acc7f51..2861d52 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -11975,6 +11975,30 @@
<string name="see_less">See less</string>
<!-- Strings for toggling subscriptions dialog activity -->
+ <!-- Title of confirmation dialog asking the user if they want to enable subscription. [CHAR_LIMIT=NONE] -->
+ <string name="sim_action_enable_sub_dialog_title">Turn on <xliff:g id="carrier_name" example="Google Fi">%1$s</xliff:g>?</string>
+ <!-- Title of confirmation dialog asking the user if they want to enable subscription without the subscription name. [CHAR_LIMIT=NONE] -->
+ <string name="sim_action_enable_sub_dialog_title_without_carrier_name">Turn on SIM?</string>
+ <!-- Title of confirmation dialog asking the user if they want to switch subscription. [CHAR_LIMIT=NONE] -->
+ <string name="sim_action_switch_sub_dialog_title">Switch to <xliff:g id="carrier_name" example="Google Fi">%1$s</xliff:g>?</string>
+ <!-- Title of confirmation dialog asking the user if they want to switch to the SIM card. [CHAR_LIMIT=NONE] -->
+ <string name="sim_action_switch_psim_dialog_title">Switch to using SIM card?</string>
+ <!-- Body text of confirmation dialog for switching subscription that involves switching SIM slots. Indicates that only one SIM can be active at a time. Also that switching will not cancel the user's mobile service plan. [CHAR_LIMIT=NONE] -->
+ <string name="sim_action_switch_sub_dialog_text">Only one SIM can be active at a time.\n\nSwitching to <xliff:g id="to_carrier_name" example="Google Fi">%1$s</xliff:g> won\u2019t cancel your <xliff:g id="from_carrier_name" example="Sprint">%2$s</xliff:g> service.</string>
+ <!-- Body text of confirmation dialog for switching subscription between two eSIM profiles. Indicates that only one downloaded SIM can be active at a time. Also that switching will not cancel the user's mobile service plan. [CHAR_LIMIT=NONE] -->
+ <string name="sim_action_switch_sub_dialog_text_downloaded">Only one downloaded SIM can be active at a time.\n\nSwitching to <xliff:g id="to_carrier_name" example="Google Fi">%1$s</xliff:g> won\u2019t cancel your <xliff:g id="from_carrier_name" example="Sprint">%2$s</xliff:g> service.</string>
+ <!-- Body text of confirmation dialog for switching subscription between two eSIM profiles. Indicates that only one SIM can be active at a time. Also that switching will not cancel the user's mobile service plan. [CHAR_LIMIT=NONE] -->
+ <string name="sim_action_switch_sub_dialog_text_single_sim">Only one SIM can be active at a time.\n\nSwitching won\u2019t cancel your <xliff:g id="to_carrier_name" example="Google Fi">%1$s</xliff:g> service.</string>
+ <!-- Text of confirm button in the confirmation dialog asking the user if they want to switch subscription. [CHAR_LIMIT=NONE] -->
+ <string name="sim_action_switch_sub_dialog_confirm">Switch to <xliff:g id="carrier_name" example="Google Fi">%1$s</xliff:g></string>
+ <!-- Status message indicating the device is in the process of disconnecting from one mobile network and immediately connecting to another. [CHAR_LIMIT=NONE] -->
+ <string name="sim_action_enabling_sim_without_carrier_name">Connecting to network…</string>
+ <!-- Text of progress dialog indicating the subscription switch is in progress. [CHAR_LIMIT=NONE] -->
+ <string name="sim_action_switch_sub_dialog_progress">Switching to <xliff:g id="carrier_name" example="Google Fi">%1$s</xliff:g></string>
+ <!-- Title of error message indicating that the device could not disconnect from one mobile network and immediately connect to another. [CHAR_LIMIT=NONE] -->
+ <string name="sim_action_enable_sim_fail_title">Can\u2019t switch carrier</string>
+ <!-- Body text of error message indicating the device could not disconnect from one mobile network and immediately connect to another, due to an unspecified issue. [CHAR_LIMIT=NONE] -->
+ <string name="sim_action_enable_sim_fail_text">The carrier can\u2019t be switched due to an error.</string>
<!-- Title of confirmation dialog asking the user if they want to disable subscription. [CHAR_LIMIT=NONE] -->
<string name="privileged_action_disable_sub_dialog_title">Turn off <xliff:g id="carrier_name" example="Google Fi">%1$s</xliff:g>?</string>
<!-- Title of confirmation dialog asking the user if they want to disable subscription. [CHAR_LIMIT=NONE] -->
@@ -11982,9 +12006,31 @@
<!-- Disabling SIMs progress dialog message [CHAR LIMIT=NONE] -->
<string name="privileged_action_disable_sub_dialog_progress">Turning off SIM<xliff:g id="ellipsis" example="...">…</xliff:g></string>
<!-- Title of error messaging indicating the device could not disable the mobile network. [CHAR LIMIT=NONE] -->
- <string name="privileged_action_disable_fail_title">Can\'t disable carrier</string>
+ <string name="privileged_action_disable_fail_title">Can\u2019t disable carrier</string>
<!-- Body text of error message indicating the device could not disable the mobile network, due to an unknown issue. [CHAR LIMIT=NONE] -->
<string name="privileged_action_disable_fail_text">Something went wrong and your carrier could not be disabled.</string>
+ <!-- Title on a dialog asking the users whether they want to enable DSDS mode. [CHAR LIMIT=NONE] -->
+ <string name="sim_action_enable_dsds_title">Use 2 SIMs?</string>
+ <!-- Message in a dialog indicating the user can enable DSDS mode. [CHAR LIMIT=NONE] -->
+ <string name="sim_action_enable_dsds_text">This device can have 2 SIMs active at once. To continue using 1 SIM at a time, tap \"No thanks\".</string>
+ <!-- Ask the user whether to restart device. [CHAR LIMIT=NONE] -->
+ <string name="sim_action_restart_title">Restart device?</string>
+ <!-- Tell the user that in order to enable DSDS mode, the phone needs to restart. [CHAR LIMIT=NONE] -->
+ <string name="sim_action_restart_text">To get started, restart your device. Then you can add another SIM.</string>
+ <!-- Button on a dialog to confirm SIM operations. [CHAR LIMIT=30] -->
+ <string name="sim_action_continue">Continue</string>
+ <!-- User confirms reboot the phone. [CHAR LIMIT=30] -->
+ <string name="sim_action_reboot">Restart</string>
+ <!-- Button on a dialog to reject SIM operations. [CHAR LIMIT=30] -->
+ <string name="sim_action_no_thanks">No thanks</string>
+ <!-- Button which will disconnect the user from one mobile network and immediately connect to another. [CHAR LIMIT=30] -->
+ <string name="sim_switch_button">Switch</string>
+ <!-- Title of DSDS activation failure dialog [CHAR LIMIT=40] -->
+ <string name="dsds_activation_failure_title">Can\u2019t activate SIM</string>
+ <!-- Body text of DSDS activation failure dialog. Users could reinsert the SIM card or reboot to recover. [CHAR LIMIT=NONE] -->
+ <string name="dsds_activation_failure_body_msg1">Remove the SIM and insert it again. If the problem continues, restart your device.</string>
+ <!-- Body text of DSDS activation failure dialog. Users could toggle the selected SIM again or reboot to recover. [CHAR LIMIT=NONE] -->
+ <string name="dsds_activation_failure_body_msg2">Try turning on the SIM again. If the problem continues, restart your device.</string>
<!-- Strings for deleting eUICC subscriptions dialog activity -->
<!-- Title on confirmation dialog asking the user if they want to erase the downloaded SIM from the device. [CHAR_LIMIT=NONE] -->
diff --git a/src/com/android/settings/AsyncTaskSidecar.java b/src/com/android/settings/AsyncTaskSidecar.java
new file mode 100644
index 0000000..31c8298
--- /dev/null
+++ b/src/com/android/settings/AsyncTaskSidecar.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2020 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.settings;
+
+import android.os.AsyncTask;
+
+import androidx.annotation.Nullable;
+
+import com.android.settingslib.utils.ThreadUtils;
+
+import java.util.concurrent.Future;
+
+/** A {@link SidecarFragment} which uses an {@link AsyncTask} to perform background work. */
+public abstract class AsyncTaskSidecar<Param, Result> extends SidecarFragment {
+
+ private Future<Result> mAsyncTask;
+
+ @Override
+ public void onDestroy() {
+ if (mAsyncTask != null) {
+ mAsyncTask.cancel(true /* mayInterruptIfRunning */);
+ }
+
+ super.onDestroy();
+ }
+
+ /**
+ * Executes the background task.
+ *
+ * @param param parameters passed in from {@link #run}
+ */
+ protected abstract Result doInBackground(@Nullable Param param);
+
+ /** Handles the background task's result. */
+ protected void onPostExecute(Result result) {}
+
+ /** Runs the sidecar and sets the state to RUNNING. */
+ public void run(@Nullable final Param param) {
+ setState(State.RUNNING, Substate.UNUSED);
+
+ if (mAsyncTask != null) {
+ mAsyncTask.cancel(true /* mayInterruptIfRunning */);
+ }
+
+ mAsyncTask =
+ ThreadUtils.postOnBackgroundThread(
+ () -> {
+ Result result = doInBackground(param);
+ ThreadUtils.postOnMainThread(() -> onPostExecute(result));
+ });
+ }
+}
diff --git a/src/com/android/settings/network/CarrierConfigChangedReceiver.java b/src/com/android/settings/network/CarrierConfigChangedReceiver.java
new file mode 100644
index 0000000..8a6d47d
--- /dev/null
+++ b/src/com/android/settings/network/CarrierConfigChangedReceiver.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2020 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.settings.network;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.telephony.CarrierConfigManager;
+import android.util.Log;
+
+import java.util.concurrent.CountDownLatch;
+
+/** A receiver listens to the carrier config changes. */
+public class CarrierConfigChangedReceiver extends BroadcastReceiver {
+ private static final String TAG = "CarrierConfigChangedReceiver";
+ private static final String ACTION_CARRIER_CONFIG_CHANGED =
+ CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
+
+ private final CountDownLatch mLatch;
+
+ public CarrierConfigChangedReceiver(CountDownLatch latch) {
+ mLatch = latch;
+ }
+
+ public void registerOn(Context context) {
+ context.registerReceiver(this, new IntentFilter(ACTION_CARRIER_CONFIG_CHANGED));
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (isInitialStickyBroadcast()) {
+ return;
+ }
+
+ if (ACTION_CARRIER_CONFIG_CHANGED.equals(intent.getAction())) {
+ checkSubscriptionIndex(intent);
+ }
+ }
+
+ private void checkSubscriptionIndex(Intent intent) {
+ if (intent.hasExtra(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX)) {
+ int subId = intent.getIntExtra(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX, -1);
+ Log.i(TAG, "subId from config changed: " + subId);
+ mLatch.countDown();
+ }
+ }
+}
diff --git a/src/com/android/settings/network/EnableMultiSimSidecar.java b/src/com/android/settings/network/EnableMultiSimSidecar.java
new file mode 100644
index 0000000..c47e61a
--- /dev/null
+++ b/src/com/android/settings/network/EnableMultiSimSidecar.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2020 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.settings.network;
+
+import android.app.FragmentManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.telephony.CarrierConfigManager;
+import android.telephony.TelephonyManager;
+import android.telephony.UiccSlotInfo;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.settings.AsyncTaskSidecar;
+import com.android.settings.SidecarFragment;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * {@code EnableMultiSimSidecar} enables multi SIM on the device. It should only be called for
+ * Android R+. After {@code run} is called, it sets the configuration on modem side to enable
+ * multiple SIMs. Once the configuration is set successfully, it will listen to UICC card changes
+ * until {@code TelMan.EXTRA_ACTIVE_SIM_SUPPORTED_COUNT} matches {@code mNumOfActiveSim} or timeout.
+ */
+public class EnableMultiSimSidecar extends AsyncTaskSidecar<Void, Boolean> {
+
+ // Tags
+ private static final String TAG = "EnableMultiSimSidecar";
+
+ // TODO(b/171846124): Pass timeout value from LPA to Settings
+ private static final long ENABLE_MULTI_SIM_TIMEOUT_MILLS = 40 * 1000L;
+
+ public static EnableMultiSimSidecar get(FragmentManager fm) {
+ return SidecarFragment.get(fm, TAG, EnableMultiSimSidecar.class, null /* args */);
+ }
+
+ final CountDownLatch mSimCardStateChangedLatch = new CountDownLatch(1);
+ private TelephonyManager mTelephonyManager;
+ private int mNumOfActiveSim = 0;
+
+ private final BroadcastReceiver mCarrierConfigChangeReceiver =
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ int readySimsCount = getReadySimsCount();
+ int activeSlotsCount = getActiveSlotsCount();
+ // If the number of ready SIM count and active slots equal to the number of SIMs
+ // need to be activated, the device is successfully switched to multiple active
+ // SIM mode.
+ if (readySimsCount == mNumOfActiveSim && activeSlotsCount == mNumOfActiveSim) {
+ Log.i(
+ TAG,
+ String.format("%d slots are active and ready.", mNumOfActiveSim));
+ mSimCardStateChangedLatch.countDown();
+ return;
+ }
+ Log.i(
+ TAG,
+ String.format(
+ "%d slots are active and %d SIMs are ready. Keep waiting until"
+ + " timeout.",
+ activeSlotsCount, readySimsCount));
+ }
+ };
+
+ @Override
+ protected Boolean doInBackground(Void aVoid) {
+ return updateMultiSimConfig();
+ }
+
+ @Override
+ protected void onPostExecute(Boolean isDsdsEnabled) {
+ if (isDsdsEnabled) {
+ setState(State.SUCCESS, Substate.UNUSED);
+ } else {
+ setState(State.ERROR, Substate.UNUSED);
+ }
+ }
+
+ public void run(int numberOfSimToActivate) {
+ mTelephonyManager = getContext().getSystemService(TelephonyManager.class);
+ mNumOfActiveSim = numberOfSimToActivate;
+
+ if (mNumOfActiveSim > mTelephonyManager.getSupportedModemCount()) {
+ Log.e(TAG, "Requested number of active SIM is greater than supported modem count.");
+ setState(State.ERROR, Substate.UNUSED);
+ return;
+ }
+ if (mTelephonyManager.doesSwitchMultiSimConfigTriggerReboot()) {
+ Log.e(TAG, "The device does not support reboot free DSDS.");
+ setState(State.ERROR, Substate.UNUSED);
+ return;
+ }
+ super.run(null /* param */);
+ }
+
+ // This method registers a ACTION_SIM_CARD_STATE_CHANGED broadcast receiver and wait for slot
+ // changes. If multi SIMs have been successfully enabled, it returns true. Otherwise, returns
+ // false.
+ private boolean updateMultiSimConfig() {
+ try {
+ getContext()
+ .registerReceiver(
+ mCarrierConfigChangeReceiver,
+ new IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
+ mTelephonyManager.switchMultiSimConfig(mNumOfActiveSim);
+ if (mSimCardStateChangedLatch.await(
+ ENABLE_MULTI_SIM_TIMEOUT_MILLS, TimeUnit.MILLISECONDS)) {
+ Log.i(TAG, "Multi SIM were successfully enabled.");
+ return true;
+ } else {
+ Log.e(TAG, "Timeout for waiting SIM status.");
+ return false;
+ }
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Failed to enable multiple SIM due to InterruptedException", e);
+ return false;
+ } finally {
+ getContext().unregisterReceiver(mCarrierConfigChangeReceiver);
+ }
+ }
+
+ // Returns how many SIMs have SIM ready state, not ready state, or removable slot with absent
+ // SIM state.
+ private int getReadySimsCount() {
+ int readyCardsCount = 0;
+ int activeSlotCount = mTelephonyManager.getActiveModemCount();
+ Set<Integer> activeRemovableLogicalSlots = getActiveRemovableLogicalSlotIds();
+ for (int logicalSlotId = 0; logicalSlotId < activeSlotCount; logicalSlotId++) {
+ int simState = mTelephonyManager.getSimState(logicalSlotId);
+ if (simState == TelephonyManager.SIM_STATE_READY
+ || simState == TelephonyManager.SIM_STATE_NOT_READY
+ || simState == TelephonyManager.SIM_STATE_LOADED
+ || (simState == TelephonyManager.SIM_STATE_ABSENT
+ && activeRemovableLogicalSlots.contains(logicalSlotId))) {
+ readyCardsCount++;
+ }
+ }
+ return readyCardsCount;
+ }
+
+ // Get active slots count from {@code TelephonyManager#getUiccSlotsInfo}.
+ private int getActiveSlotsCount() {
+ UiccSlotInfo[] slotsInfo = mTelephonyManager.getUiccSlotsInfo();
+ if (slotsInfo == null) {
+ return 0;
+ }
+ int activeSlots = 0;
+ for (UiccSlotInfo slotInfo : slotsInfo) {
+ if (slotInfo != null && slotInfo.getIsActive()) {
+ activeSlots++;
+ }
+ }
+ return activeSlots;
+ }
+
+ /** Returns a list of active removable logical slot ids. */
+ public Set<Integer> getActiveRemovableLogicalSlotIds() {
+ UiccSlotInfo[] infos = mTelephonyManager.getUiccSlotsInfo();
+ if (infos == null) {
+ return Collections.emptySet();
+ }
+ Set<Integer> activeRemovableLogicalSlotIds = new ArraySet<>();
+ for (UiccSlotInfo info : infos) {
+ if (info != null && info.getIsActive() && info.isRemovable()) {
+ activeRemovableLogicalSlotIds.add(info.getLogicalSlotIdx());
+ }
+ }
+ return activeRemovableLogicalSlotIds;
+ }
+}
diff --git a/src/com/android/settings/network/SwitchSlotSidecar.java b/src/com/android/settings/network/SwitchSlotSidecar.java
new file mode 100644
index 0000000..cffb23f
--- /dev/null
+++ b/src/com/android/settings/network/SwitchSlotSidecar.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2020 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.settings.network;
+
+import android.annotation.IntDef;
+import android.app.FragmentManager;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.android.settings.AsyncTaskSidecar;
+import com.android.settings.SidecarFragment;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.annotation.Nullable;
+
+/** {@link SidecarFragment} to switch SIM slot. */
+public class SwitchSlotSidecar
+ extends AsyncTaskSidecar<SwitchSlotSidecar.Param, SwitchSlotSidecar.Result> {
+ private static final String TAG = "SwitchSlotSidecar";
+
+ /** Commands */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ Command.SWITCH_TO_REMOVABLE_SIM,
+ })
+ private @interface Command {
+ int SWITCH_TO_REMOVABLE_SIM = 0;
+ }
+
+ static class Param {
+ @Command int command;
+ int slotId;
+ }
+
+ static class Result {
+ Exception exception;
+ }
+
+ /** Returns a SwitchSlotSidecar sidecar instance. */
+ public static SwitchSlotSidecar get(FragmentManager fm) {
+ return SidecarFragment.get(fm, TAG, SwitchSlotSidecar.class, null /* args */);
+ }
+
+ @Nullable private Exception mException;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+
+ /** Starts switching to the removable slot. */
+ public void runSwitchToRemovableSlot(int id) {
+ Param param = new Param();
+ param.command = Command.SWITCH_TO_REMOVABLE_SIM;
+ param.slotId = id;
+ super.run(param);
+ }
+
+ /**
+ * Returns the exception thrown during the execution of a command. Will be null in any state
+ * other than {@link State#SUCCESS}, and may be null in that state if there was not an error.
+ */
+ @Nullable
+ public Exception getException() {
+ return mException;
+ }
+
+ @Override
+ protected Result doInBackground(@Nullable Param param) {
+ Result result = new Result();
+ if (param == null) {
+ result.exception = new UiccSlotsException("Null param");
+ return result;
+ }
+ try {
+ switch (param.command) {
+ case Command.SWITCH_TO_REMOVABLE_SIM:
+ UiccSlotUtil.switchToRemovableSlot(param.slotId, getContext());
+ break;
+ default:
+ Log.e(TAG, "Wrong command.");
+ break;
+ }
+ } catch (UiccSlotsException e) {
+ result.exception = e;
+ }
+ return result;
+ }
+
+ @Override
+ protected void onPostExecute(Result result) {
+ if (result.exception == null) {
+ setState(State.SUCCESS, Substate.UNUSED);
+ } else {
+ mException = result.exception;
+ setState(State.ERROR, Substate.UNUSED);
+ }
+ }
+}
diff --git a/src/com/android/settings/network/SwitchToRemovableSlotSidecar.java b/src/com/android/settings/network/SwitchToRemovableSlotSidecar.java
new file mode 100644
index 0000000..132a2fd
--- /dev/null
+++ b/src/com/android/settings/network/SwitchToRemovableSlotSidecar.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2020 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.settings.network;
+
+import android.app.FragmentManager;
+import android.os.Bundle;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.util.Log;
+
+import com.android.settings.SidecarFragment;
+import com.android.settings.network.telephony.EuiccOperationSidecar;
+
+/**
+ * This sidecar is responsible for switching to the removable slot. It disables the active eSIM
+ * profile before switching if there is one.
+ */
+public class SwitchToRemovableSlotSidecar extends EuiccOperationSidecar
+ implements SidecarFragment.Listener {
+
+ private static final String TAG = "DisableSubscriptionAndSwitchSlotSidecar";
+ private static final String ACTION_DISABLE_SUBSCRIPTION_AND_SWITCH_SLOT =
+ "disable_subscription_and_switch_slot_sidecar";
+
+ // Stateless members.
+ private SwitchToEuiccSubscriptionSidecar mSwitchToSubscriptionSidecar;
+ private SwitchSlotSidecar mSwitchSlotSidecar;
+ private int mPhysicalSlotId;
+
+ /** Returns a SwitchToRemovableSlotSidecar sidecar instance. */
+ public static SwitchToRemovableSlotSidecar get(FragmentManager fm) {
+ return SidecarFragment.get(fm, TAG, SwitchToRemovableSlotSidecar.class, null /* args */);
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mSwitchToSubscriptionSidecar =
+ SwitchToEuiccSubscriptionSidecar.get(getChildFragmentManager());
+ mSwitchSlotSidecar = SwitchSlotSidecar.get(getChildFragmentManager());
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ mSwitchToSubscriptionSidecar.addListener(this);
+ mSwitchSlotSidecar.addListener(this);
+ }
+
+ @Override
+ public void onPause() {
+ mSwitchToSubscriptionSidecar.removeListener(this);
+ mSwitchSlotSidecar.removeListener(this);
+ super.onPause();
+ }
+
+ @Override
+ protected String getReceiverAction() {
+ return ACTION_DISABLE_SUBSCRIPTION_AND_SWITCH_SLOT;
+ }
+
+ @Override
+ public void onStateChange(SidecarFragment fragment) {
+ if (fragment == mSwitchToSubscriptionSidecar) {
+ onSwitchToSubscriptionSidecarStateChange();
+ } else if (fragment == mSwitchSlotSidecar) {
+ onSwitchSlotSidecarStateChange();
+ } else {
+ Log.wtf(TAG, "Received state change from a sidecar not expected.");
+ }
+ }
+
+ /**
+ * Starts switching to the removable slot. It disables the active eSIM profile before switching
+ * if there is one.
+ *
+ * @param physicalSlotId removable physical SIM slot ID.
+ */
+ public void run(int physicalSlotId) {
+ mPhysicalSlotId = physicalSlotId;
+ SubscriptionManager subscriptionManager =
+ getContext().getSystemService(SubscriptionManager.class);
+ if (SubscriptionUtil.getActiveSubscriptions(subscriptionManager).stream()
+ .anyMatch(SubscriptionInfo::isEmbedded)) {
+ Log.i(TAG, "There is an active eSIM profile. Disable the profile first.");
+ // Use INVALID_SUBSCRIPTION_ID to disable the only active profile.
+ mSwitchToSubscriptionSidecar.run(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ } else {
+ Log.i(TAG, "There is no active eSIM profiles. Start to switch to removable slot.");
+ mSwitchSlotSidecar.runSwitchToRemovableSlot(mPhysicalSlotId);
+ }
+ }
+
+ private void onSwitchToSubscriptionSidecarStateChange() {
+ switch (mSwitchToSubscriptionSidecar.getState()) {
+ case State.SUCCESS:
+ mSwitchToSubscriptionSidecar.reset();
+ Log.i(
+ TAG,
+ "Successfully disabled eSIM profile. Start to switch to Removable slot.");
+ mSwitchSlotSidecar.runSwitchToRemovableSlot(mPhysicalSlotId);
+ break;
+ case State.ERROR:
+ mSwitchToSubscriptionSidecar.reset();
+ Log.i(TAG, "Failed to disable the active eSIM profile.");
+ setState(State.ERROR, Substate.UNUSED);
+ break;
+ }
+ }
+
+ private void onSwitchSlotSidecarStateChange() {
+ switch (mSwitchSlotSidecar.getState()) {
+ case State.SUCCESS:
+ mSwitchSlotSidecar.reset();
+ Log.i(TAG, "Successfully switched to removable slot.");
+ setState(State.SUCCESS, Substate.UNUSED);
+ break;
+ case State.ERROR:
+ mSwitchSlotSidecar.reset();
+ Log.i(TAG, "Failed to switch to removable slot.");
+ setState(State.ERROR, Substate.UNUSED);
+ break;
+ }
+ }
+}
diff --git a/src/com/android/settings/network/UiccSlotUtil.java b/src/com/android/settings/network/UiccSlotUtil.java
new file mode 100644
index 0000000..792f02a
--- /dev/null
+++ b/src/com/android/settings/network/UiccSlotUtil.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2020 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.settings.network;
+
+import android.annotation.IntDef;
+import android.content.Context;
+import android.telephony.TelephonyManager;
+import android.telephony.UiccSlotInfo;
+import android.util.Log;
+
+import com.android.settingslib.utils.ThreadUtils;
+
+import com.google.common.collect.ImmutableList;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class UiccSlotUtil {
+
+ private static final String TAG = "UiccSlotUtil";
+
+ // TODO(b/171846124): Pass timeout value from LPA to Settings
+ private static final long WAIT_AFTER_SWITCH_TIMEOUT_MILLIS = 25000;
+
+ public static final int INVALID_PHYSICAL_SLOT_ID = -1;
+
+ /**
+ * Mode for switching to eSIM slot which decides whether there is cleanup process, e.g.
+ * disabling test profile, after eSIM slot is activated and whether we will wait it finished.
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ SwitchingEsimMode.NO_CLEANUP,
+ SwitchingEsimMode.ASYNC_CLEANUP,
+ SwitchingEsimMode.SYNC_CLEANUP
+ })
+ public @interface SwitchingEsimMode {
+ /** No cleanup process after switching to eSIM slot */
+ int NO_CLEANUP = 0;
+ /** Has cleanup process, but we will not wait it finished. */
+ int ASYNC_CLEANUP = 1;
+ /** Has cleanup process and we will wait until it's finished */
+ int SYNC_CLEANUP = 2;
+ }
+
+ /**
+ * Returns an immutable list of all UICC slots. If TelephonyManager#getUiccSlotsInfo returns, it
+ * returns an empty list instead.
+ */
+ public static ImmutableList<UiccSlotInfo> getSlotInfos(TelephonyManager telMgr) {
+ UiccSlotInfo[] slotInfos = telMgr.getUiccSlotsInfo();
+ if (slotInfos == null) {
+ return ImmutableList.of();
+ }
+ return ImmutableList.copyOf(slotInfos);
+ }
+
+ /**
+ * Switches to the removable slot. It waits for SIM_STATE_LOADED after switch. If slotId is
+ * INVALID_PHYSICAL_SLOT_ID, the method will use the first detected inactive removable slot.
+ *
+ * @param slotId the physical removable slot id.
+ * @param context the application context.
+ * @throws UiccSlotsException if there is an error.
+ */
+ public static synchronized void switchToRemovableSlot(int slotId, Context context)
+ throws UiccSlotsException {
+ if (ThreadUtils.isMainThread()) {
+ throw new IllegalThreadStateException(
+ "Do not call switchToRemovableSlot on the main thread.");
+ }
+ TelephonyManager telMgr = context.getSystemService(TelephonyManager.class);
+ if (telMgr.isMultiSimEnabled()) {
+ // If this device supports multiple active slots, don't mess with TelephonyManager.
+ Log.i(TAG, "Multiple active slots supported. Not calling switchSlots.");
+ return;
+ }
+ UiccSlotInfo[] slots = telMgr.getUiccSlotsInfo();
+ if (slotId == INVALID_PHYSICAL_SLOT_ID) {
+ for (int i = 0; i < slots.length; i++) {
+ if (slots[i].isRemovable()
+ && !slots[i].getIsActive()
+ && slots[i].getCardStateInfo() != UiccSlotInfo.CARD_STATE_INFO_ERROR
+ && slots[i].getCardStateInfo() != UiccSlotInfo.CARD_STATE_INFO_RESTRICTED) {
+ performSwitchToRemovableSlot(i, context);
+ return;
+ }
+ }
+ } else {
+ if (slotId >= slots.length || !slots[slotId].isRemovable()) {
+ throw new UiccSlotsException("The given slotId is not a removable slot: " + slotId);
+ }
+ if (!slots[slotId].getIsActive()) {
+ performSwitchToRemovableSlot(slotId, context);
+ }
+ }
+ }
+
+ private static void performSwitchToRemovableSlot(int slotId, Context context)
+ throws UiccSlotsException {
+ CarrierConfigChangedReceiver receiver = null;
+ try {
+ CountDownLatch latch = new CountDownLatch(1);
+ receiver = new CarrierConfigChangedReceiver(latch);
+ receiver.registerOn(context);
+ switchSlots(context, slotId);
+ latch.await(WAIT_AFTER_SWITCH_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ Log.e(TAG, "Failed switching to physical slot.", e);
+ } finally {
+ if (receiver != null) {
+ context.unregisterReceiver(receiver);
+ }
+ }
+ }
+
+ /**
+ * Changes the logical slot to physical slot mapping. OEM should override this to provide
+ * device-specific implementation if the device supports switching slots.
+ *
+ * @param context the application context.
+ * @param physicalSlots List of physical slot ids in the order of logical slots.
+ */
+ private static void switchSlots(Context context, int... physicalSlots)
+ throws UiccSlotsException {
+ TelephonyManager telMgr = context.getSystemService(TelephonyManager.class);
+ if (telMgr.isMultiSimEnabled()) {
+ // If this device supports multiple active slots, don't mess with TelephonyManager.
+ Log.i(TAG, "Multiple active slots supported. Not calling switchSlots.");
+ return;
+ }
+ if (!telMgr.switchSlots(physicalSlots)) {
+ throw new UiccSlotsException("Failed to switch slots");
+ }
+ }
+}
diff --git a/src/com/android/settings/network/UiccSlotsException.java b/src/com/android/settings/network/UiccSlotsException.java
new file mode 100644
index 0000000..2a2edc5
--- /dev/null
+++ b/src/com/android/settings/network/UiccSlotsException.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 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.settings.network;
+
+/** The exception that is thrown when an error happens in a call to {@link UiccSlotUtil}. */
+public class UiccSlotsException extends Exception {
+
+ public UiccSlotsException() {}
+
+ public UiccSlotsException(String message) {
+ super(message);
+ }
+
+ public UiccSlotsException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public UiccSlotsException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java b/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java
index 905ead0..919415b 100644
--- a/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java
+++ b/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java
@@ -22,15 +22,22 @@
import android.os.UserManager;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.telephony.UiccSlotInfo;
import android.text.TextUtils;
import android.util.Log;
-import androidx.appcompat.app.AlertDialog;
-
import com.android.settings.R;
import com.android.settings.SidecarFragment;
+import com.android.settings.network.EnableMultiSimSidecar;
import com.android.settings.network.SubscriptionUtil;
import com.android.settings.network.SwitchToEuiccSubscriptionSidecar;
+import com.android.settings.network.SwitchToRemovableSlotSidecar;
+import com.android.settings.network.UiccSlotUtil;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
/** This dialog activity handles both eSIM and pSIM subscriptions enabling and disabling. */
public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogActivity
@@ -41,6 +48,11 @@
private static final String ARG_enable = "enable";
// Dialog tags
private static final int DIALOG_TAG_DISABLE_SIM_CONFIRMATION = 1;
+ private static final int DIALOG_TAG_ENABLE_SIM_CONFIRMATION = 2;
+ private static final int DIALOG_TAG_ENABLE_DSDS_CONFIRMATION = 3;
+ private static final int DIALOG_TAG_ENABLE_DSDS_REBOOT_CONFIRMATION = 4;
+ // Number of SIMs for DSDS
+ private static final int NUM_OF_SIMS_FOR_DSDS = 2;
/**
* Returns an intent of ToggleSubscriptionDialogActivity.
@@ -58,8 +70,11 @@
private SubscriptionInfo mSubInfo;
private SwitchToEuiccSubscriptionSidecar mSwitchToEuiccSubscriptionSidecar;
- private AlertDialog mToggleSimConfirmDialog;
+ private SwitchToRemovableSlotSidecar mSwitchToRemovableSlotSidecar;
+ private EnableMultiSimSidecar mEnableMultiSimSidecar;
private boolean mEnable;
+ private boolean mIsEsimOperation;
+ private TelephonyManager mTelMgr;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -67,6 +82,7 @@
Intent intent = getIntent();
int subId = intent.getIntExtra(ARG_SUB_ID, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ mTelMgr = getSystemService(TelephonyManager.class);
UserManager userManager = getSystemService(UserManager.class);
if (!userManager.isAdminUser()) {
@@ -82,13 +98,16 @@
}
mSubInfo = SubscriptionUtil.getSubById(mSubscriptionManager, subId);
+ mIsEsimOperation = mSubInfo != null && mSubInfo.isEmbedded();
mSwitchToEuiccSubscriptionSidecar =
SwitchToEuiccSubscriptionSidecar.get(getFragmentManager());
+ mSwitchToRemovableSlotSidecar = SwitchToRemovableSlotSidecar.get(getFragmentManager());
+ mEnableMultiSimSidecar = EnableMultiSimSidecar.get(getFragmentManager());
mEnable = intent.getBooleanExtra(ARG_enable, true);
if (savedInstanceState == null) {
if (mEnable) {
- handleEnablingSubAction();
+ showEnableSubDialog();
} else {
showDisableSimConfirmDialog();
}
@@ -99,10 +118,14 @@
protected void onResume() {
super.onResume();
mSwitchToEuiccSubscriptionSidecar.addListener(this);
+ mSwitchToRemovableSlotSidecar.addListener(this);
+ mEnableMultiSimSidecar.addListener(this);
}
@Override
protected void onPause() {
+ mEnableMultiSimSidecar.removeListener(this);
+ mSwitchToRemovableSlotSidecar.removeListener(this);
mSwitchToEuiccSubscriptionSidecar.removeListener(this);
super.onPause();
}
@@ -111,19 +134,25 @@
public void onStateChange(SidecarFragment fragment) {
if (fragment == mSwitchToEuiccSubscriptionSidecar) {
handleSwitchToEuiccSubscriptionSidecarStateChange();
+ } else if (fragment == mSwitchToRemovableSlotSidecar) {
+ handleSwitchToRemovableSlotSidecarStateChange();
+ } else if (fragment == mEnableMultiSimSidecar) {
+ handleEnableMultiSimSidecarStateChange();
}
}
@Override
public void onConfirm(int tag, boolean confirmed) {
- if (!confirmed) {
+ if (!confirmed
+ && tag != DIALOG_TAG_ENABLE_DSDS_CONFIRMATION
+ && tag != DIALOG_TAG_ENABLE_DSDS_REBOOT_CONFIRMATION) {
finish();
return;
}
switch (tag) {
case DIALOG_TAG_DISABLE_SIM_CONFIRMATION:
- if (mSubInfo.isEmbedded()) {
+ if (mIsEsimOperation) {
Log.i(TAG, "Disabling the eSIM profile.");
showProgressDialog(
getString(R.string.privileged_action_disable_sub_dialog_progress));
@@ -132,6 +161,51 @@
return;
}
Log.i(TAG, "Disabling the pSIM profile.");
+ handleTogglePsimAction();
+ break;
+ case DIALOG_TAG_ENABLE_DSDS_CONFIRMATION:
+ if (!confirmed) {
+ Log.i(TAG, "User cancel the dialog to enable DSDS.");
+ showEnableSimConfirmDialog();
+ return;
+ }
+ if (mTelMgr.doesSwitchMultiSimConfigTriggerReboot()) {
+ Log.i(TAG, "Device does not support reboot free DSDS.");
+ showRebootConfirmDialog();
+ return;
+ }
+ Log.i(
+ TAG,
+ "Enabling DSDS without rebooting. "
+ + getString(R.string.sim_action_enabling_sim_without_carrier_name));
+ showProgressDialog(
+ getString(R.string.sim_action_enabling_sim_without_carrier_name));
+ mEnableMultiSimSidecar.run(NUM_OF_SIMS_FOR_DSDS);
+ break;
+ case DIALOG_TAG_ENABLE_DSDS_REBOOT_CONFIRMATION:
+ if (!confirmed) {
+ Log.i(TAG, "User cancel the dialog to reboot to enable DSDS.");
+ showEnableSimConfirmDialog();
+ return;
+ }
+ Log.i(TAG, "User confirmed reboot to enable DSDS.");
+ mTelMgr.switchMultiSimConfig(NUM_OF_SIMS_FOR_DSDS);
+ // TODO(b/170507290): Store a bit in preferences for displaying the notification
+ // after the reboot.
+ break;
+ case DIALOG_TAG_ENABLE_SIM_CONFIRMATION:
+ Log.i(TAG, "User confirmed to enable the subscription.");
+ if (mIsEsimOperation) {
+ showProgressDialog(
+ getString(
+ R.string.sim_action_switch_sub_dialog_progress,
+ mSubInfo.getDisplayName()));
+ mSwitchToEuiccSubscriptionSidecar.run(mSubInfo.getSubscriptionId());
+ return;
+ }
+ showProgressDialog(
+ getString(R.string.sim_action_enabling_sim_without_carrier_name));
+ mSwitchToRemovableSlotSidecar.run(UiccSlotUtil.INVALID_PHYSICAL_SLOT_ID);
break;
default:
Log.e(TAG, "Unrecognized confirmation dialog tag: " + tag);
@@ -165,10 +239,105 @@
}
}
+ private void handleSwitchToRemovableSlotSidecarStateChange() {
+ switch (mSwitchToRemovableSlotSidecar.getState()) {
+ case SidecarFragment.State.SUCCESS:
+ Log.i(TAG, "Successfully switched to removable slot.");
+ mSwitchToRemovableSlotSidecar.reset();
+ handleTogglePsimAction();
+ dismissProgressDialog();
+ finish();
+ break;
+ case SidecarFragment.State.ERROR:
+ Log.e(TAG, "Failed switching to removable slot.");
+ mSwitchToRemovableSlotSidecar.reset();
+ dismissProgressDialog();
+ showErrorDialog(
+ getString(R.string.sim_action_enable_sim_fail_title),
+ getString(R.string.sim_action_enable_sim_fail_text));
+ break;
+ }
+ }
+
+ private void handleEnableMultiSimSidecarStateChange() {
+ switch (mEnableMultiSimSidecar.getState()) {
+ case SidecarFragment.State.SUCCESS:
+ mEnableMultiSimSidecar.reset();
+ Log.i(TAG, "Successfully switched to DSDS without reboot.");
+ handleEnableSubscriptionAfterEnablingDsds();
+ break;
+ case SidecarFragment.State.ERROR:
+ mEnableMultiSimSidecar.reset();
+ Log.i(TAG, "Failed to switch to DSDS without rebooting.");
+ ProgressDialogFragment.dismiss(getFragmentManager());
+ showErrorDialog(
+ getString(R.string.dsds_activation_failure_title),
+ getString(R.string.dsds_activation_failure_body_msg2));
+ break;
+ }
+ }
+
+ private void handleEnableSubscriptionAfterEnablingDsds() {
+ if (mIsEsimOperation) {
+ Log.i(TAG, "DSDS enabled, start to enable profile: " + mSubInfo.getSubscriptionId());
+ // For eSIM operations, we simply switch to the selected eSIM profile.
+ mSwitchToEuiccSubscriptionSidecar.run(mSubInfo.getSubscriptionId());
+ return;
+ }
+
+ Log.i(TAG, "DSDS enabled, start to enable pSIM profile.");
+ handleTogglePsimAction();
+ ProgressDialogFragment.dismiss(getFragmentManager());
+ finish();
+ }
+
+ private void handleTogglePsimAction() {
+ if (mSubscriptionManager.canDisablePhysicalSubscription() && mSubInfo != null) {
+ mSubscriptionManager.setUiccApplicationsEnabled(mSubInfo.getSubscriptionId(), mEnable);
+ } else {
+ Log.i(
+ TAG,
+ "The device does not support toggling pSIM. It is enough to just "
+ + "enable the removable slot.");
+ }
+ }
+
/* Handles the enabling SIM action. */
- private void handleEnablingSubAction() {
- Log.i(TAG, "handleEnableSub");
- // TODO(b/160819390): Implement enabling eSIM/pSIM profile.
+ private void showEnableSubDialog() {
+ Log.i(TAG, "Handle subscription enabling.");
+ if (isDsdsConditionSatisfied()) {
+ showEnableDsdsConfirmDialog();
+ return;
+ }
+ if (!mIsEsimOperation && mTelMgr.isMultiSimEnabled()) {
+ Log.i(TAG, "Toggle on pSIM, no dialog displayed.");
+ handleTogglePsimAction();
+ finish();
+ return;
+ }
+ showEnableSimConfirmDialog();
+ }
+
+ private void showEnableDsdsConfirmDialog() {
+ ConfirmDialogFragment.show(
+ this,
+ ConfirmDialogFragment.OnConfirmListener.class,
+ DIALOG_TAG_ENABLE_DSDS_CONFIRMATION,
+ getString(R.string.sim_action_enable_dsds_title),
+ getString(R.string.sim_action_enable_dsds_text),
+ getString(R.string.sim_action_continue),
+ getString(R.string.sim_action_no_thanks));
+ }
+
+ private void showRebootConfirmDialog() {
+ ConfirmDialogFragment.show(
+ this,
+ ConfirmDialogFragment.OnConfirmListener.class,
+ DIALOG_TAG_ENABLE_DSDS_REBOOT_CONFIRMATION,
+ getString(R.string.sim_action_restart_title),
+ getString(R.string.sim_action_enable_dsds_text),
+ getString(R.string.sim_action_reboot),
+ getString(R.string.cancel));
}
/* Displays the SIM toggling confirmation dialog. */
@@ -191,10 +360,116 @@
getString(R.string.cancel));
}
- /* Dismisses the SIM toggling confirmation dialog. */
- private void dismissToggleSimConfirmDialog() {
- if (mToggleSimConfirmDialog != null) {
- mToggleSimConfirmDialog.dismiss();
+ private void showEnableSimConfirmDialog() {
+ List<SubscriptionInfo> activeSubs =
+ SubscriptionUtil.getActiveSubscriptions(mSubscriptionManager);
+ SubscriptionInfo activeSub = activeSubs.isEmpty() ? null : activeSubs.get(0);
+ if (activeSub == null) {
+ Log.i(TAG, "No active subscriptions available.");
+ showNonSwitchSimConfirmDialog();
+ return;
}
+ Log.i(TAG, "Found active subscription.");
+ boolean isBetweenEsim = mIsEsimOperation && activeSub.isEmbedded();
+ if (mTelMgr.isMultiSimEnabled() && !isBetweenEsim) {
+ showNonSwitchSimConfirmDialog();
+ return;
+ }
+ ConfirmDialogFragment.show(
+ this,
+ ConfirmDialogFragment.OnConfirmListener.class,
+ DIALOG_TAG_ENABLE_SIM_CONFIRMATION,
+ getSwitchSubscriptionTitle(),
+ getSwitchDialogBodyMsg(activeSub, isBetweenEsim),
+ getSwitchDialogPosBtnText(),
+ getString(android.R.string.cancel));
+ }
+
+ private void showNonSwitchSimConfirmDialog() {
+ ConfirmDialogFragment.show(
+ this,
+ ConfirmDialogFragment.OnConfirmListener.class,
+ DIALOG_TAG_ENABLE_SIM_CONFIRMATION,
+ getEnableSubscriptionTitle(),
+ null /* msg */,
+ getString(R.string.yes),
+ getString(android.R.string.cancel));
+ }
+
+ private String getSwitchDialogPosBtnText() {
+ return mIsEsimOperation
+ ? getString(
+ R.string.sim_action_switch_sub_dialog_confirm, mSubInfo.getDisplayName())
+ : getString(R.string.sim_switch_button);
+ }
+
+ private String getEnableSubscriptionTitle() {
+ if (mSubInfo == null || TextUtils.isEmpty(mSubInfo.getDisplayName())) {
+ return getString(R.string.sim_action_enable_sub_dialog_title_without_carrier_name);
+ }
+ return getString(R.string.sim_action_enable_sub_dialog_title, mSubInfo.getDisplayName());
+ }
+
+ private String getSwitchSubscriptionTitle() {
+ if (mIsEsimOperation) {
+ return getString(
+ R.string.sim_action_switch_sub_dialog_title, mSubInfo.getDisplayName());
+ }
+ return getString(R.string.sim_action_switch_psim_dialog_title);
+ }
+
+ private String getSwitchDialogBodyMsg(SubscriptionInfo activeSub, boolean betweenEsim) {
+ if (betweenEsim && mIsEsimOperation) {
+ return getString(
+ R.string.sim_action_switch_sub_dialog_text_downloaded,
+ mSubInfo.getDisplayName(),
+ activeSub.getDisplayName());
+ } else if (mIsEsimOperation) {
+ return getString(
+ R.string.sim_action_switch_sub_dialog_text,
+ mSubInfo.getDisplayName(),
+ activeSub.getDisplayName());
+ } else {
+ return getString(
+ R.string.sim_action_switch_sub_dialog_text_single_sim,
+ activeSub.getDisplayName());
+ }
+ }
+
+ private boolean isDsdsConditionSatisfied() {
+ if (mTelMgr.isMultiSimEnabled()) {
+ Log.i(TAG, "DSDS is already enabled. Condition not satisfied.");
+ return false;
+ }
+ if (mTelMgr.isMultiSimSupported() != TelephonyManager.MULTISIM_ALLOWED) {
+ Log.i(TAG, "Hardware does not support DSDS.");
+ return false;
+ }
+ ImmutableList<UiccSlotInfo> slotInfos = UiccSlotUtil.getSlotInfos(mTelMgr);
+ boolean isRemovableSimEnabled =
+ slotInfos.stream()
+ .anyMatch(
+ slot ->
+ slot != null
+ && slot.isRemovable()
+ && slot.getIsActive()
+ && slot.getCardStateInfo()
+ == UiccSlotInfo.CARD_STATE_INFO_PRESENT);
+ if (mIsEsimOperation && isRemovableSimEnabled) {
+ Log.i(TAG, "eSIM operation and removable SIM is enabled. DSDS condition satisfied.");
+ return true;
+ }
+ boolean isEsimProfileEnabled =
+ SubscriptionUtil.getActiveSubscriptions(mSubscriptionManager).stream()
+ .anyMatch(SubscriptionInfo::isEmbedded);
+ if (!mIsEsimOperation && isEsimProfileEnabled) {
+ Log.i(
+ TAG,
+ "Removable SIM operation and eSIM profile is enabled. DSDS condition"
+ + " satisfied.");
+ return true;
+ }
+ Log.i(TAG, "DSDS condition not satisfied.");
+ return false;
}
}