• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1  /*
2   * Copyright (C) 2017 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  package com.android.settings.fuelgauge;
17  
18  import android.app.AppOpsManager;
19  import android.content.Context;
20  import android.content.pm.ApplicationInfo;
21  import android.content.pm.PackageManager;
22  import android.os.BatteryStats;
23  import android.os.Bundle;
24  import android.os.Build;
25  import android.os.SystemClock;
26  import android.os.UserManager;
27  import android.support.annotation.IntDef;
28  import android.support.annotation.Nullable;
29  import android.support.annotation.StringRes;
30  import android.support.annotation.VisibleForTesting;
31  import android.text.format.DateUtils;
32  import android.util.Log;
33  import android.util.SparseLongArray;
34  
35  import com.android.internal.os.BatterySipper;
36  import com.android.internal.os.BatteryStatsHelper;
37  import com.android.internal.util.ArrayUtils;
38  import com.android.settings.R;
39  import com.android.settings.fuelgauge.anomaly.Anomaly;
40  import com.android.settings.overlay.FeatureFactory;
41  
42  import java.lang.annotation.Retention;
43  import java.lang.annotation.RetentionPolicy;
44  import java.util.Collections;
45  import java.util.Comparator;
46  import java.util.List;
47  
48  /**
49   * Utils for battery operation
50   */
51  public class BatteryUtils {
52      public static final int UID_NULL = -1;
53      public static final int SDK_NULL = -1;
54  
55      @Retention(RetentionPolicy.SOURCE)
56      @IntDef({StatusType.SCREEN_USAGE,
57              StatusType.FOREGROUND,
58              StatusType.BACKGROUND,
59              StatusType.ALL
60      })
61      public @interface StatusType {
62          int SCREEN_USAGE = 0;
63          int FOREGROUND = 1;
64          int BACKGROUND = 2;
65          int ALL = 3;
66      }
67  
68      private static final String TAG = "BatteryUtils";
69  
70      private static final int MIN_POWER_THRESHOLD_MILLI_AMP = 5;
71      private static final int SECONDS_IN_HOUR = 60 * 60;
72      private static BatteryUtils sInstance;
73  
74      private PackageManager mPackageManager;
75      private AppOpsManager mAppOpsManager;
76      @VisibleForTesting
77      PowerUsageFeatureProvider mPowerUsageFeatureProvider;
78  
getInstance(Context context)79      public static BatteryUtils getInstance(Context context) {
80          if (sInstance == null || sInstance.isDataCorrupted()) {
81              sInstance = new BatteryUtils(context);
82          }
83          return sInstance;
84      }
85  
86      @VisibleForTesting
BatteryUtils(Context context)87      BatteryUtils(Context context) {
88          mPackageManager = context.getPackageManager();
89          mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
90          mPowerUsageFeatureProvider = FeatureFactory.getFactory(
91                  context).getPowerUsageFeatureProvider(context);
92      }
93  
getProcessTimeMs(@tatusType int type, @Nullable BatteryStats.Uid uid, int which)94      public long getProcessTimeMs(@StatusType int type, @Nullable BatteryStats.Uid uid,
95              int which) {
96          if (uid == null) {
97              return 0;
98          }
99  
100          switch (type) {
101              case StatusType.SCREEN_USAGE:
102                  return getScreenUsageTimeMs(uid, which);
103              case StatusType.FOREGROUND:
104                  return getProcessForegroundTimeMs(uid, which);
105              case StatusType.BACKGROUND:
106                  return getProcessBackgroundTimeMs(uid, which);
107              case StatusType.ALL:
108                  return getProcessForegroundTimeMs(uid, which)
109                          + getProcessBackgroundTimeMs(uid, which);
110          }
111          return 0;
112      }
113  
getScreenUsageTimeMs(BatteryStats.Uid uid, int which, long rawRealTimeUs)114      private long getScreenUsageTimeMs(BatteryStats.Uid uid, int which, long rawRealTimeUs) {
115          final int foregroundTypes[] = {BatteryStats.Uid.PROCESS_STATE_TOP};
116          Log.v(TAG, "package: " + mPackageManager.getNameForUid(uid.getUid()));
117  
118          long timeUs = 0;
119          for (int type : foregroundTypes) {
120              final long localTime = uid.getProcessStateTime(type, rawRealTimeUs, which);
121              Log.v(TAG, "type: " + type + " time(us): " + localTime);
122              timeUs += localTime;
123          }
124          Log.v(TAG, "foreground time(us): " + timeUs);
125  
126          // Return the min value of STATE_TOP time and foreground activity time, since both of these
127          // time have some errors
128          return convertUsToMs(
129                  Math.min(timeUs, getForegroundActivityTotalTimeUs(uid, rawRealTimeUs)));
130      }
131  
getScreenUsageTimeMs(BatteryStats.Uid uid, int which)132      private long getScreenUsageTimeMs(BatteryStats.Uid uid, int which) {
133          final long rawRealTimeUs = convertMsToUs(SystemClock.elapsedRealtime());
134          return getScreenUsageTimeMs(uid, which, rawRealTimeUs);
135      }
136  
getProcessBackgroundTimeMs(BatteryStats.Uid uid, int which)137      private long getProcessBackgroundTimeMs(BatteryStats.Uid uid, int which) {
138          final long rawRealTimeUs = convertMsToUs(SystemClock.elapsedRealtime());
139          final long timeUs = uid.getProcessStateTime(
140                  BatteryStats.Uid.PROCESS_STATE_BACKGROUND, rawRealTimeUs, which);
141  
142          Log.v(TAG, "package: " + mPackageManager.getNameForUid(uid.getUid()));
143          Log.v(TAG, "background time(us): " + timeUs);
144          return convertUsToMs(timeUs);
145      }
146  
getProcessForegroundTimeMs(BatteryStats.Uid uid, int which)147      private long getProcessForegroundTimeMs(BatteryStats.Uid uid, int which) {
148          final long rawRealTimeUs = convertMsToUs(SystemClock.elapsedRealtime());
149          return getScreenUsageTimeMs(uid, which, rawRealTimeUs)
150                  + convertUsToMs(getForegroundServiceTotalTimeUs(uid, rawRealTimeUs));
151      }
152  
153      /**
154       * Remove the {@link BatterySipper} that we should hide and smear the screen usage based on
155       * foreground activity time.
156       *
157       * @param sippers sipper list that need to check and remove
158       * @return the total power of the hidden items of {@link BatterySipper}
159       * for proportional smearing
160       */
removeHiddenBatterySippers(List<BatterySipper> sippers)161      public double removeHiddenBatterySippers(List<BatterySipper> sippers) {
162          double proportionalSmearPowerMah = 0;
163          BatterySipper screenSipper = null;
164          for (int i = sippers.size() - 1; i >= 0; i--) {
165              final BatterySipper sipper = sippers.get(i);
166              if (shouldHideSipper(sipper)) {
167                  sippers.remove(i);
168                  if (sipper.drainType != BatterySipper.DrainType.OVERCOUNTED
169                          && sipper.drainType != BatterySipper.DrainType.SCREEN
170                          && sipper.drainType != BatterySipper.DrainType.UNACCOUNTED
171                          && sipper.drainType != BatterySipper.DrainType.BLUETOOTH
172                          && sipper.drainType != BatterySipper.DrainType.WIFI
173                          && sipper.drainType != BatterySipper.DrainType.IDLE) {
174                      // Don't add it if it is overcounted, unaccounted, wifi, bluetooth, or screen
175                      proportionalSmearPowerMah += sipper.totalPowerMah;
176                  }
177              }
178  
179              if (sipper.drainType == BatterySipper.DrainType.SCREEN) {
180                  screenSipper = sipper;
181              }
182          }
183  
184          smearScreenBatterySipper(sippers, screenSipper);
185  
186          return proportionalSmearPowerMah;
187      }
188  
189      /**
190       * Smear the screen on power usage among {@code sippers}, based on ratio of foreground activity
191       * time.
192       */
193      @VisibleForTesting
smearScreenBatterySipper(List<BatterySipper> sippers, BatterySipper screenSipper)194      void smearScreenBatterySipper(List<BatterySipper> sippers, BatterySipper screenSipper) {
195          long totalActivityTimeMs = 0;
196          final SparseLongArray activityTimeArray = new SparseLongArray();
197          for (int i = 0, size = sippers.size(); i < size; i++) {
198              final BatteryStats.Uid uid = sippers.get(i).uidObj;
199              if (uid != null) {
200                  final long timeMs = getProcessTimeMs(StatusType.SCREEN_USAGE, uid,
201                          BatteryStats.STATS_SINCE_CHARGED);
202                  activityTimeArray.put(uid.getUid(), timeMs);
203                  totalActivityTimeMs += timeMs;
204              }
205          }
206  
207          if (totalActivityTimeMs >= 10 * DateUtils.MINUTE_IN_MILLIS) {
208              final double screenPowerMah = screenSipper.totalPowerMah;
209              for (int i = 0, size = sippers.size(); i < size; i++) {
210                  final BatterySipper sipper = sippers.get(i);
211                  sipper.totalPowerMah += screenPowerMah * activityTimeArray.get(sipper.getUid(), 0)
212                          / totalActivityTimeMs;
213              }
214          }
215      }
216  
217      /**
218       * Check whether we should hide the battery sipper.
219       */
shouldHideSipper(BatterySipper sipper)220      public boolean shouldHideSipper(BatterySipper sipper) {
221          final BatterySipper.DrainType drainType = sipper.drainType;
222  
223          return drainType == BatterySipper.DrainType.IDLE
224                  || drainType == BatterySipper.DrainType.CELL
225                  || drainType == BatterySipper.DrainType.SCREEN
226                  || drainType == BatterySipper.DrainType.UNACCOUNTED
227                  || drainType == BatterySipper.DrainType.OVERCOUNTED
228                  || drainType == BatterySipper.DrainType.BLUETOOTH
229                  || drainType == BatterySipper.DrainType.WIFI
230                  || (sipper.totalPowerMah * SECONDS_IN_HOUR) < MIN_POWER_THRESHOLD_MILLI_AMP
231                  || mPowerUsageFeatureProvider.isTypeService(sipper)
232                  || mPowerUsageFeatureProvider.isTypeSystem(sipper);
233      }
234  
235      /**
236       * Calculate the power usage percentage for an app
237       *
238       * @param powerUsageMah   power used by the app
239       * @param totalPowerMah   total power used in the system
240       * @param hiddenPowerMah  power used by no-actionable app that we want to hide, i.e. Screen,
241       *                        Android OS.
242       * @param dischargeAmount The discharge amount calculated by {@link BatteryStats}
243       * @return A percentage value scaled by {@paramref dischargeAmount}
244       * @see BatteryStats#getDischargeAmount(int)
245       */
calculateBatteryPercent(double powerUsageMah, double totalPowerMah, double hiddenPowerMah, int dischargeAmount)246      public double calculateBatteryPercent(double powerUsageMah, double totalPowerMah,
247              double hiddenPowerMah, int dischargeAmount) {
248          if (totalPowerMah == 0) {
249              return 0;
250          }
251  
252          return (powerUsageMah / (totalPowerMah - hiddenPowerMah)) * dischargeAmount;
253      }
254  
255      /**
256       * Calculate the whole running time in the state {@code statsType}
257       *
258       * @param batteryStatsHelper utility class that contains the data
259       * @param statsType          state that we want to calculate the time for
260       * @return the running time in millis
261       */
calculateRunningTimeBasedOnStatsType(BatteryStatsHelper batteryStatsHelper, int statsType)262      public long calculateRunningTimeBasedOnStatsType(BatteryStatsHelper batteryStatsHelper,
263              int statsType) {
264          final long elapsedRealtimeUs = convertMsToUs(SystemClock.elapsedRealtime());
265          // Return the battery time (millisecond) on status mStatsType
266          return convertUsToMs(
267                  batteryStatsHelper.getStats().computeBatteryRealtime(elapsedRealtimeUs, statsType));
268  
269      }
270  
271      /**
272       * Find the package name for a {@link android.os.BatteryStats.Uid}
273       *
274       * @param uid id to get the package name
275       * @return the package name. If there are multiple packages related to
276       * given id, return the first one. Or return null if there are no known
277       * packages with the given id
278       * @see PackageManager#getPackagesForUid(int)
279       */
getPackageName(int uid)280      public String getPackageName(int uid) {
281          final String[] packageNames = mPackageManager.getPackagesForUid(uid);
282  
283          return ArrayUtils.isEmpty(packageNames) ? null : packageNames[0];
284      }
285  
286      /**
287       * Find the targetSdkVersion for package with name {@code packageName}
288       *
289       * @return the targetSdkVersion, or {@link #SDK_NULL} if {@code packageName} doesn't exist
290       */
getTargetSdkVersion(final String packageName)291      public int getTargetSdkVersion(final String packageName) {
292          try {
293              ApplicationInfo info = mPackageManager.getApplicationInfo(packageName,
294                      PackageManager.GET_META_DATA);
295  
296              return info.targetSdkVersion;
297          } catch (PackageManager.NameNotFoundException e) {
298              Log.e(TAG, "Cannot find package: " + packageName, e);
299          }
300  
301          return SDK_NULL;
302      }
303  
304      /**
305       * Check whether background restriction is enabled
306       */
isBackgroundRestrictionEnabled(final int targetSdkVersion, final int uid, final String packageName)307      public boolean isBackgroundRestrictionEnabled(final int targetSdkVersion, final int uid,
308              final String packageName) {
309          if (targetSdkVersion >= Build.VERSION_CODES.O) {
310              return true;
311          }
312          final int mode = mAppOpsManager
313                  .checkOpNoThrow(AppOpsManager.OP_RUN_IN_BACKGROUND, uid, packageName);
314          return mode == AppOpsManager.MODE_IGNORED || mode == AppOpsManager.MODE_ERRORED;
315      }
316  
317      /**
318       * Sort the {@code usageList} based on {@link BatterySipper#totalPowerMah}
319       */
sortUsageList(List<BatterySipper> usageList)320      public void sortUsageList(List<BatterySipper> usageList) {
321          Collections.sort(usageList, new Comparator<BatterySipper>() {
322              @Override
323              public int compare(BatterySipper a, BatterySipper b) {
324                  return Double.compare(b.totalPowerMah, a.totalPowerMah);
325              }
326          });
327      }
328  
329      /**
330       * Calculate the time since last full charge, including the device off time
331       *
332       * @param batteryStatsHelper utility class that contains the data
333       * @param currentTimeMs      current wall time
334       * @return time in millis
335       */
calculateLastFullChargeTime(BatteryStatsHelper batteryStatsHelper, long currentTimeMs)336      public long calculateLastFullChargeTime(BatteryStatsHelper batteryStatsHelper,
337              long currentTimeMs) {
338          return currentTimeMs - batteryStatsHelper.getStats().getStartClockTime();
339  
340      }
341  
logRuntime(String tag, String message, long startTime)342      public static void logRuntime(String tag, String message, long startTime) {
343          Log.d(tag, message + ": " + (System.currentTimeMillis() - startTime) + "ms");
344      }
345  
346      /**
347       * Find package uid from package name
348       *
349       * @param packageName used to find the uid
350       * @return uid for packageName, or {@link #UID_NULL} if exception happens or
351       * {@code packageName} is null
352       */
getPackageUid(String packageName)353      public int getPackageUid(String packageName) {
354          try {
355              return packageName == null ? UID_NULL : mPackageManager.getPackageUid(packageName,
356                      PackageManager.GET_META_DATA);
357          } catch (PackageManager.NameNotFoundException e) {
358              return UID_NULL;
359          }
360      }
361  
362      @StringRes
getSummaryResIdFromAnomalyType(@nomaly.AnomalyType int type)363      public int getSummaryResIdFromAnomalyType(@Anomaly.AnomalyType int type) {
364          switch (type) {
365              case Anomaly.AnomalyType.WAKE_LOCK:
366                  return R.string.battery_abnormal_wakelock_summary;
367              case Anomaly.AnomalyType.WAKEUP_ALARM:
368                  return R.string.battery_abnormal_wakeup_alarm_summary;
369              case Anomaly.AnomalyType.BLUETOOTH_SCAN:
370                  return R.string.battery_abnormal_location_summary;
371              default:
372                  throw new IllegalArgumentException("Incorrect anomaly type: " + type);
373          }
374      }
375  
convertUsToMs(long timeUs)376      public static long convertUsToMs(long timeUs) {
377          return timeUs / 1000;
378      }
379  
convertMsToUs(long timeMs)380      public static long convertMsToUs(long timeMs) {
381          return timeMs * 1000;
382      }
383  
initBatteryStatsHelper(BatteryStatsHelper statsHelper, Bundle bundle, UserManager userManager)384      public void initBatteryStatsHelper(BatteryStatsHelper statsHelper, Bundle bundle,
385              UserManager userManager) {
386          statsHelper.create(bundle);
387          statsHelper.clearStats();
388          statsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, userManager.getUserProfiles());
389      }
390  
isDataCorrupted()391      private boolean isDataCorrupted() {
392          return mPackageManager == null || mAppOpsManager == null;
393      }
394  
395      @VisibleForTesting
getForegroundActivityTotalTimeUs(BatteryStats.Uid uid, long rawRealtimeUs)396      long getForegroundActivityTotalTimeUs(BatteryStats.Uid uid, long rawRealtimeUs) {
397          final BatteryStats.Timer timer = uid.getForegroundActivityTimer();
398          if (timer != null) {
399              return timer.getTotalTimeLocked(rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED);
400          }
401  
402          return 0;
403      }
404  
405      @VisibleForTesting
getForegroundServiceTotalTimeUs(BatteryStats.Uid uid, long rawRealtimeUs)406      long getForegroundServiceTotalTimeUs(BatteryStats.Uid uid, long rawRealtimeUs) {
407          final BatteryStats.Timer timer = uid.getForegroundServiceTimer();
408          if (timer != null) {
409              return timer.getTotalTimeLocked(rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED);
410          }
411  
412          return 0;
413      }
414  
415  }
416  
417