|  | /* | 
|  | * Copyright (C) 2014 The Android Open Source Project | 
|  | * | 
|  | * Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | * you may not use this file except in compliance with the License. | 
|  | * You may obtain a copy of the License at | 
|  | * | 
|  | *      http://www.apache.org/licenses/LICENSE-2.0 | 
|  | * | 
|  | * Unless required by applicable law or agreed to in writing, software | 
|  | * distributed under the License is distributed on an "AS IS" BASIS, | 
|  | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | * See the License for the specific language governing permissions and | 
|  | * limitations under the License. | 
|  | */ | 
|  |  | 
|  | package android.telephony; | 
|  |  | 
|  | import static android.net.NetworkPolicyManager.OVERRIDE_CONGESTED; | 
|  | import static android.net.NetworkPolicyManager.OVERRIDE_UNMETERED; | 
|  |  | 
|  | import android.annotation.DurationMillisLong; | 
|  | import android.annotation.NonNull; | 
|  | import android.annotation.Nullable; | 
|  | import android.annotation.RequiresPermission; | 
|  | import android.annotation.SdkConstant; | 
|  | import android.annotation.SdkConstant.SdkConstantType; | 
|  | import android.annotation.SuppressAutoDoc; | 
|  | import android.annotation.SystemApi; | 
|  | import android.annotation.SystemService; | 
|  | import android.app.BroadcastOptions; | 
|  | import android.content.Context; | 
|  | import android.content.Intent; | 
|  | import android.content.pm.PackageInfo; | 
|  | import android.content.pm.PackageManager; | 
|  | import android.content.res.Configuration; | 
|  | import android.content.res.Resources; | 
|  | import android.net.INetworkPolicyManager; | 
|  | import android.net.NetworkCapabilities; | 
|  | import android.net.Uri; | 
|  | import android.os.Handler; | 
|  | import android.os.Looper; | 
|  | import android.os.Message; | 
|  | import android.os.RemoteException; | 
|  | import android.os.ServiceManager; | 
|  | import android.util.DisplayMetrics; | 
|  |  | 
|  | import com.android.internal.telephony.IOnSubscriptionsChangedListener; | 
|  | import com.android.internal.telephony.ISub; | 
|  | import com.android.internal.telephony.ITelephonyRegistry; | 
|  | import com.android.internal.telephony.PhoneConstants; | 
|  |  | 
|  | import java.util.ArrayList; | 
|  | import java.util.Arrays; | 
|  | import java.util.Collections; | 
|  | import java.util.List; | 
|  | import java.util.concurrent.TimeUnit; | 
|  |  | 
|  | /** | 
|  | * SubscriptionManager is the application interface to SubscriptionController | 
|  | * and provides information about the current Telephony Subscriptions. | 
|  | */ | 
|  | @SystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE) | 
|  | public class SubscriptionManager { | 
|  | private static final String LOG_TAG = "SubscriptionManager"; | 
|  | private static final boolean DBG = false; | 
|  | private static final boolean VDBG = false; | 
|  |  | 
|  | /** An invalid subscription identifier */ | 
|  | public static final int INVALID_SUBSCRIPTION_ID = -1; | 
|  |  | 
|  | /** Base value for Dummy SUBSCRIPTION_ID's. */ | 
|  | /** FIXME: Remove DummySubId's, but for now have them map just below INVALID_SUBSCRIPTION_ID | 
|  | /** @hide */ | 
|  | public static final int DUMMY_SUBSCRIPTION_ID_BASE = INVALID_SUBSCRIPTION_ID - 1; | 
|  |  | 
|  | /** An invalid phone identifier */ | 
|  | /** @hide */ | 
|  | public static final int INVALID_PHONE_INDEX = -1; | 
|  |  | 
|  | /** An invalid slot identifier */ | 
|  | /** @hide */ | 
|  | public static final int INVALID_SIM_SLOT_INDEX = -1; | 
|  |  | 
|  | /** Indicates the caller wants the default sub id. */ | 
|  | /** @hide */ | 
|  | public static final int DEFAULT_SUBSCRIPTION_ID = Integer.MAX_VALUE; | 
|  |  | 
|  | /** | 
|  | * Indicates the caller wants the default phone id. | 
|  | * Used in SubscriptionController and Phone but do we really need it??? | 
|  | * @hide | 
|  | */ | 
|  | public static final int DEFAULT_PHONE_INDEX = Integer.MAX_VALUE; | 
|  |  | 
|  | /** Indicates the caller wants the default slot id. NOT used remove? */ | 
|  | /** @hide */ | 
|  | public static final int DEFAULT_SIM_SLOT_INDEX = Integer.MAX_VALUE; | 
|  |  | 
|  | /** Minimum possible subid that represents a subscription */ | 
|  | /** @hide */ | 
|  | public static final int MIN_SUBSCRIPTION_ID_VALUE = 0; | 
|  |  | 
|  | /** Maximum possible subid that represents a subscription */ | 
|  | /** @hide */ | 
|  | public static final int MAX_SUBSCRIPTION_ID_VALUE = DEFAULT_SUBSCRIPTION_ID - 1; | 
|  |  | 
|  | /** @hide */ | 
|  | public static final Uri CONTENT_URI = Uri.parse("content://telephony/siminfo"); | 
|  |  | 
|  | /** | 
|  | * TelephonyProvider unique key column name is the subscription id. | 
|  | * <P>Type: TEXT (String)</P> | 
|  | */ | 
|  | /** @hide */ | 
|  | public static final String UNIQUE_KEY_SUBSCRIPTION_ID = "_id"; | 
|  |  | 
|  | /** | 
|  | * TelephonyProvider column name for SIM ICC Identifier | 
|  | * <P>Type: TEXT (String)</P> | 
|  | */ | 
|  | /** @hide */ | 
|  | public static final String ICC_ID = "icc_id"; | 
|  |  | 
|  | /** | 
|  | * TelephonyProvider column name for user SIM_SlOT_INDEX | 
|  | * <P>Type: INTEGER (int)</P> | 
|  | */ | 
|  | /** @hide */ | 
|  | public static final String SIM_SLOT_INDEX = "sim_id"; | 
|  |  | 
|  | /** SIM is not inserted */ | 
|  | /** @hide */ | 
|  | public static final int SIM_NOT_INSERTED = -1; | 
|  |  | 
|  | /** | 
|  | * TelephonyProvider column name for user displayed name. | 
|  | * <P>Type: TEXT (String)</P> | 
|  | */ | 
|  | /** @hide */ | 
|  | public static final String DISPLAY_NAME = "display_name"; | 
|  |  | 
|  | /** | 
|  | * TelephonyProvider column name for the service provider name for the SIM. | 
|  | * <P>Type: TEXT (String)</P> | 
|  | */ | 
|  | /** @hide */ | 
|  | public static final String CARRIER_NAME = "carrier_name"; | 
|  |  | 
|  | /** | 
|  | * Default name resource | 
|  | * @hide | 
|  | */ | 
|  | public static final int DEFAULT_NAME_RES = com.android.internal.R.string.unknownName; | 
|  |  | 
|  | /** | 
|  | * TelephonyProvider column name for source of the user displayed name. | 
|  | * <P>Type: INT (int)</P> with one of the NAME_SOURCE_XXXX values below | 
|  | * | 
|  | * @hide | 
|  | */ | 
|  | public static final String NAME_SOURCE = "name_source"; | 
|  |  | 
|  | /** | 
|  | * The name_source is undefined | 
|  | * @hide | 
|  | */ | 
|  | public static final int NAME_SOURCE_UNDEFINDED = -1; | 
|  |  | 
|  | /** | 
|  | * The name_source is the default | 
|  | * @hide | 
|  | */ | 
|  | public static final int NAME_SOURCE_DEFAULT_SOURCE = 0; | 
|  |  | 
|  | /** | 
|  | * The name_source is from the SIM | 
|  | * @hide | 
|  | */ | 
|  | public static final int NAME_SOURCE_SIM_SOURCE = 1; | 
|  |  | 
|  | /** | 
|  | * The name_source is from the user | 
|  | * @hide | 
|  | */ | 
|  | public static final int NAME_SOURCE_USER_INPUT = 2; | 
|  |  | 
|  | /** | 
|  | * TelephonyProvider column name for the color of a SIM. | 
|  | * <P>Type: INTEGER (int)</P> | 
|  | */ | 
|  | /** @hide */ | 
|  | public static final String COLOR = "color"; | 
|  |  | 
|  | /** @hide */ | 
|  | public static final int COLOR_1 = 0; | 
|  |  | 
|  | /** @hide */ | 
|  | public static final int COLOR_2 = 1; | 
|  |  | 
|  | /** @hide */ | 
|  | public static final int COLOR_3 = 2; | 
|  |  | 
|  | /** @hide */ | 
|  | public static final int COLOR_4 = 3; | 
|  |  | 
|  | /** @hide */ | 
|  | public static final int COLOR_DEFAULT = COLOR_1; | 
|  |  | 
|  | /** | 
|  | * TelephonyProvider column name for the phone number of a SIM. | 
|  | * <P>Type: TEXT (String)</P> | 
|  | */ | 
|  | /** @hide */ | 
|  | public static final String NUMBER = "number"; | 
|  |  | 
|  | /** | 
|  | * TelephonyProvider column name for the number display format of a SIM. | 
|  | * <P>Type: INTEGER (int)</P> | 
|  | */ | 
|  | /** @hide */ | 
|  | public static final String DISPLAY_NUMBER_FORMAT = "display_number_format"; | 
|  |  | 
|  | /** @hide */ | 
|  | public static final int DISPLAY_NUMBER_NONE = 0; | 
|  |  | 
|  | /** @hide */ | 
|  | public static final int DISPLAY_NUMBER_FIRST = 1; | 
|  |  | 
|  | /** @hide */ | 
|  | public static final int DISPLAY_NUMBER_LAST = 2; | 
|  |  | 
|  | /** @hide */ | 
|  | public static final int DISPLAY_NUMBER_DEFAULT = DISPLAY_NUMBER_FIRST; | 
|  |  | 
|  | /** | 
|  | * TelephonyProvider column name for permission for data roaming of a SIM. | 
|  | * <P>Type: INTEGER (int)</P> | 
|  | */ | 
|  | /** @hide */ | 
|  | public static final String DATA_ROAMING = "data_roaming"; | 
|  |  | 
|  | /** Indicates that data roaming is enabled for a subscription */ | 
|  | public static final int DATA_ROAMING_ENABLE = 1; | 
|  |  | 
|  | /** Indicates that data roaming is disabled for a subscription */ | 
|  | public static final int DATA_ROAMING_DISABLE = 0; | 
|  |  | 
|  | /** @hide */ | 
|  | public static final int DATA_ROAMING_DEFAULT = DATA_ROAMING_DISABLE; | 
|  |  | 
|  | /** @hide */ | 
|  | public static final int SIM_PROVISIONED = 0; | 
|  |  | 
|  | /** | 
|  | * TelephonyProvider column name for the MCC associated with a SIM. | 
|  | * <P>Type: INTEGER (int)</P> | 
|  | * @hide | 
|  | */ | 
|  | public static final String MCC = "mcc"; | 
|  |  | 
|  | /** | 
|  | * TelephonyProvider column name for the MNC associated with a SIM. | 
|  | * <P>Type: INTEGER (int)</P> | 
|  | * @hide | 
|  | */ | 
|  | public static final String MNC = "mnc"; | 
|  |  | 
|  | /** | 
|  | * TelephonyProvider column name for the sim provisioning status associated with a SIM. | 
|  | * <P>Type: INTEGER (int)</P> | 
|  | * @hide | 
|  | */ | 
|  | public static final String SIM_PROVISIONING_STATUS = "sim_provisioning_status"; | 
|  |  | 
|  | /** | 
|  | * TelephonyProvider column name for whether a subscription is embedded (that is, present on an | 
|  | * eSIM). | 
|  | * <p>Type: INTEGER (int), 1 for embedded or 0 for non-embedded. | 
|  | * @hide | 
|  | */ | 
|  | public static final String IS_EMBEDDED = "is_embedded"; | 
|  |  | 
|  | /** | 
|  | * TelephonyProvider column name for SIM card identifier. For UICC card it is the ICCID of the | 
|  | * current enabled profile on the card, while for eUICC card it is the EID of the card. | 
|  | * <P>Type: TEXT (String)</P> | 
|  | * @hide | 
|  | */ | 
|  | public static final String CARD_ID = "card_id"; | 
|  |  | 
|  | /** | 
|  | * TelephonyProvider column name for the encoded {@link UiccAccessRule}s from | 
|  | * {@link UiccAccessRule#encodeRules}. Only present if {@link #IS_EMBEDDED} is 1. | 
|  | * <p>TYPE: BLOB | 
|  | * @hide | 
|  | */ | 
|  | public static final String ACCESS_RULES = "access_rules"; | 
|  |  | 
|  | /** | 
|  | * TelephonyProvider column name identifying whether an embedded subscription is on a removable | 
|  | * card. Such subscriptions are marked inaccessible as soon as the current card is removed. | 
|  | * Otherwise, they will remain accessible unless explicitly deleted. Only present if | 
|  | * {@link #IS_EMBEDDED} is 1. | 
|  | * <p>TYPE: INTEGER (int), 1 for removable or 0 for non-removable. | 
|  | * @hide | 
|  | */ | 
|  | public static final String IS_REMOVABLE = "is_removable"; | 
|  |  | 
|  | /** | 
|  | *  TelephonyProvider column name for extreme threat in CB settings | 
|  | * @hide | 
|  | */ | 
|  | public static final String CB_EXTREME_THREAT_ALERT = "enable_cmas_extreme_threat_alerts"; | 
|  |  | 
|  | /** | 
|  | * TelephonyProvider column name for severe threat in CB settings | 
|  | *@hide | 
|  | */ | 
|  | public static final String CB_SEVERE_THREAT_ALERT = "enable_cmas_severe_threat_alerts"; | 
|  |  | 
|  | /** | 
|  | * TelephonyProvider column name for amber alert in CB settings | 
|  | *@hide | 
|  | */ | 
|  | public static final String CB_AMBER_ALERT = "enable_cmas_amber_alerts"; | 
|  |  | 
|  | /** | 
|  | * TelephonyProvider column name for emergency alert in CB settings | 
|  | *@hide | 
|  | */ | 
|  | public static final String CB_EMERGENCY_ALERT = "enable_emergency_alerts"; | 
|  |  | 
|  | /** | 
|  | * TelephonyProvider column name for alert sound duration in CB settings | 
|  | *@hide | 
|  | */ | 
|  | public static final String CB_ALERT_SOUND_DURATION = "alert_sound_duration"; | 
|  |  | 
|  | /** | 
|  | * TelephonyProvider column name for alert reminder interval in CB settings | 
|  | *@hide | 
|  | */ | 
|  | public static final String CB_ALERT_REMINDER_INTERVAL = "alert_reminder_interval"; | 
|  |  | 
|  | /** | 
|  | * TelephonyProvider column name for enabling vibrate in CB settings | 
|  | *@hide | 
|  | */ | 
|  | public static final String CB_ALERT_VIBRATE = "enable_alert_vibrate"; | 
|  |  | 
|  | /** | 
|  | * TelephonyProvider column name for enabling alert speech in CB settings | 
|  | *@hide | 
|  | */ | 
|  | public static final String CB_ALERT_SPEECH = "enable_alert_speech"; | 
|  |  | 
|  | /** | 
|  | * TelephonyProvider column name for ETWS test alert in CB settings | 
|  | *@hide | 
|  | */ | 
|  | public static final String CB_ETWS_TEST_ALERT = "enable_etws_test_alerts"; | 
|  |  | 
|  | /** | 
|  | * TelephonyProvider column name for enable channel50 alert in CB settings | 
|  | *@hide | 
|  | */ | 
|  | public static final String CB_CHANNEL_50_ALERT = "enable_channel_50_alerts"; | 
|  |  | 
|  | /** | 
|  | * TelephonyProvider column name for CMAS test alert in CB settings | 
|  | *@hide | 
|  | */ | 
|  | public static final String CB_CMAS_TEST_ALERT= "enable_cmas_test_alerts"; | 
|  |  | 
|  | /** | 
|  | * TelephonyProvider column name for Opt out dialog in CB settings | 
|  | *@hide | 
|  | */ | 
|  | public static final String CB_OPT_OUT_DIALOG = "show_cmas_opt_out_dialog"; | 
|  |  | 
|  | /** | 
|  | * TelephonyProvider column name for enable Volte. | 
|  | * | 
|  | * If this setting is not initialized (set to -1)  then we use the Carrier Config value | 
|  | * {@link CarrierConfigManager#KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL}. | 
|  | *@hide | 
|  | */ | 
|  | public static final String ENHANCED_4G_MODE_ENABLED = "volte_vt_enabled"; | 
|  |  | 
|  | /** | 
|  | * TelephonyProvider column name for enable VT (Video Telephony over IMS) | 
|  | *@hide | 
|  | */ | 
|  | public static final String VT_IMS_ENABLED = "vt_ims_enabled"; | 
|  |  | 
|  | /** | 
|  | * TelephonyProvider column name for enable Wifi calling | 
|  | *@hide | 
|  | */ | 
|  | public static final String WFC_IMS_ENABLED = "wfc_ims_enabled"; | 
|  |  | 
|  | /** | 
|  | * TelephonyProvider column name for Wifi calling mode | 
|  | *@hide | 
|  | */ | 
|  | public static final String WFC_IMS_MODE = "wfc_ims_mode"; | 
|  |  | 
|  | /** | 
|  | * TelephonyProvider column name for Wifi calling mode in roaming | 
|  | *@hide | 
|  | */ | 
|  | public static final String WFC_IMS_ROAMING_MODE = "wfc_ims_roaming_mode"; | 
|  |  | 
|  | /** | 
|  | * TelephonyProvider column name for enable Wifi calling in roaming | 
|  | *@hide | 
|  | */ | 
|  | public static final String WFC_IMS_ROAMING_ENABLED = "wfc_ims_roaming_enabled"; | 
|  |  | 
|  | /** | 
|  | * Broadcast Action: The user has changed one of the default subs related to | 
|  | * data, phone calls, or sms</p> | 
|  | * | 
|  | * TODO: Change to a listener | 
|  | * @hide | 
|  | */ | 
|  | @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) | 
|  | public static final String SUB_DEFAULT_CHANGED_ACTION = | 
|  | "android.intent.action.SUB_DEFAULT_CHANGED"; | 
|  |  | 
|  | /** | 
|  | * Broadcast Action: The default subscription has changed.  This has the following | 
|  | * extra values:</p> | 
|  | * The {@link #EXTRA_SUBSCRIPTION_INDEX} extra indicates the current default subscription index | 
|  | */ | 
|  | @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) | 
|  | public static final String ACTION_DEFAULT_SUBSCRIPTION_CHANGED | 
|  | = "android.telephony.action.DEFAULT_SUBSCRIPTION_CHANGED"; | 
|  |  | 
|  | /** | 
|  | * Broadcast Action: The default sms subscription has changed.  This has the following | 
|  | * extra values:</p> | 
|  | * {@link #EXTRA_SUBSCRIPTION_INDEX} extra indicates the current default sms | 
|  | * subscription index | 
|  | */ | 
|  | @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) | 
|  | public static final String ACTION_DEFAULT_SMS_SUBSCRIPTION_CHANGED | 
|  | = "android.telephony.action.DEFAULT_SMS_SUBSCRIPTION_CHANGED"; | 
|  |  | 
|  | /** | 
|  | * Activity Action: Display UI for managing the billing relationship plans | 
|  | * between a carrier and a specific subscriber. | 
|  | * <p> | 
|  | * Carrier apps are encouraged to implement this activity, and the OS will | 
|  | * provide an affordance to quickly enter this activity, typically via | 
|  | * Settings. This affordance will only be shown when the carrier app is | 
|  | * actively providing subscription plan information via | 
|  | * {@link #setSubscriptionPlans(int, List)}. | 
|  | * <p> | 
|  | * Contains {@link #EXTRA_SUBSCRIPTION_INDEX} to indicate which subscription | 
|  | * the user is interested in. | 
|  | */ | 
|  | @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) | 
|  | @SystemApi | 
|  | public static final String ACTION_MANAGE_SUBSCRIPTION_PLANS | 
|  | = "android.telephony.action.MANAGE_SUBSCRIPTION_PLANS"; | 
|  |  | 
|  | /** | 
|  | * Broadcast Action: Request a refresh of the billing relationship plans | 
|  | * between a carrier and a specific subscriber. | 
|  | * <p> | 
|  | * Carrier apps are encouraged to implement this receiver, and the OS will | 
|  | * provide an affordance to request a refresh. This affordance will only be | 
|  | * shown when the carrier app is actively providing subscription plan | 
|  | * information via {@link #setSubscriptionPlans(int, List)}. | 
|  | * <p> | 
|  | * Contains {@link #EXTRA_SUBSCRIPTION_INDEX} to indicate which subscription | 
|  | * the user is interested in. | 
|  | * <p> | 
|  | * Receivers should protect themselves by checking that the sender holds the | 
|  | * {@code android.permission.MANAGE_SUBSCRIPTION_PLANS} permission. | 
|  | */ | 
|  | @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) | 
|  | @SystemApi | 
|  | public static final String ACTION_REFRESH_SUBSCRIPTION_PLANS | 
|  | = "android.telephony.action.REFRESH_SUBSCRIPTION_PLANS"; | 
|  |  | 
|  | /** | 
|  | * Broadcast Action: The billing relationship plans between a carrier and a | 
|  | * specific subscriber has changed. | 
|  | * <p> | 
|  | * Contains {@link #EXTRA_SUBSCRIPTION_INDEX} to indicate which subscription | 
|  | * changed. | 
|  | * | 
|  | * @hide | 
|  | */ | 
|  | @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) | 
|  | @RequiresPermission(android.Manifest.permission.MANAGE_SUBSCRIPTION_PLANS) | 
|  | public static final String ACTION_SUBSCRIPTION_PLANS_CHANGED | 
|  | = "android.telephony.action.SUBSCRIPTION_PLANS_CHANGED"; | 
|  |  | 
|  | /** | 
|  | * Integer extra used with {@link #ACTION_DEFAULT_SUBSCRIPTION_CHANGED} and | 
|  | * {@link #ACTION_DEFAULT_SMS_SUBSCRIPTION_CHANGED} to indicate the subscription | 
|  | * which has changed. | 
|  | */ | 
|  | public static final String EXTRA_SUBSCRIPTION_INDEX = "android.telephony.extra.SUBSCRIPTION_INDEX"; | 
|  |  | 
|  | private final Context mContext; | 
|  | private volatile INetworkPolicyManager mNetworkPolicy; | 
|  |  | 
|  | /** | 
|  | * A listener class for monitoring changes to {@link SubscriptionInfo} records. | 
|  | * <p> | 
|  | * Override the onSubscriptionsChanged method in the object that extends this | 
|  | * class and pass it to {@link #addOnSubscriptionsChangedListener(OnSubscriptionsChangedListener)} | 
|  | * to register your listener and to unregister invoke | 
|  | * {@link #removeOnSubscriptionsChangedListener(OnSubscriptionsChangedListener)} | 
|  | * <p> | 
|  | * Permissions android.Manifest.permission.READ_PHONE_STATE is required | 
|  | * for #onSubscriptionsChanged to be invoked. | 
|  | */ | 
|  | public static class OnSubscriptionsChangedListener { | 
|  | private class OnSubscriptionsChangedListenerHandler extends Handler { | 
|  | OnSubscriptionsChangedListenerHandler() { | 
|  | super(); | 
|  | } | 
|  |  | 
|  | OnSubscriptionsChangedListenerHandler(Looper looper) { | 
|  | super(looper); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void handleMessage(Message msg) { | 
|  | if (DBG) { | 
|  | log("handleMessage: invoke the overriden onSubscriptionsChanged()"); | 
|  | } | 
|  | OnSubscriptionsChangedListener.this.onSubscriptionsChanged(); | 
|  | } | 
|  | } | 
|  |  | 
|  | private final Handler mHandler; | 
|  |  | 
|  | public OnSubscriptionsChangedListener() { | 
|  | mHandler = new OnSubscriptionsChangedListenerHandler(); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Allow a listener to be created with a custom looper | 
|  | * @param looper the looper that the underlining handler should run on | 
|  | * @hide | 
|  | */ | 
|  | public OnSubscriptionsChangedListener(Looper looper) { | 
|  | mHandler = new OnSubscriptionsChangedListenerHandler(looper); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Callback invoked when there is any change to any SubscriptionInfo. Typically | 
|  | * this method would invoke {@link #getActiveSubscriptionInfoList} | 
|  | */ | 
|  | public void onSubscriptionsChanged() { | 
|  | if (DBG) log("onSubscriptionsChanged: NOT OVERRIDDEN"); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * The callback methods need to be called on the handler thread where | 
|  | * this object was created.  If the binder did that for us it'd be nice. | 
|  | */ | 
|  | IOnSubscriptionsChangedListener callback = new IOnSubscriptionsChangedListener.Stub() { | 
|  | @Override | 
|  | public void onSubscriptionsChanged() { | 
|  | if (DBG) log("callback: received, sendEmptyMessage(0) to handler"); | 
|  | mHandler.sendEmptyMessage(0); | 
|  | } | 
|  | }; | 
|  |  | 
|  | private void log(String s) { | 
|  | Rlog.d(LOG_TAG, s); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** @hide */ | 
|  | public SubscriptionManager(Context context) { | 
|  | if (DBG) logd("SubscriptionManager created"); | 
|  | mContext = context; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @deprecated developers should always obtain references directly from | 
|  | *             {@link Context#getSystemService(Class)}. | 
|  | */ | 
|  | @Deprecated | 
|  | public static SubscriptionManager from(Context context) { | 
|  | return (SubscriptionManager) context | 
|  | .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); | 
|  | } | 
|  |  | 
|  | private final INetworkPolicyManager getNetworkPolicy() { | 
|  | if (mNetworkPolicy == null) { | 
|  | mNetworkPolicy = INetworkPolicyManager.Stub | 
|  | .asInterface(ServiceManager.getService(Context.NETWORK_POLICY_SERVICE)); | 
|  | } | 
|  | return mNetworkPolicy; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Register for changes to the list of active {@link SubscriptionInfo} records or to the | 
|  | * individual records themselves. When a change occurs the onSubscriptionsChanged method of | 
|  | * the listener will be invoked immediately if there has been a notification. | 
|  | * | 
|  | * @param listener an instance of {@link OnSubscriptionsChangedListener} with | 
|  | *                 onSubscriptionsChanged overridden. | 
|  | */ | 
|  | public void addOnSubscriptionsChangedListener(OnSubscriptionsChangedListener listener) { | 
|  | String pkgName = mContext != null ? mContext.getOpPackageName() : "<unknown>"; | 
|  | if (DBG) { | 
|  | logd("register OnSubscriptionsChangedListener pkgName=" + pkgName | 
|  | + " listener=" + listener); | 
|  | } | 
|  | try { | 
|  | // We use the TelephonyRegistry as it runs in the system and thus is always | 
|  | // available. Where as SubscriptionController could crash and not be available | 
|  | ITelephonyRegistry tr = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService( | 
|  | "telephony.registry")); | 
|  | if (tr != null) { | 
|  | tr.addOnSubscriptionsChangedListener(pkgName, listener.callback); | 
|  | } | 
|  | } catch (RemoteException ex) { | 
|  | // Should not happen | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Unregister the {@link OnSubscriptionsChangedListener}. This is not strictly necessary | 
|  | * as the listener will automatically be unregistered if an attempt to invoke the listener | 
|  | * fails. | 
|  | * | 
|  | * @param listener that is to be unregistered. | 
|  | */ | 
|  | public void removeOnSubscriptionsChangedListener(OnSubscriptionsChangedListener listener) { | 
|  | String pkgForDebug = mContext != null ? mContext.getOpPackageName() : "<unknown>"; | 
|  | if (DBG) { | 
|  | logd("unregister OnSubscriptionsChangedListener pkgForDebug=" + pkgForDebug | 
|  | + " listener=" + listener); | 
|  | } | 
|  | try { | 
|  | // We use the TelephonyRegistry as its runs in the system and thus is always | 
|  | // available where as SubscriptionController could crash and not be available | 
|  | ITelephonyRegistry tr = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService( | 
|  | "telephony.registry")); | 
|  | if (tr != null) { | 
|  | tr.removeOnSubscriptionsChangedListener(pkgForDebug, listener.callback); | 
|  | } | 
|  | } catch (RemoteException ex) { | 
|  | // Should not happen | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Get the active SubscriptionInfo with the input subId. | 
|  | * | 
|  | * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} | 
|  | * or that the calling app has carrier privileges (see | 
|  | * {@link TelephonyManager#hasCarrierPrivileges}). | 
|  | * | 
|  | * @param subId The unique SubscriptionInfo key in database. | 
|  | * @return SubscriptionInfo, maybe null if its not active. | 
|  | */ | 
|  | @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges | 
|  | @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) | 
|  | public SubscriptionInfo getActiveSubscriptionInfo(int subId) { | 
|  | if (VDBG) logd("[getActiveSubscriptionInfo]+ subId=" + subId); | 
|  | if (!isValidSubscriptionId(subId)) { | 
|  | if (DBG) { | 
|  | logd("[getActiveSubscriptionInfo]- invalid subId"); | 
|  | } | 
|  | return null; | 
|  | } | 
|  |  | 
|  | SubscriptionInfo subInfo = null; | 
|  |  | 
|  | try { | 
|  | ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); | 
|  | if (iSub != null) { | 
|  | subInfo = iSub.getActiveSubscriptionInfo(subId, mContext.getOpPackageName()); | 
|  | } | 
|  | } catch (RemoteException ex) { | 
|  | // ignore it | 
|  | } | 
|  |  | 
|  | return subInfo; | 
|  |  | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Get the active SubscriptionInfo associated with the iccId | 
|  | * @param iccId the IccId of SIM card | 
|  | * @return SubscriptionInfo, maybe null if its not active | 
|  | * @hide | 
|  | */ | 
|  | public SubscriptionInfo getActiveSubscriptionInfoForIccIndex(String iccId) { | 
|  | if (VDBG) logd("[getActiveSubscriptionInfoForIccIndex]+ iccId=" + iccId); | 
|  | if (iccId == null) { | 
|  | logd("[getActiveSubscriptionInfoForIccIndex]- null iccid"); | 
|  | return null; | 
|  | } | 
|  |  | 
|  | SubscriptionInfo result = null; | 
|  |  | 
|  | try { | 
|  | ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); | 
|  | if (iSub != null) { | 
|  | result = iSub.getActiveSubscriptionInfoForIccId(iccId, mContext.getOpPackageName()); | 
|  | } | 
|  | } catch (RemoteException ex) { | 
|  | // ignore it | 
|  | } | 
|  |  | 
|  | return result; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Get the active SubscriptionInfo associated with the slotIndex | 
|  | * | 
|  | * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} | 
|  | * or that the calling app has carrier privileges (see | 
|  | * {@link TelephonyManager#hasCarrierPrivileges}). | 
|  | * | 
|  | * @param slotIndex the slot which the subscription is inserted | 
|  | * @return SubscriptionInfo, maybe null if its not active | 
|  | */ | 
|  | @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges | 
|  | @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) | 
|  | public SubscriptionInfo getActiveSubscriptionInfoForSimSlotIndex(int slotIndex) { | 
|  | if (VDBG) logd("[getActiveSubscriptionInfoForSimSlotIndex]+ slotIndex=" + slotIndex); | 
|  | if (!isValidSlotIndex(slotIndex)) { | 
|  | logd("[getActiveSubscriptionInfoForSimSlotIndex]- invalid slotIndex"); | 
|  | return null; | 
|  | } | 
|  |  | 
|  | SubscriptionInfo result = null; | 
|  |  | 
|  | try { | 
|  | ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); | 
|  | if (iSub != null) { | 
|  | result = iSub.getActiveSubscriptionInfoForSimSlotIndex(slotIndex, | 
|  | mContext.getOpPackageName()); | 
|  | } | 
|  | } catch (RemoteException ex) { | 
|  | // ignore it | 
|  | } | 
|  |  | 
|  | return result; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @return List of all SubscriptionInfo records in database, | 
|  | * include those that were inserted before, maybe empty but not null. | 
|  | * @hide | 
|  | */ | 
|  | public List<SubscriptionInfo> getAllSubscriptionInfoList() { | 
|  | if (VDBG) logd("[getAllSubscriptionInfoList]+"); | 
|  |  | 
|  | List<SubscriptionInfo> result = null; | 
|  |  | 
|  | try { | 
|  | ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); | 
|  | if (iSub != null) { | 
|  | result = iSub.getAllSubInfoList(mContext.getOpPackageName()); | 
|  | } | 
|  | } catch (RemoteException ex) { | 
|  | // ignore it | 
|  | } | 
|  |  | 
|  | if (result == null) { | 
|  | result = new ArrayList<>(); | 
|  | } | 
|  | return result; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Get the SubscriptionInfo(s) of the currently inserted SIM(s). The records will be sorted | 
|  | * by {@link SubscriptionInfo#getSimSlotIndex} then by {@link SubscriptionInfo#getSubscriptionId}. | 
|  | * | 
|  | * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} | 
|  | * or that the calling app has carrier privileges (see | 
|  | * {@link TelephonyManager#hasCarrierPrivileges}). In the latter case, only records accessible | 
|  | * to the calling app are returned. | 
|  | * | 
|  | * @return Sorted list of the currently {@link SubscriptionInfo} records available on the device. | 
|  | * <ul> | 
|  | * <li> | 
|  | * If null is returned the current state is unknown but if a {@link OnSubscriptionsChangedListener} | 
|  | * has been registered {@link OnSubscriptionsChangedListener#onSubscriptionsChanged} will be | 
|  | * invoked in the future. | 
|  | * </li> | 
|  | * <li> | 
|  | * If the list is empty then there are no {@link SubscriptionInfo} records currently available. | 
|  | * </li> | 
|  | * <li> | 
|  | * if the list is non-empty the list is sorted by {@link SubscriptionInfo#getSimSlotIndex} | 
|  | * then by {@link SubscriptionInfo#getSubscriptionId}. | 
|  | * </li> | 
|  | * </ul> | 
|  | */ | 
|  | @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges | 
|  | @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) | 
|  | public List<SubscriptionInfo> getActiveSubscriptionInfoList() { | 
|  | List<SubscriptionInfo> result = null; | 
|  |  | 
|  | try { | 
|  | ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); | 
|  | if (iSub != null) { | 
|  | result = iSub.getActiveSubscriptionInfoList(mContext.getOpPackageName()); | 
|  | } | 
|  | } catch (RemoteException ex) { | 
|  | // ignore it | 
|  | } | 
|  | return result; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Gets the SubscriptionInfo(s) of all available subscriptions, if any. | 
|  | * | 
|  | * <p>Available subscriptions include active ones (those with a non-negative | 
|  | * {@link SubscriptionInfo#getSimSlotIndex()}) as well as inactive but installed embedded | 
|  | * subscriptions. | 
|  | * | 
|  | * <p>The records will be sorted by {@link SubscriptionInfo#getSimSlotIndex} then by | 
|  | * {@link SubscriptionInfo#getSubscriptionId}. | 
|  | * | 
|  | * @return Sorted list of the current {@link SubscriptionInfo} records available on the | 
|  | * device. | 
|  | * <ul> | 
|  | * <li> | 
|  | * If null is returned the current state is unknown but if a | 
|  | * {@link OnSubscriptionsChangedListener} has been registered | 
|  | * {@link OnSubscriptionsChangedListener#onSubscriptionsChanged} will be invoked in the future. | 
|  | * <li> | 
|  | * If the list is empty then there are no {@link SubscriptionInfo} records currently available. | 
|  | * <li> | 
|  | * if the list is non-empty the list is sorted by {@link SubscriptionInfo#getSimSlotIndex} | 
|  | * then by {@link SubscriptionInfo#getSubscriptionId}. | 
|  | * </ul> | 
|  | * | 
|  | * <p> | 
|  | * Permissions android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE is required | 
|  | * for #getAvailableSubscriptionInfoList to be invoked. | 
|  | * @hide | 
|  | */ | 
|  | @SystemApi | 
|  | public List<SubscriptionInfo> getAvailableSubscriptionInfoList() { | 
|  | List<SubscriptionInfo> result = null; | 
|  |  | 
|  | try { | 
|  | ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); | 
|  | if (iSub != null) { | 
|  | result = iSub.getAvailableSubscriptionInfoList(mContext.getOpPackageName()); | 
|  | } | 
|  | } catch (RemoteException ex) { | 
|  | // ignore it | 
|  | } | 
|  | return result; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Gets the SubscriptionInfo(s) of all embedded subscriptions accessible to the calling app, if | 
|  | * any. | 
|  | * | 
|  | * <p>Only those subscriptions for which the calling app has carrier privileges per the | 
|  | * subscription metadata, if any, will be included in the returned list. | 
|  | * | 
|  | * <p>The records will be sorted by {@link SubscriptionInfo#getSimSlotIndex} then by | 
|  | * {@link SubscriptionInfo#getSubscriptionId}. | 
|  | * | 
|  | * @return Sorted list of the current embedded {@link SubscriptionInfo} records available on the | 
|  | * device which are accessible to the caller. | 
|  | * <ul> | 
|  | * <li> | 
|  | * If null is returned the current state is unknown but if a | 
|  | * {@link OnSubscriptionsChangedListener} has been registered | 
|  | * {@link OnSubscriptionsChangedListener#onSubscriptionsChanged} will be invoked in the future. | 
|  | * <li> | 
|  | * If the list is empty then there are no {@link SubscriptionInfo} records currently available. | 
|  | * <li> | 
|  | * if the list is non-empty the list is sorted by {@link SubscriptionInfo#getSimSlotIndex} | 
|  | * then by {@link SubscriptionInfo#getSubscriptionId}. | 
|  | * </ul> | 
|  | */ | 
|  | public List<SubscriptionInfo> getAccessibleSubscriptionInfoList() { | 
|  | List<SubscriptionInfo> result = null; | 
|  |  | 
|  | try { | 
|  | ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); | 
|  | if (iSub != null) { | 
|  | result = iSub.getAccessibleSubscriptionInfoList(mContext.getOpPackageName()); | 
|  | } | 
|  | } catch (RemoteException ex) { | 
|  | // ignore it | 
|  | } | 
|  | return result; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Request a refresh of the platform cache of profile information. | 
|  | * | 
|  | * <p>Should be called by the EuiccService implementation whenever this information changes due | 
|  | * to an operation done outside the scope of a request initiated by the platform to the | 
|  | * EuiccService. There is no need to refresh for downloads, deletes, or other operations that | 
|  | * were made through the EuiccService. | 
|  | * | 
|  | * <p>Requires the {@link android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission. | 
|  | * @hide | 
|  | */ | 
|  | @SystemApi | 
|  | public void requestEmbeddedSubscriptionInfoListRefresh() { | 
|  | try { | 
|  | ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); | 
|  | if (iSub != null) { | 
|  | iSub.requestEmbeddedSubscriptionInfoListRefresh(); | 
|  | } | 
|  | } catch (RemoteException ex) { | 
|  | // ignore it | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @return the count of all subscriptions in the database, this includes | 
|  | * all subscriptions that have been seen. | 
|  | * @hide | 
|  | */ | 
|  | public int getAllSubscriptionInfoCount() { | 
|  | if (VDBG) logd("[getAllSubscriptionInfoCount]+"); | 
|  |  | 
|  | int result = 0; | 
|  |  | 
|  | try { | 
|  | ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); | 
|  | if (iSub != null) { | 
|  | result = iSub.getAllSubInfoCount(mContext.getOpPackageName()); | 
|  | } | 
|  | } catch (RemoteException ex) { | 
|  | // ignore it | 
|  | } | 
|  |  | 
|  | return result; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * | 
|  | * Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} | 
|  | * or that the calling app has carrier privileges (see | 
|  | * {@link TelephonyManager#hasCarrierPrivileges}). In the latter case, the count will include | 
|  | * only those subscriptions accessible to the caller. | 
|  | * | 
|  | * @return the current number of active subscriptions. There is no guarantee the value | 
|  | * returned by this method will be the same as the length of the list returned by | 
|  | * {@link #getActiveSubscriptionInfoList}. | 
|  | */ | 
|  | @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges | 
|  | @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) | 
|  | public int getActiveSubscriptionInfoCount() { | 
|  | int result = 0; | 
|  |  | 
|  | try { | 
|  | ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); | 
|  | if (iSub != null) { | 
|  | result = iSub.getActiveSubInfoCount(mContext.getOpPackageName()); | 
|  | } | 
|  | } catch (RemoteException ex) { | 
|  | // ignore it | 
|  | } | 
|  |  | 
|  | return result; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @return the maximum number of active subscriptions that will be returned by | 
|  | * {@link #getActiveSubscriptionInfoList} and the value returned by | 
|  | * {@link #getActiveSubscriptionInfoCount}. | 
|  | */ | 
|  | public int getActiveSubscriptionInfoCountMax() { | 
|  | int result = 0; | 
|  |  | 
|  | try { | 
|  | ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); | 
|  | if (iSub != null) { | 
|  | result = iSub.getActiveSubInfoCountMax(); | 
|  | } | 
|  | } catch (RemoteException ex) { | 
|  | // ignore it | 
|  | } | 
|  |  | 
|  | return result; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Add a new SubscriptionInfo to SubscriptionInfo database if needed | 
|  | * @param iccId the IccId of the SIM card | 
|  | * @param slotIndex the slot which the SIM is inserted | 
|  | * @return the URL of the newly created row or the updated row | 
|  | * @hide | 
|  | */ | 
|  | public Uri addSubscriptionInfoRecord(String iccId, int slotIndex) { | 
|  | if (VDBG) logd("[addSubscriptionInfoRecord]+ iccId:" + iccId + " slotIndex:" + slotIndex); | 
|  | if (iccId == null) { | 
|  | logd("[addSubscriptionInfoRecord]- null iccId"); | 
|  | } | 
|  | if (!isValidSlotIndex(slotIndex)) { | 
|  | logd("[addSubscriptionInfoRecord]- invalid slotIndex"); | 
|  | } | 
|  |  | 
|  | try { | 
|  | ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); | 
|  | if (iSub != null) { | 
|  | // FIXME: This returns 1 on success, 0 on error should should we return it? | 
|  | iSub.addSubInfoRecord(iccId, slotIndex); | 
|  | } | 
|  | } catch (RemoteException ex) { | 
|  | // ignore it | 
|  | } | 
|  |  | 
|  | // FIXME: Always returns null? | 
|  | return null; | 
|  |  | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Set SIM icon tint color by simInfo index | 
|  | * @param tint the RGB value of icon tint color of the SIM | 
|  | * @param subId the unique SubInfoRecord index in database | 
|  | * @return the number of records updated | 
|  | * @hide | 
|  | */ | 
|  | public int setIconTint(int tint, int subId) { | 
|  | if (VDBG) logd("[setIconTint]+ tint:" + tint + " subId:" + subId); | 
|  | if (!isValidSubscriptionId(subId)) { | 
|  | logd("[setIconTint]- fail"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | int result = 0; | 
|  |  | 
|  | try { | 
|  | ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); | 
|  | if (iSub != null) { | 
|  | result = iSub.setIconTint(tint, subId); | 
|  | } | 
|  | } catch (RemoteException ex) { | 
|  | // ignore it | 
|  | } | 
|  |  | 
|  | return result; | 
|  |  | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Set display name by simInfo index | 
|  | * @param displayName the display name of SIM card | 
|  | * @param subId the unique SubscriptionInfo index in database | 
|  | * @return the number of records updated | 
|  | * @hide | 
|  | */ | 
|  | public int setDisplayName(String displayName, int subId) { | 
|  | return setDisplayName(displayName, subId, NAME_SOURCE_UNDEFINDED); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Set display name by simInfo index with name source | 
|  | * @param displayName the display name of SIM card | 
|  | * @param subId the unique SubscriptionInfo index in database | 
|  | * @param nameSource 0: NAME_SOURCE_DEFAULT_SOURCE, 1: NAME_SOURCE_SIM_SOURCE, | 
|  | *                   2: NAME_SOURCE_USER_INPUT, -1 NAME_SOURCE_UNDEFINED | 
|  | * @return the number of records updated or < 0 if invalid subId | 
|  | * @hide | 
|  | */ | 
|  | public int setDisplayName(String displayName, int subId, long nameSource) { | 
|  | if (VDBG) { | 
|  | logd("[setDisplayName]+  displayName:" + displayName + " subId:" + subId | 
|  | + " nameSource:" + nameSource); | 
|  | } | 
|  | if (!isValidSubscriptionId(subId)) { | 
|  | logd("[setDisplayName]- fail"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | int result = 0; | 
|  |  | 
|  | try { | 
|  | ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); | 
|  | if (iSub != null) { | 
|  | result = iSub.setDisplayNameUsingSrc(displayName, subId, nameSource); | 
|  | } | 
|  | } catch (RemoteException ex) { | 
|  | // ignore it | 
|  | } | 
|  |  | 
|  | return result; | 
|  |  | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Set phone number by subId | 
|  | * @param number the phone number of the SIM | 
|  | * @param subId the unique SubscriptionInfo index in database | 
|  | * @return the number of records updated | 
|  | * @hide | 
|  | */ | 
|  | public int setDisplayNumber(String number, int subId) { | 
|  | if (number == null || !isValidSubscriptionId(subId)) { | 
|  | logd("[setDisplayNumber]- fail"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | int result = 0; | 
|  |  | 
|  | try { | 
|  | ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); | 
|  | if (iSub != null) { | 
|  | result = iSub.setDisplayNumber(number, subId); | 
|  | } | 
|  | } catch (RemoteException ex) { | 
|  | // ignore it | 
|  | } | 
|  |  | 
|  | return result; | 
|  |  | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Set data roaming by simInfo index | 
|  | * @param roaming 0:Don't allow data when roaming, 1:Allow data when roaming | 
|  | * @param subId the unique SubscriptionInfo index in database | 
|  | * @return the number of records updated | 
|  | * @hide | 
|  | */ | 
|  | public int setDataRoaming(int roaming, int subId) { | 
|  | if (VDBG) logd("[setDataRoaming]+ roaming:" + roaming + " subId:" + subId); | 
|  | if (roaming < 0 || !isValidSubscriptionId(subId)) { | 
|  | logd("[setDataRoaming]- fail"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | int result = 0; | 
|  |  | 
|  | try { | 
|  | ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); | 
|  | if (iSub != null) { | 
|  | result = iSub.setDataRoaming(roaming, subId); | 
|  | } | 
|  | } catch (RemoteException ex) { | 
|  | // ignore it | 
|  | } | 
|  |  | 
|  | return result; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Get slotIndex associated with the subscription. | 
|  | * @return slotIndex as a positive integer or a negative value if an error either | 
|  | * SIM_NOT_INSERTED or < 0 if an invalid slot index | 
|  | * @hide | 
|  | */ | 
|  | public static int getSlotIndex(int subId) { | 
|  | if (!isValidSubscriptionId(subId)) { | 
|  | if (DBG) { | 
|  | logd("[getSlotIndex]- fail"); | 
|  | } | 
|  | } | 
|  |  | 
|  | int result = INVALID_SIM_SLOT_INDEX; | 
|  |  | 
|  | try { | 
|  | ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); | 
|  | if (iSub != null) { | 
|  | result = iSub.getSlotIndex(subId); | 
|  | } | 
|  | } catch (RemoteException ex) { | 
|  | // ignore it | 
|  | } | 
|  |  | 
|  | return result; | 
|  |  | 
|  | } | 
|  |  | 
|  | /** @hide */ | 
|  | public static int[] getSubId(int slotIndex) { | 
|  | if (!isValidSlotIndex(slotIndex)) { | 
|  | logd("[getSubId]- fail"); | 
|  | return null; | 
|  | } | 
|  |  | 
|  | int[] subId = null; | 
|  |  | 
|  | try { | 
|  | ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); | 
|  | if (iSub != null) { | 
|  | subId = iSub.getSubId(slotIndex); | 
|  | } | 
|  | } catch (RemoteException ex) { | 
|  | // ignore it | 
|  | } | 
|  |  | 
|  | return subId; | 
|  | } | 
|  |  | 
|  | /** @hide */ | 
|  | public static int getPhoneId(int subId) { | 
|  | if (!isValidSubscriptionId(subId)) { | 
|  | if (DBG) { | 
|  | logd("[getPhoneId]- fail"); | 
|  | } | 
|  | return INVALID_PHONE_INDEX; | 
|  | } | 
|  |  | 
|  | int result = INVALID_PHONE_INDEX; | 
|  |  | 
|  | try { | 
|  | ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); | 
|  | if (iSub != null) { | 
|  | result = iSub.getPhoneId(subId); | 
|  | } | 
|  | } catch (RemoteException ex) { | 
|  | // ignore it | 
|  | } | 
|  |  | 
|  | if (VDBG) logd("[getPhoneId]- phoneId=" + result); | 
|  | return result; | 
|  |  | 
|  | } | 
|  |  | 
|  | private static void logd(String msg) { | 
|  | Rlog.d(LOG_TAG, msg); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns the system's default subscription id. | 
|  | * | 
|  | * For a voice capable device, it will return getDefaultVoiceSubscriptionId. | 
|  | * For a data only device, it will return the getDefaultDataSubscriptionId. | 
|  | * May return an INVALID_SUBSCRIPTION_ID on error. | 
|  | * | 
|  | * @return the "system" default subscription id. | 
|  | */ | 
|  | public static int getDefaultSubscriptionId() { | 
|  | int subId = INVALID_SUBSCRIPTION_ID; | 
|  |  | 
|  | try { | 
|  | ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); | 
|  | if (iSub != null) { | 
|  | subId = iSub.getDefaultSubId(); | 
|  | } | 
|  | } catch (RemoteException ex) { | 
|  | // ignore it | 
|  | } | 
|  |  | 
|  | if (VDBG) logd("getDefaultSubId=" + subId); | 
|  | return subId; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns the system's default voice subscription id. | 
|  | * | 
|  | * On a data only device or on error, will return INVALID_SUBSCRIPTION_ID. | 
|  | * | 
|  | * @return the default voice subscription Id. | 
|  | */ | 
|  | public static int getDefaultVoiceSubscriptionId() { | 
|  | int subId = INVALID_SUBSCRIPTION_ID; | 
|  |  | 
|  | try { | 
|  | ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); | 
|  | if (iSub != null) { | 
|  | subId = iSub.getDefaultVoiceSubId(); | 
|  | } | 
|  | } catch (RemoteException ex) { | 
|  | // ignore it | 
|  | } | 
|  |  | 
|  | if (VDBG) logd("getDefaultVoiceSubscriptionId, sub id = " + subId); | 
|  | return subId; | 
|  | } | 
|  |  | 
|  | /** @hide */ | 
|  | public void setDefaultVoiceSubId(int subId) { | 
|  | if (VDBG) logd("setDefaultVoiceSubId sub id = " + subId); | 
|  | try { | 
|  | ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); | 
|  | if (iSub != null) { | 
|  | iSub.setDefaultVoiceSubId(subId); | 
|  | } | 
|  | } catch (RemoteException ex) { | 
|  | // ignore it | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Return the SubscriptionInfo for default voice subscription. | 
|  | * | 
|  | * Will return null on data only devices, or on error. | 
|  | * | 
|  | * @return the SubscriptionInfo for the default voice subscription. | 
|  | * @hide | 
|  | */ | 
|  | public SubscriptionInfo getDefaultVoiceSubscriptionInfo() { | 
|  | return getActiveSubscriptionInfo(getDefaultVoiceSubscriptionId()); | 
|  | } | 
|  |  | 
|  | /** @hide */ | 
|  | public static int getDefaultVoicePhoneId() { | 
|  | return getPhoneId(getDefaultVoiceSubscriptionId()); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns the system's default SMS subscription id. | 
|  | * | 
|  | * On a data only device or on error, will return INVALID_SUBSCRIPTION_ID. | 
|  | * | 
|  | * @return the default SMS subscription Id. | 
|  | */ | 
|  | public static int getDefaultSmsSubscriptionId() { | 
|  | int subId = INVALID_SUBSCRIPTION_ID; | 
|  |  | 
|  | try { | 
|  | ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); | 
|  | if (iSub != null) { | 
|  | subId = iSub.getDefaultSmsSubId(); | 
|  | } | 
|  | } catch (RemoteException ex) { | 
|  | // ignore it | 
|  | } | 
|  |  | 
|  | if (VDBG) logd("getDefaultSmsSubscriptionId, sub id = " + subId); | 
|  | return subId; | 
|  | } | 
|  |  | 
|  | /** @hide */ | 
|  | public void setDefaultSmsSubId(int subId) { | 
|  | if (VDBG) logd("setDefaultSmsSubId sub id = " + subId); | 
|  | try { | 
|  | ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); | 
|  | if (iSub != null) { | 
|  | iSub.setDefaultSmsSubId(subId); | 
|  | } | 
|  | } catch (RemoteException ex) { | 
|  | // ignore it | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Return the SubscriptionInfo for default voice subscription. | 
|  | * | 
|  | * Will return null on data only devices, or on error. | 
|  | * | 
|  | * @return the SubscriptionInfo for the default SMS subscription. | 
|  | * @hide | 
|  | */ | 
|  | public SubscriptionInfo getDefaultSmsSubscriptionInfo() { | 
|  | return getActiveSubscriptionInfo(getDefaultSmsSubscriptionId()); | 
|  | } | 
|  |  | 
|  | /** @hide */ | 
|  | public int getDefaultSmsPhoneId() { | 
|  | return getPhoneId(getDefaultSmsSubscriptionId()); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns the system's default data subscription id. | 
|  | * | 
|  | * On a voice only device or on error, will return INVALID_SUBSCRIPTION_ID. | 
|  | * | 
|  | * @return the default data subscription Id. | 
|  | */ | 
|  | public static int getDefaultDataSubscriptionId() { | 
|  | int subId = INVALID_SUBSCRIPTION_ID; | 
|  |  | 
|  | try { | 
|  | ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); | 
|  | if (iSub != null) { | 
|  | subId = iSub.getDefaultDataSubId(); | 
|  | } | 
|  | } catch (RemoteException ex) { | 
|  | // ignore it | 
|  | } | 
|  |  | 
|  | if (VDBG) logd("getDefaultDataSubscriptionId, sub id = " + subId); | 
|  | return subId; | 
|  | } | 
|  |  | 
|  | /** @hide */ | 
|  | public void setDefaultDataSubId(int subId) { | 
|  | if (VDBG) logd("setDataSubscription sub id = " + subId); | 
|  | try { | 
|  | ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); | 
|  | if (iSub != null) { | 
|  | iSub.setDefaultDataSubId(subId); | 
|  | } | 
|  | } catch (RemoteException ex) { | 
|  | // ignore it | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Return the SubscriptionInfo for default data subscription. | 
|  | * | 
|  | * Will return null on voice only devices, or on error. | 
|  | * | 
|  | * @return the SubscriptionInfo for the default data subscription. | 
|  | * @hide | 
|  | */ | 
|  | public SubscriptionInfo getDefaultDataSubscriptionInfo() { | 
|  | return getActiveSubscriptionInfo(getDefaultDataSubscriptionId()); | 
|  | } | 
|  |  | 
|  | /** @hide */ | 
|  | public int getDefaultDataPhoneId() { | 
|  | return getPhoneId(getDefaultDataSubscriptionId()); | 
|  | } | 
|  |  | 
|  | /** @hide */ | 
|  | public void clearSubscriptionInfo() { | 
|  | try { | 
|  | ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); | 
|  | if (iSub != null) { | 
|  | iSub.clearSubInfo(); | 
|  | } | 
|  | } catch (RemoteException ex) { | 
|  | // ignore it | 
|  | } | 
|  |  | 
|  | return; | 
|  | } | 
|  |  | 
|  | //FIXME this is vulnerable to race conditions | 
|  | /** @hide */ | 
|  | public boolean allDefaultsSelected() { | 
|  | if (!isValidSubscriptionId(getDefaultDataSubscriptionId())) { | 
|  | return false; | 
|  | } | 
|  | if (!isValidSubscriptionId(getDefaultSmsSubscriptionId())) { | 
|  | return false; | 
|  | } | 
|  | if (!isValidSubscriptionId(getDefaultVoiceSubscriptionId())) { | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * If a default is set to subscription which is not active, this will reset that default back to | 
|  | * an invalid subscription id, i.e. < 0. | 
|  | * @hide | 
|  | */ | 
|  | public void clearDefaultsForInactiveSubIds() { | 
|  | if (VDBG) logd("clearDefaultsForInactiveSubIds"); | 
|  | try { | 
|  | ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); | 
|  | if (iSub != null) { | 
|  | iSub.clearDefaultsForInactiveSubIds(); | 
|  | } | 
|  | } catch (RemoteException ex) { | 
|  | // ignore it | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @return true if a valid subId else false | 
|  | * @hide | 
|  | */ | 
|  | public static boolean isValidSubscriptionId(int subId) { | 
|  | return subId > INVALID_SUBSCRIPTION_ID ; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @return true if subId is an usable subId value else false. A | 
|  | * usable subId means its neither a INVALID_SUBSCRIPTION_ID nor a DEFAULT_SUB_ID. | 
|  | * @hide | 
|  | */ | 
|  | public static boolean isUsableSubIdValue(int subId) { | 
|  | return subId >= MIN_SUBSCRIPTION_ID_VALUE && subId <= MAX_SUBSCRIPTION_ID_VALUE; | 
|  | } | 
|  |  | 
|  | /** @hide */ | 
|  | public static boolean isValidSlotIndex(int slotIndex) { | 
|  | return slotIndex >= 0 && slotIndex < TelephonyManager.getDefault().getSimCount(); | 
|  | } | 
|  |  | 
|  | /** @hide */ | 
|  | public static boolean isValidPhoneId(int phoneId) { | 
|  | return phoneId >= 0 && phoneId < TelephonyManager.getDefault().getPhoneCount(); | 
|  | } | 
|  |  | 
|  | /** @hide */ | 
|  | public static void putPhoneIdAndSubIdExtra(Intent intent, int phoneId) { | 
|  | int[] subIds = SubscriptionManager.getSubId(phoneId); | 
|  | if (subIds != null && subIds.length > 0) { | 
|  | putPhoneIdAndSubIdExtra(intent, phoneId, subIds[0]); | 
|  | } else { | 
|  | logd("putPhoneIdAndSubIdExtra: no valid subs"); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** @hide */ | 
|  | public static void putPhoneIdAndSubIdExtra(Intent intent, int phoneId, int subId) { | 
|  | if (VDBG) logd("putPhoneIdAndSubIdExtra: phoneId=" + phoneId + " subId=" + subId); | 
|  | intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId); | 
|  | intent.putExtra(EXTRA_SUBSCRIPTION_INDEX, subId); | 
|  | intent.putExtra(PhoneConstants.PHONE_KEY, phoneId); | 
|  | //FIXME this is using phoneId and slotIndex interchangeably | 
|  | //Eventually, this should be removed as it is not the slot id | 
|  | intent.putExtra(PhoneConstants.SLOT_KEY, phoneId); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @return the list of subId's that are active, | 
|  | *         is never null but the length maybe 0. | 
|  | * @hide | 
|  | */ | 
|  | public @NonNull int[] getActiveSubscriptionIdList() { | 
|  | int[] subId = null; | 
|  |  | 
|  | try { | 
|  | ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); | 
|  | if (iSub != null) { | 
|  | subId = iSub.getActiveSubIdList(); | 
|  | } | 
|  | } catch (RemoteException ex) { | 
|  | // ignore it | 
|  | } | 
|  |  | 
|  | if (subId == null) { | 
|  | subId = new int[0]; | 
|  | } | 
|  |  | 
|  | return subId; | 
|  |  | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns true if the device is considered roaming on the current | 
|  | * network for a subscription. | 
|  | * <p> | 
|  | * Availability: Only when user registered to a network. | 
|  | * | 
|  | * @param subId The subscription ID | 
|  | * @return true if the network for the subscription is roaming, false otherwise | 
|  | */ | 
|  | public boolean isNetworkRoaming(int subId) { | 
|  | final int phoneId = getPhoneId(subId); | 
|  | if (phoneId < 0) { | 
|  | // What else can we do? | 
|  | return false; | 
|  | } | 
|  | return TelephonyManager.getDefault().isNetworkRoaming(subId); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns a constant indicating the state of sim for the slot index. | 
|  | * | 
|  | * @param slotIndex | 
|  | * | 
|  | * {@See TelephonyManager#SIM_STATE_UNKNOWN} | 
|  | * {@See TelephonyManager#SIM_STATE_ABSENT} | 
|  | * {@See TelephonyManager#SIM_STATE_PIN_REQUIRED} | 
|  | * {@See TelephonyManager#SIM_STATE_PUK_REQUIRED} | 
|  | * {@See TelephonyManager#SIM_STATE_NETWORK_LOCKED} | 
|  | * {@See TelephonyManager#SIM_STATE_READY} | 
|  | * {@See TelephonyManager#SIM_STATE_NOT_READY} | 
|  | * {@See TelephonyManager#SIM_STATE_PERM_DISABLED} | 
|  | * {@See TelephonyManager#SIM_STATE_CARD_IO_ERROR} | 
|  | * | 
|  | * {@hide} | 
|  | */ | 
|  | public static int getSimStateForSlotIndex(int slotIndex) { | 
|  | int simState = TelephonyManager.SIM_STATE_UNKNOWN; | 
|  |  | 
|  | try { | 
|  | ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); | 
|  | if (iSub != null) { | 
|  | simState = iSub.getSimStateForSlotIndex(slotIndex); | 
|  | } | 
|  | } catch (RemoteException ex) { | 
|  | } | 
|  |  | 
|  | return simState; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Store properties associated with SubscriptionInfo in database | 
|  | * @param subId Subscription Id of Subscription | 
|  | * @param propKey Column name in database associated with SubscriptionInfo | 
|  | * @param propValue Value to store in DB for particular subId & column name | 
|  | * @hide | 
|  | */ | 
|  | public static void setSubscriptionProperty(int subId, String propKey, String propValue) { | 
|  | try { | 
|  | ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); | 
|  | if (iSub != null) { | 
|  | iSub.setSubscriptionProperty(subId, propKey, propValue); | 
|  | } | 
|  | } catch (RemoteException ex) { | 
|  | // ignore it | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Store properties associated with SubscriptionInfo in database | 
|  | * @param subId Subscription Id of Subscription | 
|  | * @param propKey Column name in SubscriptionInfo database | 
|  | * @return Value associated with subId and propKey column in database | 
|  | * @hide | 
|  | */ | 
|  | private static String getSubscriptionProperty(int subId, String propKey, | 
|  | Context context) { | 
|  | String resultValue = null; | 
|  | try { | 
|  | ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); | 
|  | if (iSub != null) { | 
|  | resultValue = iSub.getSubscriptionProperty(subId, propKey, | 
|  | context.getOpPackageName()); | 
|  | } | 
|  | } catch (RemoteException ex) { | 
|  | // ignore it | 
|  | } | 
|  | return resultValue; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns boolean value corresponding to query result. | 
|  | * @param subId Subscription Id of Subscription | 
|  | * @param propKey Column name in SubscriptionInfo database | 
|  | * @param defValue Default boolean value to be returned | 
|  | * @return boolean result value to be returned | 
|  | * @hide | 
|  | */ | 
|  | public static boolean getBooleanSubscriptionProperty(int subId, String propKey, | 
|  | boolean defValue, Context context) { | 
|  | String result = getSubscriptionProperty(subId, propKey, context); | 
|  | if (result != null) { | 
|  | try { | 
|  | return Integer.parseInt(result) == 1; | 
|  | } catch (NumberFormatException err) { | 
|  | logd("getBooleanSubscriptionProperty NumberFormat exception"); | 
|  | } | 
|  | } | 
|  | return defValue; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns integer value corresponding to query result. | 
|  | * @param subId Subscription Id of Subscription | 
|  | * @param propKey Column name in SubscriptionInfo database | 
|  | * @param defValue Default integer value to be returned | 
|  | * @return integer result value to be returned | 
|  | * @hide | 
|  | */ | 
|  | public static int getIntegerSubscriptionProperty(int subId, String propKey, int defValue, | 
|  | Context context) { | 
|  | String result = getSubscriptionProperty(subId, propKey, context); | 
|  | if (result != null) { | 
|  | try { | 
|  | return Integer.parseInt(result); | 
|  | } catch (NumberFormatException err) { | 
|  | logd("getBooleanSubscriptionProperty NumberFormat exception"); | 
|  | } | 
|  | } | 
|  | return defValue; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns the resources associated with Subscription. | 
|  | * @param context Context object | 
|  | * @param subId Subscription Id of Subscription who's resources are required | 
|  | * @return Resources associated with Subscription. | 
|  | * @hide | 
|  | */ | 
|  | public static Resources getResourcesForSubId(Context context, int subId) { | 
|  | final SubscriptionInfo subInfo = | 
|  | SubscriptionManager.from(context).getActiveSubscriptionInfo(subId); | 
|  |  | 
|  | Configuration config = context.getResources().getConfiguration(); | 
|  | Configuration newConfig = new Configuration(); | 
|  | newConfig.setTo(config); | 
|  | if (subInfo != null) { | 
|  | newConfig.mcc = subInfo.getMcc(); | 
|  | newConfig.mnc = subInfo.getMnc(); | 
|  | if (newConfig.mnc == 0) newConfig.mnc = Configuration.MNC_ZERO; | 
|  | } | 
|  | DisplayMetrics metrics = context.getResources().getDisplayMetrics(); | 
|  | DisplayMetrics newMetrics = new DisplayMetrics(); | 
|  | newMetrics.setTo(metrics); | 
|  | return new Resources(context.getResources().getAssets(), newMetrics, newConfig); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @return true if the sub ID is active. i.e. The sub ID corresponds to a known subscription | 
|  | * and the SIM providing the subscription is present in a slot and in "LOADED" state. | 
|  | * @hide | 
|  | */ | 
|  | public boolean isActiveSubId(int subId) { | 
|  | try { | 
|  | ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); | 
|  | if (iSub != null) { | 
|  | return iSub.isActiveSubId(subId); | 
|  | } | 
|  | } catch (RemoteException ex) { | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Get the description of the billing relationship plan between a carrier | 
|  | * and a specific subscriber. | 
|  | * <p> | 
|  | * This method is only accessible to the following narrow set of apps: | 
|  | * <ul> | 
|  | * <li>The carrier app for this subscriberId, as determined by | 
|  | * {@link TelephonyManager#hasCarrierPrivileges()}. | 
|  | * <li>The carrier app explicitly delegated access through | 
|  | * {@link CarrierConfigManager#KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING}. | 
|  | * </ul> | 
|  | * | 
|  | * @param subId the subscriber this relationship applies to | 
|  | * @throws SecurityException if the caller doesn't meet the requirements | 
|  | *             outlined above. | 
|  | */ | 
|  | @SystemApi | 
|  | public @NonNull List<SubscriptionPlan> getSubscriptionPlans(int subId) { | 
|  | try { | 
|  | SubscriptionPlan[] subscriptionPlans = | 
|  | getNetworkPolicy().getSubscriptionPlans(subId, mContext.getOpPackageName()); | 
|  | return subscriptionPlans == null | 
|  | ? Collections.emptyList() : Arrays.asList(subscriptionPlans); | 
|  | } catch (RemoteException e) { | 
|  | throw e.rethrowFromSystemServer(); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Set the description of the billing relationship plan between a carrier | 
|  | * and a specific subscriber. | 
|  | * <p> | 
|  | * This method is only accessible to the following narrow set of apps: | 
|  | * <ul> | 
|  | * <li>The carrier app for this subscriberId, as determined by | 
|  | * {@link TelephonyManager#hasCarrierPrivileges()}. | 
|  | * <li>The carrier app explicitly delegated access through | 
|  | * {@link CarrierConfigManager#KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING}. | 
|  | * </ul> | 
|  | * | 
|  | * @param subId the subscriber this relationship applies to. An empty list | 
|  | *            may be sent to clear any existing plans. | 
|  | * @param plans the list of plans. The first plan is always the primary and | 
|  | *            most important plan. Any additional plans are secondary and | 
|  | *            may not be displayed or used by decision making logic. | 
|  | * @throws SecurityException if the caller doesn't meet the requirements | 
|  | *             outlined above. | 
|  | */ | 
|  | @SystemApi | 
|  | public void setSubscriptionPlans(int subId, @NonNull List<SubscriptionPlan> plans) { | 
|  | try { | 
|  | getNetworkPolicy().setSubscriptionPlans(subId, | 
|  | plans.toArray(new SubscriptionPlan[plans.size()]), mContext.getOpPackageName()); | 
|  | } catch (RemoteException e) { | 
|  | throw e.rethrowFromSystemServer(); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** @hide */ | 
|  | private String getSubscriptionPlansOwner(int subId) { | 
|  | try { | 
|  | return getNetworkPolicy().getSubscriptionPlansOwner(subId); | 
|  | } catch (RemoteException e) { | 
|  | throw e.rethrowFromSystemServer(); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Temporarily override the billing relationship plan between a carrier and | 
|  | * a specific subscriber to be considered unmetered. This will be reflected | 
|  | * to apps via {@link NetworkCapabilities#NET_CAPABILITY_NOT_METERED}. | 
|  | * <p> | 
|  | * This method is only accessible to the following narrow set of apps: | 
|  | * <ul> | 
|  | * <li>The carrier app for this subscriberId, as determined by | 
|  | * {@link TelephonyManager#hasCarrierPrivileges()}. | 
|  | * <li>The carrier app explicitly delegated access through | 
|  | * {@link CarrierConfigManager#KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING}. | 
|  | * </ul> | 
|  | * | 
|  | * @param subId the subscriber this override applies to. | 
|  | * @param overrideUnmetered set if the billing relationship should be | 
|  | *            considered unmetered. | 
|  | * @param timeoutMillis the timeout after which the requested override will | 
|  | *            be automatically cleared, or {@code 0} to leave in the | 
|  | *            requested state until explicitly cleared, or the next reboot, | 
|  | *            whichever happens first. | 
|  | * @throws SecurityException if the caller doesn't meet the requirements | 
|  | *             outlined above. | 
|  | */ | 
|  | @SystemApi | 
|  | public void setSubscriptionOverrideUnmetered(int subId, boolean overrideUnmetered, | 
|  | @DurationMillisLong long timeoutMillis) { | 
|  | try { | 
|  | final int overrideValue = overrideUnmetered ? OVERRIDE_UNMETERED : 0; | 
|  | getNetworkPolicy().setSubscriptionOverride(subId, OVERRIDE_UNMETERED, overrideValue, | 
|  | timeoutMillis, mContext.getOpPackageName()); | 
|  | } catch (RemoteException e) { | 
|  | throw e.rethrowFromSystemServer(); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Temporarily override the billing relationship plan between a carrier and | 
|  | * a specific subscriber to be considered congested. This will cause the | 
|  | * device to delay certain network requests when possible, such as developer | 
|  | * jobs that are willing to run in a flexible time window. | 
|  | * <p> | 
|  | * This method is only accessible to the following narrow set of apps: | 
|  | * <ul> | 
|  | * <li>The carrier app for this subscriberId, as determined by | 
|  | * {@link TelephonyManager#hasCarrierPrivileges()}. | 
|  | * <li>The carrier app explicitly delegated access through | 
|  | * {@link CarrierConfigManager#KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING}. | 
|  | * </ul> | 
|  | * | 
|  | * @param subId the subscriber this override applies to. | 
|  | * @param overrideCongested set if the subscription should be considered | 
|  | *            congested. | 
|  | * @param timeoutMillis the timeout after which the requested override will | 
|  | *            be automatically cleared, or {@code 0} to leave in the | 
|  | *            requested state until explicitly cleared, or the next reboot, | 
|  | *            whichever happens first. | 
|  | * @throws SecurityException if the caller doesn't meet the requirements | 
|  | *             outlined above. | 
|  | */ | 
|  | @SystemApi | 
|  | public void setSubscriptionOverrideCongested(int subId, boolean overrideCongested, | 
|  | @DurationMillisLong long timeoutMillis) { | 
|  | try { | 
|  | final int overrideValue = overrideCongested ? OVERRIDE_CONGESTED : 0; | 
|  | getNetworkPolicy().setSubscriptionOverride(subId, OVERRIDE_CONGESTED, overrideValue, | 
|  | timeoutMillis, mContext.getOpPackageName()); | 
|  | } catch (RemoteException e) { | 
|  | throw e.rethrowFromSystemServer(); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Create an {@link Intent} that can be launched towards the carrier app | 
|  | * that is currently defining the billing relationship plan through | 
|  | * {@link #setSubscriptionPlans(int, List)}. | 
|  | * | 
|  | * @return ready to launch Intent targeted towards the carrier app, or | 
|  | *         {@code null} if no carrier app is defined, or if the defined | 
|  | *         carrier app provides no management activity. | 
|  | * @hide | 
|  | */ | 
|  | public @Nullable Intent createManageSubscriptionIntent(int subId) { | 
|  | // Bail if no owner | 
|  | final String owner = getSubscriptionPlansOwner(subId); | 
|  | if (owner == null) return null; | 
|  |  | 
|  | // Bail if no plans | 
|  | final List<SubscriptionPlan> plans = getSubscriptionPlans(subId); | 
|  | if (plans.isEmpty()) return null; | 
|  |  | 
|  | final Intent intent = new Intent(ACTION_MANAGE_SUBSCRIPTION_PLANS); | 
|  | intent.setPackage(owner); | 
|  | intent.putExtra(EXTRA_SUBSCRIPTION_INDEX, subId); | 
|  |  | 
|  | // Bail if not implemented | 
|  | if (mContext.getPackageManager().queryIntentActivities(intent, | 
|  | PackageManager.MATCH_DEFAULT_ONLY).isEmpty()) { | 
|  | return null; | 
|  | } | 
|  |  | 
|  | return intent; | 
|  | } | 
|  |  | 
|  | /** @hide */ | 
|  | private @Nullable Intent createRefreshSubscriptionIntent(int subId) { | 
|  | // Bail if no owner | 
|  | final String owner = getSubscriptionPlansOwner(subId); | 
|  | if (owner == null) return null; | 
|  |  | 
|  | // Bail if no plans | 
|  | final List<SubscriptionPlan> plans = getSubscriptionPlans(subId); | 
|  | if (plans.isEmpty()) return null; | 
|  |  | 
|  | final Intent intent = new Intent(ACTION_REFRESH_SUBSCRIPTION_PLANS); | 
|  | intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); | 
|  | intent.setPackage(owner); | 
|  | intent.putExtra(EXTRA_SUBSCRIPTION_INDEX, subId); | 
|  |  | 
|  | // Bail if not implemented | 
|  | if (mContext.getPackageManager().queryBroadcastReceivers(intent, 0).isEmpty()) { | 
|  | return null; | 
|  | } | 
|  |  | 
|  | return intent; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Check if there is a carrier app that is currently defining the billing | 
|  | * relationship plan through {@link #setSubscriptionPlans(int, List)} that | 
|  | * supports refreshing of subscription plans. | 
|  | * | 
|  | * @hide | 
|  | */ | 
|  | public boolean isSubscriptionPlansRefreshSupported(int subId) { | 
|  | return createRefreshSubscriptionIntent(subId) != null; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Request that the carrier app that is currently defining the billing | 
|  | * relationship plan through {@link #setSubscriptionPlans(int, List)} | 
|  | * refresh its subscription plans. | 
|  | * <p> | 
|  | * If the app is able to successfully update the plans, you'll expect to | 
|  | * receive the {@link #ACTION_SUBSCRIPTION_PLANS_CHANGED} broadcast. | 
|  | * | 
|  | * @hide | 
|  | */ | 
|  | public void requestSubscriptionPlansRefresh(int subId) { | 
|  | final Intent intent = createRefreshSubscriptionIntent(subId); | 
|  | final BroadcastOptions options = BroadcastOptions.makeBasic(); | 
|  | options.setTemporaryAppWhitelistDuration(TimeUnit.MINUTES.toMillis(1)); | 
|  | mContext.sendBroadcast(intent, null, options.toBundle()); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Checks whether the app with the given context is authorized to manage the given subscription | 
|  | * according to its metadata. Only supported for embedded subscriptions (if | 
|  | * {@code SubscriptionInfo#isEmbedded} returns true). | 
|  | * | 
|  | * @param info The subscription to check. | 
|  | * @return whether the app is authorized to manage this subscription per its metadata. | 
|  | * @throws IllegalArgumentException if this subscription is not embedded. | 
|  | */ | 
|  | public boolean canManageSubscription(SubscriptionInfo info) { | 
|  | return canManageSubscription(info, mContext.getPackageName()); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Checks whether the given app is authorized to manage the given subscription. An app can only | 
|  | * be authorized if it is included in the {@link android.telephony.UiccAccessRule} of the | 
|  | * {@link android.telephony.SubscriptionInfo} with the access status. | 
|  | * Only supported for embedded subscriptions (if {@link SubscriptionInfo#isEmbedded} | 
|  | * returns true). | 
|  | * | 
|  | * @param info The subscription to check. | 
|  | * @param packageName Package name of the app to check. | 
|  | * @return whether the app is authorized to manage this subscription per its access rules. | 
|  | * @throws IllegalArgumentException if this subscription is not embedded. | 
|  | * @hide | 
|  | */ | 
|  | public boolean canManageSubscription(SubscriptionInfo info, String packageName) { | 
|  | if (!info.isEmbedded()) { | 
|  | throw new IllegalArgumentException("Not an embedded subscription"); | 
|  | } | 
|  | if (info.getAccessRules() == null) { | 
|  | return false; | 
|  | } | 
|  | PackageManager packageManager = mContext.getPackageManager(); | 
|  | PackageInfo packageInfo; | 
|  | try { | 
|  | packageInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES); | 
|  | } catch (PackageManager.NameNotFoundException e) { | 
|  | throw new IllegalArgumentException("Unknown package: " + packageName, e); | 
|  | } | 
|  | for (UiccAccessRule rule : info.getAccessRules()) { | 
|  | if (rule.getCarrierPrivilegeStatus(packageInfo) | 
|  | == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) { | 
|  | return true; | 
|  | } | 
|  | } | 
|  | return false; | 
|  | } | 
|  | } |