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