整体流程架构
SIM卡插入 → 识别激活 → 订阅注册 → 选择默认数据卡
↓
UICC应用激活 → 设置数据允许 → 建立数据连接 → 获取数据配置
第一阶段:SIM卡识别和激活
1.1 SIM卡插入后的初始化
当SIM卡插入时:
SubscriptionManagerService.onSimStateChanged()
→ updateSubscription()
→ 创建订阅信息(SubscriptionInfoInternal)
1.2 订阅信息创建
// SubscriptionManagerService.java: 订阅初始化时的关键信息
SubscriptionInfoInternal.Builder builder = new SubscriptionInfoInternal.Builder()
.setId(subId) // 订阅ID
.setSimSlotIndex(phoneId) // SIM卡槽位
.setIccId(iccId) // ICC ID
.setImsi(imsi) // IMSI
.setCarrierId(carrierId) // 运营商ID
.setMcc(mcc) // 国家代码
.setMnc(mnc) // 网络代码
.setDisplayName(displayName) // 显示名称
.setCountryIso(countryIso) // 国家ISO代码
.setMccMnc(mccMnc) // MCC+MNC
.setPhoneId(phoneId) // Phone ID
.setPortIndex(portIndex) // 端口索引
.setCardId(cardId) // 卡ID (ICC ID或EID)
第二阶段:UICC应用激活
2.1 启用UICC应用
通过调用HAL的setUiccSubscription命令激活卡上的应用:
// RIL.java: 4144-4163 行
@Override
public void setUiccSubscription(int slotId, int appIndex, int subId, int subStatus,
Message result) {
RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class);
if (!canMakeRequest("setUiccSubscription", simProxy, result, RADIO_HAL_VERSION_1_4)) {
return;
}
RILRequest rr = obtainRequest(RIL_REQUEST_SET_UICC_SUBSCRIPTION, result,
mRILDefaultWorkSource);
if (RILJ_LOGD) {
riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+ " slot = " + slotId + " appIndex = " + appIndex
+ " subId = " + subId + " subStatus = " + subStatus);
}
radioServiceInvokeHelper(HAL_SERVICE_SIM, rr, "setUiccSubscription", () -> {
simProxy.setUiccSubscription(rr.mSerial, slotId, appIndex, subId, subStatus);
});
}
参数说明:
| 参数 | 含义 | 值 |
|---|---|---|
| slotId | SIM卡槽位索引 | 0, 1, 2等 |
| appIndex | UICC应用索引 | 0 (GSM), 1 (CDMA), 2 (IMS) 等 |
| subId | 订阅ID | 正整数 |
| subStatus | 激活状态 | 1 (激活), 0 (停用) |
2.2 启用UICC应用(HAL 1.5+)
// RadioSimProxy.java: 139-147 行
public void enableUiccApplications(int serial, boolean enable) throws RemoteException {
if (isEmpty() || mHalVersion.less(RIL.RADIO_HAL_VERSION_1_5)) return;
if (isAidl()) {
mSimProxy.enableUiccApplications(serial, enable);
} else {
((android.hardware.radio.V1_5.IRadio) mRadioProxy).enableUiccApplications(
serial, enable);
}
}
第三阶段:设置SIM卡电源状态
3.1 SIM卡电源管理
// RadioSimProxy.java: 651-660 行
public void setSimCardPower(int serial, int state) throws RemoteException {
if (isEmpty()) return;
if (isAidl()) {
mSimProxy.setSimCardPower(serial, state);
} else if (mHalVersion.greaterOrEqual(RIL.RADIO_HAL_VERSION_1_6)) {
((android.hardware.radio.V1_6.IRadio) mRadioProxy).setSimCardPower_1_6(serial, state);
} else {
mRadioProxy.setSimCardPower_1_1(serial, state);
}
}
SIM卡电源状态:
// TelephonyManager.java
CARD_POWER_DOWN = 0 // 关闭SIM卡电源
CARD_POWER_UP = 1 // 打开SIM卡电源 (正常模式)
POWER_UP_PASS_THROUGH = 2 // 打开SIM卡电源 (直通模式)
// 在此模式下,Modem不控制SIM卡
// 由系统直接发送APDU指令
第四阶段:设置默认数据卡
4.1 用户选择数据卡
当用户在设置中选择数据卡时,触发:
// MultiSimSettingController.java: 数据卡选择流程
private void onUserSelectSim(int subId, int simSelectDialogType) {
if (simSelectDialogType == EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_DATA) {
// 用户选择了数据卡
mSubscriptionManagerService.setDefaultDataSubId(subId);
}
}
4.2 设置默认数据订阅ID
// SubscriptionManagerService.java
public void setDefaultDataSubId(int subId) {
setDefaultSubId(subId, SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
SettingsProvider.SETTING_USER_PREF_DATA_SUB);
}
private void setDefaultSubId(int subId, int subId Constant, String setting) {
// 1. 更新数据库
mSubscriptionDatabaseManager.setDefaultSubId(subId);
// 2. 更新全局设置
android.provider.Settings.Global.putInt(mContext.getContentResolver(),
setting, subId);
// 3. 触发监听器
notifyDefaultSubscriptionChanged(subId);
}
存储位置:
| 设置项 | 值 | 说明 |
|---|---|---|
user_pref_data_sub | SubId | 用户选择的数据卡 |
user_pref_voice_sub | SubId | 用户选择的通话卡 |
user_pref_sms_sub | SubId | 用户选择的短信卡 |
第五阶段:设置数据使能状态
5.1 用户启用/禁用数据
// DataSettingsManager.java: 用户启用数据
public void setDataEnabledForReason(@DataEnabledReason int reason, boolean enabled,
@NonNull String callingPackage) {
Message msg = Message.obtain(
this,
EVENT_SET_DATA_ENABLED_FOR_REASON,
new AsyncResult(callingPackage, enabled, null));
obtainMessage(msg).sendToTarget();
}
数据使能原因类型:
@DataEnabledReason
// 用户原因
REASON_USER_DATA_ENABLED = 1 << 0 // 用户启用数据
// 策略原因
REASON_POLICY_DATA_ENABLED = 1 << 1 // 政策启用数据
REASON_THERMAL = 1 << 2 // 热管制禁用
REASON_CARRIER = 1 << 3 // 运营商禁用
REASON_PROVISIONING_DATA_ENABLED = 1<<4 // 配置数据启用
5.2 通知Modem数据允许状态
// CommandsInterface.java: 2185 行
public void setDataAllowed(boolean allowed, Message result);
实现流程:
// RIL.java
mRil.setDataAllowed(isDataEnabled, message);
→ RadioDataProxy.setDataAllowed(serial, allowed)
→ HAL接口调用
第六阶段:多SIM卡协调设置
6.1 分组订阅管理
某些运营商支持分组SIM卡(Grouped Subscriptions),它们必须共享相同的设置:
// MultiSimSettingController.java: 73-81 行
/**
* 多SIM卡协调规则:
*
* 1) 分组订阅必须有相同的MOBILE_DATA和DATA_ROAMING设置
* 2) 默认设置会自动更新和继承
* - 如果默认订阅A切换到同组的订阅B,B会变成新的默认
* 3) 对于主要订阅,只有默认数据订阅会启用MOBILE_DATA
*/
6.2 订阅分组信息
// SubscriptionInfoInternal.java
class SubscriptionInfoInternal {
private String mGroupUuid; // 组UUID
private String mGroupOwner; // 组所有者
private int mIsOpportunistic; // 是否为机会性订阅
}
6.3 自动数据卡选择
系统会自动选择默认数据卡:
// MultiSimSettingController.java: 624-677 行
private void updateDefaultValues() {
// 场景1: 只有一个主订阅,自动设为默认数据卡
if (conditionForOnePrimarySim) {
int subId = mPrimarySubList.get(0);
if (hasData()) {
mSubscriptionManagerService.setDefaultDataSubId(subId);
}
}
// 场景2: 多个主订阅,如果没有选择默认数据卡,提示用户选择
if (hasDualActiveData()) {
// 弹出用户选择对话框
}
}
第七阶段:建立数据连接
7.1 数据连接请求
一旦设置数据卡并启用数据,系统会建立数据连接:
// DataNetworkController.java
setupDataCall(int accessNetworkType, DataProfile dataProfile,
boolean allowRoaming, int reason, ...)
7.2 设置数据配置文件
// RIL.java
public void setupDataCall(int accessNetworkType, DataProfile dataProfile,
boolean allowRoaming, int reason, LinkProperties linkProperties,
int pduSessionId, NetworkSliceInfo sliceInfo,
TrafficDescriptor trafficDescriptor, boolean matchAllRuleAllowed,
Message result)
数据配置文件包含:
class DataProfile {
String apn; // APN名称
int protocol; // IP/IPV6/IPV4V6
int roamingProtocol; // 漫游协议
String user; // 用户名
String password; // 密码
int authType; // 认证类型
String operatorNumeric; // 运营商代码(MCC+MNC)
boolean enabled; // 是否启用
int type; // APN类型(default/mms/supl等)
}
第八阶段:订阅管理数据库
8.1 订阅信息存储
所有订阅信息都存储在SimInfo表中:
| 字段 | 说明 |
|---|---|
| _id | 订阅ID |
| icc_id | SIM卡ICC ID |
| sim_slot_index | SIM卡槽位 |
| display_name | 显示名称 |
| carrier_name | 运营商名称 |
| mcc | 国家代码 |
| mnc | 网络代码 |
| sim_serial_number | SIM卡序列号 |
| country_iso | 国家ISO代码 |
| uicc_applications_enabled | UICC应用是否启用 |
| is_embedded | 是否为eSIM |
| carrier_id | 运营商ID |
| group_uuid | 分组UUID |
| is_opportunistic | 是否为机会性订阅 |
| port_index | 端口索引 |
| usage_setting | 使用场景(VOICE/SMS/DATA) |
8.2 查询订阅信息
// 获取特定订阅信息
SubscriptionInfo subInfo = SubscriptionManager.getActiveSubscriptionInfo(subId);
// 获取所有活跃订阅
List<SubscriptionInfo> subInfoList = SubscriptionManager.getActiveSubscriptionInfoList();
// 获取默认数据订阅
int defaultDataSubId = SubscriptionManager.getDefaultDataSubscriptionId();
完整的设置数据卡流程时序
时间轴 ────────────────────────────────────────────────────────
│
├─ [1] 用户插入SIM卡
│ └─→ Modem检测到卡插入
│ └─→ 发送卡状态改变通知
│
├─ [2] RIL处理卡状态
│ └─→ getIccCardStatus()
│ └─→ UiccController.onIccStatusChanged()
│
├─ [3] SubscriptionManager创建订阅
│ └─→ SubscriptionManagerService.updateSubscription()
│ ├─→ 读取SIM卡信息(IMSI, ICCID等)
│ ├─→ 创建SubscriptionInfoInternal
│ └─→ 插入SimInfo数据库
│
├─ [4] 激活UICC应用
│ └─→ setUiccSubscription(slotId, appIndex, subId, 1)
│ └─→ HAL层激活应用
│
├─ [5] 启用UICC应用(可选, HAL 1.5+)
│ └─→ enableUiccApplications(serial, true)
│
├─ [6] 系统自动或用户手动选择数据卡
│ ├─→ MultiSimSettingController分析
│ ├─→ 如果只有一张卡,自动设为默认
│ └─→ 如果多张卡,提示用户选择
│
├─ [7] 设置默认数据订阅ID
│ └─→ SubscriptionManagerService.setDefaultDataSubId(subId)
│ ├─→ 更新数据库
│ ├─→ Settings.Global更新user_pref_data_sub
│ └─→ 通知监听器
│
├─ [8] DataSettingsManager设置数据使能
│ └─→ setDataEnabledForReason(REASON_USER_DATA_ENABLED, true)
│
├─ [9] 通知Modem数据允许状态
│ └─→ setDataAllowed(true, message)
│ └─→ HAL接口setDataAllowed()
│
├─ [10] 加载数据配置和APN列表
│ └─→ DataProfileManager.loadDataProfiles()
│ ├─→ 从TelephonyProvider查询APN
│ └─→ 创建DataProfile对象
│
├─ [11] 建立初始数据连接 (如果需要)
│ └─→ DataNetworkController.setupDataCall()
│ ├─→ RIL.setupDataCall(AccessNetworkType, DataProfile)
│ └─→ 等待Modem建立连接
│
└─ [12] 数据连接就绪,应用可使用数据
配置文件关键参数
配置优先级
运营商可通过CarrierConfig配置数据卡行为:
// CarrierConfigManager.KEY_* 常见配置
KEY_DATA_LIMIT_THRESHOLD_BYTES // 数据限制阈值
KEY_MOBILE_DATA_DOWNLOAD_SPEED_MBPS // 下行速率
KEY_MOBILE_DATA_UPLOAD_SPEED_MBPS // 上行速率
KEY_ENHANCED_4G_LTE_BOOL // 是否支持LTE+
KEY_DEFAULT_DATA_ROAMING_ENABLED_BOOL // 默认漫游数据
KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR // 显示运营商名称
多SIM协调配置
KEY_ALLOW_METERED_NETWORK_TYPES // 计量网络类型
KEY_DATA_ROAMING_ENABLED_BY_DEFAULT // 默认启用数据漫游
KEY_SHOW_DATA_SWITCH_NOTIFICATION_BOOL // 显示数据切换提示
特殊场景处理
1. eSIM (嵌入式SIM)
// eSIM需要下载profile
EuiccManager manager = context.getSystemService(EuiccManager.class);
manager.downloadSubscription(...);
2. 双卡双待(DSDS)
// 两张卡可以同时有网络
// 但通常只有一张是默认数据卡
// 用户可选择使用哪张卡的数据
3. 5G NSA/SA支持
// 新增网络类型支持
NETWORK_TYPE_NR // 5G NR
NETWORK_TYPE_LTE_CA // 5G NSA (LTE+NR)
调试和日志
查看数据卡设置的相关日志:
# 查看订阅信息
adb shell dumpsys telephony.registry | grep -i subscription
# 查看多SIM控制器状态
adb shell dumpsys telephony.tel_registry | grep -i "MultiSimSettingController"
# 查看数据设置
adb shell dumpsys telephony.tel_registry | grep -i "DataSettings"
# 查看默认SIM卡设置
settings get global user_pref_data_sub
settings get global user_pref_voice_sub
settings get global user_pref_sms_sub
这就是Android系统完整的设置数据卡流程!关键要点包括:
- 自动识别 - 插入SIM卡后系统自动识别和创建订阅
- UICC激活 - 通过HAL激活SIM卡上的应用
- 智能选择 - 自动或提示用户选择默认数据卡
- 多SIM协调 - 分组SIM卡共享设置
- 数据配置 - 从数据库加载APN和配置信息
- 连接建立 - 最后建立实际的数据连接