[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
This commit is contained in:
Jiashen Wang
2020-10-29 16:55:43 -07:00
parent 903a211665
commit 7840ae7569
9 changed files with 1095 additions and 14 deletions

View File

@@ -11975,6 +11975,30 @@
<string name="see_less">See less</string> <string name="see_less">See less</string>
<!-- Strings for toggling subscriptions dialog activity --> <!-- 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&#8230;</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] --> <!-- 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> <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] --> <!-- 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] --> <!-- Disabling SIMs progress dialog message [CHAR LIMIT=NONE] -->
<string name="privileged_action_disable_sub_dialog_progress">Turning off SIM<xliff:g id="ellipsis" example="...">&#8230;</xliff:g></string> <string name="privileged_action_disable_sub_dialog_progress">Turning off SIM<xliff:g id="ellipsis" example="...">&#8230;</xliff:g></string>
<!-- Title of error messaging indicating the device could not disable the mobile network. [CHAR LIMIT=NONE] --> <!-- 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] --> <!-- 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> <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 --> <!-- 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] --> <!-- Title on confirmation dialog asking the user if they want to erase the downloaded SIM from the device. [CHAR_LIMIT=NONE] -->

View File

@@ -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));
});
}
}

View File

@@ -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();
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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");
}
}
}

View File

@@ -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);
}
}

View File

