1 /* 2 * Copyright (C) 2022 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.batteryusage; 17 18 import android.content.Context; 19 import android.content.pm.ApplicationInfo; 20 import android.content.pm.PackageManager; 21 import android.content.pm.PackageManager.NameNotFoundException; 22 import android.graphics.drawable.Drawable; 23 import android.os.UserHandle; 24 import android.os.UserManager; 25 import android.text.TextUtils; 26 import android.util.ArrayMap; 27 import android.util.Log; 28 import android.util.Pair; 29 30 import androidx.annotation.GuardedBy; 31 import androidx.annotation.VisibleForTesting; 32 33 import com.android.settings.R; 34 import com.android.settings.fuelgauge.BatteryUtils; 35 import com.android.settings.fuelgauge.batteryusage.BatteryEntry.NameAndIcon; 36 import com.android.settingslib.utils.StringUtil; 37 38 import java.util.Comparator; 39 import java.util.Locale; 40 import java.util.Map; 41 42 /** A container class to carry battery data in a specific time slot. */ 43 public class BatteryDiffEntry { 44 private static final String TAG = "BatteryDiffEntry"; 45 private static final Object sResourceCacheLock = new Object(); 46 private static final Object sPackageNameAndUidCacheLock = new Object(); 47 private static final Object sValidForRestrictionLock = new Object(); 48 49 static Locale sCurrentLocale = null; 50 51 // Caches app label and icon to improve loading performance. 52 @GuardedBy("sResourceCacheLock") 53 static final Map<String, NameAndIcon> sResourceCache = new ArrayMap<>(); 54 55 // Caches package name and uid to improve loading performance. 56 @GuardedBy("sPackageNameAndUidCacheLock") 57 static final Map<String, Integer> sPackageNameAndUidCache = new ArrayMap<>(); 58 59 // Whether a specific item is valid to launch restriction page? 60 @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) 61 @GuardedBy("sValidForRestrictionLock") 62 static final Map<String, Boolean> sValidForRestriction = new ArrayMap<>(); 63 64 /** A comparator for {@link BatteryDiffEntry} based on the sorting key. */ 65 static final Comparator<BatteryDiffEntry> COMPARATOR = 66 (a, b) -> Double.compare(b.getSortingKey(), a.getSortingKey()); 67 68 static final String SYSTEM_APPS_KEY = "A|SystemApps"; 69 static final String UNINSTALLED_APPS_KEY = "A|UninstalledApps"; 70 static final String OTHERS_KEY = "S|Others"; 71 72 // key -> (label_id, icon_id) 73 private static final Map<String, Pair<Integer, Integer>> SPECIAL_ENTRY_MAP = 74 Map.of( 75 SYSTEM_APPS_KEY, 76 Pair.create(R.string.battery_usage_system_apps, R.drawable.ic_power_system), 77 UNINSTALLED_APPS_KEY, 78 Pair.create( 79 R.string.battery_usage_uninstalled_apps, 80 R.drawable.ic_battery_uninstalled), 81 OTHERS_KEY, 82 Pair.create( 83 R.string.battery_usage_others, 84 R.drawable.ic_settings_battery_usage_others)); 85 86 public long mUid; 87 public long mUserId; 88 public String mKey; 89 public boolean mIsHidden; 90 public int mComponentId; 91 public String mLegacyPackageName; 92 public String mLegacyLabel; 93 public int mConsumerType; 94 public long mForegroundUsageTimeInMs; 95 public long mForegroundServiceUsageTimeInMs; 96 public long mBackgroundUsageTimeInMs; 97 public long mScreenOnTimeInMs; 98 public double mConsumePower; 99 public double mForegroundUsageConsumePower; 100 public double mForegroundServiceUsageConsumePower; 101 public double mBackgroundUsageConsumePower; 102 public double mCachedUsageConsumePower; 103 104 protected Context mContext; 105 106 private double mTotalConsumePower; 107 private double mPercentage; 108 private int mAdjustPercentageOffset; 109 private UserManager mUserManager; 110 private String mDefaultPackageName = null; 111 112 @VisibleForTesting int mAppIconId; 113 @VisibleForTesting String mAppLabel = null; 114 @VisibleForTesting Drawable mAppIcon = null; 115 @VisibleForTesting boolean mIsLoaded = false; 116 @VisibleForTesting boolean mValidForRestriction = true; 117 BatteryDiffEntry( Context context, long uid, long userId, String key, boolean isHidden, int componentId, String legacyPackageName, String legacyLabel, int consumerType, long foregroundUsageTimeInMs, long foregroundServiceUsageTimeInMs, long backgroundUsageTimeInMs, long screenOnTimeInMs, double consumePower, double foregroundUsageConsumePower, double foregroundServiceUsageConsumePower, double backgroundUsageConsumePower, double cachedUsageConsumePower)118 public BatteryDiffEntry( 119 Context context, 120 long uid, 121 long userId, 122 String key, 123 boolean isHidden, 124 int componentId, 125 String legacyPackageName, 126 String legacyLabel, 127 int consumerType, 128 long foregroundUsageTimeInMs, 129 long foregroundServiceUsageTimeInMs, 130 long backgroundUsageTimeInMs, 131 long screenOnTimeInMs, 132 double consumePower, 133 double foregroundUsageConsumePower, 134 double foregroundServiceUsageConsumePower, 135 double backgroundUsageConsumePower, 136 double cachedUsageConsumePower) { 137 mContext = context; 138 mUid = uid; 139 mUserId = userId; 140 mKey = key; 141 mIsHidden = isHidden; 142 mComponentId = componentId; 143 mLegacyPackageName = legacyPackageName; 144 mLegacyLabel = legacyLabel; 145 mConsumerType = consumerType; 146 mForegroundUsageTimeInMs = foregroundUsageTimeInMs; 147 mForegroundServiceUsageTimeInMs = foregroundServiceUsageTimeInMs; 148 mBackgroundUsageTimeInMs = backgroundUsageTimeInMs; 149 mScreenOnTimeInMs = screenOnTimeInMs; 150 mConsumePower = consumePower; 151 mForegroundUsageConsumePower = foregroundUsageConsumePower; 152 mForegroundServiceUsageConsumePower = foregroundServiceUsageConsumePower; 153 mBackgroundUsageConsumePower = backgroundUsageConsumePower; 154 mCachedUsageConsumePower = cachedUsageConsumePower; 155 mUserManager = context.getSystemService(UserManager.class); 156 } 157 BatteryDiffEntry(Context context, String key, String legacyLabel, int consumerType)158 public BatteryDiffEntry(Context context, String key, String legacyLabel, int consumerType) { 159 this( 160 context, 161 /* uid= */ 0, 162 /* userId= */ 0, 163 key, 164 /* isHidden= */ false, 165 /* componentId= */ -1, 166 /* legacyPackageName= */ null, 167 legacyLabel, 168 consumerType, 169 /* foregroundUsageTimeInMs= */ 0, 170 /* foregroundServiceUsageTimeInMs= */ 0, 171 /* backgroundUsageTimeInMs= */ 0, 172 /* screenOnTimeInMs= */ 0, 173 /* consumePower= */ 0, 174 /* foregroundUsageConsumePower= */ 0, 175 /* foregroundServiceUsageConsumePower= */ 0, 176 /* backgroundUsageConsumePower= */ 0, 177 /* cachedUsageConsumePower= */ 0); 178 } 179 180 /** Sets the total consumed power in a specific time slot. */ setTotalConsumePower(double totalConsumePower)181 public void setTotalConsumePower(double totalConsumePower) { 182 mTotalConsumePower = totalConsumePower; 183 mPercentage = totalConsumePower == 0 ? 0 : (mConsumePower / mTotalConsumePower) * 100.0; 184 mAdjustPercentageOffset = 0; 185 } 186 187 /** Gets the total consumed power in a specific time slot. */ getTotalConsumePower()188 public double getTotalConsumePower() { 189 return mTotalConsumePower; 190 } 191 192 /** Gets the percentage of total consumed power. */ getPercentage()193 public double getPercentage() { 194 return mPercentage; 195 } 196 197 /** Gets the percentage offset to adjust. */ getAdjustPercentageOffset()198 public double getAdjustPercentageOffset() { 199 return mAdjustPercentageOffset; 200 } 201 202 /** Sets the percentage offset to adjust. */ setAdjustPercentageOffset(int offset)203 public void setAdjustPercentageOffset(int offset) { 204 mAdjustPercentageOffset = offset; 205 } 206 207 /** Gets the key for sorting */ getSortingKey()208 public double getSortingKey() { 209 String key = getKey(); 210 if (key == null) { 211 return getPercentage() + getAdjustPercentageOffset(); 212 } 213 214 // For special entries, put them to the end of the list. 215 switch (key) { 216 case UNINSTALLED_APPS_KEY: 217 case OTHERS_KEY: 218 return -1; 219 case SYSTEM_APPS_KEY: 220 return -2; 221 default: 222 return getPercentage() + getAdjustPercentageOffset(); 223 } 224 } 225 226 /** Clones a new instance. */ clone()227 public BatteryDiffEntry clone() { 228 return new BatteryDiffEntry( 229 this.mContext, 230 this.mUid, 231 this.mUserId, 232 this.mKey, 233 this.mIsHidden, 234 this.mComponentId, 235 this.mLegacyPackageName, 236 this.mLegacyLabel, 237 this.mConsumerType, 238 this.mForegroundUsageTimeInMs, 239 this.mForegroundServiceUsageTimeInMs, 240 this.mBackgroundUsageTimeInMs, 241 this.mScreenOnTimeInMs, 242 this.mConsumePower, 243 this.mForegroundUsageConsumePower, 244 this.mForegroundServiceUsageConsumePower, 245 this.mBackgroundUsageConsumePower, 246 this.mCachedUsageConsumePower); 247 } 248 249 /** Gets the app label name for this entry. */ getAppLabel()250 public String getAppLabel() { 251 loadLabelAndIcon(); 252 // Returns default application label if we cannot find it. 253 return mAppLabel == null || mAppLabel.length() == 0 ? mLegacyLabel : mAppLabel; 254 } 255 256 /** Gets the app icon {@link Drawable} for this entry. */ getAppIcon()257 public Drawable getAppIcon() { 258 loadLabelAndIcon(); 259 return mAppIcon != null && mAppIcon.getConstantState() != null 260 ? mAppIcon.getConstantState().newDrawable() 261 : null; 262 } 263 264 /** Gets the app icon id for this entry. */ getAppIconId()265 public int getAppIconId() { 266 loadLabelAndIcon(); 267 return mAppIconId; 268 } 269 270 /** Gets the searching package name for UID battery type. */ getPackageName()271 public String getPackageName() { 272 final String packageName = 273 mDefaultPackageName != null ? mDefaultPackageName : mLegacyPackageName; 274 if (packageName == null) { 275 return packageName; 276 } 277 // Removes potential appended process name in the PackageName. 278 // From "com.opera.browser:privileged_process0" to "com.opera.browser" 279 final String[] splitPackageNames = packageName.split(":"); 280 return splitPackageNames != null && splitPackageNames.length > 0 281 ? splitPackageNames[0] 282 : packageName; 283 } 284 285 /** Whether this item is valid for users to launch restriction page? */ validForRestriction()286 public boolean validForRestriction() { 287 loadLabelAndIcon(); 288 return mValidForRestriction; 289 } 290 291 /** Whether the current BatteryDiffEntry is system component or not. */ isSystemEntry()292 public boolean isSystemEntry() { 293 if (mIsHidden) { 294 return false; 295 } 296 switch (mConsumerType) { 297 case ConvertUtils.CONSUMER_TYPE_USER_BATTERY: 298 case ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY: 299 return true; 300 case ConvertUtils.CONSUMER_TYPE_UID_BATTERY: 301 default: 302 return false; 303 } 304 } 305 306 /** Whether the current BatteryDiffEntry is uninstalled app or not. */ isUninstalledEntry()307 public boolean isUninstalledEntry() { 308 final String packageName = getPackageName(); 309 if (TextUtils.isEmpty(packageName) 310 || isSystemEntry() 311 // Some special package UIDs could be 0. Those packages are not installed by users. 312 || mUid == BatteryUtils.UID_ZERO) { 313 return false; 314 } 315 316 final int uid = getPackageUid(packageName); 317 return uid == BatteryUtils.UID_REMOVED_APPS || uid == BatteryUtils.UID_NULL; 318 } 319 getPackageUid(String packageName)320 private int getPackageUid(String packageName) { 321 synchronized (sPackageNameAndUidCacheLock) { 322 if (sPackageNameAndUidCache.containsKey(packageName)) { 323 return sPackageNameAndUidCache.get(packageName); 324 } 325 } 326 327 int uid = 328 BatteryUtils.getInstance(mContext).getPackageUidAsUser(packageName, (int) mUserId); 329 synchronized (sPackageNameAndUidCacheLock) { 330 sPackageNameAndUidCache.put(packageName, uid); 331 } 332 return uid; 333 } 334 loadLabelAndIcon()335 void loadLabelAndIcon() { 336 if (mIsLoaded) { 337 return; 338 } 339 // Checks whether we have cached data or not first before fetching. 340 final NameAndIcon nameAndIcon = getCache(); 341 if (nameAndIcon != null) { 342 mAppLabel = nameAndIcon.mName; 343 mAppIcon = nameAndIcon.mIcon; 344 mAppIconId = nameAndIcon.mIconId; 345 } 346 Boolean validForRestriction = null; 347 synchronized (sValidForRestrictionLock) { 348 validForRestriction = sValidForRestriction.get(getKey()); 349 } 350 if (validForRestriction != null) { 351 mValidForRestriction = validForRestriction; 352 } 353 // Both nameAndIcon and restriction configuration have cached data. 354 if (nameAndIcon != null && validForRestriction != null) { 355 return; 356 } 357 mIsLoaded = true; 358 359 // Configures whether we can launch restriction page or not. 360 updateRestrictionFlagState(); 361 synchronized (sValidForRestrictionLock) { 362 sValidForRestriction.put(getKey(), Boolean.valueOf(mValidForRestriction)); 363 } 364 365 if (getKey() != null && SPECIAL_ENTRY_MAP.containsKey(getKey())) { 366 Pair<Integer, Integer> pair = SPECIAL_ENTRY_MAP.get(getKey()); 367 mAppLabel = mContext.getString(pair.first); 368 mAppIconId = pair.second; 369 mAppIcon = mContext.getDrawable(mAppIconId); 370 putResourceCache(getKey(), new NameAndIcon(mAppLabel, mAppIcon, mAppIconId)); 371 return; 372 } 373 374 // Loads application icon and label based on consumer type. 375 switch (mConsumerType) { 376 case ConvertUtils.CONSUMER_TYPE_USER_BATTERY: 377 final NameAndIcon nameAndIconForUser = 378 BatteryEntry.getNameAndIconFromUserId(mContext, (int) mUserId); 379 if (nameAndIconForUser != null) { 380 mAppIcon = nameAndIconForUser.mIcon; 381 mAppLabel = nameAndIconForUser.mName; 382 putResourceCache( 383 getKey(), new NameAndIcon(mAppLabel, mAppIcon, /* iconId= */ 0)); 384 } 385 break; 386 case ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY: 387 final NameAndIcon nameAndIconForSystem = 388 BatteryEntry.getNameAndIconFromPowerComponent(mContext, mComponentId); 389 if (nameAndIconForSystem != null) { 390 mAppLabel = nameAndIconForSystem.mName; 391 if (nameAndIconForSystem.mIconId != 0) { 392 mAppIconId = nameAndIconForSystem.mIconId; 393 mAppIcon = mContext.getDrawable(nameAndIconForSystem.mIconId); 394 } 395 putResourceCache(getKey(), new NameAndIcon(mAppLabel, mAppIcon, mAppIconId)); 396 } 397 break; 398 case ConvertUtils.CONSUMER_TYPE_UID_BATTERY: 399 loadNameAndIconForUid(); 400 // Uses application default icon if we cannot find it from package. 401 if (mAppIcon == null) { 402 mAppIcon = mContext.getPackageManager().getDefaultActivityIcon(); 403 } 404 // Adds badge icon into app icon for work profile. 405 mAppIcon = getBadgeIconForUser(mAppIcon); 406 if (mAppLabel != null || mAppIcon != null) { 407 putResourceCache( 408 getKey(), new NameAndIcon(mAppLabel, mAppIcon, /* iconId= */ 0)); 409 } 410 break; 411 } 412 } 413 getKey()414 String getKey() { 415 return mKey; 416 } 417 418 @VisibleForTesting updateRestrictionFlagState()419 void updateRestrictionFlagState() { 420 if (isSystemEntry()) { 421 mValidForRestriction = false; 422 return; 423 } 424 final boolean isValidPackage = 425 BatteryUtils.getInstance(mContext).getPackageUid(getPackageName()) 426 != BatteryUtils.UID_NULL; 427 if (!isValidPackage) { 428 mValidForRestriction = false; 429 return; 430 } 431 try { 432 mValidForRestriction = 433 mContext.getPackageManager() 434 .getPackageInfo( 435 getPackageName(), 436 PackageManager.MATCH_DISABLED_COMPONENTS 437 | PackageManager.MATCH_ANY_USER 438 | PackageManager.GET_SIGNATURES 439 | PackageManager.GET_PERMISSIONS) 440 != null; 441 } catch (Exception e) { 442 Log.e( 443 TAG, 444 String.format( 445 "getPackageInfo() error %s for package=%s", 446 e.getCause(), getPackageName())); 447 mValidForRestriction = false; 448 } 449 } 450 getCache()451 private NameAndIcon getCache() { 452 final Locale locale = Locale.getDefault(); 453 if (sCurrentLocale != locale) { 454 Log.d( 455 TAG, 456 String.format( 457 "clearCache() locale is changed from %s to %s", 458 sCurrentLocale, locale)); 459 sCurrentLocale = locale; 460 clearCache(); 461 } 462 synchronized (sResourceCacheLock) { 463 return sResourceCache.get(getKey()); 464 } 465 } 466 loadNameAndIconForUid()467 private void loadNameAndIconForUid() { 468 final String packageName = getPackageName(); 469 final PackageManager packageManager = mContext.getPackageManager(); 470 // Gets the application label from PackageManager. 471 if (packageName != null && packageName.length() != 0) { 472 try { 473 final ApplicationInfo appInfo = 474 packageManager.getApplicationInfo(packageName, /*no flags*/ 0); 475 if (appInfo != null) { 476 mAppLabel = packageManager.getApplicationLabel(appInfo).toString(); 477 mAppIcon = packageManager.getApplicationIcon(appInfo); 478 } 479 } catch (NameNotFoundException e) { 480 Log.e(TAG, "failed to retrieve ApplicationInfo for: " + packageName); 481 mAppLabel = packageName; 482 } 483 } 484 // Early return if we found the app label and icon resource. 485 if (mAppLabel != null && mAppIcon != null) { 486 return; 487 } 488 489 final int uid = (int) mUid; 490 final String[] packages = packageManager.getPackagesForUid(uid); 491 // Loads special defined application label and icon if available. 492 if (packages == null || packages.length == 0) { 493 final NameAndIcon nameAndIcon = 494 BatteryEntry.getNameAndIconFromUid(mContext, mAppLabel, uid); 495 mAppLabel = nameAndIcon.mName; 496 mAppIcon = nameAndIcon.mIcon; 497 } 498 499 final NameAndIcon nameAndIcon = 500 BatteryEntry.loadNameAndIcon( 501 mContext, uid, /* batteryEntry= */ null, packageName, mAppLabel, mAppIcon); 502 // Clears BatteryEntry internal cache since we will have another one. 503 BatteryEntry.clearUidCache(); 504 if (nameAndIcon != null) { 505 mAppLabel = nameAndIcon.mName; 506 mAppIcon = nameAndIcon.mIcon; 507 mDefaultPackageName = nameAndIcon.mPackageName; 508 if (mDefaultPackageName != null 509 && !mDefaultPackageName.equals(nameAndIcon.mPackageName)) { 510 Log.w( 511 TAG, 512 String.format( 513 "found different package: %s | %s", 514 mDefaultPackageName, nameAndIcon.mPackageName)); 515 } 516 } 517 } 518 519 @Override toString()520 public String toString() { 521 final StringBuilder builder = new StringBuilder(); 522 builder.append("BatteryDiffEntry{"); 523 builder.append( 524 String.format("\n\tname=%s restrictable=%b", mAppLabel, mValidForRestriction)); 525 builder.append( 526 String.format( 527 "\n\tconsume=%.2f%% %f/%f", 528 mPercentage, mConsumePower, mTotalConsumePower)); 529 builder.append( 530 String.format( 531 "\n\tconsume power= foreground:%f foregroundService:%f", 532 mForegroundUsageConsumePower, mForegroundServiceUsageConsumePower)); 533 builder.append( 534 String.format( 535 "\n\tconsume power= background:%f cached:%f", 536 mBackgroundUsageConsumePower, mCachedUsageConsumePower)); 537 builder.append( 538 String.format( 539 "\n\ttime= foreground:%s foregroundService:%s " 540 + "background:%s screen-on:%s", 541 StringUtil.formatElapsedTime( 542 mContext, 543 (double) mForegroundUsageTimeInMs, 544 /* withSeconds= */ true, 545 /* collapseTimeUnit= */ false), 546 StringUtil.formatElapsedTime( 547 mContext, 548 (double) mForegroundServiceUsageTimeInMs, 549 /* withSeconds= */ true, 550 /* collapseTimeUnit= */ false), 551 StringUtil.formatElapsedTime( 552 mContext, 553 (double) mBackgroundUsageTimeInMs, 554 /* withSeconds= */ true, 555 /* collapseTimeUnit= */ false), 556 StringUtil.formatElapsedTime( 557 mContext, 558 (double) mScreenOnTimeInMs, 559 /* withSeconds= */ true, 560 /* collapseTimeUnit= */ false))); 561 builder.append( 562 String.format( 563 "\n\tpackage:%s|%s uid:%d userId:%d", 564 mLegacyPackageName, getPackageName(), mUid, mUserId)); 565 return builder.toString(); 566 } 567 568 /** Clears all cache data. */ clearCache()569 public static void clearCache() { 570 synchronized (sResourceCacheLock) { 571 sResourceCache.clear(); 572 } 573 synchronized (sValidForRestrictionLock) { 574 sValidForRestriction.clear(); 575 } 576 synchronized (sPackageNameAndUidCacheLock) { 577 sPackageNameAndUidCache.clear(); 578 } 579 } 580 putResourceCache(String key, NameAndIcon nameAndIcon)581 private static void putResourceCache(String key, NameAndIcon nameAndIcon) { 582 synchronized (sResourceCacheLock) { 583 sResourceCache.put(key, nameAndIcon); 584 } 585 } 586 getBadgeIconForUser(Drawable icon)587 private Drawable getBadgeIconForUser(Drawable icon) { 588 final int userId = UserHandle.getUserId((int) mUid); 589 return userId == UserHandle.USER_OWNER 590 ? icon 591 : mUserManager.getBadgedIconForUser(icon, new UserHandle(userId)); 592 } 593 } 594