1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations 14 * under the License. 15 */ 16 package com.android.server.am; 17 18 import static android.net.wifi.WifiManager.WIFI_FEATURE_LINK_LAYER_STATS; 19 20 import android.annotation.Nullable; 21 import android.bluetooth.BluetoothActivityEnergyInfo; 22 import android.bluetooth.BluetoothAdapter; 23 import android.content.Context; 24 import android.net.wifi.IWifiManager; 25 import android.net.wifi.WifiActivityEnergyInfo; 26 import android.os.BatteryStats; 27 import android.os.Parcelable; 28 import android.os.Process; 29 import android.os.RemoteException; 30 import android.os.ServiceManager; 31 import android.os.SynchronousResultReceiver; 32 import android.os.SystemClock; 33 import android.os.ThreadLocalWorkSource; 34 import android.telephony.ModemActivityInfo; 35 import android.telephony.TelephonyManager; 36 import android.util.IntArray; 37 import android.util.Slog; 38 import android.util.StatsLog; 39 import android.util.TimeUtils; 40 41 import com.android.internal.annotations.GuardedBy; 42 import com.android.internal.os.BatteryStatsImpl; 43 import com.android.internal.util.function.pooled.PooledLambda; 44 45 import libcore.util.EmptyArray; 46 47 import java.util.concurrent.CompletableFuture; 48 import java.util.concurrent.Executors; 49 import java.util.concurrent.Future; 50 import java.util.concurrent.ScheduledExecutorService; 51 import java.util.concurrent.ThreadFactory; 52 import java.util.concurrent.TimeUnit; 53 import java.util.concurrent.TimeoutException; 54 55 /** 56 * A Worker that fetches data from external sources (WiFi controller, bluetooth chipset) on a 57 * dedicated thread and updates BatteryStatsImpl with that information. 58 * 59 * As much work as possible is done without holding the BatteryStatsImpl lock, and only the 60 * readily available data is pushed into BatteryStatsImpl with the lock held. 61 */ 62 class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { 63 private static final String TAG = "BatteryExternalStatsWorker"; 64 private static final boolean DEBUG = false; 65 66 /** 67 * How long to wait on an individual subsystem to return its stats. 68 */ 69 private static final long EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS = 2000; 70 71 // There is some accuracy error in wifi reports so allow some slop in the results. 72 private static final long MAX_WIFI_STATS_SAMPLE_ERROR_MILLIS = 750; 73 74 private final ScheduledExecutorService mExecutorService = 75 Executors.newSingleThreadScheduledExecutor( 76 (ThreadFactory) r -> { 77 Thread t = new Thread( 78 () -> { 79 ThreadLocalWorkSource.setUid(Process.myUid()); 80 r.run(); 81 }, 82 "batterystats-worker"); 83 t.setPriority(Thread.NORM_PRIORITY); 84 return t; 85 }); 86 87 private final Context mContext; 88 private final BatteryStatsImpl mStats; 89 90 @GuardedBy("this") 91 private int mUpdateFlags = 0; 92 93 @GuardedBy("this") 94 private Future<?> mCurrentFuture = null; 95 96 @GuardedBy("this") 97 private String mCurrentReason = null; 98 99 @GuardedBy("this") 100 private boolean mOnBattery; 101 102 @GuardedBy("this") 103 private boolean mOnBatteryScreenOff; 104 105 @GuardedBy("this") 106 private boolean mUseLatestStates = true; 107 108 @GuardedBy("this") 109 private final IntArray mUidsToRemove = new IntArray(); 110 111 @GuardedBy("this") 112 private Future<?> mWakelockChangesUpdate; 113 114 @GuardedBy("this") 115 private Future<?> mBatteryLevelSync; 116 117 private final Object mWorkerLock = new Object(); 118 119 @GuardedBy("mWorkerLock") 120 private IWifiManager mWifiManager = null; 121 122 @GuardedBy("mWorkerLock") 123 private TelephonyManager mTelephony = null; 124 125 // WiFi keeps an accumulated total of stats, unlike Bluetooth. 126 // Keep the last WiFi stats so we can compute a delta. 127 @GuardedBy("mWorkerLock") 128 private WifiActivityEnergyInfo mLastInfo = 129 new WifiActivityEnergyInfo(0, 0, 0, new long[]{0}, 0, 0, 0, 0); 130 131 /** 132 * Timestamp at which all external stats were last collected in 133 * {@link SystemClock#elapsedRealtime()} time base. 134 */ 135 @GuardedBy("this") 136 private long mLastCollectionTimeStamp; 137 BatteryExternalStatsWorker(Context context, BatteryStatsImpl stats)138 BatteryExternalStatsWorker(Context context, BatteryStatsImpl stats) { 139 mContext = context; 140 mStats = stats; 141 } 142 143 @Override scheduleSync(String reason, int flags)144 public synchronized Future<?> scheduleSync(String reason, int flags) { 145 return scheduleSyncLocked(reason, flags); 146 } 147 148 @Override scheduleCpuSyncDueToRemovedUid(int uid)149 public synchronized Future<?> scheduleCpuSyncDueToRemovedUid(int uid) { 150 mUidsToRemove.add(uid); 151 return scheduleSyncLocked("remove-uid", UPDATE_CPU); 152 } 153 154 @Override scheduleCpuSyncDueToSettingChange()155 public synchronized Future<?> scheduleCpuSyncDueToSettingChange() { 156 return scheduleSyncLocked("setting-change", UPDATE_CPU); 157 } 158 159 @Override scheduleReadProcStateCpuTimes( boolean onBattery, boolean onBatteryScreenOff, long delayMillis)160 public Future<?> scheduleReadProcStateCpuTimes( 161 boolean onBattery, boolean onBatteryScreenOff, long delayMillis) { 162 synchronized (mStats) { 163 if (!mStats.trackPerProcStateCpuTimes()) { 164 return null; 165 } 166 } 167 synchronized (BatteryExternalStatsWorker.this) { 168 if (!mExecutorService.isShutdown()) { 169 return mExecutorService.schedule(PooledLambda.obtainRunnable( 170 BatteryStatsImpl::updateProcStateCpuTimes, 171 mStats, onBattery, onBatteryScreenOff).recycleOnUse(), 172 delayMillis, TimeUnit.MILLISECONDS); 173 } 174 } 175 return null; 176 } 177 178 @Override scheduleCopyFromAllUidsCpuTimes( boolean onBattery, boolean onBatteryScreenOff)179 public Future<?> scheduleCopyFromAllUidsCpuTimes( 180 boolean onBattery, boolean onBatteryScreenOff) { 181 synchronized (mStats) { 182 if (!mStats.trackPerProcStateCpuTimes()) { 183 return null; 184 } 185 } 186 synchronized (BatteryExternalStatsWorker.this) { 187 if (!mExecutorService.isShutdown()) { 188 return mExecutorService.submit(PooledLambda.obtainRunnable( 189 BatteryStatsImpl::copyFromAllUidsCpuTimes, 190 mStats, onBattery, onBatteryScreenOff).recycleOnUse()); 191 } 192 } 193 return null; 194 } 195 196 @Override scheduleCpuSyncDueToScreenStateChange( boolean onBattery, boolean onBatteryScreenOff)197 public Future<?> scheduleCpuSyncDueToScreenStateChange( 198 boolean onBattery, boolean onBatteryScreenOff) { 199 synchronized (BatteryExternalStatsWorker.this) { 200 if (mCurrentFuture == null || (mUpdateFlags & UPDATE_CPU) == 0) { 201 mOnBattery = onBattery; 202 mOnBatteryScreenOff = onBatteryScreenOff; 203 mUseLatestStates = false; 204 } 205 return scheduleSyncLocked("screen-state", UPDATE_CPU); 206 } 207 } 208 209 @Override scheduleCpuSyncDueToWakelockChange(long delayMillis)210 public Future<?> scheduleCpuSyncDueToWakelockChange(long delayMillis) { 211 synchronized (BatteryExternalStatsWorker.this) { 212 mWakelockChangesUpdate = scheduleDelayedSyncLocked(mWakelockChangesUpdate, 213 () -> { 214 scheduleSync("wakelock-change", UPDATE_CPU); 215 scheduleRunnable(() -> mStats.postBatteryNeedsCpuUpdateMsg()); 216 }, 217 delayMillis); 218 return mWakelockChangesUpdate; 219 } 220 } 221 222 @Override cancelCpuSyncDueToWakelockChange()223 public void cancelCpuSyncDueToWakelockChange() { 224 synchronized (BatteryExternalStatsWorker.this) { 225 if (mWakelockChangesUpdate != null) { 226 mWakelockChangesUpdate.cancel(false); 227 mWakelockChangesUpdate = null; 228 } 229 } 230 } 231 232 @Override scheduleSyncDueToBatteryLevelChange(long delayMillis)233 public Future<?> scheduleSyncDueToBatteryLevelChange(long delayMillis) { 234 synchronized (BatteryExternalStatsWorker.this) { 235 mBatteryLevelSync = scheduleDelayedSyncLocked(mBatteryLevelSync, 236 () -> scheduleSync("battery-level", UPDATE_ALL), 237 delayMillis); 238 return mBatteryLevelSync; 239 } 240 } 241 242 @GuardedBy("this") cancelSyncDueToBatteryLevelChangeLocked()243 private void cancelSyncDueToBatteryLevelChangeLocked() { 244 if (mBatteryLevelSync != null) { 245 mBatteryLevelSync.cancel(false); 246 mBatteryLevelSync = null; 247 } 248 } 249 250 /** 251 * Schedule a sync {@param syncRunnable} with a delay. If there's already a scheduled sync, a 252 * new sync won't be scheduled unless it is being scheduled to run immediately (delayMillis=0). 253 * 254 * @param lastScheduledSync the task which was earlier scheduled to run 255 * @param syncRunnable the task that needs to be scheduled to run 256 * @param delayMillis time after which {@param syncRunnable} needs to be scheduled 257 * @return scheduled {@link Future} which can be used to check if task is completed or to 258 * cancel it if needed 259 */ 260 @GuardedBy("this") scheduleDelayedSyncLocked(Future<?> lastScheduledSync, Runnable syncRunnable, long delayMillis)261 private Future<?> scheduleDelayedSyncLocked(Future<?> lastScheduledSync, Runnable syncRunnable, 262 long delayMillis) { 263 if (mExecutorService.isShutdown()) { 264 return CompletableFuture.failedFuture(new IllegalStateException("worker shutdown")); 265 } 266 267 if (lastScheduledSync != null) { 268 // If there's already a scheduled task, leave it as is if we're trying to 269 // re-schedule it again with a delay, otherwise cancel and re-schedule it. 270 if (delayMillis == 0) { 271 lastScheduledSync.cancel(false); 272 } else { 273 return lastScheduledSync; 274 } 275 } 276 277 return mExecutorService.schedule(syncRunnable, delayMillis, TimeUnit.MILLISECONDS); 278 } 279 scheduleWrite()280 public synchronized Future<?> scheduleWrite() { 281 if (mExecutorService.isShutdown()) { 282 return CompletableFuture.failedFuture(new IllegalStateException("worker shutdown")); 283 } 284 285 scheduleSyncLocked("write", UPDATE_ALL); 286 // Since we use a single threaded executor, we can assume the next scheduled task's 287 // Future finishes after the sync. 288 return mExecutorService.submit(mWriteTask); 289 } 290 291 /** 292 * Schedules a task to run on the BatteryExternalStatsWorker thread. If scheduling more work 293 * within the task, never wait on the resulting Future. This will result in a deadlock. 294 */ scheduleRunnable(Runnable runnable)295 public synchronized void scheduleRunnable(Runnable runnable) { 296 if (!mExecutorService.isShutdown()) { 297 mExecutorService.submit(runnable); 298 } 299 } 300 shutdown()301 public void shutdown() { 302 mExecutorService.shutdownNow(); 303 } 304 305 @GuardedBy("this") scheduleSyncLocked(String reason, int flags)306 private Future<?> scheduleSyncLocked(String reason, int flags) { 307 if (mExecutorService.isShutdown()) { 308 return CompletableFuture.failedFuture(new IllegalStateException("worker shutdown")); 309 } 310 311 if (mCurrentFuture == null) { 312 mUpdateFlags = flags; 313 mCurrentReason = reason; 314 mCurrentFuture = mExecutorService.submit(mSyncTask); 315 } 316 mUpdateFlags |= flags; 317 return mCurrentFuture; 318 } 319 getLastCollectionTimeStamp()320 long getLastCollectionTimeStamp() { 321 synchronized (this) { 322 return mLastCollectionTimeStamp; 323 } 324 } 325 326 private final Runnable mSyncTask = new Runnable() { 327 @Override 328 public void run() { 329 // Capture a snapshot of the state we are meant to process. 330 final int updateFlags; 331 final String reason; 332 final int[] uidsToRemove; 333 final boolean onBattery; 334 final boolean onBatteryScreenOff; 335 final boolean useLatestStates; 336 synchronized (BatteryExternalStatsWorker.this) { 337 updateFlags = mUpdateFlags; 338 reason = mCurrentReason; 339 uidsToRemove = mUidsToRemove.size() > 0 ? mUidsToRemove.toArray() : EmptyArray.INT; 340 onBattery = mOnBattery; 341 onBatteryScreenOff = mOnBatteryScreenOff; 342 useLatestStates = mUseLatestStates; 343 mUpdateFlags = 0; 344 mCurrentReason = null; 345 mUidsToRemove.clear(); 346 mCurrentFuture = null; 347 mUseLatestStates = true; 348 if ((updateFlags & UPDATE_ALL) != 0) { 349 cancelSyncDueToBatteryLevelChangeLocked(); 350 } 351 if ((updateFlags & UPDATE_CPU) != 0) { 352 cancelCpuSyncDueToWakelockChange(); 353 } 354 } 355 356 try { 357 synchronized (mWorkerLock) { 358 if (DEBUG) { 359 Slog.d(TAG, "begin updateExternalStatsSync reason=" + reason); 360 } 361 try { 362 updateExternalStatsLocked(reason, updateFlags, onBattery, 363 onBatteryScreenOff, useLatestStates); 364 } finally { 365 if (DEBUG) { 366 Slog.d(TAG, "end updateExternalStatsSync"); 367 } 368 } 369 } 370 371 if ((updateFlags & UPDATE_CPU) != 0) { 372 mStats.copyFromAllUidsCpuTimes(); 373 } 374 375 // Clean up any UIDs if necessary. 376 synchronized (mStats) { 377 for (int uid : uidsToRemove) { 378 StatsLog.write(StatsLog.ISOLATED_UID_CHANGED, -1, uid, 379 StatsLog.ISOLATED_UID_CHANGED__EVENT__REMOVED); 380 mStats.removeIsolatedUidLocked(uid); 381 } 382 mStats.clearPendingRemovedUids(); 383 } 384 } catch (Exception e) { 385 Slog.wtf(TAG, "Error updating external stats: ", e); 386 } 387 388 synchronized (BatteryExternalStatsWorker.this) { 389 mLastCollectionTimeStamp = SystemClock.elapsedRealtime(); 390 } 391 } 392 }; 393 394 private final Runnable mWriteTask = new Runnable() { 395 @Override 396 public void run() { 397 synchronized (mStats) { 398 mStats.writeAsyncLocked(); 399 } 400 } 401 }; 402 403 @GuardedBy("mWorkerLock") updateExternalStatsLocked(final String reason, int updateFlags, boolean onBattery, boolean onBatteryScreenOff, boolean useLatestStates)404 private void updateExternalStatsLocked(final String reason, int updateFlags, 405 boolean onBattery, boolean onBatteryScreenOff, boolean useLatestStates) { 406 // We will request data from external processes asynchronously, and wait on a timeout. 407 SynchronousResultReceiver wifiReceiver = null; 408 SynchronousResultReceiver bluetoothReceiver = null; 409 SynchronousResultReceiver modemReceiver = null; 410 boolean railUpdated = false; 411 412 if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_WIFI) != 0) { 413 // We were asked to fetch WiFi data. 414 if (mWifiManager == null) { 415 mWifiManager = IWifiManager.Stub.asInterface(ServiceManager.getService( 416 Context.WIFI_SERVICE)); 417 } 418 419 if (mWifiManager != null) { 420 try { 421 // Only fetch WiFi power data if it is supported. 422 if ((mWifiManager.getSupportedFeatures() & WIFI_FEATURE_LINK_LAYER_STATS) != 0) { 423 wifiReceiver = new SynchronousResultReceiver("wifi"); 424 mWifiManager.requestActivityInfo(wifiReceiver); 425 } 426 } catch (RemoteException e) { 427 // Oh well. 428 } 429 } 430 synchronized (mStats) { 431 mStats.updateRailStatsLocked(); 432 } 433 railUpdated = true; 434 } 435 436 if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_BT) != 0) { 437 // We were asked to fetch Bluetooth data. 438 final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); 439 if (adapter != null) { 440 bluetoothReceiver = new SynchronousResultReceiver("bluetooth"); 441 adapter.requestControllerActivityEnergyInfo(bluetoothReceiver); 442 } 443 } 444 445 if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_RADIO) != 0) { 446 // We were asked to fetch Telephony data. 447 if (mTelephony == null) { 448 mTelephony = mContext.getSystemService(TelephonyManager.class); 449 } 450 451 if (mTelephony != null) { 452 modemReceiver = new SynchronousResultReceiver("telephony"); 453 mTelephony.requestModemActivityInfo(modemReceiver); 454 } 455 if (!railUpdated) { 456 synchronized (mStats) { 457 mStats.updateRailStatsLocked(); 458 } 459 } 460 } 461 462 final WifiActivityEnergyInfo wifiInfo = awaitControllerInfo(wifiReceiver); 463 final BluetoothActivityEnergyInfo bluetoothInfo = awaitControllerInfo(bluetoothReceiver); 464 final ModemActivityInfo modemInfo = awaitControllerInfo(modemReceiver); 465 466 synchronized (mStats) { 467 mStats.addHistoryEventLocked( 468 SystemClock.elapsedRealtime(), 469 SystemClock.uptimeMillis(), 470 BatteryStats.HistoryItem.EVENT_COLLECT_EXTERNAL_STATS, 471 reason, 0); 472 473 if ((updateFlags & UPDATE_CPU) != 0) { 474 if (useLatestStates) { 475 onBattery = mStats.isOnBatteryLocked(); 476 onBatteryScreenOff = mStats.isOnBatteryScreenOffLocked(); 477 } 478 mStats.updateCpuTimeLocked(onBattery, onBatteryScreenOff); 479 } 480 481 if ((updateFlags & UPDATE_ALL) != 0) { 482 mStats.updateKernelWakelocksLocked(); 483 mStats.updateKernelMemoryBandwidthLocked(); 484 } 485 486 if ((updateFlags & UPDATE_RPM) != 0) { 487 mStats.updateRpmStatsLocked(); 488 } 489 490 if (bluetoothInfo != null) { 491 if (bluetoothInfo.isValid()) { 492 mStats.updateBluetoothStateLocked(bluetoothInfo); 493 } else { 494 Slog.w(TAG, "bluetooth info is invalid: " + bluetoothInfo); 495 } 496 } 497 } 498 499 // WiFi and Modem state are updated without the mStats lock held, because they 500 // do some network stats retrieval before internally grabbing the mStats lock. 501 502 if (wifiInfo != null) { 503 if (wifiInfo.isValid()) { 504 mStats.updateWifiState(extractDeltaLocked(wifiInfo)); 505 } else { 506 Slog.w(TAG, "wifi info is invalid: " + wifiInfo); 507 } 508 } 509 510 if (modemInfo != null) { 511 if (modemInfo.isValid()) { 512 mStats.updateMobileRadioState(modemInfo); 513 } else { 514 Slog.w(TAG, "modem info is invalid: " + modemInfo); 515 } 516 } 517 } 518 519 /** 520 * Helper method to extract the Parcelable controller info from a 521 * SynchronousResultReceiver. 522 */ awaitControllerInfo( @ullable SynchronousResultReceiver receiver)523 private static <T extends Parcelable> T awaitControllerInfo( 524 @Nullable SynchronousResultReceiver receiver) { 525 if (receiver == null) { 526 return null; 527 } 528 529 try { 530 final SynchronousResultReceiver.Result result = 531 receiver.awaitResult(EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS); 532 if (result.bundle != null) { 533 // This is the final destination for the Bundle. 534 result.bundle.setDefusable(true); 535 536 final T data = result.bundle.getParcelable( 537 BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY); 538 if (data != null) { 539 return data; 540 } 541 } 542 Slog.e(TAG, "no controller energy info supplied for " + receiver.getName()); 543 } catch (TimeoutException e) { 544 Slog.w(TAG, "timeout reading " + receiver.getName() + " stats"); 545 } 546 return null; 547 } 548 549 @GuardedBy("mWorkerLock") extractDeltaLocked(WifiActivityEnergyInfo latest)550 private WifiActivityEnergyInfo extractDeltaLocked(WifiActivityEnergyInfo latest) { 551 final long timePeriodMs = latest.mTimestamp - mLastInfo.mTimestamp; 552 final long lastScanMs = mLastInfo.mControllerScanTimeMs; 553 final long lastIdleMs = mLastInfo.mControllerIdleTimeMs; 554 final long lastTxMs = mLastInfo.mControllerTxTimeMs; 555 final long lastRxMs = mLastInfo.mControllerRxTimeMs; 556 final long lastEnergy = mLastInfo.mControllerEnergyUsed; 557 558 // We will modify the last info object to be the delta, and store the new 559 // WifiActivityEnergyInfo object as our last one. 560 final WifiActivityEnergyInfo delta = mLastInfo; 561 delta.mTimestamp = latest.getTimeStamp(); 562 delta.mStackState = latest.getStackState(); 563 564 final long txTimeMs = latest.mControllerTxTimeMs - lastTxMs; 565 final long rxTimeMs = latest.mControllerRxTimeMs - lastRxMs; 566 final long idleTimeMs = latest.mControllerIdleTimeMs - lastIdleMs; 567 final long scanTimeMs = latest.mControllerScanTimeMs - lastScanMs; 568 569 if (txTimeMs < 0 || rxTimeMs < 0 || scanTimeMs < 0 || idleTimeMs < 0) { 570 // The stats were reset by the WiFi system (which is why our delta is negative). 571 // Returns the unaltered stats. The total on time should not exceed the time 572 // duartion between reports. 573 final long totalOnTimeMs = latest.mControllerTxTimeMs + latest.mControllerRxTimeMs 574 + latest.mControllerIdleTimeMs; 575 if (totalOnTimeMs <= timePeriodMs + MAX_WIFI_STATS_SAMPLE_ERROR_MILLIS) { 576 delta.mControllerEnergyUsed = latest.mControllerEnergyUsed; 577 delta.mControllerRxTimeMs = latest.mControllerRxTimeMs; 578 delta.mControllerTxTimeMs = latest.mControllerTxTimeMs; 579 delta.mControllerIdleTimeMs = latest.mControllerIdleTimeMs; 580 delta.mControllerScanTimeMs = latest.mControllerScanTimeMs; 581 } else { 582 delta.mControllerEnergyUsed = 0; 583 delta.mControllerRxTimeMs = 0; 584 delta.mControllerTxTimeMs = 0; 585 delta.mControllerIdleTimeMs = 0; 586 delta.mControllerScanTimeMs = 0; 587 } 588 Slog.v(TAG, "WiFi energy data was reset, new WiFi energy data is " + delta); 589 } else { 590 final long totalActiveTimeMs = txTimeMs + rxTimeMs; 591 long maxExpectedIdleTimeMs; 592 if (totalActiveTimeMs > timePeriodMs) { 593 // Cap the max idle time at zero since the active time consumed the whole time 594 maxExpectedIdleTimeMs = 0; 595 if (totalActiveTimeMs > timePeriodMs + MAX_WIFI_STATS_SAMPLE_ERROR_MILLIS) { 596 StringBuilder sb = new StringBuilder(); 597 sb.append("Total Active time "); 598 TimeUtils.formatDuration(totalActiveTimeMs, sb); 599 sb.append(" is longer than sample period "); 600 TimeUtils.formatDuration(timePeriodMs, sb); 601 sb.append(".\n"); 602 sb.append("Previous WiFi snapshot: ").append("idle="); 603 TimeUtils.formatDuration(lastIdleMs, sb); 604 sb.append(" rx="); 605 TimeUtils.formatDuration(lastRxMs, sb); 606 sb.append(" tx="); 607 TimeUtils.formatDuration(lastTxMs, sb); 608 sb.append(" e=").append(lastEnergy); 609 sb.append("\n"); 610 sb.append("Current WiFi snapshot: ").append("idle="); 611 TimeUtils.formatDuration(latest.mControllerIdleTimeMs, sb); 612 sb.append(" rx="); 613 TimeUtils.formatDuration(latest.mControllerRxTimeMs, sb); 614 sb.append(" tx="); 615 TimeUtils.formatDuration(latest.mControllerTxTimeMs, sb); 616 sb.append(" e=").append(latest.mControllerEnergyUsed); 617 Slog.wtf(TAG, sb.toString()); 618 } 619 } else { 620 maxExpectedIdleTimeMs = timePeriodMs - totalActiveTimeMs; 621 } 622 // These times seem to be the most reliable. 623 delta.mControllerTxTimeMs = txTimeMs; 624 delta.mControllerRxTimeMs = rxTimeMs; 625 delta.mControllerScanTimeMs = scanTimeMs; 626 // WiFi calculates the idle time as a difference from the on time and the various 627 // Rx + Tx times. There seems to be some missing time there because this sometimes 628 // becomes negative. Just cap it at 0 and ensure that it is less than the expected idle 629 // time from the difference in timestamps. 630 // b/21613534 631 delta.mControllerIdleTimeMs = Math.min(maxExpectedIdleTimeMs, Math.max(0, idleTimeMs)); 632 delta.mControllerEnergyUsed = Math.max(0, latest.mControllerEnergyUsed - lastEnergy); 633 } 634 635 mLastInfo = latest; 636 return delta; 637 } 638 } 639