All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.robolectric.shadows.ShadowSubscriptionManager Maven / Gradle / Ivy

The newest version!
package org.robolectric.shadows;

import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.N;
import static android.os.Build.VERSION_CODES.O_MR1;
import static android.os.Build.VERSION_CODES.P;
import static android.os.Build.VERSION_CODES.Q;
import static android.os.Build.VERSION_CODES.R;
import static android.os.Build.VERSION_CODES.TIRAMISU;

import android.os.Build.VERSION;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
import org.robolectric.annotation.HiddenApi;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.Resetter;
import org.robolectric.util.ReflectionHelpers;

@Implements(value = SubscriptionManager.class, minSdk = LOLLIPOP_MR1)
public class ShadowSubscriptionManager {

  private static boolean readPhoneStatePermission = true;
  private static boolean readPhoneNumbersPermission = true;
  public static final int INVALID_PHONE_INDEX =
      ReflectionHelpers.getStaticField(SubscriptionManager.class, "INVALID_PHONE_INDEX");

  private static int activeDataSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
  private static int defaultSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
  private static int defaultDataSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
  private static int defaultSmsSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
  private static int defaultVoiceSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;

  private static final Map phoneNumberMap = new HashMap<>();

  /** Returns value set with {@link #setActiveDataSubscriptionId(int)}. */
  @Implementation(minSdk = R)
  protected static int getActiveDataSubscriptionId() {
    return activeDataSubscriptionId;
  }

  /** Returns value set with {@link #setDefaultSubscriptionId(int)}. */
  @Implementation(minSdk = N)
  protected static int getDefaultSubscriptionId() {
    return defaultSubscriptionId;
  }

  /** Returns value set with {@link #setDefaultDataSubscriptionId(int)}. */
  @Implementation(minSdk = N)
  protected static int getDefaultDataSubscriptionId() {
    return defaultDataSubscriptionId;
  }

  /** Returns value set with {@link #setDefaultSmsSubscriptionId(int)}. */
  @Implementation(minSdk = N)
  protected static int getDefaultSmsSubscriptionId() {
    return defaultSmsSubscriptionId;
  }

  /** Returns value set with {@link #setDefaultVoiceSubscriptionId(int)}. */
  @Implementation(minSdk = N)
  protected static int getDefaultVoiceSubscriptionId() {
    return defaultVoiceSubscriptionId;
  }

  @Implementation(maxSdk = M)
  @HiddenApi
  protected static int getDefaultSubId() {
    return defaultSubscriptionId;
  }

  @Implementation(maxSdk = M)
  @HiddenApi
  protected static int getDefaultVoiceSubId() {
    return defaultVoiceSubscriptionId;
  }

  @Implementation(maxSdk = M)
  @HiddenApi
  protected static int getDefaultSmsSubId() {
    return defaultSmsSubscriptionId;
  }

  @Implementation(maxSdk = M)
  @HiddenApi
  protected static int getDefaultDataSubId() {
    return defaultDataSubscriptionId;
  }

  /** Sets the value that will be returned by {@link #getActiveDataSubscriptionId()}. */
  public static void setActiveDataSubscriptionId(int activeDataSubscriptionId) {
    ShadowSubscriptionManager.activeDataSubscriptionId = activeDataSubscriptionId;
  }

  /** Sets the value that will be returned by {@link #getDefaultSubscriptionId()}. */
  public static void setDefaultSubscriptionId(int defaultSubscriptionId) {
    ShadowSubscriptionManager.defaultSubscriptionId = defaultSubscriptionId;
  }

  public static void setDefaultDataSubscriptionId(int defaultDataSubscriptionId) {
    ShadowSubscriptionManager.defaultDataSubscriptionId = defaultDataSubscriptionId;
  }

  public static void setDefaultSmsSubscriptionId(int defaultSmsSubscriptionId) {
    ShadowSubscriptionManager.defaultSmsSubscriptionId = defaultSmsSubscriptionId;
  }

  public static void setDefaultVoiceSubscriptionId(int defaultVoiceSubscriptionId) {
    ShadowSubscriptionManager.defaultVoiceSubscriptionId = defaultVoiceSubscriptionId;
  }

  /**
   * Cache of phone IDs used by {@link getPhoneId}. Managed by {@link putPhoneId} and {@link
   * removePhoneId}.
   */
  private static Map phoneIds = new HashMap<>();

