PhoneSwitcher 如何决定哪张 SIM 卡可以使用射频资源

37 阅读6分钟

很好的问题!让我分析 PhoneSwitcher 是如何决定哪张 SIM 卡可以使用射频资源的:

完美!现在我可以给你一个完整的解释了:

PhoneSwitcher 如何决定哪张 SIM 卡可以使用射频资源

核心决策流程

PhoneSwitcher 通过 onEvaluate() 方法来决定射频资源的分配:

protected boolean onEvaluate(boolean requestsChanged, String reason) {
    StringBuilder sb = new StringBuilder(reason);

    // If we use HAL_COMMAND_PREFERRED_DATA,
    boolean diffDetected = mHalCommandToUse != HAL_COMMAND_PREFERRED_DATA && requestsChanged;

    // Check if user setting of default non-opportunistic data sub is changed.
    int primaryDataSubId = mSubscriptionManagerService.getDefaultDataSubId();
    if (primaryDataSubId != mPrimaryDataSubId) {
        sb.append(" mPrimaryDataSubId ").append(mPrimaryDataSubId).append("->")
            .append(primaryDataSubId);
        mPrimaryDataSubId = primaryDataSubId;
        mLastSwitchPreferredDataReason = DataSwitch.Reason.DATA_SWITCH_REASON_MANUAL;
    }

    // Check to see if there is any active subscription on any phone
    boolean hasAnyActiveSubscription = false;

    // Check if phoneId to subId mapping is changed.
    for (int i = 0; i < mActiveModemCount; i++) {
        int sub = SubscriptionManager.getSubscriptionId(i);

        if (SubscriptionManager.isValidSubscriptionId(sub)) hasAnyActiveSubscription = true;

        if (sub != mPhoneSubscriptions[i]) {
            sb.append(" phone[").append(i).append("] ").append(mPhoneSubscriptions[i]);
            sb.append("->").append(sub);
            if (mAutoSelectedDataSubId == mPhoneSubscriptions[i]) {
                mAutoSelectedDataSubId = DEFAULT_SUBSCRIPTION_ID;
            }
            mPhoneSubscriptions[i] = sub;

            if (!mFlags.changeMethodOfObtainingImsRegistrationRadioTech()) {
                // Listen to IMS radio tech change for new sub
                if (SubscriptionManager.isValidSubscriptionId(sub)) {
                    registerForImsRadioTechChange(mContext, i);
                }
            }

            diffDetected = true;
            mAutoDataSwitchController.notifySubscriptionsMappingChanged();
        }
    }

    if (!hasAnyActiveSubscription) {
        transitionToEmergencyPhone();
    } else {
        if (VDBG) log("Found an active subscription");
    }

    // Check if phoneId for preferred data is changed.
    int oldPreferredDataPhoneId = mPreferredDataPhoneId;

    // Check if subId for preferred data is changed.
    int oldPreferredDataSubId = mPreferredDataSubId.get();

    // When there are no subscriptions, the preferred data phone ID is invalid, but we want
    // to keep a valid phoneId for Emergency, so skip logic that updates for preferred data
    // phone ID. Ideally there should be a single set of checks that evaluate the correct
    // phoneId on a service-by-service basis (EIMS being one), but for now... just bypass
    // this logic in the no-SIM case.
    if (hasAnyActiveSubscription) updatePreferredDataPhoneId();

    if (oldPreferredDataPhoneId != mPreferredDataPhoneId) {
        sb.append(" preferred data phoneId ").append(oldPreferredDataPhoneId)
                .append("->").append(mPreferredDataPhoneId);
        diffDetected = true;
    } else if (oldPreferredDataSubId != mPreferredDataSubId.get()) {
        logl("SIM refresh, notify dds change");
        // Inform connectivity about the active data phone
        notifyPreferredDataSubIdChanged();
    }

    // Always force DDS when radio on. This is to handle the corner cases that modem and android
    // DDS are out of sync after APM, AP should force DDS when radio on. long term solution
    // should be having API to query preferred data modem to detect the out-of-sync scenarios.
    if (diffDetected || EVALUATION_REASON_RADIO_ON.equals(reason)) {
        logl("evaluating due to " + sb);
        if (mHalCommandToUse == HAL_COMMAND_PREFERRED_DATA) {
            // With HAL_COMMAND_PREFERRED_DATA, all phones are assumed to allow PS attach.
            // So marking all phone as active, and the phone with mPreferredDataPhoneId
            // will send radioConfig command.
            for (int phoneId = 0; phoneId < mActiveModemCount; phoneId++) {
                mPhoneStates[phoneId].active = true;
            }
            sendRilCommands(mPreferredDataPhoneId);
        } else {
            List<Integer> newActivePhones = new ArrayList<>();

            // If all phones can have PS attached, activate all.
            // Otherwise, choose to activate phones according to requests. And
            // if list is not full, add mPreferredDataPhoneId.
            if (mMaxDataAttachModemCount == mActiveModemCount) {
                for (int i = 0; i < mMaxDataAttachModemCount; i++) {
                    newActivePhones.add(i);
                }
            } else {
                // First try to activate phone in voice call.
                if (mPhoneIdInVoiceCall != SubscriptionManager.INVALID_PHONE_INDEX) {
                    newActivePhones.add(mPhoneIdInVoiceCall);
                }

                if (newActivePhones.size() < mMaxDataAttachModemCount) {
                    for (TelephonyNetworkRequest networkRequest : mNetworkRequestList) {
                        int phoneIdForRequest = phoneIdForRequest(networkRequest);
                        if (phoneIdForRequest == INVALID_PHONE_INDEX) continue;
                        if (newActivePhones.contains(phoneIdForRequest)) continue;
                        newActivePhones.add(phoneIdForRequest);
                        if (newActivePhones.size() >= mMaxDataAttachModemCount) break;
                    }
                }

                if (newActivePhones.size() < mMaxDataAttachModemCount
                        && !newActivePhones.contains(mPreferredDataPhoneId)
                        && SubscriptionManager.isUsableSubIdValue(mPreferredDataPhoneId)) {
                    newActivePhones.add(mPreferredDataPhoneId);
                }
            }

            if (VDBG) {
                log("mPrimaryDataSubId = " + mPrimaryDataSubId);
                log("mAutoSelectedDataSubId = " + mAutoSelectedDataSubId);
                for (int i = 0; i < mActiveModemCount; i++) {
                    log(" phone[" + i + "] using sub[" + mPhoneSubscriptions[i] + "]");
                }
                log(" newActivePhones:");
                for (Integer i : newActivePhones) log("  " + i);
            }

            for (int phoneId = 0; phoneId < mActiveModemCount; phoneId++) {
                if (!newActivePhones.contains(phoneId)) {
                    deactivate(phoneId);
                }
            }

            // only activate phones up to the limit
            for (int phoneId : newActivePhones) {
                activate(phoneId);
            }
        }
    }
    return diffDetected;
}

