• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.shadows;
2 
3 import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1;
4 import static android.os.Build.VERSION_CODES.M;
5 import static android.os.Build.VERSION_CODES.N;
6 import static android.os.Build.VERSION_CODES.O;
7 import static android.os.Build.VERSION_CODES.O_MR1;
8 import static android.os.Build.VERSION_CODES.P;
9 import static android.os.Build.VERSION_CODES.Q;
10 import static android.os.Build.VERSION_CODES.R;
11 import static android.os.Build.VERSION_CODES.TIRAMISU;
12 import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
13 import static android.telephony.SubscriptionManager.INVALID_SIM_SLOT_INDEX;
14 
15 import android.os.Build.VERSION;
16 import android.telephony.SubscriptionInfo;
17 import android.telephony.SubscriptionManager;
18 import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
19 import com.google.common.collect.ImmutableList;
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.HashMap;
23 import java.util.HashSet;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Set;
27 import java.util.concurrent.Executor;
28 import org.robolectric.annotation.HiddenApi;
29 import org.robolectric.annotation.Implementation;
30 import org.robolectric.annotation.Implements;
31 import org.robolectric.annotation.Resetter;
32 import org.robolectric.util.ReflectionHelpers;
33 
34 @Implements(value = SubscriptionManager.class, minSdk = LOLLIPOP_MR1)
35 public class ShadowSubscriptionManager {
36 
37   private static boolean readPhoneStatePermission = true;
38   private static boolean readPhoneNumbersPermission = true;
39   public static final int INVALID_PHONE_INDEX =
40       ReflectionHelpers.getStaticField(SubscriptionManager.class, "INVALID_PHONE_INDEX");
41 
42   private static int activeDataSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
43   private static int defaultSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
44   private static int defaultDataSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
45   private static int defaultSmsSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
46   private static int defaultVoiceSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
47 
48   private static final Map<Integer, String> phoneNumberMap = new HashMap<>();
49 
50   /** Returns value set with {@link #setActiveDataSubscriptionId(int)}. */
51   @Implementation(minSdk = R)
getActiveDataSubscriptionId()52   protected static int getActiveDataSubscriptionId() {
53     return activeDataSubscriptionId;
54   }
55 
56   /** Returns value set with {@link #setDefaultSubscriptionId(int)}. */
57   @Implementation(minSdk = N)
getDefaultSubscriptionId()58   protected static int getDefaultSubscriptionId() {
59     return defaultSubscriptionId;
60   }
61 
62   /** Returns value set with {@link #setDefaultDataSubscriptionId(int)}. */
63   @Implementation(minSdk = N)
getDefaultDataSubscriptionId()64   protected static int getDefaultDataSubscriptionId() {
65     return defaultDataSubscriptionId;
66   }
67 
68   /** Returns value set with {@link #setDefaultSmsSubscriptionId(int)}. */
69   @Implementation(minSdk = N)
getDefaultSmsSubscriptionId()70   protected static int getDefaultSmsSubscriptionId() {
71     return defaultSmsSubscriptionId;
72   }
73 
74   /** Returns value set with {@link #setDefaultVoiceSubscriptionId(int)}. */
75   @Implementation(minSdk = N)
getDefaultVoiceSubscriptionId()76   protected static int getDefaultVoiceSubscriptionId() {
77     return defaultVoiceSubscriptionId;
78   }
79 
80   @Implementation(maxSdk = M)
81   @HiddenApi
getDefaultSubId()82   protected static int getDefaultSubId() {
83     return defaultSubscriptionId;
84   }
85 
86   @Implementation(maxSdk = M)
87   @HiddenApi
getDefaultVoiceSubId()88   protected static int getDefaultVoiceSubId() {
89     return defaultVoiceSubscriptionId;
90   }
91 
92   @Implementation(maxSdk = M)
93   @HiddenApi
getDefaultSmsSubId()94   protected static int getDefaultSmsSubId() {
95     return defaultSmsSubscriptionId;
96   }
97 
98   @Implementation(maxSdk = M)
99   @HiddenApi
getDefaultDataSubId()100   protected static int getDefaultDataSubId() {
101     return defaultDataSubscriptionId;
102   }
103 
104   /** Sets the value that will be returned by {@link #getActiveDataSubscriptionId()}. */
setActiveDataSubscriptionId(int activeDataSubscriptionId)105   public static void setActiveDataSubscriptionId(int activeDataSubscriptionId) {
106     ShadowSubscriptionManager.activeDataSubscriptionId = activeDataSubscriptionId;
107   }
108 
109   /** Sets the value that will be returned by {@link #getDefaultSubscriptionId()}. */
setDefaultSubscriptionId(int defaultSubscriptionId)110   public static void setDefaultSubscriptionId(int defaultSubscriptionId) {
111     ShadowSubscriptionManager.defaultSubscriptionId = defaultSubscriptionId;
112   }
113 
setDefaultDataSubscriptionId(int defaultDataSubscriptionId)114   public static void setDefaultDataSubscriptionId(int defaultDataSubscriptionId) {
115     ShadowSubscriptionManager.defaultDataSubscriptionId = defaultDataSubscriptionId;
116   }
117 
setDefaultSmsSubscriptionId(int defaultSmsSubscriptionId)118   public static void setDefaultSmsSubscriptionId(int defaultSmsSubscriptionId) {
119     ShadowSubscriptionManager.defaultSmsSubscriptionId = defaultSmsSubscriptionId;
120   }
121 
setDefaultVoiceSubscriptionId(int defaultVoiceSubscriptionId)122   public static void setDefaultVoiceSubscriptionId(int defaultVoiceSubscriptionId) {
123     ShadowSubscriptionManager.defaultVoiceSubscriptionId = defaultVoiceSubscriptionId;
124   }
125 
126   /**
127    * Cache of phone IDs used by {@link getPhoneId}. Managed by {@link putPhoneId} and {@link
128    * removePhoneId}.
129    */
130   private static Map<Integer, Integer> phoneIds = new HashMap<>();
131 
132   /**
133    * Cache of {@link SubscriptionInfo} used by {@link #getActiveSubscriptionInfoList}. Managed by
134    * {@link #setActiveSubscriptionInfoList}. May be {@code null}.
135    */
136   private static List<SubscriptionInfo> subscriptionList = new ArrayList<>();
137 
138   /**
139    * Cache of {@link SubscriptionInfo} used by {@link #getAccessibleSubscriptionInfoList}. Managed
140    * by {@link #setAccessibleSubscriptionInfos}. May be {@code null}.
141    */
142   private List<SubscriptionInfo> accessibleSubscriptionList = new ArrayList<>();
143 
144   /**
145    * Cache of {@link SubscriptionInfo} used by {@link #getAvailableSubscriptionInfoList}. Managed by
146    * {@link #setAvailableSubscriptionInfos}. May be {@code null}.
147    */
148   private List<SubscriptionInfo> availableSubscriptionList = new ArrayList<>();
149 
150   /**
151    * List of listeners to be notified if the list of {@link SubscriptionInfo} changes. Managed by
152    * {@link #addOnSubscriptionsChangedListener} and {@link removeOnSubscriptionsChangedListener}.
153    */
154   private List<OnSubscriptionsChangedListener> listeners = new ArrayList<>();
155 
156   /**
157    * Cache of subscription ids used by {@link #isNetworkRoaming}. Managed by {@link
158    * #setNetworkRoamingStatus} and {@link #clearNetworkRoamingStatus}.
159    */
160   private Set<Integer> roamingSimSubscriptionIds = new HashSet<>();
161 
162   /**
163    * Returns the active list of {@link SubscriptionInfo} that were set via {@link
164    * #setActiveSubscriptionInfoList}.
165    */
166   @Implementation(minSdk = LOLLIPOP_MR1)
getActiveSubscriptionInfoList()167   protected List<SubscriptionInfo> getActiveSubscriptionInfoList() {
168     checkReadPhoneStatePermission();
169     return subscriptionList;
170   }
171 
172   /**
173    * Returns the accessible list of {@link SubscriptionInfo} that were set via {@link
174    * #setAccessibleSubscriptionInfoList}.
175    */
176   @Implementation(minSdk = O_MR1)
getAccessibleSubscriptionInfoList()177   protected List<SubscriptionInfo> getAccessibleSubscriptionInfoList() {
178     return accessibleSubscriptionList;
179   }
180 
181   /**
182    * Returns the available list of {@link SubscriptionInfo} that were set via {@link
183    * #setAvailableSubscriptionInfoList}.
184    */
185   @Implementation(minSdk = O_MR1)
getAvailableSubscriptionInfoList()186   protected List<SubscriptionInfo> getAvailableSubscriptionInfoList() {
187     return availableSubscriptionList;
188   }
189 
190   /**
191    * Returns the size of the list of {@link SubscriptionInfo} that were set via {@link
192    * #setActiveSubscriptionInfoList}. If no list was set, returns 0.
193    */
194   @Implementation(minSdk = LOLLIPOP_MR1)
getActiveSubscriptionInfoCount()195   protected int getActiveSubscriptionInfoCount() {
196     checkReadPhoneStatePermission();
197     return subscriptionList == null ? 0 : subscriptionList.size();
198   }
199 
200   /**
201    * Returns subscription that were set via {@link #setActiveSubscriptionInfoList} if it can find
202    * one with the specified id or null if none found.
203    *
204    * <p>An exception will be thrown if the READ_PHONE_STATE permission has not been granted.
205    */
206   @Implementation(minSdk = LOLLIPOP_MR1)
getActiveSubscriptionInfo(int subId)207   protected SubscriptionInfo getActiveSubscriptionInfo(int subId) {
208     checkReadPhoneStatePermission();
209     if (subscriptionList == null) {
210       return null;
211     }
212     for (SubscriptionInfo info : subscriptionList) {
213       if (info.getSubscriptionId() == subId) {
214         return info;
215       }
216     }
217     return null;
218   }
219 
220   /**
221    * @return the maximum number of active subscriptions that will be returned by {@link
222    *     #getActiveSubscriptionInfoList} and the value returned by {@link
223    *     #getActiveSubscriptionInfoCount}.
224    */
225   @Implementation(minSdk = LOLLIPOP_MR1)
getActiveSubscriptionInfoCountMax()226   protected int getActiveSubscriptionInfoCountMax() {
227     List<SubscriptionInfo> infoList = getActiveSubscriptionInfoList();
228 
229     if (infoList == null) {
230       return getActiveSubscriptionInfoCount();
231     }
232 
233     return Math.max(getActiveSubscriptionInfoList().size(), getActiveSubscriptionInfoCount());
234   }
235 
236   /**
237    * Returns subscription that were set via {@link #setActiveSubscriptionInfoList} if it can find
238    * one with the specified slot index or null if none found.
239    */
240   @Implementation(minSdk = N)
getActiveSubscriptionInfoForSimSlotIndex(int slotIndex)241   protected SubscriptionInfo getActiveSubscriptionInfoForSimSlotIndex(int slotIndex) {
242     checkReadPhoneStatePermission();
243     if (subscriptionList == null) {
244       return null;
245     }
246     for (SubscriptionInfo info : subscriptionList) {
247       if (info.getSimSlotIndex() == slotIndex) {
248         return info;
249       }
250     }
251     return null;
252   }
253 
254   /**
255    * Sets the active list of {@link SubscriptionInfo}. This call internally triggers {@link
256    * OnSubscriptionsChangedListener#onSubscriptionsChanged()} to all the listeners.
257    *
258    * <p>"Active" here means subscriptions which are currently mapped to a live modem stack in the
259    * device (i.e. the modem will attempt to use them to connect to nearby towers), and they are
260    * expected to have {@link SubscriptionInfo#getSimSlotIndex()} >= 0. A subscription being "active"
261    * in the device does NOT have any relation to a carrier's "activation" process for subscribers'
262    * SIMs.
263    *
264    * @param list - The subscription info list, can be null.
265    */
setActiveSubscriptionInfoList(List<SubscriptionInfo> list)266   public void setActiveSubscriptionInfoList(List<SubscriptionInfo> list) {
267     subscriptionList = list;
268     dispatchOnSubscriptionsChanged();
269   }
270 
271   /**
272    * Sets the accessible list of {@link SubscriptionInfo}. This call internally triggers {@link
273    * OnSubscriptionsChangedListener#onSubscriptionsChanged()} to all the listeners.
274    *
275    * <p>"Accessible" here means subscriptions which are eSIM ({@link SubscriptionInfo#isEmbedded})
276    * and "owned" by the calling app, i.e. by {@link
277    * SubscriptionManager#canManageSubscription(SubscriptionInfo)}. They may be active, or
278    * installed-but-inactive. This is generally intended to be called by carrier apps that directly
279    * manage their own eSIM profiles on the device in concert with {@link
280    * android.telephony.EuiccManager}.
281    *
282    * @param list - The subscription info list, can be null.
283    */
setAccessibleSubscriptionInfoList(List<SubscriptionInfo> list)284   public void setAccessibleSubscriptionInfoList(List<SubscriptionInfo> list) {
285     accessibleSubscriptionList = list;
286     dispatchOnSubscriptionsChanged();
287   }
288 
289   /**
290    * Sets the available list of {@link SubscriptionInfo}. This call internally triggers {@link
291    * OnSubscriptionsChangedListener#onSubscriptionsChanged()} to all the listeners.
292    *
293    * <p>"Available" here means all active subscriptions (see {@link #setActiveSubscriptionInfoList})
294    * combined with all installed-but-inactive eSIM subscriptions (similar to {@link
295    * #setAccessibleSubscriptionInfoList}, but not filtered to one particular app's "ownership"
296    * rights for subscriptions). This is generally intended to be called by system components such as
297    * the eSIM LPA or Settings that allow the user to manage all subscriptions on the device through
298    * some system-provided user interface.
299    *
300    * @param list - The subscription info list, can be null.
301    */
setAvailableSubscriptionInfoList(List<SubscriptionInfo> list)302   public void setAvailableSubscriptionInfoList(List<SubscriptionInfo> list) {
303     availableSubscriptionList = list;
304     dispatchOnSubscriptionsChanged();
305   }
306 
307   /**
308    * Sets the active list of {@link SubscriptionInfo}. This call internally triggers {@link
309    * OnSubscriptionsChangedListener#onSubscriptionsChanged()} to all the listeners.
310    */
setActiveSubscriptionInfos(SubscriptionInfo... infos)311   public void setActiveSubscriptionInfos(SubscriptionInfo... infos) {
312     if (infos == null) {
313       setActiveSubscriptionInfoList(ImmutableList.of());
314     } else {
315       setActiveSubscriptionInfoList(Arrays.asList(infos));
316     }
317   }
318 
319   /**
320    * Sets the accessible list of {@link SubscriptionInfo}. This call internally triggers {@link
321    * OnSubscriptionsChangedListener#onSubscriptionsChanged()} to all the listeners.
322    */
setAccessibleSubscriptionInfos(SubscriptionInfo... infos)323   public void setAccessibleSubscriptionInfos(SubscriptionInfo... infos) {
324     if (infos == null) {
325       setAccessibleSubscriptionInfoList(ImmutableList.of());
326     } else {
327       setAccessibleSubscriptionInfoList(Arrays.asList(infos));
328     }
329   }
330 
331   /**
332    * Sets the available list of {@link SubscriptionInfo}. This call internally triggers {@link
333    * OnSubscriptionsChangedListener#onSubscriptionsChanged()} to all the listeners.
334    */
setAvailableSubscriptionInfos(SubscriptionInfo... infos)335   public void setAvailableSubscriptionInfos(SubscriptionInfo... infos) {
336     if (infos == null) {
337       setAvailableSubscriptionInfoList(ImmutableList.of());
338     } else {
339       setAvailableSubscriptionInfoList(Arrays.asList(infos));
340     }
341   }
342 
343   /**
344    * Adds a listener to a local list of listeners. Will be triggered by {@link
345    * #setActiveSubscriptionInfoList} when the local list of {@link SubscriptionInfo} is updated.
346    */
347   @Implementation(minSdk = LOLLIPOP_MR1)
addOnSubscriptionsChangedListener(OnSubscriptionsChangedListener listener)348   protected void addOnSubscriptionsChangedListener(OnSubscriptionsChangedListener listener) {
349     listeners.add(listener);
350     listener.onSubscriptionsChanged();
351   }
352 
353   /**
354    * Adds a listener to a local list of listeners. Will be triggered by {@link
355    * #setActiveSubscriptionInfoList} when the local list of {@link SubscriptionInfo} is updated.
356    */
357   @Implementation(minSdk = R)
addOnSubscriptionsChangedListener( Executor executor, OnSubscriptionsChangedListener listener)358   protected void addOnSubscriptionsChangedListener(
359       Executor executor, OnSubscriptionsChangedListener listener) {
360     listeners.add(listener);
361     listener.onSubscriptionsChanged();
362   }
363 
364   /**
365    * Removes a listener from a local list of listeners. Will be triggered by {@link
366    * #setActiveSubscriptionInfoList} when the local list of {@link SubscriptionInfo} is updated.
367    */
368   @Implementation(minSdk = LOLLIPOP_MR1)
removeOnSubscriptionsChangedListener(OnSubscriptionsChangedListener listener)369   protected void removeOnSubscriptionsChangedListener(OnSubscriptionsChangedListener listener) {
370     listeners.remove(listener);
371   }
372 
373   /**
374    * Check if a listener exists in the {@link ShadowSubscriptionManager.listeners}.
375    *
376    * @param listener The listener to check.
377    * @return boolean True if the listener already added, otherwise false.
378    */
hasOnSubscriptionsChangedListener(OnSubscriptionsChangedListener listener)379   public boolean hasOnSubscriptionsChangedListener(OnSubscriptionsChangedListener listener) {
380     return listeners.contains(listener);
381   }
382 
383   /** Returns subscription Ids that were set via {@link #setActiveSubscriptionInfoList}. */
384   @Implementation(minSdk = LOLLIPOP_MR1)
385   @HiddenApi
getActiveSubscriptionIdList()386   protected int[] getActiveSubscriptionIdList() {
387     final List<SubscriptionInfo> infos = getActiveSubscriptionInfoList();
388     if (infos == null) {
389       return new int[0];
390     }
391     int[] ids = new int[infos.size()];
392     for (int i = 0; i < infos.size(); i++) {
393       ids[i] = infos.get(i).getSubscriptionId();
394     }
395     return ids;
396   }
397 
398   /**
399    * Notifies {@link OnSubscriptionsChangedListener} listeners that the list of {@link
400    * SubscriptionInfo} has been updated.
401    */
dispatchOnSubscriptionsChanged()402   private void dispatchOnSubscriptionsChanged() {
403     for (OnSubscriptionsChangedListener listener : listeners) {
404       listener.onSubscriptionsChanged();
405     }
406   }
407 
408   /** Clears the local cache of roaming subscription Ids used by {@link #isNetworkRoaming}. */
clearNetworkRoamingStatus()409   public void clearNetworkRoamingStatus() {
410     roamingSimSubscriptionIds.clear();
411   }
412 
413   /**
414    * If isNetworkRoaming is set, it will mark the provided sim subscriptionId as roaming in a local
415    * cache. If isNetworkRoaming is unset it will remove the subscriptionId from the local cache. The
416    * local cache is used to provide roaming status returned by {@link #isNetworkRoaming}.
417    */
setNetworkRoamingStatus(int simSubscriptionId, boolean isNetworkRoaming)418   public void setNetworkRoamingStatus(int simSubscriptionId, boolean isNetworkRoaming) {
419     if (isNetworkRoaming) {
420       roamingSimSubscriptionIds.add(simSubscriptionId);
421     } else {
422       roamingSimSubscriptionIds.remove(simSubscriptionId);
423     }
424   }
425 
426   /**
427    * Uses the local cache of roaming sim subscription Ids managed by {@link
428    * #setNetworkRoamingStatus} to return subscription Ids marked as roaming. Otherwise subscription
429    * Ids will be considered as non-roaming if they are not in the cache.
430    */
431   @Implementation(minSdk = LOLLIPOP_MR1)
isNetworkRoaming(int simSubscriptionId)432   protected boolean isNetworkRoaming(int simSubscriptionId) {
433     return roamingSimSubscriptionIds.contains(simSubscriptionId);
434   }
435 
436   /** Adds a subscription ID-phone ID mapping to the map used by {@link getPhoneId}. */
putPhoneId(int subId, int phoneId)437   public static void putPhoneId(int subId, int phoneId) {
438     phoneIds.put(subId, phoneId);
439   }
440 
441   /**
442    * Removes a subscription ID-phone ID mapping from the map used by {@link getPhoneId}.
443    *
444    * @return the previous phone ID associated with the subscription ID, or null if there was no
445    *     mapping for the subscription ID
446    */
removePhoneId(int subId)447   public static Integer removePhoneId(int subId) {
448     return phoneIds.remove(subId);
449   }
450 
451   /**
452    * Removes all mappings between subscription IDs and phone IDs from the map used by {@link
453    * getPhoneId}.
454    */
clearPhoneIds()455   public static void clearPhoneIds() {
456     phoneIds.clear();
457   }
458 
459   /**
460    * Uses the map of subscription IDs to phone IDs managed by {@link putPhoneId} and {@link
461    * removePhoneId} to return the phone ID for a given subscription ID.
462    */
463   @Implementation(minSdk = LOLLIPOP_MR1, maxSdk = P)
464   @HiddenApi
getPhoneId(int subId)465   protected static int getPhoneId(int subId) {
466     if (phoneIds.containsKey(subId)) {
467       return phoneIds.get(subId);
468     }
469     return INVALID_PHONE_INDEX;
470   }
471 
472   /**
473    * Older form of {@link #getSubscriptionId} that was designed prior to mainstream multi-SIM
474    * support, so its {@code int[]} return type ended up being an unused vestige from that older
475    * design.
476    */
477   @Implementation(minSdk = LOLLIPOP_MR1)
478   @HiddenApi
getSubId(int slotIndex)479   protected static int[] getSubId(int slotIndex) {
480     int subId = getSubscriptionId(slotIndex);
481     return subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID ? null : new int[] {subId};
482   }
483 
484   /**
485    * Older form of {@link #getSubscriptionId} that was designed prior to mainstream multi-SIM
486    * support, so its {@code int[]} return type ended up being an unused vestige from that older
487    * design.
488    */
489   @Implementation(minSdk = Q)
getSubscriptionIds(int slotIndex)490   protected int[] getSubscriptionIds(int slotIndex) {
491     return getSubId(slotIndex);
492   }
493 
494   /**
495    * Derives the subscription ID corresponding to an "active" {@link SubscriptionInfo} for the given
496    * SIM slot index.
497    */
498   @Implementation(minSdk = UPSIDE_DOWN_CAKE)
getSubscriptionId(int slotIndex)499   protected static int getSubscriptionId(int slotIndex) {
500     // Intentionally not re-calling getActiveSubscriptionInfoForSimSlotIndex since this API does not
501     // require any permissions (and this is static).
502     if (subscriptionList == null) {
503       return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
504     }
505     for (SubscriptionInfo info : subscriptionList) {
506       if (info.getSimSlotIndex() == slotIndex) {
507         return info.getSubscriptionId();
508       }
509     }
510     return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
511   }
512 
513   @Implementation(minSdk = O)
getSlotIndex(int subscriptionId)514   protected static int getSlotIndex(int subscriptionId) {
515     if (subscriptionList != null) {
516       for (SubscriptionInfo info : subscriptionList) {
517         if (info.getSubscriptionId() == subscriptionId) {
518           return info.getSimSlotIndex();
519         }
520       }
521     }
522     return INVALID_SIM_SLOT_INDEX;
523   }
524 
525   /**
526    * When set to false methods requiring {@link android.Manifest.permission.READ_PHONE_STATE}
527    * permission will throw a {@link SecurityException}. By default it's set to true for backwards
528    * compatibility.
529    */
setReadPhoneStatePermission(boolean readPhoneStatePermission)530   public void setReadPhoneStatePermission(boolean readPhoneStatePermission) {
531     this.readPhoneStatePermission = readPhoneStatePermission;
532   }
533 
checkReadPhoneStatePermission()534   private void checkReadPhoneStatePermission() {
535     if (!readPhoneStatePermission) {
536       throw new SecurityException();
537     }
538   }
539 
540   /**
541    * When set to false methods requiring {@link android.Manifest.permission.READ_PHONE_NUMBERS}
542    * permission will throw a {@link SecurityException}. By default it's set to true for backwards
543    * compatibility.
544    */
setReadPhoneNumbersPermission(boolean readPhoneNumbersPermission)545   public void setReadPhoneNumbersPermission(boolean readPhoneNumbersPermission) {
546     this.readPhoneNumbersPermission = readPhoneNumbersPermission;
547   }
548 
checkReadPhoneNumbersPermission()549   private void checkReadPhoneNumbersPermission() {
550     if (!readPhoneNumbersPermission) {
551       throw new SecurityException();
552     }
553   }
554 
555   /**
556    * Returns the phone number for the given {@code subscriptionId}, or an empty string if not
557    * available.
558    *
559    * <p>The phone number can be set by {@link #setPhoneNumber(int, String)}
560    *
561    * <p>An exception will be thrown if the READ_PHONE_NUMBERS permission has not been granted.
562    */
563   @Implementation(minSdk = TIRAMISU)
getPhoneNumber(int subscriptionId)564   protected String getPhoneNumber(int subscriptionId) {
565     checkReadPhoneNumbersPermission();
566     return phoneNumberMap.getOrDefault(subscriptionId, "");
567   }
568 
569   /**
570    * Returns the phone number for the given {@code subscriptionId}, or an empty string if not
571    * available. {@code source} is ignored and will return the same as {@link #getPhoneNumber(int)}.
572    *
573    * <p>The phone number can be set by {@link #setPhoneNumber(int, String)}
574    */
575   @Implementation(minSdk = TIRAMISU)
getPhoneNumber(int subscriptionId, int source)576   protected String getPhoneNumber(int subscriptionId, int source) {
577     return getPhoneNumber(subscriptionId);
578   }
579 
580   /** Sets the phone number returned by {@link #getPhoneNumber(int)}. */
setPhoneNumber(int subscriptionId, String phoneNumber)581   public void setPhoneNumber(int subscriptionId, String phoneNumber) {
582     phoneNumberMap.put(subscriptionId, phoneNumber);
583   }
584 
585   @Resetter
reset()586   public static void reset() {
587     activeDataSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
588     defaultDataSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
589     defaultSmsSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
590     defaultVoiceSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
591     defaultSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
592     subscriptionList = new ArrayList<>();
593     phoneIds.clear();
594     phoneNumberMap.clear();
595     readPhoneStatePermission = true;
596     readPhoneNumbersPermission = true;
597   }
598 
599   /** Builder class to create instance of {@link SubscriptionInfo}. */
600   public static class SubscriptionInfoBuilder {
601     private final SubscriptionInfo subscriptionInfo =
602         ReflectionHelpers.callConstructor(SubscriptionInfo.class);
603 
newBuilder()604     public static SubscriptionInfoBuilder newBuilder() {
605       return new SubscriptionInfoBuilder();
606     }
607 
buildSubscriptionInfo()608     public SubscriptionInfo buildSubscriptionInfo() {
609       return subscriptionInfo;
610     }
611 
setId(int id)612     public SubscriptionInfoBuilder setId(int id) {
613       ReflectionHelpers.setField(subscriptionInfo, "mId", id);
614       return this;
615     }
616 
setIccId(String iccId)617     public SubscriptionInfoBuilder setIccId(String iccId) {
618       ReflectionHelpers.setField(subscriptionInfo, "mIccId", iccId);
619       return this;
620     }
621 
setSimSlotIndex(int index)622     public SubscriptionInfoBuilder setSimSlotIndex(int index) {
623       ReflectionHelpers.setField(subscriptionInfo, "mSimSlotIndex", index);
624       return this;
625     }
626 
setDisplayName(String name)627     public SubscriptionInfoBuilder setDisplayName(String name) {
628       ReflectionHelpers.setField(subscriptionInfo, "mDisplayName", name);
629       return this;
630     }
631 
setCarrierName(String carrierName)632     public SubscriptionInfoBuilder setCarrierName(String carrierName) {
633       ReflectionHelpers.setField(subscriptionInfo, "mCarrierName", carrierName);
634       return this;
635     }
636 
setIconTint(int iconTint)637     public SubscriptionInfoBuilder setIconTint(int iconTint) {
638       ReflectionHelpers.setField(subscriptionInfo, "mIconTint", iconTint);
639       return this;
640     }
641 
setNumber(String number)642     public SubscriptionInfoBuilder setNumber(String number) {
643       ReflectionHelpers.setField(subscriptionInfo, "mNumber", number);
644       return this;
645     }
646 
setDataRoaming(int dataRoaming)647     public SubscriptionInfoBuilder setDataRoaming(int dataRoaming) {
648       ReflectionHelpers.setField(subscriptionInfo, "mDataRoaming", dataRoaming);
649       return this;
650     }
651 
setCountryIso(String countryIso)652     public SubscriptionInfoBuilder setCountryIso(String countryIso) {
653       ReflectionHelpers.setField(subscriptionInfo, "mCountryIso", countryIso);
654       return this;
655     }
656 
setProfileClass(int profileClass)657     public SubscriptionInfoBuilder setProfileClass(int profileClass) {
658       ReflectionHelpers.setField(subscriptionInfo, "mProfileClass", profileClass);
659       return this;
660     }
661 
setIsEmbedded(boolean isEmbedded)662     public SubscriptionInfoBuilder setIsEmbedded(boolean isEmbedded) {
663       ReflectionHelpers.setField(subscriptionInfo, "mIsEmbedded", isEmbedded);
664       return this;
665     }
666 
setIsOpportunistic(boolean isOpportunistic)667     public SubscriptionInfoBuilder setIsOpportunistic(boolean isOpportunistic) {
668       ReflectionHelpers.setField(subscriptionInfo, "mIsOpportunistic", isOpportunistic);
669       return this;
670     }
671 
setMnc(String mnc)672     public SubscriptionInfoBuilder setMnc(String mnc) {
673       if (VERSION.SDK_INT < Q) {
674         ReflectionHelpers.setField(subscriptionInfo, "mMnc", Integer.valueOf(mnc));
675       } else {
676         ReflectionHelpers.setField(subscriptionInfo, "mMnc", mnc);
677       }
678       return this;
679     }
680 
setMcc(String mcc)681     public SubscriptionInfoBuilder setMcc(String mcc) {
682       if (VERSION.SDK_INT < Q) {
683         ReflectionHelpers.setField(subscriptionInfo, "mMcc", Integer.valueOf(mcc));
684       } else {
685         ReflectionHelpers.setField(subscriptionInfo, "mMcc", mcc);
686       }
687       return this;
688     }
689 
690     // Use {@link #newBuilder} to construct builders.
SubscriptionInfoBuilder()691     private SubscriptionInfoBuilder() {}
692   }
693 }
694