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