很好的问题!让我分析 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
- 优先级顺序:
- 语音通话中的卡
- 有网络请求的卡
- 首选数据卡
- 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() 具体干了什么?
-
检查 phoneId 有效性
-
创建 Message 用于接收 Modem 响应 (
EVENT_MODEM_COMMAND_DONE) -
根据 Modem 能力选择命令:
- 旧 Modem: 调用
setDataAllowed(phoneId, active)单独控制每个 Phone - 新 Modem: 调用
setPreferredDataModem(modemId)告诉 Modem 首选哪个
- 旧 Modem: 调用
-
通过 RIL → RadioProxy → HIDL/AIDL → RILD → Vendor RIL 发送命令到 Modem
-
Modem 执行射频资源切换,允许/禁止特定 SIM 卡的数据连接