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.provider.Settings; 26 import android.telephony.SubscriptionInfo; 27 import android.telephony.SubscriptionManager; 28 import android.telephony.TelephonyManager; 29 import android.telephony.UiccSlotInfo; 30 import android.util.Log; 31 32 import com.android.settings.network.SubscriptionUtil; 33 import com.android.settings.network.UiccSlotUtil; 34 import com.android.settings.network.UiccSlotsException; 35 import com.android.settings.sim.ChooseSimActivity; 36 import com.android.settings.sim.DsdsDialogActivity; 37 import com.android.settings.sim.SimActivationNotifier; 38 import com.android.settings.sim.SimNotificationService; 39 import com.android.settings.sim.SwitchToEsimConfirmDialogActivity; 40 41 import com.google.common.collect.ImmutableList; 42 43 import java.util.List; 44 import java.util.stream.Collectors; 45 46 import javax.annotation.Nullable; 47 48 /** Perform actions after a slot change event is triggered. */ 49 public class SimSlotChangeHandler { 50 private static final String TAG = "SimSlotChangeHandler"; 51 52 private static final String EUICC_PREFS = "euicc_prefs"; 53 // Shared preference keys 54 private static final String KEY_REMOVABLE_SLOT_STATE = "removable_slot_state"; 55 private static final String KEY_SUW_PSIM_ACTION = "suw_psim_action"; 56 // User's last removable SIM insertion / removal action during SUW. 57 private static final int LAST_USER_ACTION_IN_SUW_NONE = 0; 58 private static final int LAST_USER_ACTION_IN_SUW_INSERT = 1; 59 private static final int LAST_USER_ACTION_IN_SUW_REMOVE = 2; 60 61 private static volatile SimSlotChangeHandler sSlotChangeHandler; 62 63 /** Returns a SIM slot change handler singleton. */ get()64 public static SimSlotChangeHandler get() { 65 if (sSlotChangeHandler == null) { 66 synchronized (SimSlotChangeHandler.class) { 67 if (sSlotChangeHandler == null) { 68 sSlotChangeHandler = new SimSlotChangeHandler(); 69 } 70 } 71 } 72 return sSlotChangeHandler; 73 } 74 75 private SubscriptionManager mSubMgr; 76 private TelephonyManager mTelMgr; 77 private Context mContext; 78 onSlotsStatusChange(Context context)79 void onSlotsStatusChange(Context context) { 80 init(context); 81 82 if (Looper.myLooper() == Looper.getMainLooper()) { 83 throw new IllegalStateException("Cannot be called from main thread."); 84 } 85 86 if (mTelMgr.getActiveModemCount() > 1) { 87 Log.i(TAG, "The device is already in DSDS mode. Do nothing."); 88 return; 89 } 90 91 UiccSlotInfo removableSlotInfo = getRemovableUiccSlotInfo(); 92 if (removableSlotInfo == null) { 93 Log.e(TAG, "Unable to find the removable slot. Do nothing."); 94 return; 95 } 96 97 int lastRemovableSlotState = getLastRemovableSimSlotState(mContext); 98 int currentRemovableSlotState = removableSlotInfo.getCardStateInfo(); 99 100 // Sets the current removable slot state. 101 setRemovableSimSlotState(mContext, currentRemovableSlotState); 102 103 if (lastRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_ABSENT 104 && currentRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_PRESENT) { 105 handleSimInsert(removableSlotInfo); 106 return; 107 } 108 if (lastRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_PRESENT 109 && currentRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_ABSENT) { 110 handleSimRemove(removableSlotInfo); 111 return; 112 } 113 Log.i(TAG, "Do nothing on slot status changes."); 114 } 115 onSuwFinish(Context context)116 void onSuwFinish(Context context) { 117 init(context); 118 119 if (Looper.myLooper() == Looper.getMainLooper()) { 120 throw new IllegalStateException("Cannot be called from main thread."); 121 } 122 123 if (mTelMgr.getActiveModemCount() > 1) { 124 Log.i(TAG, "The device is already in DSDS mode. Do nothing."); 125 return; 126 } 127 128 UiccSlotInfo removableSlotInfo = getRemovableUiccSlotInfo(); 129 if (removableSlotInfo == null) { 130 Log.e(TAG, "Unable to find the removable slot. Do nothing."); 131 return; 132 } 133 134 boolean embeddedSimExist = getGroupedEmbeddedSubscriptions().size() != 0; 135 int removableSlotAction = getSuwRemovableSlotAction(mContext); 136 setSuwRemovableSlotAction(mContext, LAST_USER_ACTION_IN_SUW_NONE); 137 138 if (embeddedSimExist 139 && removableSlotInfo.getCardStateInfo() == UiccSlotInfo.CARD_STATE_INFO_PRESENT) { 140 if (mTelMgr.isMultiSimSupported() == TelephonyManager.MULTISIM_ALLOWED) { 141 Log.i(TAG, "DSDS condition satisfied. Show notification."); 142 SimNotificationService.scheduleSimNotification( 143 mContext, SimActivationNotifier.NotificationType.ENABLE_DSDS); 144 } else if (removableSlotAction == LAST_USER_ACTION_IN_SUW_INSERT) { 145 Log.i( 146 TAG, 147 "Both removable SIM and eSIM are present. DSDS condition doesn't" 148 + " satisfied. User inserted pSIM during SUW. Show choose SIM" 149 + " screen."); 150 startChooseSimActivity(true); 151 } 152 } else if (removableSlotAction == LAST_USER_ACTION_IN_SUW_REMOVE) { 153 handleSimRemove(removableSlotInfo); 154 } 155 } 156 init(Context context)157 private void init(Context context) { 158 mSubMgr = 159 (SubscriptionManager) 160 context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); 161 mTelMgr = context.getSystemService(TelephonyManager.class); 162 mContext = context; 163 } 164 handleSimInsert(UiccSlotInfo removableSlotInfo)165 private void handleSimInsert(UiccSlotInfo removableSlotInfo) { 166 Log.i(TAG, "Handle SIM inserted."); 167 168 if (!isSuwFinished(mContext)) { 169 Log.i(TAG, "Still in SUW. Handle SIM insertion after SUW is finished"); 170 setSuwRemovableSlotAction(mContext, LAST_USER_ACTION_IN_SUW_INSERT); 171 return; 172 } 173 174 if (removableSlotInfo.getIsActive()) { 175 Log.i(TAG, "The removable slot is already active. Do nothing."); 176 return; 177 } 178 179 if (hasActiveEsimSubscription()) { 180 if (mTelMgr.isMultiSimSupported() == TelephonyManager.MULTISIM_ALLOWED) { 181 Log.i(TAG, "Enabled profile exists. DSDS condition satisfied."); 182 startDsdsDialogActivity(); 183 } else { 184 Log.i(TAG, "Enabled profile exists. DSDS condition not satisfied."); 185 startChooseSimActivity(true); 186 } 187 return; 188 } 189 190 Log.i( 191 TAG, 192 "No enabled eSIM profile. Ready to switch to removable slot and show" 193 + " notification."); 194 try { 195 UiccSlotUtil.switchToRemovableSlot( 196 UiccSlotUtil.INVALID_PHYSICAL_SLOT_ID, mContext.getApplicationContext()); 197 } catch (UiccSlotsException e) { 198 Log.e(TAG, "Failed to switch to removable slot."); 199 return; 200 } 201 SimNotificationService.scheduleSimNotification( 202 mContext, SimActivationNotifier.NotificationType.SWITCH_TO_REMOVABLE_SLOT); 203 } 204 handleSimRemove(UiccSlotInfo removableSlotInfo)205 private void handleSimRemove(UiccSlotInfo removableSlotInfo) { 206 Log.i(TAG, "Handle SIM removed."); 207 208 if (!isSuwFinished(mContext)) { 209 Log.i(TAG, "Still in SUW. Handle SIM removal after SUW is finished"); 210 setSuwRemovableSlotAction(mContext, LAST_USER_ACTION_IN_SUW_REMOVE); 211 return; 212 } 213 214 List<SubscriptionInfo> groupedEmbeddedSubscriptions = getGroupedEmbeddedSubscriptions(); 215 216 if (groupedEmbeddedSubscriptions.size() == 0 || !removableSlotInfo.getIsActive()) { 217 Log.i(TAG, "eSIM slot is active or no subscriptions exist. Do nothing."); 218 return; 219 } 220 221 // If there is only 1 eSIM profile exists, we ask the user if they want to switch to that 222 // profile. 223 if (groupedEmbeddedSubscriptions.size() == 1) { 224 Log.i(TAG, "Only 1 eSIM profile found. Ask user's consent to switch."); 225 startSwitchSlotConfirmDialogActivity(groupedEmbeddedSubscriptions.get(0)); 226 return; 227 } 228 229 // If there are more than 1 eSIM profiles installed, we show a screen to let users to choose 230 // the number they want to use. 231 Log.i(TAG, "Multiple eSIM profiles found. Ask user which subscription to use."); 232 startChooseSimActivity(false); 233 } 234 getLastRemovableSimSlotState(Context context)235 private int getLastRemovableSimSlotState(Context context) { 236 final SharedPreferences prefs = context.getSharedPreferences(EUICC_PREFS, MODE_PRIVATE); 237 return prefs.getInt(KEY_REMOVABLE_SLOT_STATE, UiccSlotInfo.CARD_STATE_INFO_ABSENT); 238 } 239 setRemovableSimSlotState(Context context, int state)240 private void setRemovableSimSlotState(Context context, int state) { 241 final SharedPreferences prefs = context.getSharedPreferences(EUICC_PREFS, MODE_PRIVATE); 242 prefs.edit().putInt(KEY_REMOVABLE_SLOT_STATE, state).apply(); 243 } 244 getSuwRemovableSlotAction(Context context)245 private int getSuwRemovableSlotAction(Context context) { 246 final SharedPreferences prefs = context.getSharedPreferences(EUICC_PREFS, MODE_PRIVATE); 247 return prefs.getInt(KEY_SUW_PSIM_ACTION, LAST_USER_ACTION_IN_SUW_NONE); 248 } 249 setSuwRemovableSlotAction(Context context, int action)250 private void setSuwRemovableSlotAction(Context context, int action) { 251 final SharedPreferences prefs = context.getSharedPreferences(EUICC_PREFS, MODE_PRIVATE); 252 prefs.edit().putInt(KEY_SUW_PSIM_ACTION, action).apply(); 253 } 254 255 @Nullable getRemovableUiccSlotInfo()256 private UiccSlotInfo getRemovableUiccSlotInfo() { 257 UiccSlotInfo[] slotInfos = mTelMgr.getUiccSlotsInfo(); 258 if (slotInfos == null) { 259 Log.e(TAG, "slotInfos is null. Unable to get slot infos."); 260 return null; 261 } 262 for (UiccSlotInfo slotInfo : slotInfos) { 263 if (slotInfo != null && slotInfo.isRemovable()) { 264 265 return slotInfo; 266 } 267 } 268 return null; 269 } 270 isSuwFinished(Context context)271 private static boolean isSuwFinished(Context context) { 272 try { 273 // DEVICE_PROVISIONED is 0 if still in setup wizard. 1 if setup completed. 274 return Settings.Global.getInt( 275 context.getContentResolver(), Settings.Global.DEVICE_PROVISIONED) 276 == 1; 277 } catch (Settings.SettingNotFoundException e) { 278 Log.e(TAG, "Cannot get DEVICE_PROVISIONED from the device.", e); 279 return false; 280 } 281 } 282 hasActiveEsimSubscription()283 private boolean hasActiveEsimSubscription() { 284 List<SubscriptionInfo> activeSubs = SubscriptionUtil.getActiveSubscriptions(mSubMgr); 285 return activeSubs.stream().anyMatch(SubscriptionInfo::isEmbedded); 286 } 287 getGroupedEmbeddedSubscriptions()288 private List<SubscriptionInfo> getGroupedEmbeddedSubscriptions() { 289 List<SubscriptionInfo> groupedSubscriptions = 290 SubscriptionUtil.getSelectableSubscriptionInfoList(mContext); 291 if (groupedSubscriptions == null) { 292 return ImmutableList.of(); 293 } 294 return ImmutableList.copyOf( 295 groupedSubscriptions.stream() 296 .filter(sub -> sub.isEmbedded()) 297 .collect(Collectors.toList())); 298 } 299 startChooseSimActivity(boolean psimInserted)300 private void startChooseSimActivity(boolean psimInserted) { 301 Intent intent = ChooseSimActivity.getIntent(mContext); 302 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 303 intent.putExtra(ChooseSimActivity.KEY_HAS_PSIM, psimInserted); 304 mContext.startActivity(intent); 305 } 306 startSwitchSlotConfirmDialogActivity(SubscriptionInfo subscriptionInfo)307 private void startSwitchSlotConfirmDialogActivity(SubscriptionInfo subscriptionInfo) { 308 Intent intent = new Intent(mContext, SwitchToEsimConfirmDialogActivity.class); 309 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 310 intent.putExtra(SwitchToEsimConfirmDialogActivity.KEY_SUB_TO_ENABLE, subscriptionInfo); 311 mContext.startActivity(intent); 312 } 313 startDsdsDialogActivity()314 private void startDsdsDialogActivity() { 315 Intent intent = new Intent(mContext, DsdsDialogActivity.class); 316 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 317 mContext.startActivity(intent); 318 } 319 SimSlotChangeHandler()320 private SimSlotChangeHandler() {} 321 } 322