• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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