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.TelephonyManager; 23 import android.telephony.UiccSlotInfo; 24 import android.util.Log; 25 26 import com.android.settingslib.utils.ThreadUtils; 27 28 import com.google.common.collect.ImmutableList; 29 30 import java.lang.annotation.Retention; 31 import java.lang.annotation.RetentionPolicy; 32 import java.util.concurrent.CountDownLatch; 33 import java.util.concurrent.TimeUnit; 34 35 public class UiccSlotUtil { 36 37 private static final String TAG = "UiccSlotUtil"; 38 39 private static final long DEFAULT_WAIT_AFTER_SWITCH_TIMEOUT_MILLIS = 25 * 1000L; 40 ; 41 42 public static final int INVALID_PHYSICAL_SLOT_ID = -1; 43 44 /** 45 * Mode for switching to eSIM slot which decides whether there is cleanup process, e.g. 46 * disabling test profile, after eSIM slot is activated and whether we will wait it finished. 47 */ 48 @Retention(RetentionPolicy.SOURCE) 49 @IntDef({ 50 SwitchingEsimMode.NO_CLEANUP, 51 SwitchingEsimMode.ASYNC_CLEANUP, 52 SwitchingEsimMode.SYNC_CLEANUP 53 }) 54 public @interface SwitchingEsimMode { 55 /** No cleanup process after switching to eSIM slot */ 56 int NO_CLEANUP = 0; 57 /** Has cleanup process, but we will not wait it finished. */ 58 int ASYNC_CLEANUP = 1; 59 /** Has cleanup process and we will wait until it's finished */ 60 int SYNC_CLEANUP = 2; 61 } 62 63 /** 64 * Returns an immutable list of all UICC slots. If TelephonyManager#getUiccSlotsInfo returns, it 65 * returns an empty list instead. 66 */ getSlotInfos(TelephonyManager telMgr)67 public static ImmutableList<UiccSlotInfo> getSlotInfos(TelephonyManager telMgr) { 68 UiccSlotInfo[] slotInfos = telMgr.getUiccSlotsInfo(); 69 if (slotInfos == null) { 70 return ImmutableList.of(); 71 } 72 return ImmutableList.copyOf(slotInfos); 73 } 74 75 /** 76 * Switches to the removable slot. It waits for SIM_STATE_LOADED after switch. If slotId is 77 * INVALID_PHYSICAL_SLOT_ID, the method will use the first detected inactive removable slot. 78 * 79 * @param slotId the physical removable slot id. 80 * @param context the application context. 81 * @throws UiccSlotsException if there is an error. 82 */ switchToRemovableSlot(int slotId, Context context)83 public static synchronized void switchToRemovableSlot(int slotId, Context context) 84 throws UiccSlotsException { 85 if (ThreadUtils.isMainThread()) { 86 throw new IllegalThreadStateException( 87 "Do not call switchToRemovableSlot on the main thread."); 88 } 89 TelephonyManager telMgr = context.getSystemService(TelephonyManager.class); 90 if (telMgr.isMultiSimEnabled()) { 91 // If this device supports multiple active slots, don't mess with TelephonyManager. 92 Log.i(TAG, "Multiple active slots supported. Not calling switchSlots."); 93 return; 94 } 95 UiccSlotInfo[] slots = telMgr.getUiccSlotsInfo(); 96 if (slotId == INVALID_PHYSICAL_SLOT_ID) { 97 for (int i = 0; i < slots.length; i++) { 98 if (slots[i].isRemovable() 99 && !slots[i].getIsActive() 100 && slots[i].getCardStateInfo() != UiccSlotInfo.CARD_STATE_INFO_ERROR 101 && slots[i].getCardStateInfo() != UiccSlotInfo.CARD_STATE_INFO_RESTRICTED) { 102 performSwitchToRemovableSlot(i, context); 103 return; 104 } 105 } 106 } else { 107 if (slotId >= slots.length || !slots[slotId].isRemovable()) { 108 throw new UiccSlotsException("The given slotId is not a removable slot: " + slotId); 109 } 110 if (!slots[slotId].getIsActive()) { 111 performSwitchToRemovableSlot(slotId, context); 112 } 113 } 114 } 115 performSwitchToRemovableSlot(int slotId, Context context)116 private static void performSwitchToRemovableSlot(int slotId, Context context) 117 throws UiccSlotsException { 118 CarrierConfigChangedReceiver receiver = null; 119 long waitingTimeMillis = 120 Settings.Global.getLong( 121 context.getContentResolver(), 122 Settings.Global.EUICC_SWITCH_SLOT_TIMEOUT_MILLIS, 123 DEFAULT_WAIT_AFTER_SWITCH_TIMEOUT_MILLIS); 124 try { 125 CountDownLatch latch = new CountDownLatch(1); 126 receiver = new CarrierConfigChangedReceiver(latch); 127 receiver.registerOn(context); 128 switchSlots(context, slotId); 129 latch.await(waitingTimeMillis, TimeUnit.MILLISECONDS); 130 } catch (InterruptedException e) { 131 Thread.currentThread().interrupt(); 132 Log.e(TAG, "Failed switching to physical slot.", e); 133 } finally { 134 if (receiver != null) { 135 context.unregisterReceiver(receiver); 136 } 137 } 138 } 139 140 /** 141 * Changes the logical slot to physical slot mapping. OEM should override this to provide 142 * device-specific implementation if the device supports switching slots. 143 * 144 * @param context the application context. 145 * @param physicalSlots List of physical slot ids in the order of logical slots. 146 */ switchSlots(Context context, int... physicalSlots)147 private static void switchSlots(Context context, int... physicalSlots) 148 throws UiccSlotsException { 149 TelephonyManager telMgr = context.getSystemService(TelephonyManager.class); 150 if (telMgr.isMultiSimEnabled()) { 151 // If this device supports multiple active slots, don't mess with TelephonyManager. 152 Log.i(TAG, "Multiple active slots supported. Not calling switchSlots."); 153 return; 154 } 155 if (!telMgr.switchSlots(physicalSlots)) { 156 throw new UiccSlotsException("Failed to switch slots"); 157 } 158 } 159 } 160