【Android】SIM 卡相关功能浅略解析
前言
SIM 卡简介
在 Android 手机中,SIM卡(Subscriber Identity Module,即用户身份模块)是一个至关重要的硬件智能卡,它是手机连接移动网络的钥匙。它不仅存储了你的身份信息,还决定了你能否打电话、发短信和使用移动数据。
SIM 卡主要功能
- 网络身份认证:存储着用于识别用户和加密信息的核心数据,如IMSI(国际移动用户识别码)和Ki(鉴权密钥),确保只有合法用户才能接入网络。
- 实现基本通信:让你能够进行语音通话、发送短信和使用运营商提供的移动数据上网。
- 存储用户数据:可以保存联系人(电话本)、短信息等个人数据,方便你换手机时随身携带。
- 安全保护:支持PIN码(个人识别码)功能。如果连续输错三次,SIM卡会被锁住,需要PUK码(个人解锁码)来解锁,防止他人盗用。
SIM 卡的演变
| 类型 | 尺寸 | 推出时间/特点 |
|---|---|---|
| 标准SIM卡 | 25mm × 15mm | 早期手机(如功能机时代)使用,尺寸如同邮票大小。 |
| Micro-SIM卡 | 15mm × 12mm | 为了节省空间而推出的缩小版,苹果iPhone 4曾带火了这种规格。 |
| Nano-SIM卡 | 12.3mm × 8.8mm | 目前主流物理SIM卡的标准,几乎只剩下芯片本身,边框塑料极少。 |
| eSIM | 无物理形态 | 嵌入式SIM卡,芯片直接焊在手机主板上。你可以通过软件在线开通运营商服务,无需插拔卡。这是当前的发展趋势,从iPhone 14系列开始,美版iPhone已完全取消物理卡槽。 |
Android 双卡双待技术解析
- 现在的Android手机,双卡功能已经非常普及。但这背后有不少技术细节:
- 双卡双待单通:这是最常见的技术方案。手机虽然插了两张卡,但共用一套射频资源。这意味着如果一张卡在通话,另一张卡就会"失联",电话打不进来,也无法上网。很多早期的双卡手机都是这样。
- 双卡双通:这是更高级的技术。手机内部有两套射频系统,可以实现一卡通话时,另一卡也能接到来电或上网。不过,这对硬件要求高,只有部分搭载高端芯片(如骁龙8 Gen 2、天玑9000等)的旗舰手机才支持。
- 主副卡设置:现在手机大多支持"盲插",即两个卡槽不区分主次。你可以在系统设置里,自由指定哪张卡用来上网,哪张卡用来打电话,非常灵活。
正文
想要判断 Android 设备是否支持使用 SIM 卡,可以使用以下代码:
public boolean isMobileDataSupported() {
getTelephonyManager().isDataCapable();//检查设备是否具备移动数据连接的能力
getTelephonyManager().getSimState() == SIM_STATE_READY;//检查SIM卡的状态是否为"准备就绪"
}
想要在“设置”中查看 SIM 卡状态,可以定位到源码packages/apps/Settings/src/com/android/settings/deviceinfo/simstatus/SimStatusPreferenceController.java
private CharSequence getCarrierName(int simSlot) {
SubscriptionInfo subInfo = getSubscriptionInfo(simSlot);
if (DomesticRoamUtils.isFeatureEnabled(mContext) && subInfo != null) {
String operatorName = DomesticRoamUtils.getRegisteredOperatorName(
mContext, subInfo.getSubscriptionId());
if (DomesticRoamUtils.EMPTY_OPERATOR_NAME != operatorName) {
return operatorName;
}
}
return (subInfo != null) ? subInfo.getCarrierName() :
mContext.getText(R.string.device_info_not_available);
}
SIM 卡很多功能可以在“设置”中进行设置与查看。在已插入 SIM 卡的前提下,进入“设置 -> 网络和互联网 -> SIM 卡”界面。该界面主要涉及到以下功能(不同系统可能不同):
- 使用 SIM 卡:即 SIM 卡的开关
- 流量信息汇总:显示已使用多少 B 的流量;数据流量警告;流量过期剩余天数
- 移动数据开关:通过移动网络访问数据
- 漫游:漫游时连接到移动数据网络服务
- 彩信开关:在关闭移动数据时发送和接收多媒体消息
- VoLTE:使用 LTE 服务提升语音通话质量(推荐)
- 首选网络类型
- WLAN 通话
- 运营商视频通话开关
- 系统选择:更改 CDMA 漫游模式
- CDMA 订阅:在 RUIM/SIM 和 NV 之间切换
- 自动选择网络开关
- 接入点名称
以下对SIM 卡的开关相关的源码进行简要分析:
“SIM 卡”界面的布局文件定位到packages/apps/Settings/res/xml/mobile_network_settings.xml:
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:key="mobile_network_pref_screen">
<com.android.settings.widget.SettingsMainSwitchPreference
android:key="use_sim_switch"
android:title="@string/mobile_network_use_sim_on"
settings:controller="com.android.settings.network.telephony.MobileNetworkSwitchController"/>
<!-- 省略部分源代码 -->
</PreferenceScreen>
由 xml 布局文件,定位出使用 SIM 卡功能的控制器源码路径 packages/apps/Settings/src/com/android/settings/network/telephony/MobileNetworkSwitchController.java:
public class MobileNetworkSwitchController extends BasePreferenceController implements
SubscriptionsChangeListener.SubscriptionsChangeListenerClient, LifecycleObserver {
private static final String TAG = "MobileNetworkSwitchCtrl";
private SettingsMainSwitchPreference mSwitchBar;
private int mSubId;
private SubscriptionsChangeListener mChangeListener;
private SubscriptionManager mSubscriptionManager;
private TelephonyManager mTelephonyManager;
private SubscriptionInfo mSubInfo = null;
private Context mContext;
private int mCallState;
private boolean isReceiverRegistered = false;
public MobileNetworkSwitchController(Context context, String preferenceKey) {
super(context, preferenceKey);
mContext = context;
mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class);
mChangeListener = new SubscriptionsChangeListener(context, this);
mTelephonyManager = (TelephonyManager) mContext
.getSystemService(Context.TELEPHONY_SERVICE);
IntentFilter filter = new IntentFilter();
filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
mContext.registerReceiver(mIntentReceiver, filter);
isReceiverRegistered = true;
mCallState = mTelephonyManager.getCallState();
}
void init(int subId) {
mSubId = subId;
}
@OnLifecycleEvent(ON_RESUME)
public void onResume() {
mChangeListener.start();
update();
}
@OnLifecycleEvent(ON_PAUSE)
public void onPause() {
mChangeListener.stop();
}
@OnLifecycleEvent(ON_DESTROY)
public void onDestroy() {
if(isReceiverRegistered) {
mContext.unregisterReceiver(mIntentReceiver);
isReceiverRegistered = false;
}
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mSwitchBar = (SettingsMainSwitchPreference) screen.findPreference(mPreferenceKey);
mSwitchBar.setOnBeforeCheckedChangeListener((toggleSwitch, isChecked) -> {
// TODO b/135222940: re-evaluate whether to use
// mSubscriptionManager#isSubscriptionEnabled
int phoneId = mSubscriptionManager.getSlotIndex(mSubId);
Log.d(TAG, "displayPreference: mSubId=" + mSubId + ", mSubInfo=" + mSubInfo);
if (mSubscriptionManager.isActiveSubscriptionId(mSubId) != isChecked) {
SubscriptionUtil.startToggleSubscriptionDialogActivity(mContext, mSubId, isChecked);
return true;
}
return false;
});
update();
}
private void update() {
if (mSwitchBar == null) {
return;
}
if (mTelephonyManager.getActiveModemCount() == 1 && !mSubscriptionManager.
canDisablePhysicalSubscription()) {
Log.d(TAG, "update: Hide SIM option for 1.4 HAL in single sim");
mSwitchBar.hide();
return;
}
for (SubscriptionInfo info : SubscriptionUtil.getAvailableSubscriptions(mContext)) {
if (info.getSubscriptionId() == mSubId) {
mSubInfo = info;
break;
}
}
boolean isEcbmEnabled = mTelephonyManager.getEmergencyCallbackMode();
boolean isScbmEnabled = TelephonyProperties.in_scbm().orElse(false);
if ((TelephonyManager.CALL_STATE_IDLE != mCallState) || isEcbmEnabled || isScbmEnabled) {
Log.d(TAG, "update: disable switchbar, isEcbmEnabled=" + isEcbmEnabled +
", isScbmEnabled=" + isScbmEnabled + ", mCallState=" + mCallState);
mSwitchBar.setSwitchBarEnabled(false);
} else {
mSwitchBar.setSwitchBarEnabled(true);
}
// For eSIM, we always want the toggle. If telephony stack support disabling a pSIM
// directly, we show the toggle.
if (mSubInfo == null) {
mSwitchBar.hide();
} else {
mSwitchBar.show();
Log.d(TAG, "update(): mSubId=" + mSubId +
", isActiveSubscriptionId=" +
mSubscriptionManager.isActiveSubscriptionId(mSubId));
mSwitchBar.setCheckedInternal(mSubscriptionManager.isActiveSubscriptionId(mSubId));
}
//#ifdef VENDOR_UROVO
//add by suychuan for usdk disable wwan
if(android.os.SystemProperties.getInt("persist.sys.allow.wwan", 0) > 0){
mSwitchBar.setEnabled(false);
}
//#endif
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE_UNSEARCHABLE;
}
@Override
public void onAirplaneModeChanged(boolean airplaneModeEnabled) {
}
@Override
public void onSubscriptionsChanged() {
update();
}
private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(TelephonyManager.ACTION_PHONE_STATE_CHANGED)) {
mCallState = mTelephonyManager.getCallState();
Log.d(TAG, "onReceive: mCallState= " + mCallState + ", mSubId=" + mSubId);
update();
}
}
};
}
说到 SIM 卡,就不得不提及一个核心概念:订阅ID(subId):
定义:subId(订阅ID)是 Android Telephony 框架中,用于唯一标识一个移动网络订阅(即一张SIM卡或一个eSIM配置文件)的整数。
作用:它代表一个逻辑订阅,而不是物理卡槽。在多SIM卡设备中,每个 SIM 卡都有自己独立的 subId,系统通过它来区分和管理不同运营商提供的通话、短信和数据服务。
无效值:系统定义了一个常量 \(SubscriptionManager.INVALID_SUBSCRIPTION_ID\),其值通常为 \(-1\),代表这是一个无效的订阅ID。
点击“SIM 卡”功能的开关按钮,需关注 MobileNetworkSwitchController.java 的核心代码 displayPreference(PreferenceScreen screen),于是可以定位到 packages/apps/Settings/src/com/android/settings/network/SubscriptionUtil.java
// 省略其他源代码
/**
* Starts a dialog activity to handle SIM enabling/disabling.
* @param context {@code Context}
* @param subId The id of subscription need to be enabled or disabled.
* @param enable Whether the subscription with {@code subId} should be enabled or disabled.
*/
public static void startToggleSubscriptionDialogActivity(
Context context, int subId, boolean enable) {
if (!SubscriptionManager.isUsableSubscriptionId(subId)) {// 检查给定的订阅ID(subId)是否是一个“可用”或“有效”的ID
Log.i(TAG, "Unable to toggle subscription due to invalid subscription ID.");
return;// 无效订阅,即 SIM 卡不可用,直接返回
}
context.startActivity(ToggleSubscriptionDialogActivity.getIntent(context, subId, enable));// 订阅ID 为 subId 是可用的,跳转界面
}
根据界面跳转的代码定位到
public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogActivity
implements SidecarFragment.Listener, ConfirmDialogFragment.OnConfirmListener,
SubscriptionsChangeListener.SubscriptionsChangeListenerClient {
// 省略部分源代码
/**
* Returns an intent of ToggleSubscriptionDialogActivity.
*
* @param context The context used to start the ToggleSubscriptionDialogActivity.
* @param subId The subscription ID of the subscription needs to be toggled.
* @param enable Whether the activity should enable or disable the subscription.
*/
public static Intent getIntent(Context context, int subId, boolean enable) {
Intent intent = new Intent(context, ToggleSubscriptionDialogActivity.class);
intent.putExtra(ARG_SUB_ID, subId);
intent.putExtra(ARG_enable, enable);
return intent;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
int subId = intent.getIntExtra(ARG_SUB_ID, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
mTelMgr = getSystemService(TelephonyManager.class);
UserManager userManager = getSystemService(UserManager.class);
if (!userManager.isAdminUser()) {
Log.e(TAG, "It is not the admin user. Unable to toggle subscription.");
finish();
return;
}
if (!SubscriptionManager.isUsableSubscriptionId(subId)) {
Log.e(TAG, "The subscription id is not usable.");
finish();
return;
}
mActiveSubInfos = SubscriptionUtil.getActiveSubscriptions(mSubscriptionManager);
mSubInfo = SubscriptionUtil.getSubById(mSubscriptionManager, subId);
mIsEsimOperation = mSubInfo != null && mSubInfo.isEmbedded();
mSwitchToEuiccSubscriptionSidecar =
SwitchToEuiccSubscriptionSidecar.get(getFragmentManager());
mSwitchToRemovableSlotSidecar = SwitchToRemovableSlotSidecar.get(getFragmentManager());
mEnableMultiSimSidecar = EnableMultiSimSidecar.get(getFragmentManager());
mEnable = intent.getBooleanExtra(ARG_enable, true);
isRtlMode = getResources().getConfiguration().getLayoutDirection()
== View.LAYOUT_DIRECTION_RTL;
Log.i(TAG, "isMultipleEnabledProfilesSupported():" + isMultipleEnabledProfilesSupported());
if (savedInstanceState == null) {
if (mEnable) {
showEnableSubDialog();
} else {
showDisableSimConfirmDialog();
}
}
}
private void showEnableSubDialog() {
Log.d(TAG, "Handle subscription enabling.");
if (isDsdsConditionSatisfied()) {
showEnableDsdsConfirmDialog();
return;
}
if (!mIsEsimOperation && isRemovableSimEnabled()) {
// This case is for switching on psim when device is not multiple enable profile
// supported.
Log.i(TAG, "Toggle on pSIM, no dialog displayed.");
handleTogglePsimAction();
finish();
return;
}
showEnableSimConfirmDialog();
}
/* Displays the SIM toggling confirmation dialog. */
private void showDisableSimConfirmDialog() {
final CharSequence displayName = SubscriptionUtil.getUniqueSubscriptionDisplayName(
mSubInfo, this);
String title =
mSubInfo == null || TextUtils.isEmpty(displayName)
? getString(
R.string.privileged_action_disable_sub_dialog_title_without_carrier)
: getString(
R.string.privileged_action_disable_sub_dialog_title, displayName);
ConfirmDialogFragment.show(
this,
ConfirmDialogFragment.OnConfirmListener.class,
DIALOG_TAG_DISABLE_SIM_CONFIRMATION,
title,
null,
getString(R.string.yes),
getString(R.string.sim_action_cancel));
}
// 核心代码:实现 ConfirmDialogFragment.OnConfirmListener 接口的方法,实现 Fragment 对 Activity 的回调
@Override
public void onConfirm(int tag, boolean confirmed, int itemPosition) {
if (!confirmed
&& tag != DIALOG_TAG_ENABLE_DSDS_CONFIRMATION
&& tag != DIALOG_TAG_ENABLE_DSDS_REBOOT_CONFIRMATION) {
finish();
return;
}
SubscriptionInfo removedSubInfo = null;
switch (tag) {
case DIALOG_TAG_DISABLE_SIM_CONFIRMATION:
if (mIsEsimOperation) {
Log.i(TAG, "Disabling the eSIM profile.");
showProgressDialog(
getString(R.string.privileged_action_disable_sub_dialog_progress));
int port = mSubInfo != null ? mSubInfo.getPortIndex() : 0;
mSwitchToEuiccSubscriptionSidecar.run(
SubscriptionManager.INVALID_SUBSCRIPTION_ID, port, null);
return;
}
Log.i(TAG, "Disabling the pSIM profile.");
handleTogglePsimAction();
break;
case DIALOG_TAG_ENABLE_DSDS_CONFIRMATION:
if (!confirmed) {
Log.i(TAG, "User cancel the dialog to enable DSDS.");
showEnableSimConfirmDialog();
return;
}
if (mTelMgr.doesSwitchMultiSimConfigTriggerReboot()) {
Log.i(TAG, "Device does not support reboot free DSDS.");
showRebootConfirmDialog();
return;
}
Log.i(TAG, "Enabling DSDS without rebooting.");
showProgressDialog(
getString(R.string.sim_action_enabling_sim_without_carrier_name));
mEnableMultiSimSidecar.run(NUM_OF_SIMS_FOR_DSDS);
break;
case DIALOG_TAG_ENABLE_DSDS_REBOOT_CONFIRMATION:
if (!confirmed) {
Log.i(TAG, "User cancel the dialog to reboot to enable DSDS.");
showEnableSimConfirmDialog();
return;
}
Log.i(TAG, "User confirmed reboot to enable DSDS.");
SimActivationNotifier.setShowSimSettingsNotification(this, true);
mTelMgr.switchMultiSimConfig(NUM_OF_SIMS_FOR_DSDS);
break;
case DIALOG_TAG_ENABLE_SIM_CONFIRMATION_MEP:
if (itemPosition != -1) {
removedSubInfo = (mActiveSubInfos != null) ? mActiveSubInfos.get(itemPosition)
: null;
}
case DIALOG_TAG_ENABLE_SIM_CONFIRMATION:
Log.i(TAG, "User confirmed to enable the subscription.");
showProgressDialog(
getString(
R.string.sim_action_switch_sub_dialog_progress,
SubscriptionUtil.getUniqueSubscriptionDisplayName(mSubInfo, this)),
removedSubInfo != null ? true : false);
if (mIsEsimOperation) {
mSwitchToEuiccSubscriptionSidecar.run(mSubInfo.getSubscriptionId(),
UiccSlotUtil.INVALID_PORT_ID,
removedSubInfo);
return;
}
mSwitchToRemovableSlotSidecar.run(UiccSlotUtil.INVALID_PHYSICAL_SLOT_ID,
removedSubInfo);
break;
default:
Log.e(TAG, "Unrecognized confirmation dialog tag: " + tag);
break;
}
}
}
根据上述源码,可知在点击开关按钮后,会弹出对话框,让用户确认是否开启/关闭。相关代码定位到 packages/apps/Settings/src/com/android/settings/network/telephony/ConfirmDialogFragment.java
/** Fragment to show a confirm dialog. The caller should implement onConfirmListener. */
public class ConfirmDialogFragment extends BaseDialogFragment
implements DialogInterface.OnClickListener {
private static final String TAG = "ConfirmDialogFragment";
private static final String ARG_TITLE = "title";
private static final String ARG_MSG = "msg";
private static final String ARG_POS_BUTTON_STRING = "pos_button_string";
private static final String ARG_NEG_BUTTON_STRING = "neg_button_string";
private static final String ARG_LIST = "list";
/**
* Interface defining the method that will be invoked when the user has done with the dialog.
*/
public interface OnConfirmListener {
/**
* @param tag The tag in the caller.
* @param confirmed True if the user has clicked the positive button. False if the
* user has
* clicked the negative button or cancel the dialog.
* @param itemPosition It is the position of item, if user selects one of the list item.
* If the user select "cancel" or the dialog does not have list, then
* the value is -1.
*/
void onConfirm(int tag, boolean confirmed, int itemPosition);
}
/** Displays a confirmation dialog which has confirm and cancel buttons. */
public static <T> void show(
FragmentActivity activity,
Class<T> callbackInterfaceClass,
int tagInCaller,
String title,
String msg,
String posButtonString,
String negButtonString) {
ConfirmDialogFragment fragment = new ConfirmDialogFragment();
Bundle arguments = new Bundle();
arguments.putString(ARG_TITLE, title);
arguments.putCharSequence(ARG_MSG, msg);
arguments.putString(ARG_POS_BUTTON_STRING, posButtonString);
arguments.putString(ARG_NEG_BUTTON_STRING, negButtonString);
setListener(activity, null, callbackInterfaceClass, tagInCaller, arguments);
fragment.setArguments(arguments);
fragment.show(activity.getSupportFragmentManager(), TAG);
}
/** Displays a confirmation dialog which has confirm and cancel buttons and carrier list.*/
public static <T> void show(
FragmentActivity activity,
Class<T> callbackInterfaceClass,
int tagInCaller,
String title,
String msg,
String posButtonString,
String negButtonString,
ArrayList<String> list) {
ConfirmDialogFragment fragment = new ConfirmDialogFragment();
Bundle arguments = new Bundle();
arguments.putString(ARG_TITLE, title);
arguments.putCharSequence(ARG_MSG, msg);
arguments.putString(ARG_POS_BUTTON_STRING, posButtonString);
arguments.putString(ARG_NEG_BUTTON_STRING, negButtonString);
arguments.putStringArrayList(ARG_LIST, list);
setListener(activity, null, callbackInterfaceClass, tagInCaller, arguments);
fragment.setArguments(arguments);
fragment.show(activity.getSupportFragmentManager(), TAG);
}
@Override
public final Dialog onCreateDialog(Bundle savedInstanceState) {
String title = getArguments().getString(ARG_TITLE);
String message = getArguments().getString(ARG_MSG);
String posBtnString = getArguments().getString(ARG_POS_BUTTON_STRING);
String negBtnString = getArguments().getString(ARG_NEG_BUTTON_STRING);
ArrayList<String> list = getArguments().getStringArrayList(ARG_LIST);
Log.i(TAG, "Showing dialog with title =" + title);
AlertDialog.Builder builder = new AlertDialog.Builder(getContext())
.setPositiveButton(posBtnString, this)
.setNegativeButton(negBtnString, this);
View content = LayoutInflater.from(getContext()).inflate(
R.layout.sim_confirm_dialog_multiple_enabled_profiles_supported, null);
if (list != null && !list.isEmpty() && content != null) {
Log.i(TAG, "list =" + list.toString());
if (!TextUtils.isEmpty(title)) {
View titleView = LayoutInflater.from(getContext()).inflate(
R.layout.sim_confirm_dialog_title_multiple_enabled_profiles_supported,
null);
TextView titleTextView = titleView.findViewById(R.id.title);
titleTextView.setText(title);
builder.setCustomTitle(titleTextView);
}
TextView dialogMessage = content.findViewById(R.id.msg);
if (!TextUtils.isEmpty(message) && dialogMessage != null) {
dialogMessage.setText(message);
dialogMessage.setVisibility(View.VISIBLE);
}
final ArrayAdapter<String> arrayAdapterItems = new ArrayAdapter<String>(
getContext(),
R.layout.sim_confirm_dialog_item_multiple_enabled_profiles_supported, list);
final ListView lvItems = content.findViewById(R.id.carrier_list);
if (lvItems != null) {
lvItems.setVisibility(View.VISIBLE);
lvItems.setAdapter(arrayAdapterItems);
lvItems.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
Log.i(TAG, "list onClick =" + position);
Log.i(TAG, "list item =" + list.get(position));
if (position == list.size() - 1) {
// user select the "cancel" item;
informCaller(false, -1);
} else {
informCaller(true, position);
}
}
});
}
final LinearLayout infoOutline = content.findViewById(R.id.info_outline_layout);
if (infoOutline != null) {
infoOutline.setVisibility(View.VISIBLE);
}
builder.setView(content);
} else {
if (!TextUtils.isEmpty(title)) {
builder.setTitle(title);
}
if (!TextUtils.isEmpty(message)) {
builder.setMessage(message);
}
}
AlertDialog dialog = builder.create();
dialog.setCanceledOnTouchOutside(false);
return dialog;
}
@Override
public void onClick(DialogInterface dialog, int which) {
Log.i(TAG, "dialog onClick =" + which);
informCaller(which == DialogInterface.BUTTON_POSITIVE, -1);
}
@Override
public void onCancel(DialogInterface dialog) {
informCaller(false, -1);
}
private void informCaller(boolean confirmed, int itemPosition) {
OnConfirmListener listener = getListener(OnConfirmListener.class);
if (listener == null) {
return;
}
listener.onConfirm(getTagInCaller(), confirmed, itemPosition);
}
}
最终定位到路径 frameworks/base/telephony/java/android/telephony/SubscriptionManager.java
/**
* Set uicc applications being enabled or disabled.
* The value will be remembered on the subscription and will be applied whenever it's present.
* If the subscription in currently present, it will also apply the setting to modem
* immediately (the setting in the modem will not change until the modem receives and responds
* to the request, but typically this should only take a few seconds. The user visible setting
* available from SubscriptionInfo.areUiccApplicationsEnabled() will be updated
* immediately.)
*
* Permissions android.Manifest.permission.MODIFY_PHONE_STATE is required
*
* @param subscriptionId which subscription to operate on.
* @param enabled whether uicc applications are enabled or disabled.
* @hide
*/
@SystemApi
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
public void setUiccApplicationsEnabled(int subscriptionId, boolean enabled) {
if (VDBG) {
logd("setUiccApplicationsEnabled subId= " + subscriptionId + " enable " + enabled);
}
try {
ISub iSub = ISub.Stub.asInterface(
TelephonyFrameworkInitializer
.getTelephonyServiceManager()
.getSubscriptionServiceRegisterer()
.get());
if (iSub != null) {
iSub.setUiccApplicationsEnabled(enabled, subscriptionId);
}
} catch (RemoteException ex) {
// ignore it
}
}
浙公网安备 33010602011771号