• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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