1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.sim.receivers; 18 19 import static android.content.Context.MODE_PRIVATE; 20 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.SharedPreferences; 24 import android.os.Looper; 25 import android.os.UserHandle; 26 import android.provider.Settings; 27 import android.telephony.SubscriptionInfo; 28 import android.telephony.SubscriptionManager; 29 import android.telephony.TelephonyManager; 30 import android.telephony.UiccCardInfo; 31 import android.telephony.UiccSlotInfo; 32 import android.util.Log; 33 34 import com.android.settings.flags.Flags; 35 import com.android.settings.network.SubscriptionUtil; 36 import com.android.settings.network.UiccSlotUtil; 37 import com.android.settings.network.UiccSlotsException; 38 import com.android.settings.sim.ChooseSimActivity; 39 import com.android.settings.sim.DsdsDialogActivity; 40 import com.android.settings.sim.SimActivationNotifier; 41 import com.android.settings.sim.SimNotificationService; 42 import com.android.settings.sim.SwitchToEsimConfirmDialogActivity; 43 44 import com.google.common.collect.ImmutableList; 45 46 import java.util.List; 47 import java.util.stream.Collectors; 48 49 import javax.annotation.Nullable; 50 51 /** Perform actions after a slot change event is triggered. */ 52 public class SimSlotChangeHandler { 53 private static final String TAG = "SimSlotChangeHandler"; 54 55 private static final String EUICC_PREFS = "euicc_prefs"; 56 // Shared preference keys 57 private static final String KEY_REMOVABLE_SLOT_STATE = "removable_slot_state"; 58 private static final String KEY_SUW_PSIM_ACTION = "suw_psim_action"; 59 // User's last removable SIM insertion / removal action during SUW. 60 private static final int LAST_USER_ACTION_IN_SUW_NONE = 0; 61 private static final int LAST_USER_ACTION_IN_SUW_INSERT = 1; 62 private static final int LAST_USER_ACTION_IN_SUW_REMOVE = 2; 63 64 private static volatile SimSlotChangeHandler sSlotChangeHandler; 65 66 /** Returns a SIM slot change handler singleton. */ get()67 public static SimSlotChangeHandler get() { 68 if (sSlotChangeHandler == null) { 69 synchronized (SimSlotChangeHandler.class) { 70 if (sSlotChangeHandler == null) { 71 sSlotChangeHandler = new SimSlotChangeHandler(); 72 } 73 } 74 } 75 return sSlotChangeHandler; 76 } 77 78 private SubscriptionManager mSubMgr; 79 private TelephonyManager mTelMgr; 80 private Context mContext; 81 onSlotsStatusChange(Context context)82 void onSlotsStatusChange(Context context) { 83 init(context); 84 85 if (Looper.myLooper() == Looper.getMainLooper()) { 86 throw new IllegalStateException("Cannot be called from main thread."); 87 } 88 89 UiccSlotInfo removableSlotInfo = getRemovableUiccSlotInfo(); 90 if (removableSlotInfo == null) { 91 Log.e(TAG, "Unable to find the removable slot. Do nothing."); 92 return; 93 } 94 95 int lastRemovableSlotState = getLastRemovableSimSlotState(mContext); 96 int currentRemovableSlotState = removableSlotInfo.getCardStateInfo(); 97 Log.i(TAG, 98 "lastRemovableSlotState: " + lastRemovableSlotState + ",currentRemovableSlotState: " 99 + currentRemovableSlotState); 100 boolean isRemovableSimInserted = 101 lastRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_ABSENT 102 && currentRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_PRESENT; 103 boolean isRemovableSimRemoved = 104 lastRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_PRESENT 105 && currentRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_ABSENT; 106 107 // Sets the current removable slot state. 108 setRemovableSimSlotState(mContext, currentRemovableSlotState); 109 110 if (mTelMgr.getActiveModemCount() > 1) { 111 if (!isRemovableSimInserted) { 112 Log.d(TAG, "Removable Sim is not inserted in DSDS mode. Do nothing."); 113 return; 114 } 115 116 if (Flags.isDualSimOnboardingEnabled()) { 117 // ForNewUi, when the user inserts the psim, showing the sim onboarding for the user 118 // to setup the sim switching or the default data subscription. 119 handleRemovableSimInsertWhenDsds(); 120 } else if (!isMultipleEnabledProfilesSupported()) { 121 Log.d(TAG, "The device is already in DSDS mode and no MEP. Do nothing."); 122 return; 123 } else if (isMultipleEnabledProfilesSupported()) { 124 handleRemovableSimInsertUnderDsdsMep(removableSlotInfo); 125 return; 126 } 127 Log.d(TAG, "the device is already in DSDS mode and have the DDS. Do nothing."); 128 return; 129 } 130 131 if (isRemovableSimInserted) { 132 handleSimInsert(removableSlotInfo); 133 return; 134 } 135 if (isRemovableSimRemoved) { 136 handleSimRemove(removableSlotInfo); 137 return; 138 } 139 Log.i(TAG, "Do nothing on slot status changes."); 140 } 141 onSuwFinish(Context context)142 void onSuwFinish(Context context) { 143 init(context); 144 145 if (Looper.myLooper() == Looper.getMainLooper()) { 146 throw new IllegalStateException("Cannot be called from main thread."); 147 } 148 149 if (mTelMgr.getActiveModemCount() > 1) { 150 Log.i(TAG, "The device is already in DSDS mode. Do nothing."); 151 return; 152 } 153 154 UiccSlotInfo removableSlotInfo = getRemovableUiccSlotInfo(); 155 if (removableSlotInfo == null) { 156 Log.e(TAG, "Unable to find the removable slot. Do nothing."); 157 return; 158 } 159 160 boolean embeddedSimExist = getGroupedEmbeddedSubscriptions().size() != 0; 161 int removableSlotAction = getSuwRemovableSlotAction(mContext); 162 setSuwRemovableSlotAction(mContext, LAST_USER_ACTION_IN_SUW_NONE); 163 164 if (embeddedSimExist 165 && removableSlotInfo.getCardStateInfo() == UiccSlotInfo.CARD_STATE_INFO_PRESENT) { 166 if (mTelMgr.isMultiSimSupported() == TelephonyManager.MULTISIM_ALLOWED) { 167 Log.i(TAG, "DSDS condition satisfied. Show notification."); 168 SimNotificationService.scheduleSimNotification( 169 mContext, SimActivationNotifier.NotificationType.ENABLE_DSDS); 170 } else if (removableSlotAction == LAST_USER_ACTION_IN_SUW_INSERT) { 171 Log.i( 172 TAG, 173 "Both removable SIM and eSIM are present. DSDS condition doesn't" 174 + " satisfied. User inserted pSIM during SUW. Show choose SIM" 175 + " screen."); 176 startChooseSimActivity(true); 177 } 178 } else if (removableSlotAction == LAST_USER_ACTION_IN_SUW_REMOVE) { 179 handleSimRemove(removableSlotInfo); 180 } 181 } 182 init(Context context)183 private void init(Context context) { 184 mSubMgr = 185 (SubscriptionManager) 186 context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); 187 mTelMgr = context.getSystemService(TelephonyManager.class); 188 mContext = context; 189 } 190 handleSimInsert(UiccSlotInfo removableSlotInfo)191 private void handleSimInsert(UiccSlotInfo removableSlotInfo) { 192 Log.i(TAG, "Handle SIM inserted."); 193 if (!isSuwFinished(mContext)) { 194 Log.i(TAG, "Still in SUW. Handle SIM insertion after SUW is finished"); 195 setSuwRemovableSlotAction(mContext, LAST_USER_ACTION_IN_SUW_INSERT); 196 return; 197 } 198 if (removableSlotInfo.getPorts().stream().findFirst().get().isActive()) { 199 Log.i(TAG, "The removable slot is already active. Do nothing."); 200 return; 201 } 202 203 if (hasActiveEsimSubscription()) { 204 if (mTelMgr.isMultiSimSupported() == TelephonyManager.MULTISIM_ALLOWED) { 205 Log.i(TAG, "Enabled profile exists. DSDS condition satisfied."); 206 startDsdsDialogActivity(); 207 } else { 208 Log.i(TAG, "Enabled profile exists. DSDS condition not satisfied."); 209 startChooseSimActivity(true); 210 } 211 return; 212 } 213 214 Log.i( 215 TAG, 216 "No enabled eSIM profile. Ready to switch to removable slot and show" 217 + " notification."); 218 try { 219 UiccSlotUtil.switchToRemovableSlot( 220 UiccSlotUtil.INVALID_PHYSICAL_SLOT_ID, mContext.getApplicationContext()); 221 } catch (UiccSlotsException e) { 222 Log.e(TAG, "Failed to switch to removable slot."); 223 return; 224 } 225 SimNotificationService.scheduleSimNotification( 226 mContext, SimActivationNotifier.NotificationType.SWITCH_TO_REMOVABLE_SLOT); 227 } 228 handleSimRemove(UiccSlotInfo removableSlotInfo)229 private void handleSimRemove(UiccSlotInfo removableSlotInfo) { 230 Log.i(TAG, "Handle SIM removed."); 231 232 if (!isSuwFinished(mContext)) { 233 Log.i(TAG, "Still in SUW. Handle SIM removal after SUW is finished"); 234 setSuwRemovableSlotAction(mContext, LAST_USER_ACTION_IN_SUW_REMOVE); 235 return; 236 } 237 238 List<SubscriptionInfo> groupedEmbeddedSubscriptions = getGroupedEmbeddedSubscriptions(); 239 if (groupedEmbeddedSubscriptions.size() == 0 || !removableSlotInfo.getPorts().stream() 240 .findFirst().get().isActive()) { 241 Log.i(TAG, "eSIM slot is active or no subscriptions exist. Do nothing." 242 + " The removableSlotInfo: " + removableSlotInfo 243 + ", groupedEmbeddedSubscriptions: " + groupedEmbeddedSubscriptions); 244 return; 245 } 246 247 // If there is only 1 eSIM profile exists, we ask the user if they want to switch to that 248 // profile. 249 if (groupedEmbeddedSubscriptions.size() == 1) { 250 Log.i(TAG, "Only 1 eSIM profile found. Ask user's consent to switch."); 251 startSwitchSlotConfirmDialogActivity(groupedEmbeddedSubscriptions.get(0)); 252 return; 253 } 254 255 // If there are more than 1 eSIM profiles installed, we show a screen to let users to choose 256 // the number they want to use. 257 Log.i(TAG, "Multiple eSIM profiles found. Ask user which subscription to use."); 258 startChooseSimActivity(false); 259 } 260 handleRemovableSimInsertWhenDsds()261 private void handleRemovableSimInsertWhenDsds() { 262 List<SubscriptionInfo> subscriptionInfos = getAvailableRemovableSubscription(); 263 if (subscriptionInfos.isEmpty()) { 264 Log.e(TAG, "Unable to find the removable subscriptionInfo. Do nothing."); 265 return; 266 } 267 Log.d(TAG, "ForNewUi and getAvailableRemovableSubscription:" 268 + subscriptionInfos); 269 startSimConfirmDialogActivity(subscriptionInfos.get(0).getSubscriptionId()); 270 } 271 handleRemovableSimInsertUnderDsdsMep(UiccSlotInfo removableSlotInfo)272 private void handleRemovableSimInsertUnderDsdsMep(UiccSlotInfo removableSlotInfo) { 273 Log.i(TAG, "Handle Removable SIM inserted under DSDS+Mep."); 274 275 if (removableSlotInfo.getPorts().stream().findFirst().get().isActive()) { 276 Log.i(TAG, "The removable slot is already active. Do nothing. removableSlotInfo: " 277 + removableSlotInfo); 278 return; 279 } 280 281 List<SubscriptionInfo> subscriptionInfos = getAvailableRemovableSubscription(); 282 if (subscriptionInfos.isEmpty()) { 283 Log.e(TAG, "Unable to find the removable subscriptionInfo. Do nothing."); 284 return; 285 } 286 Log.d(TAG, "getAvailableRemovableSubscription:" + subscriptionInfos); 287 startSimConfirmDialogActivity(subscriptionInfos.get(0).getSubscriptionId()); 288 } 289 getLastRemovableSimSlotState(Context context)290 private int getLastRemovableSimSlotState(Context context) { 291 final SharedPreferences prefs = context.getSharedPreferences(EUICC_PREFS, MODE_PRIVATE); 292 return prefs.getInt(KEY_REMOVABLE_SLOT_STATE, UiccSlotInfo.CARD_STATE_INFO_ABSENT); 293 } 294 setRemovableSimSlotState(Context context, int state)295 private void setRemovableSimSlotState(Context context, int state) { 296 final SharedPreferences prefs = context.getSharedPreferences(EUICC_PREFS, MODE_PRIVATE); 297 prefs.edit().putInt(KEY_REMOVABLE_SLOT_STATE, state).apply(); 298 Log.d(TAG, "setRemovableSimSlotState: " + state); 299 } 300 getSuwRemovableSlotAction(Context context)301 private int getSuwRemovableSlotAction(Context context) { 302 final SharedPreferences prefs = context.getSharedPreferences(EUICC_PREFS, MODE_PRIVATE); 303 return prefs.getInt(KEY_SUW_PSIM_ACTION, LAST_USER_ACTION_IN_SUW_NONE); 304 } 305 setSuwRemovableSlotAction(Context context, int action)306 private void setSuwRemovableSlotAction(Context context, int action) { 307 final SharedPreferences prefs = context.getSharedPreferences(EUICC_PREFS, MODE_PRIVATE); 308 prefs.edit().putInt(KEY_SUW_PSIM_ACTION, action).apply(); 309 } 310 311 @Nullable getRemovableUiccSlotInfo()312 private UiccSlotInfo getRemovableUiccSlotInfo() { 313 UiccSlotInfo[] slotInfos = mTelMgr.getUiccSlotsInfo(); 314 if (slotInfos == null) { 315 Log.e(TAG, "slotInfos is null. Unable to get slot infos."); 316 return null; 317 } 318 for (UiccSlotInfo slotInfo : slotInfos) { 319 if (slotInfo != null && slotInfo.isRemovable()) { 320 return slotInfo; 321 } 322 } 323 return null; 324 } 325 isSuwFinished(Context context)326 private static boolean isSuwFinished(Context context) { 327 try { 328 // DEVICE_PROVISIONED is 0 if still in setup wizard. 1 if setup completed. 329 return Settings.Global.getInt( 330 context.getContentResolver(), Settings.Global.DEVICE_PROVISIONED) 331 == 1; 332 } catch (Settings.SettingNotFoundException e) { 333 Log.e(TAG, "Cannot get DEVICE_PROVISIONED from the device.", e); 334 return false; 335 } 336 } 337 hasActiveEsimSubscription()338 private boolean hasActiveEsimSubscription() { 339 List<SubscriptionInfo> activeSubs = SubscriptionUtil.getActiveSubscriptions(mSubMgr); 340 return activeSubs.stream().anyMatch(SubscriptionInfo::isEmbedded); 341 } 342 getGroupedEmbeddedSubscriptions()343 private List<SubscriptionInfo> getGroupedEmbeddedSubscriptions() { 344 List<SubscriptionInfo> groupedSubscriptions = 345 SubscriptionUtil.getSelectableSubscriptionInfoList(mContext); 346 if (groupedSubscriptions == null) { 347 return ImmutableList.of(); 348 } 349 return ImmutableList.copyOf( 350 groupedSubscriptions.stream() 351 .filter(sub -> sub.isEmbedded()) 352 .collect(Collectors.toList())); 353 } 354 getAvailableRemovableSubscription()355 protected List<SubscriptionInfo> getAvailableRemovableSubscription() { 356 List<SubscriptionInfo> removableSubscriptions = 357 SubscriptionUtil.getAvailableSubscriptions(mContext); 358 return ImmutableList.copyOf( 359 removableSubscriptions.stream() 360 // ToDo: This condition is for psim only. If device supports removable 361 // esim, it needs an new condition. 362 .filter(sub -> !sub.isEmbedded()) 363 .collect(Collectors.toList())); 364 } 365 startChooseSimActivity(boolean psimInserted)366 private void startChooseSimActivity(boolean psimInserted) { 367 Intent intent = ChooseSimActivity.getIntent(mContext); 368 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 369 intent.putExtra(ChooseSimActivity.KEY_HAS_PSIM, psimInserted); 370 mContext.startActivityAsUser(intent, UserHandle.SYSTEM); 371 } 372 startSwitchSlotConfirmDialogActivity(SubscriptionInfo subscriptionInfo)373 private void startSwitchSlotConfirmDialogActivity(SubscriptionInfo subscriptionInfo) { 374 Intent intent = new Intent(mContext, SwitchToEsimConfirmDialogActivity.class); 375 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 376 intent.putExtra(SwitchToEsimConfirmDialogActivity.KEY_SUB_TO_ENABLE, subscriptionInfo); 377 mContext.startActivityAsUser(intent, UserHandle.SYSTEM); 378 } 379 startDsdsDialogActivity()380 private void startDsdsDialogActivity() { 381 Intent intent = new Intent(mContext, DsdsDialogActivity.class); 382 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 383 mContext.startActivityAsUser(intent, UserHandle.SYSTEM); 384 } 385 startSimConfirmDialogActivity(int subId)386 private void startSimConfirmDialogActivity(int subId) { 387 if (!isSuwFinished(mContext)) { 388 Log.d(TAG, "Still in SUW. Do nothing"); 389 return; 390 } 391 if (!SubscriptionManager.isUsableSubscriptionId(subId)) { 392 Log.i(TAG, "Unable to enable subscription due to invalid subscription ID."); 393 return; 394 } 395 Log.d(TAG, "Start ToggleSubscriptionDialogActivity with " + subId + " under DSDS+Mep."); 396 SubscriptionUtil.startToggleSubscriptionDialogActivity(mContext, subId, true); 397 } 398 isMultipleEnabledProfilesSupported()399 private boolean isMultipleEnabledProfilesSupported() { 400 List<UiccCardInfo> cardInfos = mTelMgr.getUiccCardsInfo(); 401 if (cardInfos == null) { 402 Log.d(TAG, "UICC cards info list is empty."); 403 return false; 404 } 405 return cardInfos.stream().anyMatch( 406 cardInfo -> cardInfo.isMultipleEnabledProfilesSupported()); 407 } 408 SimSlotChangeHandler()409 private SimSlotChangeHandler() {} 410 } 411