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