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