1 /*
2  * Copyright (C) 2018 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 static android.telephony.SubscriptionManager.INVALID_SIM_SLOT_INDEX;
20 import static android.telephony.UiccSlotInfo.CARD_STATE_INFO_PRESENT;
21 
22 import static com.android.internal.util.CollectionUtils.emptyIfNull;
23 
24 import android.content.Context;
25 import android.os.ParcelUuid;
26 import android.telephony.SubscriptionInfo;
27 import android.telephony.SubscriptionManager;
28 import android.telephony.TelephonyManager;
29 import android.telephony.UiccSlotInfo;
30 
31 import androidx.annotation.VisibleForTesting;
32 
33 import java.util.ArrayList;
34 import java.util.HashMap;
35 import java.util.List;
36 import java.util.Map;
37 
38 public class SubscriptionUtil {
39     private static final String TAG = "SubscriptionUtil";
40     private static List<SubscriptionInfo> sAvailableResultsForTesting;
41     private static List<SubscriptionInfo> sActiveResultsForTesting;
42 
43     @VisibleForTesting
setAvailableSubscriptionsForTesting(List<SubscriptionInfo> results)44     public static void setAvailableSubscriptionsForTesting(List<SubscriptionInfo> results) {
45         sAvailableResultsForTesting = results;
46     }
47 
48     @VisibleForTesting
setActiveSubscriptionsForTesting(List<SubscriptionInfo> results)49     public static void setActiveSubscriptionsForTesting(List<SubscriptionInfo> results) {
50         sActiveResultsForTesting = results;
51     }
52 
getActiveSubscriptions(SubscriptionManager manager)53     public static List<SubscriptionInfo> getActiveSubscriptions(SubscriptionManager manager) {
54         if (sActiveResultsForTesting != null) {
55             return sActiveResultsForTesting;
56         }
57         final List<SubscriptionInfo> subscriptions = manager.getActiveSubscriptionInfoList();
58         if (subscriptions == null) {
59             return new ArrayList<>();
60         }
61         return subscriptions;
62     }
63 
64     @VisibleForTesting
isInactiveInsertedPSim(UiccSlotInfo slotInfo)65     static boolean isInactiveInsertedPSim(UiccSlotInfo slotInfo) {
66         if (slotInfo == null)  {
67             return false;
68         }
69         return !slotInfo.getIsEuicc() && !slotInfo.getIsActive() &&
70                 slotInfo.getCardStateInfo() == CARD_STATE_INFO_PRESENT;
71     }
72 
73     /**
74      * Get all of the subscriptions which is available to display to the user.
75      *
76      * @param context {@code Context}
77      * @return list of {@code SubscriptionInfo}
78      */
getAvailableSubscriptions(Context context)79     public static List<SubscriptionInfo> getAvailableSubscriptions(Context context) {
80         if (sAvailableResultsForTesting != null) {
81             return sAvailableResultsForTesting;
82         }
83         return new ArrayList<>(emptyIfNull(getSelectableSubscriptionInfoList(context)));
84     }
85 
86     /**
87      * Get subscription which is available to be displayed to the user
88      * per subscription id.
89      *
90      * @param context {@code Context}
91      * @param subscriptionManager The ProxySubscriptionManager for accessing subcription
92      *         information
93      * @param subId The id of subscription to be retrieved
94      * @return {@code SubscriptionInfo} based on the given subscription id. Null of subscription
95      *         is invalid or not allowed to be displayed to the user.
96      */
getAvailableSubscription(Context context, ProxySubscriptionManager subscriptionManager, int subId)97     public static SubscriptionInfo getAvailableSubscription(Context context,
98             ProxySubscriptionManager subscriptionManager, int subId) {
99         final SubscriptionInfo subInfo = subscriptionManager.getAccessibleSubscriptionInfo(subId);
100         if (subInfo == null) {
101             return null;
102         }
103 
104         final ParcelUuid groupUuid = subInfo.getGroupUuid();
105 
106         if (groupUuid != null) {
107             if (isPrimarySubscriptionWithinSameUuid(getUiccSlotsInfo(context), groupUuid,
108                     subscriptionManager.getAccessibleSubscriptionsInfo(), subId)) {
109                 return subInfo;
110             }
111             return null;
112         }
113 
114         return subInfo;
115     }
116 
getUiccSlotsInfo(Context context)117     private static UiccSlotInfo [] getUiccSlotsInfo(Context context) {
118         final TelephonyManager telMgr = context.getSystemService(TelephonyManager.class);
119         return telMgr.getUiccSlotsInfo();
120     }
121 
isPrimarySubscriptionWithinSameUuid(UiccSlotInfo[] slotsInfo, ParcelUuid groupUuid, List<SubscriptionInfo> subscriptions, int subId)122     private static boolean isPrimarySubscriptionWithinSameUuid(UiccSlotInfo[] slotsInfo,
123             ParcelUuid groupUuid, List<SubscriptionInfo> subscriptions, int subId) {
124         // only interested in subscriptions with this group UUID
125         final ArrayList<SubscriptionInfo> physicalSubInfoList =
126                 new ArrayList<SubscriptionInfo>();
127         final ArrayList<SubscriptionInfo> nonOpportunisticSubInfoList =
128                 new ArrayList<SubscriptionInfo>();
129         final ArrayList<SubscriptionInfo> activeSlotSubInfoList =
130                 new ArrayList<SubscriptionInfo>();
131         final ArrayList<SubscriptionInfo> inactiveSlotSubInfoList =
132                 new ArrayList<SubscriptionInfo>();
133         for (SubscriptionInfo subInfo : subscriptions) {
134             if (groupUuid.equals(subInfo.getGroupUuid())) {
135                 if (!subInfo.isEmbedded()) {
136                     physicalSubInfoList.add(subInfo);
137                 } else  {
138                     if (!subInfo.isOpportunistic()) {
139                         nonOpportunisticSubInfoList.add(subInfo);
140                     }
141                     if (subInfo.getSimSlotIndex()
142                             != SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
143                         activeSlotSubInfoList.add(subInfo);
144                     } else {
145                         inactiveSlotSubInfoList.add(subInfo);
146                     }
147                 }
148             }
149         }
150 
151         // find any physical SIM which is currently inserted within logical slot
152         // and which is our target subscription
153         if ((slotsInfo != null) && (physicalSubInfoList.size() > 0)) {
154             final SubscriptionInfo subInfo = searchForSubscriptionId(physicalSubInfoList, subId);
155             if (subInfo == null) {
156                 return false;
157             }
158             // verify if subscription is inserted within slot
159             for (UiccSlotInfo slotInfo : slotsInfo) {
160                 if ((slotInfo != null) && (!slotInfo.getIsEuicc())
161                         && (slotInfo.getLogicalSlotIdx() == subInfo.getSimSlotIndex())) {
162                     return true;
163                 }
164             }
165             return false;
166         }
167 
168         // When all of the eSIM profiles are opprtunistic and no physical SIM,
169         // first opportunistic subscriptions with same group UUID can be primary.
170         if (nonOpportunisticSubInfoList.size() <= 0) {
171             if (physicalSubInfoList.size() > 0) {
172                 return false;
173             }
174             if (activeSlotSubInfoList.size() > 0) {
175                 return (activeSlotSubInfoList.get(0).getSubscriptionId() == subId);
176             }
177             return (inactiveSlotSubInfoList.get(0).getSubscriptionId() == subId);
178         }
179 
180         // Allow non-opportunistic + active eSIM subscription as primary
181         int numberOfActiveNonOpportunisticSubs = 0;
182         boolean isTargetNonOpportunistic = false;
183         for (SubscriptionInfo subInfo : nonOpportunisticSubInfoList) {
184             final boolean isTargetSubInfo = (subInfo.getSubscriptionId() == subId);
185             if (subInfo.getSimSlotIndex() != SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
186                 if (isTargetSubInfo) {
187                     return true;
188                 }
189                 numberOfActiveNonOpportunisticSubs++;
190             } else {
191                 isTargetNonOpportunistic |= isTargetSubInfo;
192             }
193         }
194         if (numberOfActiveNonOpportunisticSubs > 0) {
195             return false;
196         }
197         return isTargetNonOpportunistic;
198     }
199 
searchForSubscriptionId(List<SubscriptionInfo> subInfoList, int subscriptionId)200     private static SubscriptionInfo searchForSubscriptionId(List<SubscriptionInfo> subInfoList,
201             int subscriptionId) {
202         for (SubscriptionInfo subInfo : subInfoList) {
203             if (subInfo.getSubscriptionId() == subscriptionId) {
204                 return subInfo;
205             }
206         }
207         return null;
208     }
209 
getDisplayName(SubscriptionInfo info)210     public static String getDisplayName(SubscriptionInfo info) {
211         final CharSequence name = info.getDisplayName();
212         if (name != null) {
213             return name.toString();
214         }
215         return "";
216     }
217 
218     /**
219      * Whether Settings should show a "Use SIM" toggle in pSIM detailed page.
220      */
showToggleForPhysicalSim(SubscriptionManager subMgr)221     public static boolean showToggleForPhysicalSim(SubscriptionManager subMgr) {
222         return subMgr.canDisablePhysicalSubscription();
223     }
224 
225     /**
226      * Get phoneId or logical slot index for a subId if active, or INVALID_PHONE_INDEX if inactive.
227      */
getPhoneId(Context context, int subId)228     public static int getPhoneId(Context context, int subId) {
229         final SubscriptionManager subManager = context.getSystemService(SubscriptionManager.class);
230         if (subManager == null) {
231             return INVALID_SIM_SLOT_INDEX;
232         }
233         final SubscriptionInfo info = subManager.getActiveSubscriptionInfo(subId);
234         if (info == null) {
235             return INVALID_SIM_SLOT_INDEX;
236         }
237         return info.getSimSlotIndex();
238     }
239 
240     /**
241      * Return a list of subscriptions that are available and visible to the user.
242      *
243      * @return list of user selectable subscriptions.
244      */
getSelectableSubscriptionInfoList(Context context)245     public static List<SubscriptionInfo> getSelectableSubscriptionInfoList(Context context) {
246         SubscriptionManager subManager = context.getSystemService(SubscriptionManager.class);
247         List<SubscriptionInfo> availableList = subManager.getAvailableSubscriptionInfoList();
248         if (availableList == null) {
249             return null;
250         } else {
251             // Multiple subscriptions in a group should only have one representative.
252             // It should be the current active primary subscription if any, or any
253             // primary subscription.
254             List<SubscriptionInfo> selectableList = new ArrayList<>();
255             Map<ParcelUuid, SubscriptionInfo> groupMap = new HashMap<>();
256 
257             for (SubscriptionInfo info : availableList) {
258                 // Opportunistic subscriptions are considered invisible
259                 // to users so they should never be returned.
260                 if (!isSubscriptionVisible(subManager, context, info)) continue;
261 
262                 ParcelUuid groupUuid = info.getGroupUuid();
263                 if (groupUuid == null) {
264                     // Doesn't belong to any group. Add in the list.
265                     selectableList.add(info);
266                 } else if (!groupMap.containsKey(groupUuid)
267                         || (groupMap.get(groupUuid).getSimSlotIndex() == INVALID_SIM_SLOT_INDEX
268                         && info.getSimSlotIndex() != INVALID_SIM_SLOT_INDEX)) {
269                     // If it belongs to a group that has never been recorded or it's the current
270                     // active subscription, add it in the list.
271                     selectableList.remove(groupMap.get(groupUuid));
272                     selectableList.add(info);
273                     groupMap.put(groupUuid, info);
274                 }
275 
276             }
277             return selectableList;
278         }
279     }
280 
281 
282     /**
283      * Whether a subscription is visible to API caller. If it's a bundled opportunistic
284      * subscription, it should be hidden anywhere in Settings, dialer, status bar etc.
285      * Exception is if caller owns carrier privilege, in which case they will
286      * want to see their own hidden subscriptions.
287      *
288      * @param info the subscriptionInfo to check against.
289      * @return true if this subscription should be visible to the API caller.
290      */
isSubscriptionVisible( SubscriptionManager subscriptionManager, Context context, SubscriptionInfo info)291     private static boolean isSubscriptionVisible(
292             SubscriptionManager subscriptionManager, Context context, SubscriptionInfo info) {
293         if (info == null) return false;
294         // If subscription is NOT grouped opportunistic subscription, it's visible.
295         if (info.getGroupUuid() == null || !info.isOpportunistic()) return true;
296 
297         // If the caller is the carrier app and owns the subscription, it should be visible
298         // to the caller.
299         TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class)
300                 .createForSubscriptionId(info.getSubscriptionId());
301         boolean hasCarrierPrivilegePermission = telephonyManager.hasCarrierPrivileges()
302                 || subscriptionManager.canManageSubscription(info);
303         return hasCarrierPrivilegePermission;
304     }
305 }
306