aboutsummaryrefslogtreecommitdiff
path: root/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/java/com/android/internal/telephony/data/AutoDataSwitchController.java')
-rw-r--r--src/java/com/android/internal/telephony/data/AutoDataSwitchController.java503
1 files changed, 416 insertions, 87 deletions
diff --git a/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java b/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java
index 87591deebc..e8cd8f017d 100644
--- a/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java
+++ b/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java
@@ -37,6 +37,7 @@ import android.os.Message;
import android.provider.Settings;
import android.telephony.AccessNetworkConstants;
import android.telephony.NetworkRegistrationInfo;
+import android.telephony.NetworkRegistrationInfo.RegistrationState;
import android.telephony.SignalStrength;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
@@ -46,6 +47,7 @@ import android.util.LocalLog;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.flags.FeatureFlags;
import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
import com.android.internal.telephony.subscription.SubscriptionManagerService;
import com.android.internal.telephony.util.NotificationChannelController;
@@ -57,6 +59,8 @@ import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
+import java.util.Set;
+import java.util.stream.Collectors;
/**
* Recommend a data phone to use based on its availability.
@@ -102,6 +106,8 @@ public class AutoDataSwitchController extends Handler {
private static final int EVENT_SIGNAL_STRENGTH_CHANGED = 4;
/** Event indicates the switch state is stable, proceed to validation as the next step. */
private static final int EVENT_MEETS_AUTO_DATA_SWITCH_STATE = 5;
+ /** Event when subscriptions changed. */
+ private static final int EVENT_SUBSCRIPTIONS_CHANGED = 6;
/** Fragment "key" argument passed thru {@link #SETTINGS_EXTRA_SHOW_FRAGMENT_ARGUMENTS} */
private static final String SETTINGS_EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
@@ -120,6 +126,7 @@ public class AutoDataSwitchController extends Handler {
private final @NonNull LocalLog mLocalLog = new LocalLog(128);
private final @NonNull Context mContext;
+ private final @NonNull FeatureFlags mFlags;
private final @NonNull SubscriptionManagerService mSubscriptionManagerService;
private final @NonNull PhoneSwitcher mPhoneSwitcher;
private final @NonNull AutoDataSwitchControllerCallback mPhoneSwitcherCallback;
@@ -133,6 +140,12 @@ public class AutoDataSwitchController extends Handler {
*/
private long mAutoDataSwitchAvailabilityStabilityTimeThreshold = -1;
/**
+ * The tolerated gap of score for auto data switch decision, larger than which the device will
+ * switch to the SIM with higher score. If 0, the device will always switch to the higher score
+ * SIM. If < 0, the network type and signal strength based auto switch is disabled.
+ */
+ private int mScoreTolerance = -1;
+ /**
* {@code true} if requires ping test before switching preferred data modem; otherwise, switch
* even if ping test fails.
*/
@@ -144,36 +157,82 @@ public class AutoDataSwitchController extends Handler {
*/
private int mAutoDataSwitchValidationMaxRetry;
+ /** The signal status of phones, where index corresponds to phone Id. */
private @NonNull PhoneSignalStatus[] mPhonesSignalStatus;
+ /**
+ * The phone Id of the pending switching phone. Used for pruning frequent switch evaluation.
+ */
+ private int mSelectedTargetPhoneId = INVALID_PHONE_INDEX;
/**
* To track the signal status of a phone in order to evaluate whether it's a good candidate to
* switch to.
*/
private static class PhoneSignalStatus {
- private @NonNull Phone mPhone;
- private @NetworkRegistrationInfo.RegistrationState int mDataRegState =
- NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING;
- private @NonNull TelephonyDisplayInfo mDisplayInfo;
- private @NonNull SignalStrength mSignalStrength;
-
- private int mScore;
-
+ /**
+ * How preferred the current phone is.
+ */
+ enum UsableState {
+ HOME(1), ROAMING_ENABLED(0), NOT_USABLE(-1);
+ /**
+ * The higher the score, the more preferred.
+ * HOME is preferred over ROAMING assuming roaming is metered.
+ */
+ final int mScore;
+ UsableState(int score) {
+ this.mScore = score;
+ }
+ }
+ /** The phone */
+ @NonNull private final Phone mPhone;
+ /** Data registration state of the phone */
+ @RegistrationState private int mDataRegState = NetworkRegistrationInfo
+ .REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING;
+ /** Current Telephony display info of the phone */
+ @NonNull private TelephonyDisplayInfo mDisplayInfo;
+ /** Signal strength of the phone */
+ @NonNull private SignalStrength mSignalStrength;
+ /** {@code true} if this slot is listening for events. */
+ private boolean mListeningForEvents;
private PhoneSignalStatus(@NonNull Phone phone) {
this.mPhone = phone;
this.mDisplayInfo = phone.getDisplayInfoController().getTelephonyDisplayInfo();
this.mSignalStrength = phone.getSignalStrength();
}
- private int updateScore() {
- // TODO: score = inservice? dcm.getscore() : 0
- return mScore;
+
+ /**
+ * @return the current score of this phone. 0 indicates out of service and it will never be
+ * selected as the secondary data candidate.
+ */
+ private int getRatSignalScore() {
+ return isInService(mDataRegState)
+ ? mPhone.getDataNetworkController().getDataConfigManager()
+ .getAutoDataSwitchScore(mDisplayInfo, mSignalStrength) : 0;
+ }
+
+ /**
+ * @return The current usable state of the phone.
+ */
+ private UsableState getUsableState() {
+ switch (mDataRegState) {
+ case NetworkRegistrationInfo.REGISTRATION_STATE_HOME:
+ return UsableState.HOME;
+ case NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING:
+ return mPhone.getDataRoamingEnabled()
+ ? UsableState.ROAMING_ENABLED : UsableState.NOT_USABLE;
+ default:
+ return UsableState.NOT_USABLE;
+ }
}
+
@Override
public String toString() {
- return "{phoneId=" + mPhone.getPhoneId()
- + " score=" + mScore + " dataRegState="
+ return "{phone " + mPhone.getPhoneId()
+ + " score=" + getRatSignalScore() + " dataRegState="
+ NetworkRegistrationInfo.registrationStateToString(mDataRegState)
+ + " " + getUsableState()
+ " display=" + mDisplayInfo + " signalStrength=" + mSignalStrength.getLevel()
+ + " listeningForEvents=" + mListeningForEvents
+ "}";
}
@@ -212,16 +271,19 @@ public class AutoDataSwitchController extends Handler {
* @param phoneSwitcherCallback Callback for phone switcher to execute.
*/
public AutoDataSwitchController(@NonNull Context context, @NonNull Looper looper,
- @NonNull PhoneSwitcher phoneSwitcher,
+ @NonNull PhoneSwitcher phoneSwitcher, @NonNull FeatureFlags featureFlags,
@NonNull AutoDataSwitchControllerCallback phoneSwitcherCallback) {
super(looper);
mContext = context;
+ mFlags = featureFlags;
mSubscriptionManagerService = SubscriptionManagerService.getInstance();
mPhoneSwitcher = phoneSwitcher;
mPhoneSwitcherCallback = phoneSwitcherCallback;
readDeviceResourceConfig();
int numActiveModems = PhoneFactory.getPhones().length;
mPhonesSignalStatus = new PhoneSignalStatus[numActiveModems];
+ // Listening on all slots on boot up to make sure nothing missed. Later the tracking is
+ // pruned upon subscriptions changed.
for (int phoneId = 0; phoneId < numActiveModems; phoneId++) {
registerAllEventsForPhone(phoneId);
}
@@ -236,16 +298,46 @@ public class AutoDataSwitchController extends Handler {
if (oldActiveModems == numActiveModems) return;
// Dual -> Single
for (int phoneId = numActiveModems; phoneId < oldActiveModems; phoneId++) {
- Phone phone = mPhonesSignalStatus[phoneId].mPhone;
- phone.getDisplayInfoController().unregisterForTelephonyDisplayInfoChanged(this);
- phone.getSignalStrengthController().unregisterForSignalStrengthChanged(this);
- phone.getServiceStateTracker().unregisterForServiceStateChanged(this);
+ unregisterAllEventsForPhone(phoneId);
}
mPhonesSignalStatus = Arrays.copyOf(mPhonesSignalStatus, numActiveModems);
// Signal -> Dual
for (int phoneId = oldActiveModems; phoneId < numActiveModems; phoneId++) {
registerAllEventsForPhone(phoneId);
}
+ logl("onMultiSimConfigChanged: " + Arrays.toString(mPhonesSignalStatus));
+ }
+
+ /** Notify subscriptions changed. */
+ public void notifySubscriptionsMappingChanged() {
+ sendEmptyMessage(EVENT_SUBSCRIPTIONS_CHANGED);
+ }
+
+ /**
+ * On subscription changed, register/unregister events on phone Id slot that has active/inactive
+ * sub to reduce unnecessary tracking.
+ */
+ private void onSubscriptionsChanged() {
+ Set<Integer> activePhoneIds = Arrays.stream(mSubscriptionManagerService
+ .getActiveSubIdList(true /*visibleOnly*/))
+ .map(mSubscriptionManagerService::getPhoneId)
+ .boxed()
+ .collect(Collectors.toSet());
+ // Track events only if there are at least two active visible subscriptions.
+ if (activePhoneIds.size() < 2) activePhoneIds.clear();
+ boolean changed = false;
+ for (int phoneId = 0; phoneId < mPhonesSignalStatus.length; phoneId++) {
+ if (activePhoneIds.contains(phoneId)
+ && !mPhonesSignalStatus[phoneId].mListeningForEvents) {
+ registerAllEventsForPhone(phoneId);
+ changed = true;
+ } else if (!activePhoneIds.contains(phoneId)
+ && mPhonesSignalStatus[phoneId].mListeningForEvents) {
+ unregisterAllEventsForPhone(phoneId);
+ changed = true;
+ }
+ }
+ if (changed) logl("onSubscriptionChanged: " + Arrays.toString(mPhonesSignalStatus));
}
/**
@@ -254,7 +346,7 @@ public class AutoDataSwitchController extends Handler {
*/
private void registerAllEventsForPhone(int phoneId) {
Phone phone = PhoneFactory.getPhone(phoneId);
- if (phone != null) {
+ if (phone != null && isActiveModemPhone(phoneId)) {
mPhonesSignalStatus[phoneId] = new PhoneSignalStatus(phone);
phone.getDisplayInfoController().registerForTelephonyDisplayInfoChanged(
this, EVENT_DISPLAY_INFO_CHANGED, phoneId);
@@ -262,18 +354,36 @@ public class AutoDataSwitchController extends Handler {
this, EVENT_SIGNAL_STRENGTH_CHANGED, phoneId);
phone.getServiceStateTracker().registerForServiceStateChanged(this,
EVENT_SERVICE_STATE_CHANGED, phoneId);
+ mPhonesSignalStatus[phoneId].mListeningForEvents = true;
} else {
loge("Unexpected null phone " + phoneId + " when register all events");
}
}
/**
+ * Unregister all tracking events for a phone.
+ * @param phoneId The phone to unregister for all events.
+ */
+ private void unregisterAllEventsForPhone(int phoneId) {
+ if (isActiveModemPhone(phoneId)) {
+ Phone phone = mPhonesSignalStatus[phoneId].mPhone;
+ phone.getDisplayInfoController().unregisterForTelephonyDisplayInfoChanged(this);
+ phone.getSignalStrengthController().unregisterForSignalStrengthChanged(this);
+ phone.getServiceStateTracker().unregisterForServiceStateChanged(this);
+ mPhonesSignalStatus[phoneId].mListeningForEvents = false;
+ } else {
+ loge("Unexpected out of bound phone " + phoneId + " when unregister all events");
+ }
+ }
+
+ /**
* Read the default device config from any default phone because the resource config are per
* device. No need to register callback for the same reason.
*/
private void readDeviceResourceConfig() {
Phone phone = PhoneFactory.getDefaultPhone();
DataConfigManager dataConfig = phone.getDataNetworkController().getDataConfigManager();
+ mScoreTolerance = dataConfig.getAutoDataSwitchScoreTolerance();
mRequirePingTestBeforeSwitch = dataConfig.isPingTestBeforeAutoDataSwitchRequired();
mAutoDataSwitchAvailabilityStabilityTimeThreshold =
dataConfig.getAutoDataSwitchAvailabilityStabilityTimeThreshold();
@@ -289,24 +399,32 @@ public class AutoDataSwitchController extends Handler {
case EVENT_SERVICE_STATE_CHANGED:
ar = (AsyncResult) msg.obj;
phoneId = (int) ar.userObj;
- onRegistrationStateChanged(phoneId);
+ onServiceStateChanged(phoneId);
break;
case EVENT_DISPLAY_INFO_CHANGED:
ar = (AsyncResult) msg.obj;
phoneId = (int) ar.userObj;
onDisplayInfoChanged(phoneId);
break;
+ case EVENT_SIGNAL_STRENGTH_CHANGED:
+ ar = (AsyncResult) msg.obj;
+ phoneId = (int) ar.userObj;
+ onSignalStrengthChanged(phoneId);
+ break;
case EVENT_EVALUATE_AUTO_SWITCH:
int reason = (int) msg.obj;
onEvaluateAutoDataSwitch(reason);
break;
case EVENT_MEETS_AUTO_DATA_SWITCH_STATE:
int targetPhoneId = msg.arg1;
- boolean needValidation = (boolean) msg.obj;
+ boolean needValidation = msg.arg2 == 1;
log("require validation on phone " + targetPhoneId
+ (needValidation ? "" : " no") + " need to pass");
mPhoneSwitcherCallback.onRequireValidation(targetPhoneId, needValidation);
break;
+ case EVENT_SUBSCRIPTIONS_CHANGED:
+ onSubscriptionsChanged();
+ break;
default:
loge("Unexpected event " + msg.what);
}
@@ -315,9 +433,9 @@ public class AutoDataSwitchController extends Handler {
/**
* Called when registration state changed.
*/
- private void onRegistrationStateChanged(int phoneId) {
+ private void onServiceStateChanged(int phoneId) {
Phone phone = PhoneFactory.getPhone(phoneId);
- if (phone != null) {
+ if (phone != null && isActiveModemPhone(phoneId)) {
int oldRegState = mPhonesSignalStatus[phoneId].mDataRegState;
int newRegState = phone.getServiceState()
.getNetworkRegistrationInfo(
@@ -326,44 +444,93 @@ public class AutoDataSwitchController extends Handler {
.getRegistrationState();
if (newRegState != oldRegState) {
mPhonesSignalStatus[phoneId].mDataRegState = newRegState;
- log("onRegistrationStateChanged: phone " + phoneId + " "
- + NetworkRegistrationInfo.registrationStateToString(oldRegState)
- + " -> "
- + NetworkRegistrationInfo.registrationStateToString(newRegState));
- evaluateAutoDataSwitch(EVALUATION_REASON_REGISTRATION_STATE_CHANGED);
- } else {
- log("onRegistrationStateChanged: no change.");
+ if (isInService(oldRegState) != isInService(newRegState)
+ || isHomeService(oldRegState) != isHomeService(newRegState)) {
+ log("onServiceStateChanged: phone " + phoneId + " "
+ + NetworkRegistrationInfo.registrationStateToString(oldRegState)
+ + " -> "
+ + NetworkRegistrationInfo.registrationStateToString(newRegState));
+ evaluateAutoDataSwitch(EVALUATION_REASON_REGISTRATION_STATE_CHANGED);
+ }
}
} else {
loge("Unexpected null phone " + phoneId + " upon its registration state changed");
}
}
- /**
- * @return {@code true} if the phone state is considered in service.
- */
- private boolean isInService(@NetworkRegistrationInfo.RegistrationState int dataRegState) {
+ /** @return {@code true} if the phone state is considered in service. */
+ private static boolean isInService(@RegistrationState int dataRegState) {
return dataRegState == NetworkRegistrationInfo.REGISTRATION_STATE_HOME
|| dataRegState == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING;
}
+ /** @return {@code true} if the phone state is in home service. */
+ private static boolean isHomeService(@RegistrationState int dataRegState) {
+ return dataRegState == NetworkRegistrationInfo.REGISTRATION_STATE_HOME;
+ }
+
/**
* Called when {@link TelephonyDisplayInfo} changed. This can happen when network types or
* override network types (5G NSA, 5G MMWAVE) change.
+ * @param phoneId The phone that changed.
*/
private void onDisplayInfoChanged(int phoneId) {
Phone phone = PhoneFactory.getPhone(phoneId);
- if (phone != null) {
+ if (phone != null && isActiveModemPhone(phoneId)) {
TelephonyDisplayInfo displayInfo = phone.getDisplayInfoController()
.getTelephonyDisplayInfo();
- //TODO(b/260928808)
- log("onDisplayInfoChanged:" + displayInfo);
+ mPhonesSignalStatus[phoneId].mDisplayInfo = displayInfo;
+ if (getHigherScoreCandidatePhoneId() != mSelectedTargetPhoneId) {
+ log("onDisplayInfoChanged: phone " + phoneId + " " + displayInfo);
+ evaluateAutoDataSwitch(EVALUATION_REASON_DISPLAY_INFO_CHANGED);
+ }
} else {
loge("Unexpected null phone " + phoneId + " upon its display info changed");
}
}
/**
+ * Called when {@link SignalStrength} changed.
+ * @param phoneId The phone that changed.
+ */
+ private void onSignalStrengthChanged(int phoneId) {
+ Phone phone = PhoneFactory.getPhone(phoneId);
+ if (phone != null && isActiveModemPhone(phoneId)) {
+ SignalStrength newSignalStrength = phone.getSignalStrength();
+ SignalStrength oldSignalStrength = mPhonesSignalStatus[phoneId].mSignalStrength;
+ if (oldSignalStrength.getLevel() != newSignalStrength.getLevel()) {
+ mPhonesSignalStatus[phoneId].mSignalStrength = newSignalStrength;
+ if (getHigherScoreCandidatePhoneId() != mSelectedTargetPhoneId) {
+ log("onSignalStrengthChanged: phone " + phoneId + " "
+ + oldSignalStrength.getLevel() + "->" + newSignalStrength.getLevel());
+ evaluateAutoDataSwitch(EVALUATION_REASON_SIGNAL_STRENGTH_CHANGED);
+ }
+ }
+ } else {
+ loge("Unexpected null phone " + phoneId + " upon its signal strength changed");
+ }
+ }
+
+ /**
+ * Called as a preliminary check for the frequent signal/display info change.
+ * @return The phone Id if found a candidate phone with higher signal score.
+ */
+ private int getHigherScoreCandidatePhoneId() {
+ int preferredPhoneId = mPhoneSwitcher.getPreferredDataPhoneId();
+ if (isActiveModemPhone(preferredPhoneId)) {
+ int currentScore = mPhonesSignalStatus[preferredPhoneId].getRatSignalScore();
+ for (int phoneId = 0; phoneId < mPhonesSignalStatus.length; phoneId++) {
+ int candidateScore = mPhonesSignalStatus[phoneId].getRatSignalScore();
+ if (phoneId != preferredPhoneId
+ && (candidateScore - currentScore) > mScoreTolerance) {
+ return phoneId;
+ }
+ }
+ }
+ return INVALID_PHONE_INDEX;
+ }
+
+ /**
* Schedule for auto data switch evaluation.
* @param reason The reason for the evaluation.
*/
@@ -388,10 +555,7 @@ public class AutoDataSwitchController extends Handler {
if (mAutoDataSwitchAvailabilityStabilityTimeThreshold < 0) return;
int defaultDataSubId = mSubscriptionManagerService.getDefaultDataSubId();
// check is valid DSDS
- if (!isActiveSubId(defaultDataSubId) || mSubscriptionManagerService
- .getActiveSubIdList(true).length <= 1) {
- return;
- }
+ if (mSubscriptionManagerService.getActiveSubIdList(true).length < 2) return;
Phone defaultDataPhone = PhoneFactory.getPhone(mSubscriptionManagerService.getPhoneId(
defaultDataSubId));
if (defaultDataPhone == null) {
@@ -401,13 +565,16 @@ public class AutoDataSwitchController extends Handler {
}
int defaultDataPhoneId = defaultDataPhone.getPhoneId();
int preferredPhoneId = mPhoneSwitcher.getPreferredDataPhoneId();
- log("onEvaluateAutoDataSwitch: defaultPhoneId: " + defaultDataPhoneId
- + " preferredPhoneId: " + preferredPhoneId
- + " reason: " + evaluationReasonToString(reason));
+ StringBuilder debugMessage = new StringBuilder("onEvaluateAutoDataSwitch:");
+ debugMessage.append(" defaultPhoneId: ").append(defaultDataPhoneId)
+ .append(" preferredPhoneId: ").append(preferredPhoneId)
+ .append(", reason: ").append(evaluationReasonToString(reason));
if (preferredPhoneId == defaultDataPhoneId) {
// on default data sub
- int candidatePhoneId = getSwitchCandidatePhoneId(defaultDataPhoneId);
+ int candidatePhoneId = getSwitchCandidatePhoneId(defaultDataPhoneId, debugMessage);
+ log(debugMessage.toString());
if (candidatePhoneId != INVALID_PHONE_INDEX) {
+ mSelectedTargetPhoneId = candidatePhoneId;
startStabilityCheck(candidatePhoneId, mRequirePingTestBeforeSwitch);
} else {
cancelAnyPendingSwitch();
@@ -415,90 +582,239 @@ public class AutoDataSwitchController extends Handler {
} else {
// on backup data sub
Phone backupDataPhone = PhoneFactory.getPhone(preferredPhoneId);
- if (backupDataPhone == null) {
- loge("onEvaluateAutoDataSwitch: Unexpected null phone " + preferredPhoneId
- + " as the current active data phone");
+ if (backupDataPhone == null || !isActiveModemPhone(preferredPhoneId)) {
+ loge(debugMessage.append(" Unexpected null phone ").append(preferredPhoneId)
+ .append(" as the current active data phone").toString());
return;
}
if (!defaultDataPhone.isUserDataEnabled() || !backupDataPhone.isDataAllowed()) {
- // immediately switch back if user disabled setting changes
mPhoneSwitcherCallback.onRequireImmediatelySwitchToPhone(DEFAULT_PHONE_INDEX,
EVALUATION_REASON_DATA_SETTINGS_CHANGED);
+ log(debugMessage.append(", immediately back to default as user turns off settings")
+ .toString());
return;
}
- if (mDefaultNetworkIsOnNonCellular) {
- log("onEvaluateAutoDataSwitch: Default network is active on nonCellular transport");
- startStabilityCheck(DEFAULT_PHONE_INDEX, false);
- return;
- }
+ boolean backToDefault = false;
+ boolean needValidation = true;
- if (mPhonesSignalStatus[preferredPhoneId].mDataRegState
- != NetworkRegistrationInfo.REGISTRATION_STATE_HOME) {
- // backup phone lost its HOME registration
- startStabilityCheck(DEFAULT_PHONE_INDEX, false);
- return;
- }
+ if (mFlags.autoSwitchAllowRoaming()) {
+ if (mDefaultNetworkIsOnNonCellular) {
+ debugMessage.append(", back to default as default network")
+ .append(" is active on nonCellular transport");
+ backToDefault = true;
+ needValidation = false;
+ } else {
+ PhoneSignalStatus.UsableState defaultUsableState =
+ mPhonesSignalStatus[defaultDataPhoneId].getUsableState();
+ PhoneSignalStatus.UsableState currentUsableState =
+ mPhonesSignalStatus[preferredPhoneId].getUsableState();
- if (isInService(mPhonesSignalStatus[defaultDataPhoneId].mDataRegState)) {
- // default phone is back to service
- startStabilityCheck(DEFAULT_PHONE_INDEX, mRequirePingTestBeforeSwitch);
- return;
+ boolean isCurrentUsable = currentUsableState.mScore
+ > PhoneSignalStatus.UsableState.NOT_USABLE.mScore;
+
+ if (currentUsableState.mScore < defaultUsableState.mScore) {
+ debugMessage.append(", back to default phone ").append(preferredPhoneId)
+ .append(" : ").append(defaultUsableState)
+ .append(" , backup phone: ").append(currentUsableState);
+
+ backToDefault = true;
+ // Require validation if the current preferred phone is usable.
+ needValidation = isCurrentUsable && mRequirePingTestBeforeSwitch;
+ } else if (defaultUsableState.mScore == currentUsableState.mScore) {
+ debugMessage.append(", default phone ").append(preferredPhoneId)
+ .append(" : ").append(defaultUsableState)
+ .append(" , backup phone: ").append(currentUsableState);
+
+ if (isCurrentUsable) {
+ // Both phones are usable.
+ if (isRatSignalStrengthBasedSwitchEnabled()) {
+ int defaultScore = mPhonesSignalStatus[defaultDataPhoneId]
+ .getRatSignalScore();
+ int currentScore = mPhonesSignalStatus[preferredPhoneId]
+ .getRatSignalScore();
+ if ((defaultScore - currentScore) > mScoreTolerance) {
+ debugMessage
+ .append(", back to default for higher score ")
+ .append(defaultScore).append(" versus current ")
+ .append(currentScore);
+ backToDefault = true;
+ needValidation = mRequirePingTestBeforeSwitch;
+ }
+ } else {
+ // Only OOS/in service switch is enabled, switch back.
+ debugMessage.append(", back to default as it's usable. ");
+ backToDefault = true;
+ needValidation = mRequirePingTestBeforeSwitch;
+ }
+ } else {
+ debugMessage.append(", back to default as both phones are unusable.");
+ backToDefault = true;
+ needValidation = false;
+ }
+ }
+ }
+ } else {
+ if (mDefaultNetworkIsOnNonCellular) {
+ debugMessage.append(", back to default as default network")
+ .append(" is active on nonCellular transport");
+ backToDefault = true;
+ needValidation = false;
+ } else if (!isHomeService(mPhonesSignalStatus[preferredPhoneId].mDataRegState)) {
+ debugMessage.append(", back to default as backup phone lost HOME registration");
+ backToDefault = true;
+ needValidation = false;
+ } else if (isRatSignalStrengthBasedSwitchEnabled()) {
+ int defaultScore = mPhonesSignalStatus[defaultDataPhoneId].getRatSignalScore();
+ int currentScore = mPhonesSignalStatus[preferredPhoneId].getRatSignalScore();
+ if ((defaultScore - currentScore) > mScoreTolerance) {
+ debugMessage
+ .append(", back to default as default phone has higher score ")
+ .append(defaultScore).append(" versus current ")
+ .append(currentScore);
+ backToDefault = true;
+ needValidation = mRequirePingTestBeforeSwitch;
+ }
+ } else if (isInService(mPhonesSignalStatus[defaultDataPhoneId].mDataRegState)) {
+ debugMessage.append(", back to default as the default is back to service ");
+ backToDefault = true;
+ needValidation = mRequirePingTestBeforeSwitch;
+ }
}
- // cancel any previous attempts of switching back to default phone
- cancelAnyPendingSwitch();
+ if (backToDefault) {
+ log(debugMessage.toString());
+ mSelectedTargetPhoneId = defaultDataPhoneId;
+ startStabilityCheck(DEFAULT_PHONE_INDEX, needValidation);
+ } else {
+ // cancel any previous attempts of switching back to default phone
+ cancelAnyPendingSwitch();
+ }
}
}
/**
* Called when consider switching from primary default data sub to another data sub.
+ * @param defaultPhoneId The default data phone
+ * @param debugMessage Debug message.
* @return the target subId if a suitable candidate is found, otherwise return
* {@link SubscriptionManager#INVALID_PHONE_INDEX}
*/
- private int getSwitchCandidatePhoneId(int defaultPhoneId) {
+ private int getSwitchCandidatePhoneId(int defaultPhoneId, @NonNull StringBuilder debugMessage) {
Phone defaultDataPhone = PhoneFactory.getPhone(defaultPhoneId);
if (defaultDataPhone == null) {
- log("getSwitchCandidatePhoneId: no sim loaded");
+ debugMessage.append(", no candidate as no sim loaded");
return INVALID_PHONE_INDEX;
}
if (!defaultDataPhone.isUserDataEnabled()) {
- log("getSwitchCandidatePhoneId: user disabled data");
+ debugMessage.append(", no candidate as user disabled mobile data");
return INVALID_PHONE_INDEX;
}
if (mDefaultNetworkIsOnNonCellular) {
- // Exists other active default transport
- log("getSwitchCandidatePhoneId: Default network is active on non-cellular transport");
+ debugMessage.append(", no candidate as default network is active")
+ .append(" on non-cellular transport");
return INVALID_PHONE_INDEX;
}
- // check whether primary and secondary signal status are worth switching
- if (isInService(mPhonesSignalStatus[defaultPhoneId].mDataRegState)) {
- log("getSwitchCandidatePhoneId: DDS is in service");
- return INVALID_PHONE_INDEX;
+ if (mFlags.autoSwitchAllowRoaming()) {
+ // check whether primary and secondary signal status are worth switching
+ if (!isRatSignalStrengthBasedSwitchEnabled()
+ && isHomeService(mPhonesSignalStatus[defaultPhoneId].mDataRegState)) {
+ debugMessage.append(", no candidate as default phone is in HOME service");
+ return INVALID_PHONE_INDEX;
+ }
+ } else {
+ // check whether primary and secondary signal status are worth switching
+ if (!isRatSignalStrengthBasedSwitchEnabled()
+ && isInService(mPhonesSignalStatus[defaultPhoneId].mDataRegState)) {
+ debugMessage.append(", no candidate as default phone is in service");
+ return INVALID_PHONE_INDEX;
+ }
}
+
+ PhoneSignalStatus defaultPhoneStatus = mPhonesSignalStatus[defaultPhoneId];
for (int phoneId = 0; phoneId < mPhonesSignalStatus.length; phoneId++) {
- if (phoneId != defaultPhoneId) {
+ if (phoneId == defaultPhoneId) continue;
+
+ Phone secondaryDataPhone = null;
+ PhoneSignalStatus candidatePhoneStatus = mPhonesSignalStatus[phoneId];
+ if (mFlags.autoSwitchAllowRoaming()) {
+ PhoneSignalStatus.UsableState currentUsableState =
+ mPhonesSignalStatus[defaultPhoneId].getUsableState();
+ PhoneSignalStatus.UsableState candidatePhoneUsableRank =
+ mPhonesSignalStatus[phoneId].getUsableState();
+ debugMessage.append(", found phone ").append(phoneId).append(" is ").append(
+ candidatePhoneUsableRank)
+ .append(", current is ").append(currentUsableState);
+ if (candidatePhoneUsableRank.mScore > currentUsableState.mScore) {
+ secondaryDataPhone = PhoneFactory.getPhone(phoneId);
+ } else if (isRatSignalStrengthBasedSwitchEnabled()
+ && currentUsableState.mScore == candidatePhoneUsableRank.mScore) {
+ // Both phones are home or both roaming enabled, so compare RAT/signal score.
+
+ int defaultScore = defaultPhoneStatus.getRatSignalScore();
+ int candidateScore = candidatePhoneStatus.getRatSignalScore();
+ if ((candidateScore - defaultScore) > mScoreTolerance) {
+ debugMessage.append(" with higher score ").append(
+ candidateScore)
+ .append(" versus current ").append(defaultScore);
+ secondaryDataPhone = PhoneFactory.getPhone(phoneId);
+ } else {
+ debugMessage.append(", but its score ").append(candidateScore)
+ .append(" doesn't meet the bar to switch given the current ")
+ .append(defaultScore);
+ }
+ }
+ } else if (isHomeService(candidatePhoneStatus.mDataRegState)) {
// the alternative phone must have HOME availability
- if (mPhonesSignalStatus[phoneId].mDataRegState
- == NetworkRegistrationInfo.REGISTRATION_STATE_HOME) {
- log("getSwitchCandidatePhoneId: found phone " + phoneId
- + " in HOME service");
- Phone secondaryDataPhone = PhoneFactory.getPhone(phoneId);
- if (secondaryDataPhone != null && // check auto switch feature enabled
- secondaryDataPhone.isDataAllowed()) {
- return phoneId;
+ debugMessage.append(", found phone ").append(phoneId).append(" in HOME service");
+
+ if (isInService(defaultPhoneStatus.mDataRegState)) {
+ // Use score if RAT/signal strength based switch is enabled and both phone are
+ // in service.
+ if (isRatSignalStrengthBasedSwitchEnabled()) {
+ int defaultScore = mPhonesSignalStatus[defaultPhoneId].getRatSignalScore();
+ int candidateScore = mPhonesSignalStatus[phoneId].getRatSignalScore();
+ if ((candidateScore - defaultScore) > mScoreTolerance) {
+ debugMessage.append(" with higher score ").append(candidateScore)
+ .append(" versus current ").append(defaultScore);
+ secondaryDataPhone = PhoneFactory.getPhone(phoneId);
+ } else {
+ debugMessage.append(", but its score ").append(candidateScore)
+ .append(" doesn't meet the bar to switch given the current ")
+ .append(defaultScore);
+ }
}
+ } else {
+ // Only OOS/in service switch is enabled.
+ secondaryDataPhone = PhoneFactory.getPhone(phoneId);
+ }
+ }
+
+ if (secondaryDataPhone != null) {
+ // check auto switch feature enabled
+ if (secondaryDataPhone.isDataAllowed()) {
+ return phoneId;
+ } else {
+ debugMessage.append(", but its data is not allowed");
}
}
}
+ debugMessage.append(", found no qualified candidate.");
return INVALID_PHONE_INDEX;
}
/**
+ * @return {@code true} If the feature of switching base on RAT and signal strength is enabled.
+ */
+ private boolean isRatSignalStrengthBasedSwitchEnabled() {
+ return mFlags.autoDataSwitchRatSs() && mScoreTolerance >= 0;
+ }
+
+ /**
* Called when the current environment suits auto data switch.
* Start pre-switch validation if the current environment suits auto data switch for
* {@link #mAutoDataSwitchAvailabilityStabilityTimeThreshold} MS.
@@ -508,10 +824,12 @@ public class AutoDataSwitchController extends Handler {
private void startStabilityCheck(int targetPhoneId, boolean needValidation) {
log("startAutoDataSwitchStabilityCheck: targetPhoneId=" + targetPhoneId
+ " needValidation=" + needValidation);
- if (!hasMessages(EVENT_MEETS_AUTO_DATA_SWITCH_STATE, needValidation)) {
+ String combinationIdentifier = targetPhoneId + "" + needValidation;
+ if (!hasEqualMessages(EVENT_MEETS_AUTO_DATA_SWITCH_STATE, combinationIdentifier)) {
+ removeMessages(EVENT_MEETS_AUTO_DATA_SWITCH_STATE);
sendMessageDelayed(obtainMessage(EVENT_MEETS_AUTO_DATA_SWITCH_STATE, targetPhoneId,
- 0/*placeholder*/,
- needValidation),
+ needValidation ? 1 : 0,
+ combinationIdentifier),
mAutoDataSwitchAvailabilityStabilityTimeThreshold);
}
}
@@ -566,6 +884,7 @@ public class AutoDataSwitchController extends Handler {
* Cancel any auto switch attempts when the current environment is not suitable for auto switch.
*/
private void cancelAnyPendingSwitch() {
+ mSelectedTargetPhoneId = INVALID_PHONE_INDEX;
resetFailedCount();
removeMessages(EVENT_MEETS_AUTO_DATA_SWITCH_STATE);
mPhoneSwitcherCallback.onRequireCancelAnyPendingAutoSwitchValidation();
@@ -651,6 +970,14 @@ public class AutoDataSwitchController extends Handler {
}
/**
+ * @param phoneId The phone Id to check.
+ * @return {@code true} if the phone Id is an active modem.
+ */
+ private boolean isActiveModemPhone(int phoneId) {
+ return phoneId >= 0 && phoneId < mPhonesSignalStatus.length;
+ }
+
+ /**
* Log debug messages.
* @param s debug messages
*/
@@ -686,11 +1013,13 @@ public class AutoDataSwitchController extends Handler {
IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " ");
pw.println("AutoDataSwitchController:");
pw.increaseIndent();
+ pw.println("mScoreTolerance=" + mScoreTolerance);
pw.println("mAutoDataSwitchValidationMaxRetry=" + mAutoDataSwitchValidationMaxRetry
+ " mAutoSwitchValidationFailedCount=" + mAutoSwitchValidationFailedCount);
pw.println("mRequirePingTestBeforeDataSwitch=" + mRequirePingTestBeforeSwitch);
pw.println("mAutoDataSwitchAvailabilityStabilityTimeThreshold="
+ mAutoDataSwitchAvailabilityStabilityTimeThreshold);
+ pw.println("mSelectedTargetPhoneId=" + mSelectedTargetPhoneId);
pw.increaseIndent();
for (PhoneSignalStatus status: mPhonesSignalStatus) {
pw.println(status);