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