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