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