• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.internal.telephony.data;
18 
19 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
20 import static android.telephony.SubscriptionManager.DEFAULT_PHONE_INDEX;
21 import static android.telephony.SubscriptionManager.INVALID_PHONE_INDEX;
22 
23 import android.annotation.IntDef;
24 import android.annotation.NonNull;
25 import android.annotation.Nullable;
26 import android.app.Notification;
27 import android.app.NotificationManager;
28 import android.app.PendingIntent;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.net.NetworkCapabilities;
32 import android.os.AsyncResult;
33 import android.os.Bundle;
34 import android.os.Handler;
35 import android.os.Looper;
36 import android.os.Message;
37 import android.provider.Settings;
38 import android.telephony.AccessNetworkConstants;
39 import android.telephony.NetworkRegistrationInfo;
40 import android.telephony.SignalStrength;
41 import android.telephony.SubscriptionInfo;
42 import android.telephony.SubscriptionManager;
43 import android.telephony.TelephonyDisplayInfo;
44 import android.util.IndentingPrintWriter;
45 import android.util.LocalLog;
46 
47 import com.android.internal.telephony.Phone;
48 import com.android.internal.telephony.PhoneFactory;
49 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
50 import com.android.internal.telephony.subscription.SubscriptionManagerService;
51 import com.android.internal.telephony.util.NotificationChannelController;
52 import com.android.telephony.Rlog;
53 
54 
55 import java.io.FileDescriptor;
56 import java.io.PrintWriter;
57 import java.lang.annotation.Retention;
58 import java.lang.annotation.RetentionPolicy;
59 import java.util.Arrays;
60 
61 /**
62  * Recommend a data phone to use based on its availability.
63  */
64 public class AutoDataSwitchController extends Handler {
65     /** Registration state changed. */
66     public static final int EVALUATION_REASON_REGISTRATION_STATE_CHANGED = 1;
67     /** Telephony Display Info changed. */
68     public static final int EVALUATION_REASON_DISPLAY_INFO_CHANGED = 2;
69     /** Signal Strength changed. */
70     public static final int EVALUATION_REASON_SIGNAL_STRENGTH_CHANGED = 3;
71     /** Default network capabilities changed or lost. */
72     public static final int EVALUATION_REASON_DEFAULT_NETWORK_CHANGED = 4;
73     /** Data enabled settings changed. */
74     public static final int EVALUATION_REASON_DATA_SETTINGS_CHANGED = 5;
75     /** Retry due to previous validation failed. */
76     public static final int EVALUATION_REASON_RETRY_VALIDATION = 6;
77     /** Sim loaded which means slot mapping became available. */
78     public static final int EVALUATION_REASON_SIM_LOADED = 7;
79     /** Voice call ended. */
80     public static final int EVALUATION_REASON_VOICE_CALL_END = 8;
81     @Retention(RetentionPolicy.SOURCE)
82     @IntDef(prefix = "EVALUATION_REASON_",
83             value = {EVALUATION_REASON_REGISTRATION_STATE_CHANGED,
84                     EVALUATION_REASON_DISPLAY_INFO_CHANGED,
85                     EVALUATION_REASON_SIGNAL_STRENGTH_CHANGED,
86                     EVALUATION_REASON_DEFAULT_NETWORK_CHANGED,
87                     EVALUATION_REASON_DATA_SETTINGS_CHANGED,
88                     EVALUATION_REASON_RETRY_VALIDATION,
89                     EVALUATION_REASON_SIM_LOADED,
90                     EVALUATION_REASON_VOICE_CALL_END})
91     public @interface AutoDataSwitchEvaluationReason {}
92 
93     private static final String LOG_TAG = "ADSC";
94 
95     /** Event for service state changed. */
96     private static final int EVENT_SERVICE_STATE_CHANGED = 1;
97     /** Event for display info changed. This is for getting 5G NSA or mmwave information. */
98     private static final int EVENT_DISPLAY_INFO_CHANGED = 2;
99     /** Event for evaluate auto data switch opportunity. */
100     private static final int EVENT_EVALUATE_AUTO_SWITCH = 3;
101     /** Event for signal strength changed. */
102     private static final int EVENT_SIGNAL_STRENGTH_CHANGED = 4;
103     /** Event indicates the switch state is stable, proceed to validation as the next step. */
104     private static final int EVENT_MEETS_AUTO_DATA_SWITCH_STATE = 5;
105 
106     /** Fragment "key" argument passed thru {@link #SETTINGS_EXTRA_SHOW_FRAGMENT_ARGUMENTS} */
107     private static final String SETTINGS_EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
108     /**
109      * When starting this activity, this extra can also be specified to supply a Bundle of arguments
110      * to pass to that fragment when it is instantiated during the initial creation of the activity.
111      */
112     private static final String SETTINGS_EXTRA_SHOW_FRAGMENT_ARGUMENTS =
113             ":settings:show_fragment_args";
114     /** The resource ID of the auto data switch fragment in settings. **/
115     private static final String AUTO_DATA_SWITCH_SETTING_R_ID = "auto_data_switch";
116     /** Notification tag **/
117     private static final String AUTO_DATA_SWITCH_NOTIFICATION_TAG = "auto_data_switch";
118     /** Notification ID **/
119     private static final int AUTO_DATA_SWITCH_NOTIFICATION_ID = 1;
120 
121     private final @NonNull LocalLog mLocalLog = new LocalLog(128);
122     private final @NonNull Context mContext;
123     private final @NonNull SubscriptionManagerService mSubscriptionManagerService;
124     private final @NonNull PhoneSwitcher mPhoneSwitcher;
125     private final @NonNull AutoDataSwitchControllerCallback mPhoneSwitcherCallback;
126     private boolean mDefaultNetworkIsOnNonCellular = false;
127     /** {@code true} if we've displayed the notification the first time auto switch occurs **/
128     private boolean mDisplayedNotification = false;
129     /**
130      * Time threshold in ms to define a internet connection status to be stable(e.g. out of service,
131      * in service, wifi is the default active network.etc), while -1 indicates auto switch
132      * feature disabled.
133      */
134     private long mAutoDataSwitchAvailabilityStabilityTimeThreshold = -1;
135     /**
136      * {@code true} if requires ping test before switching preferred data modem; otherwise, switch
137      * even if ping test fails.
138      */
139     private boolean mRequirePingTestBeforeSwitch = true;
140     /** The count of consecutive auto switch validation failure **/
141     private int mAutoSwitchValidationFailedCount = 0;
142     /**
143      * The maximum number of retries when a validation for switching failed.
144      */
145     private int mAutoDataSwitchValidationMaxRetry;
146 
147     private @NonNull PhoneSignalStatus[] mPhonesSignalStatus;
148 
149     /**
150      * To track the signal status of a phone in order to evaluate whether it's a good candidate to
151      * switch to.
152      */
153     private static class PhoneSignalStatus {
154         private @NonNull Phone mPhone;
155         private @NetworkRegistrationInfo.RegistrationState int mDataRegState =
156                 NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING;
157         private @NonNull TelephonyDisplayInfo mDisplayInfo;
158         private @NonNull SignalStrength mSignalStrength;
159 
160         private int mScore;
161 
PhoneSignalStatus(@onNull Phone phone)162         private PhoneSignalStatus(@NonNull Phone phone) {
163             this.mPhone = phone;
164             this.mDisplayInfo = phone.getDisplayInfoController().getTelephonyDisplayInfo();
165             this.mSignalStrength = phone.getSignalStrength();
166         }
updateScore()167         private int updateScore() {
168             // TODO: score = inservice? dcm.getscore() : 0
169             return mScore;
170         }
171         @Override
toString()172         public String toString() {
173             return "{phoneId=" + mPhone.getPhoneId()
174                     + " score=" + mScore + " dataRegState="
175                     + NetworkRegistrationInfo.registrationStateToString(mDataRegState)
176                     + " display=" + mDisplayInfo + " signalStrength=" + mSignalStrength.getLevel()
177                     + "}";
178 
179         }
180     }
181 
182     /**
183      * This is the callback used for listening events from {@link AutoDataSwitchController}.
184      */
185     public abstract static class AutoDataSwitchControllerCallback {
186         /**
187          * Called when a target data phone is recommended by the controller.
188          * @param targetPhoneId The target phone Id.
189          * @param needValidation {@code true} if need a ping test to pass before switching.
190          */
onRequireValidation(int targetPhoneId, boolean needValidation)191         public abstract void onRequireValidation(int targetPhoneId, boolean needValidation);
192 
193         /**
194          * Called when a target data phone is demanded by the controller.
195          * @param targetPhoneId The target phone Id.
196          * @param reason The reason for the demand.
197          */
onRequireImmediatelySwitchToPhone(int targetPhoneId, @AutoDataSwitchEvaluationReason int reason)198         public abstract void onRequireImmediatelySwitchToPhone(int targetPhoneId,
199                 @AutoDataSwitchEvaluationReason int reason);
200 
201         /**
202          * Called when the controller asks to cancel any pending validation attempts because the
203          * environment is no longer suited for switching.
204          */
onRequireCancelAnyPendingAutoSwitchValidation()205         public abstract void onRequireCancelAnyPendingAutoSwitchValidation();
206     }
207 
208     /**
209      * @param context Context.
210      * @param looper Main looper.
211      * @param phoneSwitcher Phone switcher.
212      * @param phoneSwitcherCallback Callback for phone switcher to execute.
213      */
AutoDataSwitchController(@onNull Context context, @NonNull Looper looper, @NonNull PhoneSwitcher phoneSwitcher, @NonNull AutoDataSwitchControllerCallback phoneSwitcherCallback)214     public AutoDataSwitchController(@NonNull Context context, @NonNull Looper looper,
215             @NonNull PhoneSwitcher phoneSwitcher,
216             @NonNull AutoDataSwitchControllerCallback phoneSwitcherCallback) {
217         super(looper);
218         mContext = context;
219         mSubscriptionManagerService = SubscriptionManagerService.getInstance();
220         mPhoneSwitcher = phoneSwitcher;
221         mPhoneSwitcherCallback = phoneSwitcherCallback;
222         readDeviceResourceConfig();
223         int numActiveModems = PhoneFactory.getPhones().length;
224         mPhonesSignalStatus = new PhoneSignalStatus[numActiveModems];
225         for (int phoneId = 0; phoneId < numActiveModems; phoneId++) {
226             registerAllEventsForPhone(phoneId);
227         }
228     }
229 
230     /**
231      * Called when active modem count changed, update all tracking events.
232      * @param numActiveModems The current number of active modems.
233      */
onMultiSimConfigChanged(int numActiveModems)234     public synchronized void onMultiSimConfigChanged(int numActiveModems) {
235         int oldActiveModems = mPhonesSignalStatus.length;
236         if (oldActiveModems == numActiveModems) return;
237         // Dual -> Single
238         for (int phoneId = numActiveModems; phoneId < oldActiveModems; phoneId++) {
239             Phone phone = mPhonesSignalStatus[phoneId].mPhone;
240             phone.getDisplayInfoController().unregisterForTelephonyDisplayInfoChanged(this);
241             phone.getSignalStrengthController().unregisterForSignalStrengthChanged(this);
242             phone.getServiceStateTracker().unregisterForServiceStateChanged(this);
243         }
244         mPhonesSignalStatus = Arrays.copyOf(mPhonesSignalStatus, numActiveModems);
245         // Signal -> Dual
246         for (int phoneId = oldActiveModems; phoneId < numActiveModems; phoneId++) {
247             registerAllEventsForPhone(phoneId);
248         }
249     }
250 
251     /**
252      * Register all tracking events for a phone.
253      * @param phoneId The phone to register for all events.
254      */
registerAllEventsForPhone(int phoneId)255     private void registerAllEventsForPhone(int phoneId) {
256         Phone phone = PhoneFactory.getPhone(phoneId);
257         if (phone != null) {
258             mPhonesSignalStatus[phoneId] = new PhoneSignalStatus(phone);
259             phone.getDisplayInfoController().registerForTelephonyDisplayInfoChanged(
260                     this, EVENT_DISPLAY_INFO_CHANGED, phoneId);
261             phone.getSignalStrengthController().registerForSignalStrengthChanged(
262                     this, EVENT_SIGNAL_STRENGTH_CHANGED, phoneId);
263             phone.getServiceStateTracker().registerForServiceStateChanged(this,
264                     EVENT_SERVICE_STATE_CHANGED, phoneId);
265         } else {
266             loge("Unexpected null phone " + phoneId + " when register all events");
267         }
268     }
269 
270     /**
271      * Read the default device config from any default phone because the resource config are per
272      * device. No need to register callback for the same reason.
273      */
readDeviceResourceConfig()274     private void readDeviceResourceConfig() {
275         Phone phone = PhoneFactory.getDefaultPhone();
276         DataConfigManager dataConfig = phone.getDataNetworkController().getDataConfigManager();
277         mRequirePingTestBeforeSwitch = dataConfig.isPingTestBeforeAutoDataSwitchRequired();
278         mAutoDataSwitchAvailabilityStabilityTimeThreshold =
279                 dataConfig.getAutoDataSwitchAvailabilityStabilityTimeThreshold();
280         mAutoDataSwitchValidationMaxRetry =
281                 dataConfig.getAutoDataSwitchValidationMaxRetry();
282     }
283 
284     @Override
handleMessage(@onNull Message msg)285     public void handleMessage(@NonNull Message msg) {
286         AsyncResult ar;
287         int phoneId;
288         switch (msg.what) {
289             case EVENT_SERVICE_STATE_CHANGED:
290                 ar = (AsyncResult) msg.obj;
291                 phoneId = (int) ar.userObj;
292                 onRegistrationStateChanged(phoneId);
293                 break;
294             case EVENT_DISPLAY_INFO_CHANGED:
295                 ar = (AsyncResult) msg.obj;
296                 phoneId = (int) ar.userObj;
297                 onDisplayInfoChanged(phoneId);
298                 break;
299             case EVENT_EVALUATE_AUTO_SWITCH:
300                 int reason = (int) msg.obj;
301                 onEvaluateAutoDataSwitch(reason);
302                 break;
303             case EVENT_MEETS_AUTO_DATA_SWITCH_STATE:
304                 int targetPhoneId = msg.arg1;
305                 boolean needValidation = (boolean) msg.obj;
306                 log("require validation on phone " + targetPhoneId
307                         + (needValidation ? "" : " no") + " need to pass");
308                 mPhoneSwitcherCallback.onRequireValidation(targetPhoneId, needValidation);
309                 break;
310             default:
311                 loge("Unexpected event " + msg.what);
312         }
313     }
314 
315     /**
316      * Called when registration state changed.
317      */
onRegistrationStateChanged(int phoneId)318     private void onRegistrationStateChanged(int phoneId) {
319         Phone phone = PhoneFactory.getPhone(phoneId);
320         if (phone != null) {
321             int oldRegState = mPhonesSignalStatus[phoneId].mDataRegState;
322             int newRegState = phone.getServiceState()
323                     .getNetworkRegistrationInfo(
324                             NetworkRegistrationInfo.DOMAIN_PS,
325                             AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
326                     .getRegistrationState();
327             if (newRegState != oldRegState) {
328                 mPhonesSignalStatus[phoneId].mDataRegState = newRegState;
329                 log("onRegistrationStateChanged: phone " + phoneId + " "
330                         + NetworkRegistrationInfo.registrationStateToString(oldRegState)
331                         + " -> "
332                         + NetworkRegistrationInfo.registrationStateToString(newRegState));
333                 evaluateAutoDataSwitch(EVALUATION_REASON_REGISTRATION_STATE_CHANGED);
334             } else {
335                 log("onRegistrationStateChanged: no change.");
336             }
337         } else {
338             loge("Unexpected null phone " + phoneId + " upon its registration state changed");
339         }
340     }
341 
342     /**
343      * @return {@code true} if the phone state is considered in service.
344      */
isInService(@etworkRegistrationInfo.RegistrationState int dataRegState)345     private boolean isInService(@NetworkRegistrationInfo.RegistrationState int dataRegState) {
346         return dataRegState == NetworkRegistrationInfo.REGISTRATION_STATE_HOME
347                 || dataRegState == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING;
348     }
349 
350     /**
351      * Called when {@link TelephonyDisplayInfo} changed. This can happen when network types or
352      * override network types (5G NSA, 5G MMWAVE) change.
353      */
onDisplayInfoChanged(int phoneId)354     private void onDisplayInfoChanged(int phoneId) {
355         Phone phone = PhoneFactory.getPhone(phoneId);
356         if (phone != null) {
357             TelephonyDisplayInfo displayInfo = phone.getDisplayInfoController()
358                     .getTelephonyDisplayInfo();
359             //TODO(b/260928808)
360             log("onDisplayInfoChanged:" + displayInfo);
361         } else {
362             loge("Unexpected null phone " + phoneId + " upon its display info changed");
363         }
364     }
365 
366     /**
367      * Schedule for auto data switch evaluation.
368      * @param reason The reason for the evaluation.
369      */
evaluateAutoDataSwitch(@utoDataSwitchEvaluationReason int reason)370     public void evaluateAutoDataSwitch(@AutoDataSwitchEvaluationReason int reason) {
371         long delayMs = reason == EVALUATION_REASON_RETRY_VALIDATION
372                 ? mAutoDataSwitchAvailabilityStabilityTimeThreshold
373                 << mAutoSwitchValidationFailedCount
374                 : 0;
375         if (!hasMessages(EVENT_EVALUATE_AUTO_SWITCH)) {
376             sendMessageDelayed(obtainMessage(EVENT_EVALUATE_AUTO_SWITCH, reason), delayMs);
377         }
378     }
379 
380     /**
381      * Evaluate for auto data switch opportunity.
382      * If suitable to switch, check that the suitable state is stable(or switch immediately if user
383      * turned off settings).
384      * @param reason The reason for the evaluation.
385      */
onEvaluateAutoDataSwitch(@utoDataSwitchEvaluationReason int reason)386     private void onEvaluateAutoDataSwitch(@AutoDataSwitchEvaluationReason int reason) {
387         // auto data switch feature is disabled.
388         if (mAutoDataSwitchAvailabilityStabilityTimeThreshold < 0) return;
389         int defaultDataSubId = mSubscriptionManagerService.getDefaultDataSubId();
390         // check is valid DSDS
391         if (!isActiveSubId(defaultDataSubId) || mSubscriptionManagerService
392                 .getActiveSubIdList(true).length <= 1) {
393             return;
394         }
395         Phone defaultDataPhone = PhoneFactory.getPhone(mSubscriptionManagerService.getPhoneId(
396                 defaultDataSubId));
397         if (defaultDataPhone == null) {
398             loge("onEvaluateAutoDataSwitch: cannot find the phone associated with default data"
399                     + " subscription " + defaultDataSubId);
400             return;
401         }
402         int defaultDataPhoneId = defaultDataPhone.getPhoneId();
403         int preferredPhoneId = mPhoneSwitcher.getPreferredDataPhoneId();
404         log("onEvaluateAutoDataSwitch: defaultPhoneId: " + defaultDataPhoneId
405                 + " preferredPhoneId: " + preferredPhoneId
406                 + " reason: " + evaluationReasonToString(reason));
407         if (preferredPhoneId == defaultDataPhoneId) {
408             // on default data sub
409             int candidatePhoneId = getSwitchCandidatePhoneId(defaultDataPhoneId);
410             if (candidatePhoneId != INVALID_PHONE_INDEX) {
411                 startStabilityCheck(candidatePhoneId, mRequirePingTestBeforeSwitch);
412             } else {
413                 cancelAnyPendingSwitch();
414             }
415         } else {
416             // on backup data sub
417             Phone backupDataPhone = PhoneFactory.getPhone(preferredPhoneId);
418             if (backupDataPhone == null) {
419                 loge("onEvaluateAutoDataSwitch: Unexpected null phone " + preferredPhoneId
420                         + " as the current active data phone");
421                 return;
422             }
423 
424             if (!defaultDataPhone.isUserDataEnabled() || !backupDataPhone.isDataAllowed()) {
425                 // immediately switch back if user disabled setting changes
426                 mPhoneSwitcherCallback.onRequireImmediatelySwitchToPhone(DEFAULT_PHONE_INDEX,
427                         EVALUATION_REASON_DATA_SETTINGS_CHANGED);
428                 return;
429             }
430 
431             if (mDefaultNetworkIsOnNonCellular) {
432                 log("onEvaluateAutoDataSwitch: Default network is active on nonCellular transport");
433                 startStabilityCheck(DEFAULT_PHONE_INDEX, false);
434                 return;
435             }
436 
437             if (mPhonesSignalStatus[preferredPhoneId].mDataRegState
438                     != NetworkRegistrationInfo.REGISTRATION_STATE_HOME) {
439                 // backup phone lost its HOME registration
440                 startStabilityCheck(DEFAULT_PHONE_INDEX, false);
441                 return;
442             }
443 
444             if (isInService(mPhonesSignalStatus[defaultDataPhoneId].mDataRegState)) {
445                 // default phone is back to service
446                 startStabilityCheck(DEFAULT_PHONE_INDEX, mRequirePingTestBeforeSwitch);
447                 return;
448             }
449 
450             // cancel any previous attempts of switching back to default phone
451             cancelAnyPendingSwitch();
452         }
453     }
454 
455     /**
456      * Called when consider switching from primary default data sub to another data sub.
457      * @return the target subId if a suitable candidate is found, otherwise return
458      * {@link SubscriptionManager#INVALID_PHONE_INDEX}
459      */
getSwitchCandidatePhoneId(int defaultPhoneId)460     private int getSwitchCandidatePhoneId(int defaultPhoneId) {
461         Phone defaultDataPhone = PhoneFactory.getPhone(defaultPhoneId);
462         if (defaultDataPhone == null) {
463             log("getSwitchCandidatePhoneId: no sim loaded");
464             return INVALID_PHONE_INDEX;
465         }
466 
467         if (!defaultDataPhone.isUserDataEnabled()) {
468             log("getSwitchCandidatePhoneId: user disabled data");
469             return INVALID_PHONE_INDEX;
470         }
471 
472         if (mDefaultNetworkIsOnNonCellular) {
473             // Exists other active default transport
474             log("getSwitchCandidatePhoneId: Default network is active on non-cellular transport");
475             return INVALID_PHONE_INDEX;
476         }
477 
478         // check whether primary and secondary signal status are worth switching
479         if (isInService(mPhonesSignalStatus[defaultPhoneId].mDataRegState)) {
480             log("getSwitchCandidatePhoneId: DDS is in service");
481             return INVALID_PHONE_INDEX;
482         }
483         for (int phoneId = 0; phoneId < mPhonesSignalStatus.length; phoneId++) {
484             if (phoneId != defaultPhoneId) {
485                 // the alternative phone must have HOME availability
486                 if (mPhonesSignalStatus[phoneId].mDataRegState
487                         == NetworkRegistrationInfo.REGISTRATION_STATE_HOME) {
488                     log("getSwitchCandidatePhoneId: found phone " + phoneId
489                             + " in HOME service");
490                     Phone secondaryDataPhone = PhoneFactory.getPhone(phoneId);
491                     if (secondaryDataPhone != null && // check auto switch feature enabled
492                             secondaryDataPhone.isDataAllowed()) {
493                         return phoneId;
494                     }
495                 }
496             }
497         }
498         return INVALID_PHONE_INDEX;
499     }
500 
501     /**
502      * Called when the current environment suits auto data switch.
503      * Start pre-switch validation if the current environment suits auto data switch for
504      * {@link #mAutoDataSwitchAvailabilityStabilityTimeThreshold} MS.
505      * @param targetPhoneId the target phone Id.
506      * @param needValidation {@code true} if validation is needed.
507      */
startStabilityCheck(int targetPhoneId, boolean needValidation)508     private void startStabilityCheck(int targetPhoneId, boolean needValidation) {
509         log("startAutoDataSwitchStabilityCheck: targetPhoneId=" + targetPhoneId
510                 + " needValidation=" + needValidation);
511         if (!hasMessages(EVENT_MEETS_AUTO_DATA_SWITCH_STATE, needValidation)) {
512             sendMessageDelayed(obtainMessage(EVENT_MEETS_AUTO_DATA_SWITCH_STATE, targetPhoneId,
513                             0/*placeholder*/,
514                             needValidation),
515                     mAutoDataSwitchAvailabilityStabilityTimeThreshold);
516         }
517     }
518 
519     /** Auto data switch evaluation reason to string. */
evaluationReasonToString( @utoDataSwitchEvaluationReason int reason)520     public static @NonNull String evaluationReasonToString(
521             @AutoDataSwitchEvaluationReason int reason) {
522         switch (reason) {
523             case EVALUATION_REASON_REGISTRATION_STATE_CHANGED: return "REGISTRATION_STATE_CHANGED";
524             case EVALUATION_REASON_DISPLAY_INFO_CHANGED: return "DISPLAY_INFO_CHANGED";
525             case EVALUATION_REASON_SIGNAL_STRENGTH_CHANGED: return "SIGNAL_STRENGTH_CHANGED";
526             case EVALUATION_REASON_DEFAULT_NETWORK_CHANGED: return "DEFAULT_NETWORK_CHANGED";
527             case EVALUATION_REASON_DATA_SETTINGS_CHANGED: return "DATA_SETTINGS_CHANGED";
528             case EVALUATION_REASON_RETRY_VALIDATION: return "RETRY_VALIDATION";
529             case EVALUATION_REASON_SIM_LOADED: return "SIM_LOADED";
530             case EVALUATION_REASON_VOICE_CALL_END: return "VOICE_CALL_END";
531         }
532         return "Unknown(" + reason + ")";
533     }
534 
535     /** @return {@code true} if the sub is active. */
isActiveSubId(int subId)536     private boolean isActiveSubId(int subId) {
537         SubscriptionInfoInternal subInfo = mSubscriptionManagerService
538                 .getSubscriptionInfoInternal(subId);
539         return subInfo != null && subInfo.isActive();
540     }
541 
542     /**
543      * Called when default network capabilities changed. If default network is active on
544      * non-cellular, switch back to the default data phone. If default network is lost, try to find
545      * another sub to switch to.
546      * @param networkCapabilities {@code null} indicates default network lost.
547      */
updateDefaultNetworkCapabilities( @ullable NetworkCapabilities networkCapabilities)548     public void updateDefaultNetworkCapabilities(
549             @Nullable NetworkCapabilities networkCapabilities) {
550         if (networkCapabilities != null) {
551             // Exists default network
552             mDefaultNetworkIsOnNonCellular = !networkCapabilities.hasTransport(TRANSPORT_CELLULAR);
553             if (mDefaultNetworkIsOnNonCellular
554                     && isActiveSubId(mPhoneSwitcher.getAutoSelectedDataSubId())) {
555                 log("default network is active on non cellular, switch back to default");
556                 evaluateAutoDataSwitch(EVALUATION_REASON_DEFAULT_NETWORK_CHANGED);
557             }
558         } else {
559             log("default network is lost, try to find another active sub to switch to");
560             mDefaultNetworkIsOnNonCellular = false;
561             evaluateAutoDataSwitch(EVALUATION_REASON_DEFAULT_NETWORK_CHANGED);
562         }
563     }
564 
565     /**
566      * Cancel any auto switch attempts when the current environment is not suitable for auto switch.
567      */
cancelAnyPendingSwitch()568     private void cancelAnyPendingSwitch() {
569         resetFailedCount();
570         removeMessages(EVENT_MEETS_AUTO_DATA_SWITCH_STATE);
571         mPhoneSwitcherCallback.onRequireCancelAnyPendingAutoSwitchValidation();
572     }
573 
574     /**
575      * Display a notification the first time auto data switch occurs.
576      * @param phoneId The phone Id of the current preferred phone.
577      * @param isDueToAutoSwitch {@code true} if the switch was due to auto data switch feature.
578      */
displayAutoDataSwitchNotification(int phoneId, boolean isDueToAutoSwitch)579     public void displayAutoDataSwitchNotification(int phoneId, boolean isDueToAutoSwitch) {
580         NotificationManager notificationManager = (NotificationManager)
581                 mContext.getSystemService(Context.NOTIFICATION_SERVICE);
582         if (mDisplayedNotification) {
583             // cancel posted notification if any exist
584             log("displayAutoDataSwitchNotification: canceling any notifications for phone "
585                     + phoneId);
586             notificationManager.cancel(AUTO_DATA_SWITCH_NOTIFICATION_TAG,
587                     AUTO_DATA_SWITCH_NOTIFICATION_ID);
588             return;
589         }
590         // proceed only the first time auto data switch occurs, which includes data during call
591         if (!isDueToAutoSwitch) {
592             return;
593         }
594         SubscriptionInfo subInfo = mSubscriptionManagerService
595                 .getSubscriptionInfo(mSubscriptionManagerService.getSubId(phoneId));
596         if (subInfo == null || subInfo.isOpportunistic()) {
597             loge("displayAutoDataSwitchNotification: phoneId="
598                     + phoneId + " unexpected subInfo " + subInfo);
599             return;
600         }
601         int subId = subInfo.getSubscriptionId();
602         logl("displayAutoDataSwitchNotification: display for subId=" + subId);
603         // "Mobile network settings" screen / dialog
604         Intent intent = new Intent(Settings.ACTION_NETWORK_OPERATOR_SETTINGS);
605         final Bundle fragmentArgs = new Bundle();
606         // Special contract for Settings to highlight permission row
607         fragmentArgs.putString(SETTINGS_EXTRA_FRAGMENT_ARG_KEY, AUTO_DATA_SWITCH_SETTING_R_ID);
608         intent.putExtra(Settings.EXTRA_SUB_ID, subId);
609         intent.putExtra(SETTINGS_EXTRA_SHOW_FRAGMENT_ARGUMENTS, fragmentArgs);
610         PendingIntent contentIntent = PendingIntent.getActivity(
611                 mContext, subId, intent, PendingIntent.FLAG_IMMUTABLE);
612 
613         CharSequence activeCarrierName = subInfo.getDisplayName();
614         CharSequence contentTitle = mContext.getString(
615                 com.android.internal.R.string.auto_data_switch_title, activeCarrierName);
616         CharSequence contentText = mContext.getText(
617                 com.android.internal.R.string.auto_data_switch_content);
618 
619         final Notification notif = new Notification.Builder(mContext)
620                 .setContentTitle(contentTitle)
621                 .setContentText(contentText)
622                 .setSmallIcon(android.R.drawable.stat_sys_warning)
623                 .setColor(mContext.getResources().getColor(
624                         com.android.internal.R.color.system_notification_accent_color))
625                 .setChannelId(NotificationChannelController.CHANNEL_ID_MOBILE_DATA_STATUS)
626                 .setContentIntent(contentIntent)
627                 .setStyle(new Notification.BigTextStyle().bigText(contentText))
628                 .build();
629         notificationManager.notify(AUTO_DATA_SWITCH_NOTIFICATION_TAG,
630                 AUTO_DATA_SWITCH_NOTIFICATION_ID, notif);
631         mDisplayedNotification = true;
632     }
633 
634     /** Enable future switch retry again. Called when switch condition changed. */
resetFailedCount()635     public void resetFailedCount() {
636         mAutoSwitchValidationFailedCount = 0;
637     }
638 
639     /**
640      * Called when skipped switch due to validation failed. Schedule retry to switch again.
641      */
evaluateRetryOnValidationFailed()642     public void evaluateRetryOnValidationFailed() {
643         if (mAutoSwitchValidationFailedCount < mAutoDataSwitchValidationMaxRetry) {
644             evaluateAutoDataSwitch(EVALUATION_REASON_RETRY_VALIDATION);
645             mAutoSwitchValidationFailedCount++;
646         } else {
647             logl("evaluateRetryOnValidationFailed: reached max auto switch retry count "
648                     + mAutoDataSwitchValidationMaxRetry);
649             mAutoSwitchValidationFailedCount = 0;
650         }
651     }
652 
653     /**
654      * Log debug messages.
655      * @param s debug messages
656      */
log(@onNull String s)657     private void log(@NonNull String s) {
658         Rlog.d(LOG_TAG, s);
659     }
660 
661     /**
662      * Log error messages.
663      * @param s error messages
664      */
loge(@onNull String s)665     private void loge(@NonNull String s) {
666         Rlog.e(LOG_TAG, s);
667     }
668 
669     /**
670      * Log debug messages and also log into the local log.
671      * @param s debug messages
672      */
logl(@onNull String s)673     private void logl(@NonNull String s) {
674         log(s);
675         mLocalLog.log(s);
676     }
677 
678     /**
679      * Dump the state of DataNetworkController
680      *
681      * @param fd File descriptor
682      * @param printWriter Print writer
683      * @param args Arguments
684      */
dump(FileDescriptor fd, PrintWriter printWriter, String[] args)685     public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
686         IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
687         pw.println("AutoDataSwitchController:");
688         pw.increaseIndent();
689         pw.println("mAutoDataSwitchValidationMaxRetry=" + mAutoDataSwitchValidationMaxRetry
690                 + " mAutoSwitchValidationFailedCount=" + mAutoSwitchValidationFailedCount);
691         pw.println("mRequirePingTestBeforeDataSwitch=" + mRequirePingTestBeforeSwitch);
692         pw.println("mAutoDataSwitchAvailabilityStabilityTimeThreshold="
693                 + mAutoDataSwitchAvailabilityStabilityTimeThreshold);
694         pw.increaseIndent();
695         for (PhoneSignalStatus status: mPhonesSignalStatus) {
696             pw.println(status);
697         }
698         pw.decreaseIndent();
699         mLocalLog.dump(fd, pw, args);
700         pw.decreaseIndent();
701     }
702 }
703