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