副卡收发彩信流程

2 阅读6分钟

一、系统架构概览

Android 彩信处理涉及多个层级的组件:

应用层 (MmsManager/SmsManager)
    ↓
系统服务层 (MmsServiceBroker)
    ↓
运营商消息服务 (CarrierMessagingService)
    ↓
数据网络层 (DataNetwork/PhoneSwitcher)
    ↓
运营商 MMSC 服务器

二、彩信发送流程

2.1 应用端发起发送请求

应用通过 MmsManagerSmsManager 调用发送接口:

MmsManager.sendMultimediaMessage(subId, contentUri, locationUrl, configOverrides, sentIntent, messageId)

关键参数说明:

  • subId: 副卡的 Subscription ID (例如:副卡可能是 subId=2)
  • contentUri: MMS PDU (Protocol Data Unit) 的内容 URI,从 ContentProvider 读取
  • locationUrl: 可选,指定 MMSC (Multimedia Messaging Service Center) 服务器地址
  • sentIntent: 发送结果回调 PendingIntent
  • messageId: 用于日志和诊断的唯一消息ID

2.2 MmsServiceBroker 权限检查与转发

MmsServiceBroker.BinderService.sendMessage()
    ├─ 权限检查 (SEND_SMS)
    ├─ 订阅关联性检查 (checkSubscriptionAssociatedWithUser)
    ├─ AppOps 权限检查 (OP_SEND_SMS)
    ├─ URI 权限调整和赋予 (adjustUriForUserAndGrantPermission)
    └─ 转发到真实的 MmsService

代码路径:

public void sendMessage(int subId, String callingPkg, Uri contentUri,
        String locationUrl, Bundle configOverrides, PendingIntent sentIntent,
        long messageId, String attributionTag)
        throws RemoteException {
    // 权限检查
    mContext.enforceCallingPermission(Manifest.permission.SEND_SMS, "Send MMS message");
    
    // 订阅检查
    if (!TelephonyPermissions.checkSubscriptionAssociatedWithUser(mContext, subId,
            Binder.getCallingUserHandle())
            && isActiveSubId(subId)) {
        return;
    }
    
    // AppOps 检查
    if (getAppOpsManager().noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(),
            callingPkg, attributionTag, null) != AppOpsManager.MODE_ALLOWED) {
        return;
    }
    
    // 转发到真实服务
    getServiceGuarded().sendMessage(subId, callingPkg, contentUri, locationUrl,
            configOverrides, sentIntent, messageId, attributionTag);
}

2.3 MmsService 处理发送

MmsService (在 com.android.mms.service 进程中) 接收请求:

  1. 读取 MMS PDU 数据:从 ContentProvider 读取彩信内容
  2. 查询副卡配置:获取副卡对应的 MMSC URL、代理等配置
  3. 请求 MMS 网络:向 ConnectivityService 请求 MMS 数据连接

2.4 MMS 数据网络建立(多卡设备关键步骤)

在多卡设备上,系统需要处理数据连接的切换:

PhoneSwitcher.onRequestNetwork(NetworkRequest with NET_CAPABILITY_MMS)
    ├─ 检查多卡状态 (mActiveModemCount > 1)
    ├─ 将 MMS NetworkRequest 添加到列表
    ├─ 触发网络评估 (onEvaluate)
    └─ 记录指标 (collectRequestNetworkMetrics)

关键代码:

private void onRequestNetwork(NetworkRequest networkRequest) {
    TelephonyNetworkRequest telephonyNetworkRequest = new TelephonyNetworkRequest(
            networkRequest, PhoneFactory.getDefaultPhone(), mFlags);
    mNetworkRequestList.add(telephonyNetworkRequest);
    onEvaluate(REQUESTS_CHANGED, "netRequest");
}

private void collectRequestNetworkMetrics(NetworkRequest networkRequest) {
    // 在多卡设备上,MMS 请求会临时禁用默认数据SIM卡上的网络
    if (mActiveModemCount > 1 && networkRequest.hasCapability(
            NetworkCapabilities.NET_CAPABILITY_MMS)) {
        OnDemandDataSwitch onDemandDataSwitch = new OnDemandDataSwitch();
        onDemandDataSwitch.apn = TelephonyEvent.ApnType.APN_TYPE_MMS;
        onDemandDataSwitch.state = TelephonyEvent.EventState.EVENT_STATE_START;
        TelephonyMetrics.getInstance().writeOnDemandDataSwitch(onDemandDataSwitch);
    }
}

2.5 DataNetwork 建立连接

DataNetworkController 根据 NetworkRequest 寻找合适的 DataProfile (APN配置):

DataNetworkController.createDataNetwork()
    ├─ 查询副卡的 DataProfile 列表
    ├─ 选择支持 MMS 的 APN (TYPE_MMS)
    ├─ 确定传输类型 (WWAN 或 IWLAN)
    └─ 建立 DataNetwork

MMS 网络能力配置:

