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.Intent; 21 import android.content.IntentFilter; 22 import android.content.pm.ApplicationInfo; 23 import android.content.pm.PackageInfo; 24 import android.content.pm.PackageManager; 25 import android.content.pm.ResolveInfo; 26 import android.os.BatteryConsumer; 27 import android.os.BatteryStats; 28 import android.os.BatteryStatsManager; 29 import android.os.BatteryUsageStats; 30 import android.os.BatteryUsageStatsQuery; 31 import android.os.Build; 32 import android.os.Process; 33 import android.os.SystemClock; 34 import android.os.UidBatteryConsumer; 35 import android.os.UserHandle; 36 import android.util.Log; 37 38 import androidx.annotation.IntDef; 39 import androidx.annotation.Nullable; 40 import androidx.annotation.VisibleForTesting; 41 import androidx.annotation.WorkerThread; 42 43 import com.android.internal.util.ArrayUtils; 44 import com.android.settings.fuelgauge.batterytip.AnomalyDatabaseHelper; 45 import com.android.settings.fuelgauge.batterytip.AnomalyInfo; 46 import com.android.settings.fuelgauge.batterytip.BatteryDatabaseManager; 47 import com.android.settings.fuelgauge.batterytip.StatsManagerConfig; 48 import com.android.settings.overlay.FeatureFactory; 49 import com.android.settingslib.applications.AppUtils; 50 import com.android.settingslib.fuelgauge.Estimate; 51 import com.android.settingslib.fuelgauge.EstimateKt; 52 import com.android.settingslib.fuelgauge.PowerAllowlistBackend; 53 import com.android.settingslib.utils.PowerUtil; 54 import com.android.settingslib.utils.ThreadUtils; 55 56 import java.lang.annotation.Retention; 57 import java.lang.annotation.RetentionPolicy; 58 import java.time.Duration; 59 import java.time.Instant; 60 import java.util.List; 61 62 /** 63 * Utils for battery operation 64 */ 65 public class BatteryUtils { 66 public static final int UID_NULL = -1; 67 public static final int SDK_NULL = -1; 68 69 @Retention(RetentionPolicy.SOURCE) 70 @IntDef({StatusType.SCREEN_USAGE, 71 StatusType.FOREGROUND, 72 StatusType.BACKGROUND, 73 StatusType.ALL 74 }) 75 public @interface StatusType { 76 int SCREEN_USAGE = 0; 77 int FOREGROUND = 1; 78 int BACKGROUND = 2; 79 int ALL = 3; 80 } 81 82 private static final String TAG = "BatteryUtils"; 83 84 private static BatteryUtils sInstance; 85 private PackageManager mPackageManager; 86 87 private AppOpsManager mAppOpsManager; 88 private Context mContext; 89 @VisibleForTesting 90 PowerUsageFeatureProvider mPowerUsageFeatureProvider; 91 getInstance(Context context)92 public static BatteryUtils getInstance(Context context) { 93 if (sInstance == null || sInstance.isDataCorrupted()) { 94 sInstance = new BatteryUtils(context.getApplicationContext()); 95 } 96 return sInstance; 97 } 98 99 @VisibleForTesting BatteryUtils(Context context)100 public BatteryUtils(Context context) { 101 mContext = context; 102 mPackageManager = context.getPackageManager(); 103 mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); 104 mPowerUsageFeatureProvider = FeatureFactory.getFactory(context) 105 .getPowerUsageFeatureProvider(context); 106 } 107 getProcessTimeMs(@tatusType int type, @Nullable BatteryStats.Uid uid, int which)108 public long getProcessTimeMs(@StatusType int type, @Nullable BatteryStats.Uid uid, 109 int which) { 110 if (uid == null) { 111 return 0; 112 } 113 114 switch (type) { 115 case StatusType.SCREEN_USAGE: 116 return getScreenUsageTimeMs(uid, which); 117 case StatusType.FOREGROUND: 118 return getProcessForegroundTimeMs(uid, which); 119 case StatusType.BACKGROUND: 120 return getProcessBackgroundTimeMs(uid, which); 121 case StatusType.ALL: 122 return getProcessForegroundTimeMs(uid, which) 123 + getProcessBackgroundTimeMs(uid, which); 124 } 125 return 0; 126 } 127 getScreenUsageTimeMs(BatteryStats.Uid uid, int which, long rawRealTimeUs)128 private long getScreenUsageTimeMs(BatteryStats.Uid uid, int which, long rawRealTimeUs) { 129 final int foregroundTypes[] = {BatteryStats.Uid.PROCESS_STATE_TOP}; 130 Log.v(TAG, "package: " + mPackageManager.getNameForUid(uid.getUid())); 131 132 long timeUs = 0; 133 for (int type : foregroundTypes) { 134 final long localTime = uid.getProcessStateTime(type, rawRealTimeUs, which); 135 Log.v(TAG, "type: " + type + " time(us): " + localTime); 136 timeUs += localTime; 137 } 138 Log.v(TAG, "foreground time(us): " + timeUs); 139 140 // Return the min value of STATE_TOP time and foreground activity time, since both of these 141 // time have some errors 142 return PowerUtil.convertUsToMs( 143 Math.min(timeUs, getForegroundActivityTotalTimeUs(uid, rawRealTimeUs))); 144 } 145 getScreenUsageTimeMs(BatteryStats.Uid uid, int which)146 private long getScreenUsageTimeMs(BatteryStats.Uid uid, int which) { 147 final long rawRealTimeUs = PowerUtil.convertMsToUs(SystemClock.elapsedRealtime()); 148 return getScreenUsageTimeMs(uid, which, rawRealTimeUs); 149 } 150 getProcessBackgroundTimeMs(BatteryStats.Uid uid, int which)151 private long getProcessBackgroundTimeMs(BatteryStats.Uid uid, int which) { 152 final long rawRealTimeUs = PowerUtil.convertMsToUs(SystemClock.elapsedRealtime()); 153 final long timeUs = uid.getProcessStateTime( 154 BatteryStats.Uid.PROCESS_STATE_BACKGROUND, rawRealTimeUs, which); 155 156 Log.v(TAG, "package: " + mPackageManager.getNameForUid(uid.getUid())); 157 Log.v(TAG, "background time(us): " + timeUs); 158 return PowerUtil.convertUsToMs(timeUs); 159 } 160 getProcessForegroundTimeMs(BatteryStats.Uid uid, int which)161 private long getProcessForegroundTimeMs(BatteryStats.Uid uid, int which) { 162 final long rawRealTimeUs = PowerUtil.convertMsToUs(SystemClock.elapsedRealtime()); 163 return getScreenUsageTimeMs(uid, which, rawRealTimeUs) 164 + PowerUtil.convertUsToMs(getForegroundServiceTotalTimeUs(uid, rawRealTimeUs)); 165 } 166 167 /** 168 * Returns true if the specified battery consumer should be excluded from the summary 169 * battery consumption list. 170 */ shouldHideUidBatteryConsumer(UidBatteryConsumer consumer)171 public boolean shouldHideUidBatteryConsumer(UidBatteryConsumer consumer) { 172 return shouldHideUidBatteryConsumer(consumer, 173 mPackageManager.getPackagesForUid(consumer.getUid())); 174 } 175 176 /** 177 * Returns true if the specified battery consumer should be excluded from the summary 178 * battery consumption list. 179 */ shouldHideUidBatteryConsumer(UidBatteryConsumer consumer, String[] packages)180 public boolean shouldHideUidBatteryConsumer(UidBatteryConsumer consumer, String[] packages) { 181 return mPowerUsageFeatureProvider.isTypeSystem(consumer.getUid(), packages) 182 || shouldHideUidBatteryConsumerUnconditionally(consumer, packages); 183 } 184 185 /** 186 * Returns true if the specified battery consumer should be excluded from 187 * battery consumption lists, either short or full. 188 */ shouldHideUidBatteryConsumerUnconditionally(UidBatteryConsumer consumer, String[] packages)189 boolean shouldHideUidBatteryConsumerUnconditionally(UidBatteryConsumer consumer, 190 String[] packages) { 191 return consumer.getUid() < 0 || isHiddenSystemModule(packages); 192 } 193 194 /** 195 * Returns true if the specified device power component should be excluded from the summary 196 * battery consumption list. 197 */ shouldHideDevicePowerComponent(BatteryConsumer consumer, @BatteryConsumer.PowerComponent int powerComponentId)198 public boolean shouldHideDevicePowerComponent(BatteryConsumer consumer, 199 @BatteryConsumer.PowerComponent int powerComponentId) { 200 switch (powerComponentId) { 201 case BatteryConsumer.POWER_COMPONENT_IDLE: 202 case BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO: 203 case BatteryConsumer.POWER_COMPONENT_SCREEN: 204 case BatteryConsumer.POWER_COMPONENT_BLUETOOTH: 205 case BatteryConsumer.POWER_COMPONENT_WIFI: 206 return true; 207 default: 208 return false; 209 } 210 } 211 212 /** 213 * Returns true if one the specified packages belongs to a hidden system module. 214 */ isHiddenSystemModule(String[] packages)215 public boolean isHiddenSystemModule(String[] packages) { 216 if (packages != null) { 217 for (int i = 0, length = packages.length; i < length; i++) { 218 if (AppUtils.isHiddenSystemModule(mContext, packages[i])) { 219 return true; 220 } 221 } 222 } 223 return false; 224 } 225 226 /** 227 * Calculate the power usage percentage for an app 228 * 229 * @param powerUsageMah power used by the app 230 * @param totalPowerMah total power used in the system 231 * @param dischargeAmount The discharge amount calculated by {@link BatteryStats} 232 * @return A percentage value scaled by {@paramref dischargeAmount} 233 * @see BatteryStats#getDischargeAmount(int) 234 */ calculateBatteryPercent(double powerUsageMah, double totalPowerMah, int dischargeAmount)235 public double calculateBatteryPercent(double powerUsageMah, double totalPowerMah, 236 int dischargeAmount) { 237 if (totalPowerMah == 0) { 238 return 0; 239 } 240 241 return (powerUsageMah / totalPowerMah) * dischargeAmount; 242 } 243 244 /** 245 * Find the package name for a {@link android.os.BatteryStats.Uid} 246 * 247 * @param uid id to get the package name 248 * @return the package name. If there are multiple packages related to 249 * given id, return the first one. Or return null if there are no known 250 * packages with the given id 251 * @see PackageManager#getPackagesForUid(int) 252 */ getPackageName(int uid)253 public String getPackageName(int uid) { 254 final String[] packageNames = mPackageManager.getPackagesForUid(uid); 255 256 return ArrayUtils.isEmpty(packageNames) ? null : packageNames[0]; 257 } 258 259 /** 260 * Find the targetSdkVersion for package with name {@code packageName} 261 * 262 * @return the targetSdkVersion, or {@link #SDK_NULL} if {@code packageName} doesn't exist 263 */ getTargetSdkVersion(final String packageName)264 public int getTargetSdkVersion(final String packageName) { 265 try { 266 ApplicationInfo info = mPackageManager.getApplicationInfo(packageName, 267 PackageManager.GET_META_DATA); 268 269 return info.targetSdkVersion; 270 } catch (PackageManager.NameNotFoundException e) { 271 Log.e(TAG, "Cannot find package: " + packageName, e); 272 } 273 274 return SDK_NULL; 275 } 276 277 /** 278 * Check whether background restriction is enabled 279 */ isBackgroundRestrictionEnabled(final int targetSdkVersion, final int uid, final String packageName)280 public boolean isBackgroundRestrictionEnabled(final int targetSdkVersion, final int uid, 281 final String packageName) { 282 if (targetSdkVersion >= Build.VERSION_CODES.O) { 283 return true; 284 } 285 final int mode = mAppOpsManager 286 .checkOpNoThrow(AppOpsManager.OP_RUN_IN_BACKGROUND, uid, packageName); 287 return mode == AppOpsManager.MODE_IGNORED || mode == AppOpsManager.MODE_ERRORED; 288 } 289 290 /** 291 * Calculate the time since last full charge, including the device off time 292 * 293 * @param batteryUsageStats class that contains the data 294 * @param currentTimeMs current wall time 295 * @return time in millis 296 */ calculateLastFullChargeTime(BatteryUsageStats batteryUsageStats, long currentTimeMs)297 public long calculateLastFullChargeTime(BatteryUsageStats batteryUsageStats, 298 long currentTimeMs) { 299 return currentTimeMs - batteryUsageStats.getStatsStartTimestamp(); 300 } 301 logRuntime(String tag, String message, long startTime)302 public static void logRuntime(String tag, String message, long startTime) { 303 Log.d(tag, message + ": " + (System.currentTimeMillis() - startTime) + "ms"); 304 } 305 306 /** 307 * Return {@code true} if battery is overheated and charging. 308 */ isBatteryDefenderOn(BatteryInfo batteryInfo)309 public static boolean isBatteryDefenderOn(BatteryInfo batteryInfo) { 310 return batteryInfo.isOverheated && !batteryInfo.discharging; 311 } 312 313 /** 314 * Find package uid from package name 315 * 316 * @param packageName used to find the uid 317 * @return uid for packageName, or {@link #UID_NULL} if exception happens or 318 * {@code packageName} is null 319 */ getPackageUid(String packageName)320 public int getPackageUid(String packageName) { 321 try { 322 return packageName == null ? UID_NULL : mPackageManager.getPackageUid(packageName, 323 PackageManager.GET_META_DATA); 324 } catch (PackageManager.NameNotFoundException e) { 325 return UID_NULL; 326 } 327 } 328 setForceAppStandby(int uid, String packageName, int mode)329 public void setForceAppStandby(int uid, String packageName, 330 int mode) { 331 final boolean isPreOApp = isPreOApp(packageName); 332 if (isPreOApp) { 333 // Control whether app could run in the background if it is pre O app 334 mAppOpsManager.setMode(AppOpsManager.OP_RUN_IN_BACKGROUND, uid, packageName, mode); 335 } 336 // Control whether app could run jobs in the background 337 mAppOpsManager.setMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uid, packageName, mode); 338 339 ThreadUtils.postOnBackgroundThread(() -> { 340 final BatteryDatabaseManager batteryDatabaseManager = BatteryDatabaseManager 341 .getInstance(mContext); 342 if (mode == AppOpsManager.MODE_IGNORED) { 343 batteryDatabaseManager.insertAction(AnomalyDatabaseHelper.ActionType.RESTRICTION, 344 uid, packageName, System.currentTimeMillis()); 345 } else if (mode == AppOpsManager.MODE_ALLOWED) { 346 batteryDatabaseManager.deleteAction(AnomalyDatabaseHelper.ActionType.RESTRICTION, 347 uid, packageName); 348 } 349 }); 350 } 351 isForceAppStandbyEnabled(int uid, String packageName)352 public boolean isForceAppStandbyEnabled(int uid, String packageName) { 353 return mAppOpsManager.checkOpNoThrow(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uid, 354 packageName) == AppOpsManager.MODE_IGNORED; 355 } 356 clearForceAppStandby(String packageName)357 public boolean clearForceAppStandby(String packageName) { 358 final int uid = getPackageUid(packageName); 359 if (uid != UID_NULL && isForceAppStandbyEnabled(uid, packageName)) { 360 setForceAppStandby(uid, packageName, AppOpsManager.MODE_ALLOWED); 361 return true; 362 } else { 363 return false; 364 } 365 } 366 367 @WorkerThread getBatteryInfo(final String tag)368 public BatteryInfo getBatteryInfo(final String tag) { 369 final BatteryStatsManager systemService = mContext.getSystemService( 370 BatteryStatsManager.class); 371 final BatteryUsageStats batteryUsageStats = systemService.getBatteryUsageStats( 372 new BatteryUsageStatsQuery.Builder().includeBatteryHistory().build()); 373 374 final long startTime = System.currentTimeMillis(); 375 376 // Stuff we always need to get BatteryInfo 377 final Intent batteryBroadcast = mContext.registerReceiver(null, 378 new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); 379 380 final long elapsedRealtimeUs = PowerUtil.convertMsToUs( 381 SystemClock.elapsedRealtime()); 382 383 BatteryInfo batteryInfo; 384 Estimate estimate = getEnhancedEstimate(); 385 386 // couldn't get estimate from cache or provider, use fallback 387 if (estimate == null) { 388 estimate = new Estimate( 389 batteryUsageStats.getBatteryTimeRemainingMs(), 390 false /* isBasedOnUsage */, 391 EstimateKt.AVERAGE_TIME_TO_DISCHARGE_UNKNOWN); 392 } 393 394 BatteryUtils.logRuntime(tag, "BatteryInfoLoader post query", startTime); 395 batteryInfo = BatteryInfo.getBatteryInfo(mContext, batteryBroadcast, 396 batteryUsageStats, estimate, elapsedRealtimeUs, false /* shortString */); 397 BatteryUtils.logRuntime(tag, "BatteryInfoLoader.loadInBackground", startTime); 398 399 return batteryInfo; 400 } 401 402 @VisibleForTesting getEnhancedEstimate()403 Estimate getEnhancedEstimate() { 404 Estimate estimate = null; 405 // Get enhanced prediction if available 406 if (Duration.between(Estimate.getLastCacheUpdateTime(mContext), Instant.now()) 407 .compareTo(Duration.ofSeconds(10)) < 0) { 408 estimate = Estimate.getCachedEstimateIfAvailable(mContext); 409 } else if (mPowerUsageFeatureProvider != null && 410 mPowerUsageFeatureProvider.isEnhancedBatteryPredictionEnabled(mContext)) { 411 estimate = mPowerUsageFeatureProvider.getEnhancedBatteryPrediction(mContext); 412 if (estimate != null) { 413 Estimate.storeCachedEstimate(mContext, estimate); 414 } 415 } 416 return estimate; 417 } 418 isDataCorrupted()419 private boolean isDataCorrupted() { 420 return mPackageManager == null || mAppOpsManager == null; 421 } 422 423 @VisibleForTesting getForegroundActivityTotalTimeUs(BatteryStats.Uid uid, long rawRealtimeUs)424 long getForegroundActivityTotalTimeUs(BatteryStats.Uid uid, long rawRealtimeUs) { 425 final BatteryStats.Timer timer = uid.getForegroundActivityTimer(); 426 if (timer != null) { 427 return timer.getTotalTimeLocked(rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED); 428 } 429 430 return 0; 431 } 432 433 @VisibleForTesting getForegroundServiceTotalTimeUs(BatteryStats.Uid uid, long rawRealtimeUs)434 long getForegroundServiceTotalTimeUs(BatteryStats.Uid uid, long rawRealtimeUs) { 435 final BatteryStats.Timer timer = uid.getForegroundServiceTimer(); 436 if (timer != null) { 437 return timer.getTotalTimeLocked(rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED); 438 } 439 440 return 0; 441 } 442 isPreOApp(final String packageName)443 public boolean isPreOApp(final String packageName) { 444 try { 445 ApplicationInfo info = mPackageManager.getApplicationInfo(packageName, 446 PackageManager.GET_META_DATA); 447 448 return info.targetSdkVersion < Build.VERSION_CODES.O; 449 } catch (PackageManager.NameNotFoundException e) { 450 Log.e(TAG, "Cannot find package: " + packageName, e); 451 } 452 453 return false; 454 } 455 isPreOApp(final String[] packageNames)456 public boolean isPreOApp(final String[] packageNames) { 457 if (ArrayUtils.isEmpty(packageNames)) { 458 return false; 459 } 460 461 for (String packageName : packageNames) { 462 if (isPreOApp(packageName)) { 463 return true; 464 } 465 } 466 467 return false; 468 } 469 470 /** 471 * Return {@code true} if we should hide anomaly app represented by {@code uid} 472 */ shouldHideAnomaly(PowerAllowlistBackend powerAllowlistBackend, int uid, AnomalyInfo anomalyInfo)473 public boolean shouldHideAnomaly(PowerAllowlistBackend powerAllowlistBackend, int uid, 474 AnomalyInfo anomalyInfo) { 475 final String[] packageNames = mPackageManager.getPackagesForUid(uid); 476 if (ArrayUtils.isEmpty(packageNames)) { 477 // Don't show it if app has been uninstalled 478 return true; 479 } 480 481 return isSystemUid(uid) || powerAllowlistBackend.isAllowlisted(packageNames) 482 || (isSystemApp(mPackageManager, packageNames) && !hasLauncherEntry(packageNames)) 483 || (isExcessiveBackgroundAnomaly(anomalyInfo) && !isPreOApp(packageNames)); 484 } 485 isExcessiveBackgroundAnomaly(AnomalyInfo anomalyInfo)486 private boolean isExcessiveBackgroundAnomaly(AnomalyInfo anomalyInfo) { 487 return anomalyInfo.anomalyType 488 == StatsManagerConfig.AnomalyType.EXCESSIVE_BACKGROUND_SERVICE; 489 } 490 isSystemUid(int uid)491 private boolean isSystemUid(int uid) { 492 final int appUid = UserHandle.getAppId(uid); 493 return appUid >= Process.ROOT_UID && appUid < Process.FIRST_APPLICATION_UID; 494 } 495 isSystemApp(PackageManager packageManager, String[] packageNames)496 private boolean isSystemApp(PackageManager packageManager, String[] packageNames) { 497 for (String packageName : packageNames) { 498 try { 499 final ApplicationInfo info = packageManager.getApplicationInfo(packageName, 500 0 /* flags */); 501 if ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { 502 return true; 503 } 504 } catch (PackageManager.NameNotFoundException e) { 505 Log.e(TAG, "Package not found: " + packageName, e); 506 } 507 } 508 509 return false; 510 } 511 hasLauncherEntry(String[] packageNames)512 private boolean hasLauncherEntry(String[] packageNames) { 513 final Intent launchIntent = new Intent(Intent.ACTION_MAIN, null); 514 launchIntent.addCategory(Intent.CATEGORY_LAUNCHER); 515 516 // If we do not specify MATCH_DIRECT_BOOT_AWARE or 517 // MATCH_DIRECT_BOOT_UNAWARE, system will derive and update the flags 518 // according to the user's lock state. When the user is locked, 519 // components 520 // with ComponentInfo#directBootAware == false will be filtered. We should 521 // explicitly include both direct boot aware and unaware components here. 522 final List<ResolveInfo> resolveInfos = mPackageManager.queryIntentActivities(launchIntent, 523 PackageManager.MATCH_DISABLED_COMPONENTS 524 | PackageManager.MATCH_DIRECT_BOOT_AWARE 525 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE 526 | PackageManager.MATCH_SYSTEM_ONLY); 527 for (int i = 0, size = resolveInfos.size(); i < size; i++) { 528 final ResolveInfo resolveInfo = resolveInfos.get(i); 529 if (ArrayUtils.contains(packageNames, resolveInfo.activityInfo.packageName)) { 530 return true; 531 } 532 } 533 534 return false; 535 } 536 537 /** 538 * Return version number of an app represented by {@code packageName}, and return -1 if not 539 * found. 540 */ getAppLongVersionCode(String packageName)541 public long getAppLongVersionCode(String packageName) { 542 try { 543 final PackageInfo packageInfo = mPackageManager.getPackageInfo(packageName, 544 0 /* flags */); 545 return packageInfo.getLongVersionCode(); 546 } catch (PackageManager.NameNotFoundException e) { 547 Log.e(TAG, "Cannot find package: " + packageName, e); 548 } 549 550 return -1L; 551 } 552 } 553