Home | History | Annotate | Download | only in am
      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 
    138     BatteryExternalStatsWorker(Context context, BatteryStatsImpl stats) {
    139         mContext = context;
    140         mStats = stats;
    141     }
    142 
    143     @Override
    144     public synchronized Future<?> scheduleSync(String reason, int flags) {
    145         return scheduleSyncLocked(reason, flags);
    146     }
    147 
    148     @Override
    149     public synchronized Future<?> scheduleCpuSyncDueToRemovedUid(int uid) {
    150         mUidsToRemove.add(uid);
    151         return scheduleSyncLocked("remove-uid", UPDATE_CPU);
    152     }
    153 
    154     @Override
    155     public synchronized Future<?> scheduleCpuSyncDueToSettingChange() {
    156         return scheduleSyncLocked("setting-change", UPDATE_CPU);
    157     }
    158 
    159     @Override
    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
    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
    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
    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
    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
    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")
    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")
    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 
    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      */
    295     public synchronized void scheduleRunnable(Runnable runnable) {
    296         if (!mExecutorService.isShutdown()) {
    297             mExecutorService.submit(runnable);
    298         }
    299     }
    300 
    301     public void shutdown() {
    302         mExecutorService.shutdownNow();
    303     }
    304 
    305     @GuardedBy("this")
    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 
    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")
    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 = TelephonyManager.from(mContext);
    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      */
    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")
    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