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