  /**
   * Cache of {@link SubscriptionInfo} used by {@link #getActiveSubscriptionInfoList}. Managed by
   * {@link #setActiveSubscriptionInfoList}.
   */
  private List subscriptionList = new ArrayList<>();

  /**
   * Cache of {@link SubscriptionInfo} used by {@link #getAccessibleSubscriptionInfoList}. Managed
   * by {@link #setAccessibleSubscriptionInfos}.
   */
  private List accessibleSubscriptionList = new ArrayList<>();

  /**
   * Cache of {@link SubscriptionInfo} used by {@link #getAvailableSubscriptionInfoList}. Managed by
   * {@link #setAvailableSubscriptionInfos}.
   */
  private List availableSubscriptionList = new ArrayList<>();

  /**
   * List of listeners to be notified if the list of {@link SubscriptionInfo} changes. Managed by
   * {@link #addOnSubscriptionsChangedListener} and {@link removeOnSubscriptionsChangedListener}.
   */
  private List listeners = new ArrayList<>();

  /**
   * Cache of subscription ids used by {@link #isNetworkRoaming}. Managed by {@link
   * #setNetworkRoamingStatus} and {@link #clearNetworkRoamingStatus}.
   */
  private Set roamingSimSubscriptionIds = new HashSet<>();

  /**
   * Returns the active list of {@link SubscriptionInfo} that were set via {@link
   * #setActiveSubscriptionInfoList}.
   */
  @Implementation(minSdk = LOLLIPOP_MR1)
  protected List getActiveSubscriptionInfoList() {
    checkReadPhoneStatePermission();
    return subscriptionList;
  }

  /**
   * Returns the accessible list of {@link SubscriptionInfo} that were set via {@link
   * #setAccessibleSubscriptionInfoList}.
   */
  @Implementation(minSdk = O_MR1)
  protected List getAccessibleSubscriptionInfoList() {
    return accessibleSubscriptionList;
  }

  /**
   * Returns the available list of {@link SubscriptionInfo} that were set via {@link
   * #setAvailableSubscriptionInfoList}.
   */
  @Implementation(minSdk = O_MR1)
  protected List getAvailableSubscriptionInfoList() {
    return availableSubscriptionList;
  }

  /**
   * Returns the size of the list of {@link SubscriptionInfo} that were set via {@link
   * #setActiveSubscriptionInfoList}. If no list was set, returns 0.
   */
  @Implementation(minSdk = LOLLIPOP_MR1)
  protected int getActiveSubscriptionInfoCount() {
    checkReadPhoneStatePermission();
    return subscriptionList == null ? 0 : subscriptionList.size();
  }

