More details in the summary and details screens.

Show packages included, time that a subsystem was on for.
Load label and icon in the background and cache the values.
Switch between totals and since-unplugged.
Other UI improvements.
This commit is contained in:
Amith Yamasani
2009-06-09 06:34:49 -07:00
parent 8036862226
commit 78fd96a1c5
8 changed files with 618 additions and 131 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 253 B

After

Width:  |  Height:  |  Size: 98 B

View File

@@ -86,5 +86,61 @@
<!-- Insert detail items here -->
</LinearLayout>
<LinearLayout
android:id="@+id/controls_section"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/controls_section_title"
style="?android:attr/listSeparatorTextViewStyle"
android:text="@string/controls_subtitle" />
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/action_button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="6dip"
android:layout_marginTop="6dip"
android:layout_marginBottom="6dip"
android:layout_marginRight="6dip"
android:layout_alignParentLeft="true"
android:layout_weight="1"/>
<Button
android:id="@+id/action_button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="6dip"
android:layout_marginTop="6dip"
android:layout_marginBottom="6dip"
android:layout_marginRight="6dip"
android:layout_alignParentRight="true"
android:layout_weight="1"/>
</RelativeLayout>
</LinearLayout>
<TextView
android:id="@+id/packages_section_title"
style="?android:attr/listSeparatorTextViewStyle"
android:text="@string/packages_subtitle" />
<LinearLayout
android:id="@+id/packages_section"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingLeft="6dip"
android:orientation="vertical">
<!-- Insert detail items here -->
</LinearLayout>
</LinearLayout>
</ScrollView>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2009 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.
-->
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<!--Label for the item-->
<TextView
android:id="@+id/label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:singleLine="true"
android:layout_alignParentLeft="true"
android:layout_marginBottom="2dip"
android:layout_marginTop="2dip" />
</RelativeLayout>

View File

@@ -19,7 +19,7 @@
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:gravity="center_vertical"
android:paddingLeft="16dip"
android:paddingLeft="12dip"
android:id="@+android:id/widget_frame"
android:paddingRight="?android:attr/scrollbarSize">
@@ -33,8 +33,8 @@
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="6dip"
android:layout_marginTop="6dip"
android:layout_marginRight="8dip"
android:layout_marginTop="2dip"
android:layout_marginBottom="6dip"
android:layout_weight="1">
@@ -42,21 +42,37 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:visibility="gone"
android:textAppearance="?android:attr/textAppearanceLarge" />
android:layout_marginTop="2dip"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_toLeftOf="@+id/percent"
android:ellipsize="marquee"
android:textAppearance="?android:attr/textAppearanceMedium"/>
<TextView android:id="@+id/percent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:layout_alignParentRight="true"
android:layout_alignBottom="@android:id/title"
android:layout_gravity="bottom"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textStyle="bold"/>
<ImageView
android:id="@+id/appGauge"
android:background="#2e2e2e"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:layout_marginRight="6dip"
android:layout_marginTop="5dip"
android:layout_below="@id/percent"
android:layout_gravity="center_vertical" />
<TextView android:id="@+android:id/summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/appGauge"
android:layout_alignLeft="@id/appGauge"
android:layout_below="@id/appGauge"
android:textAppearance="?android:attr/textAppearanceSmall"
android:maxLines="2" />

View File

@@ -1638,6 +1638,11 @@ found in the list of installed applications.</string>
<string name="awake">Device awake time</string>
<!-- Wifi on time -->
<string name="wifi_on_time">WiFi on time</string>
<!-- Bluetooth on time -->
<string name="bluetooth_on_time">WiFi on time</string>
<!-- Application name and battery usage percentage -->
<string name="usage_name_percent"><xliff:g id="name">%1$s</xliff:g>" - "
<xliff:g id="percent">%2$s</xliff:g>"%%"</string>
<!-- Activity title for battery usage details for an app. or power consumer -->
<string name="details_title">Battery usage details</string>
@@ -1645,11 +1650,15 @@ found in the list of installed applications.</string>
<string name="details_subtitle">Usage details</string>
<!-- Subtitle for possible options -->
<string name="controls_subtitle">Controls</string>
<!-- Subtitle for list of packages -->
<string name="packages_subtitle">Included packages</string>
<!-- Label for power consumed by the screen -->
<string name="power_screen">Screen on</string>
<!-- Label for power consumed by WiFi -->
<string name="power_wifi">WiFi</string>
<!-- Label for power consumed by Bluetooth -->
<string name="power_bluetooth">Bluetooth</string>
<!-- Label for power consumed by Cell idle -->
<string name="power_cell">Cell</string>
<!-- Label for power consumed by Calling -->
@@ -1673,6 +1682,18 @@ found in the list of installed applications.</string>
<string name="usage_type_audio">Audio</string>
<!-- Label for Video usage time -->
<string name="usage_type_video">Video</string>
<!-- Label for On time -->
<string name="usage_type_on_time">On time</string>
<!-- Label for force stop action -->
<string name="battery_action_stop">Force stop</string>
<!-- Label for app details action -->
<string name="battery_action_app_details">App details</string>
<!-- Label for display settings -->
<string name="battery_action_display">Display settings</string>
<!-- Label for wifi settings -->
<string name="battery_action_wifi">WiFi settings</string>
<!-- Label for bluetooth settings -->
<string name="battery_action_bluetooth">Bluetooth settings</string>
<!-- Menu label for viewing battery usage since unplugged -->
<string name="menu_stats_unplugged">Usage since unplugged</string>

