1 /*
2  * Copyright (C) 2021 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 android.provider.cts.simphonebook;
18 
19 import android.Manifest;
20 import android.content.Context;
21 import android.content.pm.PackageManager;
22 import android.telephony.SubscriptionInfo;
23 import android.telephony.SubscriptionManager;
24 import android.telephony.TelephonyManager;
25 import android.telephony.UiccCardInfo;
26 
27 import com.android.compatibility.common.util.PollingCheck;
28 import com.android.compatibility.common.util.RequiredFeatureRule;
29 import com.android.compatibility.common.util.SystemUtil;
30 
31 import java.util.ArrayList;
32 import java.util.Collections;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Objects;
36 import java.util.stream.Collectors;
37 
38 /** Provides the SubscriptionInfo and slot count for removable SIM cards. */
39 class RemovableSims {
40     private final Context mContext;
41     private final TelephonyManager mTelephonyManager;
42 
43     private List<SubscriptionInfo> mRemovableSubscriptionInfos;
44     private int mRemovableSimSlotCount;
45 
RemovableSims(Context context)46     public RemovableSims(Context context) {
47         mContext = context;
48         mTelephonyManager = Objects.requireNonNull(
49                 context.getSystemService(TelephonyManager.class));
50     }
51 
initialize()52     private synchronized void initialize() {
53         SubscriptionManager subscriptionManager = Objects.requireNonNull(
54                 mContext.getSystemService(SubscriptionManager.class));
55         mRemovableSubscriptionInfos = new ArrayList<>();
56         mRemovableSimSlotCount = 0;
57 
58         if (RequiredFeatureRule.hasFeature(PackageManager.FEATURE_TELEPHONY_EUICC)) {
59             // Wait for the eSIM state to be loaded before continuing. Otherwise, the card info
60             // we load later may indicate that they are not eSIM when they actually are.
61             PollingCheck.waitFor(30_000, () ->
62                     mTelephonyManager.getCardIdForDefaultEuicc() !=
63                             TelephonyManager.UNINITIALIZED_CARD_ID
64             );
65         }
66 
67         List<UiccCardInfo> uiccCards = SystemUtil.runWithShellPermissionIdentity(
68                 mTelephonyManager::getUiccCardsInfo,
69                 Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
70 
71         List<SubscriptionInfo> allSubscriptions = SystemUtil.runWithShellPermissionIdentity(() ->
72                         subscriptionManager.getActiveSubscriptionInfoList(),
73                 Manifest.permission.READ_PHONE_STATE);
74         Map<Integer, List<SubscriptionInfo>> subscriptionsByCardId = allSubscriptions.stream()
75                 .collect(Collectors.groupingBy(SubscriptionInfo::getCardId));
76         for (UiccCardInfo cardInfo : uiccCards) {
77             // On GSI builds the eUICC won't be loaded but its card info will still be returned
78             // and so it will have UNINITIALIZED_CARD_ID permanently.
79             if (!cardInfo.isRemovable() || cardInfo.isEuicc() ||
80                     cardInfo.getCardId() == TelephonyManager.UNINITIALIZED_CARD_ID) {
81                 continue;
82             }
83             mRemovableSimSlotCount++;
84 
85             List<SubscriptionInfo> listWithSubscription = subscriptionsByCardId
86                     .getOrDefault(cardInfo.getCardId(), Collections.emptyList());
87             // There should only be 1 in the list but using addAll simplifies things because we
88             // don't have to check for the empty case.
89             mRemovableSubscriptionInfos.addAll(listWithSubscription);
90         }
91     }
92 
getSubscriptionInfoForRemovableSims()93     public List<SubscriptionInfo> getSubscriptionInfoForRemovableSims() {
94         if (mRemovableSubscriptionInfos == null ||
95                 mRemovableSubscriptionInfos.size() < mRemovableSimSlotCount) {
96             initialize();
97         }
98         return mRemovableSubscriptionInfos;
99     }
100 
getRemovableSimSlotCount()101     public int getRemovableSimSlotCount() {
102         if (mRemovableSubscriptionInfos == null) {
103             initialize();
104         }
105         return mRemovableSimSlotCount;
106     }
107 
getDefaultSubscriptionId()108     public int getDefaultSubscriptionId() {
109         List<SubscriptionInfo> removableSubscriptionInfos = getSubscriptionInfoForRemovableSims();
110         int subscriptionId = SubscriptionManager.getDefaultSubscriptionId();
111         if (removableSubscriptionInfos.stream().anyMatch(
112                 info -> info.getSubscriptionId() == subscriptionId)) {
113             return subscriptionId;
114         } else if (!removableSubscriptionInfos.isEmpty()) {
115             return removableSubscriptionInfos.get(0).getSubscriptionId();
116         }
117         return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
118     }
119 }
120