• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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