2 * Copyright (C) 2017 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.android.settings.fuelgauge;
18 import android.content.Context;
19 import android.content.pm.PackageManager;
20 import android.os.BatteryStats;
21 import android.os.SystemClock;
22 import android.support.annotation.IntDef;
23 import android.support.annotation.Nullable;
24 import android.support.annotation.StringRes;
25 import android.support.annotation.VisibleForTesting;
26 import android.text.format.DateUtils;
27 import android.util.Log;
28 import android.util.SparseLongArray;
30 import com.android.internal.os.BatterySipper;
31 import com.android.internal.os.BatteryStatsHelper;
32 import com.android.internal.util.ArrayUtils;
33 import com.android.settings.R;
34 import com.android.settings.fuelgauge.anomaly.Anomaly;
35 import com.android.settings.overlay.FeatureFactory;
37 import java.lang.annotation.Retention;
38 import java.lang.annotation.RetentionPolicy;
39 import java.util.Collections;
40 import java.util.Comparator;
41 import java.util.List;
44 * Utils for battery operation
46 public class BatteryUtils {
47 public static final int UID_NULL = -1;
49 @Retention(RetentionPolicy.SOURCE)
50 @IntDef({StatusType.FOREGROUND,
51 StatusType.BACKGROUND,
54 public @interface StatusType {
60 private static final String TAG = "BatteryUtils";
62 private static final int MIN_POWER_THRESHOLD_MILLI_AMP = 5;
63 private static final int SECONDS_IN_HOUR = 60 * 60;
64 private static BatteryUtils sInstance;
66 private PackageManager mPackageManager;
68 PowerUsageFeatureProvider mPowerUsageFeatureProvider;
70 public static BatteryUtils getInstance(Context context) {
71 if (sInstance == null || sInstance.isDataCorrupted()) {
72 sInstance = new BatteryUtils(context);
78 BatteryUtils(Context context) {
79 mPackageManager = context.getPackageManager();
80 mPowerUsageFeatureProvider = FeatureFactory.getFactory(
81 context).getPowerUsageFeatureProvider(context);
84 public long getProcessTimeMs(@StatusType int type, @Nullable BatteryStats.Uid uid,
91 case StatusType.FOREGROUND:
92 return getProcessForegroundTimeMs(uid, which);
93 case StatusType.BACKGROUND:
94 return getProcessBackgroundTimeMs(uid, which);
96 return getProcessForegroundTimeMs(uid, which)
97 + getProcessBackgroundTimeMs(uid, which);
102 private long getProcessBackgroundTimeMs(BatteryStats.Uid uid, int which) {
103 final long rawRealTimeUs = convertMsToUs(SystemClock.elapsedRealtime());
104 final long timeUs = uid.getProcessStateTime(
105 BatteryStats.Uid.PROCESS_STATE_BACKGROUND, rawRealTimeUs, which);
107 Log.v(TAG, "package: " + mPackageManager.getNameForUid(uid.getUid()));
108 Log.v(TAG, "background time(us): " + timeUs);
109 return convertUsToMs(timeUs);
112 private long getProcessForegroundTimeMs(BatteryStats.Uid uid, int which) {
113 final long rawRealTimeUs = convertMsToUs(SystemClock.elapsedRealtime());
114 final int foregroundTypes[] = {BatteryStats.Uid.PROCESS_STATE_TOP};
115 Log.v(TAG, "package: " + mPackageManager.getNameForUid(uid.getUid()));
118 for (int type : foregroundTypes) {
119 final long localTime = uid.getProcessStateTime(type, rawRealTimeUs, which);
120 Log.v(TAG, "type: " + type + " time(us): " + localTime);
123 Log.v(TAG, "foreground time(us): " + timeUs);
125 return convertUsToMs(timeUs);
129 * Remove the {@link BatterySipper} that we should hide and smear the screen usage based on
130 * foreground activity time.
132 * @param sippers sipper list that need to check and remove
133 * @return the total power of the hidden items of {@link BatterySipper}
134 * for proportional smearing
136 public double removeHiddenBatterySippers(List<BatterySipper> sippers) {
137 double proportionalSmearPowerMah = 0;
138 BatterySipper screenSipper = null;
139 for (int i = sippers.size() - 1; i >= 0; i--) {
140 final BatterySipper sipper = sippers.get(i);
141 if (shouldHideSipper(sipper)) {
143 if (sipper.drainType != BatterySipper.DrainType.OVERCOUNTED
144 && sipper.drainType != BatterySipper.DrainType.SCREEN
145 && sipper.drainType != BatterySipper.DrainType.UNACCOUNTED
146 && sipper.drainType != BatterySipper.DrainType.BLUETOOTH
147 && sipper.drainType != BatterySipper.DrainType.WIFI
148 && sipper.drainType != BatterySipper.DrainType.IDLE) {
149 // Don't add it if it is overcounted, unaccounted, wifi, bluetooth, or screen
150 proportionalSmearPowerMah += sipper.totalPowerMah;
154 if (sipper.drainType == BatterySipper.DrainType.SCREEN) {
155 screenSipper = sipper;
159 smearScreenBatterySipper(sippers, screenSipper);
161 return proportionalSmearPowerMah;
165 * Smear the screen on power usage among {@code sippers}, based on ratio of foreground activity
169 void smearScreenBatterySipper(List<BatterySipper> sippers, BatterySipper screenSipper) {
170 final long rawRealtimeMs = SystemClock.elapsedRealtime();
171 long totalActivityTimeMs = 0;
172 final SparseLongArray activityTimeArray = new SparseLongArray();
173 for (int i = 0, size = sippers.size(); i < size; i++) {
174 final BatteryStats.Uid uid = sippers.get(i).uidObj;
176 final long timeMs = Math.min(getForegroundActivityTotalTimeMs(uid, rawRealtimeMs),
177 getProcessTimeMs(StatusType.FOREGROUND, uid,
178 BatteryStats.STATS_SINCE_CHARGED));
179 activityTimeArray.put(uid.getUid(), timeMs);
180 totalActivityTimeMs += timeMs;
184 if (totalActivityTimeMs >= 10 * DateUtils.MINUTE_IN_MILLIS) {
185 final double screenPowerMah = screenSipper.totalPowerMah;
186 for (int i = 0, size = sippers.size(); i < size; i++) {
187 final BatterySipper sipper = sippers.get(i);
188 sipper.totalPowerMah += screenPowerMah * activityTimeArray.get(sipper.getUid(), 0)
189 / totalActivityTimeMs;
195 * Check whether we should hide the battery sipper.
197 public boolean shouldHideSipper(BatterySipper sipper) {
198 final BatterySipper.DrainType drainType = sipper.drainType;
200 return drainType == BatterySipper.DrainType.IDLE
201 || drainType == BatterySipper.DrainType.CELL
202 || drainType == BatterySipper.DrainType.SCREEN
203 || drainType == BatterySipper.DrainType.UNACCOUNTED
204 || drainType == BatterySipper.DrainType.OVERCOUNTED
205 || drainType == BatterySipper.DrainType.BLUETOOTH
206 || drainType == BatterySipper.DrainType.WIFI
207 || (sipper.totalPowerMah * SECONDS_IN_HOUR) < MIN_POWER_THRESHOLD_MILLI_AMP
208 || mPowerUsageFeatureProvider.isTypeService(sipper)
209 || mPowerUsageFeatureProvider.isTypeSystem(sipper);
213 * Calculate the power usage percentage for an app
215 * @param powerUsageMah power used by the app
216 * @param totalPowerMah total power used in the system
217 * @param hiddenPowerMah power used by no-actionable app that we want to hide, i.e. Screen,
219 * @param dischargeAmount The discharge amount calculated by {@link BatteryStats}
220 * @return A percentage value scaled by {@paramref dischargeAmount}
221 * @see BatteryStats#getDischargeAmount(int)
223 public double calculateBatteryPercent(double powerUsageMah, double totalPowerMah,
224 double hiddenPowerMah, int dischargeAmount) {
225 if (totalPowerMah == 0) {
229 return (powerUsageMah / (totalPowerMah - hiddenPowerMah)) * dischargeAmount;
233 * Calculate the whole running time in the state {@code statsType}
235 * @param batteryStatsHelper utility class that contains the data
236 * @param statsType state that we want to calculate the time for
237 * @return the running time in millis
239 public long calculateRunningTimeBasedOnStatsType(BatteryStatsHelper batteryStatsHelper,
241 final long elapsedRealtimeUs = convertMsToUs(SystemClock.elapsedRealtime());
242 // Return the battery time (millisecond) on status mStatsType
243 return convertUsToMs(
244 batteryStatsHelper.getStats().computeBatteryRealtime(elapsedRealtimeUs, statsType));
249 * Find the package name for a {@link android.os.BatteryStats.Uid}
251 * @param uid id to get the package name
252 * @return the package name. If there are multiple packages related to
253 * given id, return the first one. Or return null if there are no known
254 * packages with the given id
255 * @see PackageManager#getPackagesForUid(int)
257 public String getPackageName(int uid) {
258 final String[] packageNames = mPackageManager.getPackagesForUid(uid);
260 return ArrayUtils.isEmpty(packageNames) ? null : packageNames[0];
264 * Sort the {@code usageList} based on {@link BatterySipper#totalPowerMah}
266 public void sortUsageList(List<BatterySipper> usageList) {
267 Collections.sort(usageList, new Comparator<BatterySipper>() {
269 public int compare(BatterySipper a, BatterySipper b) {
270 return Double.compare(b.totalPowerMah, a.totalPowerMah);
276 * Calculate the time since last full charge, including the device off time
278 * @param batteryStatsHelper utility class that contains the data
279 * @param currentTimeMs current wall time
280 * @return time in millis
282 public long calculateLastFullChargeTime(BatteryStatsHelper batteryStatsHelper,
283 long currentTimeMs) {
284 return currentTimeMs - batteryStatsHelper.getStats().getStartClockTime();
289 * Find package uid from package name
291 * @param packageName used to find the uid
292 * @return uid for packageName, or {@link #UID_NULL} if exception happens or
293 * {@code packageName} is null
295 public int getPackageUid(String packageName) {
297 return packageName == null ? UID_NULL : mPackageManager.getPackageUid(packageName,
298 PackageManager.GET_META_DATA);
299 } catch (PackageManager.NameNotFoundException e) {
305 public int getSummaryResIdFromAnomalyType(@Anomaly.AnomalyType int type) {
307 case Anomaly.AnomalyType.WAKE_LOCK:
308 return R.string.battery_abnormal_wakelock_summary;
309 case Anomaly.AnomalyType.WAKEUP_ALARM:
310 return R.string.battery_abnormal_wakeup_alarm_summary;
311 case Anomaly.AnomalyType.BLUETOOTH_SCAN:
312 return R.string.battery_abnormal_location_summary;
314 throw new IllegalArgumentException("Incorrect anomaly type: " + type);
318 public long convertUsToMs(long timeUs) {
319 return timeUs / 1000;
322 public long convertMsToUs(long timeMs) {
323 return timeMs * 1000;
326 private boolean isDataCorrupted() {
327 return mPackageManager == null;
331 long getForegroundActivityTotalTimeMs(BatteryStats.Uid uid, long rawRealtimeMs) {
332 final BatteryStats.Timer timer = uid.getForegroundActivityTimer();
334 return convertUsToMs(timer.getTotalTimeLocked(convertMsToUs(rawRealtimeMs),
335 BatteryStats.STATS_SINCE_CHARGED));