Files
app_Settings/src/com/android/settings/sim/receivers/SimSlotChangeHandler.java
songferngwang 6ab74ab24c Show sim onboarding for user when insert psim
When the user inserts the psim, showing the sim onboarding for the user
to setup the sim switching or the default data subscription.

Bug: 336488573
Test: verify UI
Change-Id: I1faab0a3166efab3f1fd82d6a69ab4d33b624556
2024-05-15 12:14:37 +00:00

411 lines
17 KiB
Java

/*
* 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.sim.receivers;
import static android.content.Context.MODE_PRIVATE;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Looper;
import android.os.UserHandle;
import android.provider.Settings;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.UiccCardInfo;
import android.telephony.UiccSlotInfo;
import android.util.Log;
import com.android.settings.flags.Flags;
import com.android.settings.network.SubscriptionUtil;
import com.android.settings.network.UiccSlotUtil;
import com.android.settings.network.UiccSlotsException;
import com.android.settings.sim.ChooseSimActivity;
import com.android.settings.sim.DsdsDialogActivity;
import com.android.settings.sim.SimActivationNotifier;
import com.android.settings.sim.SimNotificationService;
import com.android.settings.sim.SwitchToEsimConfirmDialogActivity;
import com.google.common.collect.ImmutableList;
import java.util.List;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
/** Perform actions after a slot change event is triggered. */
public class SimSlotChangeHandler {
private static final String TAG = "SimSlotChangeHandler";
private static final String EUICC_PREFS = "euicc_prefs";
// Shared preference keys
private static final String KEY_REMOVABLE_SLOT_STATE = "removable_slot_state";
private static final String KEY_SUW_PSIM_ACTION = "suw_psim_action";
// User's last removable SIM insertion / removal action during SUW.
private static final int LAST_USER_ACTION_IN_SUW_NONE = 0;
private static final int LAST_USER_ACTION_IN_SUW_INSERT = 1;
private static final int LAST_USER_ACTION_IN_SUW_REMOVE = 2;
private static volatile SimSlotChangeHandler sSlotChangeHandler;
/** Returns a SIM slot change handler singleton. */
public static SimSlotChangeHandler get() {
if (sSlotChangeHandler == null) {
synchronized (SimSlotChangeHandler.class) {
if (sSlotChangeHandler == null) {
sSlotChangeHandler = new SimSlotChangeHandler();
}
}
}
return sSlotChangeHandler;
}
private SubscriptionManager mSubMgr;
private TelephonyManager mTelMgr;
private Context mContext;
void onSlotsStatusChange(Context context) {
init(context);
if (Looper.myLooper() == Looper.getMainLooper()) {
throw new IllegalStateException("Cannot be called from main thread.");
}
UiccSlotInfo removableSlotInfo = getRemovableUiccSlotInfo();
if (removableSlotInfo == null) {
Log.e(TAG, "Unable to find the removable slot. Do nothing.");
return;
}
int lastRemovableSlotState = getLastRemovableSimSlotState(mContext);
int currentRemovableSlotState = removableSlotInfo.getCardStateInfo();
Log.i(TAG,
"lastRemovableSlotState: " + lastRemovableSlotState + ",currentRemovableSlotState: "
+ currentRemovableSlotState);
boolean isRemovableSimInserted =
lastRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_ABSENT
&& currentRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_PRESENT;
boolean isRemovableSimRemoved =
lastRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_PRESENT
&& currentRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_ABSENT;
// Sets the current removable slot state.
setRemovableSimSlotState(mContext, currentRemovableSlotState);
if (mTelMgr.getActiveModemCount() > 1) {
if (!isRemovableSimInserted) {
Log.d(TAG, "Removable Sim is not inserted in DSDS mode. Do nothing.");
return;
}
if (Flags.isDualSimOnboardingEnabled()) {
// ForNewUi, when the user inserts the psim, showing the sim onboarding for the user
// to setup the sim switching or the default data subscription.
handleRemovableSimInsertWhenDsds();
} else if (!isMultipleEnabledProfilesSupported()) {
Log.d(TAG, "The device is already in DSDS mode and no MEP. Do nothing.");
return;
} else if (isMultipleEnabledProfilesSupported()) {
handleRemovableSimInsertUnderDsdsMep(removableSlotInfo);
return;
}
Log.d(TAG, "the device is already in DSDS mode and have the DDS. Do nothing.");
return;
}
if (isRemovableSimInserted) {
handleSimInsert(removableSlotInfo);
return;
}
if (isRemovableSimRemoved) {
handleSimRemove(removableSlotInfo);
return;
}
Log.i(TAG, "Do nothing on slot status changes.");
}
void onSuwFinish(Context context) {
init(context);
if (Looper.myLooper() == Looper.getMainLooper()) {
throw new IllegalStateException("Cannot be called from main thread.");
}
if (mTelMgr.getActiveModemCount() > 1) {
Log.i(TAG, "The device is already in DSDS mode. Do nothing.");
return;
}
UiccSlotInfo removableSlotInfo = getRemovableUiccSlotInfo();
if (removableSlotInfo == null) {
Log.e(TAG, "Unable to find the removable slot. Do nothing.");
return;
}
boolean embeddedSimExist = getGroupedEmbeddedSubscriptions().size() != 0;
int removableSlotAction = getSuwRemovableSlotAction(mContext);
setSuwRemovableSlotAction(mContext, LAST_USER_ACTION_IN_SUW_NONE);
if (embeddedSimExist
&& removableSlotInfo.getCardStateInfo() == UiccSlotInfo.CARD_STATE_INFO_PRESENT) {
if (mTelMgr.isMultiSimSupported() == TelephonyManager.MULTISIM_ALLOWED) {
Log.i(TAG, "DSDS condition satisfied. Show notification.");
SimNotificationService.scheduleSimNotification(
mContext, SimActivationNotifier.NotificationType.ENABLE_DSDS);
} else if (removableSlotAction == LAST_USER_ACTION_IN_SUW_INSERT) {
Log.i(
TAG,
"Both removable SIM and eSIM are present. DSDS condition doesn't"
+ " satisfied. User inserted pSIM during SUW. Show choose SIM"
+ " screen.");
startChooseSimActivity(true);
}
} else if (removableSlotAction == LAST_USER_ACTION_IN_SUW_REMOVE) {
handleSimRemove(removableSlotInfo);
}
}
private void init(Context context) {
mSubMgr =
(SubscriptionManager)
context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
mTelMgr = context.getSystemService(TelephonyManager.class);
mContext = context;
}
private void handleSimInsert(UiccSlotInfo removableSlotInfo) {
Log.i(TAG, "Handle SIM inserted.");
if (!isSuwFinished(mContext)) {
Log.i(TAG, "Still in SUW. Handle SIM insertion after SUW is finished");
setSuwRemovableSlotAction(mContext, LAST_USER_ACTION_IN_SUW_INSERT);
return;
}
if (removableSlotInfo.getPorts().stream().findFirst().get().isActive()) {
Log.i(TAG, "The removable slot is already active. Do nothing.");
return;
}
if (hasActiveEsimSubscription()) {
if (mTelMgr.isMultiSimSupported() == TelephonyManager.MULTISIM_ALLOWED) {
Log.i(TAG, "Enabled profile exists. DSDS condition satisfied.");
startDsdsDialogActivity();
} else {
Log.i(TAG, "Enabled profile exists. DSDS condition not satisfied.");
startChooseSimActivity(true);
}
return;
}
Log.i(
TAG,
"No enabled eSIM profile. Ready to switch to removable slot and show"
+ " notification.");
try {
UiccSlotUtil.switchToRemovableSlot(
UiccSlotUtil.INVALID_PHYSICAL_SLOT_ID, mContext.getApplicationContext());
} catch (UiccSlotsException e) {
Log.e(TAG, "Failed to switch to removable slot.");
return;
}
SimNotificationService.scheduleSimNotification(
mContext, SimActivationNotifier.NotificationType.SWITCH_TO_REMOVABLE_SLOT);
}
private void handleSimRemove(UiccSlotInfo removableSlotInfo) {
Log.i(TAG, "Handle SIM removed.");
if (!isSuwFinished(mContext)) {
Log.i(TAG, "Still in SUW. Handle SIM removal after SUW is finished");
setSuwRemovableSlotAction(mContext, LAST_USER_ACTION_IN_SUW_REMOVE);
return;
}
List<SubscriptionInfo> groupedEmbeddedSubscriptions = getGroupedEmbeddedSubscriptions();
if (groupedEmbeddedSubscriptions.size() == 0 || !removableSlotInfo.getPorts().stream()
.findFirst().get().isActive()) {
Log.i(TAG, "eSIM slot is active or no subscriptions exist. Do nothing."
+ " The removableSlotInfo: " + removableSlotInfo
+ ", groupedEmbeddedSubscriptions: " + groupedEmbeddedSubscriptions);
return;
}
// If there is only 1 eSIM profile exists, we ask the user if they want to switch to that
// profile.
if (groupedEmbeddedSubscriptions.size() == 1) {
Log.i(TAG, "Only 1 eSIM profile found. Ask user's consent to switch.");
startSwitchSlotConfirmDialogActivity(groupedEmbeddedSubscriptions.get(0));
return;
}
// If there are more than 1 eSIM profiles installed, we show a screen to let users to choose
// the number they want to use.
Log.i(TAG, "Multiple eSIM profiles found. Ask user which subscription to use.");
startChooseSimActivity(false);
}
private void handleRemovableSimInsertWhenDsds() {
List<SubscriptionInfo> subscriptionInfos = getAvailableRemovableSubscription();
if (subscriptionInfos.isEmpty()) {
Log.e(TAG, "Unable to find the removable subscriptionInfo. Do nothing.");
return;
}
Log.d(TAG, "ForNewUi and getAvailableRemovableSubscription:"
+ subscriptionInfos);
startSimConfirmDialogActivity(subscriptionInfos.get(0).getSubscriptionId());
}
private void handleRemovableSimInsertUnderDsdsMep(UiccSlotInfo removableSlotInfo) {
Log.i(TAG, "Handle Removable SIM inserted under DSDS+Mep.");
if (removableSlotInfo.getPorts().stream().findFirst().get().isActive()) {
Log.i(TAG, "The removable slot is already active. Do nothing. removableSlotInfo: "
+ removableSlotInfo);
return;
}
List<SubscriptionInfo> subscriptionInfos = getAvailableRemovableSubscription();
if (subscriptionInfos.isEmpty()) {
Log.e(TAG, "Unable to find the removable subscriptionInfo. Do nothing.");
return;
}
Log.d(TAG, "getAvailableRemovableSubscription:" + subscriptionInfos);
startSimConfirmDialogActivity(subscriptionInfos.get(0).getSubscriptionId());
}
private int getLastRemovableSimSlotState(Context context) {
final SharedPreferences prefs = context.getSharedPreferences(EUICC_PREFS, MODE_PRIVATE);
return prefs.getInt(KEY_REMOVABLE_SLOT_STATE, UiccSlotInfo.CARD_STATE_INFO_ABSENT);
}
private void setRemovableSimSlotState(Context context, int state) {
final SharedPreferences prefs = context.getSharedPreferences(EUICC_PREFS, MODE_PRIVATE);
prefs.edit().putInt(KEY_REMOVABLE_SLOT_STATE, state).apply();
Log.d(TAG, "setRemovableSimSlotState: " + state);
}
private int getSuwRemovableSlotAction(Context context) {
final SharedPreferences prefs = context.getSharedPreferences(EUICC_PREFS, MODE_PRIVATE);
return prefs.getInt(KEY_SUW_PSIM_ACTION, LAST_USER_ACTION_IN_SUW_NONE);
}
private void setSuwRemovableSlotAction(Context context, int action) {
final SharedPreferences prefs = context.getSharedPreferences(EUICC_PREFS, MODE_PRIVATE);
prefs.edit().putInt(KEY_SUW_PSIM_ACTION, action).apply();
}
@Nullable
private UiccSlotInfo getRemovableUiccSlotInfo() {
UiccSlotInfo[] slotInfos = mTelMgr.getUiccSlotsInfo();
if (slotInfos == null) {
Log.e(TAG, "slotInfos is null. Unable to get slot infos.");
return null;
}
for (UiccSlotInfo slotInfo : slotInfos) {
if (slotInfo != null && slotInfo.isRemovable()) {
return slotInfo;
}
}
return null;
}
private static boolean isSuwFinished(Context context) {
try {
// DEVICE_PROVISIONED is 0 if still in setup wizard. 1 if setup completed.
return Settings.Global.getInt(
context.getContentResolver(), Settings.Global.DEVICE_PROVISIONED)
== 1;
} catch (Settings.SettingNotFoundException e) {
Log.e(TAG, "Cannot get DEVICE_PROVISIONED from the device.", e);
return false;
}
}
private boolean hasActiveEsimSubscription() {
List<SubscriptionInfo> activeSubs = SubscriptionUtil.getActiveSubscriptions(mSubMgr);
return activeSubs.stream().anyMatch(SubscriptionInfo::isEmbedded);
}
private List<SubscriptionInfo> getGroupedEmbeddedSubscriptions() {
List<SubscriptionInfo> groupedSubscriptions =
SubscriptionUtil.getSelectableSubscriptionInfoList(mContext);
if (groupedSubscriptions == null) {
return ImmutableList.of();
}
return ImmutableList.copyOf(
groupedSubscriptions.stream()
.filter(sub -> sub.isEmbedded())
.collect(Collectors.toList()));
}
protected List<SubscriptionInfo> getAvailableRemovableSubscription() {
List<SubscriptionInfo> removableSubscriptions =
SubscriptionUtil.getAvailableSubscriptions(mContext);
return ImmutableList.copyOf(
removableSubscriptions.stream()
// ToDo: This condition is for psim only. If device supports removable
// esim, it needs an new condition.
.filter(sub -> !sub.isEmbedded())
.collect(Collectors.toList()));
}
private void startChooseSimActivity(boolean psimInserted) {
Intent intent = ChooseSimActivity.getIntent(mContext);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(ChooseSimActivity.KEY_HAS_PSIM, psimInserted);
mContext.startActivityAsUser(intent, UserHandle.SYSTEM);
}
private void startSwitchSlotConfirmDialogActivity(SubscriptionInfo subscriptionInfo) {
Intent intent = new Intent(mContext, SwitchToEsimConfirmDialogActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(SwitchToEsimConfirmDialogActivity.KEY_SUB_TO_ENABLE, subscriptionInfo);
mContext.startActivityAsUser(intent, UserHandle.SYSTEM);
}
private void startDsdsDialogActivity() {
Intent intent = new Intent(mContext, DsdsDialogActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivityAsUser(intent, UserHandle.SYSTEM);
}
private void startSimConfirmDialogActivity(int subId) {
if (!isSuwFinished(mContext)) {
Log.d(TAG, "Still in SUW. Do nothing");
return;
}
if (!SubscriptionManager.isUsableSubscriptionId(subId)) {
Log.i(TAG, "Unable to enable subscription due to invalid subscription ID.");
return;
}
Log.d(TAG, "Start ToggleSubscriptionDialogActivity with " + subId + " under DSDS+Mep.");
SubscriptionUtil.startToggleSubscriptionDialogActivity(mContext, subId, true);
}
private boolean isMultipleEnabledProfilesSupported() {
List<UiccCardInfo> cardInfos = mTelMgr.getUiccCardsInfo();
if (cardInfos == null) {
Log.d(TAG, "UICC cards info list is empty.");
return false;
}
return cardInfos.stream().anyMatch(
cardInfo -> cardInfo.isMultipleEnabledProfilesSupported());
}
private SimSlotChangeHandler() {}
}