@@ -22,15 +22,22 @@ import android.os.Bundle;
import android.os.UserManager; import android.os.UserManager;
import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager; import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.UiccSlotInfo;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import androidx.appcompat.app.AlertDialog;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.SidecarFragment; import com.android.settings.SidecarFragment;
import com.android.settings.network.EnableMultiSimSidecar;
import com.android.settings.network.SubscriptionUtil; import com.android.settings.network.SubscriptionUtil;
import com.android.settings.network.SwitchToEuiccSubscriptionSidecar; 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. */ /** This dialog activity handles both eSIM and pSIM subscriptions enabling and disabling. */
public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogActivity public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogActivity
@@ -41,6 +48,11 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc
private static final String ARG_enable = "enable"; private static final String ARG_enable = "enable";
// Dialog tags // Dialog tags
private static final int DIALOG_TAG_DISABLE_SIM_CONFIRMATION = 1; 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. * Returns an intent of ToggleSubscriptionDialogActivity.
@@ -58,8 +70,11 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc
private SubscriptionInfo mSubInfo; private SubscriptionInfo mSubInfo;
private SwitchToEuiccSubscriptionSidecar mSwitchToEuiccSubscriptionSidecar; private SwitchToEuiccSubscriptionSidecar mSwitchToEuiccSubscriptionSidecar;
private AlertDialog mToggleSimConfirmDialog; private SwitchToRemovableSlotSidecar mSwitchToRemovableSlotSidecar;
private EnableMultiSimSidecar mEnableMultiSimSidecar;
private boolean mEnable; private boolean mEnable;
private boolean mIsEsimOperation;
private TelephonyManager mTelMgr;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@@ -67,6 +82,7 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc
Intent intent = getIntent(); Intent intent = getIntent();
int subId = intent.getIntExtra(ARG_SUB_ID, SubscriptionManager.INVALID_SUBSCRIPTION_ID); int subId = intent.getIntExtra(ARG_SUB_ID, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
mTelMgr = getSystemService(TelephonyManager.class);
UserManager userManager = getSystemService(UserManager.class); UserManager userManager = getSystemService(UserManager.class);
if (!userManager.isAdminUser()) { if (!userManager.isAdminUser()) {
@@ -82,13 +98,16 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc
} }
mSubInfo = SubscriptionUtil.getSubById(mSubscriptionManager, subId); mSubInfo = SubscriptionUtil.getSubById(mSubscriptionManager, subId);
mIsEsimOperation = mSubInfo != null && mSubInfo.isEmbedded();
mSwitchToEuiccSubscriptionSidecar = mSwitchToEuiccSubscriptionSidecar =
SwitchToEuiccSubscriptionSidecar.get(getFragmentManager()); SwitchToEuiccSubscriptionSidecar.get(getFragmentManager());
mSwitchToRemovableSlotSidecar = SwitchToRemovableSlotSidecar.get(getFragmentManager());
mEnableMultiSimSidecar = EnableMultiSimSidecar.get(getFragmentManager());
mEnable = intent.getBooleanExtra(ARG_enable, true); mEnable = intent.getBooleanExtra(ARG_enable, true);
if (savedInstanceState == null) { if (savedInstanceState == null) {
if (mEnable) { if (mEnable) {
handleEnablingSubAction(); showEnableSubDialog();
} else { } else {
showDisableSimConfirmDialog(); showDisableSimConfirmDialog();
} }
@@ -99,10 +118,14 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
mSwitchToEuiccSubscriptionSidecar.addListener(this); mSwitchToEuiccSubscriptionSidecar.addListener(this);
mSwitchToRemovableSlotSidecar.addListener(this);
mEnableMultiSimSidecar.addListener(this);
} }
@Override @Override
protected void onPause() { protected void onPause() {
mEnableMultiSimSidecar.removeListener(this);
mSwitchToRemovableSlotSidecar.removeListener(this);
mSwitchToEuiccSubscriptionSidecar.removeListener(this); mSwitchToEuiccSubscriptionSidecar.removeListener(this);
super.onPause(); super.onPause();
} }
@@ -111,19 +134,25 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc
public void onStateChange(SidecarFragment fragment) { public void onStateChange(SidecarFragment fragment) {
if (fragment == mSwitchToEuiccSubscriptionSidecar) { if (fragment == mSwitchToEuiccSubscriptionSidecar) {
handleSwitchToEuiccSubscriptionSidecarStateChange(); handleSwitchToEuiccSubscriptionSidecarStateChange();
} else if (fragment == mSwitchToRemovableSlotSidecar) {
handleSwitchToRemovableSlotSidecarStateChange();
} else if (fragment == mEnableMultiSimSidecar) {
handleEnableMultiSimSidecarStateChange();
} }
} }
@Override @Override
public void onConfirm(int tag, boolean confirmed) { 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(); finish();
return; return;
} }
switch (tag) { switch (tag) {
case DIALOG_TAG_DISABLE_SIM_CONFIRMATION: case DIALOG_TAG_DISABLE_SIM_CONFIRMATION:
if (mSubInfo.isEmbedded()) { if (mIsEsimOperation) {
Log.i(TAG, "Disabling the eSIM profile."); Log.i(TAG, "Disabling the eSIM profile.");
showProgressDialog( showProgressDialog(
getString(R.string.privileged_action_disable_sub_dialog_progress)); getString(R.string.privileged_action_disable_sub_dialog_progress));
@@ -132,6 +161,51 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc
return; return;
} }
Log.i(TAG, "Disabling the pSIM profile."); 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; break;
default: default:
Log.e(TAG, "Unrecognized confirmation dialog tag: " + tag); Log.e(TAG, "Unrecognized confirmation dialog tag: " + tag);
@@ -165,10 +239,105 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc
} }
} }
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. */ /* Handles the enabling SIM action. */
private void handleEnablingSubAction() { private void showEnableSubDialog() {
Log.i(TAG, "handleEnableSub"); Log.i(TAG, "Handle subscription enabling.");
// TODO(b/160819390): Implement enabling eSIM/pSIM profile. 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. */ /* Displays the SIM toggling confirmation dialog. */
@@ -191,10 +360,116 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc
getString(R.string.cancel)); getString(R.string.cancel));
} }
/* Dismisses the SIM toggling confirmation dialog. */ private void showEnableSimConfirmDialog() {
private void dismissToggleSimConfirmDialog() { List<SubscriptionInfo> activeSubs =
if (mToggleSimConfirmDialog != null) { SubscriptionUtil.getActiveSubscriptions(mSubscriptionManager);
mToggleSimConfirmDialog.dismiss(); 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;
}
} }