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