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