From 7f6aa6283ae759c5b013c142be036617cf79f725 Mon Sep 17 00:00:00 2001 From: Amith Yamasani Date: Wed, 3 Jun 2009 15:45:40 -0700 Subject: Add detail page for Battery usage and track GPS and foreground CPU. Show details such as Cpu time, Cpu foreground time, data sent/received, and GPS usage. --- AndroidManifest.xml | 11 +- res/layout/power_usage_detail_item_text.xml | 43 +++++ res/layout/power_usage_details.xml | 90 ++++++++++ res/values/strings.xml | 46 ++++- res/xml/power_usage_summary.xml | 6 +- .../settings/fuelgauge/PowerGaugePreference.java | 9 +- .../settings/fuelgauge/PowerUsageDetail.java | 156 ++++++++++++++++ .../settings/fuelgauge/PowerUsageSummary.java | 200 ++++++++++++++++++--- 8 files changed, 520 insertions(+), 41 deletions(-) create mode 100644 res/layout/power_usage_detail_item_text.xml create mode 100644 res/layout/power_usage_details.xml create mode 100644 src/com/android/settings/fuelgauge/PowerUsageDetail.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 60d9616..204da69 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -505,7 +505,16 @@ - + + + + + + + + diff --git a/res/layout/power_usage_detail_item_text.xml b/res/layout/power_usage_detail_item_text.xml new file mode 100644 index 0000000..097469b --- /dev/null +++ b/res/layout/power_usage_detail_item_text.xml @@ -0,0 +1,43 @@ + + + + + + + + diff --git a/res/layout/power_usage_details.xml b/res/layout/power_usage_details.xml new file mode 100644 index 0000000..dd8d486 --- /dev/null +++ b/res/layout/power_usage_details.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index 94dcaf4..08252f6 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1503,16 +1503,16 @@ found in the list of installed applications. Starts: %1$d - %1$d days, %2$d hours, %3$d minutes, %4$d seconds + %1$d d, %2$d h, %3$d m, %4$d s - %1$d hours, %2$d minutes, %3$d seconds + %1$d h, %2$d m, %3$d s - %1$d minutes, %2$d seconds + %1$d m, %2$d s - %1$d seconds + %1$d s Packages sharing this UID: @@ -1604,12 +1604,23 @@ found in the list of installed applications. - Power usage summary + Battery usage + + Battery usage since unplugged + + Battery usage since reset Device awake time WiFi on time + + Battery usage details + + Usage details + + Controls + Screen on @@ -1620,6 +1631,31 @@ found in the list of installed applications. Voice Standby + + + CPU total + + CPU foreground + + GPS + + Phone + + Data sent + + Data received + + Audio + + Video + + + Usage since unplugged + + Usage totals + + Refresh + diff --git a/res/xml/power_usage_summary.xml b/res/xml/power_usage_summary.xml index 0c35905..80342a8 100644 --- a/res/xml/power_usage_summary.xml +++ b/res/xml/power_usage_summary.xml @@ -15,10 +15,6 @@ --> - - + android:title="@string/battery_since_unplugged"> diff --git a/src/com/android/settings/fuelgauge/PowerGaugePreference.java b/src/com/android/settings/fuelgauge/PowerGaugePreference.java index 5e89535..0bfa12d 100644 --- a/src/com/android/settings/fuelgauge/PowerGaugePreference.java +++ b/src/com/android/settings/fuelgauge/PowerGaugePreference.java @@ -26,6 +26,7 @@ import android.view.View; import android.widget.ImageView; import com.android.settings.R; +import com.android.settings.fuelgauge.PowerUsageSummary.BatterySipper; /** * Custom preference for displaying power consumption as a bar and an icon on the left for the @@ -37,13 +38,15 @@ public class PowerGaugePreference extends Preference { private Drawable mIcon; private GaugeDrawable mGauge; private double mValue; + private BatterySipper mInfo; - public PowerGaugePreference(Context context, Drawable icon) { + public PowerGaugePreference(Context context, Drawable icon, BatterySipper info) { super(context); setLayoutResource(R.layout.preference_powergauge); mIcon = icon; mGauge = new GaugeDrawable(); mGauge.bar = context.getResources().getDrawable(R.drawable.app_gauge); + mInfo = info; } /** @@ -55,6 +58,10 @@ public class PowerGaugePreference extends Preference { mGauge.percent = mValue; } + BatterySipper getInfo() { + return mInfo; + } + @Override protected void onBindView(View view) { super.onBindView(view); diff --git a/src/com/android/settings/fuelgauge/PowerUsageDetail.java b/src/com/android/settings/fuelgauge/PowerUsageDetail.java new file mode 100644 index 0000000..eeb8663 --- /dev/null +++ b/src/com/android/settings/fuelgauge/PowerUsageDetail.java @@ -0,0 +1,156 @@ +/* + * 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. + */ + +package com.android.settings.fuelgauge; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.android.settings.R; + +public class PowerUsageDetail extends Activity { + + 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_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"; + + private static final int SECONDS_PER_MINUTE = 60; + private static final int SECONDS_PER_HOUR = 60 * 60; + private static final int SECONDS_PER_DAY = 24 * 60 * 60; + + private static final boolean DEBUG = true; + private String mTitle; + private double mPercentage; + private int mUsageSince; + private int[] mTypes; + private double[] mValues; + private TextView mTitleView; + private ViewGroup mDetailsParent; + private long mStartTime; + + private static final String TAG = "PowerUsageDetail"; + + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + setContentView(R.layout.power_usage_details); + createDetails(); + } + + @Override + protected void onResume() { + super.onResume(); + mStartTime = android.os.Process.getElapsedCpuTime(); + } + + @Override + protected void onPause() { + super.onPause(); + } + + private void createDetails() { + final Intent intent = getIntent(); + mTitle = intent.getStringExtra(EXTRA_TITLE); + mPercentage = intent.getDoubleExtra(EXTRA_PERCENT, -1); + mUsageSince = intent.getIntExtra(EXTRA_USAGE_SINCE, USAGE_SINCE_UNPLUGGED); + + mTypes = intent.getIntArrayExtra(EXTRA_DETAIL_TYPES); + mValues = intent.getDoubleArrayExtra(EXTRA_DETAIL_VALUES); + + mTitleView = (TextView) findViewById(R.id.name); + mTitleView.setText(mTitle); + // TODO: I18N + ((TextView)findViewById(R.id.battery_percentage)) + .setText(String.format("%3.2f%% of battery usage since last unplugged", mPercentage)); + + mDetailsParent = (ViewGroup) findViewById(R.id.details); + LayoutInflater inflater = getLayoutInflater(); + if (mTypes != null && mValues != null) { + for (int i = 0; i < mTypes.length; i++) { + // Only add an item if the time is greater than zero + if (mValues[i] <= 0) continue; + final String label = getString(mTypes[i]); + String value = null; + switch (mTypes[i]) { + case R.string.usage_type_data_recv: + case R.string.usage_type_data_send: + value = formatBytes(mValues[i]); + break; + default: + value = formatTime(mValues[i]); + } + ViewGroup item = (ViewGroup) inflater.inflate(R.layout.power_usage_detail_item_text, + null); + mDetailsParent.addView(item); + TextView labelView = (TextView) item.findViewById(R.id.label); + TextView valueView = (TextView) item.findViewById(R.id.value); + labelView.setText(label); + valueView.setText(value); + } + } + } + + private String formatTime(double millis) { + StringBuilder sb = new StringBuilder(); + int seconds = (int) Math.floor(millis / 1000); + + int days = 0, hours = 0, minutes = 0; + if (seconds > SECONDS_PER_DAY) { + days = seconds / SECONDS_PER_DAY; + seconds -= days * SECONDS_PER_DAY; + } + if (seconds > SECONDS_PER_HOUR) { + hours = seconds / SECONDS_PER_HOUR; + seconds -= hours * SECONDS_PER_HOUR; + } + if (seconds > SECONDS_PER_MINUTE) { + minutes = seconds / SECONDS_PER_MINUTE; + seconds -= minutes * SECONDS_PER_MINUTE; + } + if (days > 0) { + sb.append(getString(R.string.battery_history_days, days, hours, minutes, seconds)); + } else if (hours > 0) { + sb.append(getString(R.string.battery_history_hours, hours, minutes, seconds)); + } else if (minutes > 0) { + sb.append(getString(R.string.battery_history_minutes, minutes, seconds)); + } else { + sb.append(getString(R.string.battery_history_seconds, seconds)); + } + return sb.toString(); + } + + private String formatBytes(double bytes) { + // TODO: I18N + if (bytes > 1000 * 1000) { + return String.format("%.2f MB", ((int) (bytes / 1000)) / 1000f); + } else if (bytes > 1024) { + return String.format("%.2f KB", ((int) (bytes / 10)) / 100f); + } else { + return String.format("%d bytes", (int) bytes); + } + } +} diff --git a/src/com/android/settings/fuelgauge/PowerUsageSummary.java b/src/com/android/settings/fuelgauge/PowerUsageSummary.java index 3abd858..296a9c7 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageSummary.java +++ b/src/com/android/settings/fuelgauge/PowerUsageSummary.java @@ -16,22 +16,31 @@ package com.android.settings.fuelgauge; +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.hardware.SensorManager; import android.os.BatteryStats; import android.os.Bundle; 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; +import android.preference.PreferenceScreen; import android.util.Log; import android.util.SparseArray; +import android.view.Menu; +import android.view.MenuItem; import com.android.internal.app.IBatteryStats; import com.android.internal.os.BatteryStatsImpl; @@ -52,7 +61,19 @@ public class PowerUsageSummary extends PreferenceActivity { private static final boolean DEBUG = true; private static final String TAG = "PowerUsageSummary"; - private static final String PREF_APP_LIST = "app_list"; + + 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; @@ -72,8 +93,6 @@ public class PowerUsageSummary extends PreferenceActivity { private PowerProfile mPowerProfile; - private static final long BATTERY_SIZE = 1200; - @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); @@ -81,7 +100,7 @@ public class PowerUsageSummary extends PreferenceActivity { addPreferencesFromResource(R.xml.power_usage_summary); mBatteryInfo = IBatteryStats.Stub.asInterface( ServiceManager.getService("batteryinfo")); - mAppListGroup = (PreferenceGroup) findPreference(PREF_APP_LIST); + mAppListGroup = getPreferenceScreen(); mPowerProfile = new PowerProfile(this, "power_profile_default"); } @@ -92,14 +111,100 @@ public class PowerUsageSummary extends PreferenceActivity { updateAppsList(); } + @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_PERCENT, sipper.getSortValue() * 100 / mTotalPower); + + switch (sipper.mDrainType) { + case APP: + { + Uid uid = sipper.mUid; + int[] types = new int[] { + R.string.usage_type_cpu, + R.string.usage_type_cpu_foreground, + R.string.usage_type_gps, + R.string.usage_type_data_send, + R.string.usage_type_data_recv, + R.string.usage_type_audio, + R.string.usage_type_video, + }; + double[] values = new double[] { + sipper.mCpuTime, + sipper.mCpuFgTime, + sipper.mGpsTime, + uid != null? uid.getTcpBytesSent(mStatsType) : 0, + uid != null? uid.getTcpBytesReceived(mStatsType) : 0, + 0, + 0 + }; + intent.putExtra(PowerUsageDetail.EXTRA_DETAIL_TYPES, types); + intent.putExtra(PowerUsageDetail.EXTRA_DETAIL_VALUES, values); + + } + break; + } + startActivity(intent); + + return super.onPreferenceTreeClick(preferenceScreen, preference); + } + + @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'); + */ + menu.add(0, MENU_STATS_REFRESH, 0, R.string.menu_stats_refresh) + .setIcon(com.android.internal.R.drawable.ic_menu_refresh) + .setAlphabeticShortcut('r'); + return true; + } + + @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); + */ + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case MENU_STATS_TYPE: + if (mStatsType == BatteryStats.STATS_TOTAL) { + mStatsType = BatteryStats.STATS_UNPLUGGED; + } else { + mStatsType = BatteryStats.STATS_TOTAL; + } + updateAppsList(); + return true; + case MENU_STATS_REFRESH: + mStats = null; + updateAppsList(); + return true; + default: + return false; + } + } + private void updateAppsList() { if (mStats == null) { load(); } + mMaxPower = 0; + mTotalPower = 0; mAppListGroup.removeAll(); mUsageList.clear(); - processCpuUsage(); + processAppUsage(); processMiscUsage(); mAppListGroup.setOrderingAsAdded(false); @@ -107,11 +212,10 @@ public class PowerUsageSummary extends PreferenceActivity { Collections.sort(mUsageList); for (BatterySipper g : mUsageList) { if (g.getSortValue() < MIN_POWER_THRESHOLD) continue; - double percent = ((g.getSortValue() / mTotalPower) * 100 / BATTERY_SIZE); - PowerGaugePreference pref = new PowerGaugePreference(this, g.getIcon()); - double scaleByMax = (g.getSortValue() * 100) / mTotalPower; - pref.setSummary(g.getLabel() + " ( " + (int) g.getSortValue() + "mA, " - + String.format("%3.2f", scaleByMax) + "% )"); + double percent = ((g.getSortValue() / mTotalPower) * 100); + PowerGaugePreference pref = new PowerGaugePreference(this, g.getIcon(), g); + double scaleByMax = (g.getSortValue() * 100) / mMaxPower; + pref.setSummary(g.getLabel() + " ( " + String.format("%3.2f", percent) + "% )"); pref.setOrder(Integer.MAX_VALUE - (int) g.getSortValue()); // Invert the order pref.setGaugeValue(mScaleByMax ? scaleByMax : percent); mAppListGroup.addPreference(pref); @@ -119,7 +223,8 @@ public class PowerUsageSummary extends PreferenceActivity { } } - private void processCpuUsage() { + private void processAppUsage() { + SensorManager sensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE); final int which = mStatsType; final double powerCpuNormal = mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_NORMAL); long uSecTime = mStats.computeBatteryRealtime(SystemClock.elapsedRealtime(), which) * 1000; @@ -131,6 +236,9 @@ public class PowerUsageSummary extends PreferenceActivity { double power = 0; //mUsageList.add(new AppUsage(u.getUid(), new double[] {power})); Map processStats = u.getProcessStats(); + long cpuTime = 0; + long cpuFgTime = 0; + long gpsTime = 0; if (processStats.size() > 0) { for (Map.Entry ent : processStats.entrySet()) { @@ -138,12 +246,20 @@ public class PowerUsageSummary extends PreferenceActivity { Uid.Proc ps = ent.getValue(); long userTime = ps.getUserTime(which); long systemTime = ps.getSystemTime(which); - //long starts = ps.getStarts(which); - power += (userTime + systemTime) * 10 /* convert to milliseconds */ - * powerCpuNormal; + 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; } } + if (cpuFgTime > cpuTime) { + if (DEBUG && cpuFgTime > cpuTime + 10000) { + Log.i(TAG, "WARNING! Cputime is more than 10 seconds behind Foreground time"); + } + cpuTime = cpuFgTime; // Statistics may not have been gathered yet. + } power /= 1000; Map sensorStats = u.getSensorStats(); @@ -152,17 +268,32 @@ public class PowerUsageSummary extends PreferenceActivity { Uid.Sensor sensor = sensorEntry.getValue(); int sensorType = sensor.getHandle(); BatteryStats.Timer timer = sensor.getSensorTime(); - long sensorTime = timer.getTotalTimeLocked(uSecTime, which); + long sensorTime = timer.getTotalTimeLocked(uSecTime, which) / 1000; double multiplier = 0; switch (sensorType) { case Uid.Sensor.GPS: multiplier = mPowerProfile.getAveragePower(PowerProfile.POWER_GPS_ON); + gpsTime = sensorTime; break; + default: + android.hardware.Sensor sensorData = + sensorManager.getDefaultSensor(sensorType); + if (sensorData != null) { + multiplier = sensorData.getPower(); + if (DEBUG) { + Log.i(TAG, "Got sensor " + sensorData.getName() + " with power = " + + multiplier); + } + } } - power += multiplier * sensorTime; + power += (multiplier * sensorTime) / 1000; } if (power != 0) { - BatterySipper app = new BatterySipper(null, 0, u.getUid(), new double[] {power}); + BatterySipper app = new BatterySipper(null, DrainType.APP, 0, u, + new double[] {power}); + app.mCpuTime = cpuTime; + app.mGpsTime = gpsTime; + app.mCpuFgTime = cpuFgTime; mUsageList.add(app); } if (power > mMaxPower) mMaxPower = power; @@ -183,7 +314,8 @@ public class PowerUsageSummary extends PreferenceActivity { final double screenFullPower = mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_FULL); for (int i = 0; i < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; i++) { - double screenBinPower = screenFullPower * i / BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; + double screenBinPower = screenFullPower * (i + 0.5f) + / BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; long brightnessTime = mStats.getScreenBrightnessTime(i, uSecNow, mStatsType) / 1000; power += screenBinPower * brightnessTime; if (DEBUG) { @@ -215,30 +347,34 @@ public class PowerUsageSummary extends PreferenceActivity { } double phoneOnPower = getPhoneOnPower(uSecNow); - addEntry(getString(R.string.power_phone), android.R.drawable.ic_menu_call, phoneOnPower); + 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), android.R.drawable.ic_menu_view, screenOnPower); + 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), R.drawable.ic_wifi_signal_4, wifiPower); + 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), android.R.drawable.ic_lock_power_off, idlePower); + 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), + addEntry(getString(R.string.power_cell), DrainType.CELL, android.R.drawable.ic_menu_sort_by_size, radioPower); } - private void addEntry(String label, int iconId, double power) { + private void addEntry(String label, DrainType drainType, int iconId, double power) { if (power > mMaxPower) mMaxPower = power; mTotalPower += power; - BatterySipper bs = new BatterySipper(label, iconId, 0, new double[] {power}); + BatterySipper bs = new BatterySipper(label, drainType, iconId, null, new double[] {power}); mUsageList.add(bs); } @@ -258,21 +394,27 @@ public class PowerUsageSummary extends PreferenceActivity { class BatterySipper implements Comparable { String mLabel; Drawable mIcon; - int mUid; + Uid mUid; double mValue; double[] mValues; + DrainType mDrainType; + long mCpuTime; + long mGpsTime; + long mCpuFgTime; - BatterySipper(String label, int iconId, int uid, double[] values) { + BatterySipper(String label, DrainType drainType, int iconId, Uid uid, double[] values) { mValues = values; mLabel = label; + mDrainType = drainType; if (iconId > 0) { mIcon = getResources().getDrawable(iconId); } if (mValues != null) mValue = mValues[0]; //if (uid > 0 && (mLabel == null || mIcon == null) // TODO: - if ((label == null || iconId == 0) && uid > 0) { - getNameForUid(uid); + if ((label == null || iconId == 0) && uid!= null) { + getNameForUid(uid.getUid()); } + mUid = uid; } double getSortValue() { -- cgit v1.1