blob: 1e5bd1894843df44f7cff2f5b9d6b7a7d7c10edb [file] [log] [blame]
Justin Klaassen10d07c82017-09-15 17:58:39 -04001/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy 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,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.internal.os;
18
19import android.content.Context;
20import android.content.Intent;
21import android.content.IntentFilter;
22import android.content.pm.PackageManager;
23import android.content.res.Resources;
24import android.hardware.SensorManager;
25import android.net.ConnectivityManager;
26import android.os.BatteryStats;
27import android.os.BatteryStats.Uid;
28import android.os.Bundle;
29import android.os.MemoryFile;
30import android.os.Parcel;
31import android.os.ParcelFileDescriptor;
32import android.os.Process;
33import android.os.RemoteException;
34import android.os.ServiceManager;
35import android.os.SystemClock;
36import android.os.UserHandle;
37import android.text.format.DateUtils;
38import android.util.ArrayMap;
39import android.util.Log;
40import android.util.SparseArray;
41import android.util.SparseLongArray;
42
43import com.android.internal.annotations.VisibleForTesting;
44import com.android.internal.app.IBatteryStats;
45import com.android.internal.os.BatterySipper.DrainType;
46import com.android.internal.util.ArrayUtils;
47
48import java.io.File;
49import java.io.FileInputStream;
50import java.io.FileOutputStream;
51import java.io.IOException;
52import java.util.ArrayList;
53import java.util.Collections;
54import java.util.Comparator;
55import java.util.List;
56import java.util.Locale;
57
58/**
59 * A helper class for retrieving the power usage information for all applications and services.
60 *
61 * The caller must initialize this class as soon as activity object is ready to use (for example, in
62 * onAttach() for Fragment), call create() in onCreate() and call destroy() in onDestroy().
63 */
64public class BatteryStatsHelper {
65 static final boolean DEBUG = false;
66
67 private static final String TAG = BatteryStatsHelper.class.getSimpleName();
68
69 private static BatteryStats sStatsXfer;
70 private static Intent sBatteryBroadcastXfer;
71 private static ArrayMap<File, BatteryStats> sFileXfer = new ArrayMap<>();
72
73 final private Context mContext;
74 final private boolean mCollectBatteryBroadcast;
75 final private boolean mWifiOnly;
76
77 private IBatteryStats mBatteryInfo;
78 private BatteryStats mStats;
79 private Intent mBatteryBroadcast;
80 private PowerProfile mPowerProfile;
81
82 private String[] mSystemPackageArray;
83 private String[] mServicepackageArray;
84 private PackageManager mPackageManager;
85
86 /**
87 * List of apps using power.
88 */
89 private final List<BatterySipper> mUsageList = new ArrayList<>();
90
91 /**
92 * List of apps using wifi power.
93 */
94 private final List<BatterySipper> mWifiSippers = new ArrayList<>();
95
96 /**
97 * List of apps using bluetooth power.
98 */
99 private final List<BatterySipper> mBluetoothSippers = new ArrayList<>();
100
101 private final SparseArray<List<BatterySipper>> mUserSippers = new SparseArray<>();
102
103 private final List<BatterySipper> mMobilemsppList = new ArrayList<>();
104
105 private int mStatsType = BatteryStats.STATS_SINCE_CHARGED;
106
107 long mRawRealtimeUs;
108 long mRawUptimeUs;
109 long mBatteryRealtimeUs;
110 long mBatteryUptimeUs;
111 long mTypeBatteryRealtimeUs;
112 long mTypeBatteryUptimeUs;
113 long mBatteryTimeRemainingUs;
114 long mChargeTimeRemainingUs;
115
116 private long mStatsPeriod = 0;
117
118 // The largest entry by power.
119 private double mMaxPower = 1;
120
121 // The largest real entry by power (not undercounted or overcounted).
122 private double mMaxRealPower = 1;
123
124 // Total computed power.
125 private double mComputedPower;
126 private double mTotalPower;
127 private double mMinDrainedPower;
128 private double mMaxDrainedPower;
129
130 PowerCalculator mCpuPowerCalculator;
131 PowerCalculator mWakelockPowerCalculator;
132 MobileRadioPowerCalculator mMobileRadioPowerCalculator;
133 PowerCalculator mWifiPowerCalculator;
134 PowerCalculator mBluetoothPowerCalculator;
135 PowerCalculator mSensorPowerCalculator;
136 PowerCalculator mCameraPowerCalculator;
137 PowerCalculator mFlashlightPowerCalculator;
138 PowerCalculator mMemoryPowerCalculator;
Justin Klaassen4d01eea2018-04-03 23:21:57 -0400139 PowerCalculator mMediaPowerCalculator;
Justin Klaassen10d07c82017-09-15 17:58:39 -0400140
141 boolean mHasWifiPowerReporting = false;
142 boolean mHasBluetoothPowerReporting = false;
143
144 public static boolean checkWifiOnly(Context context) {
145 ConnectivityManager cm = (ConnectivityManager) context.getSystemService(
146 Context.CONNECTIVITY_SERVICE);
Justin Klaassen93b7ee42017-10-10 15:20:13 -0400147 if (cm == null) {
148 return false;
149 }
Justin Klaassen10d07c82017-09-15 17:58:39 -0400150 return !cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
151 }
152
153 public static boolean checkHasWifiPowerReporting(BatteryStats stats, PowerProfile profile) {
154 return stats.hasWifiActivityReporting() &&
155 profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_IDLE) != 0 &&
156 profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_RX) != 0 &&
157 profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_TX) != 0;
158 }
159
160 public static boolean checkHasBluetoothPowerReporting(BatteryStats stats,
161 PowerProfile profile) {
162 return stats.hasBluetoothActivityReporting() &&
163 profile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_IDLE) != 0 &&
164 profile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_RX) != 0 &&
165 profile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_TX) != 0;
166 }
167
168 public BatteryStatsHelper(Context context) {
169 this(context, true);
170 }
171
172 public BatteryStatsHelper(Context context, boolean collectBatteryBroadcast) {
173 this(context, collectBatteryBroadcast, checkWifiOnly(context));
174 }
175
176 public BatteryStatsHelper(Context context, boolean collectBatteryBroadcast, boolean wifiOnly) {
177 mContext = context;
178 mCollectBatteryBroadcast = collectBatteryBroadcast;
179 mWifiOnly = wifiOnly;
180 mPackageManager = context.getPackageManager();
181
182 final Resources resources = context.getResources();
183 mSystemPackageArray = resources.getStringArray(
184 com.android.internal.R.array.config_batteryPackageTypeSystem);
185 mServicepackageArray = resources.getStringArray(
186 com.android.internal.R.array.config_batteryPackageTypeService);
187 }
188
189 public void storeStatsHistoryInFile(String fname) {
190 synchronized (sFileXfer) {
191 File path = makeFilePath(mContext, fname);
192 sFileXfer.put(path, this.getStats());
193 FileOutputStream fout = null;
194 try {
195 fout = new FileOutputStream(path);
196 Parcel hist = Parcel.obtain();
197 getStats().writeToParcelWithoutUids(hist, 0);
198 byte[] histData = hist.marshall();
199 fout.write(histData);
200 } catch (IOException e) {
201 Log.w(TAG, "Unable to write history to file", e);
202 } finally {
203 if (fout != null) {
204 try {
205 fout.close();
206 } catch (IOException e) {
207 }
208 }
209 }
210 }
211 }
212
213 public static BatteryStats statsFromFile(Context context, String fname) {
214 synchronized (sFileXfer) {
215 File path = makeFilePath(context, fname);
216 BatteryStats stats = sFileXfer.get(path);
217 if (stats != null) {
218 return stats;
219 }
220 FileInputStream fin = null;
221 try {
222 fin = new FileInputStream(path);
223 byte[] data = readFully(fin);
224 Parcel parcel = Parcel.obtain();
225 parcel.unmarshall(data, 0, data.length);
226 parcel.setDataPosition(0);
227 return com.android.internal.os.BatteryStatsImpl.CREATOR.createFromParcel(parcel);
228 } catch (IOException e) {
229 Log.w(TAG, "Unable to read history to file", e);
230 } finally {
231 if (fin != null) {
232 try {
233 fin.close();
234 } catch (IOException e) {
235 }
236 }
237 }
238 }
239 return getStats(IBatteryStats.Stub.asInterface(
240 ServiceManager.getService(BatteryStats.SERVICE_NAME)));
241 }
242
243 public static void dropFile(Context context, String fname) {
244 makeFilePath(context, fname).delete();
245 }
246
247 private static File makeFilePath(Context context, String fname) {
248 return new File(context.getFilesDir(), fname);
249 }
250
251 /** Clears the current stats and forces recreating for future use. */
252 public void clearStats() {
253 mStats = null;
254 }
255
256 public BatteryStats getStats() {
257 if (mStats == null) {
258 load();
259 }
260 return mStats;
261 }
262
263 public Intent getBatteryBroadcast() {
264 if (mBatteryBroadcast == null && mCollectBatteryBroadcast) {
265 load();
266 }
267 return mBatteryBroadcast;
268 }
269
270 public PowerProfile getPowerProfile() {
271 return mPowerProfile;
272 }
273
274 public void create(BatteryStats stats) {
275 mPowerProfile = new PowerProfile(mContext);
276 mStats = stats;
277 }
278
279 public void create(Bundle icicle) {
280 if (icicle != null) {
281 mStats = sStatsXfer;
282 mBatteryBroadcast = sBatteryBroadcastXfer;
283 }
284 mBatteryInfo = IBatteryStats.Stub.asInterface(
285 ServiceManager.getService(BatteryStats.SERVICE_NAME));
286 mPowerProfile = new PowerProfile(mContext);
287 }
288
289 public void storeState() {
290 sStatsXfer = mStats;
291 sBatteryBroadcastXfer = mBatteryBroadcast;
292 }
293
294 public static String makemAh(double power) {
295 if (power == 0) return "0";
296
297 final String format;
298 if (power < .00001) {
299 format = "%.8f";
300 } else if (power < .0001) {
301 format = "%.7f";
302 } else if (power < .001) {
303 format = "%.6f";
304 } else if (power < .01) {
305 format = "%.5f";
306 } else if (power < .1) {
307 format = "%.4f";
308 } else if (power < 1) {
309 format = "%.3f";
310 } else if (power < 10) {
311 format = "%.2f";
312 } else if (power < 100) {
313 format = "%.1f";
314 } else {
315 format = "%.0f";
316 }
317
318 // Use English locale because this is never used in UI (only in checkin and dump).
319 return String.format(Locale.ENGLISH, format, power);
320 }
321
322 /**
323 * Refreshes the power usage list.
324 */
325 public void refreshStats(int statsType, int asUser) {
326 SparseArray<UserHandle> users = new SparseArray<>(1);
327 users.put(asUser, new UserHandle(asUser));
328 refreshStats(statsType, users);
329 }
330
331 /**
332 * Refreshes the power usage list.
333 */
334 public void refreshStats(int statsType, List<UserHandle> asUsers) {
335 final int n = asUsers.size();
336 SparseArray<UserHandle> users = new SparseArray<>(n);
337 for (int i = 0; i < n; ++i) {
338 UserHandle userHandle = asUsers.get(i);
339 users.put(userHandle.getIdentifier(), userHandle);
340 }
341 refreshStats(statsType, users);
342 }
343
344 /**
345 * Refreshes the power usage list.
346 */
347 public void refreshStats(int statsType, SparseArray<UserHandle> asUsers) {
348 refreshStats(statsType, asUsers, SystemClock.elapsedRealtime() * 1000,
349 SystemClock.uptimeMillis() * 1000);
350 }
351
352 public void refreshStats(int statsType, SparseArray<UserHandle> asUsers, long rawRealtimeUs,
353 long rawUptimeUs) {
354 // Initialize mStats if necessary.
355 getStats();
356
357 mMaxPower = 0;
358 mMaxRealPower = 0;
359 mComputedPower = 0;
360 mTotalPower = 0;
361
362 mUsageList.clear();
363 mWifiSippers.clear();
364 mBluetoothSippers.clear();
365 mUserSippers.clear();
366 mMobilemsppList.clear();
367
368 if (mStats == null) {
369 return;
370 }
371
372 if (mCpuPowerCalculator == null) {
373 mCpuPowerCalculator = new CpuPowerCalculator(mPowerProfile);
374 }
375 mCpuPowerCalculator.reset();
376
377 if (mMemoryPowerCalculator == null) {
378 mMemoryPowerCalculator = new MemoryPowerCalculator(mPowerProfile);
379 }
380 mMemoryPowerCalculator.reset();
381
382 if (mWakelockPowerCalculator == null) {
383 mWakelockPowerCalculator = new WakelockPowerCalculator(mPowerProfile);
384 }
385 mWakelockPowerCalculator.reset();
386
387 if (mMobileRadioPowerCalculator == null) {
388 mMobileRadioPowerCalculator = new MobileRadioPowerCalculator(mPowerProfile, mStats);
389 }
390 mMobileRadioPowerCalculator.reset(mStats);
391
392 // checkHasWifiPowerReporting can change if we get energy data at a later point, so
393 // always check this field.
394 final boolean hasWifiPowerReporting = checkHasWifiPowerReporting(mStats, mPowerProfile);
395 if (mWifiPowerCalculator == null || hasWifiPowerReporting != mHasWifiPowerReporting) {
396 mWifiPowerCalculator = hasWifiPowerReporting ?
397 new WifiPowerCalculator(mPowerProfile) :
398 new WifiPowerEstimator(mPowerProfile);
399 mHasWifiPowerReporting = hasWifiPowerReporting;
400 }
401 mWifiPowerCalculator.reset();
402
403 final boolean hasBluetoothPowerReporting = checkHasBluetoothPowerReporting(mStats,
404 mPowerProfile);
405 if (mBluetoothPowerCalculator == null ||
406 hasBluetoothPowerReporting != mHasBluetoothPowerReporting) {
407 mBluetoothPowerCalculator = new BluetoothPowerCalculator(mPowerProfile);
408 mHasBluetoothPowerReporting = hasBluetoothPowerReporting;
409 }
410 mBluetoothPowerCalculator.reset();
411
412 if (mSensorPowerCalculator == null) {
413 mSensorPowerCalculator = new SensorPowerCalculator(mPowerProfile,
414 (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE));
415 }
416 mSensorPowerCalculator.reset();
417
418 if (mCameraPowerCalculator == null) {
419 mCameraPowerCalculator = new CameraPowerCalculator(mPowerProfile);
420 }
421 mCameraPowerCalculator.reset();
422
423 if (mFlashlightPowerCalculator == null) {
424 mFlashlightPowerCalculator = new FlashlightPowerCalculator(mPowerProfile);
425 }
426 mFlashlightPowerCalculator.reset();
427
Justin Klaassen4d01eea2018-04-03 23:21:57 -0400428 if (mMediaPowerCalculator == null) {
429 mMediaPowerCalculator = new MediaPowerCalculator(mPowerProfile);
430 }
431 mMediaPowerCalculator.reset();
432
Justin Klaassen10d07c82017-09-15 17:58:39 -0400433 mStatsType = statsType;
434 mRawUptimeUs = rawUptimeUs;
435 mRawRealtimeUs = rawRealtimeUs;
436 mBatteryUptimeUs = mStats.getBatteryUptime(rawUptimeUs);
437 mBatteryRealtimeUs = mStats.getBatteryRealtime(rawRealtimeUs);
438 mTypeBatteryUptimeUs = mStats.computeBatteryUptime(rawUptimeUs, mStatsType);
439 mTypeBatteryRealtimeUs = mStats.computeBatteryRealtime(rawRealtimeUs, mStatsType);
440 mBatteryTimeRemainingUs = mStats.computeBatteryTimeRemaining(rawRealtimeUs);
441 mChargeTimeRemainingUs = mStats.computeChargeTimeRemaining(rawRealtimeUs);
442
443 if (DEBUG) {
444 Log.d(TAG, "Raw time: realtime=" + (rawRealtimeUs / 1000) + " uptime="
445 + (rawUptimeUs / 1000));
446 Log.d(TAG, "Battery time: realtime=" + (mBatteryRealtimeUs / 1000) + " uptime="
447 + (mBatteryUptimeUs / 1000));
448 Log.d(TAG, "Battery type time: realtime=" + (mTypeBatteryRealtimeUs / 1000) + " uptime="
449 + (mTypeBatteryUptimeUs / 1000));
450 }
451 mMinDrainedPower = (mStats.getLowDischargeAmountSinceCharge()
452 * mPowerProfile.getBatteryCapacity()) / 100;
453 mMaxDrainedPower = (mStats.getHighDischargeAmountSinceCharge()
454 * mPowerProfile.getBatteryCapacity()) / 100;
455
456 processAppUsage(asUsers);
457
458 // Before aggregating apps in to users, collect all apps to sort by their ms per packet.
459 for (int i = 0; i < mUsageList.size(); i++) {
460 BatterySipper bs = mUsageList.get(i);
461 bs.computeMobilemspp();
462 if (bs.mobilemspp != 0) {
463 mMobilemsppList.add(bs);
464 }
465 }
466
467 for (int i = 0; i < mUserSippers.size(); i++) {
468 List<BatterySipper> user = mUserSippers.valueAt(i);
469 for (int j = 0; j < user.size(); j++) {
470 BatterySipper bs = user.get(j);
471 bs.computeMobilemspp();
472 if (bs.mobilemspp != 0) {
473 mMobilemsppList.add(bs);
474 }
475 }
476 }
477 Collections.sort(mMobilemsppList, new Comparator<BatterySipper>() {
478 @Override
479 public int compare(BatterySipper lhs, BatterySipper rhs) {
480 return Double.compare(rhs.mobilemspp, lhs.mobilemspp);
481 }
482 });
483
484 processMiscUsage();
485
486 Collections.sort(mUsageList);
487
488 // At this point, we've sorted the list so we are guaranteed the max values are at the top.
489 // We have only added real powers so far.
490 if (!mUsageList.isEmpty()) {
491 mMaxRealPower = mMaxPower = mUsageList.get(0).totalPowerMah;
492 final int usageListCount = mUsageList.size();
493 for (int i = 0; i < usageListCount; i++) {
494 mComputedPower += mUsageList.get(i).totalPowerMah;
495 }
496 }
497
498 if (DEBUG) {
499 Log.d(TAG, "Accuracy: total computed=" + makemAh(mComputedPower) + ", min discharge="
500 + makemAh(mMinDrainedPower) + ", max discharge=" + makemAh(mMaxDrainedPower));
501 }
502
503 mTotalPower = mComputedPower;
504 if (mStats.getLowDischargeAmountSinceCharge() > 1) {
505 if (mMinDrainedPower > mComputedPower) {
506 double amount = mMinDrainedPower - mComputedPower;
507 mTotalPower = mMinDrainedPower;
508 BatterySipper bs = new BatterySipper(DrainType.UNACCOUNTED, null, amount);
509
510 // Insert the BatterySipper in its sorted position.
511 int index = Collections.binarySearch(mUsageList, bs);
512 if (index < 0) {
513 index = -(index + 1);
514 }
515 mUsageList.add(index, bs);
516 mMaxPower = Math.max(mMaxPower, amount);
517 } else if (mMaxDrainedPower < mComputedPower) {
518 double amount = mComputedPower - mMaxDrainedPower;
519
520 // Insert the BatterySipper in its sorted position.
521 BatterySipper bs = new BatterySipper(DrainType.OVERCOUNTED, null, amount);
522 int index = Collections.binarySearch(mUsageList, bs);
523 if (index < 0) {
524 index = -(index + 1);
525 }
526 mUsageList.add(index, bs);
527 mMaxPower = Math.max(mMaxPower, amount);
528 }
529 }
530
531 // Smear it!
532 final double hiddenPowerMah = removeHiddenBatterySippers(mUsageList);
533 final double totalRemainingPower = getTotalPower() - hiddenPowerMah;
534 if (Math.abs(totalRemainingPower) > 1e-3) {
535 for (int i = 0, size = mUsageList.size(); i < size; i++) {
536 final BatterySipper sipper = mUsageList.get(i);
537 if (!sipper.shouldHide) {
538 sipper.proportionalSmearMah = hiddenPowerMah
539 * ((sipper.totalPowerMah + sipper.screenPowerMah)
540 / totalRemainingPower);
541 sipper.sumPower();
542 }
543 }
544 }
545 }
546
547 private void processAppUsage(SparseArray<UserHandle> asUsers) {
548 final boolean forAllUsers = (asUsers.get(UserHandle.USER_ALL) != null);
549 mStatsPeriod = mTypeBatteryRealtimeUs;
550
551 BatterySipper osSipper = null;
552 final SparseArray<? extends Uid> uidStats = mStats.getUidStats();
553 final int NU = uidStats.size();
554 for (int iu = 0; iu < NU; iu++) {
555 final Uid u = uidStats.valueAt(iu);
556 final BatterySipper app = new BatterySipper(BatterySipper.DrainType.APP, u, 0);
557
558 mCpuPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
559 mWakelockPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
560 mMobileRadioPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs,
561 mStatsType);
562 mWifiPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
563 mBluetoothPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs,
564 mStatsType);
565 mSensorPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
566 mCameraPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
567 mFlashlightPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs,
568 mStatsType);
Justin Klaassen4d01eea2018-04-03 23:21:57 -0400569 mMediaPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
Justin Klaassen10d07c82017-09-15 17:58:39 -0400570
571 final double totalPower = app.sumPower();
572 if (DEBUG && totalPower != 0) {
573 Log.d(TAG, String.format("UID %d: total power=%s", u.getUid(),
574 makemAh(totalPower)));
575 }
576
577 // Add the app to the list if it is consuming power.
578 if (totalPower != 0 || u.getUid() == 0) {
579 //
580 // Add the app to the app list, WiFi, Bluetooth, etc, or into "Other Users" list.
581 //
582 final int uid = app.getUid();
583 final int userId = UserHandle.getUserId(uid);
584 if (uid == Process.WIFI_UID) {
585 mWifiSippers.add(app);
586 } else if (uid == Process.BLUETOOTH_UID) {
587 mBluetoothSippers.add(app);
588 } else if (!forAllUsers && asUsers.get(userId) == null
589 && UserHandle.getAppId(uid) >= Process.FIRST_APPLICATION_UID) {
590 // We are told to just report this user's apps as one large entry.
591 List<BatterySipper> list = mUserSippers.get(userId);
592 if (list == null) {
593 list = new ArrayList<>();
594 mUserSippers.put(userId, list);
595 }
596 list.add(app);
597 } else {
598 mUsageList.add(app);
599 }
600
601 if (uid == 0) {
602 osSipper = app;
603 }
604 }
605 }
606
607 if (osSipper != null) {
608 // The device has probably been awake for longer than the screen on
609 // time and application wake lock time would account for. Assign
610 // this remainder to the OS, if possible.
611 mWakelockPowerCalculator.calculateRemaining(osSipper, mStats, mRawRealtimeUs,
612 mRawUptimeUs, mStatsType);
613 osSipper.sumPower();
614 }
615 }
616
617 private void addPhoneUsage() {
618 long phoneOnTimeMs = mStats.getPhoneOnTime(mRawRealtimeUs, mStatsType) / 1000;
619 double phoneOnPower = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE)
620 * phoneOnTimeMs / (60 * 60 * 1000);
621 if (phoneOnPower != 0) {
622 addEntry(BatterySipper.DrainType.PHONE, phoneOnTimeMs, phoneOnPower);
623 }
624 }
625
626 /**
627 * Screen power is the additional power the screen takes while the device is running.
628 */
629 private void addScreenUsage() {
630 double power = 0;
631 long screenOnTimeMs = mStats.getScreenOnTime(mRawRealtimeUs, mStatsType) / 1000;
632 power += screenOnTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_ON);
633 final double screenFullPower =
634 mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_FULL);
635 for (int i = 0; i < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; i++) {
636 double screenBinPower = screenFullPower * (i + 0.5f)
637 / BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS;
638 long brightnessTime = mStats.getScreenBrightnessTime(i, mRawRealtimeUs, mStatsType)
639 / 1000;
640 double p = screenBinPower * brightnessTime;
641 if (DEBUG && p != 0) {
642 Log.d(TAG, "Screen bin #" + i + ": time=" + brightnessTime
643 + " power=" + makemAh(p / (60 * 60 * 1000)));
644 }
645 power += p;
646 }
647 power /= (60 * 60 * 1000); // To hours
648 if (power != 0) {
649 addEntry(BatterySipper.DrainType.SCREEN, screenOnTimeMs, power);
650 }
651 }
652
Justin Klaassen4d01eea2018-04-03 23:21:57 -0400653 /**
654 * Ambient display power is the additional power the screen takes while in ambient display/
655 * screen doze/ always-on display (interchangeable terms) mode. Ambient display power should
656 * be hidden {@link #shouldHideSipper(BatterySipper)}, but should not be included in smearing
657 * {@link #removeHiddenBatterySippers(List)}.
658 */
659 private void addAmbientDisplayUsage() {
660 long ambientDisplayMs = mStats.getScreenDozeTime(mRawRealtimeUs, mStatsType);
661 double power = mPowerProfile.getAveragePower(PowerProfile.POWER_AMBIENT_DISPLAY)
662 * ambientDisplayMs / (60 * 60 * 1000);
663 if (power > 0) {
664 addEntry(DrainType.AMBIENT_DISPLAY, ambientDisplayMs, power);
665 }
666 }
667
Justin Klaassen10d07c82017-09-15 17:58:39 -0400668 private void addRadioUsage() {
669 BatterySipper radio = new BatterySipper(BatterySipper.DrainType.CELL, null, 0);
670 mMobileRadioPowerCalculator.calculateRemaining(radio, mStats, mRawRealtimeUs, mRawUptimeUs,
671 mStatsType);
672 radio.sumPower();
673 if (radio.totalPowerMah > 0) {
674 mUsageList.add(radio);
675 }
676 }
677
678 private void aggregateSippers(BatterySipper bs, List<BatterySipper> from, String tag) {
679 for (int i = 0; i < from.size(); i++) {
680 BatterySipper wbs = from.get(i);
681 if (DEBUG) Log.d(TAG, tag + " adding sipper " + wbs + ": cpu=" + wbs.cpuTimeMs);
682 bs.add(wbs);
683 }
684 bs.computeMobilemspp();
685 bs.sumPower();
686 }
687
688 /**
689 * Calculate the baseline power usage for the device when it is in suspend and idle.
Jeff Davidsona192cc22018-02-08 15:30:06 -0800690 * The device is drawing POWER_CPU_SUSPEND power at its lowest power state.
691 * The device is drawing POWER_CPU_SUSPEND + POWER_CPU_IDLE power when a wakelock is held.
Justin Klaassen10d07c82017-09-15 17:58:39 -0400692 */
693 private void addIdleUsage() {
694 final double suspendPowerMaMs = (mTypeBatteryRealtimeUs / 1000) *
Jeff Davidsona192cc22018-02-08 15:30:06 -0800695 mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_SUSPEND);
Justin Klaassen10d07c82017-09-15 17:58:39 -0400696 final double idlePowerMaMs = (mTypeBatteryUptimeUs / 1000) *
Jeff Davidsona192cc22018-02-08 15:30:06 -0800697 mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_IDLE);
Justin Klaassen10d07c82017-09-15 17:58:39 -0400698 final double totalPowerMah = (suspendPowerMaMs + idlePowerMaMs) / (60 * 60 * 1000);
699 if (DEBUG && totalPowerMah != 0) {
700 Log.d(TAG, "Suspend: time=" + (mTypeBatteryRealtimeUs / 1000)
701 + " power=" + makemAh(suspendPowerMaMs / (60 * 60 * 1000)));
702 Log.d(TAG, "Idle: time=" + (mTypeBatteryUptimeUs / 1000)
703 + " power=" + makemAh(idlePowerMaMs / (60 * 60 * 1000)));
704 }
705
706 if (totalPowerMah != 0) {
707 addEntry(BatterySipper.DrainType.IDLE, mTypeBatteryRealtimeUs / 1000, totalPowerMah);
708 }
709 }
710
711 /**
712 * We do per-app blaming of WiFi activity. If energy info is reported from the controller,
713 * then only the WiFi process gets blamed here since we normalize power calculations and
714 * assign all the power drain to apps. If energy info is not reported, we attribute the
715 * difference between total running time of WiFi for all apps and the actual running time
716 * of WiFi to the WiFi subsystem.
717 */
718 private void addWiFiUsage() {
719 BatterySipper bs = new BatterySipper(DrainType.WIFI, null, 0);
720 mWifiPowerCalculator.calculateRemaining(bs, mStats, mRawRealtimeUs, mRawUptimeUs,
721 mStatsType);
722 aggregateSippers(bs, mWifiSippers, "WIFI");
723 if (bs.totalPowerMah > 0) {
724 mUsageList.add(bs);
725 }
726 }
727
728 /**
729 * Bluetooth usage is not attributed to any apps yet, so the entire blame goes to the
730 * Bluetooth Category.
731 */
732 private void addBluetoothUsage() {
733 BatterySipper bs = new BatterySipper(BatterySipper.DrainType.BLUETOOTH, null, 0);
734 mBluetoothPowerCalculator.calculateRemaining(bs, mStats, mRawRealtimeUs, mRawUptimeUs,
735 mStatsType);
736 aggregateSippers(bs, mBluetoothSippers, "Bluetooth");
737 if (bs.totalPowerMah > 0) {
738 mUsageList.add(bs);
739 }
740 }
741
742 private void addUserUsage() {
743 for (int i = 0; i < mUserSippers.size(); i++) {
744 final int userId = mUserSippers.keyAt(i);
745 BatterySipper bs = new BatterySipper(DrainType.USER, null, 0);
746 bs.userId = userId;
747 aggregateSippers(bs, mUserSippers.valueAt(i), "User");
748 mUsageList.add(bs);
749 }
750 }
751
752 private void addMemoryUsage() {
753 BatterySipper memory = new BatterySipper(DrainType.MEMORY, null, 0);
754 mMemoryPowerCalculator.calculateRemaining(memory, mStats, mRawRealtimeUs, mRawUptimeUs,
755 mStatsType);
756 memory.sumPower();
757 if (memory.totalPowerMah > 0) {
758 mUsageList.add(memory);
759 }
760 }
761
762 private void processMiscUsage() {
763 addUserUsage();
764 addPhoneUsage();
765 addScreenUsage();
Justin Klaassen4d01eea2018-04-03 23:21:57 -0400766 addAmbientDisplayUsage();
Justin Klaassen10d07c82017-09-15 17:58:39 -0400767 addWiFiUsage();
768 addBluetoothUsage();
769 addMemoryUsage();
770 addIdleUsage(); // Not including cellular idle power
771 // Don't compute radio usage if it's a wifi-only device
772 if (!mWifiOnly) {
773 addRadioUsage();
774 }
775 }
776
777 private BatterySipper addEntry(DrainType drainType, long time, double power) {
778 BatterySipper bs = new BatterySipper(drainType, null, 0);
779 bs.usagePowerMah = power;
780 bs.usageTimeMs = time;
781 bs.sumPower();
782 mUsageList.add(bs);
783 return bs;
784 }
785
786 public List<BatterySipper> getUsageList() {
787 return mUsageList;
788 }
789
790 public List<BatterySipper> getMobilemsppList() {
791 return mMobilemsppList;
792 }
793
794 public long getStatsPeriod() {
795 return mStatsPeriod;
796 }
797
798 public int getStatsType() {
799 return mStatsType;
800 }
801
802 public double getMaxPower() {
803 return mMaxPower;
804 }
805
806 public double getMaxRealPower() {
807 return mMaxRealPower;
808 }
809
810 public double getTotalPower() {
811 return mTotalPower;
812 }
813
814 public double getComputedPower() {
815 return mComputedPower;
816 }
817
818 public double getMinDrainedPower() {
819 return mMinDrainedPower;
820 }
821
822 public double getMaxDrainedPower() {
823 return mMaxDrainedPower;
824 }
825
826 public static byte[] readFully(FileInputStream stream) throws java.io.IOException {
827 return readFully(stream, stream.available());
828 }
829
830 public static byte[] readFully(FileInputStream stream, int avail) throws java.io.IOException {
831 int pos = 0;
832 byte[] data = new byte[avail];
833 while (true) {
834 int amt = stream.read(data, pos, data.length - pos);
835 //Log.i("foo", "Read " + amt + " bytes at " + pos
836 // + " of avail " + data.length);
837 if (amt <= 0) {
838 //Log.i("foo", "**** FINISHED READING: pos=" + pos
839 // + " len=" + data.length);
840 return data;
841 }
842 pos += amt;
843 avail = stream.available();
844 if (avail > data.length - pos) {
845 byte[] newData = new byte[pos + avail];
846 System.arraycopy(data, 0, newData, 0, pos);
847 data = newData;
848 }
849 }
850 }
851
852 /**
853 * Mark the {@link BatterySipper} that we should hide and smear the screen usage based on
854 * foreground activity time.
855 *
856 * @param sippers sipper list that need to check and remove
857 * @return the total power of the hidden items of {@link BatterySipper}
858 * for proportional smearing
859 */
860 public double removeHiddenBatterySippers(List<BatterySipper> sippers) {
861 double proportionalSmearPowerMah = 0;
862 BatterySipper screenSipper = null;
863 for (int i = sippers.size() - 1; i >= 0; i--) {
864 final BatterySipper sipper = sippers.get(i);
865 sipper.shouldHide = shouldHideSipper(sipper);
866 if (sipper.shouldHide) {
Justin Klaassen4d01eea2018-04-03 23:21:57 -0400867 if (sipper.drainType != DrainType.OVERCOUNTED
868 && sipper.drainType != DrainType.SCREEN
869 && sipper.drainType != DrainType.AMBIENT_DISPLAY
870 && sipper.drainType != DrainType.UNACCOUNTED
871 && sipper.drainType != DrainType.BLUETOOTH
872 && sipper.drainType != DrainType.WIFI
873 && sipper.drainType != DrainType.IDLE) {
Justin Klaassen10d07c82017-09-15 17:58:39 -0400874 // Don't add it if it is overcounted, unaccounted or screen
875 proportionalSmearPowerMah += sipper.totalPowerMah;
876 }
877 }
878
879 if (sipper.drainType == BatterySipper.DrainType.SCREEN) {
880 screenSipper = sipper;
881 }
882 }
883
884 smearScreenBatterySipper(sippers, screenSipper);
885
886 return proportionalSmearPowerMah;
887 }
888
889 /**
890 * Smear the screen on power usage among {@code sippers}, based on ratio of foreground activity
891 * time.
892 */
893 public void smearScreenBatterySipper(List<BatterySipper> sippers, BatterySipper screenSipper) {
894 long totalActivityTimeMs = 0;
895 final SparseLongArray activityTimeArray = new SparseLongArray();
896 for (int i = 0, size = sippers.size(); i < size; i++) {
897 final BatteryStats.Uid uid = sippers.get(i).uidObj;
898 if (uid != null) {
899 final long timeMs = getProcessForegroundTimeMs(uid,
900 BatteryStats.STATS_SINCE_CHARGED);
901 activityTimeArray.put(uid.getUid(), timeMs);
902 totalActivityTimeMs += timeMs;
903 }
904 }
905
906 if (screenSipper != null && totalActivityTimeMs >= 10 * DateUtils.MINUTE_IN_MILLIS) {
907 final double screenPowerMah = screenSipper.totalPowerMah;
908 for (int i = 0, size = sippers.size(); i < size; i++) {
909 final BatterySipper sipper = sippers.get(i);
910 sipper.screenPowerMah = screenPowerMah * activityTimeArray.get(sipper.getUid(), 0)
911 / totalActivityTimeMs;
912 }
913 }
914 }
915
916 /**
917 * Check whether we should hide the battery sipper.
918 */
919 public boolean shouldHideSipper(BatterySipper sipper) {
Justin Klaassen4d01eea2018-04-03 23:21:57 -0400920 final DrainType drainType = sipper.drainType;
Justin Klaassen10d07c82017-09-15 17:58:39 -0400921
Justin Klaassen4d01eea2018-04-03 23:21:57 -0400922 return drainType == DrainType.IDLE
923 || drainType == DrainType.CELL
924 || drainType == DrainType.SCREEN
925 || drainType == DrainType.AMBIENT_DISPLAY
926 || drainType == DrainType.UNACCOUNTED
927 || drainType == DrainType.OVERCOUNTED
Justin Klaassen10d07c82017-09-15 17:58:39 -0400928 || isTypeService(sipper)
929 || isTypeSystem(sipper);
930 }
931
932 /**
933 * Check whether {@code sipper} is type service
934 */
935 public boolean isTypeService(BatterySipper sipper) {
936 final String[] packages = mPackageManager.getPackagesForUid(sipper.getUid());
937 if (packages == null) {
938 return false;
939 }
940
941 for (String packageName : packages) {
942 if (ArrayUtils.contains(mServicepackageArray, packageName)) {
943 return true;
944 }
945 }
946
947 return false;
948 }
949
950 /**
951 * Check whether {@code sipper} is type system
952 */
953 public boolean isTypeSystem(BatterySipper sipper) {
954 final int uid = sipper.uidObj == null ? -1 : sipper.getUid();
955 sipper.mPackages = mPackageManager.getPackagesForUid(uid);
956 // Classify all the sippers to type system if the range of uid is 0...FIRST_APPLICATION_UID
957 if (uid >= Process.ROOT_UID && uid < Process.FIRST_APPLICATION_UID) {
958 return true;
959 } else if (sipper.mPackages != null) {
960 for (final String packageName : sipper.mPackages) {
961 if (ArrayUtils.contains(mSystemPackageArray, packageName)) {
962 return true;
963 }
964 }
965 }
966
967 return false;
968 }
969
970 public long convertUsToMs(long timeUs) {
971 return timeUs / 1000;
972 }
973
974 public long convertMsToUs(long timeMs) {
975 return timeMs * 1000;
976 }
977
978 @VisibleForTesting
979 public long getForegroundActivityTotalTimeUs(BatteryStats.Uid uid, long rawRealtimeUs) {
980 final BatteryStats.Timer timer = uid.getForegroundActivityTimer();
981 if (timer != null) {
982 return timer.getTotalTimeLocked(rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED);
983 }
984
985 return 0;
986 }
987
988 @VisibleForTesting
989 public long getProcessForegroundTimeMs(BatteryStats.Uid uid, int which) {
990 final long rawRealTimeUs = convertMsToUs(SystemClock.elapsedRealtime());
991 final int foregroundTypes[] = {BatteryStats.Uid.PROCESS_STATE_TOP};
992
993 long timeUs = 0;
994 for (int type : foregroundTypes) {
995 final long localTime = uid.getProcessStateTime(type, rawRealTimeUs, which);
996 timeUs += localTime;
997 }
998
999 // Return the min value of STATE_TOP time and foreground activity time, since both of these
1000 // time have some errors.
1001 return convertUsToMs(
1002 Math.min(timeUs, getForegroundActivityTotalTimeUs(uid, rawRealTimeUs)));
1003 }
1004
1005 @VisibleForTesting
1006 public void setPackageManager(PackageManager packageManager) {
1007 mPackageManager = packageManager;
1008 }
1009
1010 @VisibleForTesting
1011 public void setSystemPackageArray(String[] array) {
1012 mSystemPackageArray = array;
1013 }
1014
1015 @VisibleForTesting
1016 public void setServicePackageArray(String[] array) {
1017 mServicepackageArray = array;
1018 }
1019
1020 private void load() {
1021 if (mBatteryInfo == null) {
1022 return;
1023 }
1024 mStats = getStats(mBatteryInfo);
1025 if (mCollectBatteryBroadcast) {
1026 mBatteryBroadcast = mContext.registerReceiver(null,
1027 new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
1028 }
1029 }
1030
1031 private static BatteryStatsImpl getStats(IBatteryStats service) {
1032 try {
1033 ParcelFileDescriptor pfd = service.getStatisticsStream();
1034 if (pfd != null) {
1035 try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) {
1036 byte[] data = readFully(fis, MemoryFile.getSize(pfd.getFileDescriptor()));
1037 Parcel parcel = Parcel.obtain();
1038 parcel.unmarshall(data, 0, data.length);
1039 parcel.setDataPosition(0);
1040 BatteryStatsImpl stats = com.android.internal.os.BatteryStatsImpl.CREATOR
1041 .createFromParcel(parcel);
1042 return stats;
1043 } catch (IOException e) {
1044 Log.w(TAG, "Unable to read statistics stream", e);
1045 }
1046 }
1047 } catch (RemoteException e) {
1048 Log.w(TAG, "RemoteException:", e);
1049 }
1050 return new BatteryStatsImpl();
1051 }
1052}