blob: 25a66b6c34daa0e1091cdc2625c6773877f21b82 [file] [log] [blame]
/*
* 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);
}
}