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