/* * 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.SystemBatteryConsumer; import android.os.UidBatteryConsumer; import android.os.UserBatteryConsumer; import android.os.UserHandle; import android.os.UserManager; import android.util.Log; import android.util.Pair; 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 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 sUidCache = new HashMap<>(); static final ArrayList sRequestQueue = new ArrayList(); 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); } be.loadNameAndIcon(); } } } 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 COMPARATOR = (a, b) -> Double.compare(b.getConsumedPower(), a.getConsumedPower()); private final Context mContext; private final BatteryConsumer mBatteryConsumer; private final boolean mIsHidden; 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) { sHandler = handler; mContext = context; mBatteryConsumer = batteryConsumer; mIsHidden = isHidden; mDefaultPackageName = packageName; mConsumedPower = batteryConsumer.getConsumedPower(); if (batteryConsumer instanceof UidBatteryConsumer) { 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); return; } else if (batteryConsumer instanceof SystemBatteryConsumer) { final Pair resourcePair = getResourcePairFromDrainType( context, ((SystemBatteryConsumer) batteryConsumer).getDrainType()); iconId = resourcePair.first; name = resourcePair.second; } else if (batteryConsumer instanceof UserBatteryConsumer) { UserInfo info = um.getUserInfo(((UserBatteryConsumer) batteryConsumer).getUserId()); if (info != null) { icon = Utils.getUserIcon(context, um, info); name = Utils.getUserLabel(context, info); } else { icon = null; name = context.getResources().getString( R.string.running_process_item_removed_user_label); } } if (iconId != 0) { icon = context.getDrawable(iconId); } } public Drawable getIcon() { return icon; } /** * Gets the application name */ public String getLabel() { return name; } void getQuickNameIconForUid(final int uid, final String[] packages) { // 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) { if (uid == 0) { name = mContext.getResources().getString(R.string.process_kernel_label); } else if ("mediaserver".equals(name)) { name = mContext.getResources().getString(R.string.process_mediaserver_label); } else if ("dex2oat".equals(name)) { name = mContext.getResources().getString(R.string.process_dex2oat_label); } iconId = R.drawable.ic_power_system; icon = mContext.getDrawable(iconId); } else { icon = mContext.getPackageManager().getDefaultActivityIcon(); } if (sHandler != null) { synchronized (sRequestQueue) { sRequestQueue.add(this); } } } /** * Loads the app label and icon image and stores into the cache. */ public void loadNameAndIcon() { // Bail out if the current sipper is not an App sipper. final int uid = getUid(); if (uid == 0 || uid == Process.INVALID_UID) { return; } final PackageManager pm = mContext.getPackageManager(); final String[] packages; if (uid == Process.SYSTEM_UID) { packages = new String[]{PACKAGE_SYSTEM}; } else { packages = pm.getPackagesForUid(uid); } if (packages != null) { String[] packageLabels = new String[packages.length]; System.arraycopy(packages, 0, packageLabels, 0, packages.length); // Convert package names to user-facing labels where possible 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; } CharSequence label = ai.loadLabel(pm); if (label != null) { packageLabels[i] = label.toString(); } if (ai.icon != 0) { mDefaultPackageName = 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) { mDefaultPackageName = 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 = mDefaultPackageName; sUidCache.put(uidString, utd); if (sHandler != null) { sHandler.sendMessage(sHandler.obtainMessage(MSG_UPDATE_NAME_ICON, this)); } } /** * 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 SystemBatteryConsumer) { return "S|" + ((SystemBatteryConsumer) mBatteryConsumer).getDrainType(); } else if (mBatteryConsumer instanceof UserBatteryConsumer) { return "U|" + ((UserBatteryConsumer) mBatteryConsumer).getUserId(); } else { Log.w(TAG, "Unsupported BatteryConsumer: " + mBatteryConsumer); return ""; } } /** * 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 mBatteryConsumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE); } } /** * 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 icon ID and name from system battery consumer drain type. */ public static Pair getResourcePairFromDrainType( Context context, int drainType) { String name = null; int iconId = 0; switch (drainType) { case SystemBatteryConsumer.DRAIN_TYPE_AMBIENT_DISPLAY: name = context.getResources().getString(R.string.ambient_display_screen_title); iconId = R.drawable.ic_settings_aod; break; case SystemBatteryConsumer.DRAIN_TYPE_BLUETOOTH: name = context.getResources().getString(R.string.power_bluetooth); iconId = com.android.internal.R.drawable.ic_settings_bluetooth; break; case SystemBatteryConsumer.DRAIN_TYPE_CAMERA: name = context.getResources().getString(R.string.power_camera); iconId = R.drawable.ic_settings_camera; break; case SystemBatteryConsumer.DRAIN_TYPE_MOBILE_RADIO: name = context.getResources().getString(R.string.power_cell); iconId = R.drawable.ic_cellular_1_bar; break; case SystemBatteryConsumer.DRAIN_TYPE_FLASHLIGHT: name = context.getResources().getString(R.string.power_flashlight); iconId = R.drawable.ic_settings_display; break; case SystemBatteryConsumer.DRAIN_TYPE_PHONE: name = context.getResources().getString(R.string.power_phone); iconId = R.drawable.ic_settings_voice_calls; break; case SystemBatteryConsumer.DRAIN_TYPE_SCREEN: name = context.getResources().getString(R.string.power_screen); iconId = R.drawable.ic_settings_display; break; case SystemBatteryConsumer.DRAIN_TYPE_WIFI: name = context.getResources().getString(R.string.power_wifi); iconId = R.drawable.ic_settings_wireless; break; case SystemBatteryConsumer.DRAIN_TYPE_IDLE: case SystemBatteryConsumer.DRAIN_TYPE_MEMORY: name = context.getResources().getString(R.string.power_idle); iconId = R.drawable.ic_settings_phone_idle; break; case SystemBatteryConsumer.DRAIN_TYPE_CUSTOM: name = null; iconId = R.drawable.ic_power_system; break; } return new Pair<>(Integer.valueOf(iconId), name); } }