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.AlarmManager;
27 import android.app.Notification;
28 import android.app.NotificationManager;
29 import android.app.PendingIntent;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.net.NetworkCapabilities;
33 import android.os.AsyncResult;
34 import android.os.Bundle;
35 import android.os.Handler;
36 import android.os.Looper;
37 import android.os.Message;
38 import android.os.SystemClock;
39 import android.provider.Settings;
40 import android.telephony.AccessNetworkConstants;
41 import android.telephony.NetworkRegistrationInfo;
42 import android.telephony.NetworkRegistrationInfo.RegistrationState;
43 import android.telephony.ServiceState;
44 import android.telephony.SignalStrength;
45 import android.telephony.SubscriptionInfo;
46 import android.telephony.TelephonyDisplayInfo;
47 import android.util.IndentingPrintWriter;
48 import android.util.LocalLog;
49 
50 import com.android.internal.telephony.Phone;
51 import com.android.internal.telephony.PhoneFactory;
52 import com.android.internal.telephony.flags.FeatureFlags;
53 import com.android.internal.telephony.flags.FeatureFlagsImpl;
54 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
55 import com.android.internal.telephony.subscription.SubscriptionManagerService;
56 import com.android.internal.telephony.util.NotificationChannelController;
57 import com.android.telephony.Rlog;
58 
59 
60 import java.io.FileDescriptor;
61 import java.io.PrintWriter;
62 import java.lang.annotation.Retention;
63 import java.lang.annotation.RetentionPolicy;
64 import java.util.Arrays;
65 import java.util.HashMap;
66 import java.util.Map;
67 import java.util.Set;
68 import java.util.concurrent.TimeUnit;
69 import java.util.stream.Collectors;
70 
71 /**
72  * Recommend a data phone to use based on its availability.
73  */
74 public class AutoDataSwitchController extends Handler {
75     /** Registration state changed. */
76     public static final int EVALUATION_REASON_REGISTRATION_STATE_CHANGED = 1;
77     /** Telephony Display Info changed. */
78     public static final int EVALUATION_REASON_DISPLAY_INFO_CHANGED = 2;
79     /** Signal Strength changed. */
80     public static final int EVALUATION_REASON_SIGNAL_STRENGTH_CHANGED = 3;
81     /** Default network capabilities changed or lost. */
82     public static final int EVALUATION_REASON_DEFAULT_NETWORK_CHANGED = 4;
83     /** Data enabled settings changed. */
84     public static final int EVALUATION_REASON_DATA_SETTINGS_CHANGED = 5;
85     /** Retry due to previous validation failed. */
86     public static final int EVALUATION_REASON_RETRY_VALIDATION = 6;
87     /** Sim loaded which means slot mapping became available. */
88     public static final int EVALUATION_REASON_SIM_LOADED = 7;
89     /** Voice call ended. */
90     public static final int EVALUATION_REASON_VOICE_CALL_END = 8;
91     @Retention(RetentionPolicy.SOURCE)
92     @IntDef(prefix = "EVALUATION_REASON_",
93             value = {EVALUATION_REASON_REGISTRATION_STATE_CHANGED,
94                     EVALUATION_REASON_DISPLAY_INFO_CHANGED,
95                     EVALUATION_REASON_SIGNAL_STRENGTH_CHANGED,
96                     EVALUATION_REASON_DEFAULT_NETWORK_CHANGED,
97                     EVALUATION_REASON_DATA_SETTINGS_CHANGED,
98                     EVALUATION_REASON_RETRY_VALIDATION,
99                     EVALUATION_REASON_SIM_LOADED,
100                     EVALUATION_REASON_VOICE_CALL_END})
101     public @interface AutoDataSwitchEvaluationReason {}
102 
103     private static final String LOG_TAG = "ADSC";
104 
105     /** Event for service state changed. */
106     private static final int EVENT_SERVICE_STATE_CHANGED = 1;
107     /** Event for display info changed. This is for getting 5G NSA or mmwave information. */
108     private static final int EVENT_DISPLAY_INFO_CHANGED = 2;
109     /** Event for evaluate auto data switch opportunity. */
110     private static final int EVENT_EVALUATE_AUTO_SWITCH = 3;
111     /** Event for signal strength changed. */
112     private static final int EVENT_SIGNAL_STRENGTH_CHANGED = 4;
113     /** Event indicates the switch state is stable, proceed to validation as the next step. */
114     private static final int EVENT_STABILITY_CHECK_PASSED = 5;
115     /** Event when subscriptions changed. */
116     private static final int EVENT_SUBSCRIPTIONS_CHANGED = 6;
117 
118     /** Fragment "key" argument passed thru {@link #SETTINGS_EXTRA_SHOW_FRAGMENT_ARGUMENTS} */
119     private static final String SETTINGS_EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
120     /**
121      * When starting this activity, this extra can also be specified to supply a Bundle of arguments
122      * to pass to that fragment when it is instantiated during the initial creation of the activity.
123      */
124     private static final String SETTINGS_EXTRA_SHOW_FRAGMENT_ARGUMENTS =
125             ":settings:show_fragment_args";
126     /** The resource ID of the auto data switch fragment in settings. **/
127     private static final String AUTO_DATA_SWITCH_SETTING_R_ID = "auto_data_switch";
128     /** Notification tag **/
129     private static final String AUTO_DATA_SWITCH_NOTIFICATION_TAG = "auto_data_switch";
130     /** Notification ID **/
131     private static final int AUTO_DATA_SWITCH_NOTIFICATION_ID = 1;
132 
133     /**
134      * The threshold of long timer, longer than or equal to which we use alarm manager to schedule
135      * instead of handler.
136      */
137     private static final long RETRY_LONG_DELAY_TIMER_THRESHOLD_MILLIS = TimeUnit
138             .MINUTES.toMillis(1);
139 
140     @NonNull
141     private final LocalLog mLocalLog = new LocalLog(128);
142     @NonNull
143     private final Context mContext;
144     @NonNull
145     private static FeatureFlags sFeatureFlags = new FeatureFlagsImpl();
146     @NonNull
147     private final SubscriptionManagerService mSubscriptionManagerService;
148     @NonNull
149     private final PhoneSwitcher mPhoneSwitcher;
150     @NonNull
151     private final AutoDataSwitchControllerCallback mPhoneSwitcherCallback;
152     @NonNull
153     private final AlarmManager mAlarmManager;
154     /** A map of a scheduled event to its associated extra for action when the event fires off. */
155     @NonNull
156     private final Map<Integer, Object> mScheduledEventsToExtras;
157     /** A map of an event to its associated alarm listener callback for when the event fires off. */
158     @NonNull
159     private final Map<Integer, AlarmManager.OnAlarmListener> mEventsToAlarmListener;
160     /**
161      * Event extras for checking environment stability.
162      * @param targetPhoneId The target phone Id to switch to when the stability check pass.
163      * @param isForPerformance Whether the switch is due to RAT/signal strength performance.
164      * @param needValidation Whether ping test needs to pass.
165      */
StabilityEventExtra(int targetPhoneId, boolean isForPerformance, boolean needValidation)166     private record StabilityEventExtra(int targetPhoneId, boolean isForPerformance,
167                                boolean needValidation) {}
168 
169     /**
170      * Event extras for evaluating switch environment.
171      * @param evaluateReason The reason that triggers the evaluation.
172      */
EvaluateEventExtra(@utoDataSwitchEvaluationReason int evaluateReason)173     private record EvaluateEventExtra(@AutoDataSwitchEvaluationReason int evaluateReason) {}
174     private boolean mDefaultNetworkIsOnNonCellular = false;
175     /** {@code true} if we've displayed the notification the first time auto switch occurs **/
176     private boolean mDisplayedNotification = false;
177     /**
178      * Configurable time threshold in ms to define an internet connection status to be stable(e.g.
179      * out of service, in service, wifi is the default active network.etc), while -1 indicates auto
180      * switch feature disabled.
181      */
182     private long mAutoDataSwitchAvailabilityStabilityTimeThreshold = -1;
183     /**
184      * Configurable time threshold in ms to define an internet connection performance status to be
185      * stable (e.g. LTE + 4 signal strength, UMTS + 2 signal strength), while -1 indicates
186      * auto switch feature based on RAT/SS is disabled.
187      */
188     private long mAutoDataSwitchPerformanceStabilityTimeThreshold = -1;
189     /**
190      * The tolerated gap of score for auto data switch decision, larger than which the device will
191      * switch to the SIM with higher score. If 0, the device will always switch to the higher score
192      * SIM. If < 0, the network type and signal strength based auto switch is disabled.
193      */
194     private int mScoreTolerance = -1;
195     /**
196      * {@code true} if requires ping test before switching preferred data modem; otherwise, switch
197      * even if ping test fails.
198      */
199     private boolean mRequirePingTestBeforeSwitch = true;
200     /**
201      * TODO: remove after V.
202      * To indicate whether allow using roaming nDDS if user enabled its roaming when the DDS is not
203      * usable(OOS or disabled roaming)
204      */
205     private boolean mAllowNddsRoaming = true;
206     /** The count of consecutive auto switch validation failure **/
207     private int mAutoSwitchValidationFailedCount = 0;
208     /**
209      * The maximum number of retries when a validation for switching failed.
210      */
211     private int mAutoDataSwitchValidationMaxRetry;
212 
213     /** The signal status of phones, where index corresponds to phone Id. */
214     @NonNull
215     private PhoneSignalStatus[] mPhonesSignalStatus;
216     /**
217      * The phone Id of the pending switching phone. Used for pruning frequent switch evaluation.
218      */
219     private int mSelectedTargetPhoneId = INVALID_PHONE_INDEX;
220 
221     /**
222      * To track the signal status of a phone in order to evaluate whether it's a good candidate to
223      * switch to.
224      */
225     private static class PhoneSignalStatus {
226         /**
227          * How preferred the current phone is.
228          */
229         enum UsableState {
230             HOME(2),
231             ROAMING_ENABLED(1),
232             NON_TERRESTRIAL(0),
233             NOT_USABLE(-1);
234             /**
235              * The higher the score, the more preferred.
236              * HOME is preferred over ROAMING assuming roaming is metered.
237              */
238             final int mScore;
UsableState(int score)239             UsableState(int score) {
240                 this.mScore = score;
241             }
242         }
243         /** The phone */
244         @NonNull private final Phone mPhone;
245         /** Data registration state of the phone */
246         @RegistrationState private int mDataRegState;
247         /** Current Telephony display info of the phone */
248         @NonNull private TelephonyDisplayInfo mDisplayInfo;
249         /** Signal strength of the phone */
250         @NonNull private SignalStrength mSignalStrength;
251         /** {@code true} if this slot is listening for events. */
252         private boolean mListeningForEvents;
PhoneSignalStatus(@onNull Phone phone)253         private PhoneSignalStatus(@NonNull Phone phone) {
254             this.mPhone = phone;
255             this.mDataRegState = phone.getServiceState().getNetworkRegistrationInfo(
256                             NetworkRegistrationInfo.DOMAIN_PS,
257                             AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
258                     .getRegistrationState();
259             this.mDisplayInfo = phone.getDisplayInfoController().getTelephonyDisplayInfo();
260             this.mSignalStrength = phone.getSignalStrength();
261         }
262 
263         /**
264          * @return the current score of this phone. 0 indicates out of service and it will never be
265          * selected as the secondary data candidate.
266          */
getRatSignalScore()267         private int getRatSignalScore() {
268             return isInService(mDataRegState)
269                     ? mPhone.getDataNetworkController().getDataConfigManager()
270                             .getAutoDataSwitchScore(mDisplayInfo, mSignalStrength) : 0;
271         }
272 
273         /**
274          * @return The current usable state of the phone.
275          */
getUsableState()276         private UsableState getUsableState() {
277             ServiceState serviceState = mPhone.getServiceState();
278             boolean isUsingNonTerrestrialNetwork = sFeatureFlags.carrierEnabledSatelliteFlag()
279                     && (serviceState != null) && serviceState.isUsingNonTerrestrialNetwork();
280 
281             return switch (mDataRegState) {
282                 case NetworkRegistrationInfo.REGISTRATION_STATE_HOME -> {
283                     if (isUsingNonTerrestrialNetwork) {
284                         yield UsableState.NON_TERRESTRIAL;
285                     }
286                     yield UsableState.HOME;
287                 }
288                 case NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING -> {
289                     if (mPhone.getDataRoamingEnabled()) {
290                         if (isUsingNonTerrestrialNetwork) {
291                             yield UsableState.NON_TERRESTRIAL;
292                         }
293                         yield UsableState.ROAMING_ENABLED;
294                     }
295                     yield UsableState.NOT_USABLE;
296                 }
297                 default -> UsableState.NOT_USABLE;
298             };
299         }
300 
301         @Override
302         public String toString() {
303             return "{phone " + mPhone.getPhoneId()
304                     + " score=" + getRatSignalScore() + " dataRegState="
305                     + NetworkRegistrationInfo.registrationStateToString(mDataRegState)
306                     + " " + getUsableState() + " " + mDisplayInfo
307                     + " signalStrength=" + mSignalStrength.getLevel()
308                     + " listeningForEvents=" + mListeningForEvents
309                     + "}";
310 
311         }
312     }
313 
314     /**
315      * This is the callback used for listening events from {@link AutoDataSwitchController}.
316      */
317     public abstract static class AutoDataSwitchControllerCallback {
318         /**
319          * Called when a target data phone is recommended by the controller.
320          * @param targetPhoneId The target phone Id.
321          * @param needValidation {@code true} if need a ping test to pass before switching.
322          */
323         public abstract void onRequireValidation(int targetPhoneId, boolean needValidation);
324 
325         /**
326          * Called when a target data phone is demanded by the controller.
327          * @param targetPhoneId The target phone Id.
328          * @param reason The reason for the demand.
329          */
330         public abstract void onRequireImmediatelySwitchToPhone(int targetPhoneId,
331                 @AutoDataSwitchEvaluationReason int reason);
332 
333         /**
334          * Called when the controller asks to cancel any pending validation attempts because the
335          * environment is no longer suited for switching.
336          */
337         public abstract void onRequireCancelAnyPendingAutoSwitchValidation();
338     }
339 
340     /**
341      * @param context Context.
342      * @param looper Main looper.
343      * @param phoneSwitcher Phone switcher.
344      * @param phoneSwitcherCallback Callback for phone switcher to execute.
345      */
346     public AutoDataSwitchController(@NonNull Context context, @NonNull Looper looper,
347             @NonNull PhoneSwitcher phoneSwitcher, @NonNull FeatureFlags featureFlags,
348             @NonNull AutoDataSwitchControllerCallback phoneSwitcherCallback) {
349         super(looper);
350         mContext = context;
351         sFeatureFlags = featureFlags;
352         mPhoneSwitcherCallback = phoneSwitcherCallback;
353         mAlarmManager = context.getSystemService(AlarmManager.class);
354         mScheduledEventsToExtras = new HashMap<>();
355         mEventsToAlarmListener = new HashMap<>();
356         mSubscriptionManagerService = SubscriptionManagerService.getInstance();
357         mPhoneSwitcher = phoneSwitcher;
358         readDeviceResourceConfig();
359         int numActiveModems = PhoneFactory.getPhones().length;
360         mPhonesSignalStatus = new PhoneSignalStatus[numActiveModems];
361         // Listening on all slots on boot up to make sure nothing missed. Later the tracking is
362         // pruned upon subscriptions changed.
363         for (int phoneId = 0; phoneId < numActiveModems; phoneId++) {
364             registerAllEventsForPhone(phoneId);
365         }
366     }
367 
368     /**
369      * Called when active modem count changed, update all tracking events.
370      * @param numActiveModems The current number of active modems.
371      */
372     public synchronized void onMultiSimConfigChanged(int numActiveModems) {
373         int oldActiveModems = mPhonesSignalStatus.length;
374         if (oldActiveModems == numActiveModems) return;
375         // Dual -> Single
376         for (int phoneId = numActiveModems; phoneId < oldActiveModems; phoneId++) {
377             unregisterAllEventsForPhone(phoneId);
378         }
379         mPhonesSignalStatus = Arrays.copyOf(mPhonesSignalStatus, numActiveModems);
380         // Signal -> Dual
381         for (int phoneId = oldActiveModems; phoneId < numActiveModems; phoneId++) {
382             registerAllEventsForPhone(phoneId);
383         }
384         logl("onMultiSimConfigChanged: " + Arrays.toString(mPhonesSignalStatus));
385     }
386 
387     /** Notify subscriptions changed. */
388     public void notifySubscriptionsMappingChanged() {
389         sendEmptyMessage(EVENT_SUBSCRIPTIONS_CHANGED);
390     }
391 
392     /**
393      * On subscription changed, register/unregister events on phone Id slot that has active/inactive
394      * sub to reduce unnecessary tracking.
395      */
396     private void onSubscriptionsChanged() {
397         Set<Integer> activePhoneIds = Arrays.stream(mSubscriptionManagerService
398                 .getActiveSubIdList(true /*visibleOnly*/))
399                 .map(mSubscriptionManagerService::getPhoneId)
400                 .boxed()
401                 .collect(Collectors.toSet());
402         // Track events only if there are at least two active visible subscriptions.
403         if (activePhoneIds.size() < 2) activePhoneIds.clear();
404         boolean changed = false;
405         for (int phoneId = 0; phoneId < mPhonesSignalStatus.length; phoneId++) {
406             if (activePhoneIds.contains(phoneId)
407                     && !mPhonesSignalStatus[phoneId].mListeningForEvents) {
408                 registerAllEventsForPhone(phoneId);
409                 changed = true;
410             } else if (!activePhoneIds.contains(phoneId)
411                     && mPhonesSignalStatus[phoneId].mListeningForEvents) {
412                 unregisterAllEventsForPhone(phoneId);
413                 changed = true;
414             }
415         }
416         if (changed) logl("onSubscriptionChanged: " + Arrays.toString(mPhonesSignalStatus));
417     }
418 
419     /**
420      * Register all tracking events for a phone.
421      * @param phoneId The phone to register for all events.
422      */
423     private void registerAllEventsForPhone(int phoneId) {
424         Phone phone = PhoneFactory.getPhone(phoneId);
425         if (phone != null && isActiveModemPhone(phoneId)) {
426             mPhonesSignalStatus[phoneId] = new PhoneSignalStatus(phone);
427             phone.getDisplayInfoController().registerForTelephonyDisplayInfoChanged(
428                     this, EVENT_DISPLAY_INFO_CHANGED, phoneId);
429             phone.getSignalStrengthController().registerForSignalStrengthChanged(
430                     this, EVENT_SIGNAL_STRENGTH_CHANGED, phoneId);
431             phone.getServiceStateTracker().registerForServiceStateChanged(this,
432                     EVENT_SERVICE_STATE_CHANGED, phoneId);
433             mPhonesSignalStatus[phoneId].mListeningForEvents = true;
434         } else {
435             loge("Unexpected null phone " + phoneId + " when register all events");
436         }
437     }
438 
439     /**
440      * Unregister all tracking events for a phone.
441      * @param phoneId The phone to unregister for all events.
442      */
443     private void unregisterAllEventsForPhone(int phoneId) {
444         if (isActiveModemPhone(phoneId)) {
445             Phone phone = mPhonesSignalStatus[phoneId].mPhone;
446             phone.getDisplayInfoController().unregisterForTelephonyDisplayInfoChanged(this);
447             phone.getSignalStrengthController().unregisterForSignalStrengthChanged(this);
448             phone.getServiceStateTracker().unregisterForServiceStateChanged(this);
449             mPhonesSignalStatus[phoneId].mListeningForEvents = false;
450         } else {
451             loge("Unexpected out of bound phone " + phoneId + " when unregister all events");
452         }
453     }
454 
455     /**
456      * Read the default device config from any default phone because the resource config are per
457      * device. No need to register callback for the same reason.
458      */
459     private void readDeviceResourceConfig() {
460         Phone phone = PhoneFactory.getDefaultPhone();
461         DataConfigManager dataConfig = phone.getDataNetworkController().getDataConfigManager();
462         mScoreTolerance =  dataConfig.getAutoDataSwitchScoreTolerance();
463         mRequirePingTestBeforeSwitch = dataConfig.isPingTestBeforeAutoDataSwitchRequired();
464         mAllowNddsRoaming = dataConfig.doesAutoDataSwitchAllowRoaming();
465         mAutoDataSwitchAvailabilityStabilityTimeThreshold =
466                 dataConfig.getAutoDataSwitchAvailabilityStabilityTimeThreshold();
467         mAutoDataSwitchPerformanceStabilityTimeThreshold =
468                 dataConfig.getAutoDataSwitchPerformanceStabilityTimeThreshold();
469         mAutoDataSwitchValidationMaxRetry =
470                 dataConfig.getAutoDataSwitchValidationMaxRetry();
471     }
472 
473     @Override
474     public void handleMessage(@NonNull Message msg) {
475         AsyncResult ar;
476         Object obj;
477         int phoneId;
478         switch (msg.what) {
479             case EVENT_SERVICE_STATE_CHANGED:
480                 ar = (AsyncResult) msg.obj;
481                 phoneId = (int) ar.userObj;
482                 onServiceStateChanged(phoneId);
483                 break;
484             case EVENT_DISPLAY_INFO_CHANGED:
485                 ar = (AsyncResult) msg.obj;
486                 phoneId = (int) ar.userObj;
487                 onDisplayInfoChanged(phoneId);
488                 break;
489             case EVENT_SIGNAL_STRENGTH_CHANGED:
490                 ar = (AsyncResult) msg.obj;
491                 phoneId = (int) ar.userObj;
492                 onSignalStrengthChanged(phoneId);
493                 break;
494             case EVENT_EVALUATE_AUTO_SWITCH:
495                 obj = mScheduledEventsToExtras.get(EVENT_EVALUATE_AUTO_SWITCH);
496                 if (obj instanceof EvaluateEventExtra extra) {
497                     mScheduledEventsToExtras.remove(EVENT_EVALUATE_AUTO_SWITCH);
498                     onEvaluateAutoDataSwitch(extra.evaluateReason);
499                 }
500                 break;
501             case EVENT_STABILITY_CHECK_PASSED:
502                 obj = mScheduledEventsToExtras.get(EVENT_STABILITY_CHECK_PASSED);
503                 if (obj instanceof StabilityEventExtra extra) {
504                     int targetPhoneId = extra.targetPhoneId;
505                     boolean needValidation = extra.needValidation;
506                     log("require validation on phone " + targetPhoneId
507                             + (needValidation ? "" : " no") + " need to pass");
508                     mScheduledEventsToExtras.remove(EVENT_STABILITY_CHECK_PASSED);
509                     mPhoneSwitcherCallback.onRequireValidation(targetPhoneId, needValidation);
510                 }
511                 break;
512             case EVENT_SUBSCRIPTIONS_CHANGED:
513                 onSubscriptionsChanged();
514                 break;
515             default:
516                 loge("Unexpected event " + msg.what);
517         }
518     }
519 
520     /**
521      * Called when registration state changed.
522      */
523     private void onServiceStateChanged(int phoneId) {
524         Phone phone = PhoneFactory.getPhone(phoneId);
525         if (phone != null && isActiveModemPhone(phoneId)) {
526             int oldRegState = mPhonesSignalStatus[phoneId].mDataRegState;
527             int newRegState = phone.getServiceState()
528                     .getNetworkRegistrationInfo(
529                             NetworkRegistrationInfo.DOMAIN_PS,
530                             AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
531                     .getRegistrationState();
532             if (newRegState != oldRegState) {
533                 mPhonesSignalStatus[phoneId].mDataRegState = newRegState;
534                 if (isInService(oldRegState) != isInService(newRegState)
535                         || isHomeService(oldRegState) != isHomeService(newRegState)) {
536                     log("onServiceStateChanged: phone " + phoneId + " "
537                             + NetworkRegistrationInfo.registrationStateToString(oldRegState)
538                             + " -> "
539                             + NetworkRegistrationInfo.registrationStateToString(newRegState));
540                     evaluateAutoDataSwitch(EVALUATION_REASON_REGISTRATION_STATE_CHANGED);
541                 }
542             }
543         } else {
544             loge("Unexpected null phone " + phoneId + " upon its registration state changed");
545         }
546     }
547 
548     /** @return {@code true} if the phone state is considered in service. */
549     private static boolean isInService(@RegistrationState int dataRegState) {
550         return dataRegState == NetworkRegistrationInfo.REGISTRATION_STATE_HOME
551                 || dataRegState == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING;
552     }
553 
554     /** @return {@code true} if the phone state is in home service. */
555     private static boolean isHomeService(@RegistrationState int dataRegState) {
556         return dataRegState == NetworkRegistrationInfo.REGISTRATION_STATE_HOME;
557     }
558 
559     /**
560      * Called when {@link TelephonyDisplayInfo} changed. This can happen when network types or
561      * override network types (5G NSA, 5G MMWAVE) change.
562      * @param phoneId The phone that changed.
563      */
564     private void onDisplayInfoChanged(int phoneId) {
565         Phone phone = PhoneFactory.getPhone(phoneId);
566         if (phone != null && isActiveModemPhone(phoneId)) {
567             TelephonyDisplayInfo displayInfo = phone.getDisplayInfoController()
568                     .getTelephonyDisplayInfo();
569             mPhonesSignalStatus[phoneId].mDisplayInfo = displayInfo;
570             if (getHigherScoreCandidatePhoneId() != mSelectedTargetPhoneId) {
571                 log("onDisplayInfoChanged: phone " + phoneId + " " + displayInfo);
572                 evaluateAutoDataSwitch(EVALUATION_REASON_DISPLAY_INFO_CHANGED);
573             }
574         } else {
575             loge("Unexpected null phone " + phoneId + " upon its display info changed");
576         }
577     }
578 
579     /**
580      * Called when {@link SignalStrength} changed.
581      * @param phoneId The phone that changed.
582      */
583     private void onSignalStrengthChanged(int phoneId) {
584         Phone phone = PhoneFactory.getPhone(phoneId);
585         if (phone != null && isActiveModemPhone(phoneId)) {
586             SignalStrength newSignalStrength = phone.getSignalStrength();
587             SignalStrength oldSignalStrength = mPhonesSignalStatus[phoneId].mSignalStrength;
588             if (oldSignalStrength.getLevel() != newSignalStrength.getLevel()) {
589                 mPhonesSignalStatus[phoneId].mSignalStrength = newSignalStrength;
590                 if (getHigherScoreCandidatePhoneId() != mSelectedTargetPhoneId) {
591                     log("onSignalStrengthChanged: phone " + phoneId + " "
592                             + oldSignalStrength.getLevel() + "->" + newSignalStrength.getLevel());
593                     evaluateAutoDataSwitch(EVALUATION_REASON_SIGNAL_STRENGTH_CHANGED);
594                 }
595             }
596         } else {
597             loge("Unexpected null phone " + phoneId + " upon its signal strength changed");
598         }
599     }
600 
601     /**
602      * Called as a preliminary check for the frequent signal/display info change.
603      * @return The phone Id if found a candidate phone with higher signal score, or the DDS has
604      * an equal score.
605      */
606     private int getHigherScoreCandidatePhoneId() {
607         int preferredPhoneId = mPhoneSwitcher.getPreferredDataPhoneId();
608         int ddsPhoneId = mSubscriptionManagerService.getPhoneId(
609                 mSubscriptionManagerService.getDefaultDataSubId());
610         if (isActiveModemPhone(preferredPhoneId) && isActiveModemPhone(ddsPhoneId)) {
611             int currentScore = mPhonesSignalStatus[preferredPhoneId].getRatSignalScore();
612             for (int phoneId = 0; phoneId < mPhonesSignalStatus.length; phoneId++) {
613                 if (phoneId == preferredPhoneId) continue;
614                 int candidateScore = mPhonesSignalStatus[phoneId].getRatSignalScore();
615                 if ((candidateScore - currentScore) > mScoreTolerance
616                         // Also reevaluate if DDS has the same score as the current phone.
617                         || (candidateScore >= currentScore && phoneId == ddsPhoneId)) {
618                     return phoneId;
619                 }
620             }
621         }
622         return INVALID_PHONE_INDEX;
623     }
624 
625     /**
626      * Schedule for auto data switch evaluation.
627      * @param reason The reason for the evaluation.
628      */
629     public void evaluateAutoDataSwitch(@AutoDataSwitchEvaluationReason int reason) {
630         long delayMs = reason == EVALUATION_REASON_RETRY_VALIDATION
631                 ? mAutoDataSwitchAvailabilityStabilityTimeThreshold
632                 << mAutoSwitchValidationFailedCount
633                 : 0;
634         if (!mScheduledEventsToExtras.containsKey(EVENT_EVALUATE_AUTO_SWITCH)) {
635             scheduleEventWithTimer(EVENT_EVALUATE_AUTO_SWITCH, new EvaluateEventExtra(reason),
636                     delayMs);
637         }
638     }
639 
640     /**
641      * Evaluate for auto data switch opportunity.
642      * If suitable to switch, check that the suitable state is stable(or switch immediately if user
643      * turned off settings).
644      * @param reason The reason for the evaluation.
645      */
646     private void onEvaluateAutoDataSwitch(@AutoDataSwitchEvaluationReason int reason) {
647         // auto data switch feature is disabled.
648         if (mAutoDataSwitchAvailabilityStabilityTimeThreshold < 0) return;
649         int defaultDataSubId = mSubscriptionManagerService.getDefaultDataSubId();
650         // check is valid DSDS
651         if (mSubscriptionManagerService.getActiveSubIdList(true).length < 2) return;
652         int defaultDataPhoneId = mSubscriptionManagerService.getPhoneId(
653                 defaultDataSubId);
654         Phone defaultDataPhone = PhoneFactory.getPhone(defaultDataPhoneId);
655         if (defaultDataPhone == null) {
656             loge("onEvaluateAutoDataSwitch: cannot find the phone associated with default data"
657                     + " subscription " + defaultDataSubId);
658             return;
659         }
660 
661         int preferredPhoneId = mPhoneSwitcher.getPreferredDataPhoneId();
662         StringBuilder debugMessage = new StringBuilder("onEvaluateAutoDataSwitch:");
663         debugMessage.append(" defaultPhoneId: ").append(defaultDataPhoneId)
664                 .append(" preferredPhoneId: ").append(preferredPhoneId)
665                 .append(", reason: ").append(evaluationReasonToString(reason));
666         if (preferredPhoneId == defaultDataPhoneId) {
667             // on default data sub
668             StabilityEventExtra res = evaluateAnyCandidateToUse(defaultDataPhoneId, debugMessage);
669             log(debugMessage.toString());
670             if (res.targetPhoneId != INVALID_PHONE_INDEX) {
671                 mSelectedTargetPhoneId = res.targetPhoneId;
672                 startStabilityCheck(res.targetPhoneId, res.isForPerformance, res.needValidation);
673             } else {
674                 cancelAnyPendingSwitch();
675             }
676         } else {
677             // on backup data sub
678             Phone backupDataPhone = PhoneFactory.getPhone(preferredPhoneId);
679             if (backupDataPhone == null || !isActiveModemPhone(preferredPhoneId)) {
680                 loge(debugMessage.append(" Unexpected null phone ").append(preferredPhoneId)
681                         .append(" as the current active data phone").toString());
682                 return;
683             }
684 
685             DataEvaluation internetEvaluation;
686             if (sFeatureFlags.autoDataSwitchUsesDataEnabled()) {
687                 if (!defaultDataPhone.isUserDataEnabled()) {
688                     mPhoneSwitcherCallback.onRequireImmediatelySwitchToPhone(DEFAULT_PHONE_INDEX,
689                             EVALUATION_REASON_DATA_SETTINGS_CHANGED);
690                     log(debugMessage.append(
691                             ", immediately back to default as user turns off default").toString());
692                     return;
693                 } else if (!(internetEvaluation = backupDataPhone.getDataNetworkController()
694                         .getInternetEvaluation(false/*ignoreExistingNetworks*/))
695                         .isSubsetOf(DataEvaluation.DataDisallowedReason.NOT_IN_SERVICE)) {
696                     mPhoneSwitcherCallback.onRequireImmediatelySwitchToPhone(
697                             DEFAULT_PHONE_INDEX, EVALUATION_REASON_DATA_SETTINGS_CHANGED);
698                     log(debugMessage.append(
699                                     ", immediately back to default because backup ")
700                             .append(internetEvaluation).toString());
701                     return;
702                 }
703             } else {
704                 if (!defaultDataPhone.isUserDataEnabled() || !backupDataPhone.isDataAllowed()) {
705                     mPhoneSwitcherCallback.onRequireImmediatelySwitchToPhone(DEFAULT_PHONE_INDEX,
706                             EVALUATION_REASON_DATA_SETTINGS_CHANGED);
707                     log(debugMessage.append(
708                             ", immediately back to default as user turns off settings").toString());
709                     return;
710                 }
711             }
712 
713             boolean backToDefault = false;
714             boolean isForPerformance = false;
715             boolean needValidation = true;
716 
717             if (isNddsRoamingEnabled()) {
718                 if (mDefaultNetworkIsOnNonCellular) {
719                     debugMessage.append(", back to default as default network")
720                             .append(" is active on nonCellular transport");
721                     backToDefault = true;
722                     needValidation = false;
723                 } else {
724                     PhoneSignalStatus.UsableState defaultUsableState =
725                             mPhonesSignalStatus[defaultDataPhoneId].getUsableState();
726                     PhoneSignalStatus.UsableState currentUsableState =
727                             mPhonesSignalStatus[preferredPhoneId].getUsableState();
728 
729                     boolean isCurrentUsable = currentUsableState.mScore
730                             > PhoneSignalStatus.UsableState.NOT_USABLE.mScore;
731 
732                     if (currentUsableState.mScore < defaultUsableState.mScore) {
733                         debugMessage.append(", back to default phone ").append(preferredPhoneId)
734                                 .append(" : ").append(defaultUsableState)
735                                 .append(" , backup phone: ").append(currentUsableState);
736 
737                         backToDefault = true;
738                         // Require validation if the current preferred phone is usable.
739                         needValidation = isCurrentUsable && mRequirePingTestBeforeSwitch;
740                     } else if (defaultUsableState.mScore == currentUsableState.mScore) {
741                         debugMessage.append(", default phone ").append(preferredPhoneId)
742                                 .append(" : ").append(defaultUsableState)
743                                 .append(" , backup phone: ").append(currentUsableState);
744 
745                         if (isCurrentUsable) {
746                             // Both phones are usable.
747                             if (isRatSignalStrengthBasedSwitchEnabled()) {
748                                 int defaultScore = mPhonesSignalStatus[defaultDataPhoneId]
749                                         .getRatSignalScore();
750                                 int currentScore = mPhonesSignalStatus[preferredPhoneId]
751                                         .getRatSignalScore();
752                                 if (defaultScore >= currentScore) {
753                                     debugMessage
754                                             .append(", back to default for higher or equal score ")
755                                             .append(defaultScore).append(" versus current ")
756                                             .append(currentScore);
757                                     backToDefault = true;
758                                     isForPerformance = true;
759                                     needValidation = mRequirePingTestBeforeSwitch;
760                                 }
761                             } else {
762                                 // Only OOS/in service switch is enabled, switch back.
763                                 debugMessage.append(", back to default as it's usable. ");
764                                 backToDefault = true;
765                                 needValidation = mRequirePingTestBeforeSwitch;
766                             }
767                         } else {
768                             debugMessage.append(", back to default as both phones are unusable.");
769                             backToDefault = true;
770                             needValidation = false;
771                         }
772                     }
773                 }
774             } else {
775                 if (mDefaultNetworkIsOnNonCellular) {
776                     debugMessage.append(", back to default as default network")
777                             .append(" is active on nonCellular transport");
778                     backToDefault = true;
779                     needValidation = false;
780                 } else if (!isHomeService(mPhonesSignalStatus[preferredPhoneId].mDataRegState)) {
781                     debugMessage.append(", back to default as backup phone lost HOME registration");
782                     backToDefault = true;
783                     needValidation = false;
784                 } else if (isRatSignalStrengthBasedSwitchEnabled()) {
785                     int defaultScore = mPhonesSignalStatus[defaultDataPhoneId].getRatSignalScore();
786                     int currentScore = mPhonesSignalStatus[preferredPhoneId].getRatSignalScore();
787                     if (defaultScore >= currentScore) {
788                         debugMessage
789                                 .append(", back to default as default has higher or equal score ")
790                                 .append(defaultScore).append(" versus current ")
791                                 .append(currentScore);
792                         backToDefault = true;
793                         isForPerformance = true;
794                         needValidation = mRequirePingTestBeforeSwitch;
795                     }
796                 } else if (isInService(mPhonesSignalStatus[defaultDataPhoneId].mDataRegState)) {
797                     debugMessage.append(", back to default as the default is back to service ");
798                     backToDefault = true;
799                     needValidation = mRequirePingTestBeforeSwitch;
800                 }
801             }
802 
803             if (backToDefault) {
804                 log(debugMessage.toString());
805                 mSelectedTargetPhoneId = defaultDataPhoneId;
806                 startStabilityCheck(DEFAULT_PHONE_INDEX, isForPerformance, needValidation);
807             } else {
808                 // cancel any previous attempts of switching back to default phone
809                 cancelAnyPendingSwitch();
810             }
811         }
812     }
813 
814     /**
815      * Called when consider switching from primary default data sub to another data sub.
816      * @param defaultPhoneId The default data phone
817      * @param debugMessage Debug message.
818      * @return StabilityEventExtra As evaluation result.
819      */
820     @NonNull private StabilityEventExtra evaluateAnyCandidateToUse(int defaultPhoneId,
821             @NonNull StringBuilder debugMessage) {
822         Phone defaultDataPhone = PhoneFactory.getPhone(defaultPhoneId);
823         boolean isForPerformance = false;
824         StabilityEventExtra invalidResult = new StabilityEventExtra(INVALID_PHONE_INDEX,
825                 isForPerformance, mRequirePingTestBeforeSwitch);
826 
827         if (defaultDataPhone == null) {
828             debugMessage.append(", no candidate as no sim loaded");
829             return invalidResult;
830         }
831 
832         if (!defaultDataPhone.isUserDataEnabled()) {
833             debugMessage.append(", no candidate as user disabled mobile data");
834             return invalidResult;
835         }
836 
837         if (mDefaultNetworkIsOnNonCellular) {
838             debugMessage.append(", no candidate as default network is active")
839                     .append(" on non-cellular transport");
840             return invalidResult;
841         }
842 
843         if (isNddsRoamingEnabled()) {
844             // check whether primary and secondary signal status are worth switching
845             if (!isRatSignalStrengthBasedSwitchEnabled()
846                     && isHomeService(mPhonesSignalStatus[defaultPhoneId].mDataRegState)) {
847                 debugMessage.append(", no candidate as default phone is in HOME service");
848                 return invalidResult;
849             }
850         } else {
851             // check whether primary and secondary signal status are worth switching
852             if (!isRatSignalStrengthBasedSwitchEnabled()
853                     && isInService(mPhonesSignalStatus[defaultPhoneId].mDataRegState)) {
854                 debugMessage.append(", no candidate as default phone is in service");
855                 return invalidResult;
856             }
857         }
858 
859         PhoneSignalStatus defaultPhoneStatus = mPhonesSignalStatus[defaultPhoneId];
860         for (int phoneId = 0; phoneId < mPhonesSignalStatus.length; phoneId++) {
861             if (phoneId == defaultPhoneId) continue;
862 
863             Phone secondaryDataPhone = null;
864             PhoneSignalStatus candidatePhoneStatus = mPhonesSignalStatus[phoneId];
865             if (isNddsRoamingEnabled()) {
866                 PhoneSignalStatus.UsableState currentUsableState =
867                         mPhonesSignalStatus[defaultPhoneId].getUsableState();
868                 PhoneSignalStatus.UsableState candidateUsableState =
869                         mPhonesSignalStatus[phoneId].getUsableState();
870                 debugMessage.append(", found phone ").append(phoneId).append(" ")
871                         .append(candidateUsableState)
872                         .append(", default is ").append(currentUsableState);
873                 if (candidateUsableState.mScore > currentUsableState.mScore) {
874                     secondaryDataPhone = PhoneFactory.getPhone(phoneId);
875                 } else if (isRatSignalStrengthBasedSwitchEnabled()
876                         && currentUsableState.mScore == candidateUsableState.mScore) {
877                     // Both phones are home or both roaming enabled, so compare RAT/signal score.
878 
879                     int defaultScore = defaultPhoneStatus.getRatSignalScore();
880                     int candidateScore = candidatePhoneStatus.getRatSignalScore();
881                     if ((candidateScore - defaultScore) > mScoreTolerance) {
882                         debugMessage.append(" with ").append(defaultScore)
883                                 .append(" versus candidate higher score ").append(candidateScore);
884                         secondaryDataPhone = PhoneFactory.getPhone(phoneId);
885                         isForPerformance = true;
886                     } else {
887                         debugMessage.append(", candidate's score ").append(candidateScore)
888                                 .append(" doesn't justify the switch given the current ")
889                                 .append(defaultScore);
890                     }
891                 }
892             } else if (isHomeService(candidatePhoneStatus.mDataRegState)) {
893                 // the alternative phone must have HOME availability
894                 debugMessage.append(", found phone ").append(phoneId).append(" in HOME service");
895 
896                 if (isInService(defaultPhoneStatus.mDataRegState)) {
897                     // Use score if RAT/signal strength based switch is enabled and both phone are
898                     // in service.
899                     if (isRatSignalStrengthBasedSwitchEnabled()) {
900                         int defaultScore = mPhonesSignalStatus[defaultPhoneId].getRatSignalScore();
901                         int candidateScore = mPhonesSignalStatus[phoneId].getRatSignalScore();
902                         if ((candidateScore - defaultScore) > mScoreTolerance) {
903                             debugMessage.append(" with higher score ").append(candidateScore)
904                                     .append(" versus current ").append(defaultScore);
905                             secondaryDataPhone = PhoneFactory.getPhone(phoneId);
906                             isForPerformance = true;
907                         } else {
908                             debugMessage.append(", but its score ").append(candidateScore)
909                                     .append(" doesn't meet the bar to switch given the current ")
910                                     .append(defaultScore);
911                         }
912                     }
913                 } else {
914                     // Only OOS/in service switch is enabled.
915                     secondaryDataPhone = PhoneFactory.getPhone(phoneId);
916                 }
917             }
918 
919             if (secondaryDataPhone != null) {
920                 // check internet data is allowed on the candidate
921                 DataEvaluation internetEvaluation = secondaryDataPhone.getDataNetworkController()
922                         .getInternetEvaluation(false/*ignoreExistingNetworks*/);
923                 if (!internetEvaluation.containsDisallowedReasons()) {
924                     return new StabilityEventExtra(phoneId,
925                             isForPerformance, mRequirePingTestBeforeSwitch);
926                 } else {
927                     debugMessage.append(", but candidate's data is not allowed ")
928                             .append(internetEvaluation);
929                 }
930             }
931         }
932         debugMessage.append(", found no qualified candidate.");
933         return invalidResult;
934     }
935 
936     /**
937      * @return {@code true} If the feature of switching base on RAT and signal strength is enabled.
938      */
939     private boolean isRatSignalStrengthBasedSwitchEnabled() {
940         return mScoreTolerance >= 0 && mAutoDataSwitchPerformanceStabilityTimeThreshold >= 0;
941     }
942 
943     /**
944      * @return {@code true} If the feature of switching to roaming non DDS is enabled.
945      */
946     private boolean isNddsRoamingEnabled() {
947         return sFeatureFlags.autoDataSwitchAllowRoaming() && mAllowNddsRoaming;
948     }
949 
950     /**
951      * Called when the current environment suits auto data switch.
952      * Start pre-switch validation if the current environment suits auto data switch for
953      * {@link #mAutoDataSwitchAvailabilityStabilityTimeThreshold} MS.
954      * @param targetPhoneId the target phone Id.
955      * @param isForPerformance {@code true} entails longer stability check.
956      * @param needValidation {@code true} if validation is needed.
957      */
958     private void startStabilityCheck(int targetPhoneId, boolean isForPerformance,
959             boolean needValidation) {
960         StabilityEventExtra eventExtras = (StabilityEventExtra)
961                 mScheduledEventsToExtras.getOrDefault(EVENT_STABILITY_CHECK_PASSED,
962                         new StabilityEventExtra(INVALID_PHONE_INDEX, false /*need validation*/,
963                                 false /*isForPerformance*/));
964         long delayMs = -1;
965         // Check if already scheduled one with that combination of extras.
966         if (eventExtras.targetPhoneId != targetPhoneId
967                 || eventExtras.needValidation != needValidation
968                 || eventExtras.isForPerformance != isForPerformance) {
969             eventExtras =
970                     new StabilityEventExtra(targetPhoneId, isForPerformance, needValidation);
971 
972             // Reset with new timer.
973             delayMs = isForPerformance
974                     ? mAutoDataSwitchPerformanceStabilityTimeThreshold
975                     : mAutoDataSwitchAvailabilityStabilityTimeThreshold;
976             scheduleEventWithTimer(EVENT_STABILITY_CHECK_PASSED, eventExtras, delayMs);
977         }
978         log("startStabilityCheck: "
979                 + (delayMs != -1 ? "scheduling " : "already scheduled ")
980                 + eventExtras);
981     }
982 
983     /**
984      * Use when need to schedule with timer. Short timer uses handler, while the longer timer uses
985      * alarm manager to account for real time elapse.
986      *
987      * @param event The event.
988      * @param extras Any extra data associated with the event.
989      * @param delayMs The delayed interval in ms.
990      */
991     private void scheduleEventWithTimer(int event, @NonNull Object extras, long delayMs) {
992         // Get singleton alarm listener.
993         mEventsToAlarmListener.putIfAbsent(event, () -> sendEmptyMessage(event));
994         AlarmManager.OnAlarmListener listener = mEventsToAlarmListener.get(event);
995 
996         // Cancel any existing.
997         removeMessages(event);
998         mAlarmManager.cancel(listener);
999         // Override with new extras.
1000         mScheduledEventsToExtras.put(event, extras);
1001         // Reset timer.
1002         if (delayMs <= RETRY_LONG_DELAY_TIMER_THRESHOLD_MILLIS) {
1003             // Use handler for short timer.
1004             sendEmptyMessageDelayed(event, delayMs);
1005         } else {
1006             // Not using setWhileIdle because it can wait util the next time the device wakes up to
1007             // save power.
1008             // If another evaluation is processed before the alarm fires,
1009             // this timer is restarted (AlarmManager using the same listener resets the
1010             // timer).
1011             mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME,
1012                     SystemClock.elapsedRealtime() + delayMs,
1013                     LOG_TAG /*debug tag*/, listener, this);
1014         }
1015     }
1016 
1017     /** Auto data switch evaluation reason to string. */
1018     @NonNull
1019     public static String evaluationReasonToString(
1020             @AutoDataSwitchEvaluationReason int reason) {
1021         return switch (reason) {
1022             case EVALUATION_REASON_REGISTRATION_STATE_CHANGED -> "REGISTRATION_STATE_CHANGED";
1023             case EVALUATION_REASON_DISPLAY_INFO_CHANGED -> "DISPLAY_INFO_CHANGED";
1024             case EVALUATION_REASON_SIGNAL_STRENGTH_CHANGED -> "SIGNAL_STRENGTH_CHANGED";
1025             case EVALUATION_REASON_DEFAULT_NETWORK_CHANGED -> "DEFAULT_NETWORK_CHANGED";
1026             case EVALUATION_REASON_DATA_SETTINGS_CHANGED -> "DATA_SETTINGS_CHANGED";
1027             case EVALUATION_REASON_RETRY_VALIDATION -> "RETRY_VALIDATION";
1028             case EVALUATION_REASON_SIM_LOADED -> "SIM_LOADED";
1029             case EVALUATION_REASON_VOICE_CALL_END -> "VOICE_CALL_END";
1030             default -> "Unknown(" + reason + ")";
1031         };
1032     }
1033 
1034     /** @return {@code true} if the sub is active. */
1035     private boolean isActiveSubId(int subId) {
1036         SubscriptionInfoInternal subInfo = mSubscriptionManagerService
1037                 .getSubscriptionInfoInternal(subId);
1038         return subInfo != null && subInfo.isActive();
1039     }
1040 
1041     /**
1042      * Called when default network capabilities changed. If default network is active on
1043      * non-cellular, switch back to the default data phone. If default network is lost, try to find
1044      * another sub to switch to.
1045      * @param networkCapabilities {@code null} indicates default network lost.
1046      */
1047     public void updateDefaultNetworkCapabilities(
1048             @Nullable NetworkCapabilities networkCapabilities) {
1049         if (networkCapabilities != null) {
1050             // Exists default network
1051             mDefaultNetworkIsOnNonCellular = !networkCapabilities.hasTransport(TRANSPORT_CELLULAR);
1052             if (mDefaultNetworkIsOnNonCellular
1053                     && isActiveSubId(mPhoneSwitcher.getAutoSelectedDataSubId())) {
1054                 log("default network is active on non cellular, switch back to default");
1055                 evaluateAutoDataSwitch(EVALUATION_REASON_DEFAULT_NETWORK_CHANGED);
1056             }
1057         } else {
1058             log("default network is lost, try to find another active sub to switch to");
1059             mDefaultNetworkIsOnNonCellular = false;
1060             evaluateAutoDataSwitch(EVALUATION_REASON_DEFAULT_NETWORK_CHANGED);
1061         }
1062     }
1063 
1064     /**
1065      * Cancel any auto switch attempts when the current environment is not suitable for auto switch.
1066      */
1067     private void cancelAnyPendingSwitch() {
1068         mSelectedTargetPhoneId = INVALID_PHONE_INDEX;
1069         resetFailedCount();
1070         if (mScheduledEventsToExtras.containsKey(EVENT_STABILITY_CHECK_PASSED)) {
1071             if (mEventsToAlarmListener.containsKey(EVENT_STABILITY_CHECK_PASSED)) {
1072                 mAlarmManager.cancel(mEventsToAlarmListener.get(EVENT_STABILITY_CHECK_PASSED));
1073             } else {
1074                 loge("cancelAnyPendingSwitch: EVENT_STABILITY_CHECK_PASSED listener is null");
1075             }
1076             removeMessages(EVENT_STABILITY_CHECK_PASSED);
1077             mScheduledEventsToExtras.remove(EVENT_STABILITY_CHECK_PASSED);
1078         }
1079         mPhoneSwitcherCallback.onRequireCancelAnyPendingAutoSwitchValidation();
1080     }
1081 
1082     /**
1083      * Display a notification the first time auto data switch occurs.
1084      * @param phoneId The phone Id of the current preferred phone.
1085      * @param isDueToAutoSwitch {@code true} if the switch was due to auto data switch feature.
1086      */
1087     public void displayAutoDataSwitchNotification(int phoneId, boolean isDueToAutoSwitch) {
1088         NotificationManager notificationManager = mContext.getSystemService(
1089                 NotificationManager.class);
1090         if (notificationManager == null) return;
1091         if (mDisplayedNotification) {
1092             // cancel posted notification if any exist
1093             notificationManager.cancel(AUTO_DATA_SWITCH_NOTIFICATION_TAG,
1094                     AUTO_DATA_SWITCH_NOTIFICATION_ID);
1095             return;
1096         }
1097         // proceed only the first time auto data switch occurs, which includes data during call
1098         if (!isDueToAutoSwitch) {
1099             return;
1100         }
1101         SubscriptionInfo subInfo = mSubscriptionManagerService
1102                 .getSubscriptionInfo(mSubscriptionManagerService.getSubId(phoneId));
1103         if (subInfo == null || subInfo.isOpportunistic()) {
1104             loge("displayAutoDataSwitchNotification: phoneId="
1105                     + phoneId + " unexpected subInfo " + subInfo);
1106             return;
1107         }
1108         int subId = subInfo.getSubscriptionId();
1109         logl("displayAutoDataSwitchNotification: display for subId=" + subId);
1110         // "Mobile network settings" screen / dialog
1111         Intent intent = new Intent(Settings.ACTION_NETWORK_OPERATOR_SETTINGS);
1112         final Bundle fragmentArgs = new Bundle();
1113         // Special contract for Settings to highlight permission row
1114         fragmentArgs.putString(SETTINGS_EXTRA_FRAGMENT_ARG_KEY, AUTO_DATA_SWITCH_SETTING_R_ID);
1115         intent.putExtra(Settings.EXTRA_SUB_ID, subId);
1116         intent.putExtra(SETTINGS_EXTRA_SHOW_FRAGMENT_ARGUMENTS, fragmentArgs);
1117         PendingIntent contentIntent = PendingIntent.getActivity(
1118                 mContext, subId, intent, PendingIntent.FLAG_IMMUTABLE);
1119 
1120         CharSequence activeCarrierName = subInfo.getDisplayName();
1121         CharSequence contentTitle = mContext.getString(
1122                 com.android.internal.R.string.auto_data_switch_title, activeCarrierName);
1123         CharSequence contentText = mContext.getText(
1124                 com.android.internal.R.string.auto_data_switch_content);
1125 
1126         final Notification notif = new Notification.Builder(mContext)
1127                 .setContentTitle(contentTitle)
1128                 .setContentText(contentText)
1129                 .setSmallIcon(android.R.drawable.stat_sys_warning)
1130                 .setColor(mContext.getResources().getColor(
1131                         com.android.internal.R.color.system_notification_accent_color))
1132                 .setChannelId(NotificationChannelController.CHANNEL_ID_MOBILE_DATA_STATUS)
1133                 .setContentIntent(contentIntent)
1134                 .setStyle(new Notification.BigTextStyle().bigText(contentText))
1135                 .build();
1136         notificationManager.notify(AUTO_DATA_SWITCH_NOTIFICATION_TAG,
1137                 AUTO_DATA_SWITCH_NOTIFICATION_ID, notif);
1138         mDisplayedNotification = true;
1139     }
1140 
1141     /** Enable future switch retry again. Called when switch condition changed. */
1142     public void resetFailedCount() {
1143         mAutoSwitchValidationFailedCount = 0;
1144     }
1145 
1146     /**
1147      * Called when skipped switch due to validation failed. Schedule retry to switch again.
1148      */
1149     public void evaluateRetryOnValidationFailed() {
1150         if (mAutoSwitchValidationFailedCount < mAutoDataSwitchValidationMaxRetry) {
1151             evaluateAutoDataSwitch(EVALUATION_REASON_RETRY_VALIDATION);
1152             mAutoSwitchValidationFailedCount++;
1153         } else {
1154             logl("evaluateRetryOnValidationFailed: reached max auto switch retry count "
1155                     + mAutoDataSwitchValidationMaxRetry);
1156             mAutoSwitchValidationFailedCount = 0;
1157         }
1158     }
1159 
1160     /**
1161      * @param phoneId The phone Id to check.
1162      * @return {@code true} if the phone Id is an active modem.
1163      */
1164     private boolean isActiveModemPhone(int phoneId) {
1165         return phoneId >= 0 && phoneId < mPhonesSignalStatus.length;
1166     }
1167 
1168     /**
1169      * Log debug messages.
1170      * @param s debug messages
1171      */
1172     private void log(@NonNull String s) {
1173         Rlog.d(LOG_TAG, s);
1174     }
1175 
1176     /**
1177      * Log error messages.
1178      * @param s error messages
1179      */
1180     private void loge(@NonNull String s) {
1181         Rlog.e(LOG_TAG, s);
1182     }
1183 
1184     /**
1185      * Log debug messages and also log into the local log.
1186      * @param s debug messages
1187      */
1188     private void logl(@NonNull String s) {
1189         log(s);
1190         mLocalLog.log(s);
1191     }
1192 
1193     /**
1194      * Dump the state of DataNetworkController
1195      *
1196      * @param fd File descriptor
1197      * @param printWriter Print writer
1198      * @param args Arguments
1199      */
1200     public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
1201         IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
1202         pw.println("AutoDataSwitchController:");
1203         pw.increaseIndent();
1204         pw.println("mScoreTolerance=" + mScoreTolerance);
1205         pw.println("mAutoDataSwitchValidationMaxRetry=" + mAutoDataSwitchValidationMaxRetry
1206                 + " mAutoSwitchValidationFailedCount=" + mAutoSwitchValidationFailedCount);
1207         pw.println("mRequirePingTestBeforeDataSwitch=" + mRequirePingTestBeforeSwitch);
1208         pw.println("mAutoDataSwitchAvailabilityStabilityTimeThreshold="
1209                 + mAutoDataSwitchAvailabilityStabilityTimeThreshold);
1210         pw.println("mSelectedTargetPhoneId=" + mSelectedTargetPhoneId);
1211         pw.increaseIndent();
1212         for (PhoneSignalStatus status: mPhonesSignalStatus) {
1213             pw.println(status);
1214         }
1215         pw.decreaseIndent();
1216         mLocalLog.dump(fd, pw, args);
1217         pw.decreaseIndent();
1218     }
1219 }
1220