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