// 如果设备启用了 "IWLAN 上强制 MMS" 功能
if (mFlags.forceIwlanMms() && builder.build()
        .hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)
        && mDataConfigManager.isForceIwlanMmsFeatureEnabled()) {
    
    // 获取 IWLAN 上的 MMS 数据配置
    DataProfile dataProfile = mDataNetworkController.getDataProfileManager()
            .getDataProfileForNetworkRequest(
                new TelephonyNetworkRequest(
                    new NetworkRequest.Builder()
                        .addCapability(NetworkCapabilities.NET_CAPABILITY_MMS)
                        .build(), 
                    mPhone, mFlags),
                TelephonyManager.NETWORK_TYPE_IWLAN, false, false, false);
    
    if (dataProfile != null) {
        log("Found MMS on IWLAN with APN: " + dataProfile.getApn());
    }
}

2.6 通过运营商消息服务发送

CarrierMessagingService 拦截发送请求(如果运营商实现了该服务):

CarrierMessagingService.onSendMms(pduUri, subId, location, callback)
    ├─ 可选:修改 MMS 内容或配置
    ├─ 调用运营商特定的发送逻辑
    └─ 返回发送结果给 MmsService

2.7 MMS PDU 通过 HTTP 发送到 MMSC

MmsService 通过建立的数据连接:

  1. 将 MMS PDU 编码为二进制格式
  2. 通过 HTTP POST 发送到 MMSC 服务器
  3. 接收 MMSC 的发送确认 (SendConf PDU)

2.8 发送结果回调

发送成功/失败 → PendingIntent.send() → 应用 Intent 接收者

三、彩信接收流程

3.1 WAP Push 通知接收

当副卡接收到新彩信时,运营商网络通过 WAP Push 通知设备:

RIL 层 → RadioIndication.onNewWapPdu()
    ├─ 解析 WAP Push PDU
    ├─ 提取 MMS 下载 Location URL
    └─ 发送广播 Intent.DATA_SMSRECEIVED_ACTION

3.2 系统确定接收卡槽

系统识别哪个副卡收到通知
    ├─ 从 Intent 提取 slotIndex
    ├─ 映射到对应的 subId
    └─ 后续操作基于此 subId

3.3 MMS 下载请求

应用或系统服务调用下载接口:

MmsManager.downloadMultimediaMessage(subId, locationUrl, contentUri, ...)

代码路径:

public void downloadMultimediaMessage(int subId, @NonNull String locationUrl,
        @NonNull Uri contentUri, @Nullable Bundle configOverrides,
        @Nullable PendingIntent downloadedIntent, long messageId) {
    IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
    iMms.downloadMessage(subId, ActivityThread.currentPackageName(),
            locationUrl, contentUri, configOverrides, downloadedIntent,
            messageId, mContext.getAttributionTag());
}

3.4 MmsServiceBroker 处理下载

MmsServiceBroker.downloadMessage()
    ├─ 权限检查 (RECEIVE_MMS)
    ├─ AppOps 检查 (OP_RECEIVE_MMS)
    ├─ URI 权限赋予 (读写权限)
    └─ 转发到 MmsService

代码路径:

public void downloadMessage(int subId, String callingPkg, String locationUrl,
        Uri contentUri, Bundle configOverrides, PendingIntent downloadedIntent,
        long messageId, String attributionTag) throws RemoteException {
    
    mContext.enforceCallingPermission(Manifest.permission.RECEIVE_MMS,
            "Download MMS message");
    
    if (getAppOpsManager().noteOp(AppOpsManager.OP_RECEIVE_MMS, 
            Binder.getCallingUid(), callingPkg, attributionTag, null) 
            != AppOpsManager.MODE_ALLOWED) {
        return;
    }
    
    contentUri = adjustUriForUserAndGrantPermission(contentUri,
            CarrierMessagingService.SERVICE_INTERFACE,
            Intent.FLAG_GRANT_READ_URI_PERMISSION | 
            Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
            subId);
    
    getServiceGuarded().downloadMessage(subId, callingPkg, locationUrl, 
            contentUri, configOverrides, downloadedIntent, messageId, attributionTag);
}

3.5 MmsService 建立 MMS 数据连接

同发送过程,建立副卡的 MMS 数据连接。

3.6 运营商消息服务拦截(可选)

CarrierMessagingService.onDownloadMms(contentUri, subId, location, callback)
    ├─ 可选:修改下载配置
    ├─ 调用运营商特定逻辑
    └─ 返回下载结果

3.7 通过 HTTP 从 MMSC 下载

MmsService
    ├─ 通过建立的数据连接
    ├─ 向 Location URL 发送 HTTP GET 请求
    ├─ 接收 MMS PDU 数据
    └─ 写入 ContentProvider (TelephonyProvider)

3.8 存储彩信并回调

MmsProvider 存储 MMS PDU
    ├─ 检查 subId 是否在请求体中
    ├─ 若无则使用默认彩信 subId
    ├─ 存储到数据库 (TABLE_PDU)
    └─ 返回 URI

代码路径:

