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