1 /* 2 * Copyright 2018 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 android.net.wifi.WifiInfo.DEFAULT_MAC_ADDRESS; 20 import static android.net.wifi.WifiInfo.INVALID_RSSI; 21 import static android.net.wifi.WifiInfo.LINK_SPEED_UNKNOWN; 22 23 import static com.android.server.wifi.WifiHealthMonitor.HEALTH_MONITOR_COUNT_TX_SPEED_MIN_MBPS; 24 import static com.android.server.wifi.WifiHealthMonitor.HEALTH_MONITOR_MIN_TX_PACKET_PER_SEC; 25 import static com.android.server.wifi.WifiHealthMonitor.REASON_ASSOC_REJECTION; 26 import static com.android.server.wifi.WifiHealthMonitor.REASON_ASSOC_TIMEOUT; 27 import static com.android.server.wifi.WifiHealthMonitor.REASON_AUTH_FAILURE; 28 import static com.android.server.wifi.WifiHealthMonitor.REASON_CONNECTION_FAILURE; 29 import static com.android.server.wifi.WifiHealthMonitor.REASON_CONNECTION_FAILURE_DISCONNECTION; 30 import static com.android.server.wifi.WifiHealthMonitor.REASON_DISCONNECTION_NONLOCAL; 31 import static com.android.server.wifi.WifiHealthMonitor.REASON_NO_FAILURE; 32 import static com.android.server.wifi.WifiHealthMonitor.REASON_SHORT_CONNECTION_NONLOCAL; 33 34 import android.annotation.IntDef; 35 import android.annotation.NonNull; 36 import android.annotation.Nullable; 37 import android.content.Context; 38 import android.net.MacAddress; 39 import android.net.wifi.ScanResult; 40 import android.net.wifi.SupplicantState; 41 import android.net.wifi.WifiManager; 42 import android.util.ArrayMap; 43 import android.util.Base64; 44 import android.util.LocalLog; 45 import android.util.Log; 46 import android.util.Pair; 47 import android.util.SparseLongArray; 48 49 import com.android.internal.annotations.VisibleForTesting; 50 import com.android.internal.util.Preconditions; 51 import com.android.server.wifi.WifiBlocklistMonitor.FailureReason; 52 import com.android.server.wifi.WifiHealthMonitor.FailureStats; 53 import com.android.server.wifi.proto.WifiScoreCardProto; 54 import com.android.server.wifi.proto.WifiScoreCardProto.AccessPoint; 55 import com.android.server.wifi.proto.WifiScoreCardProto.BandwidthStats; 56 import com.android.server.wifi.proto.WifiScoreCardProto.BandwidthStatsAll; 57 import com.android.server.wifi.proto.WifiScoreCardProto.BandwidthStatsAllLevel; 58 import com.android.server.wifi.proto.WifiScoreCardProto.BandwidthStatsAllLink; 59 import com.android.server.wifi.proto.WifiScoreCardProto.ConnectionStats; 60 import com.android.server.wifi.proto.WifiScoreCardProto.Event; 61 import com.android.server.wifi.proto.WifiScoreCardProto.HistogramBucket; 62 import com.android.server.wifi.proto.WifiScoreCardProto.Network; 63 import com.android.server.wifi.proto.WifiScoreCardProto.NetworkList; 64 import com.android.server.wifi.proto.WifiScoreCardProto.NetworkStats; 65 import com.android.server.wifi.proto.WifiScoreCardProto.SecurityType; 66 import com.android.server.wifi.proto.WifiScoreCardProto.Signal; 67 import com.android.server.wifi.proto.WifiScoreCardProto.UnivariateStatistic; 68 import com.android.server.wifi.proto.nano.WifiMetricsProto.BandwidthEstimatorStats; 69 import com.android.server.wifi.util.IntHistogram; 70 import com.android.server.wifi.util.LruList; 71 import com.android.server.wifi.util.NativeUtil; 72 import com.android.server.wifi.util.RssiUtil; 73 import com.android.wifi.resources.R; 74 75 import com.google.protobuf.ByteString; 76 import com.google.protobuf.InvalidProtocolBufferException; 77 78 import java.io.FileDescriptor; 79 import java.io.PrintWriter; 80 import java.lang.annotation.Retention; 81 import java.lang.annotation.RetentionPolicy; 82 import java.nio.ByteBuffer; 83 import java.security.MessageDigest; 84 import java.security.NoSuchAlgorithmException; 85 import java.util.ArrayList; 86 import java.util.Iterator; 87 import java.util.List; 88 import java.util.Map; 89 import java.util.Objects; 90 import java.util.concurrent.atomic.AtomicReference; 91 import java.util.stream.Collectors; 92 93 import javax.annotation.concurrent.NotThreadSafe; 94 95 /** 96 * Retains statistical information about the performance of various 97 * access points and networks, as experienced by this device. 98 * 99 * The purpose is to better inform future network selection and switching 100 * by this device and help health monitor detect network issues. 101 */ 102 @NotThreadSafe 103 public class WifiScoreCard { 104 105 public static final String DUMP_ARG = "WifiScoreCard"; 106 107 private static final String TAG = "WifiScoreCard"; 108 private boolean mVerboseLoggingEnabled = false; 109 110 @VisibleForTesting 111 boolean mPersistentHistograms = true; 112 113 private static final int TARGET_IN_MEMORY_ENTRIES = 50; 114 private static final int UNKNOWN_REASON = -1; 115 116 public static final String PER_BSSID_DATA_NAME = "scorecard.proto"; 117 public static final String PER_NETWORK_DATA_NAME = "perNetworkData"; 118 119 static final int INSUFFICIENT_RECENT_STATS = 0; 120 static final int SUFFICIENT_RECENT_STATS_ONLY = 1; 121 static final int SUFFICIENT_RECENT_PREV_STATS = 2; 122 123 private static final int MAX_FREQUENCIES_PER_SSID = 10; 124 private static final int MAX_TRAFFIC_STATS_POLL_TIME_DELTA_MS = 6_000; 125 126 private final Clock mClock; 127 private final String mL2KeySeed; 128 private MemoryStore mMemoryStore; 129 private final DeviceConfigFacade mDeviceConfigFacade; 130 private final FrameworkFacade mFrameworkFacade; 131 private final Context mContext; 132 private final LocalLog mLocalLog = new LocalLog(256); 133 private final long[][][] mL2ErrorAccPercent = 134 new long[NUM_LINK_BAND][NUM_LINK_DIRECTION][NUM_SIGNAL_LEVEL]; 135 private final long[][][] mBwEstErrorAccPercent = 136 new long[NUM_LINK_BAND][NUM_LINK_DIRECTION][NUM_SIGNAL_LEVEL]; 137 private final long[][][] mBwEstValue = 138 new long[NUM_LINK_BAND][NUM_LINK_DIRECTION][NUM_SIGNAL_LEVEL]; 139 private final int[][][] mBwEstCount = 140 new int[NUM_LINK_BAND][NUM_LINK_DIRECTION][NUM_SIGNAL_LEVEL]; 141 142 @VisibleForTesting 143 static final int[] RSSI_BUCKETS = intsInRange(-100, -20); 144 intsInRange(int min, int max)145 private static int[] intsInRange(int min, int max) { 146 int[] a = new int[max - min + 1]; 147 for (int i = 0; i < a.length; i++) { 148 a[i] = min + i; 149 } 150 return a; 151 } 152 153 /** Our view of the memory store */ 154 public interface MemoryStore { 155 /** Requests a read, with asynchronous reply */ read(String key, String name, BlobListener blobListener)156 void read(String key, String name, BlobListener blobListener); 157 /** Requests a write, does not wait for completion */ write(String key, String name, byte[] value)158 void write(String key, String name, byte[] value); 159 /** Sets the cluster identifier */ setCluster(String key, String cluster)160 void setCluster(String key, String cluster); 161 /** Requests removal of all entries matching the cluster */ removeCluster(String cluster)162 void removeCluster(String cluster); 163 } 164 /** Asynchronous response to a read request */ 165 public interface BlobListener { 166 /** Provides the previously stored value, or null if none */ onBlobRetrieved(@ullable byte[] value)167 void onBlobRetrieved(@Nullable byte[] value); 168 } 169 170 /** 171 * Installs a memory store. 172 * 173 * Normally this happens just once, shortly after we start. But wifi can 174 * come up before the disk is ready, and we might not yet have a valid wall 175 * clock when we start up, so we need to be prepared to begin recording data 176 * even if the MemoryStore is not yet available. 177 * 178 * When the store is installed for the first time, we want to merge any 179 * recently recorded data together with data already in the store. But if 180 * the store restarts and has to be reinstalled, we don't want to do 181 * this merge, because that would risk double-counting the old data. 182 * 183 */ installMemoryStore(@onNull MemoryStore memoryStore)184 public void installMemoryStore(@NonNull MemoryStore memoryStore) { 185 Preconditions.checkNotNull(memoryStore); 186 if (mMemoryStore == null) { 187 mMemoryStore = memoryStore; 188 Log.i(TAG, "Installing MemoryStore"); 189 requestReadForAllChanged(); 190 } else { 191 mMemoryStore = memoryStore; 192 Log.e(TAG, "Reinstalling MemoryStore"); 193 // Our caller will call doWrites() eventually, so nothing more to do here. 194 } 195 } 196 197 /** 198 * Enable/Disable verbose logging. 199 * 200 * @param verbose true to enable and false to disable. 201 */ enableVerboseLogging(boolean verbose)202 public void enableVerboseLogging(boolean verbose) { 203 mVerboseLoggingEnabled = verbose; 204 } 205 206 @VisibleForTesting 207 static final long TS_NONE = -1; 208 209 /** Tracks the connection status per Wifi interface. */ 210 private static final class IfaceInfo { 211 /** 212 * Timestamp of the start of the most recent connection attempt. 213 * 214 * Based on mClock.getElapsedSinceBootMillis(). 215 * 216 * This is for calculating the time to connect and the duration of the connection. 217 * Any negative value means we are not currently connected. 218 */ 219 public long tsConnectionAttemptStart = TS_NONE; 220 221 /** 222 * Timestamp captured when we find out about a firmware roam 223 */ 224 public long tsRoam = TS_NONE; 225 226 /** 227 * Becomes true the first time we see a poll with a valid RSSI in a connection 228 */ 229 public boolean polled = false; 230 231 /** 232 * Records validation success for the current connection. 233 * 234 * We want to gather statistics only on the first success. 235 */ 236 public boolean validatedThisConnectionAtLeastOnce = false; 237 238 /** 239 * A note to ourself that we are attempting a network switch 240 */ 241 public boolean attemptingSwitch = false; 242 243 /** 244 * SSID of currently connected or connecting network. Used during disconnection 245 */ 246 public String ssidCurr = ""; 247 /** 248 * SSID of previously connected network. Used during disconnection when connection attempt 249 * of current network is issued before the disconnection of previous network. 250 */ 251 public String ssidPrev = ""; 252 /** 253 * A flag that notes that current disconnection is not generated by wpa_supplicant 254 * which may indicate abnormal disconnection. 255 */ 256 public boolean nonlocalDisconnection = false; 257 public int disconnectionReason; 258 259 public long firmwareAlertTimeMs = TS_NONE; 260 } 261 262 /** 263 * String key: iface name 264 * IfaceInfo value: current status of iface 265 */ 266 private final Map<String, IfaceInfo> mIfaceToInfoMap = new ArrayMap<>(); 267 268 /** Gets the IfaceInfo, or create it if it doesn't exist. */ getIfaceInfo(String ifaceName)269 private IfaceInfo getIfaceInfo(String ifaceName) { 270 return mIfaceToInfoMap.computeIfAbsent(ifaceName, k -> new IfaceInfo()); 271 } 272 273 /** 274 * @param clock is the time source 275 * @param l2KeySeed is for making our L2Keys usable only on this device 276 */ WifiScoreCard(Clock clock, String l2KeySeed, DeviceConfigFacade deviceConfigFacade, FrameworkFacade frameworkFacade, Context context)277 public WifiScoreCard(Clock clock, String l2KeySeed, DeviceConfigFacade deviceConfigFacade, 278 FrameworkFacade frameworkFacade, Context context) { 279 mClock = clock; 280 mContext = context; 281 mL2KeySeed = l2KeySeed; 282 mPlaceholderPerBssid = new PerBssid("", MacAddress.fromString(DEFAULT_MAC_ADDRESS)); 283 mPlaceholderPerNetwork = new PerNetwork(""); 284 mDeviceConfigFacade = deviceConfigFacade; 285 mFrameworkFacade = frameworkFacade; 286 } 287 288 /** 289 * Gets the L2Key and GroupHint associated with the connection. 290 */ getL2KeyAndGroupHint(ExtendedWifiInfo wifiInfo)291 public @NonNull Pair<String, String> getL2KeyAndGroupHint(ExtendedWifiInfo wifiInfo) { 292 PerBssid perBssid = lookupBssid(wifiInfo.getSSID(), wifiInfo.getBSSID()); 293 if (perBssid == mPlaceholderPerBssid) { 294 return new Pair<>(null, null); 295 } 296 return new Pair<>(perBssid.getL2Key(), groupHintFromSsid(perBssid.ssid)); 297 } 298 299 /** 300 * Computes the GroupHint associated with the given ssid. 301 */ groupHintFromSsid(String ssid)302 public @NonNull String groupHintFromSsid(String ssid) { 303 final long groupIdHash = computeHashLong(ssid, mPlaceholderPerBssid.bssid, mL2KeySeed); 304 return groupHintFromLong(groupIdHash); 305 } 306 307 /** Handle network disconnection. */ resetConnectionState(String ifaceName)308 public void resetConnectionState(String ifaceName) { 309 IfaceInfo ifaceInfo = getIfaceInfo(ifaceName); 310 noteDisconnectionForIface(ifaceInfo); 311 resetConnectionStateForIfaceInternal(ifaceInfo, true); 312 } 313 314 /** Handle shutdown event. */ resetAllConnectionStates()315 public void resetAllConnectionStates() { 316 for (IfaceInfo ifaceInfo : mIfaceToInfoMap.values()) { 317 noteDisconnectionForIface(ifaceInfo); 318 resetConnectionStateForIfaceInternal(ifaceInfo, true); 319 } 320 } 321 noteDisconnectionForIface(IfaceInfo ifaceInfo)322 private void noteDisconnectionForIface(IfaceInfo ifaceInfo) { 323 String ssidDisconnected = ifaceInfo.attemptingSwitch 324 ? ifaceInfo.ssidPrev : ifaceInfo.ssidCurr; 325 updatePerNetwork(Event.DISCONNECTION, ssidDisconnected, INVALID_RSSI, LINK_SPEED_UNKNOWN, 326 UNKNOWN_REASON, ifaceInfo); 327 if (mVerboseLoggingEnabled && ifaceInfo.tsConnectionAttemptStart > TS_NONE 328 && !ifaceInfo.attemptingSwitch) { 329 Log.v(TAG, "handleNetworkDisconnect", new Exception()); 330 } 331 } 332 resetAllConnectionStatesInternal()333 private void resetAllConnectionStatesInternal() { 334 for (IfaceInfo ifaceInfo : mIfaceToInfoMap.values()) { 335 resetConnectionStateForIfaceInternal(ifaceInfo, false); 336 } 337 } 338 339 /** 340 * @param calledFromResetConnectionState says the call is from outside the class, 341 * indicating that we need to respect the value of mAttemptingSwitch. 342 */ resetConnectionStateForIfaceInternal(IfaceInfo ifaceInfo, boolean calledFromResetConnectionState)343 private void resetConnectionStateForIfaceInternal(IfaceInfo ifaceInfo, 344 boolean calledFromResetConnectionState) { 345 if (!calledFromResetConnectionState) { 346 ifaceInfo.attemptingSwitch = false; 347 } 348 if (!ifaceInfo.attemptingSwitch) { 349 ifaceInfo.tsConnectionAttemptStart = TS_NONE; 350 } 351 ifaceInfo.tsRoam = TS_NONE; 352 ifaceInfo.polled = false; 353 ifaceInfo.validatedThisConnectionAtLeastOnce = false; 354 ifaceInfo.nonlocalDisconnection = false; 355 ifaceInfo.firmwareAlertTimeMs = TS_NONE; 356 } 357 358 /** 359 * Updates perBssid using relevant parts of WifiInfo 360 * 361 * @param wifiInfo object holding relevant values. 362 */ updatePerBssid(WifiScoreCardProto.Event event, ExtendedWifiInfo wifiInfo)363 private void updatePerBssid(WifiScoreCardProto.Event event, ExtendedWifiInfo wifiInfo) { 364 PerBssid perBssid = lookupBssid(wifiInfo.getSSID(), wifiInfo.getBSSID()); 365 perBssid.updateEventStats(event, 366 wifiInfo.getFrequency(), 367 wifiInfo.getRssi(), 368 wifiInfo.getLinkSpeed(), 369 wifiInfo.getIfaceName()); 370 perBssid.setNetworkConfigId(wifiInfo.getNetworkId()); 371 logd("BSSID update " + event + " ID: " + perBssid.id + " " + wifiInfo); 372 } 373 374 /** 375 * Updates perNetwork with SSID, current RSSI and failureReason. failureReason is meaningful 376 * only during connection failure. 377 */ updatePerNetwork(WifiScoreCardProto.Event event, String ssid, int rssi, int txSpeed, int failureReason, IfaceInfo ifaceInfo)378 private void updatePerNetwork(WifiScoreCardProto.Event event, String ssid, int rssi, 379 int txSpeed, int failureReason, IfaceInfo ifaceInfo) { 380 PerNetwork perNetwork = lookupNetwork(ssid); 381 logd("network update " + event + ((ssid == null) ? " " : " " 382 + ssid) + " ID: " + perNetwork.id + " RSSI " + rssi + " txSpeed " + txSpeed); 383 perNetwork.updateEventStats(event, rssi, txSpeed, failureReason, ifaceInfo); 384 } 385 386 /** 387 * Updates the score card after a signal poll 388 * 389 * @param wifiInfo object holding relevant values 390 */ noteSignalPoll(@onNull ExtendedWifiInfo wifiInfo)391 public void noteSignalPoll(@NonNull ExtendedWifiInfo wifiInfo) { 392 IfaceInfo ifaceInfo = getIfaceInfo(wifiInfo.getIfaceName()); 393 if (!ifaceInfo.polled && wifiInfo.getRssi() != INVALID_RSSI) { 394 updatePerBssid(Event.FIRST_POLL_AFTER_CONNECTION, wifiInfo); 395 ifaceInfo.polled = true; 396 } 397 updatePerBssid(Event.SIGNAL_POLL, wifiInfo); 398 int validTxSpeed = geTxLinkSpeedWithSufficientTxRate(wifiInfo); 399 updatePerNetwork(Event.SIGNAL_POLL, wifiInfo.getSSID(), wifiInfo.getRssi(), 400 validTxSpeed, UNKNOWN_REASON, ifaceInfo); 401 if (ifaceInfo.tsRoam > TS_NONE && wifiInfo.getRssi() != INVALID_RSSI) { 402 long duration = mClock.getElapsedSinceBootMillis() - ifaceInfo.tsRoam; 403 if (duration >= SUCCESS_MILLIS_SINCE_ROAM) { 404 updatePerBssid(Event.ROAM_SUCCESS, wifiInfo); 405 ifaceInfo.tsRoam = TS_NONE; 406 doWritesBssid(); 407 } 408 } 409 } 410 geTxLinkSpeedWithSufficientTxRate(@onNull ExtendedWifiInfo wifiInfo)411 private int geTxLinkSpeedWithSufficientTxRate(@NonNull ExtendedWifiInfo wifiInfo) { 412 int txRate = (int) Math.ceil(wifiInfo.getSuccessfulTxPacketsPerSecond() 413 + wifiInfo.getLostTxPacketsPerSecond() 414 + wifiInfo.getRetriedTxPacketsPerSecond()); 415 int txSpeed = wifiInfo.getTxLinkSpeedMbps(); 416 logd("txRate: " + txRate + " txSpeed: " + txSpeed); 417 return (txRate >= HEALTH_MONITOR_MIN_TX_PACKET_PER_SEC) ? txSpeed : LINK_SPEED_UNKNOWN; 418 } 419 420 /** Wait a few seconds before considering the roam successful */ 421 private static final long SUCCESS_MILLIS_SINCE_ROAM = 4_000; 422 423 /** 424 * Updates the score card after IP configuration 425 * 426 * @param wifiInfo object holding relevant values 427 */ noteIpConfiguration(@onNull ExtendedWifiInfo wifiInfo)428 public void noteIpConfiguration(@NonNull ExtendedWifiInfo wifiInfo) { 429 IfaceInfo ifaceInfo = getIfaceInfo(wifiInfo.getIfaceName()); 430 updatePerBssid(Event.IP_CONFIGURATION_SUCCESS, wifiInfo); 431 updatePerNetwork(Event.IP_CONFIGURATION_SUCCESS, wifiInfo.getSSID(), wifiInfo.getRssi(), 432 wifiInfo.getTxLinkSpeedMbps(), UNKNOWN_REASON, ifaceInfo); 433 PerNetwork perNetwork = lookupNetwork(wifiInfo.getSSID()); 434 perNetwork.initBandwidthFilter(wifiInfo); 435 ifaceInfo.attemptingSwitch = false; 436 doWrites(); 437 } 438 439 /** 440 * Updates the score card after network validation success. 441 * 442 * @param wifiInfo object holding relevant values 443 */ noteValidationSuccess(@onNull ExtendedWifiInfo wifiInfo)444 public void noteValidationSuccess(@NonNull ExtendedWifiInfo wifiInfo) { 445 IfaceInfo ifaceInfo = getIfaceInfo(wifiInfo.getIfaceName()); 446 if (ifaceInfo.validatedThisConnectionAtLeastOnce) return; // Only once per connection 447 updatePerBssid(Event.VALIDATION_SUCCESS, wifiInfo); 448 ifaceInfo.validatedThisConnectionAtLeastOnce = true; 449 doWrites(); 450 } 451 452 /** 453 * Updates the score card after network validation failure 454 * 455 * @param wifiInfo object holding relevant values 456 */ noteValidationFailure(@onNull ExtendedWifiInfo wifiInfo)457 public void noteValidationFailure(@NonNull ExtendedWifiInfo wifiInfo) { 458 // VALIDATION_FAILURE is not currently recorded. 459 } 460 461 /** 462 * Records the start of a connection attempt 463 * 464 * @param wifiInfo may have state about an existing connection 465 * @param scanRssi is the highest RSSI of recent scan found from scanDetailCache 466 * @param ssid is the network SSID of connection attempt 467 */ noteConnectionAttempt(@onNull ExtendedWifiInfo wifiInfo, int scanRssi, String ssid)468 public void noteConnectionAttempt(@NonNull ExtendedWifiInfo wifiInfo, 469 int scanRssi, String ssid) { 470 IfaceInfo ifaceInfo = getIfaceInfo(wifiInfo.getIfaceName()); 471 // We may or may not be currently connected. If not, simply record the start. 472 // But if we are connected, wrap up the old one first. 473 if (ifaceInfo.tsConnectionAttemptStart > TS_NONE) { 474 if (ifaceInfo.polled) { 475 updatePerBssid(Event.LAST_POLL_BEFORE_SWITCH, wifiInfo); 476 } 477 ifaceInfo.attemptingSwitch = true; 478 } 479 ifaceInfo.tsConnectionAttemptStart = mClock.getElapsedSinceBootMillis(); 480 ifaceInfo.polled = false; 481 ifaceInfo.ssidPrev = ifaceInfo.ssidCurr; 482 ifaceInfo.ssidCurr = ssid; 483 ifaceInfo.firmwareAlertTimeMs = TS_NONE; 484 485 updatePerNetwork(Event.CONNECTION_ATTEMPT, ssid, scanRssi, LINK_SPEED_UNKNOWN, 486 UNKNOWN_REASON, ifaceInfo); 487 logd("CONNECTION_ATTEMPT" + (ifaceInfo.attemptingSwitch ? " X " : " ") + wifiInfo); 488 } 489 490 /** 491 * Records a newly assigned NetworkAgent netId. 492 */ noteNetworkAgentCreated(@onNull ExtendedWifiInfo wifiInfo, int networkAgentId)493 public void noteNetworkAgentCreated(@NonNull ExtendedWifiInfo wifiInfo, int networkAgentId) { 494 PerBssid perBssid = lookupBssid(wifiInfo.getSSID(), wifiInfo.getBSSID()); 495 logd("NETWORK_AGENT_ID: " + networkAgentId + " ID: " + perBssid.id); 496 perBssid.mNetworkAgentId = networkAgentId; 497 } 498 499 /** 500 * Record disconnection not initiated by wpa_supplicant in connected mode 501 * @param reason is detailed disconnection reason code 502 */ noteNonlocalDisconnect(String ifaceName, int reason)503 public void noteNonlocalDisconnect(String ifaceName, int reason) { 504 IfaceInfo ifaceInfo = getIfaceInfo(ifaceName); 505 506 ifaceInfo.nonlocalDisconnection = true; 507 ifaceInfo.disconnectionReason = reason; 508 logd("nonlocal disconnection with reason: " + reason); 509 } 510 511 /** 512 * Record firmware alert timestamp and error code 513 */ noteFirmwareAlert(int errorCode)514 public void noteFirmwareAlert(int errorCode) { 515 long ts = mClock.getElapsedSinceBootMillis(); 516 // Firmware alert is device-level, not per-iface. Thus, note firmware alert on all ifaces. 517 for (IfaceInfo ifaceInfo : mIfaceToInfoMap.values()) { 518 ifaceInfo.firmwareAlertTimeMs = ts; 519 } 520 logd("firmware alert with error code: " + errorCode); 521 } 522 523 /** 524 * Updates the score card after a failed connection attempt 525 * 526 * @param wifiInfo object holding relevant values. 527 * @param scanRssi is the highest RSSI of recent scan found from scanDetailCache 528 * @param ssid is the network SSID. 529 * @param failureReason is connection failure reason 530 */ noteConnectionFailure(@onNull ExtendedWifiInfo wifiInfo, int scanRssi, String ssid, @FailureReason int failureReason)531 public void noteConnectionFailure(@NonNull ExtendedWifiInfo wifiInfo, 532 int scanRssi, String ssid, @FailureReason int failureReason) { 533 IfaceInfo ifaceInfo = getIfaceInfo(wifiInfo.getIfaceName()); 534 // TODO: add the breakdown of level2FailureReason 535 updatePerBssid(Event.CONNECTION_FAILURE, wifiInfo); 536 updatePerNetwork(Event.CONNECTION_FAILURE, ssid, scanRssi, LINK_SPEED_UNKNOWN, 537 failureReason, ifaceInfo); 538 resetConnectionStateForIfaceInternal(ifaceInfo, false); 539 } 540 541 /** 542 * Updates the score card after network reachability failure 543 * 544 * @param wifiInfo object holding relevant values 545 */ noteIpReachabilityLost(@onNull ExtendedWifiInfo wifiInfo)546 public void noteIpReachabilityLost(@NonNull ExtendedWifiInfo wifiInfo) { 547 IfaceInfo ifaceInfo = getIfaceInfo(wifiInfo.getIfaceName()); 548 if (ifaceInfo.tsRoam > TS_NONE) { 549 ifaceInfo.tsConnectionAttemptStart = ifaceInfo.tsRoam; // just to update elapsed 550 updatePerBssid(Event.ROAM_FAILURE, wifiInfo); 551 } else { 552 updatePerBssid(Event.IP_REACHABILITY_LOST, wifiInfo); 553 } 554 // No need to call resetConnectionStateInternal() because 555 // resetConnectionState() will be called after WifiNative.disconnect() in ClientModeImpl 556 doWrites(); 557 } 558 559 /** 560 * Updates the score card before a roam 561 * 562 * We may have already done a firmware roam, but wifiInfo has not yet 563 * been updated, so we still have the old state. 564 * 565 * @param wifiInfo object holding relevant values 566 */ noteRoam(IfaceInfo ifaceInfo, @NonNull ExtendedWifiInfo wifiInfo)567 private void noteRoam(IfaceInfo ifaceInfo, @NonNull ExtendedWifiInfo wifiInfo) { 568 updatePerBssid(Event.LAST_POLL_BEFORE_ROAM, wifiInfo); 569 ifaceInfo.tsRoam = mClock.getElapsedSinceBootMillis(); 570 } 571 572 /** 573 * Called when the supplicant state is about to change, before wifiInfo is updated 574 * 575 * @param wifiInfo object holding old values 576 * @param state the new supplicant state 577 */ noteSupplicantStateChanging(@onNull ExtendedWifiInfo wifiInfo, SupplicantState state)578 public void noteSupplicantStateChanging(@NonNull ExtendedWifiInfo wifiInfo, 579 SupplicantState state) { 580 IfaceInfo ifaceInfo = getIfaceInfo(wifiInfo.getIfaceName()); 581 if (state == SupplicantState.COMPLETED && wifiInfo.getSupplicantState() == state) { 582 // Our signal that a firmware roam has occurred 583 noteRoam(ifaceInfo, wifiInfo); 584 } 585 logd("Changing state to " + state + " " + wifiInfo); 586 } 587 588 /** 589 * Called after the supplicant state changed 590 * 591 * @param wifiInfo object holding old values 592 */ noteSupplicantStateChanged(ExtendedWifiInfo wifiInfo)593 public void noteSupplicantStateChanged(ExtendedWifiInfo wifiInfo) { 594 logd("ifaceName=" + wifiInfo.getIfaceName() + ",wifiInfo=" + wifiInfo); 595 } 596 597 /** 598 * Updates the score card when wifi is disabled 599 * 600 * @param wifiInfo object holding relevant values 601 */ noteWifiDisabled(@onNull ExtendedWifiInfo wifiInfo)602 public void noteWifiDisabled(@NonNull ExtendedWifiInfo wifiInfo) { 603 updatePerBssid(Event.WIFI_DISABLED, wifiInfo); 604 } 605 606 /** 607 * Records the last successful L2 connection timestamp for a BSSID. 608 * @return the previous BSSID connection time. 609 */ setBssidConnectionTimestampMs(String ssid, String bssid, long timeMs)610 public long setBssidConnectionTimestampMs(String ssid, String bssid, long timeMs) { 611 PerBssid perBssid = lookupBssid(ssid, bssid); 612 long prev = perBssid.lastConnectionTimestampMs; 613 perBssid.lastConnectionTimestampMs = timeMs; 614 return prev; 615 } 616 617 /** 618 * Returns the last successful L2 connection time for this BSSID. 619 */ getBssidConnectionTimestampMs(String ssid, String bssid)620 public long getBssidConnectionTimestampMs(String ssid, String bssid) { 621 return lookupBssid(ssid, bssid).lastConnectionTimestampMs; 622 } 623 624 /** 625 * Increment the blocklist streak count for a failure reason on an AP. 626 * @return the updated count 627 */ incrementBssidBlocklistStreak(String ssid, String bssid, @WifiBlocklistMonitor.FailureReason int reason)628 public int incrementBssidBlocklistStreak(String ssid, String bssid, 629 @WifiBlocklistMonitor.FailureReason int reason) { 630 PerBssid perBssid = lookupBssid(ssid, bssid); 631 return ++perBssid.blocklistStreakCount[reason]; 632 } 633 634 /** 635 * Get the blocklist streak count for a failure reason on an AP. 636 * @return the blocklist streak count 637 */ getBssidBlocklistStreak(String ssid, String bssid, @WifiBlocklistMonitor.FailureReason int reason)638 public int getBssidBlocklistStreak(String ssid, String bssid, 639 @WifiBlocklistMonitor.FailureReason int reason) { 640 return lookupBssid(ssid, bssid).blocklistStreakCount[reason]; 641 } 642 643 /** 644 * Clear the blocklist streak count for a failure reason on an AP. 645 */ resetBssidBlocklistStreak(String ssid, String bssid, @WifiBlocklistMonitor.FailureReason int reason)646 public void resetBssidBlocklistStreak(String ssid, String bssid, 647 @WifiBlocklistMonitor.FailureReason int reason) { 648 lookupBssid(ssid, bssid).blocklistStreakCount[reason] = 0; 649 } 650 651 /** 652 * Clear the blocklist streak count for all APs that belong to this SSID. 653 */ resetBssidBlocklistStreakForSsid(@onNull String ssid)654 public void resetBssidBlocklistStreakForSsid(@NonNull String ssid) { 655 Iterator<Map.Entry<MacAddress, PerBssid>> it = mApForBssid.entrySet().iterator(); 656 while (it.hasNext()) { 657 PerBssid perBssid = it.next().getValue(); 658 if (!ssid.equals(perBssid.ssid)) { 659 continue; 660 } 661 for (int i = 0; i < perBssid.blocklistStreakCount.length; i++) { 662 perBssid.blocklistStreakCount[i] = 0; 663 } 664 } 665 } 666 667 /** 668 * Detect abnormal disconnection at high RSSI with a high rate 669 */ detectAbnormalDisconnection(String ifaceName)670 public int detectAbnormalDisconnection(String ifaceName) { 671 IfaceInfo ifaceInfo = getIfaceInfo(ifaceName); 672 String ssid = ifaceInfo.attemptingSwitch ? ifaceInfo.ssidPrev : ifaceInfo.ssidCurr; 673 PerNetwork perNetwork = lookupNetwork(ssid); 674 NetworkConnectionStats recentStats = perNetwork.getRecentStats(); 675 if (recentStats.getRecentCountCode() == CNT_SHORT_CONNECTION_NONLOCAL) { 676 return detectAbnormalFailureReason(recentStats, CNT_SHORT_CONNECTION_NONLOCAL, 677 REASON_SHORT_CONNECTION_NONLOCAL, 678 mDeviceConfigFacade.getShortConnectionNonlocalHighThrPercent(), 679 mDeviceConfigFacade.getShortConnectionNonlocalCountMin(), 680 CNT_DISCONNECTION); 681 } else if (recentStats.getRecentCountCode() == CNT_DISCONNECTION_NONLOCAL) { 682 return detectAbnormalFailureReason(recentStats, CNT_DISCONNECTION_NONLOCAL, 683 REASON_DISCONNECTION_NONLOCAL, 684 mDeviceConfigFacade.getDisconnectionNonlocalHighThrPercent(), 685 mDeviceConfigFacade.getDisconnectionNonlocalCountMin(), 686 CNT_DISCONNECTION); 687 } else { 688 return REASON_NO_FAILURE; 689 } 690 } 691 692 /** 693 * Detect abnormal connection failure at high RSSI with a high rate 694 */ detectAbnormalConnectionFailure(String ssid)695 public int detectAbnormalConnectionFailure(String ssid) { 696 PerNetwork perNetwork = lookupNetwork(ssid); 697 NetworkConnectionStats recentStats = perNetwork.getRecentStats(); 698 int recentCountCode = recentStats.getRecentCountCode(); 699 if (recentCountCode == CNT_AUTHENTICATION_FAILURE) { 700 return detectAbnormalFailureReason(recentStats, CNT_AUTHENTICATION_FAILURE, 701 REASON_AUTH_FAILURE, 702 mDeviceConfigFacade.getAuthFailureHighThrPercent(), 703 mDeviceConfigFacade.getAuthFailureCountMin(), 704 CNT_CONNECTION_ATTEMPT); 705 } else if (recentCountCode == CNT_ASSOCIATION_REJECTION) { 706 return detectAbnormalFailureReason(recentStats, CNT_ASSOCIATION_REJECTION, 707 REASON_ASSOC_REJECTION, 708 mDeviceConfigFacade.getAssocRejectionHighThrPercent(), 709 mDeviceConfigFacade.getAssocRejectionCountMin(), 710 CNT_CONNECTION_ATTEMPT); 711 } else if (recentCountCode == CNT_ASSOCIATION_TIMEOUT) { 712 return detectAbnormalFailureReason(recentStats, CNT_ASSOCIATION_TIMEOUT, 713 REASON_ASSOC_TIMEOUT, 714 mDeviceConfigFacade.getAssocTimeoutHighThrPercent(), 715 mDeviceConfigFacade.getAssocTimeoutCountMin(), 716 CNT_CONNECTION_ATTEMPT); 717 } else if (recentCountCode == CNT_DISCONNECTION_NONLOCAL_CONNECTING) { 718 return detectAbnormalFailureReason(recentStats, CNT_DISCONNECTION_NONLOCAL_CONNECTING, 719 REASON_CONNECTION_FAILURE_DISCONNECTION, 720 mDeviceConfigFacade.getConnectionFailureDisconnectionHighThrPercent(), 721 mDeviceConfigFacade.getConnectionFailureDisconnectionCountMin(), 722 CNT_CONNECTION_ATTEMPT); 723 } else if (recentCountCode == CNT_CONNECTION_FAILURE) { 724 return detectAbnormalFailureReason(recentStats, CNT_CONNECTION_FAILURE, 725 REASON_CONNECTION_FAILURE, 726 mDeviceConfigFacade.getConnectionFailureHighThrPercent(), 727 mDeviceConfigFacade.getConnectionFailureCountMin(), 728 CNT_CONNECTION_ATTEMPT); 729 } else { 730 return REASON_NO_FAILURE; 731 } 732 } 733 detectAbnormalFailureReason(NetworkConnectionStats stats, int countCode, int reasonCode, int highThresholdPercent, int minCount, int refCountCode)734 private int detectAbnormalFailureReason(NetworkConnectionStats stats, int countCode, 735 int reasonCode, int highThresholdPercent, int minCount, int refCountCode) { 736 // To detect abnormal failure which may trigger bugReport, 737 // increase the detection threshold by thresholdRatio 738 int thresholdRatio = 739 mDeviceConfigFacade.getBugReportThresholdExtraRatio(); 740 if (isHighPercentageAndEnoughCount(stats, countCode, reasonCode, 741 highThresholdPercent * thresholdRatio, 742 minCount * thresholdRatio, 743 refCountCode)) { 744 return reasonCode; 745 } else { 746 return REASON_NO_FAILURE; 747 } 748 } 749 isHighPercentageAndEnoughCount(NetworkConnectionStats stats, int countCode, int reasonCode, int highThresholdPercent, int minCount, int refCountCode)750 private boolean isHighPercentageAndEnoughCount(NetworkConnectionStats stats, int countCode, 751 int reasonCode, int highThresholdPercent, int minCount, int refCountCode) { 752 highThresholdPercent = Math.min(highThresholdPercent, 100); 753 // Use Laplace's rule of succession, useful especially for a small 754 // connection attempt count 755 // R = (f+1)/(n+2) with a pseudo count of 2 (one for f and one for s) 756 return ((stats.getCount(countCode) >= minCount) 757 && ((stats.getCount(countCode) + 1) * 100) 758 >= (highThresholdPercent * (stats.getCount(refCountCode) + 2))); 759 } 760 761 final class PerBssid extends MemoryStoreAccessBase { 762 public int id; 763 public final String ssid; 764 public final MacAddress bssid; 765 public final int[] blocklistStreakCount = 766 new int[WifiBlocklistMonitor.NUMBER_REASON_CODES]; 767 public long[][][] bandwidthStatsValue = 768 new long[NUM_LINK_BAND][NUM_LINK_DIRECTION][NUM_SIGNAL_LEVEL]; 769 public int[][][] bandwidthStatsCount = 770 new int[NUM_LINK_BAND][NUM_LINK_DIRECTION][NUM_SIGNAL_LEVEL]; 771 // The wall clock time in milliseconds for the last successful l2 connection. 772 public long lastConnectionTimestampMs; 773 public boolean changed; 774 public boolean referenced; 775 776 private SecurityType mSecurityType = null; 777 private int mNetworkAgentId = Integer.MIN_VALUE; 778 private int mNetworkConfigId = Integer.MIN_VALUE; 779 private final Map<Pair<Event, Integer>, PerSignal> 780 mSignalForEventAndFrequency = new ArrayMap<>(); 781 PerBssid(String ssid, MacAddress bssid)782 PerBssid(String ssid, MacAddress bssid) { 783 super(computeHashLong(ssid, bssid, mL2KeySeed)); 784 this.ssid = ssid; 785 this.bssid = bssid; 786 this.id = idFromLong(); 787 this.changed = false; 788 this.referenced = false; 789 } updateEventStats(Event event, int frequency, int rssi, int linkspeed, String ifaceName)790 void updateEventStats(Event event, int frequency, int rssi, int linkspeed, 791 String ifaceName) { 792 PerSignal perSignal = lookupSignal(event, frequency); 793 if (rssi != INVALID_RSSI) { 794 perSignal.rssi.update(rssi); 795 changed = true; 796 } 797 if (linkspeed > 0) { 798 perSignal.linkspeed.update(linkspeed); 799 changed = true; 800 } 801 IfaceInfo ifaceInfo = getIfaceInfo(ifaceName); 802 if (perSignal.elapsedMs != null && ifaceInfo.tsConnectionAttemptStart > TS_NONE) { 803 long millis = 804 mClock.getElapsedSinceBootMillis() - ifaceInfo.tsConnectionAttemptStart; 805 if (millis >= 0) { 806 perSignal.elapsedMs.update(millis); 807 changed = true; 808 } 809 } 810 } lookupSignal(Event event, int frequency)811 PerSignal lookupSignal(Event event, int frequency) { 812 finishPendingRead(); 813 Pair<Event, Integer> key = new Pair<>(event, frequency); 814 PerSignal ans = mSignalForEventAndFrequency.get(key); 815 if (ans == null) { 816 ans = new PerSignal(event, frequency); 817 mSignalForEventAndFrequency.put(key, ans); 818 } 819 return ans; 820 } getSecurityType()821 SecurityType getSecurityType() { 822 finishPendingRead(); 823 return mSecurityType; 824 } setSecurityType(SecurityType securityType)825 void setSecurityType(SecurityType securityType) { 826 finishPendingRead(); 827 if (!Objects.equals(securityType, mSecurityType)) { 828 mSecurityType = securityType; 829 changed = true; 830 } 831 } setNetworkConfigId(int networkConfigId)832 void setNetworkConfigId(int networkConfigId) { 833 // Not serialized, so don't need to set changed, etc. 834 if (networkConfigId >= 0) { 835 mNetworkConfigId = networkConfigId; 836 } 837 } toAccessPoint()838 AccessPoint toAccessPoint() { 839 return toAccessPoint(false); 840 } toAccessPoint(boolean obfuscate)841 AccessPoint toAccessPoint(boolean obfuscate) { 842 finishPendingRead(); 843 AccessPoint.Builder builder = AccessPoint.newBuilder(); 844 builder.setId(id); 845 if (!obfuscate) { 846 builder.setBssid(ByteString.copyFrom(bssid.toByteArray())); 847 } 848 if (mSecurityType != null) { 849 builder.setSecurityType(mSecurityType); 850 } 851 for (PerSignal sig: mSignalForEventAndFrequency.values()) { 852 builder.addEventStats(sig.toSignal()); 853 } 854 builder.setBandwidthStatsAll(toBandwidthStatsAll( 855 bandwidthStatsValue, bandwidthStatsCount)); 856 return builder.build(); 857 } merge(AccessPoint ap)858 PerBssid merge(AccessPoint ap) { 859 if (ap.hasId() && this.id != ap.getId()) { 860 return this; 861 } 862 if (ap.hasSecurityType()) { 863 SecurityType prev = ap.getSecurityType(); 864 if (mSecurityType == null) { 865 mSecurityType = prev; 866 } else if (!mSecurityType.equals(prev)) { 867 if (mVerboseLoggingEnabled) { 868 Log.i(TAG, "ID: " + id 869 + "SecurityType changed: " + prev + " to " + mSecurityType); 870 } 871 changed = true; 872 } 873 } 874 for (Signal signal: ap.getEventStatsList()) { 875 Pair<Event, Integer> key = new Pair<>(signal.getEvent(), signal.getFrequency()); 876 PerSignal perSignal = mSignalForEventAndFrequency.get(key); 877 if (perSignal == null) { 878 mSignalForEventAndFrequency.put(key, 879 new PerSignal(key.first, key.second).merge(signal)); 880 // No need to set changed for this, since we are in sync with what's stored 881 } else { 882 perSignal.merge(signal); 883 changed = true; 884 } 885 } 886 if (ap.hasBandwidthStatsAll()) { 887 mergeBandwidthStatsAll(ap.getBandwidthStatsAll(), 888 bandwidthStatsValue, bandwidthStatsCount); 889 } 890 return this; 891 } 892 893 /** 894 * Handles (when convenient) the arrival of previously stored data. 895 * 896 * The response from IpMemoryStore arrives on a different thread, so we 897 * defer handling it until here, when we're on our favorite thread and 898 * in a good position to deal with it. We may have already collected some 899 * data before now, so we need to be prepared to merge the new and old together. 900 */ finishPendingRead()901 void finishPendingRead() { 902 final byte[] serialized = finishPendingReadBytes(); 903 if (serialized == null) return; 904 AccessPoint ap; 905 try { 906 ap = AccessPoint.parseFrom(serialized); 907 } catch (InvalidProtocolBufferException e) { 908 Log.e(TAG, "Failed to deserialize", e); 909 return; 910 } 911 merge(ap); 912 } 913 914 /** 915 * Estimates the probability of getting internet access, based on the 916 * device experience. 917 * 918 * @return a probability, expressed as a percentage in the range 0 to 100 919 */ estimatePercentInternetAvailability()920 public int estimatePercentInternetAvailability() { 921 // Initialize counts accoring to Laplace's rule of succession 922 int trials = 2; 923 int successes = 1; 924 // Aggregate over all of the frequencies 925 for (PerSignal s : mSignalForEventAndFrequency.values()) { 926 switch (s.event) { 927 case IP_CONFIGURATION_SUCCESS: 928 if (s.elapsedMs != null) { 929 trials += s.elapsedMs.count; 930 } 931 break; 932 case VALIDATION_SUCCESS: 933 if (s.elapsedMs != null) { 934 successes += s.elapsedMs.count; 935 } 936 break; 937 default: 938 break; 939 } 940 } 941 // Note that because of roaming it is possible to count successes 942 // without corresponding trials. 943 return Math.min(Math.max(Math.round(successes * 100.0f / trials), 0), 100); 944 } 945 } 946 toBandwidthStatsAll(long[][][] values, int[][][] counts)947 private BandwidthStatsAll toBandwidthStatsAll(long[][][] values, int[][][] counts) { 948 BandwidthStatsAll.Builder builder = BandwidthStatsAll.newBuilder(); 949 builder.setStats2G(toBandwidthStatsAllLink(values[0], counts[0])); 950 builder.setStatsAbove2G(toBandwidthStatsAllLink(values[1], counts[1])); 951 return builder.build(); 952 } 953 toBandwidthStatsAllLink(long[][] values, int[][] counts)954 private BandwidthStatsAllLink toBandwidthStatsAllLink(long[][] values, int[][] counts) { 955 BandwidthStatsAllLink.Builder builder = BandwidthStatsAllLink.newBuilder(); 956 builder.setTx(toBandwidthStatsAllLevel(values[LINK_TX], counts[LINK_TX])); 957 builder.setRx(toBandwidthStatsAllLevel(values[LINK_RX], counts[LINK_RX])); 958 return builder.build(); 959 } 960 toBandwidthStatsAllLevel(long[] values, int[] counts)961 private BandwidthStatsAllLevel toBandwidthStatsAllLevel(long[] values, int[] counts) { 962 BandwidthStatsAllLevel.Builder builder = BandwidthStatsAllLevel.newBuilder(); 963 for (int i = 0; i < NUM_SIGNAL_LEVEL; i++) { 964 builder.addLevel(toBandwidthStats(values[i], counts[i])); 965 } 966 return builder.build(); 967 } 968 toBandwidthStats(long value, int count)969 private BandwidthStats toBandwidthStats(long value, int count) { 970 BandwidthStats.Builder builder = BandwidthStats.newBuilder(); 971 builder.setValue(value); 972 builder.setCount(count); 973 return builder.build(); 974 } 975 mergeBandwidthStatsAll(BandwidthStatsAll source, long[][][] values, int[][][] counts)976 private void mergeBandwidthStatsAll(BandwidthStatsAll source, 977 long[][][] values, int[][][] counts) { 978 if (source.hasStats2G()) { 979 mergeBandwidthStatsAllLink(source.getStats2G(), values[0], counts[0]); 980 } 981 if (source.hasStatsAbove2G()) { 982 mergeBandwidthStatsAllLink(source.getStatsAbove2G(), values[1], counts[1]); 983 } 984 } 985 mergeBandwidthStatsAllLink(BandwidthStatsAllLink source, long[][] values, int[][] counts)986 private void mergeBandwidthStatsAllLink(BandwidthStatsAllLink source, 987 long[][] values, int[][] counts) { 988 if (source.hasTx()) { 989 mergeBandwidthStatsAllLevel(source.getTx(), values[LINK_TX], counts[LINK_TX]); 990 } 991 if (source.hasRx()) { 992 mergeBandwidthStatsAllLevel(source.getRx(), values[LINK_RX], counts[LINK_RX]); 993 } 994 } 995 mergeBandwidthStatsAllLevel(BandwidthStatsAllLevel source, long[] values, int[] counts)996 private void mergeBandwidthStatsAllLevel(BandwidthStatsAllLevel source, 997 long[] values, int[] counts) { 998 int levelCnt = source.getLevelCount(); 999 for (int i = 0; i < levelCnt; i++) { 1000 BandwidthStats stats = source.getLevel(i); 1001 if (stats.hasValue()) { 1002 values[i] += stats.getValue(); 1003 } 1004 if (stats.hasCount()) { 1005 counts[i] += stats.getCount(); 1006 } 1007 } 1008 } 1009 1010 // TODO: b/178641307 move the following parameters to config.xml 1011 // Array dimension : int [NUM_LINK_BAND][NUM_LINK_DIRECTION][NUM_SIGNAL_LEVEL] 1012 static final int[][][] LINK_BANDWIDTH_INIT_KBPS = 1013 {{{500, 2500, 10000, 12000, 12000}, {500, 2500, 10000, 30000, 30000}}, 1014 {{1500, 7500, 12000, 12000, 12000}, {1500, 7500, 30000, 60000, 60000}}}; 1015 // To be used in link bandwidth estimation, each TrafficStats poll sample needs to be above 1016 // the following values. Defined per signal level. 1017 // int [NUM_LINK_DIRECTION][NUM_SIGNAL_LEVEL] 1018 // Use the low Tx threshold because xDSL UL speed could be below 1Mbps. 1019 static final int[][] LINK_BANDWIDTH_BYTE_DELTA_THR_KBYTE = 1020 {{200, 300, 300, 300, 300}, {200, 500, 1000, 2000, 2000}}; 1021 // To be used in the long term avg, each count needs to be above the following value 1022 static final int BANDWIDTH_STATS_COUNT_THR = 5; 1023 private static final int TIME_CONSTANT_SMALL_SEC = 6; 1024 // If RSSI changes by more than the below value, update BW filter with small time constant 1025 private static final int RSSI_DELTA_THR_DB = 8; 1026 private static final int FILTER_SCALE = 128; 1027 // Force weight to 0 if the elapsed time is above LARGE_TIME_DECAY_RATIO * time constant 1028 private static final int LARGE_TIME_DECAY_RATIO = 4; 1029 // Used to derive byte count threshold from avg BW 1030 private static final int LOW_BW_TO_AVG_BW_RATIO_NUM = 6; 1031 private static final int LOW_BW_TO_AVG_BW_RATIO_DEN = 8; 1032 // For some high speed connections, heavy DL traffic could falsely trigger UL BW update due to 1033 // TCP ACK and the low Tx byte count threshold. To work around the issue, skip Tx BW update if 1034 // Rx Bytes / Tx Bytes > RX_OVER_TX_BYTE_RATIO_MAX (heavy DL and light UL traffic) 1035 private static final int RX_OVER_TX_BYTE_RATIO_MAX = 5; 1036 // radio on time below the following value is ignored. 1037 static final int RADIO_ON_TIME_MIN_MS = 200; 1038 static final int RADIO_ON_ELAPSED_TIME_DELTA_MAX_MS = 200; 1039 static final int NUM_SIGNAL_LEVEL = 5; 1040 static final int LINK_TX = 0; 1041 static final int LINK_RX = 1; 1042 private static final int NUM_LINK_BAND = 2; 1043 private static final int NUM_LINK_DIRECTION = 2; 1044 private static final long BW_UPDATE_TIME_RESET_MS = TIME_CONSTANT_SMALL_SEC * 1000 * -10; 1045 private static final int MAX_ERROR_PERCENT = 100 * 100; 1046 private static final int EXTRA_SAMPLE_BW_FILTERING = 2; 1047 /** 1048 * A class collecting the connection and link bandwidth stats of one network or SSID. 1049 */ 1050 final class PerNetwork extends MemoryStoreAccessBase { 1051 public int id; 1052 public final String ssid; 1053 public boolean changed; 1054 private int mLastRssiPoll = INVALID_RSSI; 1055 private int mLastTxSpeedPoll = LINK_SPEED_UNKNOWN; 1056 private long mLastRssiPollTimeMs = TS_NONE; 1057 private long mConnectionSessionStartTimeMs = TS_NONE; 1058 private NetworkConnectionStats mRecentStats; 1059 private NetworkConnectionStats mStatsCurrBuild; 1060 private NetworkConnectionStats mStatsPrevBuild; 1061 private LruList<Integer> mFrequencyList; 1062 // In memory keep frequency with timestamp last time available, the elapsed time since boot. 1063 private SparseLongArray mFreqTimestamp; 1064 private long mLastRxBytes; 1065 private long mLastTxBytes; 1066 private boolean mLastTrafficValid = true; 1067 private String mBssid = ""; 1068 private int mSignalLevel; // initialize to zero to handle possible race condition 1069 private int mBandIdx; // initialize to zero to handle possible race condition 1070 private int[] mByteDeltaAccThr = new int[NUM_LINK_DIRECTION]; 1071 private int[] mFilterKbps = new int[NUM_LINK_DIRECTION]; 1072 private int[] mBandwidthSampleKbps = new int[NUM_LINK_DIRECTION]; 1073 private int[] mAvgUsedKbps = new int[NUM_LINK_DIRECTION]; 1074 private int mBandwidthUpdateRssiDbm = -1; 1075 private int mBandwidthUpdateBandIdx = -1; 1076 private boolean[] mBandwidthSampleValid = new boolean[NUM_LINK_DIRECTION]; 1077 private long[] mBandwidthSampleValidTimeMs = new long[]{BW_UPDATE_TIME_RESET_MS, 1078 BW_UPDATE_TIME_RESET_MS}; 1079 long[][][] mBandwidthStatsValue = 1080 new long[NUM_LINK_BAND][NUM_LINK_DIRECTION][NUM_SIGNAL_LEVEL]; 1081 int[][][] mBandwidthStatsCount = 1082 new int[NUM_LINK_BAND][NUM_LINK_DIRECTION][NUM_SIGNAL_LEVEL]; 1083 PerNetwork(String ssid)1084 PerNetwork(String ssid) { 1085 super(computeHashLong(ssid, MacAddress.fromString(DEFAULT_MAC_ADDRESS), mL2KeySeed)); 1086 this.ssid = ssid; 1087 this.id = idFromLong(); 1088 this.changed = false; 1089 mRecentStats = new NetworkConnectionStats(); 1090 mStatsCurrBuild = new NetworkConnectionStats(); 1091 mStatsPrevBuild = new NetworkConnectionStats(); 1092 mFrequencyList = new LruList<>(MAX_FREQUENCIES_PER_SSID); 1093 mFreqTimestamp = new SparseLongArray(); 1094 } 1095 updateEventStats(Event event, int rssi, int txSpeed, int failureReason, IfaceInfo ifaceInfo)1096 void updateEventStats(Event event, int rssi, int txSpeed, int failureReason, 1097 IfaceInfo ifaceInfo) { 1098 finishPendingRead(); 1099 long currTimeMs = mClock.getElapsedSinceBootMillis(); 1100 switch (event) { 1101 case SIGNAL_POLL: 1102 mLastRssiPoll = rssi; 1103 mLastRssiPollTimeMs = currTimeMs; 1104 mLastTxSpeedPoll = txSpeed; 1105 changed = true; 1106 break; 1107 case CONNECTION_ATTEMPT: 1108 logd(" scan rssi: " + rssi); 1109 if (rssi >= mDeviceConfigFacade.getHealthMonitorMinRssiThrDbm()) { 1110 mRecentStats.incrementCount(CNT_CONNECTION_ATTEMPT); 1111 } 1112 mConnectionSessionStartTimeMs = currTimeMs; 1113 changed = true; 1114 break; 1115 case CONNECTION_FAILURE: 1116 mConnectionSessionStartTimeMs = TS_NONE; 1117 if (rssi >= mDeviceConfigFacade.getHealthMonitorMinRssiThrDbm()) { 1118 if (failureReason != WifiBlocklistMonitor.REASON_WRONG_PASSWORD) { 1119 mRecentStats.incrementCount(CNT_CONNECTION_FAILURE); 1120 mRecentStats.incrementCount(CNT_CONSECUTIVE_CONNECTION_FAILURE); 1121 } 1122 switch (failureReason) { 1123 case WifiBlocklistMonitor.REASON_ASSOCIATION_REJECTION: 1124 mRecentStats.incrementCount(CNT_ASSOCIATION_REJECTION); 1125 break; 1126 case WifiBlocklistMonitor.REASON_ASSOCIATION_TIMEOUT: 1127 mRecentStats.incrementCount(CNT_ASSOCIATION_TIMEOUT); 1128 break; 1129 case WifiBlocklistMonitor.REASON_AUTHENTICATION_FAILURE: 1130 case WifiBlocklistMonitor.REASON_EAP_FAILURE: 1131 mRecentStats.incrementCount(CNT_AUTHENTICATION_FAILURE); 1132 break; 1133 case WifiBlocklistMonitor.REASON_NONLOCAL_DISCONNECT_CONNECTING: 1134 mRecentStats.incrementCount(CNT_DISCONNECTION_NONLOCAL_CONNECTING); 1135 break; 1136 case WifiBlocklistMonitor.REASON_AP_UNABLE_TO_HANDLE_NEW_STA: 1137 case WifiBlocklistMonitor.REASON_WRONG_PASSWORD: 1138 case WifiBlocklistMonitor.REASON_DHCP_FAILURE: 1139 default: 1140 break; 1141 } 1142 } 1143 changed = true; 1144 break; 1145 case IP_CONFIGURATION_SUCCESS: 1146 // Reset CNT_CONSECUTIVE_CONNECTION_FAILURE since L3 is also connected 1147 mRecentStats.clearCount(CNT_CONSECUTIVE_CONNECTION_FAILURE); 1148 changed = true; 1149 logd(this.toString()); 1150 break; 1151 case WIFI_DISABLED: 1152 case DISCONNECTION: 1153 if (mConnectionSessionStartTimeMs <= TS_NONE) { 1154 return; 1155 } 1156 handleDisconnectionAfterConnection(ifaceInfo); 1157 mConnectionSessionStartTimeMs = TS_NONE; 1158 mLastRssiPollTimeMs = TS_NONE; 1159 mFilterKbps[LINK_TX] = 0; 1160 mFilterKbps[LINK_RX] = 0; 1161 mBandwidthUpdateRssiDbm = -1; 1162 mBandwidthUpdateBandIdx = -1; 1163 changed = true; 1164 break; 1165 default: 1166 break; 1167 } 1168 } 1169 @Override toString()1170 public String toString() { 1171 StringBuilder sb = new StringBuilder(); 1172 sb.append("SSID: ").append(ssid).append("\n"); 1173 if (mLastRssiPollTimeMs != TS_NONE) { 1174 sb.append(" LastRssiPollTime: "); 1175 sb.append(mLastRssiPollTimeMs); 1176 } 1177 sb.append(" LastRssiPoll: " + mLastRssiPoll); 1178 sb.append(" LastTxSpeedPoll: " + mLastTxSpeedPoll); 1179 sb.append("\n"); 1180 sb.append(" StatsRecent: ").append(mRecentStats).append("\n"); 1181 sb.append(" StatsCurr: ").append(mStatsCurrBuild).append("\n"); 1182 sb.append(" StatsPrev: ").append(mStatsPrevBuild); 1183 sb.append(" BandwidthStats:\n"); 1184 for (int i = 0; i < NUM_LINK_BAND; i++) { 1185 for (int j = 0; j < NUM_LINK_DIRECTION; j++) { 1186 sb.append(" avgKbps: "); 1187 for (int k = 0; k < NUM_SIGNAL_LEVEL; k++) { 1188 int avgKbps = mBandwidthStatsCount[i][j][k] == 0 ? 0 : (int) 1189 (mBandwidthStatsValue[i][j][k] / mBandwidthStatsCount[i][j][k]); 1190 sb.append(" " + avgKbps); 1191 } 1192 sb.append("\n count: "); 1193 for (int k = 0; k < NUM_SIGNAL_LEVEL; k++) { 1194 sb.append(" " + mBandwidthStatsCount[i][j][k]); 1195 } 1196 sb.append("\n"); 1197 } 1198 sb.append("\n"); 1199 } 1200 return sb.toString(); 1201 } 1202 handleDisconnectionAfterConnection(IfaceInfo ifaceInfo)1203 private void handleDisconnectionAfterConnection(IfaceInfo ifaceInfo) { 1204 long currTimeMs = mClock.getElapsedSinceBootMillis(); 1205 int currSessionDurationMs = (int) (currTimeMs - mConnectionSessionStartTimeMs); 1206 int currSessionDurationSec = currSessionDurationMs / 1000; 1207 mRecentStats.accumulate(CNT_CONNECTION_DURATION_SEC, currSessionDurationSec); 1208 long timeSinceLastRssiPollMs = currTimeMs - mLastRssiPollTimeMs; 1209 boolean hasRecentRssiPoll = mLastRssiPollTimeMs > TS_NONE 1210 && timeSinceLastRssiPollMs <= mDeviceConfigFacade 1211 .getHealthMonitorRssiPollValidTimeMs(); 1212 if (hasRecentRssiPoll) { 1213 mRecentStats.incrementCount(CNT_DISCONNECTION); 1214 } 1215 int fwAlertValidTimeMs = mDeviceConfigFacade.getHealthMonitorFwAlertValidTimeMs(); 1216 long timeSinceLastFirmAlert = currTimeMs - ifaceInfo.firmwareAlertTimeMs; 1217 boolean isInvalidFwAlertTime = ifaceInfo.firmwareAlertTimeMs == TS_NONE; 1218 boolean disableFwAlertCheck = fwAlertValidTimeMs == -1; 1219 boolean passFirmwareAlertCheck = disableFwAlertCheck ? true : (isInvalidFwAlertTime 1220 ? false : timeSinceLastFirmAlert < fwAlertValidTimeMs); 1221 boolean hasHighRssiOrHighTxSpeed = 1222 mLastRssiPoll >= mDeviceConfigFacade.getHealthMonitorMinRssiThrDbm() 1223 || mLastTxSpeedPoll >= HEALTH_MONITOR_COUNT_TX_SPEED_MIN_MBPS; 1224 if (ifaceInfo.nonlocalDisconnection && hasRecentRssiPoll 1225 && isAbnormalDisconnectionReason(ifaceInfo.disconnectionReason) 1226 && passFirmwareAlertCheck 1227 && hasHighRssiOrHighTxSpeed) { 1228 mRecentStats.incrementCount(CNT_DISCONNECTION_NONLOCAL); 1229 if (currSessionDurationMs <= mDeviceConfigFacade 1230 .getHealthMonitorShortConnectionDurationThrMs()) { 1231 mRecentStats.incrementCount(CNT_SHORT_CONNECTION_NONLOCAL); 1232 } 1233 } 1234 1235 } 1236 isAbnormalDisconnectionReason(int disconnectionReason)1237 private boolean isAbnormalDisconnectionReason(int disconnectionReason) { 1238 long mask = mDeviceConfigFacade.getAbnormalDisconnectionReasonCodeMask(); 1239 return disconnectionReason >= 0 && disconnectionReason <= 63 1240 && ((mask >> disconnectionReason) & 0x1) == 0x1; 1241 } 1242 getRecentStats()1243 @NonNull NetworkConnectionStats getRecentStats() { 1244 return mRecentStats; 1245 } getStatsCurrBuild()1246 @NonNull NetworkConnectionStats getStatsCurrBuild() { 1247 return mStatsCurrBuild; 1248 } getStatsPrevBuild()1249 @NonNull NetworkConnectionStats getStatsPrevBuild() { 1250 return mStatsPrevBuild; 1251 } 1252 1253 /** 1254 * Retrieve the list of frequencies seen for this network, with the most recent first. 1255 * @param ageInMills Max age to filter the channels. 1256 * @return a list of frequencies 1257 */ getFrequencies(Long ageInMills)1258 List<Integer> getFrequencies(Long ageInMills) { 1259 List<Integer> results = new ArrayList<>(); 1260 Long nowInMills = mClock.getElapsedSinceBootMillis(); 1261 for (Integer freq : mFrequencyList.getEntries()) { 1262 if (nowInMills - mFreqTimestamp.get(freq, 0L) > ageInMills) { 1263 continue; 1264 } 1265 results.add(freq); 1266 } 1267 return results; 1268 } 1269 1270 /** 1271 * Add a frequency to the list of frequencies for this network. 1272 * Will evict the least recently added frequency if the cache is full. 1273 */ addFrequency(int frequency)1274 void addFrequency(int frequency) { 1275 mFrequencyList.add(frequency); 1276 mFreqTimestamp.put(frequency, mClock.getElapsedSinceBootMillis()); 1277 } 1278 1279 /** 1280 * Update link bandwidth estimates based on TrafficStats byte counts and radio on time 1281 */ updateLinkBandwidth(WifiLinkLayerStats oldStats, WifiLinkLayerStats newStats, ExtendedWifiInfo wifiInfo)1282 void updateLinkBandwidth(WifiLinkLayerStats oldStats, WifiLinkLayerStats newStats, 1283 ExtendedWifiInfo wifiInfo) { 1284 mBandwidthSampleValid[LINK_TX] = false; 1285 mBandwidthSampleValid[LINK_RX] = false; 1286 long txBytes = mFrameworkFacade.getTotalTxBytes() - mFrameworkFacade.getMobileTxBytes(); 1287 long rxBytes = mFrameworkFacade.getTotalRxBytes() - mFrameworkFacade.getMobileRxBytes(); 1288 // Sometimes TrafficStats byte counts return invalid values 1289 // Ignore next two polls if it happens 1290 boolean trafficValid = txBytes >= mLastTxBytes && rxBytes >= mLastRxBytes; 1291 if (!mLastTrafficValid || !trafficValid) { 1292 mLastTrafficValid = trafficValid; 1293 logv("invalid traffic count tx " + txBytes + " last " + mLastTxBytes 1294 + " rx " + rxBytes + " last " + mLastRxBytes); 1295 mLastTxBytes = txBytes; 1296 mLastRxBytes = rxBytes; 1297 return; 1298 } 1299 1300 updateWifiInfo(wifiInfo); 1301 updateLinkBandwidthTxRxSample(oldStats, newStats, wifiInfo, txBytes, rxBytes); 1302 mLastTxBytes = txBytes; 1303 mLastRxBytes = rxBytes; 1304 1305 updateBandwidthWithFilterApplied(LINK_TX, wifiInfo); 1306 updateBandwidthWithFilterApplied(LINK_RX, wifiInfo); 1307 mBandwidthUpdateRssiDbm = wifiInfo.getRssi(); 1308 mBandwidthUpdateBandIdx = mBandIdx; 1309 } 1310 updateWifiInfo(ExtendedWifiInfo wifiInfo)1311 void updateWifiInfo(ExtendedWifiInfo wifiInfo) { 1312 int rssi = wifiInfo.getRssi(); 1313 mSignalLevel = RssiUtil.calculateSignalLevel(mContext, rssi); 1314 mSignalLevel = Math.min(mSignalLevel, NUM_SIGNAL_LEVEL - 1); 1315 mBandIdx = getBandIdx(wifiInfo); 1316 mBssid = wifiInfo.getBSSID(); 1317 mByteDeltaAccThr[LINK_TX] = getByteDeltaAccThr(LINK_TX); 1318 mByteDeltaAccThr[LINK_RX] = getByteDeltaAccThr(LINK_RX); 1319 } 1320 updateLinkBandwidthTxRxSample(WifiLinkLayerStats oldStats, WifiLinkLayerStats newStats, ExtendedWifiInfo wifiInfo, long txBytes, long rxBytes)1321 private void updateLinkBandwidthTxRxSample(WifiLinkLayerStats oldStats, 1322 WifiLinkLayerStats newStats, ExtendedWifiInfo wifiInfo, 1323 long txBytes, long rxBytes) { 1324 // oldStats is reset to null after screen off or disconnection 1325 if (oldStats == null || newStats == null) { 1326 return; 1327 } 1328 1329 int elapsedTimeMs = (int) (newStats.timeStampInMs - oldStats.timeStampInMs); 1330 if (elapsedTimeMs > MAX_TRAFFIC_STATS_POLL_TIME_DELTA_MS) { 1331 return; 1332 } 1333 1334 int onTimeMs = getTotalRadioOnTimeMs(newStats) - getTotalRadioOnTimeMs(oldStats); 1335 if (onTimeMs <= RADIO_ON_TIME_MIN_MS 1336 || onTimeMs > RADIO_ON_ELAPSED_TIME_DELTA_MAX_MS + elapsedTimeMs) { 1337 return; 1338 } 1339 onTimeMs = Math.min(elapsedTimeMs, onTimeMs); 1340 1341 long txBytesDelta = txBytes - mLastTxBytes; 1342 long rxBytesDelta = rxBytes - mLastRxBytes; 1343 if (txBytesDelta * RX_OVER_TX_BYTE_RATIO_MAX >= rxBytesDelta) { 1344 updateBandwidthSample(txBytesDelta, LINK_TX, onTimeMs, 1345 wifiInfo.getMaxSupportedTxLinkSpeedMbps()); 1346 } 1347 1348 updateBandwidthSample(rxBytesDelta, LINK_RX, onTimeMs, 1349 wifiInfo.getMaxSupportedRxLinkSpeedMbps()); 1350 1351 if (!mBandwidthSampleValid[LINK_RX] && !mBandwidthSampleValid[LINK_TX]) { 1352 return; 1353 } 1354 StringBuilder sb = new StringBuilder(); 1355 logv(sb.append(" rssi ").append(wifiInfo.getRssi()) 1356 .append(" level ").append(mSignalLevel) 1357 .append(" bssid ").append(wifiInfo.getBSSID()) 1358 .append(" freq ").append(wifiInfo.getFrequency()) 1359 .append(" onTimeMs ").append(onTimeMs) 1360 .append(" txKB ").append(txBytesDelta / 1024) 1361 .append(" rxKB ").append(rxBytesDelta / 1024) 1362 .append(" txKBThr ").append(mByteDeltaAccThr[LINK_TX] / 1024) 1363 .append(" rxKBThr ").append(mByteDeltaAccThr[LINK_RX] / 1024) 1364 .toString()); 1365 } 1366 getTotalRadioOnTimeMs(@onNull WifiLinkLayerStats stats)1367 private int getTotalRadioOnTimeMs(@NonNull WifiLinkLayerStats stats) { 1368 if (stats.radioStats != null && stats.radioStats.length > 0) { 1369 int totalRadioOnTime = 0; 1370 for (WifiLinkLayerStats.RadioStat stat : stats.radioStats) { 1371 totalRadioOnTime += stat.on_time; 1372 } 1373 return totalRadioOnTime; 1374 } 1375 return stats.on_time; 1376 } 1377 getBandIdx(ExtendedWifiInfo wifiInfo)1378 private int getBandIdx(ExtendedWifiInfo wifiInfo) { 1379 return ScanResult.is24GHz(wifiInfo.getFrequency()) ? 0 : 1; 1380 } 1381 updateBandwidthSample(long bytesDelta, int link, int onTimeMs, int maxSupportedLinkSpeedMbps)1382 private void updateBandwidthSample(long bytesDelta, int link, int onTimeMs, 1383 int maxSupportedLinkSpeedMbps) { 1384 checkAndPossiblyResetBandwidthStats(link, maxSupportedLinkSpeedMbps); 1385 if (bytesDelta < mByteDeltaAccThr[link]) { 1386 return; 1387 } 1388 long speedKbps = bytesDelta / onTimeMs * 8; 1389 if (speedKbps > (maxSupportedLinkSpeedMbps * 1000)) { 1390 return; 1391 } 1392 int linkBandwidthKbps = (int) speedKbps; 1393 changed = true; 1394 mBandwidthSampleValid[link] = true; 1395 mBandwidthSampleKbps[link] = linkBandwidthKbps; 1396 // Update SSID level stats 1397 mBandwidthStatsValue[mBandIdx][link][mSignalLevel] += linkBandwidthKbps; 1398 mBandwidthStatsCount[mBandIdx][link][mSignalLevel]++; 1399 // Update BSSID level stats 1400 PerBssid perBssid = lookupBssid(ssid, mBssid); 1401 if (perBssid != mPlaceholderPerBssid) { 1402 perBssid.changed = true; 1403 perBssid.bandwidthStatsValue[mBandIdx][link][mSignalLevel] += linkBandwidthKbps; 1404 perBssid.bandwidthStatsCount[mBandIdx][link][mSignalLevel]++; 1405 } 1406 } 1407 checkAndPossiblyResetBandwidthStats(int link, int maxSupportedLinkSpeedMbps)1408 private void checkAndPossiblyResetBandwidthStats(int link, int maxSupportedLinkSpeedMbps) { 1409 if (getAvgUsedLinkBandwidthKbps(link) > (maxSupportedLinkSpeedMbps * 1000)) { 1410 resetBandwidthStats(link); 1411 } 1412 } 1413 resetBandwidthStats(int link)1414 private void resetBandwidthStats(int link) { 1415 changed = true; 1416 // Reset SSID level stats 1417 mBandwidthStatsValue[mBandIdx][link][mSignalLevel] = 0; 1418 mBandwidthStatsCount[mBandIdx][link][mSignalLevel] = 0; 1419 // Reset BSSID level stats 1420 PerBssid perBssid = lookupBssid(ssid, mBssid); 1421 if (perBssid != mPlaceholderPerBssid) { 1422 perBssid.changed = true; 1423 perBssid.bandwidthStatsValue[mBandIdx][link][mSignalLevel] = 0; 1424 perBssid.bandwidthStatsCount[mBandIdx][link][mSignalLevel] = 0; 1425 } 1426 } 1427 getByteDeltaAccThr(int link)1428 private int getByteDeltaAccThr(int link) { 1429 int maxTimeDeltaMs = mContext.getResources().getInteger( 1430 R.integer.config_wifiPollRssiIntervalMilliseconds); 1431 int lowBytes = calculateByteCountThreshold(getAvgUsedLinkBandwidthKbps(link), 1432 maxTimeDeltaMs); 1433 // Start with a predefined value 1434 int deltaAccThr = LINK_BANDWIDTH_BYTE_DELTA_THR_KBYTE[link][mSignalLevel] * 1024; 1435 if (lowBytes > 0) { 1436 // Raise the threshold if the avg usage BW is high 1437 deltaAccThr = Math.max(lowBytes, deltaAccThr); 1438 deltaAccThr = Math.min(deltaAccThr, mDeviceConfigFacade 1439 .getTrafficStatsThresholdMaxKbyte() * 1024); 1440 } 1441 return deltaAccThr; 1442 } 1443 initBandwidthFilter(ExtendedWifiInfo wifiInfo)1444 private void initBandwidthFilter(ExtendedWifiInfo wifiInfo) { 1445 updateWifiInfo(wifiInfo); 1446 for (int link = 0; link < NUM_LINK_DIRECTION; link++) { 1447 mFilterKbps[link] = getAvgLinkBandwidthKbps(link); 1448 } 1449 } 1450 updateBandwidthWithFilterApplied(int link, ExtendedWifiInfo wifiInfo)1451 private void updateBandwidthWithFilterApplied(int link, ExtendedWifiInfo wifiInfo) { 1452 int avgKbps = getAvgLinkBandwidthKbps(link); 1453 // Feed the filter with the long term avg if there is no valid BW sample so that filter 1454 // will gradually converge the long term avg. 1455 int filterInKbps = mBandwidthSampleValid[link] ? mBandwidthSampleKbps[link] : avgKbps; 1456 1457 long currTimeMs = mClock.getElapsedSinceBootMillis(); 1458 int timeDeltaSec = (int) (currTimeMs - mBandwidthSampleValidTimeMs[link]) / 1000; 1459 1460 // If the operation condition changes since the last valid sample or the current sample 1461 // has higher BW, use a faster filter. Otherwise, use a slow filter 1462 int timeConstantSec; 1463 if (Math.abs(mBandwidthUpdateRssiDbm - wifiInfo.getRssi()) > RSSI_DELTA_THR_DB 1464 || (mBandwidthSampleValid[link] && mBandwidthSampleKbps[link] > avgKbps) 1465 || mBandwidthUpdateBandIdx != mBandIdx) { 1466 timeConstantSec = TIME_CONSTANT_SMALL_SEC; 1467 } else { 1468 timeConstantSec = mDeviceConfigFacade.getBandwidthEstimatorLargeTimeConstantSec(); 1469 } 1470 // Update timestamp for next iteration 1471 if (mBandwidthSampleValid[link]) { 1472 mBandwidthSampleValidTimeMs[link] = currTimeMs; 1473 } 1474 1475 if (filterInKbps == mFilterKbps[link]) { 1476 return; 1477 } 1478 int alpha = timeDeltaSec > LARGE_TIME_DECAY_RATIO * timeConstantSec ? 0 1479 : (int) (FILTER_SCALE * Math.exp(-1.0 * timeDeltaSec / timeConstantSec)); 1480 1481 if (alpha == 0) { 1482 mFilterKbps[link] = filterInKbps; 1483 return; 1484 } 1485 long filterOutKbps = (long) mFilterKbps[link] * alpha 1486 + filterInKbps * FILTER_SCALE - filterInKbps * alpha; 1487 filterOutKbps = filterOutKbps / FILTER_SCALE; 1488 mFilterKbps[link] = (int) Math.min(filterOutKbps, Integer.MAX_VALUE); 1489 StringBuilder sb = new StringBuilder(); 1490 logd(sb.append(link) 1491 .append(" lastSampleWeight=").append(alpha) 1492 .append("/").append(FILTER_SCALE) 1493 .append(" filterInKbps=").append(filterInKbps) 1494 .append(" avgKbps=").append(avgKbps) 1495 .append(" filterOutKbps=").append(mFilterKbps[link]) 1496 .toString()); 1497 } 1498 getAvgLinkBandwidthKbps(int link)1499 private int getAvgLinkBandwidthKbps(int link) { 1500 mAvgUsedKbps[link] = getAvgUsedLinkBandwidthKbps(link); 1501 if (mAvgUsedKbps[link] > 0) { 1502 return mAvgUsedKbps[link]; 1503 } 1504 1505 int avgBwAdjSignalKbps = getAvgUsedBandwidthAdjacentThreeLevelKbps(link); 1506 if (avgBwAdjSignalKbps > 0) { 1507 return avgBwAdjSignalKbps; 1508 } 1509 1510 // Fall back to a cold-start value 1511 return LINK_BANDWIDTH_INIT_KBPS[mBandIdx][link][mSignalLevel]; 1512 } 1513 getAvgUsedLinkBandwidthKbps(int link)1514 private int getAvgUsedLinkBandwidthKbps(int link) { 1515 // Check if current BSSID/signal level has enough count 1516 PerBssid perBssid = lookupBssid(ssid, mBssid); 1517 int count = perBssid.bandwidthStatsCount[mBandIdx][link][mSignalLevel]; 1518 long value = perBssid.bandwidthStatsValue[mBandIdx][link][mSignalLevel]; 1519 if (count >= BANDWIDTH_STATS_COUNT_THR) { 1520 return (int) (value / count); 1521 } 1522 1523 // Check if current SSID/band/signal level has enough count 1524 count = mBandwidthStatsCount[mBandIdx][link][mSignalLevel]; 1525 value = mBandwidthStatsValue[mBandIdx][link][mSignalLevel]; 1526 if (count >= BANDWIDTH_STATS_COUNT_THR) { 1527 return (int) (value / count); 1528 } 1529 1530 return -1; 1531 } 1532 getAvgUsedBandwidthAdjacentThreeLevelKbps(int link)1533 private int getAvgUsedBandwidthAdjacentThreeLevelKbps(int link) { 1534 int count = 0; 1535 long value = 0; 1536 for (int i = -1; i <= 1; i++) { 1537 int currLevel = mSignalLevel + i; 1538 if (currLevel < 0 || currLevel >= NUM_SIGNAL_LEVEL) { 1539 continue; 1540 } 1541 count += mBandwidthStatsCount[mBandIdx][link][currLevel]; 1542 value += mBandwidthStatsValue[mBandIdx][link][currLevel]; 1543 } 1544 if (count >= BANDWIDTH_STATS_COUNT_THR) { 1545 return (int) (value / count); 1546 } 1547 1548 return -1; 1549 } 1550 1551 // Calculate a byte count threshold for the given avg BW and observation window size calculateByteCountThreshold(int avgBwKbps, int durationMs)1552 private int calculateByteCountThreshold(int avgBwKbps, int durationMs) { 1553 long avgBytes = (long) avgBwKbps / 8 * durationMs; 1554 long result = avgBytes * LOW_BW_TO_AVG_BW_RATIO_NUM / LOW_BW_TO_AVG_BW_RATIO_DEN; 1555 return (int) Math.min(result, Integer.MAX_VALUE); 1556 } 1557 1558 /** 1559 * Get the latest TrafficStats based end-to-end Tx link bandwidth estimation in Kbps 1560 */ getTxLinkBandwidthKbps()1561 public int getTxLinkBandwidthKbps() { 1562 return (mFilterKbps[LINK_TX] > 0) ? mFilterKbps[LINK_TX] 1563 : getAvgLinkBandwidthKbps(LINK_TX); 1564 } 1565 1566 /** 1567 * Get the latest TrafficStats based end-to-end Rx link bandwidth estimation in Kbps 1568 */ getRxLinkBandwidthKbps()1569 public int getRxLinkBandwidthKbps() { 1570 return (mFilterKbps[LINK_RX] > 0) ? mFilterKbps[LINK_RX] 1571 : getAvgLinkBandwidthKbps(LINK_RX); 1572 } 1573 1574 /** 1575 * Update Bandwidth metrics with the latest reported bandwidth and L2 BW values 1576 */ updateBwMetrics(int[] reportedKbps, int[] l2Kbps)1577 public void updateBwMetrics(int[] reportedKbps, int[] l2Kbps) { 1578 for (int link = 0; link < NUM_LINK_DIRECTION; link++) { 1579 calculateError(link, reportedKbps[link], l2Kbps[link]); 1580 } 1581 } 1582 calculateError(int link, int reportedKbps, int l2Kbps)1583 private void calculateError(int link, int reportedKbps, int l2Kbps) { 1584 if (mBandwidthStatsCount[mBandIdx][link][mSignalLevel] < (BANDWIDTH_STATS_COUNT_THR 1585 + EXTRA_SAMPLE_BW_FILTERING) || !mBandwidthSampleValid[link] 1586 || mAvgUsedKbps[link] <= 0) { 1587 return; 1588 } 1589 int bwSampleKbps = mBandwidthSampleKbps[link]; 1590 int bwEstExtErrPercent = calculateErrorPercent(reportedKbps, bwSampleKbps); 1591 int bwEstIntErrPercent = calculateErrorPercent(mFilterKbps[link], bwSampleKbps); 1592 int l2ErrPercent = calculateErrorPercent(l2Kbps, bwSampleKbps); 1593 mBwEstErrorAccPercent[mBandIdx][link][mSignalLevel] += Math.abs(bwEstExtErrPercent); 1594 mL2ErrorAccPercent[mBandIdx][link][mSignalLevel] += Math.abs(l2ErrPercent); 1595 mBwEstValue[mBandIdx][link][mSignalLevel] += bwSampleKbps; 1596 mBwEstCount[mBandIdx][link][mSignalLevel]++; 1597 StringBuilder sb = new StringBuilder(); 1598 logv(sb.append(link) 1599 .append(" sampKbps ").append(bwSampleKbps) 1600 .append(" filtKbps ").append(mFilterKbps[link]) 1601 .append(" reportedKbps ").append(reportedKbps) 1602 .append(" avgUsedKbps ").append(mAvgUsedKbps[link]) 1603 .append(" l2Kbps ").append(l2Kbps) 1604 .append(" intErrPercent ").append(bwEstIntErrPercent) 1605 .append(" extErrPercent ").append(bwEstExtErrPercent) 1606 .append(" l2ErrPercent ").append(l2ErrPercent) 1607 .toString()); 1608 } 1609 calculateErrorPercent(int inKbps, int bwSampleKbps)1610 private int calculateErrorPercent(int inKbps, int bwSampleKbps) { 1611 long errorPercent = 100L * (inKbps - bwSampleKbps) / bwSampleKbps; 1612 return (int) Math.max(-MAX_ERROR_PERCENT, Math.min(errorPercent, MAX_ERROR_PERCENT)); 1613 } 1614 1615 /** 1616 /* Detect a significant failure stats change with historical data 1617 /* or high failure stats without historical data. 1618 /* @return 0 if recentStats doesn't have sufficient data 1619 * 1 if recentStats has sufficient data while statsPrevBuild doesn't 1620 * 2 if recentStats and statsPrevBuild have sufficient data 1621 */ dailyDetection(FailureStats statsDec, FailureStats statsInc, FailureStats statsHigh)1622 int dailyDetection(FailureStats statsDec, FailureStats statsInc, FailureStats statsHigh) { 1623 finishPendingRead(); 1624 dailyDetectionDisconnectionEvent(statsDec, statsInc, statsHigh); 1625 return dailyDetectionConnectionEvent(statsDec, statsInc, statsHigh); 1626 } 1627 dailyDetectionConnectionEvent(FailureStats statsDec, FailureStats statsInc, FailureStats statsHigh)1628 private int dailyDetectionConnectionEvent(FailureStats statsDec, FailureStats statsInc, 1629 FailureStats statsHigh) { 1630 // Skip daily detection if recentStats is not sufficient 1631 if (!isRecentConnectionStatsSufficient()) return INSUFFICIENT_RECENT_STATS; 1632 if (mStatsPrevBuild.getCount(CNT_CONNECTION_ATTEMPT) 1633 < mDeviceConfigFacade.getHealthMonitorMinNumConnectionAttempt()) { 1634 // don't have enough historical data, 1635 // so only detect high failure stats without relying on mStatsPrevBuild. 1636 recentStatsHighDetectionConnection(statsHigh); 1637 return SUFFICIENT_RECENT_STATS_ONLY; 1638 } else { 1639 // mStatsPrevBuild has enough updates, 1640 // detect improvement or degradation 1641 statsDeltaDetectionConnection(statsDec, statsInc); 1642 return SUFFICIENT_RECENT_PREV_STATS; 1643 } 1644 } 1645 dailyDetectionDisconnectionEvent(FailureStats statsDec, FailureStats statsInc, FailureStats statsHigh)1646 private void dailyDetectionDisconnectionEvent(FailureStats statsDec, FailureStats statsInc, 1647 FailureStats statsHigh) { 1648 // Skip daily detection if recentStats is not sufficient 1649 int minConnectAttempt = mDeviceConfigFacade.getHealthMonitorMinNumConnectionAttempt(); 1650 if (mRecentStats.getCount(CNT_CONNECTION_ATTEMPT) < minConnectAttempt) { 1651 return; 1652 } 1653 if (mStatsPrevBuild.getCount(CNT_CONNECTION_ATTEMPT) < minConnectAttempt) { 1654 recentStatsHighDetectionDisconnection(statsHigh); 1655 } else { 1656 statsDeltaDetectionDisconnection(statsDec, statsInc); 1657 } 1658 } 1659 statsDeltaDetectionConnection(FailureStats statsDec, FailureStats statsInc)1660 private void statsDeltaDetectionConnection(FailureStats statsDec, 1661 FailureStats statsInc) { 1662 statsDeltaDetection(statsDec, statsInc, CNT_CONNECTION_FAILURE, 1663 REASON_CONNECTION_FAILURE, 1664 mDeviceConfigFacade.getConnectionFailureCountMin(), 1665 CNT_CONNECTION_ATTEMPT); 1666 statsDeltaDetection(statsDec, statsInc, CNT_DISCONNECTION_NONLOCAL_CONNECTING, 1667 REASON_CONNECTION_FAILURE_DISCONNECTION, 1668 mDeviceConfigFacade.getConnectionFailureDisconnectionCountMin(), 1669 CNT_CONNECTION_ATTEMPT); 1670 statsDeltaDetection(statsDec, statsInc, CNT_AUTHENTICATION_FAILURE, 1671 REASON_AUTH_FAILURE, 1672 mDeviceConfigFacade.getAuthFailureCountMin(), 1673 CNT_CONNECTION_ATTEMPT); 1674 statsDeltaDetection(statsDec, statsInc, CNT_ASSOCIATION_REJECTION, 1675 REASON_ASSOC_REJECTION, 1676 mDeviceConfigFacade.getAssocRejectionCountMin(), 1677 CNT_CONNECTION_ATTEMPT); 1678 statsDeltaDetection(statsDec, statsInc, CNT_ASSOCIATION_TIMEOUT, 1679 REASON_ASSOC_TIMEOUT, 1680 mDeviceConfigFacade.getAssocTimeoutCountMin(), 1681 CNT_CONNECTION_ATTEMPT); 1682 } 1683 recentStatsHighDetectionConnection(FailureStats statsHigh)1684 private void recentStatsHighDetectionConnection(FailureStats statsHigh) { 1685 recentStatsHighDetection(statsHigh, CNT_CONNECTION_FAILURE, 1686 REASON_CONNECTION_FAILURE, 1687 mDeviceConfigFacade.getConnectionFailureHighThrPercent(), 1688 mDeviceConfigFacade.getConnectionFailureCountMin(), 1689 CNT_CONNECTION_ATTEMPT); 1690 recentStatsHighDetection(statsHigh, CNT_DISCONNECTION_NONLOCAL_CONNECTING, 1691 REASON_CONNECTION_FAILURE_DISCONNECTION, 1692 mDeviceConfigFacade.getConnectionFailureDisconnectionHighThrPercent(), 1693 mDeviceConfigFacade.getConnectionFailureDisconnectionCountMin(), 1694 CNT_CONNECTION_ATTEMPT); 1695 recentStatsHighDetection(statsHigh, CNT_AUTHENTICATION_FAILURE, 1696 REASON_AUTH_FAILURE, 1697 mDeviceConfigFacade.getAuthFailureHighThrPercent(), 1698 mDeviceConfigFacade.getAuthFailureCountMin(), 1699 CNT_CONNECTION_ATTEMPT); 1700 recentStatsHighDetection(statsHigh, CNT_ASSOCIATION_REJECTION, 1701 REASON_ASSOC_REJECTION, 1702 mDeviceConfigFacade.getAssocRejectionHighThrPercent(), 1703 mDeviceConfigFacade.getAssocRejectionCountMin(), 1704 CNT_CONNECTION_ATTEMPT); 1705 recentStatsHighDetection(statsHigh, CNT_ASSOCIATION_TIMEOUT, 1706 REASON_ASSOC_TIMEOUT, 1707 mDeviceConfigFacade.getAssocTimeoutHighThrPercent(), 1708 mDeviceConfigFacade.getAssocTimeoutCountMin(), 1709 CNT_CONNECTION_ATTEMPT); 1710 } 1711 statsDeltaDetectionDisconnection(FailureStats statsDec, FailureStats statsInc)1712 private void statsDeltaDetectionDisconnection(FailureStats statsDec, 1713 FailureStats statsInc) { 1714 statsDeltaDetection(statsDec, statsInc, CNT_SHORT_CONNECTION_NONLOCAL, 1715 REASON_SHORT_CONNECTION_NONLOCAL, 1716 mDeviceConfigFacade.getShortConnectionNonlocalCountMin(), 1717 CNT_CONNECTION_ATTEMPT); 1718 statsDeltaDetection(statsDec, statsInc, CNT_DISCONNECTION_NONLOCAL, 1719 REASON_DISCONNECTION_NONLOCAL, 1720 mDeviceConfigFacade.getDisconnectionNonlocalCountMin(), 1721 CNT_CONNECTION_ATTEMPT); 1722 } 1723 recentStatsHighDetectionDisconnection(FailureStats statsHigh)1724 private void recentStatsHighDetectionDisconnection(FailureStats statsHigh) { 1725 recentStatsHighDetection(statsHigh, CNT_SHORT_CONNECTION_NONLOCAL, 1726 REASON_SHORT_CONNECTION_NONLOCAL, 1727 mDeviceConfigFacade.getShortConnectionNonlocalHighThrPercent(), 1728 mDeviceConfigFacade.getShortConnectionNonlocalCountMin(), 1729 CNT_DISCONNECTION); 1730 recentStatsHighDetection(statsHigh, CNT_DISCONNECTION_NONLOCAL, 1731 REASON_DISCONNECTION_NONLOCAL, 1732 mDeviceConfigFacade.getDisconnectionNonlocalHighThrPercent(), 1733 mDeviceConfigFacade.getDisconnectionNonlocalCountMin(), 1734 CNT_DISCONNECTION); 1735 } 1736 statsDeltaDetection(FailureStats statsDec, FailureStats statsInc, int countCode, int reasonCode, int minCount, int refCountCode)1737 private boolean statsDeltaDetection(FailureStats statsDec, 1738 FailureStats statsInc, int countCode, int reasonCode, 1739 int minCount, int refCountCode) { 1740 if (isRatioAboveThreshold(mRecentStats, mStatsPrevBuild, countCode, refCountCode) 1741 && mRecentStats.getCount(countCode) >= minCount) { 1742 statsInc.incrementCount(reasonCode); 1743 return true; 1744 } 1745 1746 if (isRatioAboveThreshold(mStatsPrevBuild, mRecentStats, countCode, refCountCode) 1747 && mStatsPrevBuild.getCount(countCode) >= minCount) { 1748 statsDec.incrementCount(reasonCode); 1749 return true; 1750 } 1751 return false; 1752 } 1753 recentStatsHighDetection(FailureStats statsHigh, int countCode, int reasonCode, int highThresholdPercent, int minCount, int refCountCode)1754 private boolean recentStatsHighDetection(FailureStats statsHigh, int countCode, 1755 int reasonCode, int highThresholdPercent, int minCount, int refCountCode) { 1756 if (isHighPercentageAndEnoughCount(mRecentStats, countCode, reasonCode, 1757 highThresholdPercent, minCount, refCountCode)) { 1758 statsHigh.incrementCount(reasonCode); 1759 return true; 1760 } 1761 return false; 1762 } 1763 isRatioAboveThreshold(NetworkConnectionStats stats1, NetworkConnectionStats stats2, @ConnectionCountCode int countCode, int refCountCode)1764 private boolean isRatioAboveThreshold(NetworkConnectionStats stats1, 1765 NetworkConnectionStats stats2, 1766 @ConnectionCountCode int countCode, int refCountCode) { 1767 // Also with Laplace's rule of succession discussed above 1768 // R1 = (stats1(countCode) + 1) / (stats1(refCountCode) + 2) 1769 // R2 = (stats2(countCode) + 1) / (stats2(refCountCode) + 2) 1770 // Check R1 / R2 >= ratioThr 1771 return ((stats1.getCount(countCode) + 1) * (stats2.getCount(refCountCode) + 2) 1772 * mDeviceConfigFacade.HEALTH_MONITOR_RATIO_THR_DENOMINATOR) 1773 >= ((stats1.getCount(refCountCode) + 2) * (stats2.getCount(countCode) + 1) 1774 * mDeviceConfigFacade.getHealthMonitorRatioThrNumerator()); 1775 } 1776 isRecentConnectionStatsSufficient()1777 private boolean isRecentConnectionStatsSufficient() { 1778 return (mRecentStats.getCount(CNT_CONNECTION_ATTEMPT) 1779 >= mDeviceConfigFacade.getHealthMonitorMinNumConnectionAttempt()); 1780 } 1781 1782 // Update StatsCurrBuild with recentStats and clear recentStats updateAfterDailyDetection()1783 void updateAfterDailyDetection() { 1784 // Skip update if recentStats is not sufficient since daily detection is also skipped 1785 if (!isRecentConnectionStatsSufficient()) return; 1786 mStatsCurrBuild.accumulateAll(mRecentStats); 1787 mRecentStats.clear(); 1788 changed = true; 1789 } 1790 1791 // Refresh StatsPrevBuild with StatsCurrBuild which is cleared afterwards updateAfterSwBuildChange()1792 void updateAfterSwBuildChange() { 1793 finishPendingRead(); 1794 mStatsPrevBuild.copy(mStatsCurrBuild); 1795 mRecentStats.clear(); 1796 mStatsCurrBuild.clear(); 1797 changed = true; 1798 } 1799 toNetworkStats()1800 NetworkStats toNetworkStats() { 1801 finishPendingRead(); 1802 NetworkStats.Builder builder = NetworkStats.newBuilder(); 1803 builder.setId(id); 1804 builder.setRecentStats(toConnectionStats(mRecentStats)); 1805 builder.setStatsCurrBuild(toConnectionStats(mStatsCurrBuild)); 1806 builder.setStatsPrevBuild(toConnectionStats(mStatsPrevBuild)); 1807 if (mFrequencyList.size() > 0) { 1808 builder.addAllFrequencies(mFrequencyList.getEntries()); 1809 } 1810 builder.setBandwidthStatsAll(toBandwidthStatsAll( 1811 mBandwidthStatsValue, mBandwidthStatsCount)); 1812 return builder.build(); 1813 } 1814 toConnectionStats(NetworkConnectionStats stats)1815 private ConnectionStats toConnectionStats(NetworkConnectionStats stats) { 1816 ConnectionStats.Builder builder = ConnectionStats.newBuilder(); 1817 builder.setNumConnectionAttempt(stats.getCount(CNT_CONNECTION_ATTEMPT)); 1818 builder.setNumConnectionFailure(stats.getCount(CNT_CONNECTION_FAILURE)); 1819 builder.setConnectionDurationSec(stats.getCount(CNT_CONNECTION_DURATION_SEC)); 1820 builder.setNumDisconnectionNonlocal(stats.getCount(CNT_DISCONNECTION_NONLOCAL)); 1821 builder.setNumDisconnection(stats.getCount(CNT_DISCONNECTION)); 1822 builder.setNumShortConnectionNonlocal(stats.getCount(CNT_SHORT_CONNECTION_NONLOCAL)); 1823 builder.setNumAssociationRejection(stats.getCount(CNT_ASSOCIATION_REJECTION)); 1824 builder.setNumAssociationTimeout(stats.getCount(CNT_ASSOCIATION_TIMEOUT)); 1825 builder.setNumAuthenticationFailure(stats.getCount(CNT_AUTHENTICATION_FAILURE)); 1826 builder.setNumDisconnectionNonlocalConnecting( 1827 stats.getCount(CNT_DISCONNECTION_NONLOCAL_CONNECTING)); 1828 return builder.build(); 1829 } 1830 finishPendingRead()1831 void finishPendingRead() { 1832 final byte[] serialized = finishPendingReadBytes(); 1833 if (serialized == null) return; 1834 NetworkStats ns; 1835 try { 1836 ns = NetworkStats.parseFrom(serialized); 1837 } catch (InvalidProtocolBufferException e) { 1838 Log.e(TAG, "Failed to deserialize", e); 1839 return; 1840 } 1841 mergeNetworkStatsFromMemory(ns); 1842 changed = true; 1843 } 1844 mergeNetworkStatsFromMemory(@onNull NetworkStats ns)1845 PerNetwork mergeNetworkStatsFromMemory(@NonNull NetworkStats ns) { 1846 if (ns.hasId() && this.id != ns.getId()) { 1847 return this; 1848 } 1849 if (ns.hasRecentStats()) { 1850 ConnectionStats recentStats = ns.getRecentStats(); 1851 mergeConnectionStats(recentStats, mRecentStats); 1852 } 1853 if (ns.hasStatsCurrBuild()) { 1854 ConnectionStats statsCurr = ns.getStatsCurrBuild(); 1855 mStatsCurrBuild.clear(); 1856 mergeConnectionStats(statsCurr, mStatsCurrBuild); 1857 } 1858 if (ns.hasStatsPrevBuild()) { 1859 ConnectionStats statsPrev = ns.getStatsPrevBuild(); 1860 mStatsPrevBuild.clear(); 1861 mergeConnectionStats(statsPrev, mStatsPrevBuild); 1862 } 1863 if (ns.getFrequenciesList().size() > 0) { 1864 // This merge assumes that whatever data is in memory is more recent that what's 1865 // in store 1866 List<Integer> mergedFrequencyList = mFrequencyList.getEntries(); 1867 mergedFrequencyList.addAll(ns.getFrequenciesList()); 1868 mFrequencyList = new LruList<>(MAX_FREQUENCIES_PER_SSID); 1869 for (int i = mergedFrequencyList.size() - 1; i >= 0; i--) { 1870 mFrequencyList.add(mergedFrequencyList.get(i)); 1871 } 1872 } 1873 if (ns.hasBandwidthStatsAll()) { 1874 mergeBandwidthStatsAll(ns.getBandwidthStatsAll(), 1875 mBandwidthStatsValue, mBandwidthStatsCount); 1876 } 1877 return this; 1878 } 1879 mergeConnectionStats(ConnectionStats source, NetworkConnectionStats target)1880 private void mergeConnectionStats(ConnectionStats source, NetworkConnectionStats target) { 1881 if (source.hasNumConnectionAttempt()) { 1882 target.accumulate(CNT_CONNECTION_ATTEMPT, source.getNumConnectionAttempt()); 1883 } 1884 if (source.hasNumConnectionFailure()) { 1885 target.accumulate(CNT_CONNECTION_FAILURE, source.getNumConnectionFailure()); 1886 } 1887 if (source.hasConnectionDurationSec()) { 1888 target.accumulate(CNT_CONNECTION_DURATION_SEC, source.getConnectionDurationSec()); 1889 } 1890 if (source.hasNumDisconnectionNonlocal()) { 1891 target.accumulate(CNT_DISCONNECTION_NONLOCAL, source.getNumDisconnectionNonlocal()); 1892 } 1893 if (source.hasNumDisconnection()) { 1894 target.accumulate(CNT_DISCONNECTION, source.getNumDisconnection()); 1895 } 1896 if (source.hasNumShortConnectionNonlocal()) { 1897 target.accumulate(CNT_SHORT_CONNECTION_NONLOCAL, 1898 source.getNumShortConnectionNonlocal()); 1899 } 1900 if (source.hasNumAssociationRejection()) { 1901 target.accumulate(CNT_ASSOCIATION_REJECTION, source.getNumAssociationRejection()); 1902 } 1903 if (source.hasNumAssociationTimeout()) { 1904 target.accumulate(CNT_ASSOCIATION_TIMEOUT, source.getNumAssociationTimeout()); 1905 } 1906 if (source.hasNumAuthenticationFailure()) { 1907 target.accumulate(CNT_AUTHENTICATION_FAILURE, source.getNumAuthenticationFailure()); 1908 } 1909 if (source.hasNumDisconnectionNonlocalConnecting()) { 1910 target.accumulate(CNT_DISCONNECTION_NONLOCAL_CONNECTING, 1911 source.getNumDisconnectionNonlocalConnecting()); 1912 } 1913 } 1914 } 1915 1916 // Codes for various connection related counts 1917 public static final int CNT_INVALID = -1; 1918 public static final int CNT_CONNECTION_ATTEMPT = 0; 1919 public static final int CNT_CONNECTION_FAILURE = 1; 1920 public static final int CNT_CONNECTION_DURATION_SEC = 2; 1921 public static final int CNT_ASSOCIATION_REJECTION = 3; 1922 public static final int CNT_ASSOCIATION_TIMEOUT = 4; 1923 public static final int CNT_AUTHENTICATION_FAILURE = 5; 1924 public static final int CNT_SHORT_CONNECTION_NONLOCAL = 6; 1925 public static final int CNT_DISCONNECTION_NONLOCAL = 7; 1926 public static final int CNT_DISCONNECTION = 8; 1927 public static final int CNT_CONSECUTIVE_CONNECTION_FAILURE = 9; 1928 public static final int CNT_DISCONNECTION_NONLOCAL_CONNECTING = 10; 1929 // Constant being used to keep track of how many counter there are. 1930 public static final int NUMBER_CONNECTION_CNT_CODE = 11; 1931 private static final String[] CONNECTION_CNT_NAME = { 1932 " ConnectAttempt: ", 1933 " ConnectFailure: ", 1934 " ConnectDurSec: ", 1935 " AssocRej: ", 1936 " AssocTimeout: ", 1937 " AuthFailure: ", 1938 " ShortDiscNonlocal: ", 1939 " DisconnectNonlocal: ", 1940 " Disconnect: ", 1941 " ConsecutiveConnectFailure: ", 1942 " ConnectFailureDiscon: " 1943 }; 1944 1945 @IntDef(prefix = { "CNT_" }, value = { 1946 CNT_CONNECTION_ATTEMPT, 1947 CNT_CONNECTION_FAILURE, 1948 CNT_CONNECTION_DURATION_SEC, 1949 CNT_ASSOCIATION_REJECTION, 1950 CNT_ASSOCIATION_TIMEOUT, 1951 CNT_AUTHENTICATION_FAILURE, 1952 CNT_SHORT_CONNECTION_NONLOCAL, 1953 CNT_DISCONNECTION_NONLOCAL, 1954 CNT_DISCONNECTION, 1955 CNT_CONSECUTIVE_CONNECTION_FAILURE, 1956 CNT_DISCONNECTION_NONLOCAL_CONNECTING 1957 }) 1958 @Retention(RetentionPolicy.SOURCE) 1959 public @interface ConnectionCountCode {} 1960 1961 /** 1962 * A class maintaining the connection related statistics of a Wifi network. 1963 */ 1964 public static class NetworkConnectionStats { 1965 private final int[] mCount = new int[NUMBER_CONNECTION_CNT_CODE]; 1966 private int mRecentCountCode = CNT_INVALID; 1967 /** 1968 * Copy all values 1969 * @param src is the source of copy 1970 */ copy(NetworkConnectionStats src)1971 public void copy(NetworkConnectionStats src) { 1972 for (int i = 0; i < NUMBER_CONNECTION_CNT_CODE; i++) { 1973 mCount[i] = src.getCount(i); 1974 } 1975 mRecentCountCode = src.mRecentCountCode; 1976 } 1977 1978 /** 1979 * Clear all counters 1980 */ clear()1981 public void clear() { 1982 for (int i = 0; i < NUMBER_CONNECTION_CNT_CODE; i++) { 1983 mCount[i] = 0; 1984 } 1985 mRecentCountCode = CNT_INVALID; 1986 } 1987 1988 /** 1989 * Get counter value 1990 * @param countCode is the selected counter 1991 * @return the value of selected counter 1992 */ getCount(@onnectionCountCode int countCode)1993 public int getCount(@ConnectionCountCode int countCode) { 1994 return mCount[countCode]; 1995 } 1996 1997 /** 1998 * Clear counter value 1999 * @param countCode is the selected counter to be cleared 2000 */ clearCount(@onnectionCountCode int countCode)2001 public void clearCount(@ConnectionCountCode int countCode) { 2002 mCount[countCode] = 0; 2003 } 2004 2005 /** 2006 * Increment count value by 1 2007 * @param countCode is the selected counter 2008 */ incrementCount(@onnectionCountCode int countCode)2009 public void incrementCount(@ConnectionCountCode int countCode) { 2010 mCount[countCode]++; 2011 mRecentCountCode = countCode; 2012 } 2013 2014 /** 2015 * Got the recent incremented count code 2016 */ getRecentCountCode()2017 public int getRecentCountCode() { 2018 return mRecentCountCode; 2019 } 2020 2021 /** 2022 * Decrement count value by 1 2023 * @param countCode is the selected counter 2024 */ decrementCount(@onnectionCountCode int countCode)2025 public void decrementCount(@ConnectionCountCode int countCode) { 2026 mCount[countCode]--; 2027 } 2028 2029 /** 2030 * Add and accumulate the selected counter 2031 * @param countCode is the selected counter 2032 * @param cnt is the value to be added to the counter 2033 */ accumulate(@onnectionCountCode int countCode, int cnt)2034 public void accumulate(@ConnectionCountCode int countCode, int cnt) { 2035 mCount[countCode] += cnt; 2036 } 2037 2038 /** 2039 * Accumulate daily stats to historical data 2040 * @param recentStats are the raw daily counts 2041 */ accumulateAll(NetworkConnectionStats recentStats)2042 public void accumulateAll(NetworkConnectionStats recentStats) { 2043 // 32-bit counter in second can support connection duration up to 68 years. 2044 // Similarly 32-bit counter can support up to continuous connection attempt 2045 // up to 68 years with one attempt per second. 2046 for (int i = 0; i < NUMBER_CONNECTION_CNT_CODE; i++) { 2047 mCount[i] += recentStats.getCount(i); 2048 } 2049 } 2050 2051 @Override toString()2052 public String toString() { 2053 StringBuilder sb = new StringBuilder(); 2054 for (int i = 0; i < NUMBER_CONNECTION_CNT_CODE; i++) { 2055 sb.append(CONNECTION_CNT_NAME[i]); 2056 sb.append(mCount[i]); 2057 } 2058 return sb.toString(); 2059 } 2060 } 2061 2062 /** 2063 * A base class dealing with common operations of MemoryStore. 2064 */ 2065 public static class MemoryStoreAccessBase { 2066 private final String mL2Key; 2067 private final long mHash; 2068 private static final String TAG = "WifiMemoryStoreAccessBase"; 2069 private final AtomicReference<byte[]> mPendingReadFromStore = new AtomicReference<>(); MemoryStoreAccessBase(long hash)2070 MemoryStoreAccessBase(long hash) { 2071 mHash = hash; 2072 mL2Key = l2KeyFromLong(); 2073 } getL2Key()2074 String getL2Key() { 2075 return mL2Key; 2076 } 2077 l2KeyFromLong()2078 private String l2KeyFromLong() { 2079 return "W" + Long.toHexString(mHash); 2080 } 2081 2082 /** 2083 * Callback function when MemoryStore read is done 2084 * @param serialized is the readback value 2085 */ readBackListener(byte[] serialized)2086 void readBackListener(byte[] serialized) { 2087 if (serialized == null) return; 2088 byte[] old = mPendingReadFromStore.getAndSet(serialized); 2089 if (old != null) { 2090 Log.e(TAG, "More answers than we expected!"); 2091 } 2092 } 2093 2094 /** 2095 * Handles (when convenient) the arrival of previously stored data. 2096 * 2097 * The response from IpMemoryStore arrives on a different thread, so we 2098 * defer handling it until here, when we're on our favorite thread and 2099 * in a good position to deal with it. We may have already collected some 2100 * data before now, so we need to be prepared to merge the new and old together. 2101 */ finishPendingReadBytes()2102 byte[] finishPendingReadBytes() { 2103 return mPendingReadFromStore.getAndSet(null); 2104 } 2105 idFromLong()2106 int idFromLong() { 2107 return (int) mHash & 0x7fffffff; 2108 } 2109 } 2110 logd(String string)2111 private void logd(String string) { 2112 if (mVerboseLoggingEnabled) { 2113 Log.d(TAG, string); 2114 } 2115 } 2116 logv(String string)2117 private void logv(String string) { 2118 if (mVerboseLoggingEnabled) { 2119 Log.v(TAG, string); 2120 } 2121 mLocalLog.log(string); 2122 } 2123 2124 // Returned by lookupBssid when the BSSID is not available, 2125 // for instance when we are not associated. 2126 private final PerBssid mPlaceholderPerBssid; 2127 2128 private final Map<MacAddress, PerBssid> mApForBssid = new ArrayMap<>(); 2129 private int mApForBssidTargetSize = TARGET_IN_MEMORY_ENTRIES; 2130 private int mApForBssidReferenced = 0; 2131 2132 // TODO should be private, but WifiCandidates needs it lookupBssid(String ssid, String bssid)2133 @NonNull PerBssid lookupBssid(String ssid, String bssid) { 2134 MacAddress mac; 2135 if (ssid == null || WifiManager.UNKNOWN_SSID.equals(ssid) || bssid == null) { 2136 return mPlaceholderPerBssid; 2137 } 2138 try { 2139 mac = MacAddress.fromString(bssid); 2140 } catch (IllegalArgumentException e) { 2141 return mPlaceholderPerBssid; 2142 } 2143 if (mac.equals(mPlaceholderPerBssid.bssid)) { 2144 return mPlaceholderPerBssid; 2145 } 2146 PerBssid ans = mApForBssid.get(mac); 2147 if (ans == null || !ans.ssid.equals(ssid)) { 2148 ans = new PerBssid(ssid, mac); 2149 PerBssid old = mApForBssid.put(mac, ans); 2150 if (old != null) { 2151 Log.i(TAG, "Discarding stats for score card (ssid changed) ID: " + old.id); 2152 if (old.referenced) mApForBssidReferenced--; 2153 } 2154 requestReadBssid(ans); 2155 } 2156 if (!ans.referenced) { 2157 ans.referenced = true; 2158 mApForBssidReferenced++; 2159 clean(); 2160 } 2161 return ans; 2162 } 2163 requestReadBssid(final PerBssid perBssid)2164 private void requestReadBssid(final PerBssid perBssid) { 2165 if (mMemoryStore != null) { 2166 mMemoryStore.read(perBssid.getL2Key(), PER_BSSID_DATA_NAME, 2167 (value) -> perBssid.readBackListener(value)); 2168 } 2169 } 2170 requestReadForAllChanged()2171 private void requestReadForAllChanged() { 2172 for (PerBssid perBssid : mApForBssid.values()) { 2173 if (perBssid.changed) { 2174 requestReadBssid(perBssid); 2175 } 2176 } 2177 } 2178 2179 // Returned by lookupNetwork when the network is not available, 2180 // for instance when we are not associated. 2181 private final PerNetwork mPlaceholderPerNetwork; 2182 private final Map<String, PerNetwork> mApForNetwork = new ArrayMap<>(); lookupNetwork(String ssid)2183 @NonNull PerNetwork lookupNetwork(String ssid) { 2184 if (ssid == null || WifiManager.UNKNOWN_SSID.equals(ssid)) { 2185 return mPlaceholderPerNetwork; 2186 } 2187 2188 PerNetwork ans = mApForNetwork.get(ssid); 2189 if (ans == null) { 2190 ans = new PerNetwork(ssid); 2191 mApForNetwork.put(ssid, ans); 2192 requestReadNetwork(ans); 2193 } 2194 return ans; 2195 } 2196 2197 /** 2198 * Remove network from cache and memory store 2199 * @param ssid is the network SSID 2200 */ removeNetwork(String ssid)2201 public void removeNetwork(String ssid) { 2202 if (ssid == null || WifiManager.UNKNOWN_SSID.equals(ssid)) { 2203 return; 2204 } 2205 mApForNetwork.remove(ssid); 2206 mApForBssid.entrySet().removeIf(entry -> ssid.equals(entry.getValue().ssid)); 2207 if (mMemoryStore == null) return; 2208 mMemoryStore.removeCluster(groupHintFromSsid(ssid)); 2209 } 2210 requestReadNetwork(final PerNetwork perNetwork)2211 void requestReadNetwork(final PerNetwork perNetwork) { 2212 if (mMemoryStore != null) { 2213 mMemoryStore.read(perNetwork.getL2Key(), PER_NETWORK_DATA_NAME, 2214 (value) -> perNetwork.readBackListener(value)); 2215 } 2216 } 2217 2218 /** 2219 * Issues write requests for all changed entries. 2220 * 2221 * This should be called from time to time to save the state to persistent 2222 * storage. Since we always check internal state first, this does not need 2223 * to be called very often, but it should be called before shutdown. 2224 * 2225 * @returns number of writes issued. 2226 */ doWrites()2227 public int doWrites() { 2228 return doWritesBssid() + doWritesNetwork(); 2229 } 2230 doWritesBssid()2231 private int doWritesBssid() { 2232 if (mMemoryStore == null) return 0; 2233 int count = 0; 2234 int bytes = 0; 2235 for (PerBssid perBssid : mApForBssid.values()) { 2236 if (perBssid.changed) { 2237 perBssid.finishPendingRead(); 2238 byte[] serialized = perBssid.toAccessPoint(/* No BSSID */ true).toByteArray(); 2239 mMemoryStore.setCluster(perBssid.getL2Key(), groupHintFromSsid(perBssid.ssid)); 2240 mMemoryStore.write(perBssid.getL2Key(), PER_BSSID_DATA_NAME, serialized); 2241 2242 perBssid.changed = false; 2243 count++; 2244 bytes += serialized.length; 2245 } 2246 } 2247 if (mVerboseLoggingEnabled && count > 0) { 2248 Log.v(TAG, "Write count: " + count + ", bytes: " + bytes); 2249 } 2250 return count; 2251 } 2252 doWritesNetwork()2253 private int doWritesNetwork() { 2254 if (mMemoryStore == null) return 0; 2255 int count = 0; 2256 int bytes = 0; 2257 for (PerNetwork perNetwork : mApForNetwork.values()) { 2258 if (perNetwork.changed) { 2259 perNetwork.finishPendingRead(); 2260 byte[] serialized = perNetwork.toNetworkStats().toByteArray(); 2261 mMemoryStore.setCluster(perNetwork.getL2Key(), groupHintFromSsid(perNetwork.ssid)); 2262 mMemoryStore.write(perNetwork.getL2Key(), PER_NETWORK_DATA_NAME, serialized); 2263 perNetwork.changed = false; 2264 count++; 2265 bytes += serialized.length; 2266 } 2267 } 2268 if (mVerboseLoggingEnabled && count > 0) { 2269 Log.v(TAG, "Write count: " + count + ", bytes: " + bytes); 2270 } 2271 return count; 2272 } 2273 2274 /** 2275 * Evicts older entries from memory. 2276 * 2277 * This uses an approximate least-recently-used method. When the number of 2278 * referenced entries exceeds the target value, any items that have not been 2279 * referenced since the last round are evicted, and the remaining entries 2280 * are marked as unreferenced. The total count varies between the target 2281 * value and twice the target value. 2282 */ clean()2283 private void clean() { 2284 if (mMemoryStore == null) return; 2285 if (mApForBssidReferenced >= mApForBssidTargetSize) { 2286 doWritesBssid(); // Do not want to evict changed items 2287 // Evict the unreferenced ones, and clear all the referenced bits for the next round. 2288 Iterator<Map.Entry<MacAddress, PerBssid>> it = mApForBssid.entrySet().iterator(); 2289 while (it.hasNext()) { 2290 PerBssid perBssid = it.next().getValue(); 2291 if (perBssid.referenced) { 2292 perBssid.referenced = false; 2293 } else { 2294 it.remove(); 2295 if (mVerboseLoggingEnabled) Log.v(TAG, "Evict " + perBssid.id); 2296 } 2297 } 2298 mApForBssidReferenced = 0; 2299 } 2300 } 2301 2302 /** 2303 * Compute a hash value with the given SSID and MAC address 2304 * @param ssid is the network SSID 2305 * @param mac is the network MAC address 2306 * @param l2KeySeed is the seed for hash generation 2307 * @return 2308 */ computeHashLong(String ssid, MacAddress mac, String l2KeySeed)2309 public static long computeHashLong(String ssid, MacAddress mac, String l2KeySeed) { 2310 final ArrayList<Byte> decodedSsid; 2311 try { 2312 decodedSsid = NativeUtil.decodeSsid(ssid); 2313 } catch (IllegalArgumentException e) { 2314 Log.e(TAG, "NativeUtil.decodeSsid failed: malformed string: " + ssid); 2315 return 0; 2316 } 2317 byte[][] parts = { 2318 // Our seed keeps the L2Keys specific to this device 2319 l2KeySeed.getBytes(), 2320 // ssid is either quoted utf8 or hex-encoded bytes; turn it into plain bytes. 2321 NativeUtil.byteArrayFromArrayList(decodedSsid), 2322 // And the BSSID 2323 mac.toByteArray() 2324 }; 2325 // Assemble the parts into one, with single-byte lengths before each. 2326 int n = 0; 2327 for (int i = 0; i < parts.length; i++) { 2328 n += 1 + parts[i].length; 2329 } 2330 byte[] mashed = new byte[n]; 2331 int p = 0; 2332 for (int i = 0; i < parts.length; i++) { 2333 byte[] part = parts[i]; 2334 mashed[p++] = (byte) part.length; 2335 for (int j = 0; j < part.length; j++) { 2336 mashed[p++] = part[j]; 2337 } 2338 } 2339 // Finally, turn that into a long 2340 MessageDigest md; 2341 try { 2342 md = MessageDigest.getInstance("SHA-256"); 2343 } catch (NoSuchAlgorithmException e) { 2344 Log.e(TAG, "SHA-256 not supported."); 2345 return 0; 2346 } 2347 ByteBuffer buffer = ByteBuffer.wrap(md.digest(mashed)); 2348 return buffer.getLong(); 2349 } 2350 groupHintFromLong(long hash)2351 private static String groupHintFromLong(long hash) { 2352 return "G" + Long.toHexString(hash); 2353 } 2354 2355 @VisibleForTesting fetchByBssid(MacAddress mac)2356 PerBssid fetchByBssid(MacAddress mac) { 2357 return mApForBssid.get(mac); 2358 } 2359 2360 @VisibleForTesting fetchByNetwork(String ssid)2361 PerNetwork fetchByNetwork(String ssid) { 2362 return mApForNetwork.get(ssid); 2363 } 2364 2365 @VisibleForTesting perBssidFromAccessPoint(String ssid, AccessPoint ap)2366 PerBssid perBssidFromAccessPoint(String ssid, AccessPoint ap) { 2367 MacAddress bssid = MacAddress.fromBytes(ap.getBssid().toByteArray()); 2368 return new PerBssid(ssid, bssid).merge(ap); 2369 } 2370 2371 @VisibleForTesting perNetworkFromNetworkStats(String ssid, NetworkStats ns)2372 PerNetwork perNetworkFromNetworkStats(String ssid, NetworkStats ns) { 2373 return new PerNetwork(ssid).mergeNetworkStatsFromMemory(ns); 2374 } 2375 2376 final class PerSignal { 2377 public final Event event; 2378 public final int frequency; 2379 public final PerUnivariateStatistic rssi; 2380 public final PerUnivariateStatistic linkspeed; 2381 @Nullable public final PerUnivariateStatistic elapsedMs; PerSignal(Event event, int frequency)2382 PerSignal(Event event, int frequency) { 2383 this.event = event; 2384 this.frequency = frequency; 2385 switch (event) { 2386 case SIGNAL_POLL: 2387 case IP_CONFIGURATION_SUCCESS: 2388 case IP_REACHABILITY_LOST: 2389 this.rssi = new PerUnivariateStatistic(RSSI_BUCKETS); 2390 break; 2391 default: 2392 this.rssi = new PerUnivariateStatistic(); 2393 break; 2394 } 2395 this.linkspeed = new PerUnivariateStatistic(); 2396 switch (event) { 2397 case FIRST_POLL_AFTER_CONNECTION: 2398 case IP_CONFIGURATION_SUCCESS: 2399 case VALIDATION_SUCCESS: 2400 case CONNECTION_FAILURE: 2401 case DISCONNECTION: 2402 case WIFI_DISABLED: 2403 case ROAM_FAILURE: 2404 this.elapsedMs = new PerUnivariateStatistic(); 2405 break; 2406 default: 2407 this.elapsedMs = null; 2408 break; 2409 } 2410 } merge(Signal signal)2411 PerSignal merge(Signal signal) { 2412 Preconditions.checkArgument(event == signal.getEvent()); 2413 Preconditions.checkArgument(frequency == signal.getFrequency()); 2414 rssi.merge(signal.getRssi()); 2415 linkspeed.merge(signal.getLinkspeed()); 2416 if (elapsedMs != null && signal.hasElapsedMs()) { 2417 elapsedMs.merge(signal.getElapsedMs()); 2418 } 2419 return this; 2420 } toSignal()2421 Signal toSignal() { 2422 Signal.Builder builder = Signal.newBuilder(); 2423 builder.setEvent(event) 2424 .setFrequency(frequency) 2425 .setRssi(rssi.toUnivariateStatistic()) 2426 .setLinkspeed(linkspeed.toUnivariateStatistic()); 2427 if (elapsedMs != null) { 2428 builder.setElapsedMs(elapsedMs.toUnivariateStatistic()); 2429 } 2430 if (rssi.intHistogram != null 2431 && rssi.intHistogram.numNonEmptyBuckets() > 0) { 2432 logd("Histogram " + event + " RSSI" + rssi.intHistogram); 2433 } 2434 return builder.build(); 2435 } 2436 } 2437 2438 final class PerUnivariateStatistic { 2439 public long count = 0; 2440 public double sum = 0.0; 2441 public double sumOfSquares = 0.0; 2442 public double minValue = Double.POSITIVE_INFINITY; 2443 public double maxValue = Double.NEGATIVE_INFINITY; 2444 public double historicalMean = 0.0; 2445 public double historicalVariance = Double.POSITIVE_INFINITY; 2446 public IntHistogram intHistogram = null; PerUnivariateStatistic()2447 PerUnivariateStatistic() {} PerUnivariateStatistic(int[] bucketBoundaries)2448 PerUnivariateStatistic(int[] bucketBoundaries) { 2449 intHistogram = new IntHistogram(bucketBoundaries); 2450 } update(double value)2451 void update(double value) { 2452 count++; 2453 sum += value; 2454 sumOfSquares += value * value; 2455 minValue = Math.min(minValue, value); 2456 maxValue = Math.max(maxValue, value); 2457 if (intHistogram != null) { 2458 intHistogram.add(Math.round((float) value), 1); 2459 } 2460 } age()2461 void age() { 2462 //TODO Fold the current stats into the historical stats 2463 } merge(UnivariateStatistic stats)2464 void merge(UnivariateStatistic stats) { 2465 if (stats.hasCount()) { 2466 count += stats.getCount(); 2467 sum += stats.getSum(); 2468 sumOfSquares += stats.getSumOfSquares(); 2469 } 2470 if (stats.hasMinValue()) { 2471 minValue = Math.min(minValue, stats.getMinValue()); 2472 } 2473 if (stats.hasMaxValue()) { 2474 maxValue = Math.max(maxValue, stats.getMaxValue()); 2475 } 2476 if (stats.hasHistoricalVariance()) { 2477 if (historicalVariance < Double.POSITIVE_INFINITY) { 2478 // Combine the estimates; c.f. 2479 // Maybeck, Stochasic Models, Estimation, and Control, Vol. 1 2480 // equations (1-3) and (1-4) 2481 double numer1 = stats.getHistoricalVariance(); 2482 double numer2 = historicalVariance; 2483 double denom = numer1 + numer2; 2484 historicalMean = (numer1 * historicalMean 2485 + numer2 * stats.getHistoricalMean()) 2486 / denom; 2487 historicalVariance = numer1 * numer2 / denom; 2488 } else { 2489 historicalMean = stats.getHistoricalMean(); 2490 historicalVariance = stats.getHistoricalVariance(); 2491 } 2492 } 2493 if (intHistogram != null) { 2494 for (HistogramBucket bucket : stats.getBucketsList()) { 2495 long low = bucket.getLow(); 2496 long count = bucket.getNumber(); 2497 if (low != (int) low || count != (int) count || count < 0) { 2498 Log.e(TAG, "Found corrupted histogram! Clearing."); 2499 intHistogram.clear(); 2500 break; 2501 } 2502 intHistogram.add((int) low, (int) count); 2503 } 2504 } 2505 } toUnivariateStatistic()2506 UnivariateStatistic toUnivariateStatistic() { 2507 UnivariateStatistic.Builder builder = UnivariateStatistic.newBuilder(); 2508 if (count != 0) { 2509 builder.setCount(count) 2510 .setSum(sum) 2511 .setSumOfSquares(sumOfSquares) 2512 .setMinValue(minValue) 2513 .setMaxValue(maxValue); 2514 } 2515 if (historicalVariance < Double.POSITIVE_INFINITY) { 2516 builder.setHistoricalMean(historicalMean) 2517 .setHistoricalVariance(historicalVariance); 2518 } 2519 if (mPersistentHistograms 2520 && intHistogram != null && intHistogram.numNonEmptyBuckets() > 0) { 2521 for (IntHistogram.Bucket b : intHistogram) { 2522 if (b.count == 0) continue; 2523 builder.addBuckets( 2524 HistogramBucket.newBuilder().setLow(b.start).setNumber(b.count)); 2525 } 2526 } 2527 return builder.build(); 2528 } 2529 } 2530 2531 /** 2532 * Returns the current scorecard in the form of a protobuf com_android_server_wifi.NetworkList 2533 * 2534 * Synchronization is the caller's responsibility. 2535 * 2536 * @param obfuscate - if true, ssids and bssids are omitted (short id only) 2537 */ getNetworkListByteArray(boolean obfuscate)2538 public byte[] getNetworkListByteArray(boolean obfuscate) { 2539 // These are really grouped by ssid, ignoring the security type. 2540 Map<String, Network.Builder> networks = new ArrayMap<>(); 2541 for (PerBssid perBssid: mApForBssid.values()) { 2542 String key = perBssid.ssid; 2543 Network.Builder network = networks.get(key); 2544 if (network == null) { 2545 network = Network.newBuilder(); 2546 networks.put(key, network); 2547 if (!obfuscate) { 2548 network.setSsid(perBssid.ssid); 2549 } 2550 } 2551 if (perBssid.mNetworkAgentId >= network.getNetworkAgentId()) { 2552 network.setNetworkAgentId(perBssid.mNetworkAgentId); 2553 } 2554 if (perBssid.mNetworkConfigId >= network.getNetworkConfigId()) { 2555 network.setNetworkConfigId(perBssid.mNetworkConfigId); 2556 } 2557 network.addAccessPoints(perBssid.toAccessPoint(obfuscate)); 2558 } 2559 for (PerNetwork perNetwork: mApForNetwork.values()) { 2560 String key = perNetwork.ssid; 2561 Network.Builder network = networks.get(key); 2562 if (network != null) { 2563 network.setNetworkStats(perNetwork.toNetworkStats()); 2564 } 2565 } 2566 NetworkList.Builder builder = NetworkList.newBuilder(); 2567 for (Network.Builder network: networks.values()) { 2568 builder.addNetworks(network); 2569 } 2570 return builder.build().toByteArray(); 2571 } 2572 2573 /** 2574 * Returns the current scorecard as a base64-encoded protobuf 2575 * 2576 * Synchronization is the caller's responsibility. 2577 * 2578 * @param obfuscate - if true, bssids are omitted (short id only) 2579 */ getNetworkListBase64(boolean obfuscate)2580 public String getNetworkListBase64(boolean obfuscate) { 2581 byte[] raw = getNetworkListByteArray(obfuscate); 2582 return Base64.encodeToString(raw, Base64.DEFAULT); 2583 } 2584 2585 /** 2586 * Clears the internal state. 2587 * 2588 * This is called in response to a factoryReset call from Settings. 2589 * The memory store will be called after we are called, to wipe the stable 2590 * storage as well. Since we will have just removed all of our networks, 2591 * it is very unlikely that we're connected, or will connect immediately. 2592 * Any in-flight reads will land in the objects we are dropping here, and 2593 * the memory store should drop the in-flight writes. Ideally we would 2594 * avoid issuing reads until we were sure that the memory store had 2595 * received the factoryReset. 2596 */ clear()2597 public void clear() { 2598 mApForBssid.clear(); 2599 mApForNetwork.clear(); 2600 resetAllConnectionStatesInternal(); 2601 } 2602 2603 /** 2604 * build bandwidth estimator stats proto and then clear all related counters 2605 */ dumpBandwidthEstimatorStats()2606 public BandwidthEstimatorStats dumpBandwidthEstimatorStats() { 2607 BandwidthEstimatorStats stats = new BandwidthEstimatorStats(); 2608 stats.stats2G = dumpBandwdithStatsPerBand(0); 2609 stats.statsAbove2G = dumpBandwdithStatsPerBand(1); 2610 return stats; 2611 } 2612 dumpBandwdithStatsPerBand(int bandIdx)2613 private BandwidthEstimatorStats.PerBand dumpBandwdithStatsPerBand(int bandIdx) { 2614 BandwidthEstimatorStats.PerBand stats = new BandwidthEstimatorStats.PerBand(); 2615 stats.tx = dumpBandwidthStatsPerLink(bandIdx, LINK_TX); 2616 stats.rx = dumpBandwidthStatsPerLink(bandIdx, LINK_RX); 2617 return stats; 2618 } 2619 dumpBandwidthStatsPerLink( int bandIdx, int linkIdx)2620 private BandwidthEstimatorStats.PerLink dumpBandwidthStatsPerLink( 2621 int bandIdx, int linkIdx) { 2622 BandwidthEstimatorStats.PerLink stats = new BandwidthEstimatorStats.PerLink(); 2623 List<BandwidthEstimatorStats.PerLevel> levels = new ArrayList<>(); 2624 for (int level = 0; level < NUM_SIGNAL_LEVEL; level++) { 2625 BandwidthEstimatorStats.PerLevel currStats = 2626 dumpBandwidthStatsPerLevel(bandIdx, linkIdx, level); 2627 if (currStats != null) { 2628 levels.add(currStats); 2629 } 2630 } 2631 stats.level = levels.toArray(new BandwidthEstimatorStats.PerLevel[0]); 2632 return stats; 2633 } 2634 dumpBandwidthStatsPerLevel( int bandIdx, int linkIdx, int level)2635 private BandwidthEstimatorStats.PerLevel dumpBandwidthStatsPerLevel( 2636 int bandIdx, int linkIdx, int level) { 2637 int count = mBwEstCount[bandIdx][linkIdx][level]; 2638 if (count <= 0) { 2639 return null; 2640 } 2641 2642 BandwidthEstimatorStats.PerLevel stats = new BandwidthEstimatorStats.PerLevel(); 2643 stats.signalLevel = level; 2644 stats.count = count; 2645 stats.avgBandwidthKbps = calculateAvg(mBwEstValue[bandIdx][linkIdx][level], count); 2646 stats.l2ErrorPercent = calculateAvg( 2647 mL2ErrorAccPercent[bandIdx][linkIdx][level], count); 2648 stats.bandwidthEstErrorPercent = calculateAvg( 2649 mBwEstErrorAccPercent[bandIdx][linkIdx][level], count); 2650 2651 // reset counters for next run 2652 mBwEstCount[bandIdx][linkIdx][level] = 0; 2653 mBwEstValue[bandIdx][linkIdx][level] = 0; 2654 mL2ErrorAccPercent[bandIdx][linkIdx][level] = 0; 2655 mBwEstErrorAccPercent[bandIdx][linkIdx][level] = 0; 2656 return stats; 2657 } 2658 calculateAvg(long acc, int count)2659 private int calculateAvg(long acc, int count) { 2660 return (count > 0) ? (int) (acc / count) : 0; 2661 } 2662 2663 /** 2664 * Dump the internal state and local logs 2665 */ dump(FileDescriptor fd, PrintWriter pw, String[] args)2666 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 2667 pw.println("Dump of WifiScoreCard"); 2668 pw.println("current SSID(s):" + mIfaceToInfoMap.entrySet().stream() 2669 .map(entry -> 2670 "{iface=" + entry.getKey() + ",ssid=" + entry.getValue().ssidCurr + "}") 2671 .collect(Collectors.joining(","))); 2672 try { 2673 mLocalLog.dump(fd, pw, args); 2674 } catch (Exception e) { 2675 e.printStackTrace(); 2676 } 2677 2678 pw.println(" BW Estimation Stats"); 2679 for (int i = 0; i < 2; i++) { 2680 pw.println((i == 0 ? "2G" : "5G")); 2681 for (int j = 0; j < NUM_LINK_DIRECTION; j++) { 2682 pw.println((j == 0 ? " Tx" : " Rx")); 2683 pw.println(" Count"); 2684 printValues(mBwEstCount[i][j], pw); 2685 pw.println(" AvgKbps"); 2686 printAvgStats(mBwEstValue[i][j], mBwEstCount[i][j], pw); 2687 pw.println(" BwEst error"); 2688 printAvgStats(mBwEstErrorAccPercent[i][j], mBwEstCount[i][j], pw); 2689 pw.println(" L2 error"); 2690 printAvgStats(mL2ErrorAccPercent[i][j], mBwEstCount[i][j], pw); 2691 } 2692 } 2693 pw.println(); 2694 } 2695 printValues(int[] values, PrintWriter pw)2696 private void printValues(int[] values, PrintWriter pw) { 2697 StringBuilder sb = new StringBuilder(); 2698 for (int k = 0; k < NUM_SIGNAL_LEVEL; k++) { 2699 sb.append(" " + values[k]); 2700 } 2701 pw.println(sb.toString()); 2702 } 2703 printAvgStats(long[] stats, int[] count, PrintWriter pw)2704 private void printAvgStats(long[] stats, int[] count, PrintWriter pw) { 2705 StringBuilder sb = new StringBuilder(); 2706 for (int k = 0; k < NUM_SIGNAL_LEVEL; k++) { 2707 sb.append(" " + calculateAvg(stats[k], count[k])); 2708 } 2709 pw.println(sb.toString()); 2710 } 2711 } 2712