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