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