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         if (allSubscriptions == null) {
75             allSubscriptions = Collections.emptyList();
76         }
77         Map<Integer, List<SubscriptionInfo>> subscriptionsByCardId = allSubscriptions.stream()
78                 .collect(Collectors.groupingBy(SubscriptionInfo::getCardId));
79         for (UiccCardInfo cardInfo : uiccCards) {
80             // On GSI builds the eUICC won't be loaded but its card info will still be returned
81             // and so it will have UNINITIALIZED_CARD_ID permanently.
82             if (!cardInfo.isRemovable() || cardInfo.isEuicc() ||
83                     cardInfo.getCardId() == TelephonyManager.UNINITIALIZED_CARD_ID) {
84                 continue;
85             }
86             mRemovableSimSlotCount++;
87 
88             List<SubscriptionInfo> listWithSubscription = subscriptionsByCardId
89                     .getOrDefault(cardInfo.getCardId(), Collections.emptyList());
90             // There should only be 1 in the list but using addAll simplifies things because we
91             // don't have to check for the empty case.
92             mRemovableSubscriptionInfos.addAll(listWithSubscription);
93         }
94     }
95 
getSubscriptionInfoForRemovableSims()96     public List<SubscriptionInfo> getSubscriptionInfoForRemovableSims() {
97         if (mRemovableSubscriptionInfos == null ||
98                 mRemovableSubscriptionInfos.size() < mRemovableSimSlotCount) {
99             initialize();
100         }
101         return mRemovableSubscriptionInfos;
102     }
103 
getRemovableSimSlotCount()104     public int getRemovableSimSlotCount() {
105         if (mRemovableSubscriptionInfos == null) {
106             initialize();
107         }
108         return mRemovableSimSlotCount;
109     }
110 
getDefaultSubscriptionId()111     public int getDefaultSubscriptionId() {
112         List<SubscriptionInfo> removableSubscriptionInfos = getSubscriptionInfoForRemovableSims();
113         int subscriptionId = SubscriptionManager.getDefaultSubscriptionId();
114         if (removableSubscriptionInfos.stream().anyMatch(
115                 info -> info.getSubscriptionId() == subscriptionId)) {
116             return subscriptionId;
117         } else if (!removableSubscriptionInfos.isEmpty()) {
118             return removableSubscriptionInfos.get(0).getSubscriptionId();
119         }
120         return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
121     }
122 }
123