• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 众包
  • 赞助商
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录

RomanLin

  • 博客园
  • 联系
  • 订阅
  • 管理

公告

View Post

【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
    }
}

posted on 2026-03-15 21:04  RomanLin  阅读(1)  评论(0)    收藏  举报

刷新页面返回顶部
 
博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3