• 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.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