决策因素(按优先级)

1. 紧急呼叫覆盖 (Emergency Override)

protected void updatePreferredDataPhoneId() {
    if (mEmergencyOverride != null && findPhoneById(mEmergencyOverride.mPhoneId) != null) {
        // Override DDS for emergency even if user data is not enabled, since it is an
        // emergency.
        // TODO: Provide a notification to the user that metered data is currently being
        // used during this period.
        logl("updatePreferredDataPhoneId: preferred data overridden for emergency."
                + " phoneId = " + mEmergencyOverride.mPhoneId);
        mPreferredDataPhoneId = mEmergencyOverride.mPhoneId;
        mLastSwitchPreferredDataReason = DataSwitch.Reason.DATA_SWITCH_REASON_UNKNOWN;

最高优先级:如果有紧急呼叫,立即切换到紧急呼叫所在的卡。

2. 语音通话中 (Voice Call In Progress)

    } else {
        int imsRegTech = mImsRegTechProvider.get(mContext, mPhoneIdInVoiceCall);
        if (isAnyVoiceCallActiveOnDevice() && imsRegTech != REGISTRATION_TECH_IWLAN) {
            if (imsRegTech != REGISTRATION_TECH_CROSS_SIM) {
                mPreferredDataPhoneId = shouldSwitchDataDueToInCall()
                        ? mPhoneIdInVoiceCall : getFallbackDataPhoneIdForInternetRequests();
            } else {
                logl("IMS call on cross-SIM, skip switching data to phone "
                        + mPhoneIdInVoiceCall);
            }
        } else {
            mPreferredDataPhoneId = getFallbackDataPhoneIdForInternetRequests();
        }
    }

条件:如果有语音通话,并且满足条件(用户开启了数据、语音卡也开启了数据),则切换到语音卡。

3. 自动选择的数据卡 (Auto Selected Data Sub)

private int getFallbackDataPhoneIdForInternetRequests() {
    int fallbackSubId = isActiveSubId(mAutoSelectedDataSubId)
            ? mAutoSelectedDataSubId : mPrimaryDataSubId;

    if (SubscriptionManager.isUsableSubIdValue(fallbackSubId)) {
        for (int phoneId = 0; phoneId < mActiveModemCount; phoneId++) {
            if (mPhoneSubscriptions[phoneId] == fallbackSubId) {
                return phoneId;
            }
        }
    }
    return SubscriptionManager.INVALID_PHONE_INDEX;
}

智能切换:系统可能因为信号质量等原因自动选择的数据卡。

4. 用户设置的主数据卡 (Primary Data Sub)

// Check if user setting of default non-opportunistic data sub is changed.
int primaryDataSubId = mSubscriptionManagerService.getDefaultDataSubId();
if (primaryDataSubId != mPrimaryDataSubId) {
    sb.append(" mPrimaryDataSubId ").append(mPrimaryDataSubId).append("->")
        .append(primaryDataSubId);
    mPrimaryDataSubId = primaryDataSubId;
    mLastSwitchPreferredDataReason = DataSwitch.Reason.DATA_SWITCH_REASON_MANUAL;

默认选择:用户在设置中选择的默认数据卡。

射频资源分配方式

根据 Modem 能力,有两种分配方式:

方式 1: HAL_COMMAND_PREFERRED_DATA (新)

if (mHalCommandToUse == HAL_COMMAND_PREFERRED_DATA) {
    // With HAL_COMMAND_PREFERRED_DATA, all phones are assumed to allow PS attach.
    // So marking all phone as active, and the phone with mPreferredDataPhoneId
    // will send radioConfig command.
    for (int phoneId = 0; phoneId < mActiveModemCount; phoneId++) {
        mPhoneStates[phoneId].active = true;
    }
    sendRilCommands(mPreferredDataPhoneId);
  • 特点:所有 Phone 都标记为 active
  • Modem 控制:通过 setPreferredDataModem() 告诉 Modem 哪个是首选数据卡
  • 现代设备:支持动态切换

方式 2: HAL_COMMAND_ALLOW_DATA (旧)

} else {
    List<Integer> newActivePhones = new ArrayList<>();

    // If all phones can have PS attached, activate all.
    // Otherwise, choose to activate phones according to requests. And
    // if list is not full, add mPreferredDataPhoneId.
    if (mMaxDataAttachModemCount == mActiveModemCount) {
        for (int i = 0; i < mMaxDataAttachModemCount; i++) {
            newActivePhones.add(i);
        }
    } else {
        // First try to activate phone in voice call.
        if (mPhoneIdInVoiceCall != SubscriptionManager.INVALID_PHONE_INDEX) {
            newActivePhones.add(mPhoneIdInVoiceCall);
        }

        if (newActivePhones.size() < mMaxDataAttachModemCount) {
            for (TelephonyNetworkRequest networkRequest : mNetworkRequestList) {
                int phoneIdForRequest = phoneIdForRequest(networkRequest);
                if (phoneIdForRequest == INVALID_PHONE_INDEX) continue;
                if (newActivePhones.contains(phoneIdForRequest)) continue;
                newActivePhones.add(phoneIdForRequest);
                if (newActivePhones.size() >= mMaxDataAttachModemCount) break;
            }
        }

        if (newActivePhones.size() < mMaxDataAttachModemCount
                && !newActivePhones.contains(mPreferredDataPhoneId)
                && SubscriptionManager.isUsableSubIdValue(mPreferredDataPhoneId)) {
            newActivePhones.add(mPreferredDataPhoneId);
        }
    }

    if (VDBG) {
        log("mPrimaryDataSubId = " + mPrimaryDataSubId);
        log("mAutoSelectedDataSubId = " + mAutoSelectedDataSubId);
        for (int i = 0; i < mActiveModemCount; i++) {
            log(" phone[" + i + "] using sub[" + mPhoneSubscriptions[i] + "]");
        }
        log(" newActivePhones:");
        for (Integer i : newActivePhones) log("  " + i);
    }

    for (int phoneId = 0; phoneId < mActiveModemCount; phoneId++) {
        if (!newActivePhones.contains(phoneId)) {
            deactivate(phoneId);
        }
    }

    // only activate phones up to the limit
    for (int phoneId : newActivePhones) {
        activate(phoneId);
    }
}
  • 特点:选择性激活 Phone
  • 优先级顺序
    1. 语音通话中的卡
    2. 有网络请求的卡
    3. 首选数据卡
  • Android 控制:通过 setDataAllowed() 控制每个 Phone 是否允许 PS Attach

最终执行:发送 RIL 命令

protected void sendRilCommands(int phoneId) {
    if (!SubscriptionManager.isValidPhoneId(phoneId)) {
        logl("sendRilCommands: skip dds switch due to invalid phoneId=" + phoneId);
        return;
    }

    Message message = Message.obtain(this, EVENT_MODEM_COMMAND_DONE, phoneId);
    if (mHalCommandToUse == HAL_COMMAND_ALLOW_DATA || mHalCommandToUse == HAL_COMMAND_UNKNOWN) {
        // Skip ALLOW_DATA for single SIM device
        if (mActiveModemCount > 1) {
            PhoneFactory.getPhone(phoneId).mCi.setDataAllowed(isPhoneActive(phoneId), message);
        }
    } else if (phoneId == mPreferredDataPhoneId) {
        // Only setPreferredDataModem if the phoneId equals to current mPreferredDataPhoneId
        logl("sendRilCommands: setPreferredDataModem - phoneId: " + phoneId);
        mRadioConfig.setPreferredDataModem(mPreferredDataPhoneId, message);
    }
}

总结:决策流程图

用户操作/系统事件
    ↓
onEvaluate()
    ↓
updatePreferredDataPhoneId()
    ├─ 1. 有紧急呼叫? → 使用紧急呼叫卡
    ├─ 2. 有语音通话? → (条件满足)使用语音卡
    ├─ 3. 有自动选择? → 使用自动选择卡
    └─ 4. 否则 → 使用主数据卡
    ↓
根据 Modem 能力选择方式
    ├─ HAL_COMMAND_PREFERRED_DATA
    │   ├─ 所有 Phone 标记 active
    │   └─ setPreferredDataModem(phoneId)
    │
    └─ HAL_COMMAND_ALLOW_DATA
        ├─ 选择激活的 Phone (最多 mMaxDataAttachModemCount)
        │   ├─ 语音通话中的
        │   ├─ 有网络请求的
        │   └─ 首选数据卡
        └─ setDataAllowed(phoneId, true/false)
    ↓
Modem 执行射频资源分配

关键就是 onEvaluate()updatePreferredDataPhoneId()sendRilCommands() 这条主线!

sendRilCommands() 具体干了什么?

  1. 检查 phoneId 有效性

  2. 创建 Message 用于接收 Modem 响应 (EVENT_MODEM_COMMAND_DONE)

  3. 根据 Modem 能力选择命令:

    • 旧 Modem: 调用 setDataAllowed(phoneId, active) 单独控制每个 Phone
    • 新 Modem: 调用 setPreferredDataModem(modemId) 告诉 Modem 首选哪个
  4. 通过 RIL → RadioProxy → HIDL/AIDL → RILD → Vendor RIL 发送命令到 Modem

  5. Modem 执行射频资源切换,允许/禁止特定 SIM 卡的数据连接