if (values.containsKey(Telephony.Sms.SUBSCRIPTION_ID)) {
    subId = values.getAsInteger(Telephony.Sms.SUBSCRIPTION_ID);
} else {
    // 未指定则用默认彩信 subId
    subId = SmsManager.getDefaultSmsSubscriptionId();
    if (SubscriptionManager.isValidSubscriptionId(subId)) {
        values.put(Telephony.Sms.SUBSCRIPTION_ID, subId);
    }
}

3.9 下载完成回调

下载成功/失败 → PendingIntent.send() → 应用下载结果接收者

四、多卡设备特殊处理

4.1 MMS 网络请求与数据切换

在多卡设备上,当发送/接收 MMS 时:

多卡设备 (DSDS)
    ├─ 副卡发送 MMS
    ├─ PhoneSwitcher 检测到 MMS NetworkRequest
    ├─ 如果当前是默认数据卡在使用,临时禁用其连接
    ├─ 为副卡建立 MMS 数据连接
    ├─ MMS 操作完成后释放 MMS 连接
    └─ 恢复默认数据卡的连接

4.2 运营商配置的 MMS 政策

系统支持多种 MMS 相关的运营商政策:

KEY_MMS_CLOSE_CONNECTION_BOOL
    └─ 在 MMS HTTP 请求中添加 "Connection: close" 头

KEY_MMS_NETWORK_RELEASE_TIMEOUT_MILLIS_INT
    └─ MMS 数据连接释放前的等待时间(便于连续发送)

KEY_MOBILEDATA_POLICY_MMS_ALWAYS_ALLOWED (MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED)
    └─ 即使用户关闭移动数据,也允许 MMS 建立连接

4.3 用户默认 MMS 卡选择

系统存储用户的默认 MMS 发送卡:

Settings.Global.MULTI_SIM_SMS_SUBSCRIPTION
    └─ 存储默认彩信发送卡的 subId

五、关键数据结构

5.1 NetworkRequest 能力标记

NetworkCapabilities.NET_CAPABILITY_MMS
    ├─ 标识该网络支持彩信服务
    ├─ 在 DataNetwork 建立时添加
    └─ PhoneSwitcher 和 ConnectivityService 据此判断

5.2 APN 类型映射

ApnSetting.TYPE_MMS
    ↔ NetworkCapabilities.NET_CAPABILITY_MMS
    ↔ DataUtils.networkCapabilityToApnType()

// 双向转换
networkCapabilityToApnType(NET_CAPABILITY_MMS) → TYPE_MMS
apnTypeToNetworkCapability(TYPE_MMS) → NET_CAPABILITY_MMS

5.3 DataProfile 和 MMS 配置

DataProfile (包含 ApnSetting)
    ├─ apnName: "mms" 或运营商特定名称
    ├─ mmscUrl: MMSC 服务器地址
    ├─ mmsProxyAddress: MMS 代理 (可选)
    ├─ mmsProxyPort: MMS 代理端口 (可选)
    └─ apnTypes: 包含 TYPE_MMS

六、流程时序图

【发送流程】
应用 ──sendMms──> MmsManager
                    ↓
              MmsServiceBroker (权限检查)
                    ↓
              MmsService (com.android.mms.service)
                    ↓
         CarrierMessagingService (可选拦截)
                    ↓
            请求 MMS 数据连接
                    ↓ (多卡设备上)
            PhoneSwitcher (评估网络)
                    ↓
            DataNetworkController
                    ↓
            DataNetwork (建立连接)
                    ↓
            RIL.setupDataCall()
                    ↓
            Modem 建立 PDP Context
                    ↓
            HTTP POST to MMSC
                    ↓
            MMSC 返回 SendConf PDU
                    ↓
            释放 MMS 数据连接
                    ↓
         PendingIntent 回调应用
【接收流程】
MMSC WAP Push ──> RIL (RadioIndication)
                    ↓
            提取 Location URL & slotIndex
                    ↓
            Intent 通知系统
                    ↓
          MmsManager.downloadMultimediaMessage()
                    ↓
              MmsServiceBroker (权限检查)
                    ↓
              MmsService (com.android.mms.service)
                    ↓
         CarrierMessagingService (可选拦截)
                    ↓
            请求 MMS 数据连接
                    ↓
            HTTP GET from Location URL
                    ↓
            接收 MMS PDU 数据
                    ↓
            TelephonyProvider 存储
                    ↓
         PendingIntent 回调应用

七、关键要点总结

  1. SubId 追踪:整个流程中 subId 是关键,标识请求来自哪张卡

  2. 权限多重检查:SEND_SMS/RECEIVE_MMS 权限 + AppOps + 订阅关联性检查

  3. 网络资源管理:多卡设备上 MMS 网络请求可能导致数据连接切换

  4. 运营商定制:CarrierMessagingService 提供钩点进行运营商特定处理

  5. URI 权限:ContentUri 需要显式赋予权限给 CarrierMessagingService

  6. 异步处理:MMS 发送/接收是异步的,通过 PendingIntent 回调结果

  7. 多层代理:MmsServiceBroker 是中间代理,增加稳定性和权限隔离

这就是 Android 副卡彩信收发的完整流程!