diff options
Diffstat (limited to 'src/java/com/android/internal/telephony/data/AutoDataSwitchController.java')
-rw-r--r-- | src/java/com/android/internal/telephony/data/AutoDataSwitchController.java | 702 |
1 files changed, 702 insertions, 0 deletions
diff --git a/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java b/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java new file mode 100644 index 0000000000..87591deebc --- /dev/null +++ b/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java @@ -0,0 +1,702 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.data; + +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.telephony.SubscriptionManager.DEFAULT_PHONE_INDEX; +import static android.telephony.SubscriptionManager.INVALID_PHONE_INDEX; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.net.NetworkCapabilities; +import android.os.AsyncResult; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.provider.Settings; +import android.telephony.AccessNetworkConstants; +import android.telephony.NetworkRegistrationInfo; +import android.telephony.SignalStrength; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyDisplayInfo; +import android.util.IndentingPrintWriter; +import android.util.LocalLog; + +import com.android.internal.telephony.Phone; +import com.android.internal.telephony.PhoneFactory; +import com.android.internal.telephony.subscription.SubscriptionInfoInternal; +import com.android.internal.telephony.subscription.SubscriptionManagerService; +import com.android.internal.telephony.util.NotificationChannelController; +import com.android.telephony.Rlog; + + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Arrays; + +/** + * Recommend a data phone to use based on its availability. + */ +public class AutoDataSwitchController extends Handler { + /** Registration state changed. */ + public static final int EVALUATION_REASON_REGISTRATION_STATE_CHANGED = 1; + /** Telephony Display Info changed. */ + public static final int EVALUATION_REASON_DISPLAY_INFO_CHANGED = 2; + /** Signal Strength changed. */ + public static final int EVALUATION_REASON_SIGNAL_STRENGTH_CHANGED = 3; + /** Default network capabilities changed or lost. */ + public static final int EVALUATION_REASON_DEFAULT_NETWORK_CHANGED = 4; + /** Data enabled settings changed. */ + public static final int EVALUATION_REASON_DATA_SETTINGS_CHANGED = 5; + /** Retry due to previous validation failed. */ + public static final int EVALUATION_REASON_RETRY_VALIDATION = 6; + /** Sim loaded which means slot mapping became available. */ + public static final int EVALUATION_REASON_SIM_LOADED = 7; + /** Voice call ended. */ + public static final int EVALUATION_REASON_VOICE_CALL_END = 8; + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "EVALUATION_REASON_", + value = {EVALUATION_REASON_REGISTRATION_STATE_CHANGED, + EVALUATION_REASON_DISPLAY_INFO_CHANGED, + EVALUATION_REASON_SIGNAL_STRENGTH_CHANGED, + EVALUATION_REASON_DEFAULT_NETWORK_CHANGED, + EVALUATION_REASON_DATA_SETTINGS_CHANGED, + EVALUATION_REASON_RETRY_VALIDATION, + EVALUATION_REASON_SIM_LOADED, + EVALUATION_REASON_VOICE_CALL_END}) + public @interface AutoDataSwitchEvaluationReason {} + + private static final String LOG_TAG = "ADSC"; + + /** Event for service state changed. */ + private static final int EVENT_SERVICE_STATE_CHANGED = 1; + /** Event for display info changed. This is for getting 5G NSA or mmwave information. */ + private static final int EVENT_DISPLAY_INFO_CHANGED = 2; + /** Event for evaluate auto data switch opportunity. */ + private static final int EVENT_EVALUATE_AUTO_SWITCH = 3; + /** Event for signal strength changed. */ + 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; + + /** Fragment "key" argument passed thru {@link #SETTINGS_EXTRA_SHOW_FRAGMENT_ARGUMENTS} */ + private static final String SETTINGS_EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key"; + /** + * When starting this activity, this extra can also be specified to supply a Bundle of arguments + * to pass to that fragment when it is instantiated during the initial creation of the activity. + */ + private static final String SETTINGS_EXTRA_SHOW_FRAGMENT_ARGUMENTS = + ":settings:show_fragment_args"; + /** The resource ID of the auto data switch fragment in settings. **/ + private static final String AUTO_DATA_SWITCH_SETTING_R_ID = "auto_data_switch"; + /** Notification tag **/ + private static final String AUTO_DATA_SWITCH_NOTIFICATION_TAG = "auto_data_switch"; + /** Notification ID **/ + private static final int AUTO_DATA_SWITCH_NOTIFICATION_ID = 1; + + private final @NonNull LocalLog mLocalLog = new LocalLog(128); + private final @NonNull Context mContext; + private final @NonNull SubscriptionManagerService mSubscriptionManagerService; + private final @NonNull PhoneSwitcher mPhoneSwitcher; + private final @NonNull AutoDataSwitchControllerCallback mPhoneSwitcherCallback; + private boolean mDefaultNetworkIsOnNonCellular = false; + /** {@code true} if we've displayed the notification the first time auto switch occurs **/ + private boolean mDisplayedNotification = false; + /** + * Time threshold in ms to define a internet connection status to be stable(e.g. out of service, + * in service, wifi is the default active network.etc), while -1 indicates auto switch + * feature disabled. + */ + private long mAutoDataSwitchAvailabilityStabilityTimeThreshold = -1; + /** + * {@code true} if requires ping test before switching preferred data modem; otherwise, switch + * even if ping test fails. + */ + private boolean mRequirePingTestBeforeSwitch = true; + /** The count of consecutive auto switch validation failure **/ + private int mAutoSwitchValidationFailedCount = 0; + /** + * The maximum number of retries when a validation for switching failed. + */ + private int mAutoDataSwitchValidationMaxRetry; + + private @NonNull PhoneSignalStatus[] mPhonesSignalStatus; + + /** + * 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; + + 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; + } + @Override + public String toString() { + return "{phoneId=" + mPhone.getPhoneId() + + " score=" + mScore + " dataRegState=" + + NetworkRegistrationInfo.registrationStateToString(mDataRegState) + + " display=" + mDisplayInfo + " signalStrength=" + mSignalStrength.getLevel() + + "}"; + + } + } + + /** + * This is the callback used for listening events from {@link AutoDataSwitchController}. + */ + public abstract static class AutoDataSwitchControllerCallback { + /** + * Called when a target data phone is recommended by the controller. + * @param targetPhoneId The target phone Id. + * @param needValidation {@code true} if need a ping test to pass before switching. + */ + public abstract void onRequireValidation(int targetPhoneId, boolean needValidation); + + /** + * Called when a target data phone is demanded by the controller. + * @param targetPhoneId The target phone Id. + * @param reason The reason for the demand. + */ + public abstract void onRequireImmediatelySwitchToPhone(int targetPhoneId, + @AutoDataSwitchEvaluationReason int reason); + + /** + * Called when the controller asks to cancel any pending validation attempts because the + * environment is no longer suited for switching. + */ + public abstract void onRequireCancelAnyPendingAutoSwitchValidation(); + } + + /** + * @param context Context. + * @param looper Main looper. + * @param phoneSwitcher Phone switcher. + * @param phoneSwitcherCallback Callback for phone switcher to execute. + */ + public AutoDataSwitchController(@NonNull Context context, @NonNull Looper looper, + @NonNull PhoneSwitcher phoneSwitcher, + @NonNull AutoDataSwitchControllerCallback phoneSwitcherCallback) { + super(looper); + mContext = context; + mSubscriptionManagerService = SubscriptionManagerService.getInstance(); + mPhoneSwitcher = phoneSwitcher; + mPhoneSwitcherCallback = phoneSwitcherCallback; + readDeviceResourceConfig(); + int numActiveModems = PhoneFactory.getPhones().length; + mPhonesSignalStatus = new PhoneSignalStatus[numActiveModems]; + for (int phoneId = 0; phoneId < numActiveModems; phoneId++) { + registerAllEventsForPhone(phoneId); + } + } + + /** + * Called when active modem count changed, update all tracking events. + * @param numActiveModems The current number of active modems. + */ + public synchronized void onMultiSimConfigChanged(int numActiveModems) { + int oldActiveModems = mPhonesSignalStatus.length; + 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); + } + mPhonesSignalStatus = Arrays.copyOf(mPhonesSignalStatus, numActiveModems); + // Signal -> Dual + for (int phoneId = oldActiveModems; phoneId < numActiveModems; phoneId++) { + registerAllEventsForPhone(phoneId); + } + } + + /** + * Register all tracking events for a phone. + * @param phoneId The phone to register for all events. + */ + private void registerAllEventsForPhone(int phoneId) { + Phone phone = PhoneFactory.getPhone(phoneId); + if (phone != null) { + mPhonesSignalStatus[phoneId] = new PhoneSignalStatus(phone); + phone.getDisplayInfoController().registerForTelephonyDisplayInfoChanged( + this, EVENT_DISPLAY_INFO_CHANGED, phoneId); + phone.getSignalStrengthController().registerForSignalStrengthChanged( + this, EVENT_SIGNAL_STRENGTH_CHANGED, phoneId); + phone.getServiceStateTracker().registerForServiceStateChanged(this, + EVENT_SERVICE_STATE_CHANGED, phoneId); + } else { + loge("Unexpected null phone " + phoneId + " when register 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(); + mRequirePingTestBeforeSwitch = dataConfig.isPingTestBeforeAutoDataSwitchRequired(); + mAutoDataSwitchAvailabilityStabilityTimeThreshold = + dataConfig.getAutoDataSwitchAvailabilityStabilityTimeThreshold(); + mAutoDataSwitchValidationMaxRetry = + dataConfig.getAutoDataSwitchValidationMaxRetry(); + } + + @Override + public void handleMessage(@NonNull Message msg) { + AsyncResult ar; + int phoneId; + switch (msg.what) { + case EVENT_SERVICE_STATE_CHANGED: + ar = (AsyncResult) msg.obj; + phoneId = (int) ar.userObj; + onRegistrationStateChanged(phoneId); + break; + case EVENT_DISPLAY_INFO_CHANGED: + ar = (AsyncResult) msg.obj; + phoneId = (int) ar.userObj; + onDisplayInfoChanged(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; + log("require validation on phone " + targetPhoneId + + (needValidation ? "" : " no") + " need to pass"); + mPhoneSwitcherCallback.onRequireValidation(targetPhoneId, needValidation); + break; + default: + loge("Unexpected event " + msg.what); + } + } + + /** + * Called when registration state changed. + */ + private void onRegistrationStateChanged(int phoneId) { + Phone phone = PhoneFactory.getPhone(phoneId); + if (phone != null) { + int oldRegState = mPhonesSignalStatus[phoneId].mDataRegState; + int newRegState = phone.getServiceState() + .getNetworkRegistrationInfo( + NetworkRegistrationInfo.DOMAIN_PS, + AccessNetworkConstants.TRANSPORT_TYPE_WWAN) + .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."); + } + } 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 dataRegState == NetworkRegistrationInfo.REGISTRATION_STATE_HOME + || dataRegState == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING; + } + + /** + * Called when {@link TelephonyDisplayInfo} changed. This can happen when network types or + * override network types (5G NSA, 5G MMWAVE) change. + */ + private void onDisplayInfoChanged(int phoneId) { + Phone phone = PhoneFactory.getPhone(phoneId); + if (phone != null) { + TelephonyDisplayInfo displayInfo = phone.getDisplayInfoController() + .getTelephonyDisplayInfo(); + //TODO(b/260928808) + log("onDisplayInfoChanged:" + displayInfo); + } else { + loge("Unexpected null phone " + phoneId + " upon its display info changed"); + } + } + + /** + * Schedule for auto data switch evaluation. + * @param reason The reason for the evaluation. + */ + public void evaluateAutoDataSwitch(@AutoDataSwitchEvaluationReason int reason) { + long delayMs = reason == EVALUATION_REASON_RETRY_VALIDATION + ? mAutoDataSwitchAvailabilityStabilityTimeThreshold + << mAutoSwitchValidationFailedCount + : 0; + if (!hasMessages(EVENT_EVALUATE_AUTO_SWITCH)) { + sendMessageDelayed(obtainMessage(EVENT_EVALUATE_AUTO_SWITCH, reason), delayMs); + } + } + + /** + * Evaluate for auto data switch opportunity. + * If suitable to switch, check that the suitable state is stable(or switch immediately if user + * turned off settings). + * @param reason The reason for the evaluation. + */ + private void onEvaluateAutoDataSwitch(@AutoDataSwitchEvaluationReason int reason) { + // auto data switch feature is disabled. + if (mAutoDataSwitchAvailabilityStabilityTimeThreshold < 0) return; + int defaultDataSubId = mSubscriptionManagerService.getDefaultDataSubId(); + // check is valid DSDS + if (!isActiveSubId(defaultDataSubId) || mSubscriptionManagerService + .getActiveSubIdList(true).length <= 1) { + return; + } + Phone defaultDataPhone = PhoneFactory.getPhone(mSubscriptionManagerService.getPhoneId( + defaultDataSubId)); + if (defaultDataPhone == null) { + loge("onEvaluateAutoDataSwitch: cannot find the phone associated with default data" + + " subscription " + defaultDataSubId); + return; + } + int defaultDataPhoneId = defaultDataPhone.getPhoneId(); + int preferredPhoneId = mPhoneSwitcher.getPreferredDataPhoneId(); + log("onEvaluateAutoDataSwitch: defaultPhoneId: " + defaultDataPhoneId + + " preferredPhoneId: " + preferredPhoneId + + " reason: " + evaluationReasonToString(reason)); + if (preferredPhoneId == defaultDataPhoneId) { + // on default data sub + int candidatePhoneId = getSwitchCandidatePhoneId(defaultDataPhoneId); + if (candidatePhoneId != INVALID_PHONE_INDEX) { + startStabilityCheck(candidatePhoneId, mRequirePingTestBeforeSwitch); + } else { + cancelAnyPendingSwitch(); + } + } 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"); + return; + } + + if (!defaultDataPhone.isUserDataEnabled() || !backupDataPhone.isDataAllowed()) { + // immediately switch back if user disabled setting changes + mPhoneSwitcherCallback.onRequireImmediatelySwitchToPhone(DEFAULT_PHONE_INDEX, + EVALUATION_REASON_DATA_SETTINGS_CHANGED); + return; + } + + if (mDefaultNetworkIsOnNonCellular) { + log("onEvaluateAutoDataSwitch: Default network is active on nonCellular transport"); + startStabilityCheck(DEFAULT_PHONE_INDEX, false); + return; + } + + if (mPhonesSignalStatus[preferredPhoneId].mDataRegState + != NetworkRegistrationInfo.REGISTRATION_STATE_HOME) { + // backup phone lost its HOME registration + startStabilityCheck(DEFAULT_PHONE_INDEX, false); + return; + } + + if (isInService(mPhonesSignalStatus[defaultDataPhoneId].mDataRegState)) { + // default phone is back to service + startStabilityCheck(DEFAULT_PHONE_INDEX, mRequirePingTestBeforeSwitch); + return; + } + + // cancel any previous attempts of switching back to default phone + cancelAnyPendingSwitch(); + } + } + + /** + * Called when consider switching from primary default data sub to another data sub. + * @return the target subId if a suitable candidate is found, otherwise return + * {@link SubscriptionManager#INVALID_PHONE_INDEX} + */ + private int getSwitchCandidatePhoneId(int defaultPhoneId) { + Phone defaultDataPhone = PhoneFactory.getPhone(defaultPhoneId); + if (defaultDataPhone == null) { + log("getSwitchCandidatePhoneId: no sim loaded"); + return INVALID_PHONE_INDEX; + } + + if (!defaultDataPhone.isUserDataEnabled()) { + log("getSwitchCandidatePhoneId: user disabled data"); + return INVALID_PHONE_INDEX; + } + + if (mDefaultNetworkIsOnNonCellular) { + // Exists other active default transport + log("getSwitchCandidatePhoneId: Default network is active 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; + } + for (int phoneId = 0; phoneId < mPhonesSignalStatus.length; phoneId++) { + if (phoneId != defaultPhoneId) { + // 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; + } + } + } + } + return INVALID_PHONE_INDEX; + } + + /** + * 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. + * @param targetPhoneId the target phone Id. + * @param needValidation {@code true} if validation is needed. + */ + private void startStabilityCheck(int targetPhoneId, boolean needValidation) { + log("startAutoDataSwitchStabilityCheck: targetPhoneId=" + targetPhoneId + + " needValidation=" + needValidation); + if (!hasMessages(EVENT_MEETS_AUTO_DATA_SWITCH_STATE, needValidation)) { + sendMessageDelayed(obtainMessage(EVENT_MEETS_AUTO_DATA_SWITCH_STATE, targetPhoneId, + 0/*placeholder*/, + needValidation), + mAutoDataSwitchAvailabilityStabilityTimeThreshold); + } + } + + /** Auto data switch evaluation reason to string. */ + public static @NonNull String evaluationReasonToString( + @AutoDataSwitchEvaluationReason int reason) { + switch (reason) { + case EVALUATION_REASON_REGISTRATION_STATE_CHANGED: return "REGISTRATION_STATE_CHANGED"; + case EVALUATION_REASON_DISPLAY_INFO_CHANGED: return "DISPLAY_INFO_CHANGED"; + case EVALUATION_REASON_SIGNAL_STRENGTH_CHANGED: return "SIGNAL_STRENGTH_CHANGED"; + case EVALUATION_REASON_DEFAULT_NETWORK_CHANGED: return "DEFAULT_NETWORK_CHANGED"; + case EVALUATION_REASON_DATA_SETTINGS_CHANGED: return "DATA_SETTINGS_CHANGED"; + case EVALUATION_REASON_RETRY_VALIDATION: return "RETRY_VALIDATION"; + case EVALUATION_REASON_SIM_LOADED: return "SIM_LOADED"; + case EVALUATION_REASON_VOICE_CALL_END: return "VOICE_CALL_END"; + } + return "Unknown(" + reason + ")"; + } + + /** @return {@code true} if the sub is active. */ + private boolean isActiveSubId(int subId) { + SubscriptionInfoInternal subInfo = mSubscriptionManagerService + .getSubscriptionInfoInternal(subId); + return subInfo != null && subInfo.isActive(); + } + + /** + * Called when default network capabilities changed. If default network is active on + * non-cellular, switch back to the default data phone. If default network is lost, try to find + * another sub to switch to. + * @param networkCapabilities {@code null} indicates default network lost. + */ + public void updateDefaultNetworkCapabilities( + @Nullable NetworkCapabilities networkCapabilities) { + if (networkCapabilities != null) { + // Exists default network + mDefaultNetworkIsOnNonCellular = !networkCapabilities.hasTransport(TRANSPORT_CELLULAR); + if (mDefaultNetworkIsOnNonCellular + && isActiveSubId(mPhoneSwitcher.getAutoSelectedDataSubId())) { + log("default network is active on non cellular, switch back to default"); + evaluateAutoDataSwitch(EVALUATION_REASON_DEFAULT_NETWORK_CHANGED); + } + } else { + log("default network is lost, try to find another active sub to switch to"); + mDefaultNetworkIsOnNonCellular = false; + evaluateAutoDataSwitch(EVALUATION_REASON_DEFAULT_NETWORK_CHANGED); + } + } + + /** + * Cancel any auto switch attempts when the current environment is not suitable for auto switch. + */ + private void cancelAnyPendingSwitch() { + resetFailedCount(); + removeMessages(EVENT_MEETS_AUTO_DATA_SWITCH_STATE); + mPhoneSwitcherCallback.onRequireCancelAnyPendingAutoSwitchValidation(); + } + + /** + * Display a notification the first time auto data switch occurs. + * @param phoneId The phone Id of the current preferred phone. + * @param isDueToAutoSwitch {@code true} if the switch was due to auto data switch feature. + */ + public void displayAutoDataSwitchNotification(int phoneId, boolean isDueToAutoSwitch) { + NotificationManager notificationManager = (NotificationManager) + mContext.getSystemService(Context.NOTIFICATION_SERVICE); + if (mDisplayedNotification) { + // cancel posted notification if any exist + log("displayAutoDataSwitchNotification: canceling any notifications for phone " + + phoneId); + notificationManager.cancel(AUTO_DATA_SWITCH_NOTIFICATION_TAG, + AUTO_DATA_SWITCH_NOTIFICATION_ID); + return; + } + // proceed only the first time auto data switch occurs, which includes data during call + if (!isDueToAutoSwitch) { + return; + } + SubscriptionInfo subInfo = mSubscriptionManagerService + .getSubscriptionInfo(mSubscriptionManagerService.getSubId(phoneId)); + if (subInfo == null || subInfo.isOpportunistic()) { + loge("displayAutoDataSwitchNotification: phoneId=" + + phoneId + " unexpected subInfo " + subInfo); + return; + } + int subId = subInfo.getSubscriptionId(); + logl("displayAutoDataSwitchNotification: display for subId=" + subId); + // "Mobile network settings" screen / dialog + Intent intent = new Intent(Settings.ACTION_NETWORK_OPERATOR_SETTINGS); + final Bundle fragmentArgs = new Bundle(); + // Special contract for Settings to highlight permission row + fragmentArgs.putString(SETTINGS_EXTRA_FRAGMENT_ARG_KEY, AUTO_DATA_SWITCH_SETTING_R_ID); + intent.putExtra(Settings.EXTRA_SUB_ID, subId); + intent.putExtra(SETTINGS_EXTRA_SHOW_FRAGMENT_ARGUMENTS, fragmentArgs); + PendingIntent contentIntent = PendingIntent.getActivity( + mContext, subId, intent, PendingIntent.FLAG_IMMUTABLE); + + CharSequence activeCarrierName = subInfo.getDisplayName(); + CharSequence contentTitle = mContext.getString( + com.android.internal.R.string.auto_data_switch_title, activeCarrierName); + CharSequence contentText = mContext.getText( + com.android.internal.R.string.auto_data_switch_content); + + final Notification notif = new Notification.Builder(mContext) + .setContentTitle(contentTitle) + .setContentText(contentText) + .setSmallIcon(android.R.drawable.stat_sys_warning) + .setColor(mContext.getResources().getColor( + com.android.internal.R.color.system_notification_accent_color)) + .setChannelId(NotificationChannelController.CHANNEL_ID_MOBILE_DATA_STATUS) + .setContentIntent(contentIntent) + .setStyle(new Notification.BigTextStyle().bigText(contentText)) + .build(); + notificationManager.notify(AUTO_DATA_SWITCH_NOTIFICATION_TAG, + AUTO_DATA_SWITCH_NOTIFICATION_ID, notif); + mDisplayedNotification = true; + } + + /** Enable future switch retry again. Called when switch condition changed. */ + public void resetFailedCount() { + mAutoSwitchValidationFailedCount = 0; + } + + /** + * Called when skipped switch due to validation failed. Schedule retry to switch again. + */ + public void evaluateRetryOnValidationFailed() { + if (mAutoSwitchValidationFailedCount < mAutoDataSwitchValidationMaxRetry) { + evaluateAutoDataSwitch(EVALUATION_REASON_RETRY_VALIDATION); + mAutoSwitchValidationFailedCount++; + } else { + logl("evaluateRetryOnValidationFailed: reached max auto switch retry count " + + mAutoDataSwitchValidationMaxRetry); + mAutoSwitchValidationFailedCount = 0; + } + } + + /** + * Log debug messages. + * @param s debug messages + */ + private void log(@NonNull String s) { + Rlog.d(LOG_TAG, s); + } + + /** + * Log error messages. + * @param s error messages + */ + private void loge(@NonNull String s) { + Rlog.e(LOG_TAG, s); + } + + /** + * Log debug messages and also log into the local log. + * @param s debug messages + */ + private void logl(@NonNull String s) { + log(s); + mLocalLog.log(s); + } + + /** + * Dump the state of DataNetworkController + * + * @param fd File descriptor + * @param printWriter Print writer + * @param args Arguments + */ + public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) { + IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " "); + pw.println("AutoDataSwitchController:"); + pw.increaseIndent(); + pw.println("mAutoDataSwitchValidationMaxRetry=" + mAutoDataSwitchValidationMaxRetry + + " mAutoSwitchValidationFailedCount=" + mAutoSwitchValidationFailedCount); + pw.println("mRequirePingTestBeforeDataSwitch=" + mRequirePingTestBeforeSwitch); + pw.println("mAutoDataSwitchAvailabilityStabilityTimeThreshold=" + + mAutoDataSwitchAvailabilityStabilityTimeThreshold); + pw.increaseIndent(); + for (PhoneSignalStatus status: mPhonesSignalStatus) { + pw.println(status); + } + pw.decreaseIndent(); + mLocalLog.dump(fd, pw, args); + pw.decreaseIndent(); + } +} |