WifiMetrics.java revision c2c2648141e6190d85601ee8a6a1d0034e7ff927
1/* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.server.wifi; 18 19import android.net.wifi.ScanResult; 20import android.net.wifi.WifiConfiguration; 21import android.os.SystemClock; 22import android.util.Base64; 23import android.util.Log; 24import android.util.SparseIntArray; 25 26import com.android.server.wifi.hotspot2.NetworkDetail; 27import com.android.server.wifi.util.InformationElementUtil; 28 29import java.io.FileDescriptor; 30import java.io.PrintWriter; 31import java.util.ArrayList; 32import java.util.Calendar; 33import java.util.List; 34 35/** 36 * Provides storage for wireless connectivity metrics, as they are generated. 37 * Metrics logged by this class include: 38 * Aggregated connection stats (num of connections, num of failures, ...) 39 * Discrete connection event stats (time, duration, failure codes, ...) 40 * Router details (technology type, authentication type, ...) 41 * Scan stats 42 */ 43public class WifiMetrics { 44 private static final String TAG = "WifiMetrics"; 45 private static final boolean DBG = false; 46 private final Object mLock = new Object(); 47 private static final int MAX_CONNECTION_EVENTS = 256; 48 /** 49 * Metrics are stored within an instance of the WifiLog proto during runtime, 50 * The ConnectionEvent, SystemStateEntries & ScanReturnEntries metrics are stored during 51 * runtime in member lists of this WifiMetrics class, with the final WifiLog proto being pieced 52 * together at dump-time 53 */ 54 private final WifiMetricsProto.WifiLog mWifiLogProto; 55 /** 56 * Session information that gets logged for every Wifi connection attempt. 57 */ 58 private final List<ConnectionEvent> mConnectionEventList; 59 /** 60 * The latest started (but un-ended) connection attempt 61 */ 62 private ConnectionEvent mCurrentConnectionEvent; 63 /** 64 * Count of number of times each scan return code, indexed by WifiLog.ScanReturnCode 65 */ 66 private SparseIntArray mScanReturnEntries; 67 /** 68 * Mapping of system state to the counts of scans requested in that wifi state * screenOn 69 * combination. Indexed by WifiLog.WifiState * (1 + screenOn) 70 */ 71 private SparseIntArray mWifiSystemStateEntries; 72 73 class RouterFingerPrint { 74 private WifiMetricsProto.RouterFingerPrint mRouterFingerPrintProto; 75 RouterFingerPrint() { 76 mRouterFingerPrintProto = new WifiMetricsProto.RouterFingerPrint(); 77 } 78 79 public String toString() { 80 StringBuilder sb = new StringBuilder(); 81 synchronized (mLock) { 82 sb.append("mConnectionEvent.roamType=" + mRouterFingerPrintProto.roamType); 83 sb.append(", mChannelInfo=" + mRouterFingerPrintProto.channelInfo); 84 sb.append(", mDtim=" + mRouterFingerPrintProto.dtim); 85 sb.append(", mAuthentication=" + mRouterFingerPrintProto.authentication); 86 sb.append(", mHidden=" + mRouterFingerPrintProto.hidden); 87 sb.append(", mRouterTechnology=" + mRouterFingerPrintProto.routerTechnology); 88 sb.append(", mSupportsIpv6=" + mRouterFingerPrintProto.supportsIpv6); 89 } 90 return sb.toString(); 91 } 92 public void updateFromWifiConfiguration(WifiConfiguration config) { 93 if (config != null) { 94 // Is this a hidden network 95 mRouterFingerPrintProto.hidden = config.hiddenSSID; 96 // Config may not have a valid dtimInterval set yet, in which case dtim will be zero 97 // (These are only populated from beacon frame scan results, which are returned as 98 // scan results from the chip far less frequently than Probe-responses) 99 if (config.dtimInterval > 0) { 100 mRouterFingerPrintProto.dtim = config.dtimInterval; 101 } 102 mCurrentConnectionEvent.mConfigSsid = config.SSID; 103 // Get AuthType information from config (We do this again from ScanResult after 104 // associating with BSSID) 105 if (config.allowedKeyManagement != null 106 && config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE)) { 107 mCurrentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto 108 .authentication = WifiMetricsProto.RouterFingerPrint.AUTH_OPEN; 109 } else if (config.isEnterprise()) { 110 mCurrentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto 111 .authentication = WifiMetricsProto.RouterFingerPrint.AUTH_ENTERPRISE; 112 } else { 113 mCurrentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto 114 .authentication = WifiMetricsProto.RouterFingerPrint.AUTH_PERSONAL; 115 } 116 // If there's a ScanResult candidate associated with this config already, get it and 117 // log (more accurate) metrics from it 118 ScanResult candidate = config.getNetworkSelectionStatus().getCandidate(); 119 if (candidate != null) { 120 updateMetricsFromScanResult(candidate); 121 } 122 } 123 } 124 } 125 126 /** 127 * Log event, tracking the start time, end time and result of a wireless connection attempt. 128 */ 129 class ConnectionEvent { 130 WifiMetricsProto.ConnectionEvent mConnectionEvent; 131 //<TODO> Move these constants into a wifi.proto Enum, and create a new Failure Type field 132 //covering more than just l2 failures. see b/27652362 133 /** 134 * Failure codes, used for the 'level_2_failure_code' Connection event field (covers a lot 135 * more failures than just l2 though, since the proto does not have a place to log 136 * framework failures) 137 */ 138 // Failure is unknown 139 public static final int FAILURE_UNKNOWN = 0; 140 // NONE 141 public static final int FAILURE_NONE = 1; 142 // ASSOCIATION_REJECTION_EVENT 143 public static final int FAILURE_ASSOCIATION_REJECTION = 2; 144 // AUTHENTICATION_FAILURE_EVENT 145 public static final int FAILURE_AUTHENTICATION_FAILURE = 3; 146 // SSID_TEMP_DISABLED (Also Auth failure) 147 public static final int FAILURE_SSID_TEMP_DISABLED = 4; 148 // reconnect() or reassociate() call to WifiNative failed 149 public static final int FAILURE_CONNECT_NETWORK_FAILED = 5; 150 // NETWORK_DISCONNECTION_EVENT 151 public static final int FAILURE_NETWORK_DISCONNECTION = 6; 152 // NEW_CONNECTION_ATTEMPT before previous finished 153 public static final int FAILURE_NEW_CONNECTION_ATTEMPT = 7; 154 // New connection attempt to the same network & bssid 155 public static final int FAILURE_REDUNDANT_CONNECTION_ATTEMPT = 8; 156 // Roam Watchdog timer triggered (Roaming timed out) 157 public static final int FAILURE_ROAM_TIMEOUT = 9; 158 // DHCP failure 159 public static final int FAILURE_DHCP = 10; 160 161 RouterFingerPrint mRouterFingerPrint; 162 private long mRealStartTime; 163 private long mRealEndTime; 164 private String mConfigSsid; 165 private String mConfigBssid; 166 167 private ConnectionEvent() { 168 mConnectionEvent = new WifiMetricsProto.ConnectionEvent(); 169 mRealEndTime = 0; 170 mRealStartTime = 0; 171 mRouterFingerPrint = new RouterFingerPrint(); 172 mConnectionEvent.routerFingerprint = mRouterFingerPrint.mRouterFingerPrintProto; 173 mConfigSsid = "<NULL>"; 174 mConfigBssid = "<NULL>"; 175 } 176 177 public String toString() { 178 StringBuilder sb = new StringBuilder(); 179 sb.append("startTime="); 180 Calendar c = Calendar.getInstance(); 181 synchronized (mLock) { 182 c.setTimeInMillis(mConnectionEvent.startTimeMillis); 183 sb.append(mConnectionEvent.startTimeMillis == 0 ? " <null>" : 184 String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c)); 185 sb.append(", SSID="); 186 sb.append(mConfigSsid); 187 sb.append(", BSSID="); 188 sb.append(mConfigBssid); 189 sb.append(", durationMillis="); 190 sb.append(mConnectionEvent.durationTakenToConnectMillis); 191 sb.append(", roamType="); 192 switch(mConnectionEvent.roamType) { 193 case 1: 194 sb.append("ROAM_NONE"); 195 break; 196 case 2: 197 sb.append("ROAM_DBDC"); 198 break; 199 case 3: 200 sb.append("ROAM_ENTERPRISE"); 201 break; 202 case 4: 203 sb.append("ROAM_USER_SELECTED"); 204 break; 205 case 5: 206 sb.append("ROAM_UNRELATED"); 207 break; 208 default: 209 sb.append("ROAM_UNKNOWN"); 210 } 211 sb.append(", connectionResult="); 212 sb.append(mConnectionEvent.connectionResult); 213 sb.append(", level2FailureCode="); 214 switch(mConnectionEvent.level2FailureCode) { 215 case FAILURE_NONE: 216 sb.append("NONE"); 217 break; 218 case FAILURE_ASSOCIATION_REJECTION: 219 sb.append("ASSOCIATION_REJECTION"); 220 break; 221 case FAILURE_AUTHENTICATION_FAILURE: 222 sb.append("AUTHENTICATION_FAILURE"); 223 break; 224 case FAILURE_SSID_TEMP_DISABLED: 225 sb.append("SSID_TEMP_DISABLED"); 226 break; 227 case FAILURE_CONNECT_NETWORK_FAILED: 228 sb.append("CONNECT_NETWORK_FAILED"); 229 break; 230 case FAILURE_NETWORK_DISCONNECTION: 231 sb.append("NETWORK_DISCONNECTION"); 232 break; 233 case FAILURE_NEW_CONNECTION_ATTEMPT: 234 sb.append("NEW_CONNECTION_ATTEMPT"); 235 break; 236 case FAILURE_REDUNDANT_CONNECTION_ATTEMPT: 237 sb.append("REDUNDANT_CONNECTION_ATTEMPT"); 238 break; 239 case FAILURE_ROAM_TIMEOUT: 240 sb.append("ROAM_TIMEOUT"); 241 break; 242 case FAILURE_DHCP: 243 sb.append("DHCP"); 244 default: 245 sb.append("UNKNOWN"); 246 break; 247 } 248 sb.append(", connectivityLevelFailureCode="); 249 switch(mConnectionEvent.connectivityLevelFailureCode) { 250 case WifiMetricsProto.ConnectionEvent.HLF_NONE: 251 sb.append("NONE"); 252 break; 253 case WifiMetricsProto.ConnectionEvent.HLF_DHCP: 254 sb.append("DHCP"); 255 break; 256 case WifiMetricsProto.ConnectionEvent.HLF_NO_INTERNET: 257 sb.append("NO_INTERNET"); 258 break; 259 case WifiMetricsProto.ConnectionEvent.HLF_UNWANTED: 260 sb.append("UNWANTED"); 261 break; 262 default: 263 sb.append("UNKNOWN"); 264 break; 265 } 266 sb.append(", signalStrength="); 267 sb.append(mConnectionEvent.signalStrength); 268 sb.append(" "); 269 sb.append("mRouterFingerprint: "); 270 sb.append(mRouterFingerPrint.toString()); 271 } 272 return sb.toString(); 273 } 274 } 275 276 public WifiMetrics() { 277 mWifiLogProto = new WifiMetricsProto.WifiLog(); 278 mConnectionEventList = new ArrayList<>(); 279 mScanReturnEntries = new SparseIntArray(); 280 mWifiSystemStateEntries = new SparseIntArray(); 281 mCurrentConnectionEvent = null; 282 } 283 284 // Values used for indexing SystemStateEntries 285 private static final int SCREEN_ON = 1; 286 private static final int SCREEN_OFF = 0; 287 288 /** 289 * Create a new connection event. Call when wifi attempts to make a new network connection 290 * If there is a current 'un-ended' connection event, it will be ended with UNKNOWN connectivity 291 * failure code. 292 * Gathers and sets the RouterFingerPrint data as well 293 * 294 * @param config WifiConfiguration of the config used for the current connection attempt 295 * @param roamType Roam type that caused connection attempt, see WifiMetricsProto.WifiLog.ROAM_X 296 */ 297 public void startConnectionEvent(WifiConfiguration config, String targetBSSID, int roamType) { 298 synchronized (mLock) { 299 // Check if this is overlapping another current connection event 300 if (mCurrentConnectionEvent != null) { 301 //Is this new Connection Event the same as the current one 302 if (mCurrentConnectionEvent.mConfigSsid != null 303 && mCurrentConnectionEvent.mConfigBssid != null 304 && config != null 305 && mCurrentConnectionEvent.mConfigSsid.equals(config.SSID) 306 && (mCurrentConnectionEvent.mConfigBssid.equals("any") 307 || mCurrentConnectionEvent.mConfigBssid.equals(targetBSSID))) { 308 mCurrentConnectionEvent.mConfigBssid = targetBSSID; 309 // End Connection Event due to new connection attempt to the same network 310 endConnectionEvent(ConnectionEvent.FAILURE_REDUNDANT_CONNECTION_ATTEMPT, 311 WifiMetricsProto.ConnectionEvent.HLF_NONE); 312 } else { 313 // End Connection Event due to new connection attempt to different network 314 endConnectionEvent(ConnectionEvent.FAILURE_NEW_CONNECTION_ATTEMPT, 315 WifiMetricsProto.ConnectionEvent.HLF_NONE); 316 } 317 } 318 //If past maximum connection events, start removing the oldest 319 while(mConnectionEventList.size() >= MAX_CONNECTION_EVENTS) { 320 mConnectionEventList.remove(0); 321 } 322 mCurrentConnectionEvent = new ConnectionEvent(); 323 mCurrentConnectionEvent.mConnectionEvent.startTimeMillis = 324 System.currentTimeMillis(); 325 mCurrentConnectionEvent.mConfigBssid = targetBSSID; 326 mCurrentConnectionEvent.mConnectionEvent.roamType = roamType; 327 mCurrentConnectionEvent.mRouterFingerPrint.updateFromWifiConfiguration(config); 328 mCurrentConnectionEvent.mConfigBssid = "any"; 329 mCurrentConnectionEvent.mRealStartTime = SystemClock.elapsedRealtime(); 330 mConnectionEventList.add(mCurrentConnectionEvent); 331 } 332 } 333 334 /** 335 * set the RoamType of the current ConnectionEvent (if any) 336 */ 337 public void setConnectionEventRoamType(int roamType) { 338 if (mCurrentConnectionEvent != null) { 339 mCurrentConnectionEvent.mConnectionEvent.roamType = roamType; 340 } 341 } 342 343 /** 344 * Set AP related metrics from ScanDetail 345 */ 346 public void setConnectionScanDetail(ScanDetail scanDetail) { 347 if (mCurrentConnectionEvent != null && scanDetail != null) { 348 NetworkDetail networkDetail = scanDetail.getNetworkDetail(); 349 ScanResult scanResult = scanDetail.getScanResult(); 350 //Ensure that we have a networkDetail, and that it corresponds to the currently 351 //tracked connection attempt 352 if (networkDetail != null && scanResult != null 353 && mCurrentConnectionEvent.mConfigSsid != null 354 && mCurrentConnectionEvent.mConfigSsid 355 .equals("\"" + networkDetail.getSSID() + "\"")) { 356 updateMetricsFromNetworkDetail(networkDetail); 357 updateMetricsFromScanResult(scanResult); 358 } 359 } 360 } 361 362 /** 363 * End a Connection event record. Call when wifi connection attempt succeeds or fails. 364 * If a Connection event has not been started and is active when .end is called, a new one is 365 * created with zero duration. 366 * 367 * @param level2FailureCode Level 2 failure code returned by supplicant 368 * @param connectivityFailureCode WifiMetricsProto.ConnectionEvent.HLF_X 369 */ 370 public void endConnectionEvent(int level2FailureCode, int connectivityFailureCode) { 371 synchronized (mLock) { 372 if (mCurrentConnectionEvent != null) { 373 boolean result = (level2FailureCode == 1) 374 && (connectivityFailureCode == WifiMetricsProto.ConnectionEvent.HLF_NONE); 375 mCurrentConnectionEvent.mConnectionEvent.connectionResult = result ? 1 : 0; 376 mCurrentConnectionEvent.mRealEndTime = SystemClock.elapsedRealtime(); 377 mCurrentConnectionEvent.mConnectionEvent.durationTakenToConnectMillis = (int) 378 (mCurrentConnectionEvent.mRealEndTime 379 - mCurrentConnectionEvent.mRealStartTime); 380 mCurrentConnectionEvent.mConnectionEvent.level2FailureCode = level2FailureCode; 381 mCurrentConnectionEvent.mConnectionEvent.connectivityLevelFailureCode = 382 connectivityFailureCode; 383 // ConnectionEvent already added to ConnectionEvents List. Safe to null current here 384 mCurrentConnectionEvent = null; 385 } 386 } 387 } 388 389 /** 390 * Set ConnectionEvent DTIM Interval (if set), and 802.11 Connection mode, from NetworkDetail 391 */ 392 private void updateMetricsFromNetworkDetail(NetworkDetail networkDetail) { 393 int dtimInterval = networkDetail.getDtimInterval(); 394 if (dtimInterval > 0) { 395 mCurrentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto.dtim = 396 dtimInterval; 397 } 398 int connectionWifiMode; 399 switch (networkDetail.getWifiMode()) { 400 case InformationElementUtil.WifiMode.MODE_UNDEFINED: 401 connectionWifiMode = WifiMetricsProto.RouterFingerPrint.ROUTER_TECH_UNKNOWN; 402 break; 403 case InformationElementUtil.WifiMode.MODE_11A: 404 connectionWifiMode = WifiMetricsProto.RouterFingerPrint.ROUTER_TECH_A; 405 break; 406 case InformationElementUtil.WifiMode.MODE_11B: 407 connectionWifiMode = WifiMetricsProto.RouterFingerPrint.ROUTER_TECH_B; 408 break; 409 case InformationElementUtil.WifiMode.MODE_11G: 410 connectionWifiMode = WifiMetricsProto.RouterFingerPrint.ROUTER_TECH_G; 411 break; 412 case InformationElementUtil.WifiMode.MODE_11N: 413 connectionWifiMode = WifiMetricsProto.RouterFingerPrint.ROUTER_TECH_N; 414 break; 415 case InformationElementUtil.WifiMode.MODE_11AC : 416 connectionWifiMode = WifiMetricsProto.RouterFingerPrint.ROUTER_TECH_AC; 417 break; 418 default: 419 connectionWifiMode = WifiMetricsProto.RouterFingerPrint.ROUTER_TECH_OTHER; 420 break; 421 } 422 mCurrentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto 423 .routerTechnology = connectionWifiMode; 424 } 425 426 /** 427 * Set ConnectionEvent RSSI and authentication type from ScanResult 428 */ 429 private void updateMetricsFromScanResult(ScanResult scanResult) { 430 mCurrentConnectionEvent.mConnectionEvent.signalStrength = scanResult.level; 431 mCurrentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto.authentication = 432 WifiMetricsProto.RouterFingerPrint.AUTH_OPEN; 433 mCurrentConnectionEvent.mConfigBssid = scanResult.BSSID; 434 if (scanResult.capabilities != null) { 435 if (scanResult.capabilities.contains("WEP")) { 436 mCurrentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto.authentication = 437 WifiMetricsProto.RouterFingerPrint.AUTH_PERSONAL; 438 } else if (scanResult.capabilities.contains("PSK")) { 439 mCurrentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto.authentication = 440 WifiMetricsProto.RouterFingerPrint.AUTH_PERSONAL; 441 } else if (scanResult.capabilities.contains("EAP")) { 442 mCurrentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto.authentication = 443 WifiMetricsProto.RouterFingerPrint.AUTH_ENTERPRISE; 444 } 445 } 446 mCurrentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto.channelInfo = 447 scanResult.frequency; 448 } 449 450 void setNumSavedNetworks(int num) { 451 synchronized (mLock) { 452 mWifiLogProto.numSavedNetworks = num; 453 } 454 } 455 456 void setNumOpenNetworks(int num) { 457 synchronized (mLock) { 458 mWifiLogProto.numOpenNetworks = num; 459 } 460 } 461 462 void setNumPersonalNetworks(int num) { 463 synchronized (mLock) { 464 mWifiLogProto.numPersonalNetworks = num; 465 } 466 } 467 468 void setNumEnterpriseNetworks(int num) { 469 synchronized (mLock) { 470 mWifiLogProto.numEnterpriseNetworks = num; 471 } 472 } 473 474 void setNumNetworksAddedByUser(int num) { 475 synchronized (mLock) { 476 mWifiLogProto.numNetworksAddedByUser = num; 477 } 478 } 479 480 void setNumNetworksAddedByApps(int num) { 481 synchronized (mLock) { 482 mWifiLogProto.numNetworksAddedByApps = num; 483 } 484 } 485 486 void setIsLocationEnabled(boolean enabled) { 487 synchronized (mLock) { 488 mWifiLogProto.isLocationEnabled = enabled; 489 } 490 } 491 492 void setIsScanningAlwaysEnabled(boolean enabled) { 493 synchronized (mLock) { 494 mWifiLogProto.isScanningAlwaysEnabled = enabled; 495 } 496 } 497 498 /** 499 * Increment Non Empty Scan Results count 500 */ 501 public void incrementNonEmptyScanResultCount() { 502 if (DBG) Log.v(TAG, "incrementNonEmptyScanResultCount"); 503 synchronized (mLock) { 504 mWifiLogProto.numNonEmptyScanResults++; 505 } 506 } 507 508 /** 509 * Increment Empty Scan Results count 510 */ 511 public void incrementEmptyScanResultCount() { 512 if (DBG) Log.v(TAG, "incrementEmptyScanResultCount"); 513 synchronized (mLock) { 514 mWifiLogProto.numEmptyScanResults++; 515 } 516 } 517 518 /** 519 * Increment background scan count 520 */ 521 public void incrementBackgroundScanCount() { 522 if (DBG) Log.v(TAG, "incrementBackgroundScanCount"); 523 synchronized (mLock) { 524 mWifiLogProto.numBackgroundScans++; 525 } 526 } 527 528 /** 529 * Get Background scan count 530 */ 531 public int getBackgroundScanCount() { 532 synchronized (mLock) { 533 return mWifiLogProto.numBackgroundScans; 534 } 535 } 536 537 /** 538 * Increment oneshot scan count 539 */ 540 public void incrementOneshotScanCount() { 541 synchronized (mLock) { 542 mWifiLogProto.numOneshotScans++; 543 } 544 } 545 546 /** 547 * Get oneshot scan count 548 */ 549 public int getOneshotScanCount() { 550 synchronized (mLock) { 551 return mWifiLogProto.numOneshotScans; 552 } 553 } 554 555 private String returnCodeToString(int scanReturnCode) { 556 switch(scanReturnCode){ 557 case WifiMetricsProto.WifiLog.SCAN_UNKNOWN: 558 return "SCAN_UNKNOWN"; 559 case WifiMetricsProto.WifiLog.SCAN_SUCCESS: 560 return "SCAN_SUCCESS"; 561 case WifiMetricsProto.WifiLog.SCAN_FAILURE_INTERRUPTED: 562 return "SCAN_FAILURE_INTERRUPTED"; 563 case WifiMetricsProto.WifiLog.SCAN_FAILURE_INVALID_CONFIGURATION: 564 return "SCAN_FAILURE_INVALID_CONFIGURATION"; 565 case WifiMetricsProto.WifiLog.FAILURE_WIFI_DISABLED: 566 return "FAILURE_WIFI_DISABLED"; 567 default: 568 return "<UNKNOWN>"; 569 } 570 } 571 572 /** 573 * Increment count of scan return code occurrence 574 * 575 * @param scanReturnCode Return code from scan attempt WifiMetricsProto.WifiLog.SCAN_X 576 */ 577 public void incrementScanReturnEntry(int scanReturnCode, int countToAdd) { 578 synchronized (mLock) { 579 if (DBG) Log.v(TAG, "incrementScanReturnEntry " + returnCodeToString(scanReturnCode)); 580 int entry = mScanReturnEntries.get(scanReturnCode); 581 entry += countToAdd; 582 mScanReturnEntries.put(scanReturnCode, entry); 583 } 584 } 585 /** 586 * Get the count of this scanReturnCode 587 * @param scanReturnCode that we are getting the count for 588 */ 589 public int getScanReturnEntry(int scanReturnCode) { 590 synchronized (mLock) { 591 return mScanReturnEntries.get(scanReturnCode); 592 } 593 } 594 595 private String wifiSystemStateToString(int state) { 596 switch(state){ 597 case WifiMetricsProto.WifiLog.WIFI_UNKNOWN: 598 return "WIFI_UNKNOWN"; 599 case WifiMetricsProto.WifiLog.WIFI_DISABLED: 600 return "WIFI_DISABLED"; 601 case WifiMetricsProto.WifiLog.WIFI_DISCONNECTED: 602 return "WIFI_DISCONNECTED"; 603 case WifiMetricsProto.WifiLog.WIFI_ASSOCIATED: 604 return "WIFI_ASSOCIATED"; 605 default: 606 return "default"; 607 } 608 } 609 610 /** 611 * Increments the count of scans initiated by each wifi state, accounts for screenOn/Off 612 * 613 * @param state State of the system when scan was initiated, see WifiMetricsProto.WifiLog.WIFI_X 614 * @param screenOn Is the screen on 615 */ 616 public void incrementWifiSystemScanStateCount(int state, boolean screenOn) { 617 synchronized (mLock) { 618 if (DBG) { 619 Log.v(TAG, "incrementWifiSystemScanStateCount " + wifiSystemStateToString(state) 620 + " " + screenOn); 621 } 622 int index = (state * 2) + (screenOn ? SCREEN_ON : SCREEN_OFF); 623 int entry = mWifiSystemStateEntries.get(index); 624 entry++; 625 mWifiSystemStateEntries.put(index, entry); 626 } 627 } 628 629 /** 630 * Get the count of this system State Entry 631 */ 632 public int getSystemStateCount(int state, boolean screenOn) { 633 synchronized (mLock) { 634 int index = state * 2 + (screenOn ? SCREEN_ON : SCREEN_OFF); 635 return mWifiSystemStateEntries.get(index); 636 } 637 } 638 639 public static final String PROTO_DUMP_ARG = "wifiMetricsProto"; 640 /** 641 * Dump all WifiMetrics. Collects some metrics from ConfigStore, Settings and WifiManager 642 * at this time 643 * 644 * @param fd unused 645 * @param pw PrintWriter for writing dump to 646 * @param args unused 647 */ 648 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 649 synchronized (mLock) { 650 pw.println("WifiMetrics:"); 651 if (args.length > 0 && PROTO_DUMP_ARG.equals(args[0])) { 652 //Dump serialized WifiLog proto 653 consolidateProto(true); 654 for (ConnectionEvent event : mConnectionEventList) { 655 if (mCurrentConnectionEvent != event) { 656 //indicate that automatic bug report has been taken for all valid 657 //connection events 658 event.mConnectionEvent.automaticBugReportTaken = true; 659 } 660 } 661 byte[] wifiMetricsProto = WifiMetricsProto.WifiLog.toByteArray(mWifiLogProto); 662 String metricsProtoDump = Base64.encodeToString(wifiMetricsProto, Base64.DEFAULT); 663 pw.println(metricsProtoDump); 664 pw.println("EndWifiMetrics"); 665 clear(); 666 } else { 667 pw.println("mConnectionEvents:"); 668 for (ConnectionEvent event : mConnectionEventList) { 669 String eventLine = event.toString(); 670 if (event == mCurrentConnectionEvent) { 671 eventLine += "CURRENTLY OPEN EVENT"; 672 } 673 pw.println(eventLine); 674 } 675 pw.println("mWifiLogProto.numSavedNetworks=" + mWifiLogProto.numSavedNetworks); 676 pw.println("mWifiLogProto.numOpenNetworks=" + mWifiLogProto.numOpenNetworks); 677 pw.println("mWifiLogProto.numPersonalNetworks=" 678 + mWifiLogProto.numPersonalNetworks); 679 pw.println("mWifiLogProto.numEnterpriseNetworks=" 680 + mWifiLogProto.numEnterpriseNetworks); 681 pw.println("mWifiLogProto.isLocationEnabled=" + mWifiLogProto.isLocationEnabled); 682 pw.println("mWifiLogProto.isScanningAlwaysEnabled=" 683 + mWifiLogProto.isScanningAlwaysEnabled); 684 pw.println("mWifiLogProto.numNetworksAddedByUser=" 685 + mWifiLogProto.numNetworksAddedByUser); 686 pw.println("mWifiLogProto.numNetworksAddedByApps=" 687 + mWifiLogProto.numNetworksAddedByApps); 688 pw.println("mWifiLogProto.numNonEmptyScanResults=" 689 + mWifiLogProto.numNonEmptyScanResults); 690 pw.println("mWifiLogProto.numEmptyScanResults=" 691 + mWifiLogProto.numEmptyScanResults); 692 pw.println("mWifiLogProto.numOneshotScans=" 693 + mWifiLogProto.numOneshotScans); 694 pw.println("mWifiLogProto.numBackgroundScans=" 695 + mWifiLogProto.numBackgroundScans); 696 697 pw.println("mScanReturnEntries:"); 698 pw.println(" SCAN_UNKNOWN: " + getScanReturnEntry( 699 WifiMetricsProto.WifiLog.SCAN_UNKNOWN)); 700 pw.println(" SCAN_SUCCESS: " + getScanReturnEntry( 701 WifiMetricsProto.WifiLog.SCAN_SUCCESS)); 702 pw.println(" SCAN_FAILURE_INTERRUPTED: " + getScanReturnEntry( 703 WifiMetricsProto.WifiLog.SCAN_FAILURE_INTERRUPTED)); 704 pw.println(" SCAN_FAILURE_INVALID_CONFIGURATION: " + getScanReturnEntry( 705 WifiMetricsProto.WifiLog.SCAN_FAILURE_INVALID_CONFIGURATION)); 706 pw.println(" FAILURE_WIFI_DISABLED: " + getScanReturnEntry( 707 WifiMetricsProto.WifiLog.FAILURE_WIFI_DISABLED)); 708 709 pw.println("mSystemStateEntries: <state><screenOn> : <scansInitiated>"); 710 pw.println(" WIFI_UNKNOWN ON: " 711 + getSystemStateCount(WifiMetricsProto.WifiLog.WIFI_UNKNOWN, true)); 712 pw.println(" WIFI_DISABLED ON: " 713 + getSystemStateCount(WifiMetricsProto.WifiLog.WIFI_DISABLED, true)); 714 pw.println(" WIFI_DISCONNECTED ON: " 715 + getSystemStateCount(WifiMetricsProto.WifiLog.WIFI_DISCONNECTED, true)); 716 pw.println(" WIFI_ASSOCIATED ON: " 717 + getSystemStateCount(WifiMetricsProto.WifiLog.WIFI_ASSOCIATED, true)); 718 pw.println(" WIFI_UNKNOWN OFF: " 719 + getSystemStateCount(WifiMetricsProto.WifiLog.WIFI_UNKNOWN, false)); 720 pw.println(" WIFI_DISABLED OFF: " 721 + getSystemStateCount(WifiMetricsProto.WifiLog.WIFI_DISABLED, false)); 722 pw.println(" WIFI_DISCONNECTED OFF: " 723 + getSystemStateCount(WifiMetricsProto.WifiLog.WIFI_DISCONNECTED, false)); 724 pw.println(" WIFI_ASSOCIATED OFF: " 725 + getSystemStateCount(WifiMetricsProto.WifiLog.WIFI_ASSOCIATED, false)); 726 } 727 } 728 } 729 730 /** 731 * append the separate ConnectionEvent, SystemStateEntry and ScanReturnCode collections to their 732 * respective lists within mWifiLogProto 733 * 734 * @param incremental Only include ConnectionEvents created since last automatic bug report 735 */ 736 private void consolidateProto(boolean incremental) { 737 List<WifiMetricsProto.ConnectionEvent> events = new ArrayList<>(); 738 synchronized (mLock) { 739 for (ConnectionEvent event : mConnectionEventList) { 740 // If this is not incremental, dump full ConnectionEvent list 741 // Else Dump all un-dumped events except for the current one 742 if (!incremental || ((mCurrentConnectionEvent != event) 743 && !event.mConnectionEvent.automaticBugReportTaken)) { 744 //Get all ConnectionEvents that haven not been dumped as a proto, also exclude 745 //the current active un-ended connection event 746 events.add(event.mConnectionEvent); 747 if (incremental) { 748 event.mConnectionEvent.automaticBugReportTaken = true; 749 } 750 } 751 } 752 if (events.size() > 0) { 753 mWifiLogProto.connectionEvent = events.toArray(mWifiLogProto.connectionEvent); 754 } 755 756 //Convert the SparseIntArray of scanReturnEntry integers into ScanReturnEntry proto list 757 mWifiLogProto.scanReturnEntries = 758 new WifiMetricsProto.WifiLog.ScanReturnEntry[mScanReturnEntries.size()]; 759 for (int i = 0; i < mScanReturnEntries.size(); i++) { 760 mWifiLogProto.scanReturnEntries[i] = new WifiMetricsProto.WifiLog.ScanReturnEntry(); 761 mWifiLogProto.scanReturnEntries[i].scanReturnCode = mScanReturnEntries.keyAt(i); 762 mWifiLogProto.scanReturnEntries[i].scanResultsCount = mScanReturnEntries.valueAt(i); 763 } 764 765 // Convert the SparseIntArray of systemStateEntry into WifiSystemStateEntry proto list 766 // This one is slightly more complex, as the Sparse are indexed with: 767 // key: wifiState * 2 + isScreenOn, value: wifiStateCount 768 mWifiLogProto.wifiSystemStateEntries = 769 new WifiMetricsProto.WifiLog 770 .WifiSystemStateEntry[mWifiSystemStateEntries.size()]; 771 for (int i = 0; i < mWifiSystemStateEntries.size(); i++) { 772 mWifiLogProto.wifiSystemStateEntries[i] = 773 new WifiMetricsProto.WifiLog.WifiSystemStateEntry(); 774 mWifiLogProto.wifiSystemStateEntries[i].wifiState = 775 mWifiSystemStateEntries.keyAt(i) / 2; 776 mWifiLogProto.wifiSystemStateEntries[i].wifiStateCount = 777 mWifiSystemStateEntries.valueAt(i); 778 mWifiLogProto.wifiSystemStateEntries[i].isScreenOn = 779 (mWifiSystemStateEntries.keyAt(i) % 2) > 0; 780 } 781 } 782 } 783 784 /** 785 * Serializes all of WifiMetrics to WifiLog proto, and returns the byte array. 786 * Does not count as taking an automatic bug report 787 * 788 * @return byte array of the deserialized & consolidated Proto 789 */ 790 public byte[] toByteArray() { 791 consolidateProto(false); 792 return mWifiLogProto.toByteArray(mWifiLogProto); 793 } 794 795 /** 796 * Clear all WifiMetrics, except for currentConnectionEvent. 797 */ 798 private void clear() { 799 synchronized (mLock) { 800 mConnectionEventList.clear(); 801 if (mCurrentConnectionEvent != null) { 802 mConnectionEventList.add(mCurrentConnectionEvent); 803 } 804 mScanReturnEntries.clear(); 805 mWifiSystemStateEntries.clear(); 806 mWifiLogProto.clear(); 807 } 808 } 809} 810