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