• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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.server.wifi;
18 
19 import static com.android.server.wifi.ClientModeImpl.WIFI_WORK_SOURCE;
20 
21 import android.annotation.Nullable;
22 import android.content.Context;
23 import android.net.Network;
24 import android.net.NetworkCapabilities;
25 import android.net.NetworkScore;
26 import android.net.wifi.IWifiConnectedNetworkScorer;
27 import android.net.wifi.WifiConfiguration;
28 import android.net.wifi.WifiConnectedSessionInfo;
29 import android.net.wifi.WifiInfo;
30 import android.net.wifi.WifiManager;
31 import android.net.wifi.nl80211.WifiNl80211Manager;
32 import android.os.Build;
33 import android.os.IBinder;
34 import android.os.RemoteException;
35 import android.util.Log;
36 
37 import androidx.annotation.RequiresApi;
38 
39 import com.android.internal.annotations.VisibleForTesting;
40 import com.android.modules.utils.build.SdkLevel;
41 import com.android.server.wifi.ActiveModeManager.ClientRole;
42 import com.android.wifi.resources.R;
43 
44 import java.io.FileDescriptor;
45 import java.io.PrintWriter;
46 import java.util.Calendar;
47 import java.util.LinkedList;
48 
49 /**
50  * Class used to calculate scores for connected wifi networks and report it to the associated
51  * network agent.
52  */
53 public class WifiScoreReport {
54     private static final String TAG = "WifiScoreReport";
55 
56     private static final int DUMPSYS_ENTRY_COUNT_LIMIT = 3600; // 3 hours on 3 second poll
57 
58     private boolean mVerboseLoggingEnabled = false;
59     private static final long FIRST_REASONABLE_WALL_CLOCK = 1490000000000L; // mid-December 2016
60 
61     private static final long MIN_TIME_TO_KEEP_BELOW_TRANSITION_SCORE_MILLIS = 9000;
62     private long mLastDownwardBreachTimeMillis = 0;
63 
64     private static final int WIFI_CONNECTED_NETWORK_SCORER_IDENTIFIER = 0;
65     private static final int INVALID_SESSION_ID = -1;
66     private static final long MIN_TIME_TO_WAIT_BEFORE_BLOCKLIST_BSSID_MILLIS = 29000;
67     private static final long INVALID_WALL_CLOCK_MILLIS = -1;
68     private static final int WIFI_SCORE_TO_TERMINATE_CONNECTION_BLOCKLIST_BSSID = -2;
69 
70     /**
71      * Set lingering score to be artificially lower than all other scores so that it will force
72      * ConnectivityService to prefer any other network over this one.
73      */
74     @VisibleForTesting
75     static final int LINGERING_SCORE = 1;
76 
77     // Cache of the last score
78     private int mLegacyIntScore = ConnectedScore.WIFI_INITIAL_SCORE;
79     // Cache of the last usability status
80     private boolean mIsUsable = true;
81 
82     /**
83      * If true, indicates that the associated {@link ClientModeImpl} instance is lingering
84      * as a part of make before break STA + STA use-case, and will always send
85      * {@link #LINGERING_SCORE} to NetworkAgent.
86      */
87     private boolean mShouldReduceNetworkScore = false;
88 
89     /** The current role of the ClientModeManager which owns this instance of WifiScoreReport. */
90     @Nullable
91     private ClientRole mCurrentRole = null;
92 
93     private final ScoringParams mScoringParams;
94     private final Clock mClock;
95     private int mSessionNumber = 0; // not to be confused with sessionid, this just counts resets
96     private final String mInterfaceName;
97     private final WifiBlocklistMonitor mWifiBlocklistMonitor;
98     private final WifiScoreCard mWifiScoreCard;
99     private final Context mContext;
100     private long mLastScoreBreachLowTimeMillis = INVALID_WALL_CLOCK_MILLIS;
101     private long mLastScoreBreachHighTimeMillis = INVALID_WALL_CLOCK_MILLIS;
102 
103     private final ConnectedScore mAggressiveConnectedScore;
104     private VelocityBasedConnectedScore mVelocityBasedConnectedScore;
105     private final WifiSettingsStore mWifiSettingsStore;
106     private int mSessionIdNoReset = INVALID_SESSION_ID;
107     // Indicate whether current network is selected by the user
108     private boolean mIsUserSelected = false;
109 
110     @Nullable
111     private WifiNetworkAgent mNetworkAgent;
112     private final WifiMetrics mWifiMetrics;
113     private final ExtendedWifiInfo mWifiInfo;
114     private final WifiNative mWifiNative;
115     private final WifiThreadRunner mWifiThreadRunner;
116     private final DeviceConfigFacade mDeviceConfigFacade;
117     private final ExternalScoreUpdateObserverProxy mExternalScoreUpdateObserverProxy;
118     private final WifiInfo mWifiInfoNoReset;
119     private final WifiGlobals mWifiGlobals;
120     private final ActiveModeWarden mActiveModeWarden;
121     private final WifiConnectivityManager mWifiConnectivityManager;
122     private final WifiConfigManager mWifiConfigManager;
123     private long mLastLowScoreScanTimestampMs = -1;
124     private WifiConfiguration mCurrentWifiConfiguration;
125 
126     /**
127      * Callback from {@link ExternalScoreUpdateObserverProxy}
128      */
129     private class ScoreUpdateObserverProxy implements WifiManager.ScoreUpdateObserver {
130         @Override
notifyScoreUpdate(int sessionId, int score)131         public void notifyScoreUpdate(int sessionId, int score) {
132             if (mWifiConnectedNetworkScorerHolder == null
133                     || sessionId == INVALID_SESSION_ID
134                     || sessionId != getCurrentSessionId()) {
135                 Log.w(TAG, "Ignoring stale/invalid external score"
136                         + " sessionId=" + sessionId
137                         + " currentSessionId=" + getCurrentSessionId()
138                         + " score=" + score);
139                 return;
140             }
141             long millis = mClock.getWallClockMillis();
142             if (SdkLevel.isAtLeastS()) {
143                 mLegacyIntScore = score;
144                 // Only primary network can have external scorer.
145                 updateWifiMetrics(millis, -1);
146                 return;
147             }
148             // TODO (b/207058915): Disconnect WiFi and blocklist current BSSID.  This is to
149             //  maintain backward compatibility for WiFi mainline module on Android 11, and can be
150             //  removed when the WiFi mainline module is no longer updated on Android 11.
151             if (score == WIFI_SCORE_TO_TERMINATE_CONNECTION_BLOCKLIST_BSSID) {
152                 mWifiBlocklistMonitor.handleBssidConnectionFailure(mWifiInfoNoReset.getBSSID(),
153                         mCurrentWifiConfiguration,
154                         WifiBlocklistMonitor.REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE,
155                         mWifiInfoNoReset.getRssi());
156                 return;
157             }
158             if (score > ConnectedScore.WIFI_MAX_SCORE
159                     || score < ConnectedScore.WIFI_MIN_SCORE) {
160                 Log.e(TAG, "Invalid score value from external scorer: " + score);
161                 return;
162             }
163             if (score < ConnectedScore.WIFI_TRANSITION_SCORE) {
164                 if (mLegacyIntScore >= ConnectedScore.WIFI_TRANSITION_SCORE) {
165                     mLastScoreBreachLowTimeMillis = millis;
166                 }
167             } else {
168                 mLastScoreBreachLowTimeMillis = INVALID_WALL_CLOCK_MILLIS;
169             }
170             if (score > ConnectedScore.WIFI_TRANSITION_SCORE) {
171                 if (mLegacyIntScore <= ConnectedScore.WIFI_TRANSITION_SCORE) {
172                     mLastScoreBreachHighTimeMillis = millis;
173                 }
174             } else {
175                 mLastScoreBreachHighTimeMillis = INVALID_WALL_CLOCK_MILLIS;
176             }
177             mLegacyIntScore = score;
178             reportNetworkScoreToConnectivityServiceIfNecessary();
179             updateWifiMetrics(millis, -1);
180         }
181 
182         @Override
triggerUpdateOfWifiUsabilityStats(int sessionId)183         public void triggerUpdateOfWifiUsabilityStats(int sessionId) {
184             if (mWifiConnectedNetworkScorerHolder == null
185                     || sessionId == INVALID_SESSION_ID
186                     || sessionId != getCurrentSessionId()
187                     || mInterfaceName == null) {
188                 Log.w(TAG, "Ignoring triggerUpdateOfWifiUsabilityStats"
189                         + " sessionId=" + sessionId
190                         + " currentSessionId=" + getCurrentSessionId()
191                         + " interfaceName=" + mInterfaceName);
192                 return;
193             }
194             WifiLinkLayerStats stats = mWifiNative.getWifiLinkLayerStats(mInterfaceName);
195 
196             // update mWifiInfo
197             // TODO(b/153075963): Better coordinate this class and ClientModeImpl to remove
198             // redundant codes below and in ClientModeImpl#fetchRssiLinkSpeedAndFrequencyNative.
199             WifiNl80211Manager.SignalPollResult pollResult =
200                     mWifiNative.signalPoll(mInterfaceName);
201             if (pollResult != null) {
202                 int newRssi = pollResult.currentRssiDbm;
203                 int newTxLinkSpeed = pollResult.txBitrateMbps;
204                 int newFrequency = pollResult.associationFrequencyMHz;
205                 int newRxLinkSpeed = pollResult.rxBitrateMbps;
206 
207                 if (newRssi > WifiInfo.INVALID_RSSI && newRssi < WifiInfo.MAX_RSSI) {
208                     if (newRssi > (WifiInfo.INVALID_RSSI + 256)) {
209                         Log.wtf(TAG, "Error! +ve value RSSI: " + newRssi);
210                         newRssi -= 256;
211                     }
212                     mWifiInfo.setRssi(newRssi);
213                 } else {
214                     mWifiInfo.setRssi(WifiInfo.INVALID_RSSI);
215                 }
216                 /*
217                  * set Tx link speed only if it is valid
218                  */
219                 if (newTxLinkSpeed > 0) {
220                     mWifiInfo.setLinkSpeed(newTxLinkSpeed);
221                     mWifiInfo.setTxLinkSpeedMbps(newTxLinkSpeed);
222                 }
223                 /*
224                  * set Rx link speed only if it is valid
225                  */
226                 if (newRxLinkSpeed > 0) {
227                     mWifiInfo.setRxLinkSpeedMbps(newRxLinkSpeed);
228                 }
229                 if (newFrequency > 0) {
230                     mWifiInfo.setFrequency(newFrequency);
231                 }
232             }
233 
234             // TODO(b/153075963): This should not be plumbed through WifiMetrics
235             mWifiMetrics.updateWifiUsabilityStatsEntries(mInterfaceName, mWifiInfo, stats);
236         }
237 
238         @Override
notifyStatusUpdate(int sessionId, boolean isUsable)239         public void notifyStatusUpdate(int sessionId, boolean isUsable) {
240             if (mWifiConnectedNetworkScorerHolder == null
241                     || sessionId == INVALID_SESSION_ID
242                     || sessionId != getCurrentSessionId()) {
243                 Log.w(TAG, "Ignoring stale/invalid external status"
244                         + " sessionId=" + sessionId
245                         + " currentSessionId=" + getCurrentSessionId()
246                         + " isUsable=" + isUsable);
247                 return;
248             }
249             if (mNetworkAgent == null) {
250                 return;
251             }
252             if (mShouldReduceNetworkScore) {
253                 return;
254             }
255             mIsUsable = isUsable;
256             // Wifi is set to be usable if adaptive connectivity is disabled.
257             if (!mAdaptiveConnectivityEnabledSettingObserver.get()
258                     || !mWifiSettingsStore.isWifiScoringEnabled()) {
259                 mIsUsable = true;
260                 if (mVerboseLoggingEnabled) {
261                     Log.d(TAG, "Wifi scoring disabled - Notify that Wifi is usable");
262                 }
263             }
264             // Send `exiting` to NetworkScore, but don't update and send mLegacyIntScore
265             // and don't change any other fields. All we want to do is relay to ConnectivityService
266             // whether the current network is usable.
267             if (SdkLevel.isAtLeastS()) {
268                 mNetworkAgent.sendNetworkScore(
269                         getScoreBuilder()
270                                 .setLegacyInt(mLegacyIntScore)
271                                 .setExiting(!mIsUsable)
272                                 .build());
273             } else  {
274                 mNetworkAgent.sendNetworkScore(mIsUsable ? ConnectedScore.WIFI_TRANSITION_SCORE + 1
275                         : ConnectedScore.WIFI_TRANSITION_SCORE - 1);
276             }
277             mWifiInfo.setUsable(mIsUsable);
278         }
279 
280         @Override
requestNudOperation(int sessionId)281         public void requestNudOperation(int sessionId) {
282             if (mWifiConnectedNetworkScorerHolder == null
283                     || sessionId == INVALID_SESSION_ID
284                     || sessionId != getCurrentSessionId()) {
285                 Log.w(TAG, "Ignoring stale/invalid external input for NUD triggering"
286                         + " sessionId=" + sessionId
287                         + " currentSessionId=" + getCurrentSessionId());
288                 return;
289             }
290             if (!mAdaptiveConnectivityEnabledSettingObserver.get()
291                     || !mWifiSettingsStore.isWifiScoringEnabled()) {
292                 if (mVerboseLoggingEnabled) {
293                     Log.d(TAG, "Wifi scoring disabled - Cannot request a NUD operation");
294                 }
295                 return;
296             }
297             mWifiConnectedNetworkScorerHolder.setShouldCheckIpLayerOnce(true);
298         }
299 
300         @Override
blocklistCurrentBssid(int sessionId)301         public void blocklistCurrentBssid(int sessionId) {
302             if (mWifiConnectedNetworkScorerHolder == null
303                     || sessionId == INVALID_SESSION_ID
304                     || sessionId != mSessionIdNoReset) {
305                 Log.w(TAG, "Ignoring stale/invalid external input for blocklisting"
306                         + " sessionId=" + sessionId
307                         + " mSessionIdNoReset=" + mSessionIdNoReset);
308                 return;
309             }
310             if (!mAdaptiveConnectivityEnabledSettingObserver.get()
311                     || !mWifiSettingsStore.isWifiScoringEnabled()) {
312                 if (mVerboseLoggingEnabled) {
313                     Log.d(TAG, "Wifi scoring disabled - Cannot blocklist current BSSID");
314                 }
315                 return;
316             }
317             if (mWifiInfoNoReset.getBSSID() != null) {
318                 mWifiBlocklistMonitor.handleBssidConnectionFailure(mWifiInfoNoReset.getBSSID(),
319                         mCurrentWifiConfiguration,
320                         WifiBlocklistMonitor.REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE,
321                         mWifiInfoNoReset.getRssi());
322             }
323         }
324     }
325 
326     /**
327      * If true, put the WifiScoreReport in lingering mode. A very low score is reported to
328      * NetworkAgent, and the real score is never reported.
329      */
setShouldReduceNetworkScore(boolean shouldReduceNetworkScore)330     public void setShouldReduceNetworkScore(boolean shouldReduceNetworkScore) {
331         Log.d(TAG, "setShouldReduceNetworkScore=" + shouldReduceNetworkScore
332                 + " mNetworkAgent is null? " + (mNetworkAgent == null));
333         mShouldReduceNetworkScore = shouldReduceNetworkScore;
334         // inform the external scorer that ongoing session has ended (since the score is no longer
335         // under their control)
336         if (mShouldReduceNetworkScore && mWifiConnectedNetworkScorerHolder != null) {
337             mWifiConnectedNetworkScorerHolder.stopSession();
338         }
339         // if set to true, send score below disconnect threshold to start lingering
340         sendNetworkScore();
341     }
342 
343     /**
344      * Report network score to connectivity service.
345      */
reportNetworkScoreToConnectivityServiceIfNecessary()346     private void reportNetworkScoreToConnectivityServiceIfNecessary() {
347         if (mNetworkAgent == null) {
348             return;
349         }
350         if (mWifiConnectedNetworkScorerHolder == null && mLegacyIntScore == mWifiInfo.getScore()) {
351             return;
352         }
353         // only send network score if not lingering. If lingering, would have already sent score at
354         // start of lingering.
355         if (mShouldReduceNetworkScore) {
356             return;
357         }
358         if (mWifiConnectedNetworkScorerHolder != null
359                 && mContext.getResources().getBoolean(
360                         R.bool.config_wifiMinConfirmationDurationSendNetworkScoreEnabled)
361                 /// Turn off hysteresis/dampening for shell commands.
362                 && !mWifiConnectedNetworkScorerHolder.isShellCommandScorer()) {
363             long millis = mClock.getWallClockMillis();
364             if (mLastScoreBreachLowTimeMillis != INVALID_WALL_CLOCK_MILLIS) {
365                 if (mWifiInfo.getRssi()
366                         >= mDeviceConfigFacade.getRssiThresholdNotSendLowScoreToCsDbm()) {
367                     Log.d(TAG, "Not reporting low score because RSSI is high "
368                             + mWifiInfo.getRssi());
369                     return;
370                 }
371                 if ((millis - mLastScoreBreachLowTimeMillis)
372                         < mDeviceConfigFacade.getMinConfirmationDurationSendLowScoreMs()) {
373                     Log.d(TAG, "Not reporting low score because elapsed time is shorter than "
374                             + "the minimum confirmation duration");
375                     return;
376                 }
377             }
378             if (mLastScoreBreachHighTimeMillis != INVALID_WALL_CLOCK_MILLIS
379                     && (millis - mLastScoreBreachHighTimeMillis)
380                             < mDeviceConfigFacade.getMinConfirmationDurationSendHighScoreMs()) {
381                 Log.d(TAG, "Not reporting high score because elapsed time is shorter than "
382                         + "the minimum confirmation duration");
383                 return;
384             }
385         }
386         // Stay a notch above the transition score if adaptive connectivity is disabled.
387         if (!mAdaptiveConnectivityEnabledSettingObserver.get()
388                 || !mWifiSettingsStore.isWifiScoringEnabled()) {
389             mLegacyIntScore = ConnectedScore.WIFI_TRANSITION_SCORE + 1;
390             if (mVerboseLoggingEnabled) {
391                 Log.d(TAG, "Wifi scoring disabled - Stay a notch above the transition score");
392             }
393         }
394         sendNetworkScore();
395     }
396 
397     /**
398      * Container for storing info about external scorer and tracking its death.
399      */
400     private final class WifiConnectedNetworkScorerHolder implements IBinder.DeathRecipient {
401         private final IBinder mBinder;
402         private final IWifiConnectedNetworkScorer mScorer;
403         private int mSessionId = INVALID_SESSION_ID;
404         private boolean mShouldCheckIpLayerOnce = false;
405 
WifiConnectedNetworkScorerHolder(IBinder binder, IWifiConnectedNetworkScorer scorer)406         WifiConnectedNetworkScorerHolder(IBinder binder, IWifiConnectedNetworkScorer scorer) {
407             mBinder = binder;
408             mScorer = scorer;
409         }
410 
411         /**
412          * Link WiFi connected scorer to death listener.
413          */
linkScorerToDeath()414         public boolean linkScorerToDeath() {
415             try {
416                 mBinder.linkToDeath(this, 0);
417             } catch (RemoteException e) {
418                 Log.e(TAG, "Unable to linkToDeath Wifi connected network scorer " + mScorer, e);
419                 return false;
420             }
421             return true;
422         }
423 
424         /**
425          * App hosting the binder has died.
426          */
427         @Override
binderDied()428         public void binderDied() {
429             mWifiThreadRunner.post(() -> revertToDefaultConnectedScorer());
430         }
431 
432         /**
433          * Unlink this object from binder death.
434          */
reset()435         public void reset() {
436             mBinder.unlinkToDeath(this, 0);
437         }
438 
439         /**
440          * Starts a new scoring session.
441          */
startSession(int sessionId, boolean isUserSelected)442         public void startSession(int sessionId, boolean isUserSelected) {
443             if (sessionId == INVALID_SESSION_ID) {
444                 throw new IllegalArgumentException();
445             }
446             if (mSessionId != INVALID_SESSION_ID) {
447                 // This is not expected to happen, log if it does
448                 Log.e(TAG, "Stopping session " + mSessionId + " before starting " + sessionId);
449                 stopSession();
450             }
451             // Bail now if the scorer has gone away
452             if (this != mWifiConnectedNetworkScorerHolder) {
453                 return;
454             }
455             mSessionId = sessionId;
456             mSessionIdNoReset = sessionId;
457             try {
458                 WifiConnectedSessionInfo sessionInfo =
459                         new WifiConnectedSessionInfo.Builder(sessionId)
460                                 .setUserSelected(isUserSelected)
461                                 .build();
462                 mScorer.onStart(sessionInfo);
463             } catch (RemoteException e) {
464                 Log.e(TAG, "Unable to start Wifi connected network scorer " + this, e);
465                 revertToDefaultConnectedScorer();
466             }
467         }
stopSession()468         public void stopSession() {
469             final int sessionId = mSessionId;
470             if (sessionId == INVALID_SESSION_ID) return;
471             mSessionId = INVALID_SESSION_ID;
472             mShouldCheckIpLayerOnce = false;
473             try {
474                 mScorer.onStop(sessionId);
475             } catch (RemoteException e) {
476                 Log.e(TAG, "Unable to stop Wifi connected network scorer " + this, e);
477                 revertToDefaultConnectedScorer();
478             }
479         }
480 
isShellCommandScorer()481         public boolean isShellCommandScorer() {
482             return mScorer instanceof WifiShellCommand.WifiScorer;
483         }
484 
setShouldCheckIpLayerOnce(boolean shouldCheckIpLayerOnce)485         private void setShouldCheckIpLayerOnce(boolean shouldCheckIpLayerOnce) {
486             mShouldCheckIpLayerOnce = shouldCheckIpLayerOnce;
487         }
488 
getShouldCheckIpLayerOnce()489         private boolean getShouldCheckIpLayerOnce() {
490             return mShouldCheckIpLayerOnce;
491         }
492     }
493 
494     private final ScoreUpdateObserverProxy mScoreUpdateObserverCallback =
495             new ScoreUpdateObserverProxy();
496 
497     @Nullable
498     private WifiConnectedNetworkScorerHolder mWifiConnectedNetworkScorerHolder;
499 
500     private final AdaptiveConnectivityEnabledSettingObserver
501             mAdaptiveConnectivityEnabledSettingObserver;
502 
WifiScoreReport(ScoringParams scoringParams, Clock clock, WifiMetrics wifiMetrics, ExtendedWifiInfo wifiInfo, WifiNative wifiNative, WifiBlocklistMonitor wifiBlocklistMonitor, WifiThreadRunner wifiThreadRunner, WifiScoreCard wifiScoreCard, DeviceConfigFacade deviceConfigFacade, Context context, AdaptiveConnectivityEnabledSettingObserver adaptiveConnectivityEnabledSettingObserver, String interfaceName, ExternalScoreUpdateObserverProxy externalScoreUpdateObserverProxy, WifiSettingsStore wifiSettingsStore, WifiGlobals wifiGlobals, ActiveModeWarden activeModeWarden, WifiConnectivityManager wifiConnectivityManager, WifiConfigManager wifiConfigManager)503     WifiScoreReport(ScoringParams scoringParams, Clock clock, WifiMetrics wifiMetrics,
504             ExtendedWifiInfo wifiInfo, WifiNative wifiNative,
505             WifiBlocklistMonitor wifiBlocklistMonitor,
506             WifiThreadRunner wifiThreadRunner, WifiScoreCard wifiScoreCard,
507             DeviceConfigFacade deviceConfigFacade, Context context,
508             AdaptiveConnectivityEnabledSettingObserver adaptiveConnectivityEnabledSettingObserver,
509             String interfaceName,
510             ExternalScoreUpdateObserverProxy externalScoreUpdateObserverProxy,
511             WifiSettingsStore wifiSettingsStore,
512             WifiGlobals wifiGlobals,
513             ActiveModeWarden activeModeWarden,
514             WifiConnectivityManager wifiConnectivityManager,
515             WifiConfigManager wifiConfigManager) {
516         mScoringParams = scoringParams;
517         mClock = clock;
518         mAdaptiveConnectivityEnabledSettingObserver = adaptiveConnectivityEnabledSettingObserver;
519         mAggressiveConnectedScore = new AggressiveConnectedScore(scoringParams, clock);
520         mVelocityBasedConnectedScore = new VelocityBasedConnectedScore(scoringParams, clock);
521         mWifiMetrics = wifiMetrics;
522         mWifiInfo = wifiInfo;
523         mWifiNative = wifiNative;
524         mWifiBlocklistMonitor = wifiBlocklistMonitor;
525         mWifiThreadRunner = wifiThreadRunner;
526         mWifiScoreCard = wifiScoreCard;
527         mDeviceConfigFacade = deviceConfigFacade;
528         mContext = context;
529         mInterfaceName = interfaceName;
530         mExternalScoreUpdateObserverProxy = externalScoreUpdateObserverProxy;
531         mWifiSettingsStore = wifiSettingsStore;
532         mWifiInfoNoReset = new WifiInfo(mWifiInfo);
533         mWifiGlobals = wifiGlobals;
534         mActiveModeWarden = activeModeWarden;
535         mWifiConnectivityManager = wifiConnectivityManager;
536         mWifiConfigManager = wifiConfigManager;
537     }
538 
539     /** Returns whether this scores primary network based on the role */
isPrimary()540     private boolean isPrimary() {
541         return mCurrentRole != null && mCurrentRole == ActiveModeManager.ROLE_CLIENT_PRIMARY;
542     }
543 
544     /**
545      * Reset the last calculated score.
546      */
reset()547     public void reset() {
548         mSessionNumber++;
549         mLegacyIntScore = isPrimary() ? ConnectedScore.WIFI_INITIAL_SCORE
550                 : ConnectedScore.WIFI_SECONDARY_INITIAL_SCORE;
551         mIsUsable = true;
552         mLastKnownNudCheckScore = isPrimary() ? ConnectedScore.WIFI_TRANSITION_SCORE
553                 : ConnectedScore.WIFI_SECONDARY_TRANSITION_SCORE;
554         mAggressiveConnectedScore.reset();
555         if (mVelocityBasedConnectedScore != null) {
556             mVelocityBasedConnectedScore.reset();
557         }
558         mLastDownwardBreachTimeMillis = 0;
559         mLastScoreBreachLowTimeMillis = INVALID_WALL_CLOCK_MILLIS;
560         mLastScoreBreachHighTimeMillis = INVALID_WALL_CLOCK_MILLIS;
561         mLastLowScoreScanTimestampMs = -1;
562         if (mVerboseLoggingEnabled) Log.d(TAG, "reset");
563     }
564 
565     /**
566      * Enable/Disable verbose logging in score report generation.
567      */
enableVerboseLogging(boolean enable)568     public void enableVerboseLogging(boolean enable) {
569         mVerboseLoggingEnabled = enable;
570     }
571 
572     /**
573      * Calculate wifi network score based on updated link layer stats and send the score to
574      * the WifiNetworkAgent.
575      *
576      * If the score has changed from the previous value, update the WifiNetworkAgent.
577      *
578      * Called periodically (POLL_RSSI_INTERVAL_MSECS) about every 3 seconds.
579      */
calculateAndReportScore()580     public void calculateAndReportScore() {
581         // Bypass AOSP scorer if Wifi connected network scorer is set
582         if (mWifiConnectedNetworkScorerHolder != null) {
583             return;
584         }
585 
586         if (mWifiInfo.getRssi() == mWifiInfo.INVALID_RSSI) {
587             Log.d(TAG, "Not reporting score because RSSI is invalid");
588             return;
589         }
590         int score;
591 
592         long millis = mClock.getWallClockMillis();
593         mVelocityBasedConnectedScore.updateUsingWifiInfo(mWifiInfo, millis);
594 
595         int s2 = mVelocityBasedConnectedScore.generateScore();
596         score = s2;
597 
598         final int transitionScore = isPrimary() ? ConnectedScore.WIFI_TRANSITION_SCORE
599                 : ConnectedScore.WIFI_SECONDARY_TRANSITION_SCORE;
600         final int maxScore = isPrimary() ? ConnectedScore.WIFI_MAX_SCORE
601                 : ConnectedScore.WIFI_MAX_SCORE - ConnectedScore.WIFI_SECONDARY_DELTA_SCORE;
602 
603         if (mWifiInfo.getScore() > transitionScore && score <= transitionScore
604                 && mWifiInfo.getSuccessfulTxPacketsPerSecond()
605                 >= mScoringParams.getYippeeSkippyPacketsPerSecond()
606                 && mWifiInfo.getSuccessfulRxPacketsPerSecond()
607                 >= mScoringParams.getYippeeSkippyPacketsPerSecond()
608         ) {
609             score = transitionScore + 1;
610         }
611 
612         if (mWifiInfo.getScore() > transitionScore && score <= transitionScore) {
613             // We don't want to trigger a downward breach unless the rssi is
614             // below the entry threshold.  There is noise in the measured rssi, and
615             // the kalman-filtered rssi is affected by the trend, so check them both.
616             // TODO(b/74613347) skip this if there are other indications to support the low score
617             int entry = mScoringParams.getEntryRssi(mWifiInfo.getFrequency());
618             if (mVelocityBasedConnectedScore.getFilteredRssi() >= entry
619                     || mWifiInfo.getRssi() >= entry) {
620                 // Stay a notch above the transition score to reduce ambiguity.
621                 score = transitionScore + 1;
622             }
623         }
624 
625         if (mWifiInfo.getScore() >= transitionScore && score < transitionScore) {
626             mLastDownwardBreachTimeMillis = millis;
627         } else if (mWifiInfo.getScore() < transitionScore && score >= transitionScore) {
628             // Staying at below transition score for a certain period of time
629             // to prevent going back to wifi network again in a short time.
630             long elapsedMillis = millis - mLastDownwardBreachTimeMillis;
631             if (elapsedMillis < MIN_TIME_TO_KEEP_BELOW_TRANSITION_SCORE_MILLIS) {
632                 score = mWifiInfo.getScore();
633             }
634         }
635         //sanitize boundaries
636         if (score > maxScore) {
637             score = maxScore;
638         }
639         if (score < 0) {
640             score = 0;
641         }
642 
643         if (score < mWifiGlobals.getWifiLowConnectedScoreThresholdToTriggerScanForMbb()
644                 && enoughTimePassedSinceLastLowConnectedScoreScan()
645                 && mActiveModeWarden.canRequestSecondaryTransientClientModeManager()) {
646             mLastLowScoreScanTimestampMs = mClock.getElapsedSinceBootMillis();
647             mWifiConnectivityManager.forceConnectivityScan(WIFI_WORK_SOURCE);
648         }
649 
650         // report score
651         mLegacyIntScore = score;
652         reportNetworkScoreToConnectivityServiceIfNecessary();
653         updateWifiMetrics(millis, s2);
654     }
655 
enoughTimePassedSinceLastLowConnectedScoreScan()656     private boolean enoughTimePassedSinceLastLowConnectedScoreScan() {
657         return mLastLowScoreScanTimestampMs == -1
658                 || mClock.getElapsedSinceBootMillis() - mLastLowScoreScanTimestampMs
659                 > (mWifiGlobals.getWifiLowConnectedScoreScanPeriodSeconds() * 1000);
660     }
661 
getCurrentNetId()662     private int getCurrentNetId() {
663         int netId = 0;
664         if (mNetworkAgent != null) {
665             final Network network = mNetworkAgent.getNetwork();
666             if (network != null) {
667                 netId = network.getNetId();
668             }
669         }
670         return netId;
671     }
672 
673     @Nullable
getCurrentNetCapabilities()674     private NetworkCapabilities getCurrentNetCapabilities() {
675         return mNetworkAgent == null ? null : mNetworkAgent.getCurrentNetworkCapabilities();
676     }
677 
getCurrentSessionId()678     private int getCurrentSessionId() {
679         return sessionIdFromNetId(getCurrentNetId());
680     }
681 
682     /**
683      * Encodes a network id into a scoring session id.
684      *
685      * We use a different numeric value for session id and the network id
686      * to make it clear that these are not the same thing. However, for
687      * easier debugging, the network id can be recovered by dropping the
688      * last decimal digit (at least until they get very, very, large).
689      */
sessionIdFromNetId(final int netId)690     public static int sessionIdFromNetId(final int netId) {
691         if (netId <= 0) return INVALID_SESSION_ID;
692         return (int) (((long) netId * 10 + (8 - (netId % 9))) % Integer.MAX_VALUE + 1);
693     }
694 
updateWifiMetrics(long now, int s2)695     private void updateWifiMetrics(long now, int s2) {
696         int netId = getCurrentNetId();
697 
698         mAggressiveConnectedScore.updateUsingWifiInfo(mWifiInfo, now);
699         int s1 = ((AggressiveConnectedScore) mAggressiveConnectedScore).generateScore();
700         logLinkMetrics(now, netId, s1, s2, mLegacyIntScore);
701 
702         if (mLegacyIntScore != mWifiInfo.getScore()) {
703             if (mVerboseLoggingEnabled) {
704                 Log.d(TAG, "report new wifi score " + mLegacyIntScore);
705             }
706             mWifiInfo.setScore(mLegacyIntScore);
707         }
708         mWifiMetrics.incrementWifiScoreCount(mInterfaceName, mLegacyIntScore);
709     }
710 
711     private static final double TIME_CONSTANT_MILLIS = 30.0e+3;
712     private static final long NUD_THROTTLE_MILLIS = 5000;
713     private long mLastKnownNudCheckTimeMillis = 0;
714     private int mLastKnownNudCheckScore = ConnectedScore.WIFI_TRANSITION_SCORE;
715     private int mNudYes = 0;    // Counts when we voted for a NUD
716     private int mNudCount = 0;  // Counts when we were told a NUD was sent
717 
718     /**
719      * Recommends that a layer 3 check be done
720      *
721      * The caller can use this to (help) decide that an IP reachability check
722      * is desirable. The check is not done here; that is the caller's responsibility.
723      *
724      * @return true to indicate that an IP reachability check is recommended
725      */
shouldCheckIpLayer()726     public boolean shouldCheckIpLayer() {
727         // Don't recommend if adaptive connectivity is disabled.
728         if (!mAdaptiveConnectivityEnabledSettingObserver.get()
729                 || !mWifiSettingsStore.isWifiScoringEnabled()) {
730             if (mVerboseLoggingEnabled) {
731                 Log.d(TAG, "Wifi scoring disabled - Don't check IP layer");
732             }
733             return false;
734         }
735         long millis = mClock.getWallClockMillis();
736         long deltaMillis = millis - mLastKnownNudCheckTimeMillis;
737         // Don't ever ask back-to-back - allow at least 5 seconds
738         // for the previous one to finish.
739         if (deltaMillis < NUD_THROTTLE_MILLIS) {
740             return false;
741         }
742         if (SdkLevel.isAtLeastS() && mWifiConnectedNetworkScorerHolder != null) {
743             if (!mWifiConnectedNetworkScorerHolder.getShouldCheckIpLayerOnce()) {
744                 return false;
745             }
746             mNudYes++;
747             return true;
748         }
749         int nud = mScoringParams.getNudKnob();
750         if (nud == 0) {
751             return false;
752         }
753         // nextNudBreach is the bar the score needs to cross before we ask for NUD
754         double nextNudBreach = ConnectedScore.WIFI_TRANSITION_SCORE;
755         if (mWifiConnectedNetworkScorerHolder == null) {
756             // nud is between 1 and 10 at this point
757             double deltaLevel = 11 - nud;
758             // If we were below threshold the last time we checked, then compute a new bar
759             // that starts down from there and decays exponentially back up to the steady-state
760             // bar. If 5 time constants have passed, we are 99% of the way there, so skip the math.
761             if (mLastKnownNudCheckScore < ConnectedScore.WIFI_TRANSITION_SCORE
762                     && deltaMillis < 5.0 * TIME_CONSTANT_MILLIS) {
763                 double a = Math.exp(-deltaMillis / TIME_CONSTANT_MILLIS);
764                 nextNudBreach =
765                         a * (mLastKnownNudCheckScore - deltaLevel) + (1.0 - a) * nextNudBreach;
766             }
767         }
768         if (mLegacyIntScore >= nextNudBreach) {
769             return false;
770         }
771         mNudYes++;
772         return true;
773     }
774 
775     /**
776      * Should be called when a reachability check has been issued
777      *
778      * When the caller has requested an IP reachability check, calling this will
779      * help to rate-limit requests via shouldCheckIpLayer()
780      */
noteIpCheck()781     public void noteIpCheck() {
782         long millis = mClock.getWallClockMillis();
783         mLastKnownNudCheckTimeMillis = millis;
784         mLastKnownNudCheckScore = mLegacyIntScore;
785         mNudCount++;
786         // Make sure that only one NUD operation can be triggered.
787         if (mWifiConnectedNetworkScorerHolder != null) {
788             mWifiConnectedNetworkScorerHolder.setShouldCheckIpLayerOnce(false);
789         }
790     }
791 
792     /**
793      * Data for dumpsys
794      *
795      * These are stored as csv formatted lines
796      */
797     private LinkedList<String> mLinkMetricsHistory = new LinkedList<String>();
798 
799     /**
800      * Data logging for dumpsys
801      */
logLinkMetrics(long now, int netId, int s1, int s2, int score)802     private void logLinkMetrics(long now, int netId, int s1, int s2, int score) {
803         if (now < FIRST_REASONABLE_WALL_CLOCK) return;
804         double filteredRssi = -1;
805         double rssiThreshold = -1;
806         if (mWifiConnectedNetworkScorerHolder == null) {
807             filteredRssi = mVelocityBasedConnectedScore.getFilteredRssi();
808             rssiThreshold = mVelocityBasedConnectedScore.getAdjustedRssiThreshold();
809         }
810         int freq = mWifiInfo.getFrequency();
811         int txLinkSpeed = mWifiInfo.getLinkSpeed();
812         int rxLinkSpeed = mWifiInfo.getRxLinkSpeedMbps();
813         WifiScoreCard.PerNetwork network = mWifiScoreCard.lookupNetwork(mWifiInfo.getSSID());
814         int txThroughputMbps = network.getTxLinkBandwidthKbps() / 1000;
815         int rxThroughputMbps = network.getRxLinkBandwidthKbps() / 1000;
816         double txSuccessRate = mWifiInfo.getSuccessfulTxPacketsPerSecond();
817         double txRetriesRate = mWifiInfo.getRetriedTxPacketsPerSecond();
818         double txBadRate = mWifiInfo.getLostTxPacketsPerSecond();
819         double rxSuccessRate = mWifiInfo.getSuccessfulRxPacketsPerSecond();
820         long totalBeaconRx = mWifiMetrics.getTotalBeaconRxCount();
821         String s;
822         try {
823             Calendar c = Calendar.getInstance();
824             c.setTimeInMillis(now);
825             // Date format: "%tm-%td %tH:%tM:%tS.%tL"
826             String timestamp = new StringBuilder().append(c.get(Calendar.MONTH)).append("-")
827                     .append(c.get(Calendar.DAY_OF_MONTH)).append(" ")
828                     .append(c.get(Calendar.HOUR_OF_DAY)).append(":")
829                     .append(c.get(Calendar.MINUTE)).append(":")
830                     .append(c.get(Calendar.SECOND)).append(".")
831                     .append(c.get(Calendar.MILLISECOND)).toString();
832             s = timestamp + "," + mSessionNumber + "," + netId + "," + mWifiInfo.getRssi()
833                     + "," + Math.round(filteredRssi * 100) / 100 + "," + rssiThreshold
834                     + "," + freq + "," + txLinkSpeed
835                     + "," + rxLinkSpeed + "," + txThroughputMbps
836                     + "," + rxThroughputMbps + "," + totalBeaconRx
837                     + "," + Math.round(txSuccessRate * 100) / 100
838                     + "," + Math.round(txRetriesRate * 100) / 100
839                     + "," + Math.round(txBadRate * 100) / 100
840                     + "," + Math.round(rxSuccessRate * 100) / 100
841                     + "," + mNudYes + "," + mNudCount + "," + s1 + "," + s2 + "," + score;
842         } catch (Exception e) {
843             Log.e(TAG, "format problem", e);
844             return;
845         }
846         synchronized (mLinkMetricsHistory) {
847             mLinkMetricsHistory.add(s);
848             while (mLinkMetricsHistory.size() > DUMPSYS_ENTRY_COUNT_LIMIT) {
849                 mLinkMetricsHistory.removeFirst();
850             }
851         }
852     }
853 
854     /**
855      * Tag to be used in dumpsys request
856      */
857     public static final String DUMP_ARG = "WifiScoreReport";
858 
859     /**
860      * Dump logged signal strength and traffic measurements.
861      * @param fd unused
862      * @param pw PrintWriter for writing dump to
863      * @param args unused
864      */
dump(FileDescriptor fd, PrintWriter pw, String[] args)865     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
866         LinkedList<String> history;
867         synchronized (mLinkMetricsHistory) {
868             history = new LinkedList<>(mLinkMetricsHistory);
869         }
870         pw.println("time,session,netid,rssi,filtered_rssi,rssi_threshold,freq,txLinkSpeed,"
871                 + "rxLinkSpeed,txTput,rxTput,bcnCnt,tx_good,tx_retry,tx_bad,rx_pps,nudrq,nuds,"
872                 + "s1,s2,score");
873         for (String line : history) {
874             pw.println(line);
875         }
876         history.clear();
877         pw.println("externalScorerActive=" + (mWifiConnectedNetworkScorerHolder != null));
878         pw.println("mShouldReduceNetworkScore=" + mShouldReduceNetworkScore);
879     }
880 
881     /**
882      * Set a scorer for Wi-Fi connected network score handling.
883      * @param binder
884      * @param scorer
885      */
setWifiConnectedNetworkScorer(IBinder binder, IWifiConnectedNetworkScorer scorer)886     public boolean setWifiConnectedNetworkScorer(IBinder binder,
887             IWifiConnectedNetworkScorer scorer) {
888         if (binder == null || scorer == null) return false;
889         // Enforce that only a single scorer can be set successfully.
890         if (mWifiConnectedNetworkScorerHolder != null) {
891             Log.e(TAG, "Failed to set current scorer because one scorer is already set");
892             return false;
893         }
894         WifiConnectedNetworkScorerHolder scorerHolder =
895                 new WifiConnectedNetworkScorerHolder(binder, scorer);
896         if (!scorerHolder.linkScorerToDeath()) {
897             return false;
898         }
899         mWifiConnectedNetworkScorerHolder = scorerHolder;
900         mWifiGlobals.setUsingExternalScorer(true);
901 
902         // Register to receive updates from external scorer.
903         mExternalScoreUpdateObserverProxy.registerCallback(mScoreUpdateObserverCallback);
904 
905         // Disable AOSP scorer
906         mVelocityBasedConnectedScore = null;
907         mWifiMetrics.setIsExternalWifiScorerOn(true);
908         // If there is already a connection, start a new session
909         final int netId = getCurrentNetId();
910         if (netId > 0 && !mShouldReduceNetworkScore) {
911             startConnectedNetworkScorer(netId, mIsUserSelected);
912         }
913         return true;
914     }
915 
916     /**
917      * Clear an existing scorer for Wi-Fi connected network score handling.
918      */
clearWifiConnectedNetworkScorer()919     public void clearWifiConnectedNetworkScorer() {
920         if (mWifiConnectedNetworkScorerHolder == null) {
921             return;
922         }
923         mWifiConnectedNetworkScorerHolder.reset();
924         revertToDefaultConnectedScorer();
925     }
926 
927     /**
928      * If this connection is not going to be the default route on the device when cellular is
929      * present, don't send this connection to external scorer for scoring (since scoring only makes
930      * sense if we need to score wifi vs cellular to determine the default network).
931      *
932      * Hence, we ignore local only or restricted wifi connections.
933      * @return true if the connection is local only or restricted, false otherwise.
934      */
isLocalOnlyOrRestrictedConnection()935     private boolean isLocalOnlyOrRestrictedConnection() {
936         final NetworkCapabilities nc = getCurrentNetCapabilities();
937         if (nc == null) return false;
938         if (SdkLevel.isAtLeastS()) {
939             // restricted connection support only added in S.
940             if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PAID)
941                     || nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE)) {
942                 // restricted connection.
943                 Log.v(TAG, "Restricted connection, ignore.");
944                 return true;
945             }
946         }
947         if (!nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
948             // local only connection.
949             Log.v(TAG, "Local only connection, ignore.");
950             return true;
951         }
952         return false;
953     }
954 
955     /**
956      * Start the registered Wi-Fi connected network scorer.
957      * @param netId identifies the current android.net.Network
958      */
startConnectedNetworkScorer(int netId, boolean isUserSelected)959     public void startConnectedNetworkScorer(int netId, boolean isUserSelected) {
960         mIsUserSelected = isUserSelected;
961         final int sessionId = getCurrentSessionId();
962         if (mWifiConnectedNetworkScorerHolder == null
963                 || netId != getCurrentNetId()
964                 || isLocalOnlyOrRestrictedConnection()
965                 || sessionId == INVALID_SESSION_ID) {
966             Log.w(TAG, "Cannot start external scoring"
967                     + " netId=" + netId
968                     + " currentNetId=" + getCurrentNetId()
969                     + " currentNetCapabilities=" + getCurrentNetCapabilities()
970                     + " sessionId=" + sessionId);
971             return;
972         }
973         mCurrentWifiConfiguration = mWifiConfigManager.getConfiguredNetwork(
974                 mWifiInfo.getNetworkId());
975         mWifiInfo.setScore(isPrimary() ? ConnectedScore.WIFI_MAX_SCORE
976                 : ConnectedScore.WIFI_SECONDARY_MAX_SCORE);
977         mWifiConnectedNetworkScorerHolder.startSession(sessionId, mIsUserSelected);
978         mWifiInfoNoReset.setBSSID(mWifiInfo.getBSSID());
979         mWifiInfoNoReset.setSSID(mWifiInfo.getWifiSsid());
980         mWifiInfoNoReset.setRssi(mWifiInfo.getRssi());
981         mLastScoreBreachLowTimeMillis = INVALID_WALL_CLOCK_MILLIS;
982         mLastScoreBreachHighTimeMillis = INVALID_WALL_CLOCK_MILLIS;
983     }
984 
985     /**
986      * Stop the registered Wi-Fi connected network scorer.
987      */
stopConnectedNetworkScorer()988     public void stopConnectedNetworkScorer() {
989         mNetworkAgent = null;
990         if (mWifiConnectedNetworkScorerHolder == null) {
991             return;
992         }
993         mWifiConnectedNetworkScorerHolder.stopSession();
994 
995         long millis = mClock.getWallClockMillis();
996         // Blocklist the current BSS
997         if ((mLastScoreBreachLowTimeMillis != INVALID_WALL_CLOCK_MILLIS)
998                 && ((millis - mLastScoreBreachLowTimeMillis)
999                         >= MIN_TIME_TO_WAIT_BEFORE_BLOCKLIST_BSSID_MILLIS)) {
1000             mWifiBlocklistMonitor.handleBssidConnectionFailure(mWifiInfo.getBSSID(),
1001                     mCurrentWifiConfiguration,
1002                     WifiBlocklistMonitor.REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE,
1003                     mWifiInfo.getRssi());
1004             mLastScoreBreachLowTimeMillis = INVALID_WALL_CLOCK_MILLIS;
1005         }
1006     }
1007 
1008     /**
1009      * Set NetworkAgent
1010      */
setNetworkAgent(WifiNetworkAgent agent)1011     public void setNetworkAgent(WifiNetworkAgent agent) {
1012         WifiNetworkAgent oldAgent = mNetworkAgent;
1013         mNetworkAgent = agent;
1014         // if mNetworkAgent was null previously, then the score wasn't sent to ConnectivityService.
1015         // Send it now that the NetworkAgent has been set.
1016         if (oldAgent == null && mNetworkAgent != null) {
1017             sendNetworkScore();
1018         }
1019     }
1020 
1021     /** Get cached score */
1022     @VisibleForTesting
1023     @RequiresApi(Build.VERSION_CODES.S)
getScore()1024     public NetworkScore getScore() {
1025         return getScoreBuilder().build();
1026     }
1027 
1028     @RequiresApi(Build.VERSION_CODES.S)
getScoreBuilder()1029     private NetworkScore.Builder getScoreBuilder() {
1030         // We should force keep connected for a MBB CMM which is not lingering.
1031         boolean shouldForceKeepConnected =
1032                 mCurrentRole == ActiveModeManager.ROLE_CLIENT_SECONDARY_TRANSIENT
1033                         && !mShouldReduceNetworkScore;
1034         int keepConnectedReason =
1035                 shouldForceKeepConnected
1036                         ? NetworkScore.KEEP_CONNECTED_FOR_HANDOVER
1037                         : NetworkScore.KEEP_CONNECTED_NONE;
1038         boolean exiting = SdkLevel.isAtLeastS() && mWifiConnectedNetworkScorerHolder != null
1039                 ? !mIsUsable : mLegacyIntScore < ConnectedScore.WIFI_TRANSITION_SCORE;
1040         return new NetworkScore.Builder()
1041                 .setLegacyInt(mShouldReduceNetworkScore ? LINGERING_SCORE : mLegacyIntScore)
1042                 .setTransportPrimary(mCurrentRole == ActiveModeManager.ROLE_CLIENT_PRIMARY)
1043                 .setExiting(exiting)
1044                 .setKeepConnectedReason(keepConnectedReason);
1045     }
1046 
1047     /** Get legacy int score. */
1048     @VisibleForTesting
1049     public int getLegacyIntScore() {
1050         // When S Wifi module is run on R:
1051         // - mShouldReduceNetworkScore is useless since MBB doesn't exist on R, so there isn't any
1052         //   forced lingering.
1053         // - mIsUsable can't be set as notifyStatusUpdate() for external scorer didn't exist on R
1054         //   SDK (assume that only R platform + S Wifi module + R external scorer is possible,
1055         //   and R platform + S Wifi module + S external scorer is not possible)
1056         // Thus, it's ok to return the raw int score on R.
1057         return mLegacyIntScore;
1058     }
1059 
1060     /** Get counts when we voted for a NUD. */
1061     @VisibleForTesting
1062     public int getNudYes() {
1063         return mNudYes;
1064     }
1065 
1066     private void revertToDefaultConnectedScorer() {
1067         Log.d(TAG, "Using VelocityBasedConnectedScore");
1068         mVelocityBasedConnectedScore = new VelocityBasedConnectedScore(mScoringParams, mClock);
1069         mWifiConnectedNetworkScorerHolder = null;
1070         mWifiGlobals.setUsingExternalScorer(false);
1071         mExternalScoreUpdateObserverProxy.unregisterCallback(mScoreUpdateObserverCallback);
1072         mWifiMetrics.setIsExternalWifiScorerOn(false);
1073     }
1074 
1075     /**
1076      * This is a function of {@link #mCurrentRole} {@link #mShouldReduceNetworkScore}, and
1077      * {@link #mLegacyIntScore}, and should be called when any of them changes.
1078      */
1079     private void sendNetworkScore() {
1080         if (mNetworkAgent == null) {
1081             return;
1082         }
1083         if (SdkLevel.isAtLeastS()) {
1084             // NetworkScore was introduced in S
1085             mNetworkAgent.sendNetworkScore(getScore());
1086         } else {
1087             mNetworkAgent.sendNetworkScore(getLegacyIntScore());
1088         }
1089     }
1090 
1091     /** Called when the owner {@link ConcreteClientModeManager}'s role changes. */
1092     public void onRoleChanged(@Nullable ClientRole role) {
1093         mCurrentRole = role;
1094         if (mAggressiveConnectedScore != null) mAggressiveConnectedScore.onRoleChanged(role);
1095         if (mVelocityBasedConnectedScore != null) mVelocityBasedConnectedScore.onRoleChanged(role);
1096         sendNetworkScore();
1097     }
1098 }
1099