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