blob: 2291e44d9e0606b1af9e0b9d3c56427cfedc5261 [file] [log] [blame]
Adam Lesinskib3a1bad2017-05-26 11:50:40 -07001/*
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 */
16package com.android.server.am;
17
18import android.annotation.Nullable;
19import android.bluetooth.BluetoothActivityEnergyInfo;
20import android.bluetooth.BluetoothAdapter;
21import android.content.Context;
22import android.net.wifi.IWifiManager;
23import android.net.wifi.WifiActivityEnergyInfo;
24import android.os.BatteryStats;
25import android.os.Parcelable;
26import android.os.RemoteException;
27import android.os.ServiceManager;
28import android.os.SynchronousResultReceiver;
29import android.os.SystemClock;
30import android.telephony.ModemActivityInfo;
31import android.telephony.TelephonyManager;
32import android.util.IntArray;
33import android.util.Slog;
34import android.util.TimeUtils;
35
36import com.android.internal.annotations.GuardedBy;
37import com.android.internal.os.BatteryStatsImpl;
Sudheer Shankae544d162017-12-28 17:06:20 -080038import com.android.internal.util.function.pooled.PooledLambda;
Adam Lesinskib3a1bad2017-05-26 11:50:40 -070039
40import libcore.util.EmptyArray;
41
Adam Lesinskicf0b2b62017-08-29 19:30:31 -070042import java.util.concurrent.CompletableFuture;
Adam Lesinskib3a1bad2017-05-26 11:50:40 -070043import java.util.concurrent.ExecutorService;
44import java.util.concurrent.Executors;
45import java.util.concurrent.Future;
Sudheer Shankac57729a2018-02-09 15:44:42 -080046import java.util.concurrent.ScheduledExecutorService;
47import java.util.concurrent.ScheduledFuture;
Adam Lesinskib3a1bad2017-05-26 11:50:40 -070048import java.util.concurrent.ThreadFactory;
Sudheer Shankac57729a2018-02-09 15:44:42 -080049import java.util.concurrent.TimeUnit;
Adam Lesinskib3a1bad2017-05-26 11:50:40 -070050import java.util.concurrent.TimeoutException;
51
52/**
53 * A Worker that fetches data from external sources (WiFi controller, bluetooth chipset) on a
54 * dedicated thread and updates BatteryStatsImpl with that information.
55 *
56 * As much work as possible is done without holding the BatteryStatsImpl lock, and only the
57 * readily available data is pushed into BatteryStatsImpl with the lock held.
58 */
59class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync {
60 private static final String TAG = "BatteryExternalStatsWorker";
61 private static final boolean DEBUG = false;
62
63 /**
64 * How long to wait on an individual subsystem to return its stats.
65 */
66 private static final long EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS = 2000;
67
68 // There is some accuracy error in wifi reports so allow some slop in the results.
69 private static final long MAX_WIFI_STATS_SAMPLE_ERROR_MILLIS = 750;
70
Sudheer Shankac57729a2018-02-09 15:44:42 -080071 private final ScheduledExecutorService mExecutorService =
72 Executors.newSingleThreadScheduledExecutor(
73 (ThreadFactory) r -> {
74 Thread t = new Thread(r, "batterystats-worker");
75 t.setPriority(Thread.NORM_PRIORITY);
76 return t;
77 });
Adam Lesinskib3a1bad2017-05-26 11:50:40 -070078
79 private final Context mContext;
80 private final BatteryStatsImpl mStats;
81
82 @GuardedBy("this")
83 private int mUpdateFlags = 0;
84
85 @GuardedBy("this")
86 private Future<?> mCurrentFuture = null;
87
88 @GuardedBy("this")
89 private String mCurrentReason = null;
90
91 @GuardedBy("this")
Sudheer Shankac57729a2018-02-09 15:44:42 -080092 private boolean mOnBattery;
93
94 @GuardedBy("this")
95 private boolean mOnBatteryScreenOff;
96
97 @GuardedBy("this")
98 private boolean mUseLatestStates = true;
99
100 @GuardedBy("this")
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700101 private final IntArray mUidsToRemove = new IntArray();
102
Sudheer Shankac57729a2018-02-09 15:44:42 -0800103 @GuardedBy("this")
104 private Future<?> mWakelockChangesUpdate;
105
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700106 private final Object mWorkerLock = new Object();
107
108 @GuardedBy("mWorkerLock")
109 private IWifiManager mWifiManager = null;
110
111 @GuardedBy("mWorkerLock")
112 private TelephonyManager mTelephony = null;
113
114 // WiFi keeps an accumulated total of stats, unlike Bluetooth.
115 // Keep the last WiFi stats so we can compute a delta.
116 @GuardedBy("mWorkerLock")
117 private WifiActivityEnergyInfo mLastInfo =
Siddharth Rayb50a6842017-12-14 15:15:28 -0800118 new WifiActivityEnergyInfo(0, 0, 0, new long[]{0}, 0, 0, 0, 0);
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700119
120 BatteryExternalStatsWorker(Context context, BatteryStatsImpl stats) {
121 mContext = context;
122 mStats = stats;
123 }
124
125 @Override
126 public synchronized Future<?> scheduleSync(String reason, int flags) {
127 return scheduleSyncLocked(reason, flags);
128 }
129
130 @Override
131 public synchronized Future<?> scheduleCpuSyncDueToRemovedUid(int uid) {
132 mUidsToRemove.add(uid);
133 return scheduleSyncLocked("remove-uid", UPDATE_CPU);
134 }
135
Sudheer Shankab2f83c12017-11-13 19:25:01 -0800136 @Override
Sudheer Shanka5c19b892018-01-05 17:25:46 -0800137 public synchronized Future<?> scheduleCpuSyncDueToSettingChange() {
138 return scheduleSyncLocked("setting-change", UPDATE_CPU);
139 }
140
141 @Override
Sudheer Shankac20379e2018-02-15 00:06:21 -0800142 public Future<?> scheduleReadProcStateCpuTimes(
143 boolean onBattery, boolean onBatteryScreenOff, long delayMillis) {
Sudheer Shankab2f83c12017-11-13 19:25:01 -0800144 synchronized (mStats) {
Sudheer Shanka5c19b892018-01-05 17:25:46 -0800145 if (!mStats.trackPerProcStateCpuTimes()) {
Sudheer Shankab2f83c12017-11-13 19:25:01 -0800146 return null;
147 }
148 }
149 synchronized (BatteryExternalStatsWorker.this) {
150 if (!mExecutorService.isShutdown()) {
Sudheer Shankac20379e2018-02-15 00:06:21 -0800151 return mExecutorService.schedule(PooledLambda.obtainRunnable(
Sudheer Shankae544d162017-12-28 17:06:20 -0800152 BatteryStatsImpl::updateProcStateCpuTimes,
Sudheer Shankac20379e2018-02-15 00:06:21 -0800153 mStats, onBattery, onBatteryScreenOff).recycleOnUse(),
154 delayMillis, TimeUnit.MILLISECONDS);
Sudheer Shankab2f83c12017-11-13 19:25:01 -0800155 }
156 }
157 return null;
158 }
159
160 @Override
Sudheer Shankae544d162017-12-28 17:06:20 -0800161 public Future<?> scheduleCopyFromAllUidsCpuTimes(
162 boolean onBattery, boolean onBatteryScreenOff) {
Sudheer Shankab2f83c12017-11-13 19:25:01 -0800163 synchronized (mStats) {
Sudheer Shanka5c19b892018-01-05 17:25:46 -0800164 if (!mStats.trackPerProcStateCpuTimes()) {
Sudheer Shankab2f83c12017-11-13 19:25:01 -0800165 return null;
166 }
167 }
168 synchronized (BatteryExternalStatsWorker.this) {
169 if (!mExecutorService.isShutdown()) {
Sudheer Shankae544d162017-12-28 17:06:20 -0800170 return mExecutorService.submit(PooledLambda.obtainRunnable(
171 BatteryStatsImpl::copyFromAllUidsCpuTimes,
172 mStats, onBattery, onBatteryScreenOff).recycleOnUse());
Sudheer Shankab2f83c12017-11-13 19:25:01 -0800173 }
174 }
175 return null;
176 }
177
Sudheer Shankac57729a2018-02-09 15:44:42 -0800178 @Override
179 public Future<?> scheduleCpuSyncDueToScreenStateChange(
180 boolean onBattery, boolean onBatteryScreenOff) {
181 synchronized (BatteryExternalStatsWorker.this) {
182 if (mCurrentFuture == null || (mUpdateFlags & UPDATE_CPU) == 0) {
183 mOnBattery = onBattery;
184 mOnBatteryScreenOff = onBatteryScreenOff;
185 mUseLatestStates = false;
186 }
187 return scheduleSyncLocked("screen-state", UPDATE_CPU);
188 }
189 }
190
191 @Override
192 public Future<?> scheduleCpuSyncDueToWakelockChange(long delayMillis) {
193 if (mExecutorService.isShutdown()) {
194 return CompletableFuture.failedFuture(new IllegalStateException("worker shutdown"));
195 }
196
197 if (mWakelockChangesUpdate != null) {
198 // If there's already a scheduled task, leave it as is if we're trying to re-schedule
199 // it again with a delay, otherwise cancel and re-schedule it.
200 if (delayMillis == 0) {
201 mWakelockChangesUpdate.cancel(false);
202 } else {
203 return mWakelockChangesUpdate;
204 }
205 }
206
207 mWakelockChangesUpdate = mExecutorService.schedule(() -> {
208 scheduleSync("wakelock-change", UPDATE_CPU);
209 scheduleRunnable(() -> mStats.postBatteryNeedsCpuUpdateMsg());
210 mWakelockChangesUpdate = null;
211 }, delayMillis, TimeUnit.MILLISECONDS);
212 return mWakelockChangesUpdate;
213 }
214
215 @Override
216 public void cancelCpuSyncDueToWakelockChange() {
217 if (mWakelockChangesUpdate != null) {
218 mWakelockChangesUpdate.cancel(false);
219 }
220 }
221
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700222 public synchronized Future<?> scheduleWrite() {
Adam Lesinskicf0b2b62017-08-29 19:30:31 -0700223 if (mExecutorService.isShutdown()) {
224 return CompletableFuture.failedFuture(new IllegalStateException("worker shutdown"));
225 }
226
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700227 scheduleSyncLocked("write", UPDATE_ALL);
228 // Since we use a single threaded executor, we can assume the next scheduled task's
229 // Future finishes after the sync.
230 return mExecutorService.submit(mWriteTask);
231 }
232
233 /**
234 * Schedules a task to run on the BatteryExternalStatsWorker thread. If scheduling more work
235 * within the task, never wait on the resulting Future. This will result in a deadlock.
236 */
237 public synchronized void scheduleRunnable(Runnable runnable) {
Adam Lesinskicf0b2b62017-08-29 19:30:31 -0700238 if (!mExecutorService.isShutdown()) {
239 mExecutorService.submit(runnable);
240 }
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700241 }
242
243 public void shutdown() {
244 mExecutorService.shutdownNow();
245 }
246
Andreas Gampea36dc622018-02-05 17:19:22 -0800247 @GuardedBy("this")
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700248 private Future<?> scheduleSyncLocked(String reason, int flags) {
Adam Lesinskicf0b2b62017-08-29 19:30:31 -0700249 if (mExecutorService.isShutdown()) {
250 return CompletableFuture.failedFuture(new IllegalStateException("worker shutdown"));
251 }
252
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700253 if (mCurrentFuture == null) {
254 mUpdateFlags = flags;
255 mCurrentReason = reason;
256 mCurrentFuture = mExecutorService.submit(mSyncTask);
257 }
258 mUpdateFlags |= flags;
259 return mCurrentFuture;
260 }
261
262 private final Runnable mSyncTask = new Runnable() {
263 @Override
264 public void run() {
265 // Capture a snapshot of the state we are meant to process.
266 final int updateFlags;
267 final String reason;
268 final int[] uidsToRemove;
Sudheer Shankac57729a2018-02-09 15:44:42 -0800269 final boolean onBattery;
270 final boolean onBatteryScreenOff;
271 final boolean useLatestStates;
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700272 synchronized (BatteryExternalStatsWorker.this) {
273 updateFlags = mUpdateFlags;
274 reason = mCurrentReason;
275 uidsToRemove = mUidsToRemove.size() > 0 ? mUidsToRemove.toArray() : EmptyArray.INT;
Sudheer Shankac57729a2018-02-09 15:44:42 -0800276 onBattery = mOnBattery;
277 onBatteryScreenOff = mOnBatteryScreenOff;
278 useLatestStates = mUseLatestStates;
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700279 mUpdateFlags = 0;
280 mCurrentReason = null;
281 mUidsToRemove.clear();
282 mCurrentFuture = null;
Sudheer Shankac57729a2018-02-09 15:44:42 -0800283 mUseLatestStates = true;
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700284 }
285
Mike Ma561a8d92018-03-20 18:24:05 -0700286 try {
287 synchronized (mWorkerLock) {
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700288 if (DEBUG) {
Mike Ma561a8d92018-03-20 18:24:05 -0700289 Slog.d(TAG, "begin updateExternalStatsSync reason=" + reason);
290 }
291 try {
292 updateExternalStatsLocked(reason, updateFlags, onBattery,
293 onBatteryScreenOff, useLatestStates);
294 } finally {
295 if (DEBUG) {
296 Slog.d(TAG, "end updateExternalStatsSync");
297 }
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700298 }
299 }
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700300
Mike Ma561a8d92018-03-20 18:24:05 -0700301 if ((updateFlags & UPDATE_CPU) != 0) {
302 mStats.copyFromAllUidsCpuTimes();
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700303 }
Mike Ma561a8d92018-03-20 18:24:05 -0700304
305 // Clean up any UIDs if necessary.
306 synchronized (mStats) {
307 for (int uid : uidsToRemove) {
308 mStats.removeIsolatedUidLocked(uid);
309 }
310 mStats.clearPendingRemovedUids();
311 }
312 } catch (Exception e) {
313 Slog.wtf(TAG, "Error updating external stats: ", e);
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700314 }
315 }
316 };
317
318 private final Runnable mWriteTask = new Runnable() {
319 @Override
320 public void run() {
321 synchronized (mStats) {
322 mStats.writeAsyncLocked();
323 }
324 }
325 };
326
Andreas Gampea36dc622018-02-05 17:19:22 -0800327 @GuardedBy("mWorkerLock")
Sudheer Shankac57729a2018-02-09 15:44:42 -0800328 private void updateExternalStatsLocked(final String reason, int updateFlags,
329 boolean onBattery, boolean onBatteryScreenOff, boolean useLatestStates) {
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700330 // We will request data from external processes asynchronously, and wait on a timeout.
331 SynchronousResultReceiver wifiReceiver = null;
332 SynchronousResultReceiver bluetoothReceiver = null;
333 SynchronousResultReceiver modemReceiver = null;
334
335 if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_WIFI) != 0) {
336 // We were asked to fetch WiFi data.
337 if (mWifiManager == null) {
338 mWifiManager = IWifiManager.Stub.asInterface(ServiceManager.getService(
339 Context.WIFI_SERVICE));
340 }
341
342 if (mWifiManager != null) {
343 try {
344 wifiReceiver = new SynchronousResultReceiver("wifi");
345 mWifiManager.requestActivityInfo(wifiReceiver);
346 } catch (RemoteException e) {
347 // Oh well.
348 }
349 }
350 }
351
352 if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_BT) != 0) {
353 // We were asked to fetch Bluetooth data.
354 final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
355 if (adapter != null) {
356 bluetoothReceiver = new SynchronousResultReceiver("bluetooth");
357 adapter.requestControllerActivityEnergyInfo(bluetoothReceiver);
358 }
359 }
360
361 if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_RADIO) != 0) {
362 // We were asked to fetch Telephony data.
363 if (mTelephony == null) {
364 mTelephony = TelephonyManager.from(mContext);
365 }
366
367 if (mTelephony != null) {
368 modemReceiver = new SynchronousResultReceiver("telephony");
369 mTelephony.requestModemActivityInfo(modemReceiver);
370 }
371 }
372
373 final WifiActivityEnergyInfo wifiInfo = awaitControllerInfo(wifiReceiver);
374 final BluetoothActivityEnergyInfo bluetoothInfo = awaitControllerInfo(bluetoothReceiver);
375 final ModemActivityInfo modemInfo = awaitControllerInfo(modemReceiver);
376
377 synchronized (mStats) {
378 mStats.addHistoryEventLocked(
379 SystemClock.elapsedRealtime(),
380 SystemClock.uptimeMillis(),
381 BatteryStats.HistoryItem.EVENT_COLLECT_EXTERNAL_STATS,
382 reason, 0);
383
384 if ((updateFlags & UPDATE_CPU) != 0) {
Sudheer Shankac57729a2018-02-09 15:44:42 -0800385 if (useLatestStates) {
386 onBattery = mStats.isOnBatteryLocked();
387 onBatteryScreenOff = mStats.isOnBatteryScreenOffLocked();
388 }
389 mStats.updateCpuTimeLocked(onBattery, onBatteryScreenOff);
390 }
391
392 if ((updateFlags & UPDATE_ALL) != 0) {
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700393 mStats.updateKernelWakelocksLocked();
394 mStats.updateKernelMemoryBandwidthLocked();
395 }
396
Bookatz50df7112017-08-04 14:53:26 -0700397 if ((updateFlags & UPDATE_RPM) != 0) {
398 mStats.updateRpmStatsLocked();
399 }
400
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700401 if (bluetoothInfo != null) {
402 if (bluetoothInfo.isValid()) {
403 mStats.updateBluetoothStateLocked(bluetoothInfo);
404 } else {
Mike Ma561a8d92018-03-20 18:24:05 -0700405 Slog.w(TAG, "bluetooth info is invalid: " + bluetoothInfo);
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700406 }
407 }
408 }
409
410 // WiFi and Modem state are updated without the mStats lock held, because they
411 // do some network stats retrieval before internally grabbing the mStats lock.
412
413 if (wifiInfo != null) {
414 if (wifiInfo.isValid()) {
415 mStats.updateWifiState(extractDeltaLocked(wifiInfo));
416 } else {
Mike Ma561a8d92018-03-20 18:24:05 -0700417 Slog.w(TAG, "wifi info is invalid: " + wifiInfo);
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700418 }
419 }
420
421 if (modemInfo != null) {
422 if (modemInfo.isValid()) {
423 mStats.updateMobileRadioState(modemInfo);
424 } else {
Mike Ma561a8d92018-03-20 18:24:05 -0700425 Slog.w(TAG, "modem info is invalid: " + modemInfo);
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700426 }
427 }
428 }
429
430 /**
431 * Helper method to extract the Parcelable controller info from a
432 * SynchronousResultReceiver.
433 */
434 private static <T extends Parcelable> T awaitControllerInfo(
435 @Nullable SynchronousResultReceiver receiver) {
436 if (receiver == null) {
437 return null;
438 }
439
440 try {
441 final SynchronousResultReceiver.Result result =
442 receiver.awaitResult(EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS);
443 if (result.bundle != null) {
444 // This is the final destination for the Bundle.
445 result.bundle.setDefusable(true);
446
447 final T data = result.bundle.getParcelable(
448 BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY);
449 if (data != null) {
450 return data;
451 }
452 }
453 Slog.e(TAG, "no controller energy info supplied for " + receiver.getName());
454 } catch (TimeoutException e) {
455 Slog.w(TAG, "timeout reading " + receiver.getName() + " stats");
456 }
457 return null;
458 }
459
Andreas Gampea36dc622018-02-05 17:19:22 -0800460 @GuardedBy("mWorkerLock")
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700461 private WifiActivityEnergyInfo extractDeltaLocked(WifiActivityEnergyInfo latest) {
462 final long timePeriodMs = latest.mTimestamp - mLastInfo.mTimestamp;
Siddharth Rayb50a6842017-12-14 15:15:28 -0800463 final long lastScanMs = mLastInfo.mControllerScanTimeMs;
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700464 final long lastIdleMs = mLastInfo.mControllerIdleTimeMs;
465 final long lastTxMs = mLastInfo.mControllerTxTimeMs;
466 final long lastRxMs = mLastInfo.mControllerRxTimeMs;
467 final long lastEnergy = mLastInfo.mControllerEnergyUsed;
468
469 // We will modify the last info object to be the delta, and store the new
470 // WifiActivityEnergyInfo object as our last one.
471 final WifiActivityEnergyInfo delta = mLastInfo;
472 delta.mTimestamp = latest.getTimeStamp();
473 delta.mStackState = latest.getStackState();
474
475 final long txTimeMs = latest.mControllerTxTimeMs - lastTxMs;
476 final long rxTimeMs = latest.mControllerRxTimeMs - lastRxMs;
477 final long idleTimeMs = latest.mControllerIdleTimeMs - lastIdleMs;
Siddharth Rayb50a6842017-12-14 15:15:28 -0800478 final long scanTimeMs = latest.mControllerScanTimeMs - lastScanMs;
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700479
Siddharth Rayb50a6842017-12-14 15:15:28 -0800480 if (txTimeMs < 0 || rxTimeMs < 0 || scanTimeMs < 0) {
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700481 // The stats were reset by the WiFi system (which is why our delta is negative).
482 // Returns the unaltered stats.
483 delta.mControllerEnergyUsed = latest.mControllerEnergyUsed;
484 delta.mControllerRxTimeMs = latest.mControllerRxTimeMs;
485 delta.mControllerTxTimeMs = latest.mControllerTxTimeMs;
486 delta.mControllerIdleTimeMs = latest.mControllerIdleTimeMs;
Siddharth Rayb50a6842017-12-14 15:15:28 -0800487 delta.mControllerScanTimeMs = latest.mControllerScanTimeMs;
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700488 Slog.v(TAG, "WiFi energy data was reset, new WiFi energy data is " + delta);
489 } else {
490 final long totalActiveTimeMs = txTimeMs + rxTimeMs;
491 long maxExpectedIdleTimeMs;
492 if (totalActiveTimeMs > timePeriodMs) {
493 // Cap the max idle time at zero since the active time consumed the whole time
494 maxExpectedIdleTimeMs = 0;
495 if (totalActiveTimeMs > timePeriodMs + MAX_WIFI_STATS_SAMPLE_ERROR_MILLIS) {
496 StringBuilder sb = new StringBuilder();
497 sb.append("Total Active time ");
498 TimeUtils.formatDuration(totalActiveTimeMs, sb);
499 sb.append(" is longer than sample period ");
500 TimeUtils.formatDuration(timePeriodMs, sb);
501 sb.append(".\n");
502 sb.append("Previous WiFi snapshot: ").append("idle=");
503 TimeUtils.formatDuration(lastIdleMs, sb);
504 sb.append(" rx=");
505 TimeUtils.formatDuration(lastRxMs, sb);
506 sb.append(" tx=");
507 TimeUtils.formatDuration(lastTxMs, sb);
508 sb.append(" e=").append(lastEnergy);
509 sb.append("\n");
510 sb.append("Current WiFi snapshot: ").append("idle=");
511 TimeUtils.formatDuration(latest.mControllerIdleTimeMs, sb);
512 sb.append(" rx=");
513 TimeUtils.formatDuration(latest.mControllerRxTimeMs, sb);
514 sb.append(" tx=");
515 TimeUtils.formatDuration(latest.mControllerTxTimeMs, sb);
516 sb.append(" e=").append(latest.mControllerEnergyUsed);
517 Slog.wtf(TAG, sb.toString());
518 }
519 } else {
520 maxExpectedIdleTimeMs = timePeriodMs - totalActiveTimeMs;
521 }
522 // These times seem to be the most reliable.
523 delta.mControllerTxTimeMs = txTimeMs;
524 delta.mControllerRxTimeMs = rxTimeMs;
Siddharth Rayb50a6842017-12-14 15:15:28 -0800525 delta.mControllerScanTimeMs = scanTimeMs;
Adam Lesinskib3a1bad2017-05-26 11:50:40 -0700526 // WiFi calculates the idle time as a difference from the on time and the various
527 // Rx + Tx times. There seems to be some missing time there because this sometimes
528 // becomes negative. Just cap it at 0 and ensure that it is less than the expected idle
529 // time from the difference in timestamps.
530 // b/21613534
531 delta.mControllerIdleTimeMs = Math.min(maxExpectedIdleTimeMs, Math.max(0, idleTimeMs));
532 delta.mControllerEnergyUsed = Math.max(0, latest.mControllerEnergyUsed - lastEnergy);
533 }
534
535 mLastInfo = latest;
536 return delta;
537 }
538}