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.network; 18 19 import android.annotation.IntDef; 20 import android.content.BroadcastReceiver; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.IntentFilter; 24 import android.provider.Settings; 25 import android.telephony.SubscriptionInfo; 26 import android.telephony.SubscriptionManager; 27 import android.telephony.TelephonyManager; 28 import android.telephony.UiccCardInfo; 29 import android.telephony.UiccSlotInfo; 30 import android.telephony.UiccSlotMapping; 31 import android.util.Log; 32 33 import com.android.internal.annotations.VisibleForTesting; 34 import com.android.settingslib.utils.ThreadUtils; 35 36 import java.lang.annotation.Retention; 37 import java.lang.annotation.RetentionPolicy; 38 import java.util.ArrayList; 39 import java.util.Collection; 40 import java.util.Comparator; 41 import java.util.List; 42 import java.util.concurrent.CountDownLatch; 43 import java.util.concurrent.TimeUnit; 44 import java.util.stream.Collectors; 45 import java.util.stream.IntStream; 46 47 // ToDo: to do the refactor for renaming 48 public class UiccSlotUtil { 49 50 private static final String TAG = "UiccSlotUtil"; 51 52 static final long DEFAULT_WAIT_AFTER_SWITCH_TIMEOUT_MILLIS = 25 * 1000L; 53 54 public static final int INVALID_LOGICAL_SLOT_ID = -1; 55 public static final int INVALID_PHYSICAL_SLOT_ID = -1; 56 public static final int INVALID_PORT_ID = -1; 57 58 @VisibleForTesting 59 static class SimCardStateChangeReceiver extends BroadcastReceiver{ 60 private final CountDownLatch mLatch; SimCardStateChangeReceiver(CountDownLatch latch)61 SimCardStateChangeReceiver(CountDownLatch latch) { 62 mLatch = latch; 63 } 64 registerOn(Context context)65 public void registerOn(Context context) { 66 context.registerReceiver(this, 67 new IntentFilter(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED), 68 Context.RECEIVER_NOT_EXPORTED); 69 } 70 71 @Override onReceive(Context context, Intent intent)72 public void onReceive(Context context, Intent intent) { 73 Log.i(TAG, "Action: " + intent.getAction()); 74 if (!TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED.equals(intent.getAction())) { 75 return; 76 } 77 final int simState = intent.getIntExtra( 78 TelephonyManager.EXTRA_SIM_STATE, TelephonyManager.SIM_STATE_UNKNOWN); 79 Log.i(TAG, "simState: " + simState); 80 if (simState != TelephonyManager.SIM_STATE_UNKNOWN 81 && simState != TelephonyManager.SIM_STATE_ABSENT) { 82 mLatch.countDown(); 83 } 84 } 85 } 86 87 /** 88 * Mode for switching to eSIM slot which decides whether there is cleanup process, e.g. 89 * disabling test profile, after eSIM slot is activated and whether we will wait it finished. 90 */ 91 @Retention(RetentionPolicy.SOURCE) 92 @IntDef({ 93 SwitchingEsimMode.NO_CLEANUP, 94 SwitchingEsimMode.ASYNC_CLEANUP, 95 SwitchingEsimMode.SYNC_CLEANUP 96 }) 97 public @interface SwitchingEsimMode { 98 /** No cleanup process after switching to eSIM slot */ 99 int NO_CLEANUP = 0; 100 /** Has cleanup process, but we will not wait it finished. */ 101 int ASYNC_CLEANUP = 1; 102 /** Has cleanup process and we will wait until it's finished */ 103 int SYNC_CLEANUP = 2; 104 } 105 106 /** 107 * Switches to the removable slot. It waits for SIM_STATE_LOADED after switch. If slotId is 108 * INVALID_PHYSICAL_SLOT_ID, the method will use the first detected inactive removable slot. 109 * 110 * @param slotId the physical removable slot id. 111 * @param context the application context. 112 * @throws UiccSlotsException if there is an error. 113 */ 114 //ToDo: delete this api and refactor the related code. switchToRemovableSlot(int slotId, Context context)115 public static synchronized void switchToRemovableSlot(int slotId, Context context) 116 throws UiccSlotsException { 117 switchToRemovableSlot(context, slotId, null); 118 } 119 120 /** 121 * Switches to the removable slot. It waits for SIM_STATE_LOADED after switch. If slotId is 122 * INVALID_PHYSICAL_SLOT_ID, the method will use the first detected inactive removable slot. 123 * 124 * @param slotId the physical removable slot id. 125 * @param context the application context. 126 * @param removedSubInfo In the DSDS+MEP mode, if the all of slots have sims, it should 127 * remove the one of active sim. 128 * If the removedSubInfo is null, then use the default value. 129 * The default value is the esim slot and portId 0. 130 * @throws UiccSlotsException if there is an error. 131 */ switchToRemovableSlot(Context context, int slotId, SubscriptionInfo removedSubInfo)132 public static synchronized void switchToRemovableSlot(Context context, int slotId, 133 SubscriptionInfo removedSubInfo) throws UiccSlotsException { 134 if (ThreadUtils.isMainThread()) { 135 throw new IllegalThreadStateException( 136 "Do not call switchToRemovableSlot on the main thread."); 137 } 138 TelephonyManager telMgr = context.getSystemService(TelephonyManager.class); 139 int inactiveRemovableSlot = getInactiveRemovableSlot(telMgr.getUiccSlotsInfo(), slotId); 140 Log.d(TAG, "The InactiveRemovableSlot: " + inactiveRemovableSlot); 141 if (inactiveRemovableSlot == INVALID_PHYSICAL_SLOT_ID) { 142 // The slot is invalid slot id, then to skip this. 143 // The slot is active, then the sim can enable directly. 144 return; 145 } 146 147 Collection<UiccSlotMapping> uiccSlotMappings = telMgr.getSimSlotMapping(); 148 Log.d(TAG, "The SimSlotMapping: " + uiccSlotMappings); 149 150 SubscriptionManager subscriptionManager = context.getSystemService( 151 SubscriptionManager.class).createForAllUserProfiles(); 152 int excludedLogicalSlotIndex = getExcludedLogicalSlotIndex(uiccSlotMappings, 153 SubscriptionUtil.getActiveSubscriptions(subscriptionManager), removedSubInfo, 154 telMgr.isMultiSimEnabled()); 155 performSwitchToSlot(telMgr, 156 prepareUiccSlotMappings(uiccSlotMappings, 157 /*slot is psim*/ true, 158 inactiveRemovableSlot, 159 /*removable sim's port Id*/ TelephonyManager.DEFAULT_PORT_INDEX, 160 excludedLogicalSlotIndex), 161 context); 162 } 163 164 /** 165 * Switches to the Euicc slot. It waits for SIM_STATE_LOADED after switch. 166 * 167 * @param context the application context. 168 * @param physicalSlotId the Euicc slot id. 169 * @param port the Euicc slot port id. 170 * @param removedSubInfo In the DSDS+MEP mode, if the all of slots have sims, it should 171 * remove the one of active sim. 172 * If the removedSubInfo is null, then it uses the default value. 173 * The default value is the esim slot and portId 0. 174 * @throws UiccSlotsException if there is an error. 175 */ switchToEuiccSlot(Context context, int physicalSlotId, int port, SubscriptionInfo removedSubInfo)176 public static synchronized void switchToEuiccSlot(Context context, int physicalSlotId, int port, 177 SubscriptionInfo removedSubInfo) throws UiccSlotsException { 178 if (ThreadUtils.isMainThread()) { 179 throw new IllegalThreadStateException( 180 "Do not call switchToRemovableSlot on the main thread."); 181 } 182 TelephonyManager telMgr = context.getSystemService(TelephonyManager.class); 183 Collection<UiccSlotMapping> uiccSlotMappings = telMgr.getSimSlotMapping(); 184 Log.d(TAG, "The SimSlotMapping: " + uiccSlotMappings); 185 186 if (isTargetSlotActive(uiccSlotMappings, physicalSlotId, port)) { 187 Log.d(TAG, "The slot is active, then the sim can enable directly."); 188 return; 189 } 190 191 SubscriptionManager subscriptionManager = context.getSystemService( 192 SubscriptionManager.class).createForAllUserProfiles(); 193 int excludedLogicalSlotIndex = getExcludedLogicalSlotIndex(uiccSlotMappings, 194 SubscriptionUtil.getActiveSubscriptions(subscriptionManager), removedSubInfo, 195 telMgr.isMultiSimEnabled()); 196 performSwitchToSlot(telMgr, 197 prepareUiccSlotMappings(uiccSlotMappings, /*slot is not psim*/ false, 198 physicalSlotId, port, excludedLogicalSlotIndex), 199 context); 200 } 201 202 /** 203 * @param context the application context. 204 * @return the esim slot. If the value is -1, there is not the esim. 205 */ getEsimSlotId(Context context, int subId)206 public static int getEsimSlotId(Context context, int subId) { 207 TelephonyManager telMgr = context.getSystemService(TelephonyManager.class); 208 SubscriptionManager subscriptionManager = context.getSystemService( 209 SubscriptionManager.class).createForAllUserProfiles(); 210 SubscriptionInfo subInfo = SubscriptionUtil.getSubById(subscriptionManager, subId); 211 212 // checking whether this is the removable esim. If it is, then return the removable slot id. 213 if (subInfo != null && subInfo.isEmbedded()) { 214 List<UiccCardInfo> uiccCardInfos = telMgr.getUiccCardsInfo(); 215 for (UiccCardInfo uiccCardInfo : uiccCardInfos) { 216 if (uiccCardInfo.getCardId() == subInfo.getCardId() 217 && uiccCardInfo.getCardId() > TelephonyManager.UNSUPPORTED_CARD_ID 218 && uiccCardInfo.isEuicc() 219 && uiccCardInfo.isRemovable()) { 220 Log.d(TAG, "getEsimSlotId: This subInfo is a removable esim."); 221 return uiccCardInfo.getPhysicalSlotIndex(); 222 } 223 } 224 } 225 226 UiccSlotInfo[] slotInfos = telMgr.getUiccSlotsInfo(); 227 if (slotInfos == null) return -1; 228 int firstEsimSlot = IntStream.range(0, slotInfos.length) 229 .filter( 230 index -> { 231 UiccSlotInfo slotInfo = slotInfos[index]; 232 if (slotInfo == null) { 233 return false; 234 } 235 return slotInfo.getIsEuicc(); 236 }) 237 .findFirst().orElse(-1); 238 239 Log.i(TAG, "firstEsimSlot: " + firstEsimSlot); 240 return firstEsimSlot; 241 } 242 isTargetSlotActive(Collection<UiccSlotMapping> uiccSlotMappings, int physicalSlotId, int port)243 private static boolean isTargetSlotActive(Collection<UiccSlotMapping> uiccSlotMappings, 244 int physicalSlotId, int port) { 245 return uiccSlotMappings.stream() 246 .anyMatch( 247 uiccSlotMapping -> uiccSlotMapping.getPhysicalSlotIndex() == physicalSlotId 248 && uiccSlotMapping.getPortIndex() == port); 249 } 250 251 @VisibleForTesting performSwitchToSlot(TelephonyManager telMgr, Collection<UiccSlotMapping> uiccSlotMappings, Context context)252 static void performSwitchToSlot(TelephonyManager telMgr, 253 Collection<UiccSlotMapping> uiccSlotMappings, Context context) 254 throws UiccSlotsException { 255 BroadcastReceiver receiver = null; 256 long waitingTimeMillis = 257 Settings.Global.getLong( 258 context.getContentResolver(), 259 Settings.Global.EUICC_SWITCH_SLOT_TIMEOUT_MILLIS, 260 DEFAULT_WAIT_AFTER_SWITCH_TIMEOUT_MILLIS); 261 Log.d(TAG, "Set waitingTime as " + waitingTimeMillis); 262 263 try { 264 CountDownLatch latch = new CountDownLatch(1); 265 if (isMultipleEnabledProfilesSupported(telMgr)) { 266 receiver = new SimCardStateChangeReceiver(latch); 267 ((SimCardStateChangeReceiver) receiver).registerOn(context); 268 } else { 269 receiver = new CarrierConfigChangedReceiver(latch); 270 ((CarrierConfigChangedReceiver) receiver).registerOn(context); 271 } 272 telMgr.setSimSlotMapping(uiccSlotMappings); 273 latch.await(waitingTimeMillis, TimeUnit.MILLISECONDS); 274 } catch (InterruptedException e) { 275 Thread.currentThread().interrupt(); 276 Log.e(TAG, "Failed switching to physical slot.", e); 277 } finally { 278 if (receiver != null) { 279 context.unregisterReceiver(receiver); 280 } 281 } 282 } 283 284 /** 285 * @param slots The UiccSlotInfo list. 286 * @param slotId The physical removable slot id. 287 * @return The inactive physical removable slot id. If the physical removable slot id is 288 * active, then return -1. 289 * @throws UiccSlotsException if there is an error. 290 */ getInactiveRemovableSlot(UiccSlotInfo[] slots, int slotId)291 private static int getInactiveRemovableSlot(UiccSlotInfo[] slots, int slotId) 292 throws UiccSlotsException { 293 if (slots == null) { 294 throw new UiccSlotsException("UiccSlotInfo is null"); 295 } 296 if (slotId == INVALID_PHYSICAL_SLOT_ID) { 297 for (int i = 0; i < slots.length; i++) { 298 if (slots[i] != null 299 && slots[i].isRemovable() 300 && !slots[i].getIsEuicc() 301 && !slots[i].getPorts().stream().findFirst().get().isActive() 302 && slots[i].getCardStateInfo() != UiccSlotInfo.CARD_STATE_INFO_ERROR 303 && slots[i].getCardStateInfo() != UiccSlotInfo.CARD_STATE_INFO_RESTRICTED) { 304 return i; 305 } 306 } 307 } else { 308 if (slotId >= slots.length || slots[slotId] == null || !slots[slotId].isRemovable()) { 309 Log.d(TAG, "The given slotId is not a removable slot: " + slotId); 310 return INVALID_PHYSICAL_SLOT_ID; 311 } 312 if (!slots[slotId].getPorts().stream().findFirst().get().isActive()) { 313 return slotId; 314 } 315 } 316 return INVALID_PHYSICAL_SLOT_ID; 317 } 318 319 @VisibleForTesting prepareUiccSlotMappings( Collection<UiccSlotMapping> uiccSlotMappings, boolean isPsim, int physicalSlotId, int port, int removedLogicalSlotId)320 static Collection<UiccSlotMapping> prepareUiccSlotMappings( 321 Collection<UiccSlotMapping> uiccSlotMappings, boolean isPsim, int physicalSlotId, 322 int port, int removedLogicalSlotId) { 323 if (removedLogicalSlotId == INVALID_LOGICAL_SLOT_ID) { 324 Log.d(TAG, "There is no removedLogicalSlotId. Do nothing."); 325 return uiccSlotMappings; 326 } 327 Log.d(TAG, 328 String.format( 329 "Create new SimSlotMapping. Remove the UiccSlotMapping of logicalSlot%d" 330 + ", and insert PhysicalSlotId%d-Port%d", 331 removedLogicalSlotId, physicalSlotId, port)); 332 Collection<UiccSlotMapping> newUiccSlotMappings = new ArrayList<>(); 333 int logicalSlotIndex = 0; 334 if (isPsim) { 335 // The target slot is psim. The psim is always the first index at LogicalSlot. 336 newUiccSlotMappings.add( 337 new UiccSlotMapping(port, physicalSlotId, logicalSlotIndex++)); 338 } 339 Collection<UiccSlotMapping> tempUiccSlotMappings = 340 uiccSlotMappings.stream() 341 .sorted(Comparator.comparingInt(UiccSlotMapping::getLogicalSlotIndex)) 342 .collect(Collectors.toList()); 343 for (UiccSlotMapping uiccSlotMapping : tempUiccSlotMappings) { 344 if (uiccSlotMapping.getLogicalSlotIndex() == removedLogicalSlotId) { 345 if (!isPsim) { 346 // Replace this uiccSlotMapping 347 newUiccSlotMappings.add(new UiccSlotMapping(port, physicalSlotId, 348 uiccSlotMapping.getLogicalSlotIndex())); 349 } 350 continue; 351 } 352 353 // If the psim is inserted, then change the logicalSlotIndex for another 354 // uiccSlotMappings. 355 newUiccSlotMappings.add(isPsim 356 ? new UiccSlotMapping(uiccSlotMapping.getPortIndex(), 357 uiccSlotMapping.getPhysicalSlotIndex(), logicalSlotIndex++) 358 : uiccSlotMapping); 359 } 360 361 Log.d(TAG, "The new SimSlotMapping: " + newUiccSlotMappings); 362 return newUiccSlotMappings; 363 } 364 365 /** 366 * To get the excluded logical slot index from uiccSlotMapping list. If the sim which is 367 * enabled by user does not have the corresponding slot, then it needs to do the 368 * SimSlotMapping changed. This method can find the logical slot index of the corresponding slot 369 * before the Frameworks do the SimSlotMapping changed. 370 * 371 * @param uiccSlotMappings The uiccSlotMapping list from the Telephony Frameworks. 372 * @param activeSubInfos The active subscriptionInfo list. 373 * @param removedSubInfo The removed sim card which is selected by the user. If the user 374 * don't select removed sim , then the value is null. 375 * @param isMultiSimEnabled whether the device is in the DSDS mode or not. 376 * @return The logical slot index of removed slot. If it can't find the removed slot, it 377 * returns {@link #INVALID_LOGICAL_SLOT_ID}. 378 */ 379 @VisibleForTesting getExcludedLogicalSlotIndex(Collection<UiccSlotMapping> uiccSlotMappings, Collection<SubscriptionInfo> activeSubInfos, SubscriptionInfo removedSubInfo, boolean isMultiSimEnabled)380 static int getExcludedLogicalSlotIndex(Collection<UiccSlotMapping> uiccSlotMappings, 381 Collection<SubscriptionInfo> activeSubInfos, SubscriptionInfo removedSubInfo, 382 boolean isMultiSimEnabled) { 383 if (!isMultiSimEnabled) { 384 Log.i(TAG, "In the ss mode."); 385 return 0; 386 } 387 if (removedSubInfo != null) { 388 // Use removedSubInfo's logicalSlotIndex 389 Log.i(TAG, "The removedSubInfo is not null"); 390 return removedSubInfo.getSimSlotIndex(); 391 } 392 // If it needs to do simSlotMapping when user enables sim and there is an empty slot which 393 // there is no enabled sim in this slot, then the empty slot can be removed. 394 Log.i(TAG, "The removedSubInfo is null"); 395 return uiccSlotMappings.stream() 396 .filter(uiccSlotMapping -> { 397 // find the empty slots. 398 for (SubscriptionInfo subInfo : activeSubInfos) { 399 if (subInfo.getSimSlotIndex() == uiccSlotMapping.getLogicalSlotIndex()) { 400 return false; 401 } 402 } 403 return true; 404 }) 405 .sorted(Comparator.comparingInt(UiccSlotMapping::getLogicalSlotIndex)) 406 .mapToInt(uiccSlotMapping -> uiccSlotMapping.getLogicalSlotIndex()) 407 .findFirst() 408 .orElse(INVALID_LOGICAL_SLOT_ID); 409 } 410 411 private static boolean isMultipleEnabledProfilesSupported(TelephonyManager telMgr) { 412 List<UiccCardInfo> cardInfos = telMgr.getUiccCardsInfo(); 413 if (cardInfos == null) { 414 Log.w(TAG, "UICC cards info list is empty."); 415 return false; 416 } 417 return cardInfos.stream().anyMatch( 418 cardInfo -> cardInfo.isMultipleEnabledProfilesSupported()); 419 } 420 } 421