/* * 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.telephony; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.UserManager; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.telephony.UiccCardInfo; import android.text.TextUtils; import android.util.Log; import android.view.View; import com.android.internal.annotations.VisibleForTesting; 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.android.settings.sim.SimActivationNotifier; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; /** This dialog activity handles both eSIM and pSIM subscriptions enabling and disabling. */ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogActivity implements SidecarFragment.Listener, ConfirmDialogFragment.OnConfirmListener { private static final String TAG = "ToggleSubscriptionDialogActivity"; // Arguments @VisibleForTesting public 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; private static final int DIALOG_TAG_ENABLE_SIM_CONFIRMATION_MEP = 5; // Number of SIMs for DSDS private static final int NUM_OF_SIMS_FOR_DSDS = 2; // Support RTL mode private static final String LINE_BREAK = "\n"; private static final int LINE_BREAK_OFFSET_ONE = 1; private static final int LINE_BREAK_OFFSET_TWO = 2; private static final String RTL_MARK = "\u200F"; /** * Returns an intent of ToggleSubscriptionDialogActivity. * * @param context The context used to start the ToggleSubscriptionDialogActivity. * @param subId The subscription ID of the subscription needs to be toggled. * @param enable Whether the activity should enable or disable the subscription. */ public static Intent getIntent(Context context, int subId, boolean enable) { Intent intent = new Intent(context, ToggleSubscriptionDialogActivity.class); intent.putExtra(ARG_SUB_ID, subId); intent.putExtra(ARG_enable, enable); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); return intent; } private SubscriptionInfo mSubInfo; private SwitchToEuiccSubscriptionSidecar mSwitchToEuiccSubscriptionSidecar; private SwitchToRemovableSlotSidecar mSwitchToRemovableSlotSidecar; private EnableMultiSimSidecar mEnableMultiSimSidecar; private boolean mEnable; private boolean mIsEsimOperation; private TelephonyManager mTelMgr; private boolean isRtlMode; private List mActiveSubInfos; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); 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()) { Log.e(TAG, "It is not the admin user. Unable to toggle subscription."); finish(); return; } if (!SubscriptionManager.isUsableSubscriptionId(subId)) { Log.e(TAG, "The subscription id is not usable."); finish(); return; } mActiveSubInfos = SubscriptionUtil.getActiveSubscriptions(mSubscriptionManager); 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); isRtlMode = getResources().getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; Log.i(TAG, "isMultipleEnabledProfilesSupported():" + isMultipleEnabledProfilesSupported()); if (savedInstanceState == null) { if (mEnable) { showEnableSubDialog(); } else { showDisableSimConfirmDialog(); } } } @Override 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(); } @Override 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, int itemPosition) { if (!confirmed && tag != DIALOG_TAG_ENABLE_DSDS_CONFIRMATION && tag != DIALOG_TAG_ENABLE_DSDS_REBOOT_CONFIRMATION) { finish(); return; } SubscriptionInfo removedSubInfo = null; switch (tag) { case DIALOG_TAG_DISABLE_SIM_CONFIRMATION: if (mIsEsimOperation) { Log.i(TAG, "Disabling the eSIM profile."); showProgressDialog( getString(R.string.privileged_action_disable_sub_dialog_progress)); int port = mSubInfo != null ? mSubInfo.getPortIndex() : 0; mSwitchToEuiccSubscriptionSidecar.run( SubscriptionManager.INVALID_SUBSCRIPTION_ID, port, null); 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."); 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."); SimActivationNotifier.setShowSimSettingsNotification(this, true); mTelMgr.switchMultiSimConfig(NUM_OF_SIMS_FOR_DSDS); break; case DIALOG_TAG_ENABLE_SIM_CONFIRMATION_MEP: if (itemPosition != -1) { removedSubInfo = (mActiveSubInfos != null) ? mActiveSubInfos.get(itemPosition) : null; } case DIALOG_TAG_ENABLE_SIM_CONFIRMATION: Log.i(TAG, "User confirmed to enable the subscription."); showProgressDialog( getString( R.string.sim_action_switch_sub_dialog_progress, SubscriptionUtil.getUniqueSubscriptionDisplayName(mSubInfo, this)), removedSubInfo != null ? true : false); if (mIsEsimOperation) { mSwitchToEuiccSubscriptionSidecar.run(mSubInfo.getSubscriptionId(), UiccSlotUtil.INVALID_PORT_ID, removedSubInfo); return; } mSwitchToRemovableSlotSidecar.run(UiccSlotUtil.INVALID_PHYSICAL_SLOT_ID, removedSubInfo); break; default: Log.e(TAG, "Unrecognized confirmation dialog tag: " + tag); break; } } private void handleSwitchToEuiccSubscriptionSidecarStateChange() { switch (mSwitchToEuiccSubscriptionSidecar.getState()) { case SidecarFragment.State.SUCCESS: Log.i(TAG, String.format( "Successfully %s the eSIM profile.", mEnable ? "enable" : "disable")); mSwitchToEuiccSubscriptionSidecar.reset(); dismissProgressDialog(); finish(); break; case SidecarFragment.State.ERROR: Log.i(TAG, String.format( "Failed to %s the eSIM profile.", mEnable ? "enable" : "disable")); mSwitchToEuiccSubscriptionSidecar.reset(); dismissProgressDialog(); showErrorDialog( getString(R.string.privileged_action_disable_fail_title), getString(R.string.privileged_action_disable_fail_text)); break; } } 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."); dismissProgressDialog(); 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(), UiccSlotUtil.INVALID_PORT_ID, null); return; } Log.i(TAG, "DSDS enabled, start to enable pSIM profile."); handleTogglePsimAction(); dismissProgressDialog(); finish(); } private void handleTogglePsimAction() { if (mSubscriptionManager.canDisablePhysicalSubscription() && mSubInfo != null) { mSubscriptionManager.setUiccApplicationsEnabled(mSubInfo.getSubscriptionId(), mEnable); finish(); } 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 showEnableSubDialog() { Log.d(TAG, "Handle subscription enabling."); if (isDsdsConditionSatisfied()) { showEnableDsdsConfirmDialog(); return; } if (!mIsEsimOperation && mTelMgr.isMultiSimEnabled() && isRemovableSimEnabled()) { // This case is for switching on psim when device is not multiple enable profile // supported. 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_yes), 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.sim_action_cancel)); } /* Displays the SIM toggling confirmation dialog. */ private void showDisableSimConfirmDialog() { final CharSequence displayName = SubscriptionUtil.getUniqueSubscriptionDisplayName( mSubInfo, this); String title = mSubInfo == null || TextUtils.isEmpty(displayName) ? getString( R.string.privileged_action_disable_sub_dialog_title_without_carrier) : getString( R.string.privileged_action_disable_sub_dialog_title, displayName); ConfirmDialogFragment.show( this, ConfirmDialogFragment.OnConfirmListener.class, DIALOG_TAG_DISABLE_SIM_CONFIRMATION, title, null, getString(R.string.sim_action_turn_off), getString(R.string.sim_action_cancel)); } private void showEnableSimConfirmDialog() { if (mActiveSubInfos == null || mActiveSubInfos.isEmpty()) { Log.i(TAG, "No active subscriptions available."); showNonSwitchSimConfirmDialog(); return; } Log.i(TAG, "mActiveSubInfos:" + mActiveSubInfos); boolean isSwitchingBetweenEsims = mIsEsimOperation && mActiveSubInfos.stream().anyMatch(activeSubInfo -> activeSubInfo.isEmbedded()); boolean isMultiSimEnabled = mTelMgr.isMultiSimEnabled(); if (isMultiSimEnabled && !isMultipleEnabledProfilesSupported() && !isSwitchingBetweenEsims) { // Showing the "no switch dialog" for below cases. // DSDS mode + no MEP + // (there is the active psim -> esim switch on => active (psim + esim)) showNonSwitchSimConfirmDialog(); return; } if (isMultiSimEnabled && isMultipleEnabledProfilesSupported()) { if (mActiveSubInfos.size() < NUM_OF_SIMS_FOR_DSDS) { // The sim can add into device directly, so showing the "no switch dialog". // DSDS + MEP + (active sim < NUM_OF_SIMS_FOR_DSDS) showNonSwitchSimConfirmDialog(); } else { // The all of slots have sim, it needs to show the "MEP switch dialog". // DSDS + MEP + two active sims showMepSwitchSimConfirmDialog(); } return; } // Showing the "switch dialog" for below cases. // case1: SS mode + psim switch on from esim. // case2: SS mode + esim switch from psim. // case3: DSDS mode + No MEP + esim switch on from another esim. SubscriptionInfo activeSub = (isMultiSimEnabled && isSwitchingBetweenEsims) ? mActiveSubInfos.stream() .filter(activeSubInfo -> activeSubInfo.isEmbedded()) .findFirst().get() : mActiveSubInfos.get(0); ConfirmDialogFragment.show( this, ConfirmDialogFragment.OnConfirmListener.class, DIALOG_TAG_ENABLE_SIM_CONFIRMATION, getSwitchSubscriptionTitle(), getSwitchDialogBodyMsg(activeSub, isSwitchingBetweenEsims), getSwitchDialogPosBtnText(), getString(R.string.sim_action_cancel)); } private void showNonSwitchSimConfirmDialog() { ConfirmDialogFragment.show( this, ConfirmDialogFragment.OnConfirmListener.class, DIALOG_TAG_ENABLE_SIM_CONFIRMATION, getEnableSubscriptionTitle(), null /* msg */, getString(R.string.condition_turn_on), getString(R.string.sim_action_cancel)); } private void showMepSwitchSimConfirmDialog() { Log.d(TAG, "showMepSwitchSimConfirmDialog"); final CharSequence displayName = SubscriptionUtil.getUniqueSubscriptionDisplayName( mSubInfo, this); String title = getString(R.string.sim_action_switch_sub_dialog_mep_title, displayName); final StringBuilder switchDialogMsg = new StringBuilder(); switchDialogMsg.append( getString(R.string.sim_action_switch_sub_dialog_mep_text, displayName)); if (isRtlMode) { /* The RTL symbols must be added before and after each sentence. * (Each message are all with two line break symbols) */ switchDialogMsg.insert(0, RTL_MARK) .insert(switchDialogMsg.length(), RTL_MARK); } ConfirmDialogFragment.show( this, ConfirmDialogFragment.OnConfirmListener.class, DIALOG_TAG_ENABLE_SIM_CONFIRMATION_MEP, title, switchDialogMsg.toString(), null, null, getSwitchDialogBodyList()); } private String getSwitchDialogPosBtnText() { return mIsEsimOperation ? getString( R.string.sim_action_switch_sub_dialog_confirm, SubscriptionUtil.getUniqueSubscriptionDisplayName(mSubInfo, this)) : getString(R.string.sim_switch_button); } private String getEnableSubscriptionTitle() { final CharSequence displayName = SubscriptionUtil.getUniqueSubscriptionDisplayName( mSubInfo, this); if (mSubInfo == null || TextUtils.isEmpty(displayName)) { return getString(R.string.sim_action_enable_sub_dialog_title_without_carrier_name); } return getString(R.string.sim_action_enable_sub_dialog_title, displayName); } private String getSwitchSubscriptionTitle() { if (mIsEsimOperation) { return getString( R.string.sim_action_switch_sub_dialog_title, SubscriptionUtil.getUniqueSubscriptionDisplayName(mSubInfo, this)); } return getString(R.string.sim_action_switch_psim_dialog_title); } private String getSwitchDialogBodyMsg(SubscriptionInfo activeSub, boolean betweenEsim) { final CharSequence subInfoName = SubscriptionUtil.getUniqueSubscriptionDisplayName( mSubInfo, this); final CharSequence activeSubName = SubscriptionUtil.getUniqueSubscriptionDisplayName( activeSub, this); final StringBuilder switchDialogMsg = new StringBuilder(); if (betweenEsim && mIsEsimOperation) { switchDialogMsg.append(getString( R.string.sim_action_switch_sub_dialog_text_downloaded, subInfoName, activeSubName)); } else if (mIsEsimOperation) { switchDialogMsg.append(getString( R.string.sim_action_switch_sub_dialog_text, subInfoName, activeSubName)); } else { switchDialogMsg.append(getString( R.string.sim_action_switch_sub_dialog_text_single_sim, activeSubName)); } if (isRtlMode) { /* There are two lines of message in the dialog, and the RTL symbols must be added * before and after each sentence, so use the line break symbol to find the position. * (Each message are all with two line break symbols) */ switchDialogMsg.insert(0, RTL_MARK) .insert(switchDialogMsg.indexOf(LINE_BREAK) - LINE_BREAK_OFFSET_ONE, RTL_MARK) .insert(switchDialogMsg.indexOf(LINE_BREAK) + LINE_BREAK_OFFSET_TWO, RTL_MARK) .insert(switchDialogMsg.length(), RTL_MARK); } return switchDialogMsg.toString(); } private ArrayList getSwitchDialogBodyList() { ArrayList list = new ArrayList(mActiveSubInfos.stream() .map(subInfo -> { CharSequence subInfoName = SubscriptionUtil.getUniqueSubscriptionDisplayName( subInfo, this); return getString( R.string.sim_action_switch_sub_dialog_carrier_list_item_for_turning_off, subInfoName); }) .collect(Collectors.toList())); list.add(getString(R.string.sim_action_cancel)); return list; } private boolean isDsdsConditionSatisfied() { if (mTelMgr.isMultiSimEnabled()) { Log.d(TAG, "DSDS is already enabled. Condition not satisfied."); return false; } if (mTelMgr.isMultiSimSupported() != TelephonyManager.MULTISIM_ALLOWED) { Log.d(TAG, "Hardware does not support DSDS."); return false; } boolean isActiveSim = SubscriptionUtil.getActiveSubscriptions( mSubscriptionManager).size() > 0; if (isMultipleEnabledProfilesSupported() && isActiveSim) { Log.d(TAG, "Device supports MEP and eSIM operation and eSIM profile is enabled." + " DSDS condition satisfied."); return true; } boolean isRemovableSimEnabled = isRemovableSimEnabled(); if (mIsEsimOperation && isRemovableSimEnabled) { Log.d(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.d(TAG, "Removable SIM operation and eSIM profile is enabled. DSDS condition" + " satisfied."); return true; } Log.d(TAG, "DSDS condition not satisfied."); return false; } private boolean isRemovableSimEnabled() { return UiccSlotUtil.isRemovableSimEnabled(mTelMgr); } private boolean isMultipleEnabledProfilesSupported() { List cardInfos = mTelMgr.getUiccCardsInfo(); if (cardInfos == null) { Log.w(TAG, "UICC cards info list is empty."); return false; } return cardInfos.stream().anyMatch( cardInfo -> cardInfo.isMultipleEnabledProfilesSupported()); } }