  /**
   * Returns subscription that were set via {@link #setActiveSubscriptionInfoList} if it can find
   * one with the specified id or null if none found.
   *
   * 

An exception will be thrown if the READ_PHONE_STATE permission has not been granted. */ @Implementation(minSdk = LOLLIPOP_MR1) protected SubscriptionInfo getActiveSubscriptionInfo(int subId) { checkReadPhoneStatePermission(); if (subscriptionList == null) { return null; } for (SubscriptionInfo info : subscriptionList) { if (info.getSubscriptionId() == subId) { return info; } } return null; } /** * @return the maximum number of active subscriptions that will be returned by {@link * #getActiveSubscriptionInfoList} and the value returned by {@link * #getActiveSubscriptionInfoCount}. */ @Implementation(minSdk = LOLLIPOP_MR1) protected int getActiveSubscriptionInfoCountMax() { List infoList = getActiveSubscriptionInfoList(); if (infoList == null) { return getActiveSubscriptionInfoCount(); } return Math.max(getActiveSubscriptionInfoList().size(), getActiveSubscriptionInfoCount()); } /** * Returns subscription that were set via {@link #setActiveSubscriptionInfoList} if it can find * one with the specified slot index or null if none found. */ @Implementation(minSdk = N) protected SubscriptionInfo getActiveSubscriptionInfoForSimSlotIndex(int slotIndex) { checkReadPhoneStatePermission(); if (subscriptionList == null) { return null; } for (SubscriptionInfo info : subscriptionList) { if (info.getSimSlotIndex() == slotIndex) { return info; } } return null; } /** * Sets the active list of {@link SubscriptionInfo}. This call internally triggers {@link * OnSubscriptionsChangedListener#onSubscriptionsChanged()} to all the listeners. * *

"Active" here means subscriptions which are currently mapped to a live modem stack in the * device (i.e. the modem will attempt to use them to connect to nearby towers), and they are * expected to have {@link SubscriptionInfo#getSimSlotIndex()} >= 0. A subscription being "active" * in the device does NOT have any relation to a carrier's "activation" process for subscribers' * SIMs. * * @param list - The subscription info list, can be null. */ public void setActiveSubscriptionInfoList(List list) { subscriptionList = list; dispatchOnSubscriptionsChanged(); } /** * Sets the accessible list of {@link SubscriptionInfo}. This call internally triggers {@link * OnSubscriptionsChangedListener#onSubscriptionsChanged()} to all the listeners. * *

"Accessible" here means subscriptions which are eSIM ({@link SubscriptionInfo#isEmbedded}) * and "owned" by the calling app, i.e. by {@link * SubscriptionManager#canManageSubscription(SubscriptionInfo)}. They may be active, or * installed-but-inactive. This is generally intended to be called by carrier apps that directly * manage their own eSIM profiles on the device in concert with {@link * android.telephony.EuiccManager}. * * @param list - The subscription info list, can be null. */ public void setAccessibleSubscriptionInfoList(List list) { accessibleSubscriptionList = list; dispatchOnSubscriptionsChanged(); } /** * Sets the available list of {@link SubscriptionInfo}. This call internally triggers {@link * OnSubscriptionsChangedListener#onSubscriptionsChanged()} to all the listeners. * *

"Available" here means all active subscriptions (see {@link #setActiveSubscriptionInfoList}) * combined with all installed-but-inactive eSIM subscriptions (similar to {@link * #setAccessibleSubscriptionInfoList}, but not filtered to one particular app's "ownership" * rights for subscriptions). This is generally intended to be called by system components such as * the eSIM LPA or Settings that allow the user to manage all subscriptions on the device through * some system-provided user interface. * * @param list - The subscription info list, can be null. */ public void setAvailableSubscriptionInfoList(List list) { availableSubscriptionList = list; dispatchOnSubscriptionsChanged(); } /** * Sets the active list of {@link SubscriptionInfo}. This call internally triggers {@link * OnSubscriptionsChangedListener#onSubscriptionsChanged()} to all the listeners. */ public void setActiveSubscriptionInfos(SubscriptionInfo... infos) { if (infos == null) { setActiveSubscriptionInfoList(ImmutableList.of()); } else { setActiveSubscriptionInfoList(Arrays.asList(infos)); } } /** * Sets the accessible list of {@link SubscriptionInfo}. This call internally triggers {@link * OnSubscriptionsChangedListener#onSubscriptionsChanged()} to all the listeners. */ public void setAccessibleSubscriptionInfos(SubscriptionInfo... infos) { if (infos == null) { setAccessibleSubscriptionInfoList(ImmutableList.of()); } else { setAccessibleSubscriptionInfoList(Arrays.asList(infos)); } } /** * Sets the available list of {@link SubscriptionInfo}. This call internally triggers {@link * OnSubscriptionsChangedListener#onSubscriptionsChanged()} to all the listeners. */ public void setAvailableSubscriptionInfos(SubscriptionInfo... infos) { if (infos == null) { setAvailableSubscriptionInfoList(ImmutableList.of()); } else { setAvailableSubscriptionInfoList(Arrays.asList(infos)); } } /** * Adds a listener to a local list of listeners. Will be triggered by {@link * #setActiveSubscriptionInfoList} when the local list of {@link SubscriptionInfo} is updated. */ @Implementation(minSdk = LOLLIPOP_MR1) protected void addOnSubscriptionsChangedListener(OnSubscriptionsChangedListener listener) { listeners.add(listener); listener.onSubscriptionsChanged(); } /** * Adds a listener to a local list of listeners. Will be triggered by {@link * #setActiveSubscriptionInfoList} when the local list of {@link SubscriptionInfo} is updated. */ @Implementation(minSdk = R) protected void addOnSubscriptionsChangedListener( Executor executor, OnSubscriptionsChangedListener listener) { listeners.add(listener); listener.onSubscriptionsChanged(); } /** * Removes a listener from a local list of listeners. Will be triggered by {@link * #setActiveSubscriptionInfoList} when the local list of {@link SubscriptionInfo} is updated. */ @Implementation(minSdk = LOLLIPOP_MR1) protected void removeOnSubscriptionsChangedListener(OnSubscriptionsChangedListener listener) { listeners.remove(listener); } /** * Check if a listener exists in the {@link ShadowSubscriptionManager.listeners}. * * @param listener The listener to check. * @return boolean True if the listener already added, otherwise false. */ public boolean hasOnSubscriptionsChangedListener(OnSubscriptionsChangedListener listener) { return listeners.contains(listener); } /** Returns subscription Ids that were set via {@link #setActiveSubscriptionInfoList}. */ @Implementation(minSdk = LOLLIPOP_MR1) @HiddenApi protected int[] getActiveSubscriptionIdList() { final List infos = getActiveSubscriptionInfoList(); if (infos == null) { return new int[0]; } int[] ids = new int[infos.size()]; for (int i = 0; i < infos.size(); i++) { ids[i] = infos.get(i).getSubscriptionId(); } return ids; } /** * Notifies {@link OnSubscriptionsChangedListener} listeners that the list of {@link * SubscriptionInfo} has been updated. */ private void dispatchOnSubscriptionsChanged() { for (OnSubscriptionsChangedListener listener : listeners) { listener.onSubscriptionsChanged(); } } /** Clears the local cache of roaming subscription Ids used by {@link #isNetworkRoaming}. */ public void clearNetworkRoamingStatus() { roamingSimSubscriptionIds.clear(); } /** * If isNetworkRoaming is set, it will mark the provided sim subscriptionId as roaming in a local * cache. If isNetworkRoaming is unset it will remove the subscriptionId from the local cache. The * local cache is used to provide roaming status returned by {@link #isNetworkRoaming}. */ public void setNetworkRoamingStatus(int simSubscriptionId, boolean isNetworkRoaming) { if (isNetworkRoaming) { roamingSimSubscriptionIds.add(simSubscriptionId); } else { roamingSimSubscriptionIds.remove(simSubscriptionId); } } /** * Uses the local cache of roaming sim subscription Ids managed by {@link * #setNetworkRoamingStatus} to return subscription Ids marked as roaming. Otherwise subscription * Ids will be considered as non-roaming if they are not in the cache. */ @Implementation(minSdk = LOLLIPOP_MR1) protected boolean isNetworkRoaming(int simSubscriptionId) { return roamingSimSubscriptionIds.contains(simSubscriptionId); } /** Adds a subscription ID-phone ID mapping to the map used by {@link getPhoneId}. */ public static void putPhoneId(int subId, int phoneId) { phoneIds.put(subId, phoneId); } /** * Removes a subscription ID-phone ID mapping from the map used by {@link getPhoneId}. * * @return the previous phone ID associated with the subscription ID, or null if there was no * mapping for the subscription ID */ public static Integer removePhoneId(int subId) { return phoneIds.remove(subId); } /** * Removes all mappings between subscription IDs and phone IDs from the map used by {@link * getPhoneId}. */ public static void clearPhoneIds() { phoneIds.clear(); } /** * Uses the map of subscription IDs to phone IDs managed by {@link putPhoneId} and {@link * removePhoneId} to return the phone ID for a given subscription ID. */ @Implementation(minSdk = LOLLIPOP_MR1, maxSdk = P) @HiddenApi protected static int getPhoneId(int subId) { if (phoneIds.containsKey(subId)) { return phoneIds.get(subId); } return INVALID_PHONE_INDEX; } /** * When set to false methods requiring {@link android.Manifest.permission.READ_PHONE_STATE} * permission will throw a {@link SecurityException}. By default it's set to true for backwards * compatibility. */ public void setReadPhoneStatePermission(boolean readPhoneStatePermission) { this.readPhoneStatePermission = readPhoneStatePermission; } private void checkReadPhoneStatePermission() { if (!readPhoneStatePermission) { throw new SecurityException(); } } /** * When set to false methods requiring {@link android.Manifest.permission.READ_PHONE_NUMBERS} * permission will throw a {@link SecurityException}. By default it's set to true for backwards * compatibility. */ public void setReadPhoneNumbersPermission(boolean readPhoneNumbersPermission) { this.readPhoneNumbersPermission = readPhoneNumbersPermission; } private void checkReadPhoneNumbersPermission() { if (!readPhoneNumbersPermission) { throw new SecurityException(); } } /** * Returns the phone number for the given {@code subscriptionId}, or an empty string if not * available. * *

The phone number can be set by {@link #setPhoneNumber(int, String)} * *

An exception will be thrown if the READ_PHONE_NUMBERS permission has not been granted. */ @Implementation(minSdk = TIRAMISU) protected String getPhoneNumber(int subscriptionId) { checkReadPhoneNumbersPermission(); return phoneNumberMap.getOrDefault(subscriptionId, ""); } /** * Returns the phone number for the given {@code subscriptionId}, or an empty string if not * available. {@code source} is ignored and will return the same as {@link #getPhoneNumber(int)}. * *

The phone number can be set by {@link #setPhoneNumber(int, String)} */ @Implementation(minSdk = TIRAMISU) protected String getPhoneNumber(int subscriptionId, int source) { return getPhoneNumber(subscriptionId); } /** Sets the phone number returned by {@link #getPhoneNumber(int)}. */ public void setPhoneNumber(int subscriptionId, String phoneNumber) { phoneNumberMap.put(subscriptionId, phoneNumber); } @Resetter public static void reset() { activeDataSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; defaultDataSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; defaultSmsSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; defaultVoiceSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; defaultSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; phoneIds.clear(); phoneNumberMap.clear(); readPhoneStatePermission = true; readPhoneNumbersPermission = true; } /** Builder class to create instance of {@link SubscriptionInfo}. */ public static class SubscriptionInfoBuilder { private final SubscriptionInfo subscriptionInfo = ReflectionHelpers.callConstructor(SubscriptionInfo.class); public static SubscriptionInfoBuilder newBuilder() { return new SubscriptionInfoBuilder(); } public SubscriptionInfo buildSubscriptionInfo() { return subscriptionInfo; } public SubscriptionInfoBuilder setId(int id) { ReflectionHelpers.setField(subscriptionInfo, "mId", id); return this; } public SubscriptionInfoBuilder setIccId(String iccId) { ReflectionHelpers.setField(subscriptionInfo, "mIccId", iccId); return this; } public SubscriptionInfoBuilder setSimSlotIndex(int index) { ReflectionHelpers.setField(subscriptionInfo, "mSimSlotIndex", index); return this; } public SubscriptionInfoBuilder setDisplayName(String name) { ReflectionHelpers.setField(subscriptionInfo, "mDisplayName", name); return this; } public SubscriptionInfoBuilder setCarrierName(String carrierName) { ReflectionHelpers.setField(subscriptionInfo, "mCarrierName", carrierName); return this; } public SubscriptionInfoBuilder setIconTint(int iconTint) { ReflectionHelpers.setField(subscriptionInfo, "mIconTint", iconTint); return this; } public SubscriptionInfoBuilder setNumber(String number) { ReflectionHelpers.setField(subscriptionInfo, "mNumber", number); return this; } public SubscriptionInfoBuilder setDataRoaming(int dataRoaming) { ReflectionHelpers.setField(subscriptionInfo, "mDataRoaming", dataRoaming); return this; } public SubscriptionInfoBuilder setCountryIso(String countryIso) { ReflectionHelpers.setField(subscriptionInfo, "mCountryIso", countryIso); return this; } public SubscriptionInfoBuilder setProfileClass(int profileClass) { ReflectionHelpers.setField(subscriptionInfo, "mProfileClass", profileClass); return this; } public SubscriptionInfoBuilder setIsEmbedded(boolean isEmbedded) { ReflectionHelpers.setField(subscriptionInfo, "mIsEmbedded", isEmbedded); return this; } public SubscriptionInfoBuilder setIsOpportunistic(boolean isOpportunistic) { ReflectionHelpers.setField(subscriptionInfo, "mIsOpportunistic", isOpportunistic); return this; } public SubscriptionInfoBuilder setMnc(String mnc) { if (VERSION.SDK_INT < Q) { ReflectionHelpers.setField(subscriptionInfo, "mMnc", Integer.valueOf(mnc)); } else { ReflectionHelpers.setField(subscriptionInfo, "mMnc", mnc); } return this; } public SubscriptionInfoBuilder setMcc(String mcc) { if (VERSION.SDK_INT < Q) { ReflectionHelpers.setField(subscriptionInfo, "mMcc", Integer.valueOf(mcc)); } else { ReflectionHelpers.setField(subscriptionInfo, "mMcc", mcc); } return this; } // Use {@link #newBuilder} to construct builders. private SubscriptionInfoBuilder() {} } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy