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 17 package com.android.settings.fuelgauge.batteryusage; 18 19 import android.app.AppGlobals; 20 import android.content.Context; 21 import android.content.pm.ApplicationInfo; 22 import android.content.pm.IPackageManager; 23 import android.content.pm.PackageInfo; 24 import android.content.pm.PackageManager; 25 import android.content.pm.PackageManager.NameNotFoundException; 26 import android.content.pm.UserInfo; 27 import android.graphics.drawable.Drawable; 28 import android.os.BatteryConsumer; 29 import android.os.Handler; 30 import android.os.Process; 31 import android.os.RemoteException; 32 import android.os.UidBatteryConsumer; 33 import android.os.UserBatteryConsumer; 34 import android.os.UserHandle; 35 import android.os.UserManager; 36 import android.util.DebugUtils; 37 import android.util.Log; 38 39 import com.android.settings.R; 40 import com.android.settings.fuelgauge.BatteryUtils; 41 import com.android.settingslib.Utils; 42 43 import java.util.ArrayList; 44 import java.util.Comparator; 45 import java.util.HashMap; 46 import java.util.Locale; 47 48 /** 49 * Wraps the power usage data of a BatterySipper with information about package name 50 * and icon image. 51 */ 52 public class BatteryEntry { 53 54 /** The app name and icon in app list. */ 55 public static final class NameAndIcon { 56 public final String mName; 57 public final String mPackageName; 58 public final Drawable mIcon; 59 public final int mIconId; 60 NameAndIcon(String name, Drawable icon, int iconId)61 public NameAndIcon(String name, Drawable icon, int iconId) { 62 this(name, /*packageName=*/ null, icon, iconId); 63 } 64 NameAndIcon( String name, String packageName, Drawable icon, int iconId)65 public NameAndIcon( 66 String name, String packageName, Drawable icon, int iconId) { 67 this.mName = name; 68 this.mIcon = icon; 69 this.mIconId = iconId; 70 this.mPackageName = packageName; 71 } 72 } 73 74 public static final int MSG_UPDATE_NAME_ICON = 1; 75 public static final int MSG_REPORT_FULLY_DRAWN = 2; 76 77 private static final String TAG = "BatteryEntry"; 78 private static final String PACKAGE_SYSTEM = "android"; 79 80 static final HashMap<String, UidToDetail> sUidCache = new HashMap<>(); 81 82 static final ArrayList<BatteryEntry> sRequestQueue = new ArrayList<BatteryEntry>(); 83 static Handler sHandler; 84 85 static Locale sCurrentLocale = null; 86 87 private static class NameAndIconLoader extends Thread { 88 private boolean mAbort = false; 89 NameAndIconLoader()90 NameAndIconLoader() { 91 super("BatteryUsage Icon Loader"); 92 } 93 abort()94 public void abort() { 95 mAbort = true; 96 } 97 98 @Override run()99 public void run() { 100 while (true) { 101 BatteryEntry be; 102 synchronized (sRequestQueue) { 103 if (sRequestQueue.isEmpty() || mAbort) { 104 if (sHandler != null) { 105 sHandler.sendEmptyMessage(MSG_REPORT_FULLY_DRAWN); 106 } 107 return; 108 } 109 be = sRequestQueue.remove(0); 110 } 111 final NameAndIcon nameAndIcon = 112 BatteryEntry.loadNameAndIcon( 113 be.mContext, be.getUid(), sHandler, be, 114 be.mDefaultPackageName, be.mName, be.mIcon); 115 if (nameAndIcon != null) { 116 be.mIcon = nameAndIcon.mIcon; 117 be.mName = nameAndIcon.mName; 118 be.mDefaultPackageName = nameAndIcon.mPackageName; 119 } 120 } 121 } 122 } 123 124 private static NameAndIconLoader sRequestThread; 125 126 /** Starts the request queue. */ startRequestQueue()127 public static void startRequestQueue() { 128 if (sHandler != null) { 129 synchronized (sRequestQueue) { 130 if (!sRequestQueue.isEmpty()) { 131 if (sRequestThread != null) { 132 sRequestThread.abort(); 133 } 134 sRequestThread = new NameAndIconLoader(); 135 sRequestThread.setPriority(Thread.MIN_PRIORITY); 136 sRequestThread.start(); 137 sRequestQueue.notify(); 138 } 139 } 140 } 141 } 142 143 /** Stops the request queue. */ stopRequestQueue()144 public static void stopRequestQueue() { 145 synchronized (sRequestQueue) { 146 if (sRequestThread != null) { 147 sRequestThread.abort(); 148 sRequestThread = null; 149 sRequestQueue.clear(); 150 sHandler = null; 151 } 152 } 153 } 154 155 /** Clears the UID cache. */ clearUidCache()156 public static void clearUidCache() { 157 sUidCache.clear(); 158 } 159 160 public static final Comparator<BatteryEntry> COMPARATOR = 161 (a, b) -> Double.compare(b.getConsumedPower(), a.getConsumedPower()); 162 163 private final Context mContext; 164 private final BatteryConsumer mBatteryConsumer; 165 private final int mUid; 166 private final boolean mIsHidden; 167 @ConvertUtils.ConsumerType 168 private final int mConsumerType; 169 @BatteryConsumer.PowerComponent 170 private final int mPowerComponentId; 171 private long mUsageDurationMs; 172 private long mTimeInForegroundMs; 173 private long mTimeInBackgroundMs; 174 175 public String mName; 176 public Drawable mIcon; 177 public int mIconId; 178 public double mPercent; 179 private String mDefaultPackageName; 180 private double mConsumedPower; 181 182 static class UidToDetail { 183 String mName; 184 String mPackageName; 185 Drawable mIcon; 186 } 187 BatteryEntry(Context context, Handler handler, UserManager um, BatteryConsumer batteryConsumer, boolean isHidden, int uid, String[] packages, String packageName)188 public BatteryEntry(Context context, Handler handler, UserManager um, 189 BatteryConsumer batteryConsumer, boolean isHidden, int uid, String[] packages, 190 String packageName) { 191 this(context, handler, um, batteryConsumer, isHidden, uid, packages, packageName, true); 192 } 193 BatteryEntry(Context context, Handler handler, UserManager um, BatteryConsumer batteryConsumer, boolean isHidden, int uid, String[] packages, String packageName, boolean loadDataInBackground)194 public BatteryEntry(Context context, Handler handler, UserManager um, 195 BatteryConsumer batteryConsumer, boolean isHidden, int uid, String[] packages, 196 String packageName, boolean loadDataInBackground) { 197 sHandler = handler; 198 mContext = context; 199 mBatteryConsumer = batteryConsumer; 200 mIsHidden = isHidden; 201 mDefaultPackageName = packageName; 202 mPowerComponentId = -1; 203 204 if (batteryConsumer instanceof UidBatteryConsumer) { 205 mUid = uid; 206 mConsumerType = ConvertUtils.CONSUMER_TYPE_UID_BATTERY; 207 mConsumedPower = batteryConsumer.getConsumedPower(); 208 209 UidBatteryConsumer uidBatteryConsumer = (UidBatteryConsumer) batteryConsumer; 210 if (mDefaultPackageName == null) { 211 // Apps should only have one package 212 if (packages != null && packages.length == 1) { 213 mDefaultPackageName = packages[0]; 214 } else { 215 mDefaultPackageName = isSystemUid(uid) 216 ? PACKAGE_SYSTEM : uidBatteryConsumer.getPackageWithHighestDrain(); 217 } 218 } 219 if (mDefaultPackageName != null) { 220 PackageManager pm = context.getPackageManager(); 221 try { 222 ApplicationInfo appInfo = 223 pm.getApplicationInfo(mDefaultPackageName, 0 /* no flags */); 224 mName = pm.getApplicationLabel(appInfo).toString(); 225 } catch (NameNotFoundException e) { 226 Log.d(TAG, "PackageManager failed to retrieve ApplicationInfo for: " 227 + mDefaultPackageName); 228 mName = mDefaultPackageName; 229 } 230 } 231 getQuickNameIconForUid(uid, packages, loadDataInBackground); 232 mTimeInForegroundMs = 233 uidBatteryConsumer.getTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND); 234 mTimeInBackgroundMs = 235 uidBatteryConsumer.getTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND); 236 } else if (batteryConsumer instanceof UserBatteryConsumer) { 237 mUid = Process.INVALID_UID; 238 mConsumerType = ConvertUtils.CONSUMER_TYPE_USER_BATTERY; 239 mConsumedPower = batteryConsumer.getConsumedPower(); 240 final NameAndIcon nameAndIcon = getNameAndIconFromUserId( 241 context, ((UserBatteryConsumer) batteryConsumer).getUserId()); 242 mIcon = nameAndIcon.mIcon; 243 mName = nameAndIcon.mName; 244 } else { 245 throw new IllegalArgumentException("Unsupported: " + batteryConsumer); 246 } 247 } 248 249 /** Battery entry for a power component of AggregateBatteryConsumer */ BatteryEntry(Context context, int powerComponentId, double devicePowerMah, double appsPowerMah, long usageDurationMs)250 public BatteryEntry(Context context, int powerComponentId, double devicePowerMah, 251 double appsPowerMah, long usageDurationMs) { 252 mContext = context; 253 mBatteryConsumer = null; 254 mUid = Process.INVALID_UID; 255 mIsHidden = false; 256 mPowerComponentId = powerComponentId; 257 mConsumedPower = 258 powerComponentId == BatteryConsumer.POWER_COMPONENT_SCREEN 259 ? devicePowerMah 260 : devicePowerMah - appsPowerMah; 261 mUsageDurationMs = usageDurationMs; 262 mConsumerType = ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY; 263 264 final NameAndIcon nameAndIcon = 265 getNameAndIconFromPowerComponent(context, powerComponentId); 266 mIconId = nameAndIcon.mIconId; 267 mName = nameAndIcon.mName; 268 if (mIconId != 0) { 269 mIcon = context.getDrawable(mIconId); 270 } 271 } 272 273 /** Battery entry for a custom power component of AggregateBatteryConsumer */ BatteryEntry(Context context, int powerComponentId, String powerComponentName, double devicePowerMah, double appsPowerMah)274 public BatteryEntry(Context context, int powerComponentId, String powerComponentName, 275 double devicePowerMah, double appsPowerMah) { 276 mContext = context; 277 mBatteryConsumer = null; 278 mUid = Process.INVALID_UID; 279 mIsHidden = false; 280 mPowerComponentId = powerComponentId; 281 282 mIconId = R.drawable.ic_power_system; 283 mIcon = context.getDrawable(mIconId); 284 mName = powerComponentName; 285 mConsumedPower = 286 powerComponentId == BatteryConsumer.POWER_COMPONENT_SCREEN 287 ? devicePowerMah 288 : devicePowerMah - appsPowerMah; 289 mConsumerType = ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY; 290 } 291 getIcon()292 public Drawable getIcon() { 293 return mIcon; 294 } 295 getLabel()296 public String getLabel() { 297 return mName; 298 } 299 300 @ConvertUtils.ConsumerType getConsumerType()301 public int getConsumerType() { 302 return mConsumerType; 303 } 304 305 @BatteryConsumer.PowerComponent getPowerComponentId()306 public int getPowerComponentId() { 307 return mPowerComponentId; 308 } 309 getQuickNameIconForUid( final int uid, final String[] packages, final boolean loadDataInBackground)310 void getQuickNameIconForUid( 311 final int uid, final String[] packages, final boolean loadDataInBackground) { 312 // Locale sync to system config in Settings 313 final Locale locale = Locale.getDefault(); 314 if (sCurrentLocale != locale) { 315 clearUidCache(); 316 sCurrentLocale = locale; 317 } 318 319 final String uidString = Integer.toString(uid); 320 if (sUidCache.containsKey(uidString)) { 321 UidToDetail utd = sUidCache.get(uidString); 322 mDefaultPackageName = utd.mPackageName; 323 mName = utd.mName; 324 mIcon = utd.mIcon; 325 return; 326 } 327 328 if (packages == null || packages.length == 0) { 329 final NameAndIcon nameAndIcon = getNameAndIconFromUid(mContext, mName, uid); 330 mIcon = nameAndIcon.mIcon; 331 mName = nameAndIcon.mName; 332 } else { 333 mIcon = mContext.getPackageManager().getDefaultActivityIcon(); 334 } 335 336 // Avoids post the loading icon and label in the background request. 337 if (sHandler != null && loadDataInBackground) { 338 synchronized (sRequestQueue) { 339 sRequestQueue.add(this); 340 } 341 } 342 } 343 344 /** Loads the app label and icon image and stores into the cache. */ loadNameAndIcon( Context context, int uid, Handler handler, BatteryEntry batteryEntry, String defaultPackageName, String name, Drawable icon)345 public static NameAndIcon loadNameAndIcon( 346 Context context, 347 int uid, 348 Handler handler, 349 BatteryEntry batteryEntry, 350 String defaultPackageName, 351 String name, 352 Drawable icon) { 353 // Bail out if the current sipper is not an App sipper. 354 if (uid == 0 || uid == Process.INVALID_UID) { 355 return null; 356 } 357 358 final PackageManager pm = context.getPackageManager(); 359 final String[] packages = isSystemUid(uid) 360 ? new String[]{PACKAGE_SYSTEM} : pm.getPackagesForUid(uid); 361 if (packages != null) { 362 final String[] packageLabels = new String[packages.length]; 363 System.arraycopy(packages, 0, packageLabels, 0, packages.length); 364 365 // Convert package names to user-facing labels where possible 366 final IPackageManager ipm = AppGlobals.getPackageManager(); 367 final int userId = UserHandle.getUserId(uid); 368 for (int i = 0; i < packageLabels.length; i++) { 369 try { 370 final ApplicationInfo ai = ipm.getApplicationInfo(packageLabels[i], 371 0 /* no flags */, userId); 372 if (ai == null) { 373 Log.d(TAG, "Retrieving null app info for package " 374 + packageLabels[i] + ", user " + userId); 375 continue; 376 } 377 final CharSequence label = ai.loadLabel(pm); 378 if (label != null) { 379 packageLabels[i] = label.toString(); 380 } 381 if (ai.icon != 0) { 382 defaultPackageName = packages[i]; 383 icon = ai.loadIcon(pm); 384 break; 385 } 386 } catch (RemoteException e) { 387 Log.d(TAG, "Error while retrieving app info for package " 388 + packageLabels[i] + ", user " + userId, e); 389 } 390 } 391 392 if (packageLabels.length == 1) { 393 name = packageLabels[0]; 394 } else { 395 // Look for an official name for this UID. 396 for (String pkgName : packages) { 397 try { 398 final PackageInfo pi = ipm.getPackageInfo(pkgName, 0, userId); 399 if (pi == null) { 400 Log.d(TAG, "Retrieving null package info for package " 401 + pkgName + ", user " + userId); 402 continue; 403 } 404 if (pi.sharedUserLabel != 0) { 405 final CharSequence nm = pm.getText(pkgName, 406 pi.sharedUserLabel, pi.applicationInfo); 407 if (nm != null) { 408 name = nm.toString(); 409 if (pi.applicationInfo.icon != 0) { 410 defaultPackageName = pkgName; 411 icon = pi.applicationInfo.loadIcon(pm); 412 } 413 break; 414 } 415 } 416 } catch (RemoteException e) { 417 Log.d(TAG, "Error while retrieving package info for package " 418 + pkgName + ", user " + userId, e); 419 } 420 } 421 } 422 } 423 424 final String uidString = Integer.toString(uid); 425 if (icon == null) { 426 icon = pm.getDefaultActivityIcon(); 427 } 428 429 UidToDetail utd = new UidToDetail(); 430 utd.mName = name; 431 utd.mIcon = icon; 432 utd.mPackageName = defaultPackageName; 433 434 sUidCache.put(uidString, utd); 435 if (handler != null) { 436 handler.sendMessage(handler.obtainMessage(MSG_UPDATE_NAME_ICON, batteryEntry)); 437 } 438 return new NameAndIcon(name, defaultPackageName, icon, /*iconId=*/ 0); 439 } 440 441 /** Returns a string that uniquely identifies this battery consumer. */ getKey()442 public String getKey() { 443 if (mBatteryConsumer instanceof UidBatteryConsumer) { 444 return Integer.toString(mUid); 445 } else if (mBatteryConsumer instanceof UserBatteryConsumer) { 446 return "U|" + ((UserBatteryConsumer) mBatteryConsumer).getUserId(); 447 } else { 448 return "S|" + mPowerComponentId; 449 } 450 } 451 452 /** Returns true if the entry is hidden from the battery usage summary list. */ isHidden()453 public boolean isHidden() { 454 return mIsHidden; 455 } 456 457 /** Returns true if this entry describes an app (UID). */ isAppEntry()458 public boolean isAppEntry() { 459 return mBatteryConsumer instanceof UidBatteryConsumer; 460 } 461 462 /** Returns true if this entry describes a User. */ isUserEntry()463 public boolean isUserEntry() { 464 if (mBatteryConsumer instanceof UserBatteryConsumer) { 465 return true; 466 } 467 return false; 468 } 469 470 /** 471 * Returns the package name that should be used to represent the UID described 472 * by this entry. 473 */ getDefaultPackageName()474 public String getDefaultPackageName() { 475 return mDefaultPackageName; 476 } 477 478 /** 479 * Returns the UID of the app described by this entry. 480 */ getUid()481 public int getUid() { 482 return mUid; 483 } 484 485 /** Returns foreground foreground time/ms that is attributed to this entry. */ getTimeInForegroundMs()486 public long getTimeInForegroundMs() { 487 if (mBatteryConsumer instanceof UidBatteryConsumer) { 488 return mTimeInForegroundMs; 489 } else { 490 return mUsageDurationMs; 491 } 492 } 493 494 /** Returns background activity time/ms that is attributed to this entry. */ getTimeInBackgroundMs()495 public long getTimeInBackgroundMs() { 496 if (mBatteryConsumer instanceof UidBatteryConsumer) { 497 return mTimeInBackgroundMs; 498 } else { 499 return 0; 500 } 501 } 502 503 /** 504 * Returns total amount of power (in milli-amp-hours) that is attributed to this entry. 505 */ getConsumedPower()506 public double getConsumedPower() { 507 return mConsumedPower; 508 } 509 510 /** 511 * Adds the consumed power of the supplied BatteryConsumer to this entry. Also 512 * uses its package with highest drain, if necessary. 513 */ add(BatteryConsumer batteryConsumer)514 public void add(BatteryConsumer batteryConsumer) { 515 mConsumedPower += batteryConsumer.getConsumedPower(); 516 if (batteryConsumer instanceof UidBatteryConsumer) { 517 UidBatteryConsumer uidBatteryConsumer = (UidBatteryConsumer) batteryConsumer; 518 mTimeInForegroundMs += uidBatteryConsumer.getTimeInStateMs( 519 UidBatteryConsumer.STATE_FOREGROUND); 520 mTimeInBackgroundMs += uidBatteryConsumer.getTimeInStateMs( 521 UidBatteryConsumer.STATE_BACKGROUND); 522 if (mDefaultPackageName == null) { 523 mDefaultPackageName = uidBatteryConsumer.getPackageWithHighestDrain(); 524 } 525 } 526 } 527 528 /** Gets name and icon resource from UserBatteryConsumer userId. */ getNameAndIconFromUserId( Context context, final int userId)529 public static NameAndIcon getNameAndIconFromUserId( 530 Context context, final int userId) { 531 UserManager um = context.getSystemService(UserManager.class); 532 UserInfo info = um.getUserInfo(userId); 533 534 Drawable icon = null; 535 String name = null; 536 if (info != null) { 537 icon = Utils.getUserIcon(context, um, info); 538 name = Utils.getUserLabel(context, info); 539 } else { 540 name = context.getResources().getString( 541 R.string.running_process_item_removed_user_label); 542 } 543 return new NameAndIcon(name, icon, 0 /* iconId */); 544 } 545 546 /** Gets name and icon resource from UidBatteryConsumer uid. */ getNameAndIconFromUid( Context context, String name, final int uid)547 public static NameAndIcon getNameAndIconFromUid( 548 Context context, String name, final int uid) { 549 Drawable icon = context.getDrawable(R.drawable.ic_power_system); 550 if (uid == 0) { 551 name = context.getResources().getString(R.string.process_kernel_label); 552 } else if (uid == BatteryUtils.UID_REMOVED_APPS) { 553 name = context.getResources().getString(R.string.process_removed_apps); 554 } else if (uid == BatteryUtils.UID_TETHERING) { 555 name = context.getResources().getString(R.string.process_network_tethering); 556 } else if ("mediaserver".equals(name)) { 557 name = context.getResources().getString(R.string.process_mediaserver_label); 558 } else if ("dex2oat".equals(name) || "dex2oat32".equals(name) 559 || "dex2oat64".equals(name)) { 560 name = context.getResources().getString(R.string.process_dex2oat_label); 561 } 562 return new NameAndIcon(name, icon, 0 /* iconId */); 563 } 564 565 /** Gets name and icon resource from BatteryConsumer power component ID. */ getNameAndIconFromPowerComponent( Context context, @BatteryConsumer.PowerComponent int powerComponentId)566 public static NameAndIcon getNameAndIconFromPowerComponent( 567 Context context, @BatteryConsumer.PowerComponent int powerComponentId) { 568 String name; 569 int iconId; 570 switch (powerComponentId) { 571 case BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY: 572 name = context.getResources().getString(R.string.ambient_display_screen_title); 573 iconId = R.drawable.ic_settings_aod; 574 break; 575 case BatteryConsumer.POWER_COMPONENT_BLUETOOTH: 576 name = context.getResources().getString(R.string.power_bluetooth); 577 iconId = R.drawable.ic_settings_bluetooth; 578 break; 579 case BatteryConsumer.POWER_COMPONENT_CAMERA: 580 name = context.getResources().getString(R.string.power_camera); 581 iconId = R.drawable.ic_settings_camera; 582 break; 583 case BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO: 584 name = context.getResources().getString(R.string.power_cell); 585 iconId = R.drawable.ic_cellular_1_bar; 586 break; 587 case BatteryConsumer.POWER_COMPONENT_FLASHLIGHT: 588 name = context.getResources().getString(R.string.power_flashlight); 589 iconId = R.drawable.ic_settings_display; 590 break; 591 case BatteryConsumer.POWER_COMPONENT_PHONE: 592 name = context.getResources().getString(R.string.power_phone); 593 iconId = R.drawable.ic_settings_voice_calls; 594 break; 595 case BatteryConsumer.POWER_COMPONENT_SCREEN: 596 name = context.getResources().getString(R.string.power_screen); 597 iconId = R.drawable.ic_settings_display; 598 break; 599 case BatteryConsumer.POWER_COMPONENT_WIFI: 600 name = context.getResources().getString(R.string.power_wifi); 601 iconId = R.drawable.ic_settings_wireless_no_theme; 602 break; 603 case BatteryConsumer.POWER_COMPONENT_IDLE: 604 case BatteryConsumer.POWER_COMPONENT_MEMORY: 605 name = context.getResources().getString(R.string.power_idle); 606 iconId = R.drawable.ic_settings_phone_idle; 607 break; 608 default: 609 Log.w(TAG, "unknown attribute:" + DebugUtils.constantToString( 610 BatteryConsumer.class, "POWER_COMPONENT_", powerComponentId)); 611 name = null; 612 iconId = R.drawable.ic_power_system; 613 break; 614 } 615 return new NameAndIcon(name, null /* icon */, iconId); 616 } 617 618 /** Whether the uid is system uid. */ isSystemUid(int uid)619 public static boolean isSystemUid(int uid) { 620 return uid == Process.SYSTEM_UID; 621 } 622 } 623