Home | History | Annotate | Download | only in fuelgauge
      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 
     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
     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 
     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 
    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 
    132     private long getScreenUsageTimeMs(BatteryStats.Uid uid, int which) {
    133         final long rawRealTimeUs = convertMsToUs(SystemClock.elapsedRealtime());
    134         return getScreenUsageTimeMs(uid, which, rawRealTimeUs);
    135     }
    136 
    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 
    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      */
    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
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    336     public long calculateLastFullChargeTime(BatteryStatsHelper batteryStatsHelper,
    337             long currentTimeMs) {
    338         return currentTimeMs - batteryStatsHelper.getStats().getStartClockTime();
    339 
    340     }
    341 
    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      */
    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
    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 
    376     public static long convertUsToMs(long timeUs) {
    377         return timeUs / 1000;
    378     }
    379 
    380     public static long convertMsToUs(long timeMs) {
    381         return timeMs * 1000;
    382     }
    383 
    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 
    391     private boolean isDataCorrupted() {
    392         return mPackageManager == null || mAppOpsManager == null;
    393     }
    394 
    395     @VisibleForTesting
    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
    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