| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.settings.fuelgauge; |
| |
| import android.app.AppGlobals; |
| import android.content.Context; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.IPackageManager; |
| import android.content.pm.PackageInfo; |
| import android.content.pm.PackageManager; |
| import android.content.pm.PackageManager.NameNotFoundException; |
| import android.content.pm.UserInfo; |
| import android.graphics.drawable.Drawable; |
| import android.os.BatteryConsumer; |
| import android.os.Handler; |
| import android.os.Process; |
| import android.os.RemoteException; |
| import android.os.UidBatteryConsumer; |
| import android.os.UserBatteryConsumer; |
| import android.os.UserHandle; |
| import android.os.UserManager; |
| import android.util.DebugUtils; |
| import android.util.Log; |
| |
| import androidx.annotation.NonNull; |
| |
| import com.android.settings.R; |
| import com.android.settingslib.Utils; |
| |
| import java.util.ArrayList; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.Locale; |
| |
| /** |
| * Wraps the power usage data of a BatterySipper with information about package name |
| * and icon image. |
| */ |
| public class BatteryEntry { |
| |
| public static final class NameAndIcon { |
| public final String name; |
| public final String packageName; |
| public final Drawable icon; |
| public final int iconId; |
| |
| public NameAndIcon(String name, Drawable icon, int iconId) { |
| this(name, /*packageName=*/ null, icon, iconId); |
| } |
| |
| public NameAndIcon( |
| String name, String packageName, Drawable icon, int iconId) { |
| this.name = name; |
| this.icon = icon; |
| this.iconId = iconId; |
| this.packageName = packageName; |
| } |
| } |
| |
| public static final int MSG_UPDATE_NAME_ICON = 1; |
| public static final int MSG_REPORT_FULLY_DRAWN = 2; |
| |
| private static final String TAG = "BatteryEntry"; |
| private static final String PACKAGE_SYSTEM = "android"; |
| |
| static final HashMap<String, UidToDetail> sUidCache = new HashMap<>(); |
| |
| static final ArrayList<BatteryEntry> sRequestQueue = new ArrayList<BatteryEntry>(); |
| static Handler sHandler; |
| |
| static Locale sCurrentLocale = null; |
| |
| static private class NameAndIconLoader extends Thread { |
| private boolean mAbort = false; |
| |
| public NameAndIconLoader() { |
| super("BatteryUsage Icon Loader"); |
| } |
| |
| public void abort() { |
| mAbort = true; |
| } |
| |
| @Override |
| public void run() { |
| while (true) { |
| BatteryEntry be; |
| synchronized (sRequestQueue) { |
| if (sRequestQueue.isEmpty() || mAbort) { |
| if (sHandler != null) { |
| sHandler.sendEmptyMessage(MSG_REPORT_FULLY_DRAWN); |
| } |
| return; |
| } |
| be = sRequestQueue.remove(0); |
| } |
| final NameAndIcon nameAndIcon = |
| BatteryEntry.loadNameAndIcon( |
| be.mContext, be.getUid(), sHandler, be, |
| be.mDefaultPackageName, be.name, be.icon); |
| if (nameAndIcon != null) { |
| be.icon = nameAndIcon.icon; |
| be.name = nameAndIcon.name; |
| be.mDefaultPackageName = nameAndIcon.packageName; |
| } |
| } |
| } |
| } |
| |
| private static NameAndIconLoader mRequestThread; |
| |
| public static void startRequestQueue() { |
| if (sHandler != null) { |
| synchronized (sRequestQueue) { |
| if (!sRequestQueue.isEmpty()) { |
| if (mRequestThread != null) { |
| mRequestThread.abort(); |
| } |
| mRequestThread = new NameAndIconLoader(); |
| mRequestThread.setPriority(Thread.MIN_PRIORITY); |
| mRequestThread.start(); |
| sRequestQueue.notify(); |
| } |
| } |
| } |
| } |
| |
| public static void stopRequestQueue() { |
| synchronized (sRequestQueue) { |
| if (mRequestThread != null) { |
| mRequestThread.abort(); |
| mRequestThread = null; |
| sRequestQueue.clear(); |
| sHandler = null; |
| } |
| } |
| } |
| |
| public static void clearUidCache() { |
| sUidCache.clear(); |
| } |
| |
| public static final Comparator<BatteryEntry> COMPARATOR = |
| (a, b) -> Double.compare(b.getConsumedPower(), a.getConsumedPower()); |
| |
| private final Context mContext; |
| private final BatteryConsumer mBatteryConsumer; |
| private final boolean mIsHidden; |
| @ConvertUtils.ConsumerType |
| private final int mConsumerType; |
| @BatteryConsumer.PowerComponent |
| private final int mPowerComponentId; |
| private long mUsageDurationMs; |
| |
| public String name; |
| public Drawable icon; |
| public int iconId; // For passing to the detail screen. |
| public double percent; |
| private String mDefaultPackageName; |
| private double mConsumedPower; |
| |
| static class UidToDetail { |
| String name; |
| String packageName; |
| Drawable icon; |
| } |
| |
| public BatteryEntry(Context context, Handler handler, UserManager um, |
| @NonNull BatteryConsumer batteryConsumer, boolean isHidden, String[] packages, |
| String packageName) { |
| this(context, handler, um, batteryConsumer, isHidden, packages, packageName, true); |
| } |
| |
| public BatteryEntry(Context context, Handler handler, UserManager um, |
| @NonNull BatteryConsumer batteryConsumer, boolean isHidden, String[] packages, |
| String packageName, boolean loadDataInBackground) { |
| sHandler = handler; |
| mContext = context; |
| mBatteryConsumer = batteryConsumer; |
| mIsHidden = isHidden; |
| mDefaultPackageName = packageName; |
| mPowerComponentId = -1; |
| |
| if (batteryConsumer instanceof UidBatteryConsumer) { |
| mConsumerType = ConvertUtils.CONSUMER_TYPE_UID_BATTERY; |
| mConsumedPower = batteryConsumer.getConsumedPower(); |
| |
| UidBatteryConsumer uidBatteryConsumer = (UidBatteryConsumer) batteryConsumer; |
| int uid = uidBatteryConsumer.getUid(); |
| if (mDefaultPackageName == null) { |
| // Apps should only have one package |
| if (packages != null && packages.length == 1) { |
| mDefaultPackageName = packages[0]; |
| } else { |
| mDefaultPackageName = uidBatteryConsumer.getPackageWithHighestDrain(); |
| } |
| } |
| if (mDefaultPackageName != null) { |
| PackageManager pm = context.getPackageManager(); |
| try { |
| ApplicationInfo appInfo = |
| pm.getApplicationInfo(mDefaultPackageName, 0 /* no flags */); |
| name = pm.getApplicationLabel(appInfo).toString(); |
| } catch (NameNotFoundException e) { |
| Log.d(TAG, "PackageManager failed to retrieve ApplicationInfo for: " |
| + mDefaultPackageName); |
| name = mDefaultPackageName; |
| } |
| } |
| getQuickNameIconForUid(uid, packages, loadDataInBackground); |
| } else if (batteryConsumer instanceof UserBatteryConsumer) { |
| mConsumerType = ConvertUtils.CONSUMER_TYPE_USER_BATTERY; |
| mConsumedPower = batteryConsumer.getConsumedPower(); |
| final NameAndIcon nameAndIcon = getNameAndIconFromUserId( |
| context, ((UserBatteryConsumer) batteryConsumer).getUserId()); |
| icon = nameAndIcon.icon; |
| name = nameAndIcon.name; |
| } else { |
| throw new IllegalArgumentException("Unsupported battery consumer: " + batteryConsumer); |
| } |
| } |
| |
| /** Battery entry for a power component of AggregateBatteryConsumer */ |
| public BatteryEntry(Context context, int powerComponentId, double devicePowerMah, |
| double appsPowerMah, long usageDurationMs) { |
| mContext = context; |
| mBatteryConsumer = null; |
| mIsHidden = false; |
| mPowerComponentId = powerComponentId; |
| mConsumedPower = devicePowerMah - appsPowerMah; |
| mUsageDurationMs = usageDurationMs; |
| mConsumerType = ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY; |
| |
| final NameAndIcon nameAndIcon = getNameAndIconFromPowerComponent(context, powerComponentId); |
| iconId = nameAndIcon.iconId; |
| name = nameAndIcon.name; |
| if (iconId != 0) { |
| icon = context.getDrawable(iconId); |
| } |
| } |
| |
| /** Battery entry for a custom power component of AggregateBatteryConsumer */ |
| public BatteryEntry(Context context, int powerComponentId, String powerComponentName, |
| double devicePowerMah, double appsPowerMah) { |
| mContext = context; |
| mBatteryConsumer = null; |
| mIsHidden = false; |
| mPowerComponentId = powerComponentId; |
| |
| iconId = R.drawable.ic_power_system; |
| icon = context.getDrawable(iconId); |
| name = powerComponentName; |
| |
| mConsumedPower = devicePowerMah - appsPowerMah; |
| mConsumerType = ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY; |
| } |
| |
| |
| public Drawable getIcon() { |
| return icon; |
| } |
| |
| /** |
| * Gets the application name |
| */ |
| public String getLabel() { |
| return name; |
| } |
| |
| @ConvertUtils.ConsumerType |
| public int getConsumerType() { |
| return mConsumerType; |
| } |
| |
| @BatteryConsumer.PowerComponent |
| public int getPowerComponentId() { |
| return mPowerComponentId; |
| } |
| |
| void getQuickNameIconForUid( |
| final int uid, final String[] packages, final boolean loadDataInBackground) { |
| // Locale sync to system config in Settings |
| final Locale locale = Locale.getDefault(); |
| if (sCurrentLocale != locale) { |
| clearUidCache(); |
| sCurrentLocale = locale; |
| } |
| |
| final String uidString = Integer.toString(uid); |
| if (sUidCache.containsKey(uidString)) { |
| UidToDetail utd = sUidCache.get(uidString); |
| mDefaultPackageName = utd.packageName; |
| name = utd.name; |
| icon = utd.icon; |
| return; |
| } |
| |
| if (packages == null || packages.length == 0) { |
| final NameAndIcon nameAndIcon = getNameAndIconFromUid(mContext, name, uid); |
| icon = nameAndIcon.icon; |
| name = nameAndIcon.name; |
| } else { |
| icon = mContext.getPackageManager().getDefaultActivityIcon(); |
| } |
| |
| // Avoids post the loading icon and label in the background request. |
| if (sHandler != null && loadDataInBackground) { |
| synchronized (sRequestQueue) { |
| sRequestQueue.add(this); |
| } |
| } |
| } |
| |
| /** |
| * Loads the app label and icon image and stores into the cache. |
| */ |
| public static NameAndIcon loadNameAndIcon( |
| Context context, |
| int uid, |
| Handler handler, |
| BatteryEntry batteryEntry, |
| String defaultPackageName, |
| String name, |
| Drawable icon) { |
| // Bail out if the current sipper is not an App sipper. |
| if (uid == 0 || uid == Process.INVALID_UID) { |
| return null; |
| } |
| |
| final PackageManager pm = context.getPackageManager(); |
| final String[] packages; |
| if (uid == Process.SYSTEM_UID) { |
| packages = new String[] {PACKAGE_SYSTEM}; |
| } else { |
| packages = pm.getPackagesForUid(uid); |
| } |
| |
| if (packages != null) { |
| final String[] packageLabels = new String[packages.length]; |
| System.arraycopy(packages, 0, packageLabels, 0, packages.length); |
| |
| // Convert package names to user-facing labels where possible |
| final IPackageManager ipm = AppGlobals.getPackageManager(); |
| final int userId = UserHandle.getUserId(uid); |
| for (int i = 0; i < packageLabels.length; i++) { |
| try { |
| final ApplicationInfo ai = ipm.getApplicationInfo(packageLabels[i], |
| 0 /* no flags */, userId); |
| if (ai == null) { |
| Log.d(TAG, "Retrieving null app info for package " |
| + packageLabels[i] + ", user " + userId); |
| continue; |
| } |
| final CharSequence label = ai.loadLabel(pm); |
| if (label != null) { |
| packageLabels[i] = label.toString(); |
| } |
| if (ai.icon != 0) { |
| defaultPackageName = packages[i]; |
| icon = ai.loadIcon(pm); |
| break; |
| } |
| } catch (RemoteException e) { |
| Log.d(TAG, "Error while retrieving app info for package " |
| + packageLabels[i] + ", user " + userId, e); |
| } |
| } |
| |
| if (packageLabels.length == 1) { |
| name = packageLabels[0]; |
| } else { |
| // Look for an official name for this UID. |
| for (String pkgName : packages) { |
| try { |
| final PackageInfo pi = ipm.getPackageInfo(pkgName, 0 /* no flags */, userId); |
| if (pi == null) { |
| Log.d(TAG, "Retrieving null package info for package " |
| + pkgName + ", user " + userId); |
| continue; |
| } |
| if (pi.sharedUserLabel != 0) { |
| final CharSequence nm = pm.getText(pkgName, |
| pi.sharedUserLabel, pi.applicationInfo); |
| if (nm != null) { |
| name = nm.toString(); |
| if (pi.applicationInfo.icon != 0) { |
| defaultPackageName = pkgName; |
| icon = pi.applicationInfo.loadIcon(pm); |
| } |
| break; |
| } |
| } |
| } catch (RemoteException e) { |
| Log.d(TAG, "Error while retrieving package info for package " |
| + pkgName + ", user " + userId, e); |
| } |
| } |
| } |
| } |
| |
| final String uidString = Integer.toString(uid); |
| if (name == null) { |
| name = uidString; |
| } |
| |
| if (icon == null) { |
| icon = pm.getDefaultActivityIcon(); |
| } |
| |
| UidToDetail utd = new UidToDetail(); |
| utd.name = name; |
| utd.icon = icon; |
| utd.packageName = defaultPackageName; |
| |
| sUidCache.put(uidString, utd); |
| if (handler != null) { |
| handler.sendMessage(handler.obtainMessage(MSG_UPDATE_NAME_ICON, batteryEntry)); |
| } |
| return new NameAndIcon(name, defaultPackageName, icon, /*iconId=*/ 0); |
| } |
| |
| /** |
| * Returns a string that uniquely identifies this battery consumer. |
| */ |
| public String getKey() { |
| if (mBatteryConsumer instanceof UidBatteryConsumer) { |
| return Integer.toString(((UidBatteryConsumer) mBatteryConsumer).getUid()); |
| } else if (mBatteryConsumer instanceof UserBatteryConsumer) { |
| return "U|" + ((UserBatteryConsumer) mBatteryConsumer).getUserId(); |
| } else { |
| return "S|" + mPowerComponentId; |
| } |
| } |
| |
| /** |
| * Returns true if the entry is hidden from the battery usage summary list. |
| */ |
| public boolean isHidden() { |
| return mIsHidden; |
| } |
| |
| /** |
| * Returns true if this entry describes an app (UID) |
| */ |
| public boolean isAppEntry() { |
| return mBatteryConsumer instanceof UidBatteryConsumer; |
| } |
| |
| /** |
| * Returns true if this entry describes a User. |
| */ |
| public boolean isUserEntry() { |
| if (mBatteryConsumer instanceof UserBatteryConsumer) { |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Returns the package name that should be used to represent the UID described |
| * by this entry. |
| */ |
| public String getDefaultPackageName() { |
| return mDefaultPackageName; |
| } |
| |
| /** |
| * Returns the UID of the app described by this entry. |
| */ |
| public int getUid() { |
| if (mBatteryConsumer instanceof UidBatteryConsumer) { |
| return ((UidBatteryConsumer) mBatteryConsumer).getUid(); |
| } else { |
| return Process.INVALID_UID; |
| } |
| } |
| |
| /** |
| * Returns foreground foreground time (in milliseconds) that is attributed to this entry. |
| */ |
| public long getTimeInForegroundMs() { |
| if (mBatteryConsumer instanceof UidBatteryConsumer) { |
| return ((UidBatteryConsumer) mBatteryConsumer).getTimeInStateMs( |
| UidBatteryConsumer.STATE_FOREGROUND); |
| } else { |
| return mUsageDurationMs; |
| } |
| } |
| |
| /** |
| * Returns background activity time (in milliseconds) that is attributed to this entry. |
| */ |
| public long getTimeInBackgroundMs() { |
| if (mBatteryConsumer instanceof UidBatteryConsumer) { |
| return ((UidBatteryConsumer) mBatteryConsumer).getTimeInStateMs( |
| UidBatteryConsumer.STATE_BACKGROUND); |
| } else { |
| return 0; |
| } |
| } |
| |
| /** |
| * Returns total amount of power (in milli-amp-hours) that is attributed to this entry. |
| */ |
| public double getConsumedPower() { |
| return mConsumedPower; |
| } |
| |
| /** |
| * Adds the consumed power of the supplied BatteryConsumer to this entry. Also |
| * uses its package with highest drain, if necessary. |
| */ |
| public void add(BatteryConsumer batteryConsumer) { |
| mConsumedPower += batteryConsumer.getConsumedPower(); |
| if (mDefaultPackageName == null && batteryConsumer instanceof UidBatteryConsumer) { |
| mDefaultPackageName = |
| ((UidBatteryConsumer) batteryConsumer).getPackageWithHighestDrain(); |
| } |
| } |
| |
| /** |
| * Gets name and icon resource from UserBatteryConsumer userId. |
| */ |
| public static NameAndIcon getNameAndIconFromUserId( |
| Context context, final int userId) { |
| UserManager um = context.getSystemService(UserManager.class); |
| UserInfo info = um.getUserInfo(userId); |
| |
| Drawable icon = null; |
| String name = null; |
| if (info != null) { |
| icon = Utils.getUserIcon(context, um, info); |
| name = Utils.getUserLabel(context, info); |
| } else { |
| name = context.getResources().getString( |
| R.string.running_process_item_removed_user_label); |
| } |
| return new NameAndIcon(name, icon, 0 /* iconId */); |
| } |
| |
| /** |
| * Gets name and icon resource from UidBatteryConsumer uid. |
| */ |
| public static NameAndIcon getNameAndIconFromUid( |
| Context context, String name, final int uid) { |
| Drawable icon = context.getDrawable(R.drawable.ic_power_system); |
| if (uid == 0) { |
| name = context.getResources().getString(R.string.process_kernel_label); |
| } else if ("mediaserver".equals(name)) { |
| name = context.getResources().getString(R.string.process_mediaserver_label); |
| } else if ("dex2oat".equals(name)) { |
| name = context.getResources().getString(R.string.process_dex2oat_label); |
| } |
| return new NameAndIcon(name, icon, 0 /* iconId */); |
| } |
| |
| /** |
| * Gets name and icon resource from BatteryConsumer power component ID. |
| */ |
| public static NameAndIcon getNameAndIconFromPowerComponent( |
| Context context, @BatteryConsumer.PowerComponent int powerComponentId) { |
| String name; |
| int iconId; |
| switch (powerComponentId) { |
| case BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY: |
| name = context.getResources().getString(R.string.ambient_display_screen_title); |
| iconId = R.drawable.ic_settings_aod; |
| break; |
| case BatteryConsumer.POWER_COMPONENT_BLUETOOTH: |
| name = context.getResources().getString(R.string.power_bluetooth); |
| iconId = com.android.internal.R.drawable.ic_settings_bluetooth; |
| break; |
| case BatteryConsumer.POWER_COMPONENT_CAMERA: |
| name = context.getResources().getString(R.string.power_camera); |
| iconId = R.drawable.ic_settings_camera; |
| break; |
| case BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO: |
| name = context.getResources().getString(R.string.power_cell); |
| iconId = R.drawable.ic_cellular_1_bar; |
| break; |
| case BatteryConsumer.POWER_COMPONENT_FLASHLIGHT: |
| name = context.getResources().getString(R.string.power_flashlight); |
| iconId = R.drawable.ic_settings_display; |
| break; |
| case BatteryConsumer.POWER_COMPONENT_PHONE: |
| name = context.getResources().getString(R.string.power_phone); |
| iconId = R.drawable.ic_settings_voice_calls; |
| break; |
| case BatteryConsumer.POWER_COMPONENT_SCREEN: |
| name = context.getResources().getString(R.string.power_screen); |
| iconId = R.drawable.ic_settings_display; |
| break; |
| case BatteryConsumer.POWER_COMPONENT_WIFI: |
| name = context.getResources().getString(R.string.power_wifi); |
| iconId = R.drawable.ic_settings_wireless; |
| break; |
| case BatteryConsumer.POWER_COMPONENT_IDLE: |
| case BatteryConsumer.POWER_COMPONENT_MEMORY: |
| name = context.getResources().getString(R.string.power_idle); |
| iconId = R.drawable.ic_settings_phone_idle; |
| break; |
| default: |
| name = DebugUtils.constantToString(BatteryConsumer.class, "POWER_COMPONENT_", |
| powerComponentId); |
| iconId = R.drawable.ic_power_system; |
| break; |
| } |
| return new NameAndIcon(name, null /* icon */, iconId); |
| } |
| } |