diff options
-rw-r--r-- | res/drawable/app_gauge.9.png | bin | 253 -> 98 bytes | |||
-rw-r--r-- | res/layout/power_usage_details.xml | 56 | ||||
-rw-r--r-- | res/layout/power_usage_package_item.xml | 31 | ||||
-rw-r--r-- | res/layout/preference_powergauge.xml | 30 | ||||
-rw-r--r-- | res/values/strings.xml | 21 | ||||
-rw-r--r-- | src/com/android/settings/fuelgauge/PowerGaugePreference.java | 14 | ||||
-rw-r--r-- | src/com/android/settings/fuelgauge/PowerUsageDetail.java | 182 | ||||
-rw-r--r-- | src/com/android/settings/fuelgauge/PowerUsageSummary.java | 415 |
8 files changed, 618 insertions, 131 deletions
diff --git a/res/drawable/app_gauge.9.png b/res/drawable/app_gauge.9.png Binary files differindex 4d4db24..f8ff669 100644 --- a/res/drawable/app_gauge.9.png +++ b/res/drawable/app_gauge.9.png diff --git a/res/layout/power_usage_details.xml b/res/layout/power_usage_details.xml index dd8d486..206eb05 100644 --- a/res/layout/power_usage_details.xml +++ b/res/layout/power_usage_details.xml @@ -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> diff --git a/res/layout/power_usage_package_item.xml b/res/layout/power_usage_package_item.xml new file mode 100644 index 0000000..dcd5aad --- /dev/null +++ b/res/layout/power_usage_package_item.xml @@ -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> diff --git a/res/layout/preference_powergauge.xml b/res/layout/preference_powergauge.xml index 551659e..b47f6c0 100644 --- a/res/layout/preference_powergauge.xml +++ b/res/layout/preference_powergauge.xml @@ -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" /> diff --git a/res/values/strings.xml b/res/values/strings.xml index dd363de..bb65b52 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -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> diff --git a/src/com/android/settings/fuelgauge/PowerGaugePreference.java b/src/com/android/settings/fuelgauge/PowerGaugePreference.java index 0bfa12d..5778b39 100644 --- a/src/com/android/settings/fuelgauge/PowerGaugePreference.java +++ b/src/com/android/settings/fuelgauge/PowerGaugePreference.java @@ -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 { diff --git a/src/com/android/settings/fuelgauge/PowerUsageDetail.java b/src/com/android/settings/fuelgauge/PowerUsageDetail.java index eeb8663..54a0b88 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageDetail.java +++ b/src/com/android/settings/fuelgauge/PowerUsageDetail.java @@ -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) { diff --git a/src/com/android/settings/fuelgauge/PowerUsageSummary.java b/src/com/android/settings/fuelgauge/PowerUsageSummary.java index 296a9c7..10ede87 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageSummary.java +++ b/src/com/android/settings/fuelgauge/PowerUsageSummary.java @@ -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) { - /* - menu.add(0, MENU_STATS_TYPE, 0, R.string.menu_stats_total) - .setIcon(com.android.internal.R.drawable.ic_menu_info_details) - .setAlphabeticShortcut('t'); - */ + 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) { - /* - menu.findItem(MENU_STATS_TYPE).setTitle(mStatsType == BatteryStats.STATS_TOTAL - ? R.string.menu_stats_unplugged - : R.string.menu_stats_total); - */ + 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()); + if ((label == null || iconId == 0) && uid != null) { + 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); + } + }; }
\ No newline at end of file |