View File

@@ -24,6 +24,7 @@ import android.graphics.drawable.Drawable;
import android.preference.Preference;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.settings.R;
import com.android.settings.fuelgauge.PowerUsageSummary.BatterySipper;
@@ -39,6 +40,7 @@ public class PowerGaugePreference extends Preference {
private GaugeDrawable mGauge;
private double mValue;
private BatterySipper mInfo;
private double mPercent;
public PowerGaugePreference(Context context, Drawable icon, BatterySipper info) {
super(context);
@@ -58,10 +60,19 @@ public class PowerGaugePreference extends Preference {
mGauge.percent = mValue;
}
void setPercent(double percent) {
mPercent = percent;
}
BatterySipper getInfo() {
return mInfo;
}
void setIcon(Drawable icon) {
mIcon = icon;
notifyChanged();
}
@Override
protected void onBindView(View view) {
super.onBindView(view);
@@ -74,6 +85,9 @@ public class PowerGaugePreference extends Preference {
ImageView appGauge = (ImageView) view.findViewById(R.id.appGauge);
appGauge.setImageDrawable(mGauge);
TextView percentView = (TextView) view.findViewById(R.id.percent);
percentView.setText((int) (Math.ceil(mPercent)) + "%");
}
static class GaugeDrawable extends Drawable {

View File

@@ -17,26 +17,54 @@
package com.android.settings.fuelgauge;
import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.util.Log;
import android.provider.Settings;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import com.android.settings.InstalledAppDetails;
import com.android.settings.R;
public class PowerUsageDetail extends Activity {
public class PowerUsageDetail extends Activity implements Button.OnClickListener {
enum DrainType {
IDLE,
CELL,
PHONE,
WIFI,
BLUETOOTH,
SCREEN,
APP
}
public static final int ACTION_DISPLAY_SETTINGS = 1;
public static final int ACTION_WIFI_SETTINGS = 2;
public static final int ACTION_BLUETOOTH_SETTINGS = 3;
public static final int ACTION_FORCE_STOP = 4;
public static final int ACTION_UNINSTALL = 5;
public static final int USAGE_SINCE_UNPLUGGED = 1;
public static final int USAGE_SINCE_RESET = 2;
public static final String EXTRA_TITLE = "title";
public static final String EXTRA_PERCENT = "percent";
public static final String EXTRA_UID = "uid";
public static final String EXTRA_USAGE_SINCE = "since";
public static final String EXTRA_USAGE_DURATION = "duration";
public static final String EXTRA_DETAIL_TYPES = "types";
public static final String EXTRA_DETAIL_VALUES = "values";
public static final String EXTRA_DRAIN_TYPE = "drainType";
private static final int SECONDS_PER_MINUTE = 60;
private static final int SECONDS_PER_HOUR = 60 * 60;
@@ -47,12 +75,19 @@ public class PowerUsageDetail extends Activity {
private double mPercentage;
private int mUsageSince;
private int[] mTypes;
private int mUid;
private double[] mValues;
private TextView mTitleView;
private ViewGroup mDetailsParent;
private long mStartTime;
private DrainType mDrainType;
private int mAction1;
private int mAction2;
private static final String TAG = "PowerUsageDetail";
private Button mButton1;
private Button mButton2;
private String[] mPackages;
@Override
protected void onCreate(Bundle icicle) {
@@ -77,6 +112,8 @@ public class PowerUsageDetail extends Activity {
mTitle = intent.getStringExtra(EXTRA_TITLE);
mPercentage = intent.getDoubleExtra(EXTRA_PERCENT, -1);
mUsageSince = intent.getIntExtra(EXTRA_USAGE_SINCE, USAGE_SINCE_UNPLUGGED);
mUid = intent.getIntExtra(EXTRA_UID, 0);
mDrainType = (DrainType) intent.getSerializableExtra(EXTRA_DRAIN_TYPE);
mTypes = intent.getIntArrayExtra(EXTRA_DETAIL_TYPES);
mValues = intent.getDoubleArrayExtra(EXTRA_DETAIL_VALUES);
@@ -112,6 +149,147 @@ public class PowerUsageDetail extends Activity {
valueView.setText(value);
}
}
fillPackagesSection(mUid);
fillControlsSection(mUid);
}
public void onClick(View v) {
int action = v == mButton1 ? mAction1 : mAction2;
doAction(action);
}
private void doAction(int action) {
switch (action) {
case ACTION_DISPLAY_SETTINGS:
startActivity(new Intent(Settings.ACTION_DISPLAY_SETTINGS));
break;
case ACTION_WIFI_SETTINGS:
startActivity(new Intent(Settings.ACTION_WIFI_SETTINGS));
break;
case ACTION_BLUETOOTH_SETTINGS:
startActivity(new Intent(Settings.ACTION_BLUETOOTH_SETTINGS));
break;
case ACTION_FORCE_STOP:
killProcesses();
break;
case ACTION_UNINSTALL:
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setClass(this, InstalledAppDetails.class);
intent.putExtra("com.android.settings.ApplicationPkgName", mPackages[0]);
startActivity(intent);
break;
}
}
private void fillControlsSection(int uid) {
String label1 = null;
String label2 = null;
mAction1 = 0;
mAction2 = 0;
PackageManager pm = getPackageManager();
String[] packages = pm.getPackagesForUid(mUid);
PackageInfo pi = null;
try {
pi = packages != null ? pm.getPackageInfo(packages[0], 0) : null;
} catch (NameNotFoundException nnfe) { /* Nothing */ }
ApplicationInfo ai = pi != null? pi.applicationInfo : null;
boolean isSystem = ai != null? (ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0 : false;
if (uid == 0 || !isSystem) {
switch (mDrainType) {
case APP:
label1 = getString(R.string.battery_action_stop);
label2 = getString(R.string.battery_action_app_details);
mAction1 = ACTION_FORCE_STOP;
mAction2 = ACTION_UNINSTALL;
break;
case SCREEN:
//label2 = getString(R.string.battery_action_display);
//mAction2 = ACTION_DISPLAY_SETTINGS;
break;
case WIFI:
label2 = getString(R.string.battery_action_wifi);
mAction2 = ACTION_WIFI_SETTINGS;
break;
case BLUETOOTH:
//label2 = getString(R.string.battery_action_bluetooth);
//mAction2 = ACTION_BLUETOOTH_SETTINGS;
break;
}
}
mButton1 = (Button) findViewById(R.id.action_button1);
mButton2 = (Button) findViewById(R.id.action_button2);
mButton1.setOnClickListener(this);
mButton2.setOnClickListener(this);
if (label1 == null) {
mButton1.setVisibility(View.GONE);
} else {
mButton1.setText(label1);
}
if (label2 == null) {
findViewById(R.id.controls_section).setVisibility(View.GONE);
} else {
mButton2.setText(label2);
}
}
private void removePackagesSection() {
View view;
if ((view = findViewById(R.id.packages_section_title)) != null) {
view.setVisibility(View.GONE);
}
if ((view = findViewById(R.id.packages_section)) != null) {
view.setVisibility(View.GONE);
}
}
private void killProcesses() {
if (mPackages == null) return;
ActivityManager am = (ActivityManager)getSystemService(
Context.ACTIVITY_SERVICE);
for (int i = 0; i < mPackages.length; i++) {
am.restartPackage(mPackages[i]);
}
}
private void fillPackagesSection(int uid) {
if (uid == 0) {
removePackagesSection();
return;
}
ViewGroup packagesParent = (ViewGroup) findViewById(R.id.packages_section);
if (packagesParent == null) return;
LayoutInflater inflater = getLayoutInflater();
PackageManager pm = getPackageManager();
final Drawable defaultActivityIcon = pm.getDefaultActivityIcon();
mPackages = pm.getPackagesForUid(uid);
if (mPackages == null || mPackages.length < 2) {
removePackagesSection();
return;
}
// Convert package names to user-facing labels where possible
for (int i = 0; i < mPackages.length; i++) {
try {
ApplicationInfo ai = pm.getApplicationInfo(mPackages[i], 0);
CharSequence label = ai.loadLabel(pm);
Drawable icon = defaultActivityIcon;
if (label != null) {
mPackages[i] = label.toString();
}
if (ai.icon != 0) {
icon = ai.loadIcon(pm);
}
ViewGroup item = (ViewGroup) inflater.inflate(R.layout.power_usage_package_item,
null);
packagesParent.addView(item);
TextView labelView = (TextView) item.findViewById(R.id.label);
labelView.setText(mPackages[i]);
} catch (NameNotFoundException e) {
}
}
}
private String formatTime(double millis) {

View File

@@ -26,13 +26,13 @@ import android.graphics.drawable.Drawable;
import android.hardware.SensorManager;
import android.os.BatteryStats;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Parcel;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.BatteryStats.Timer;
import android.os.BatteryStats.Uid;
import android.os.BatteryStats.Uid.Sensor;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceGroup;
@@ -46,9 +46,11 @@ import com.android.internal.app.IBatteryStats;
import com.android.internal.os.BatteryStatsImpl;
import com.android.internal.os.PowerProfile;
import com.android.settings.R;
import com.android.settings.fuelgauge.PowerUsageDetail.DrainType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -56,25 +58,15 @@ import java.util.Map;
* Displays a list of apps and subsystems that consume power, ordered by how much power was
* consumed since the last time it was unplugged.
*/
public class PowerUsageSummary extends PreferenceActivity {
public class PowerUsageSummary extends PreferenceActivity implements Runnable {
private static final boolean DEBUG = true;
private static final boolean DEBUG = false;
private static final String TAG = "PowerUsageSummary";
private static final int MENU_STATS_TYPE = Menu.FIRST;
private static final int MENU_STATS_REFRESH = Menu.FIRST + 1;
enum DrainType {
IDLE,
CELL,
PHONE,
WIFI,
BLUETOOTH,
SCREEN,
APP
}
IBatteryStats mBatteryInfo;
BatteryStatsImpl mStats;
private List<BatterySipper> mUsageList = new ArrayList<BatterySipper>();
@@ -93,6 +85,14 @@ public class PowerUsageSummary extends PreferenceActivity {
private PowerProfile mPowerProfile;
private HashMap<String,String> mNameCache = new HashMap<String,String>();
private HashMap<String,Drawable> mIconCache = new HashMap<String,Drawable>();
/** Queue for fetching name and icon for an application */
private ArrayList<BatterySipper> mRequestQueue = new ArrayList<BatterySipper>();
private Thread mRequestThread;
private boolean mAbort;
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
@@ -101,28 +101,40 @@ public class PowerUsageSummary extends PreferenceActivity {
mBatteryInfo = IBatteryStats.Stub.asInterface(
ServiceManager.getService("batteryinfo"));
mAppListGroup = getPreferenceScreen();
mPowerProfile = new PowerProfile(this, "power_profile_default");
mPowerProfile = new PowerProfile(this);
}
@Override
protected void onResume() {
super.onResume();
mAbort = false;
updateAppsList();
}
@Override
protected void onPause() {
synchronized (mRequestQueue) {
mAbort = true;
}
mHandler.removeMessages(MSG_UPDATE_NAME_ICON);
super.onPause();
}
@Override
public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
PowerGaugePreference pgp = (PowerGaugePreference) preference;
BatterySipper sipper = pgp.getInfo();
Intent intent = new Intent(this, PowerUsageDetail.class);
intent.putExtra(PowerUsageDetail.EXTRA_TITLE, sipper.mLabel);
intent.putExtra(PowerUsageDetail.EXTRA_TITLE, sipper.name);
intent.putExtra(PowerUsageDetail.EXTRA_PERCENT, sipper.getSortValue() * 100 / mTotalPower);
switch (sipper.mDrainType) {
if (sipper.uidObj != null) {
intent.putExtra(PowerUsageDetail.EXTRA_UID, sipper.uidObj.getUid());
}
intent.putExtra(PowerUsageDetail.EXTRA_DRAIN_TYPE, sipper.drainType);
switch (sipper.drainType) {
case APP:
{
Uid uid = sipper.mUid;
Uid uid = sipper.uidObj;
int[] types = new int[] {
R.string.usage_type_cpu,
R.string.usage_type_cpu_foreground,
@@ -133,9 +145,9 @@ public class PowerUsageSummary extends PreferenceActivity {
R.string.usage_type_video,
};
double[] values = new double[] {
sipper.mCpuTime,
sipper.mCpuFgTime,
sipper.mGpsTime,
sipper.cpuTime,
sipper.cpuFgTime,
sipper.gpsTime,
uid != null? uid.getTcpBytesSent(mStatsType) : 0,
uid != null? uid.getTcpBytesReceived(mStatsType) : 0,
0,
@@ -146,6 +158,17 @@ public class PowerUsageSummary extends PreferenceActivity {
}
break;
default:
{
int[] types = new int[] {
R.string.usage_type_on_time
};
double[] values = new double[] {
sipper.usageTime
};
intent.putExtra(PowerUsageDetail.EXTRA_DETAIL_TYPES, types);
intent.putExtra(PowerUsageDetail.EXTRA_DETAIL_VALUES, values);
}
}
startActivity(intent);
@@ -154,11 +177,11 @@ public class PowerUsageSummary extends PreferenceActivity {
@Override
public boolean onCreateOptionsMenu(Menu menu) {
/*
if (DEBUG) {
menu.add(0, MENU_STATS_TYPE, 0, R.string.menu_stats_total)
.setIcon(com.android.internal.R.drawable.ic_menu_info_details)
.setAlphabeticShortcut('t');
*/
}
menu.add(0, MENU_STATS_REFRESH, 0, R.string.menu_stats_refresh)
.setIcon(com.android.internal.R.drawable.ic_menu_refresh)
.setAlphabeticShortcut('r');
@@ -167,11 +190,11 @@ public class PowerUsageSummary extends PreferenceActivity {
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
/*
if (DEBUG) {
menu.findItem(MENU_STATS_TYPE).setTitle(mStatsType == BatteryStats.STATS_TOTAL
? R.string.menu_stats_unplugged
: R.string.menu_stats_total);
*/
}
return true;
}
@@ -213,46 +236,73 @@ public class PowerUsageSummary extends PreferenceActivity {
for (BatterySipper g : mUsageList) {
if (g.getSortValue() < MIN_POWER_THRESHOLD) continue;
double percent = ((g.getSortValue() / mTotalPower) * 100);
if (percent < 1) continue;
PowerGaugePreference pref = new PowerGaugePreference(this, g.getIcon(), g);
double scaleByMax = (g.getSortValue() * 100) / mMaxPower;
pref.setSummary(g.getLabel() + " ( " + String.format("%3.2f", percent) + "% )");
g.percent = percent;
pref.setTitle(g.name);
pref.setPercent(percent);
pref.setOrder(Integer.MAX_VALUE - (int) g.getSortValue()); // Invert the order
pref.setGaugeValue(mScaleByMax ? scaleByMax : percent);
if (g.uidObj != null) {
pref.setKey(Integer.toString(g.uidObj.getUid()));
}
mAppListGroup.addPreference(pref);
if (mAppListGroup.getPreferenceCount() > MAX_ITEMS_TO_LIST) break;
}
if (DEBUG) setTitle("Battery total uAh = " + ((mTotalPower * 1000) / 3600));
synchronized (mRequestQueue) {
if (!mRequestQueue.isEmpty()) {
if (mRequestThread == null) {
mRequestThread = new Thread(this, "BatteryUsage Icon Loader");
mRequestThread.setPriority(Thread.MIN_PRIORITY);
mRequestThread.start();
}
mRequestQueue.notify();
}
}
}
private void processAppUsage() {
SensorManager sensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
final int which = mStatsType;
final double powerCpuNormal = mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_NORMAL);
final double averageCostPerByte = getAverageDataCost();
long uSecTime = mStats.computeBatteryRealtime(SystemClock.elapsedRealtime(), which) * 1000;
SparseArray<? extends Uid> uidStats = mStats.getUidStats();
final int NU = uidStats.size();
if (DEBUG) Log.i(TAG, "uidStats size = " + NU);
for (int iu = 0; iu < NU; iu++) {
Uid u = uidStats.valueAt(iu);
double power = 0;
double highestDrain = 0;
String packageWithHighestDrain = null;
//mUsageList.add(new AppUsage(u.getUid(), new double[] {power}));
Map<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats();
long cpuTime = 0;
long cpuFgTime = 0;
long gpsTime = 0;
if (processStats.size() > 0) {
// Process CPU time
for (Map.Entry<String, ? extends BatteryStats.Uid.Proc> ent
: processStats.entrySet()) {
if (DEBUG) Log.i(TAG, "Process name = " + ent.getKey());
Uid.Proc ps = ent.getValue();
long userTime = ps.getUserTime(which);
long systemTime = ps.getSystemTime(which);
long foregroundTime = ps.getForegroundTime(which);
final long userTime = ps.getUserTime(which);
final long systemTime = ps.getSystemTime(which);
final long foregroundTime = ps.getForegroundTime(which);
cpuFgTime += foregroundTime * 10; // convert to millis
if (DEBUG) Log.i(TAG, "CPU Fg time for " + u.getUid() + " = " + foregroundTime);
cpuTime = (userTime + systemTime) * 10; // convert to millis
power += cpuTime * powerCpuNormal;
final long tmpCpuTime = (userTime + systemTime) * 10; // convert to millis
final double processPower = tmpCpuTime * powerCpuNormal;
cpuTime += tmpCpuTime;
power += processPower;
if (highestDrain < processPower) {
highestDrain = processPower;
packageWithHighestDrain = ent.getKey();
}
}
if (DEBUG) Log.i(TAG, "Max drain of " + highestDrain
+ " by " + packageWithHighestDrain);
}
if (cpuFgTime > cpuTime) {
if (DEBUG && cpuFgTime > cpuTime + 10000) {
@@ -262,6 +312,11 @@ public class PowerUsageSummary extends PreferenceActivity {
}
power /= 1000;
// Add cost of data traffic
power += (u.getTcpBytesReceived(mStatsType) + u.getTcpBytesSent(mStatsType))
* averageCostPerByte;
// Process Sensor usage
Map<Integer, ? extends BatteryStats.Uid.Sensor> sensorStats = u.getSensorStats();
for (Map.Entry<Integer, ? extends BatteryStats.Uid.Sensor> sensorEntry
: sensorStats.entrySet()) {
@@ -288,12 +343,14 @@ public class PowerUsageSummary extends PreferenceActivity {
}
power += (multiplier * sensorTime) / 1000;
}
// Add the app to the list if it is consuming power
if (power != 0) {
BatterySipper app = new BatterySipper(null, DrainType.APP, 0, u,
BatterySipper app = new BatterySipper(packageWithHighestDrain, DrainType.APP, 0, u,
new double[] {power});
app.mCpuTime = cpuTime;
app.mGpsTime = gpsTime;
app.mCpuFgTime = cpuFgTime;
app.cpuTime = cpuTime;
app.gpsTime = gpsTime;
app.cpuFgTime = cpuFgTime;
mUsageList.add(app);
}
if (power > mMaxPower) mMaxPower = power;
@@ -302,15 +359,18 @@ public class PowerUsageSummary extends PreferenceActivity {
}
}
private double getPhoneOnPower(long uSecNow) {
return mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE)
* mStats.getPhoneOnTime(uSecNow, mStatsType) / 1000 / 1000;
private void addPhoneUsage(long uSecNow) {
long phoneOnTimeMs = mStats.getPhoneOnTime(uSecNow, mStatsType) / 1000;
double phoneOnPower = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE)
* phoneOnTimeMs / 1000;
addEntry(getString(R.string.power_phone), DrainType.PHONE, phoneOnTimeMs,
android.R.drawable.ic_menu_call, phoneOnPower);
}
private double getScreenOnPower(long uSecNow) {
private void addScreenUsage(long uSecNow) {
double power = 0;
power += mStats.getScreenOnTime(uSecNow, mStatsType)
* mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_ON) / 1000; // millis
long screenOnTimeMs = mStats.getScreenOnTime(uSecNow, mStatsType) / 1000;
power += screenOnTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_ON);
final double screenFullPower =
mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_FULL);
for (int i = 0; i < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; i++) {
@@ -323,18 +383,75 @@ public class PowerUsageSummary extends PreferenceActivity {
+ brightnessTime);
}
}
return power / 1000;
power /= 1000; // To seconds
addEntry(getString(R.string.power_screen), DrainType.SCREEN, screenOnTimeMs,
android.R.drawable.ic_menu_view, power);
}
private double getRadioPower(long uSecNow, int which) {
private void addRadioUsage(long uSecNow) {
double power = 0;
final int BINS = BatteryStats.NUM_SIGNAL_STRENGTH_BINS;
long signalTimeMs = 0;
for (int i = 0; i < BINS; i++) {
power += mStats.getPhoneSignalStrengthTime(i, uSecNow, which) / 1000 / 1000 *
mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ON)
* ((BINS - i) / (double) BINS);
long strengthTimeMs = mStats.getPhoneSignalStrengthTime(i, uSecNow, mStatsType) / 1000;
power += strengthTimeMs / 1000
* mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ON, i);
signalTimeMs += strengthTimeMs;
}
addEntry(getString(R.string.power_cell), DrainType.CELL, signalTimeMs,
android.R.drawable.ic_menu_sort_by_size, power);
}
private void addWiFiUsage(long uSecNow) {
long onTimeMs = mStats.getWifiOnTime(uSecNow, mStatsType) / 1000;
long runningTimeMs = mStats.getWifiRunningTime(uSecNow, mStatsType) / 1000;
double wifiPower = (onTimeMs * 0 /* TODO */
* mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)
+ runningTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)) / 1000;
addEntry(getString(R.string.power_wifi), DrainType.WIFI, runningTimeMs,
R.drawable.ic_wifi_signal_4, wifiPower);
}
private void addIdleUsage(long uSecNow) {
long idleTimeMs = (uSecNow - mStats.getScreenOnTime(uSecNow, mStatsType)) / 1000;
double idlePower = (idleTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_IDLE))
/ 1000;
addEntry(getString(R.string.power_idle), DrainType.IDLE, idleTimeMs,
android.R.drawable.ic_lock_power_off, idlePower);
}
private void addBluetoothUsage(long uSecNow) {
long btOnTimeMs = mStats.getBluetoothOnTime(uSecNow, mStatsType) / 1000;
double btPower = btOnTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_BLUETOOTH_ON)
/ 1000;
addEntry(getString(R.string.power_bluetooth), DrainType.IDLE, btOnTimeMs,
com.android.internal.R.drawable.ic_volume_bluetooth_in_call, btPower);
}
private double getAverageDataCost() {
final long WIFI_BPS = 1000000; // TODO: Extract average bit rates from system
final long MOBILE_BPS = 200000; // TODO: Extract average bit rates from system
final double WIFI_POWER = mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ACTIVE)
/ 3600;
final double MOBILE_POWER = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE)
/ 3600;
final long mobileData = mStats.getMobileTcpBytesReceived(mStatsType) +
mStats.getMobileTcpBytesSent(mStatsType);
final long wifiData = mStats.getTotalTcpBytesReceived(mStatsType) +
mStats.getTotalTcpBytesSent(mStatsType) - mobileData;
final long radioDataUptimeMs = mStats.getRadioDataUptimeMs();
final long mobileBps = radioDataUptimeMs != 0
? mobileData * 8 * 1000 / radioDataUptimeMs
: MOBILE_BPS;
double mobileCostPerByte = MOBILE_POWER / (mobileBps / 8);
double wifiCostPerByte = WIFI_POWER / (WIFI_BPS / 8);
if (wifiData + mobileData != 0) {
return (mobileCostPerByte * mobileData + wifiCostPerByte * wifiData)
/ (mobileData + wifiData);
} else {
return 0;
}
return power;
}
private void processMiscUsage() {
@@ -346,35 +463,20 @@ public class PowerUsageSummary extends PreferenceActivity {
Log.i(TAG, "Uptime since last unplugged = " + (timeSinceUnplugged / 1000));
}
double phoneOnPower = getPhoneOnPower(uSecNow);
addEntry(getString(R.string.power_phone), DrainType.PHONE,
android.R.drawable.ic_menu_call, phoneOnPower);
double screenOnPower = getScreenOnPower(uSecNow);
addEntry(getString(R.string.power_screen), DrainType.SCREEN,
android.R.drawable.ic_menu_view, screenOnPower);
double wifiPower = (mStats.getWifiOnTime(uSecNow, which) * 0 /* TODO */
* mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)
+ mStats.getWifiRunningTime(uSecNow, which)
* mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)) / 1000 / 1000;
addEntry(getString(R.string.power_wifi), DrainType.WIFI,
R.drawable.ic_wifi_signal_4, wifiPower);
double idlePower = ((timeSinceUnplugged - mStats.getScreenOnTime(uSecNow, mStatsType))
* mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_IDLE)) / 1000 / 1000;
addEntry(getString(R.string.power_idle), DrainType.IDLE,
android.R.drawable.ic_lock_power_off, idlePower);
double radioPower = getRadioPower(uSecNow, which);
addEntry(getString(R.string.power_cell), DrainType.CELL,
android.R.drawable.ic_menu_sort_by_size, radioPower);
addPhoneUsage(uSecNow);
addScreenUsage(uSecNow);
addWiFiUsage(uSecNow);
addBluetoothUsage(uSecNow);
addIdleUsage(uSecNow); // Not including cellular idle power
//addRadioUsage(uSecNow); // Cannot include this because airplane mode is not tracked yet
// and we don't know if the radio is currently running on 2/3G.
}
private void addEntry(String label, DrainType drainType, int iconId, double power) {
private void addEntry(String label, DrainType drainType, long time, int iconId, double power) {
if (power > mMaxPower) mMaxPower = power;
mTotalPower += power;
BatterySipper bs = new BatterySipper(label, drainType, iconId, null, new double[] {power});
bs.usageTime = time;
mUsageList.add(bs);
}
@@ -392,41 +494,43 @@ public class PowerUsageSummary extends PreferenceActivity {
}
class BatterySipper implements Comparable<BatterySipper> {
String mLabel;
Drawable mIcon;
Uid mUid;
double mValue;
double[] mValues;
DrainType mDrainType;
long mCpuTime;
long mGpsTime;
long mCpuFgTime;
String name;
Drawable icon;
Uid uidObj;
double value;
double[] values;
DrainType drainType;
long usageTime;
long cpuTime;
long gpsTime;
long cpuFgTime;
double percent;
BatterySipper(String label, DrainType drainType, int iconId, Uid uid, double[] values) {
mValues = values;
mLabel = label;
mDrainType = drainType;
this.values = values;
name = label;
this.drainType = drainType;
if (iconId > 0) {
mIcon = getResources().getDrawable(iconId);
icon = getResources().getDrawable(iconId);
}
if (mValues != null) mValue = mValues[0];
if (values != null) value = values[0];
//if (uid > 0 && (mLabel == null || mIcon == null) // TODO:
if ((label == null || iconId == 0) && uid != null) {
getNameForUid(uid.getUid());
getQuickNameIconForUid(uid);
}
mUid = uid;
uidObj = uid;
}
double getSortValue() {
return mValue;
return value;
}
double[] getValues() {
return mValues;
return values;
}
Drawable getIcon() {
return mIcon;
return icon;
}
public int compareTo(BatterySipper other) {
@@ -434,54 +538,80 @@ public class PowerUsageSummary extends PreferenceActivity {
return (int) (other.getSortValue() - getSortValue());
}
String getLabel() {
return mLabel;
void getQuickNameIconForUid(Uid uidObj) {
final int uid = uidObj.getUid();
final String uidString = Integer.toString(uid);
if (mNameCache.containsKey(uidString)) {
name = mNameCache.get(uidString);
icon = mIconCache.get(uidString);
return;
}
PackageManager pm = getPackageManager();
final Drawable defaultActivityIcon = pm.getDefaultActivityIcon();
String[] packages = pm.getPackagesForUid(uid);
if (packages == null) {
name = Integer.toString(uid);
} else {
//name = packages[0];
}
icon = pm.getDefaultActivityIcon();
synchronized (mRequestQueue) {
mRequestQueue.add(this);
}
}
/**
* Sets mLabel and mIcon
* Sets name and icon
* @param uid Uid of the application
*/
void getNameForUid(int uid) {
// TODO: Do this on a separate thread
void getNameIcon() {
PackageManager pm = getPackageManager();
final int uid = uidObj.getUid();
final Drawable defaultActivityIcon = pm.getDefaultActivityIcon();
String[] packages = pm.getPackagesForUid(uid);
if (packages == null) {
mLabel = Integer.toString(uid);
name = Integer.toString(uid);
return;
}
String[] packageNames = new String[packages.length];
System.arraycopy(packages, 0, packageNames, 0, packages.length);
String[] packageLabels = new String[packages.length];
System.arraycopy(packages, 0, packageLabels, 0, packages.length);
int preferredIndex = -1;
// Convert package names to user-facing labels where possible
for (int i = 0; i < packageNames.length; i++) {
//packageNames[i] = PowerUsageSummary.getLabel(packageNames[i], pm);
for (int i = 0; i < packageLabels.length; i++) {
// Check if package matches preferred package
if (packageLabels[i].equals(name)) preferredIndex = i;
try {
ApplicationInfo ai = pm.getApplicationInfo(packageNames[i], 0);
ApplicationInfo ai = pm.getApplicationInfo(packageLabels[i], 0);
CharSequence label = ai.loadLabel(pm);
if (label != null) {
packageNames[i] = label.toString();
packageLabels[i] = label.toString();
}
if (mIcon == null) {
mIcon = ai.loadIcon(pm);
if (ai.icon != 0) {
icon = ai.loadIcon(pm);
break;
}
} catch (NameNotFoundException e) {
}
}
if (icon == null) icon = defaultActivityIcon;
if (packageNames.length == 1) {
mLabel = packageNames[0];
if (packageLabels.length == 1) {
name = packageLabels[0];
} else {
// Look for an official name for this UID.
for (String name : packages) {
for (String pkgName : packages) {
try {
PackageInfo pi = pm.getPackageInfo(name, 0);
PackageInfo pi = pm.getPackageInfo(pkgName, 0);
if (pi.sharedUserLabel != 0) {
CharSequence nm = pm.getText(name,
CharSequence nm = pm.getText(pkgName,
pi.sharedUserLabel, pi.applicationInfo);
if (nm != null) {
mLabel = nm.toString();
name = nm.toString();
if (pi.applicationInfo.icon != 0) {
icon = pi.applicationInfo.loadIcon(pm);
}
break;
}
}
@@ -489,6 +619,47 @@ public class PowerUsageSummary extends PreferenceActivity {
}
}
}
final String uidString = Integer.toString(uidObj.getUid());
mNameCache.put(uidString, name);
mIconCache.put(uidString, icon);
mHandler.sendMessage(mHandler.obtainMessage(MSG_UPDATE_NAME_ICON, this));
}
}
public void run() {
while (true) {
BatterySipper bs;
synchronized (mRequestQueue) {
if (mRequestQueue.isEmpty() || mAbort) {
mRequestThread = null;
return;
}
bs = mRequestQueue.remove(0);
}
bs.getNameIcon();
}
}
private static final int MSG_UPDATE_NAME_ICON = 1;
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_UPDATE_NAME_ICON:
BatterySipper bs = (BatterySipper) msg.obj;
PowerGaugePreference pgp =
(PowerGaugePreference) findPreference(
Integer.toString(bs.uidObj.getUid()));
if (pgp != null) {
pgp.setIcon(bs.icon);
pgp.setPercent(bs.percent);
pgp.setTitle(bs.name);
}
break;
}
super.handleMessage(msg);
}
};
}