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) 426 .getPackageUidAsUser(getPackageName(), (int) mUserId) 427 != BatteryUtils.UID_NULL; 428 if (!isValidPackage) { 429 mValidForRestriction = false; 430 return; 431 } 432 try { 433 mValidForRestriction = 434 mContext.getPackageManager() 435 .getPackageInfo( 436 getPackageName(), 437 PackageManager.MATCH_DISABLED_COMPONENTS 438 | PackageManager.MATCH_ANY_USER 439 | PackageManager.GET_SIGNATURES 440 | PackageManager.GET_PERMISSIONS) 441 != null; 442 } catch (Exception e) { 443 Log.e( 444 TAG, 445 String.format( 446 "getPackageInfo() error %s for package=%s", 447 e.getCause(), getPackageName())); 448 mValidForRestriction = false; 449 } 450 } 451 getCache()452 private NameAndIcon getCache() { 453 final Locale locale = Locale.getDefault(); 454 if (sCurrentLocale != locale) { 455 Log.d( 456 TAG, 457 String.format( 458 "clearCache() locale is changed from %s to %s", 459 sCurrentLocale, locale)); 460 sCurrentLocale = locale; 461 clearCache(); 462 } 463 synchronized (sResourceCacheLock) { 464 return sResourceCache.get(getKey()); 465 } 466 } 467 loadNameAndIconForUid()468 private void loadNameAndIconForUid() { 469 final String packageName = getPackageName(); 470 final PackageManager packageManager = mContext.getPackageManager(); 471 // Gets the application label from PackageManager. 472 if (packageName != null && packageName.length() != 0) { 473 try { 474 final ApplicationInfo appInfo = 475 packageManager.getApplicationInfo(packageName, /*no flags*/ 0); 476 if (appInfo != null) { 477 mAppLabel = packageManager.getApplicationLabel(appInfo).toString(); 478 mAppIcon = packageManager.getApplicationIcon(appInfo); 479 } 480 } catch (NameNotFoundException e) { 481 Log.e(TAG, "failed to retrieve ApplicationInfo for: " + packageName); 482 mAppLabel = packageName; 483 } 484 } 485 // Early return if we found the app label and icon resource. 486 if (mAppLabel != null && mAppIcon != null) { 487 return; 488 } 489 490 final int uid = (int) mUid; 491 final String[] packages = packageManager.getPackagesForUid(uid); 492 // Loads special defined application label and icon if available. 493 if (packages == null || packages.length == 0) { 494 final NameAndIcon nameAndIcon = 495 BatteryEntry.getNameAndIconFromUid(mContext, mAppLabel, uid); 496 mAppLabel = nameAndIcon.mName; 497 mAppIcon = nameAndIcon.mIcon; 498 } 499 500 final NameAndIcon nameAndIcon = 501 BatteryEntry.loadNameAndIcon( 502 mContext, uid, /* batteryEntry= */ null, packageName, mAppLabel, mAppIcon); 503 // Clears BatteryEntry internal cache since we will have another one. 504 BatteryEntry.clearUidCache(); 505 if (nameAndIcon != null) { 506 mAppLabel = nameAndIcon.mName; 507 mAppIcon = nameAndIcon.mIcon; 508 mDefaultPackageName = nameAndIcon.mPackageName; 509 if (mDefaultPackageName != null 510 && !mDefaultPackageName.equals(nameAndIcon.mPackageName)) { 511 Log.w( 512 TAG, 513 String.format( 514 "found different package: %s | %s", 515 mDefaultPackageName, nameAndIcon.mPackageName)); 516 } 517 } 518 } 519 520 @Override toString()521 public String toString() { 522 final StringBuilder builder = new StringBuilder(); 523 builder.append("BatteryDiffEntry{"); 524 builder.append( 525 String.format("\n\tname=%s restrictable=%b", mAppLabel, mValidForRestriction)); 526 builder.append( 527 String.format( 528 "\n\tconsume=%.2f%% %f/%f", 529 mPercentage, mConsumePower, mTotalConsumePower)); 530 builder.append( 531 String.format( 532 "\n\tconsume power= foreground:%f foregroundService:%f", 533 mForegroundUsageConsumePower, mForegroundServiceUsageConsumePower)); 534 builder.append( 535 String.format( 536 "\n\tconsume power= background:%f cached:%f", 537 mBackgroundUsageConsumePower, mCachedUsageConsumePower)); 538 builder.append( 539 String.format( 540 "\n\ttime= foreground:%s foregroundService:%s " 541 + "background:%s screen-on:%s", 542 StringUtil.formatElapsedTime( 543 mContext, 544 (double) mForegroundUsageTimeInMs, 545 /* withSeconds= */ true, 546 /* collapseTimeUnit= */ false), 547 StringUtil.formatElapsedTime( 548 mContext, 549 (double) mForegroundServiceUsageTimeInMs, 550 /* withSeconds= */ true, 551 /* collapseTimeUnit= */ false), 552 StringUtil.formatElapsedTime( 553 mContext, 554 (double) mBackgroundUsageTimeInMs, 555 /* withSeconds= */ true, 556 /* collapseTimeUnit= */ false), 557 StringUtil.formatElapsedTime( 558 mContext, 559 (double) mScreenOnTimeInMs, 560 /* withSeconds= */ true, 561 /* collapseTimeUnit= */ false))); 562 builder.append( 563 String.format( 564 "\n\tpackage:%s|%s uid:%d userId:%d", 565 mLegacyPackageName, getPackageName(), mUid, mUserId)); 566 return builder.toString(); 567 } 568 569 /** Clears all cache data. */ clearCache()570 public static void clearCache() { 571 synchronized (sResourceCacheLock) { 572 sResourceCache.clear(); 573 } 574 synchronized (sValidForRestrictionLock) { 575 sValidForRestriction.clear(); 576 } 577 synchronized (sPackageNameAndUidCacheLock) { 578 sPackageNameAndUidCache.clear(); 579 } 580 } 581 putResourceCache(String key, NameAndIcon nameAndIcon)582 private static void putResourceCache(String key, NameAndIcon nameAndIcon) { 583 synchronized (sResourceCacheLock) { 584 sResourceCache.put(key, nameAndIcon); 585 } 586 } 587 getBadgeIconForUser(Drawable icon)588 private Drawable getBadgeIconForUser(Drawable icon) { 589 final int userId = UserHandle.getUserId((int) mUid); 590 return userId == UserHandle.USER_OWNER 591 ? icon 592 : mUserManager.getBadgedIconForUser(icon, new UserHandle(userId)); 593 } 594 } 595