diff --git a/src/com/android/settings/fuelgauge/BatterySipper.java b/src/com/android/settings/fuelgauge/BatterySipper.java index d581fb4374f..524543475e0 100644 --- a/src/com/android/settings/fuelgauge/BatterySipper.java +++ b/src/com/android/settings/fuelgauge/BatterySipper.java @@ -30,7 +30,11 @@ import android.os.BatteryStats.Uid; import java.util.ArrayList; import java.util.HashMap; -class BatterySipper implements Comparable { +/** + * Contains information about package name, icon image, power usage about an + * application or a system service. + */ +public class BatterySipper implements Comparable { final Context mContext; /* Cache cleared when PowerUsageSummary is destroyed */ static final HashMap sUidCache = new HashMap(); @@ -56,6 +60,7 @@ class BatterySipper implements Comparable { double percent; double noCoveragePercent; String defaultPackageName; + String[] mPackages; static class UidToDetail { String name; @@ -90,15 +95,30 @@ class BatterySipper implements Comparable { return values; } - Drawable getIcon() { + public Drawable getIcon() { return icon; } + /** + * Gets the application name + */ + public String getLabel() { + return name; + } + + @Override public int compareTo(BatterySipper other) { // Return the flipped value because we want the items in descending order return Double.compare(other.getSortValue(), getSortValue()); } + /** + * Gets a list of packages associated with the current user + */ + public String[] getPackages() { + return mPackages; + } + void getQuickNameIconForUid(Uid uidObj) { final int uid = uidObj.getUid(); final String uidString = Integer.toString(uid); @@ -125,27 +145,36 @@ class BatterySipper implements Comparable { } else { //name = packages[0]; } - synchronized (mRequestQueue) { - mRequestQueue.add(this); + if (mHandler != null) { + synchronized (mRequestQueue) { + mRequestQueue.add(this); + } } } + public static void clearUidCache() { + sUidCache.clear(); + } + /** - * Sets name and icon - * @param uid Uid of the application + * Loads the app label and icon image and stores into the cache. */ - void getNameIcon() { + public void loadNameAndIcon() { + // Bail out if the current sipper is not an App sipper. + if (uidObj == null) { + return; + } PackageManager pm = mContext.getPackageManager(); final int uid = uidObj.getUid(); final Drawable defaultActivityIcon = pm.getDefaultActivityIcon(); - String[] packages = pm.getPackagesForUid(uid); - if (packages == null) { + mPackages = pm.getPackagesForUid(uid); + if (mPackages == null) { name = Integer.toString(uid); return; } - String[] packageLabels = new String[packages.length]; - System.arraycopy(packages, 0, packageLabels, 0, packages.length); + String[] packageLabels = new String[mPackages.length]; + System.arraycopy(mPackages, 0, packageLabels, 0, mPackages.length); int preferredIndex = -1; // Convert package names to user-facing labels where possible @@ -159,7 +188,7 @@ class BatterySipper implements Comparable { packageLabels[i] = label.toString(); } if (ai.icon != 0) { - defaultPackageName = packages[i]; + defaultPackageName = mPackages[i]; icon = ai.loadIcon(pm); break; } @@ -172,7 +201,7 @@ class BatterySipper implements Comparable { name = packageLabels[0]; } else { // Look for an official name for this UID. - for (String pkgName : packages) { + for (String pkgName : mPackages) { try { final PackageInfo pi = pm.getPackageInfo(pkgName, 0); if (pi.sharedUserLabel != 0) { @@ -197,6 +226,9 @@ class BatterySipper implements Comparable { utd.icon = icon; utd.packageName = defaultPackageName; sUidCache.put(uidString, utd); - mHandler.sendMessage(mHandler.obtainMessage(PowerUsageSummary.MSG_UPDATE_NAME_ICON, this)); + if (mHandler != null) { + mHandler.sendMessage( + mHandler.obtainMessage(BatteryStatsHelper.MSG_UPDATE_NAME_ICON, this)); + } } -} \ No newline at end of file +} diff --git a/src/com/android/settings/fuelgauge/BatteryStatsHelper.java b/src/com/android/settings/fuelgauge/BatteryStatsHelper.java new file mode 100644 index 00000000000..2a3e6e85cc3 --- /dev/null +++ b/src/com/android/settings/fuelgauge/BatteryStatsHelper.java @@ -0,0 +1,819 @@ +/* + * 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 static android.os.BatteryStats.NETWORK_MOBILE_RX_BYTES; +import static android.os.BatteryStats.NETWORK_MOBILE_TX_BYTES; +import static android.os.BatteryStats.NETWORK_WIFI_RX_BYTES; +import static android.os.BatteryStats.NETWORK_WIFI_TX_BYTES; + +import android.app.Activity; +import android.content.Context; +import android.content.pm.UserInfo; +import android.graphics.drawable.Drawable; +import android.hardware.Sensor; +import android.hardware.SensorManager; +import android.os.BatteryStats; +import android.os.BatteryStats.Uid; +import android.os.Bundle; +import android.os.Handler; +import android.os.Parcel; +import android.os.Process; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.os.UserHandle; +import android.os.UserManager; +import android.preference.PreferenceActivity; +import android.telephony.SignalStrength; +import android.util.Log; +import android.util.SparseArray; + +import com.android.internal.app.IBatteryStats; +import com.android.internal.os.BatteryStatsImpl; +import com.android.internal.os.PowerProfile; +import com.android.internal.util.FastPrintWriter; +import com.android.settings.R; +import com.android.settings.fuelgauge.PowerUsageDetail.DrainType; +import com.android.settings.users.UserUtils; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * A helper class for retrieving the power usage information for all applications and services. + * + * The caller must initialize this class as soon as activity object is ready to use (for example, in + * onAttach() for Fragment), call create() in onCreate() and call destroy() in onDestroy(). + */ +public class BatteryStatsHelper { + + private static final boolean DEBUG = false; + + private static final String TAG = BatteryStatsHelper.class.getSimpleName(); + + private static BatteryStatsImpl sStatsXfer; + private IBatteryStats mBatteryInfo; + private UserManager mUm; + private BatteryStatsImpl mStats; + private PowerProfile mPowerProfile; + + private final List mUsageList = new ArrayList(); + private final List mWifiSippers = new ArrayList(); + private final List mBluetoothSippers = new ArrayList(); + private final SparseArray> mUserSippers + = new SparseArray>(); + private final SparseArray mUserPower = new SparseArray(); + + private int mStatsType = BatteryStats.STATS_SINCE_CHARGED; + + private long mStatsPeriod = 0; + private double mMaxPower = 1; + private double mTotalPower; + private double mWifiPower; + private double mBluetoothPower; + + // How much the apps together have left WIFI running. + private long mAppWifiRunning; + + /** Queue for fetching name and icon for an application */ + private ArrayList mRequestQueue = new ArrayList(); + + private Activity mActivity; + private Handler mHandler; + + private class NameAndIconLoader extends Thread { + private boolean mAbort = false; + + public NameAndIconLoader() { + super("BatteryUsage Icon Loader"); + } + + public void abort() { + mAbort = true; + } + + @Override + public void run() { + while (true) { + BatterySipper bs; + synchronized (mRequestQueue) { + if (mRequestQueue.isEmpty() || mAbort) { + mHandler.sendEmptyMessage(MSG_REPORT_FULLY_DRAWN); + return; + } + bs = mRequestQueue.remove(0); + } + bs.loadNameAndIcon(); + } + } + } + + private NameAndIconLoader mRequestThread; + + public BatteryStatsHelper(Activity activity, Handler handler) { + mActivity = activity; + mHandler = handler; + } + + /** Clears the current stats and forces recreating for future use. */ + public void clearStats() { + mStats = null; + } + + public BatteryStatsImpl getStats() { + if (mStats == null) { + load(); + } + return mStats; + } + + public PowerProfile getPowerProfile() { + return mPowerProfile; + } + + public void create(Bundle icicle) { + if (icicle != null) { + mStats = sStatsXfer; + } + mBatteryInfo = IBatteryStats.Stub.asInterface( + ServiceManager.getService(BatteryStats.SERVICE_NAME)); + mUm = (UserManager) mActivity.getSystemService(Context.USER_SERVICE); + mPowerProfile = new PowerProfile(mActivity); + } + + public void pause() { + if (mRequestThread != null) { + mRequestThread.abort(); + } + } + + public void destroy() { + if (mActivity.isChangingConfigurations()) { + sStatsXfer = mStats; + } else { + BatterySipper.sUidCache.clear(); + } + } + + public void startBatteryDetailPage(PreferenceActivity caller, BatterySipper sipper) { + // Initialize mStats if necessary. + getStats(); + + Bundle args = new Bundle(); + args.putString(PowerUsageDetail.EXTRA_TITLE, sipper.name); + args.putInt(PowerUsageDetail.EXTRA_PERCENT, (int) + Math.ceil(sipper.getSortValue() * 100 / mTotalPower)); + args.putInt(PowerUsageDetail.EXTRA_GAUGE, (int) + Math.ceil(sipper.getSortValue() * 100 / mMaxPower)); + args.putLong(PowerUsageDetail.EXTRA_USAGE_DURATION, mStatsPeriod); + args.putString(PowerUsageDetail.EXTRA_ICON_PACKAGE, sipper.defaultPackageName); + args.putInt(PowerUsageDetail.EXTRA_ICON_ID, sipper.iconId); + args.putDouble(PowerUsageDetail.EXTRA_NO_COVERAGE, sipper.noCoveragePercent); + if (sipper.uidObj != null) { + args.putInt(PowerUsageDetail.EXTRA_UID, sipper.uidObj.getUid()); + } + args.putSerializable(PowerUsageDetail.EXTRA_DRAIN_TYPE, sipper.drainType); + + int[] types; + double[] values; + switch (sipper.drainType) { + case APP: + case USER: + { + Uid uid = sipper.uidObj; + types = new int[] { + R.string.usage_type_cpu, + R.string.usage_type_cpu_foreground, + R.string.usage_type_wake_lock, + R.string.usage_type_gps, + R.string.usage_type_wifi_running, + R.string.usage_type_data_recv, + R.string.usage_type_data_send, + R.string.usage_type_data_wifi_recv, + R.string.usage_type_data_wifi_send, + R.string.usage_type_audio, + R.string.usage_type_video, + }; + values = new double[] { + sipper.cpuTime, + sipper.cpuFgTime, + sipper.wakeLockTime, + sipper.gpsTime, + sipper.wifiRunningTime, + sipper.mobileRxBytes, + sipper.mobileTxBytes, + sipper.wifiRxBytes, + sipper.wifiTxBytes, + 0, + 0 + }; + + if (sipper.drainType == DrainType.APP) { + Writer result = new StringWriter(); + PrintWriter printWriter = new FastPrintWriter(result, false, 1024); + mStats.dumpLocked(printWriter, "", mStatsType, uid.getUid()); + printWriter.flush(); + args.putString(PowerUsageDetail.EXTRA_REPORT_DETAILS, result.toString()); + + result = new StringWriter(); + printWriter = new FastPrintWriter(result, false, 1024); + mStats.dumpCheckinLocked(printWriter, mStatsType, uid.getUid()); + printWriter.flush(); + args.putString(PowerUsageDetail.EXTRA_REPORT_CHECKIN_DETAILS, + result.toString()); + } + } + break; + case CELL: + { + types = new int[] { + R.string.usage_type_on_time, + R.string.usage_type_no_coverage + }; + values = new double[] { + sipper.usageTime, + sipper.noCoveragePercent + }; + } + break; + case WIFI: + { + types = new int[] { + R.string.usage_type_wifi_running, + R.string.usage_type_cpu, + R.string.usage_type_cpu_foreground, + R.string.usage_type_wake_lock, + R.string.usage_type_data_recv, + R.string.usage_type_data_send, + R.string.usage_type_data_wifi_recv, + R.string.usage_type_data_wifi_send, + }; + values = new double[] { + sipper.usageTime, + sipper.cpuTime, + sipper.cpuFgTime, + sipper.wakeLockTime, + sipper.mobileRxBytes, + sipper.mobileTxBytes, + sipper.wifiRxBytes, + sipper.wifiTxBytes, + }; + } break; + case BLUETOOTH: + { + types = new int[] { + R.string.usage_type_on_time, + R.string.usage_type_cpu, + R.string.usage_type_cpu_foreground, + R.string.usage_type_wake_lock, + R.string.usage_type_data_recv, + R.string.usage_type_data_send, + R.string.usage_type_data_wifi_recv, + R.string.usage_type_data_wifi_send, + }; + values = new double[] { + sipper.usageTime, + sipper.cpuTime, + sipper.cpuFgTime, + sipper.wakeLockTime, + sipper.mobileRxBytes, + sipper.mobileTxBytes, + sipper.wifiRxBytes, + sipper.wifiTxBytes, + }; + } break; + default: + { + types = new int[] { + R.string.usage_type_on_time + }; + values = new double[] { + sipper.usageTime + }; + } + } + args.putIntArray(PowerUsageDetail.EXTRA_DETAIL_TYPES, types); + args.putDoubleArray(PowerUsageDetail.EXTRA_DETAIL_VALUES, values); + caller.startPreferencePanel(PowerUsageDetail.class.getName(), args, + R.string.details_title, null, null, 0); + } + + public void refreshStats() { + // Initialize mStats if necessary. + getStats(); + + mMaxPower = 0; + mTotalPower = 0; + mWifiPower = 0; + mBluetoothPower = 0; + mAppWifiRunning = 0; + + mUsageList.clear(); + mWifiSippers.clear(); + mBluetoothSippers.clear(); + mUserSippers.clear(); + mUserPower.clear(); + + processAppUsage(); + processMiscUsage(); + + Collections.sort(mUsageList); + + if (mHandler != null) { + synchronized (mRequestQueue) { + if (!mRequestQueue.isEmpty()) { + if (mRequestThread != null) { + mRequestThread.abort(); + } + mRequestThread = new NameAndIconLoader(); + mRequestThread.setPriority(Thread.MIN_PRIORITY); + mRequestThread.start(); + mRequestQueue.notify(); + } + } + } + } + + private void processAppUsage() { + SensorManager sensorManager = (SensorManager) mActivity.getSystemService( + Context.SENSOR_SERVICE); + final int which = mStatsType; + final int speedSteps = mPowerProfile.getNumSpeedSteps(); + final double[] powerCpuNormal = new double[speedSteps]; + final long[] cpuSpeedStepTimes = new long[speedSteps]; + for (int p = 0; p < speedSteps; p++) { + powerCpuNormal[p] = mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_ACTIVE, p); + } + final double mobilePowerPerByte = getMobilePowerPerByte(); + final double wifiPowerPerByte = getWifiPowerPerByte(); + long uSecTime = mStats.computeBatteryRealtime(SystemClock.elapsedRealtime() * 1000, which); + long appWakelockTime = 0; + BatterySipper osApp = null; + mStatsPeriod = uSecTime; + SparseArray uidStats = mStats.getUidStats(); + final int NU = uidStats.size(); + for (int iu = 0; iu < NU; iu++) { + Uid u = uidStats.valueAt(iu); + double p; // in mAs + double power = 0; // in mAs + double highestDrain = 0; + String packageWithHighestDrain = null; + //mUsageList.add(new AppUsage(u.getUid(), new double[] {power})); + Map processStats = u.getProcessStats(); + long cpuTime = 0; + long cpuFgTime = 0; + long wakelockTime = 0; + long gpsTime = 0; + if (DEBUG) Log.i(TAG, "UID " + u.getUid()); + if (processStats.size() > 0) { + // Process CPU time + for (Map.Entry ent + : processStats.entrySet()) { + Uid.Proc ps = ent.getValue(); + final long userTime = ps.getUserTime(which); + final long systemTime = ps.getSystemTime(which); + final long foregroundTime = ps.getForegroundTime(which); + cpuFgTime += foregroundTime * 10; // convert to millis + final long tmpCpuTime = (userTime + systemTime) * 10; // convert to millis + int totalTimeAtSpeeds = 0; + // Get the total first + for (int step = 0; step < speedSteps; step++) { + cpuSpeedStepTimes[step] = ps.getTimeAtCpuSpeedStep(step, which); + totalTimeAtSpeeds += cpuSpeedStepTimes[step]; + } + if (totalTimeAtSpeeds == 0) totalTimeAtSpeeds = 1; + // Then compute the ratio of time spent at each speed + double processPower = 0; + for (int step = 0; step < speedSteps; step++) { + double ratio = (double) cpuSpeedStepTimes[step] / totalTimeAtSpeeds; + processPower += ratio * tmpCpuTime * powerCpuNormal[step]; + } + cpuTime += tmpCpuTime; + if (DEBUG && processPower != 0) { + Log.i(TAG, String.format("process %s, cpu power=%.2f", + ent.getKey(), processPower / 1000)); + } + power += processPower; + if (packageWithHighestDrain == null + || packageWithHighestDrain.startsWith("*")) { + highestDrain = processPower; + packageWithHighestDrain = ent.getKey(); + } else if (highestDrain < processPower + && !ent.getKey().startsWith("*")) { + highestDrain = processPower; + packageWithHighestDrain = ent.getKey(); + } + } + } + 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; + if (DEBUG && power != 0) Log.i(TAG, String.format("total cpu power=%.2f", power)); + + // Process wake lock usage + Map wakelockStats = u.getWakelockStats(); + for (Map.Entry wakelockEntry + : wakelockStats.entrySet()) { + Uid.Wakelock wakelock = wakelockEntry.getValue(); + // Only care about partial wake locks since full wake locks + // are canceled when the user turns the screen off. + BatteryStats.Timer timer = wakelock.getWakeTime(BatteryStats.WAKE_TYPE_PARTIAL); + if (timer != null) { + wakelockTime += timer.getTotalTimeLocked(uSecTime, which); + } + } + wakelockTime /= 1000; // convert to millis + appWakelockTime += wakelockTime; + + // Add cost of holding a wake lock + p = (wakelockTime + * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE)) / 1000; + power += p; + if (DEBUG && p != 0) Log.i(TAG, String.format("wakelock power=%.2f", p)); + + // Add cost of mobile traffic + final long mobileRx = u.getNetworkActivityCount(NETWORK_MOBILE_RX_BYTES, mStatsType); + final long mobileTx = u.getNetworkActivityCount(NETWORK_MOBILE_TX_BYTES, mStatsType); + p = (mobileRx + mobileTx) * mobilePowerPerByte; + power += p; + if (DEBUG && p != 0) Log.i(TAG, String.format("mobile power=%.2f", p)); + + // Add cost of wifi traffic + final long wifiRx = u.getNetworkActivityCount(NETWORK_WIFI_RX_BYTES, mStatsType); + final long wifiTx = u.getNetworkActivityCount(NETWORK_WIFI_TX_BYTES, mStatsType); + p = (wifiRx + wifiTx) * wifiPowerPerByte; + power += p; + if (DEBUG && p != 0) Log.i(TAG, String.format("wifi power=%.2f", p)); + + // Add cost of keeping WIFI running. + long wifiRunningTimeMs = u.getWifiRunningTime(uSecTime, which) / 1000; + mAppWifiRunning += wifiRunningTimeMs; + p = (wifiRunningTimeMs + * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)) / 1000; + power += p; + if (DEBUG && p != 0) Log.i(TAG, String.format("wifi running power=%.2f", p)); + + // Add cost of WIFI scans + long wifiScanTimeMs = u.getWifiScanTime(uSecTime, which) / 1000; + p = (wifiScanTimeMs + * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_SCAN)) / 1000; + power += p; + if (DEBUG && p != 0) Log.i(TAG, String.format("wifi scanning power=%.2f", p)); + + // Process Sensor usage + Map sensorStats = u.getSensorStats(); + for (Map.Entry sensorEntry + : sensorStats.entrySet()) { + Uid.Sensor sensor = sensorEntry.getValue(); + int sensorHandle = sensor.getHandle(); + BatteryStats.Timer timer = sensor.getSensorTime(); + long sensorTime = timer.getTotalTimeLocked(uSecTime, which) / 1000; + double multiplier = 0; + switch (sensorHandle) { + case Uid.Sensor.GPS: + multiplier = mPowerProfile.getAveragePower(PowerProfile.POWER_GPS_ON); + gpsTime = sensorTime; + break; + default: + List sensorList = sensorManager.getSensorList( + android.hardware.Sensor.TYPE_ALL); + for (android.hardware.Sensor s : sensorList) { + if (s.getHandle() == sensorHandle) { + multiplier = s.getPower(); + break; + } + } + } + p = (multiplier * sensorTime) / 1000; + power += p; + if (DEBUG && p != 0) { + Log.i(TAG, String.format("sensor %s power=%.2f", sensor.toString(), p)); + } + } + + if (DEBUG) Log.i(TAG, String.format("UID %d total power=%.2f", u.getUid(), power)); + + // Add the app to the list if it is consuming power + boolean isOtherUser = false; + final int userId = UserHandle.getUserId(u.getUid()); + if (power != 0 || u.getUid() == 0) { + BatterySipper app = new BatterySipper(mActivity, mRequestQueue, mHandler, + packageWithHighestDrain, DrainType.APP, 0, u, + new double[] {power}); + app.cpuTime = cpuTime; + app.gpsTime = gpsTime; + app.wifiRunningTime = wifiRunningTimeMs; + app.cpuFgTime = cpuFgTime; + app.wakeLockTime = wakelockTime; + app.mobileRxBytes = mobileRx; + app.mobileTxBytes = mobileTx; + app.wifiRxBytes = wifiRx; + app.wifiTxBytes = wifiTx; + if (u.getUid() == Process.WIFI_UID) { + mWifiSippers.add(app); + } else if (u.getUid() == Process.BLUETOOTH_UID) { + mBluetoothSippers.add(app); + } else if (userId != UserHandle.myUserId() + && UserHandle.getAppId(u.getUid()) >= Process.FIRST_APPLICATION_UID) { + isOtherUser = true; + List list = mUserSippers.get(userId); + if (list == null) { + list = new ArrayList(); + mUserSippers.put(userId, list); + } + list.add(app); + } else { + mUsageList.add(app); + } + if (u.getUid() == 0) { + osApp = app; + } + } + if (power != 0) { + if (u.getUid() == Process.WIFI_UID) { + mWifiPower += power; + } else if (u.getUid() == Process.BLUETOOTH_UID) { + mBluetoothPower += power; + } else if (isOtherUser) { + Double userPower = mUserPower.get(userId); + if (userPower == null) { + userPower = power; + } else { + userPower += power; + } + mUserPower.put(userId, userPower); + } else { + if (power > mMaxPower) mMaxPower = power; + mTotalPower += power; + } + } + } + + // The device has probably been awake for longer than the screen on + // time and application wake lock time would account for. Assign + // this remainder to the OS, if possible. + if (osApp != null) { + long wakeTimeMillis = mStats.computeBatteryUptime( + SystemClock.uptimeMillis() * 1000, which) / 1000; + wakeTimeMillis -= appWakelockTime + (mStats.getScreenOnTime( + SystemClock.elapsedRealtime(), which) / 1000); + if (wakeTimeMillis > 0) { + double power = (wakeTimeMillis + * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE)) / 1000; + if (DEBUG) Log.i(TAG, "OS wakeLockTime " + wakeTimeMillis + " power " + power); + osApp.wakeLockTime += wakeTimeMillis; + osApp.value += power; + osApp.values[0] += power; + if (osApp.value > mMaxPower) mMaxPower = osApp.value; + mTotalPower += power; + } + } + } + + private void addPhoneUsage(long uSecNow) { + long phoneOnTimeMs = mStats.getPhoneOnTime(uSecNow, mStatsType) / 1000; + double phoneOnPower = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE) + * phoneOnTimeMs / 1000; + addEntry(mActivity.getString(R.string.power_phone), DrainType.PHONE, phoneOnTimeMs, + R.drawable.ic_settings_voice_calls, phoneOnPower); + } + + private void addScreenUsage(long uSecNow) { + double power = 0; + 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++) { + double screenBinPower = screenFullPower * (i + 0.5f) + / BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; + long brightnessTime = mStats.getScreenBrightnessTime(i, uSecNow, mStatsType) / 1000; + power += screenBinPower * brightnessTime; + if (DEBUG) { + Log.i(TAG, "Screen bin power = " + (int) screenBinPower + ", time = " + + brightnessTime); + } + } + power /= 1000; // To seconds + addEntry(mActivity.getString(R.string.power_screen), DrainType.SCREEN, screenOnTimeMs, + R.drawable.ic_settings_display, power); + } + + private void addRadioUsage(long uSecNow) { + double power = 0; + final int BINS = SignalStrength.NUM_SIGNAL_STRENGTH_BINS; + long signalTimeMs = 0; + for (int i = 0; i < BINS; i++) { + long strengthTimeMs = mStats.getPhoneSignalStrengthTime(i, uSecNow, mStatsType) / 1000; + power += strengthTimeMs / 1000 + * mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ON, i); + signalTimeMs += strengthTimeMs; + } + long scanningTimeMs = mStats.getPhoneSignalScanningTime(uSecNow, mStatsType) / 1000; + power += scanningTimeMs / 1000 * mPowerProfile.getAveragePower( + PowerProfile.POWER_RADIO_SCANNING); + BatterySipper bs = + addEntry(mActivity.getString(R.string.power_cell), DrainType.CELL, + signalTimeMs, R.drawable.ic_settings_cell_standby, power); + if (signalTimeMs != 0) { + bs.noCoveragePercent = mStats.getPhoneSignalStrengthTime(0, uSecNow, mStatsType) + / 1000 * 100.0 / signalTimeMs; + } + } + + private void aggregateSippers(BatterySipper bs, List from, String tag) { + for (int i=0; i sippers = mUserSippers.valueAt(i); + UserInfo info = mUm.getUserInfo(userId); + Drawable icon; + String name; + if (info != null) { + icon = UserUtils.getUserIcon(mActivity, mUm, info, mActivity.getResources()); + name = info != null ? info.name : null; + if (name == null) { + name = Integer.toString(info.id); + } + name = mActivity.getResources().getString( + R.string.running_process_item_user_label, name); + } else { + icon = null; + name = mActivity.getResources().getString( + R.string.running_process_item_removed_user_label); + } + double power = mUserPower.get(userId); + BatterySipper bs = addEntry(name, DrainType.USER, 0, 0, power); + bs.icon = icon; + aggregateSippers(bs, sippers, "User"); + } + } + + /** + * Return estimated power (in mAs) of sending a byte with the mobile radio. + */ + private double getMobilePowerPerByte() { + final long MOBILE_BPS = 200000; // TODO: Extract average bit rates from system + final double MOBILE_POWER = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE) + / 3600; + + final long mobileRx = mStats.getNetworkActivityCount(NETWORK_MOBILE_RX_BYTES, mStatsType); + final long mobileTx = mStats.getNetworkActivityCount(NETWORK_MOBILE_TX_BYTES, mStatsType); + final long mobileData = mobileRx + mobileTx; + + final long radioDataUptimeMs = mStats.getRadioDataUptime() / 1000; + final long mobileBps = radioDataUptimeMs != 0 + ? mobileData * 8 * 1000 / radioDataUptimeMs + : MOBILE_BPS; + + return MOBILE_POWER / (mobileBps / 8); + } + + /** + * Return estimated power (in mAs) of sending a byte with the Wi-Fi radio. + */ + private double getWifiPowerPerByte() { + final long WIFI_BPS = 1000000; // TODO: Extract average bit rates from system + final double WIFI_POWER = mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ACTIVE) + / 3600; + return WIFI_POWER / (WIFI_BPS / 8); + } + + private void processMiscUsage() { + final int which = mStatsType; + long uSecTime = SystemClock.elapsedRealtime() * 1000; + final long uSecNow = mStats.computeBatteryRealtime(uSecTime, which); + final long timeSinceUnplugged = uSecNow; + if (DEBUG) { + Log.i(TAG, "Uptime since last unplugged = " + (timeSinceUnplugged / 1000)); + } + + addUserUsage(); + addPhoneUsage(uSecNow); + addScreenUsage(uSecNow); + addWiFiUsage(uSecNow); + addBluetoothUsage(uSecNow); + addIdleUsage(uSecNow); // Not including cellular idle power + // Don't compute radio usage if it's a wifi-only device + if (!com.android.settings.Utils.isWifiOnly(mActivity)) { + addRadioUsage(uSecNow); + } + } + + private BatterySipper addEntry(String label, DrainType drainType, long time, int iconId, + double power) { + if (power > mMaxPower) mMaxPower = power; + mTotalPower += power; + BatterySipper bs = new BatterySipper(mActivity, mRequestQueue, mHandler, + label, drainType, iconId, null, new double[] {power}); + bs.usageTime = time; + bs.iconId = iconId; + mUsageList.add(bs); + return bs; + } + + public List getUsageList() { + return mUsageList; + } + + static final int MSG_UPDATE_NAME_ICON = 1; + static final int MSG_REPORT_FULLY_DRAWN = 2; + + public double getMaxPower() { + return mMaxPower; + } + + public double getTotalPower() { + return mTotalPower; + } + + private void load() { + try { + byte[] data = mBatteryInfo.getStatistics(); + Parcel parcel = Parcel.obtain(); + parcel.unmarshall(data, 0, data.length); + parcel.setDataPosition(0); + mStats = com.android.internal.os.BatteryStatsImpl.CREATOR + .createFromParcel(parcel); + mStats.distributeWorkLocked(BatteryStats.STATS_SINCE_CHARGED); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException:", e); + } + } +} diff --git a/src/com/android/settings/fuelgauge/PowerUsageSummary.java b/src/com/android/settings/fuelgauge/PowerUsageSummary.java index 5dfbcdae4c0..1747e3ff355 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageSummary.java +++ b/src/com/android/settings/fuelgauge/PowerUsageSummary.java @@ -16,66 +16,37 @@ package com.android.settings.fuelgauge; -import static android.os.BatteryStats.NETWORK_MOBILE_RX_BYTES; -import static android.os.BatteryStats.NETWORK_MOBILE_TX_BYTES; -import static android.os.BatteryStats.NETWORK_WIFI_RX_BYTES; -import static android.os.BatteryStats.NETWORK_WIFI_TX_BYTES; - +import android.app.Activity; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.UserInfo; -import android.graphics.drawable.Drawable; -import android.hardware.Sensor; -import android.hardware.SensorManager; import android.os.BatteryStats; -import android.os.BatteryStats.Uid; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.Parcel; -import android.os.Process; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.SystemClock; -import android.os.UserHandle; -import android.os.UserManager; import android.preference.Preference; import android.preference.PreferenceActivity; import android.preference.PreferenceFragment; import android.preference.PreferenceGroup; import android.preference.PreferenceScreen; -import android.telephony.SignalStrength; import android.text.TextUtils; -import android.util.Log; -import android.util.SparseArray; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; -import com.android.internal.app.IBatteryStats; -import com.android.internal.os.BatteryStatsImpl; import com.android.internal.os.PowerProfile; -import com.android.internal.util.FastPrintWriter; import com.android.settings.HelpUtils; import com.android.settings.R; -import com.android.settings.fuelgauge.PowerUsageDetail.DrainType; -import com.android.settings.users.UserUtils; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.io.Writer; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; -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 PreferenceFragment implements Runnable { +public class PowerUsageSummary extends PreferenceFragment { private static final boolean DEBUG = false; @@ -88,18 +59,6 @@ public class PowerUsageSummary extends PreferenceFragment implements Runnable { private static final int MENU_STATS_REFRESH = Menu.FIRST + 1; private static final int MENU_HELP = Menu.FIRST + 2; - private static BatteryStatsImpl sStatsXfer; - - IBatteryStats mBatteryInfo; - UserManager mUm; - BatteryStatsImpl mStats; - private final List mUsageList = new ArrayList(); - private final List mWifiSippers = new ArrayList(); - private final List mBluetoothSippers = new ArrayList(); - private final SparseArray> mUserSippers - = new SparseArray>(); - private final SparseArray mUserPower = new SparseArray(); - private PreferenceGroup mAppListGroup; private Preference mBatteryStatusPref; @@ -108,20 +67,7 @@ public class PowerUsageSummary extends PreferenceFragment implements Runnable { private static final int MIN_POWER_THRESHOLD = 5; private static final int MAX_ITEMS_TO_LIST = 10; - private long mStatsPeriod = 0; - private double mMaxPower = 1; - private double mTotalPower; - private double mWifiPower; - private double mBluetoothPower; - private PowerProfile mPowerProfile; - - // How much the apps together have left WIFI running. - private long mAppWifiRunning; - - /** Queue for fetching name and icon for an application */ - private ArrayList mRequestQueue = new ArrayList(); - private Thread mRequestThread; - private boolean mAbort; + private BatteryStatsHelper mStatsHelper; private BroadcastReceiver mBatteryInfoReceiver = new BroadcastReceiver() { @@ -135,34 +81,32 @@ public class PowerUsageSummary extends PreferenceFragment implements Runnable { String batterySummary = context.getResources().getString( R.string.power_usage_level_and_status, batteryLevel, batteryStatus); mBatteryStatusPref.setTitle(batterySummary); - mStats = null; + mStatsHelper.clearStats(); refreshStats(); } } }; + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + mStatsHelper = new BatteryStatsHelper(activity, mHandler); + } + @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); - - if (icicle != null) { - mStats = sStatsXfer; - } + mStatsHelper.create(icicle); addPreferencesFromResource(R.xml.power_usage_summary); - mBatteryInfo = IBatteryStats.Stub.asInterface( - ServiceManager.getService(BatteryStats.SERVICE_NAME)); - mUm = (UserManager)getActivity().getSystemService(Context.USER_SERVICE); mAppListGroup = (PreferenceGroup) findPreference(KEY_APP_LIST); mBatteryStatusPref = mAppListGroup.findPreference(KEY_BATTERY_STATUS); - mPowerProfile = new PowerProfile(getActivity()); setHasOptionsMenu(true); } @Override public void onResume() { super.onResume(); - mAbort = false; getActivity().registerReceiver(mBatteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); refreshStats(); @@ -170,10 +114,8 @@ public class PowerUsageSummary extends PreferenceFragment implements Runnable { @Override public void onPause() { - synchronized (mRequestQueue) { - mAbort = true; - } - mHandler.removeMessages(MSG_UPDATE_NAME_ICON); + mStatsHelper.pause(); + mHandler.removeMessages(BatteryStatsHelper.MSG_UPDATE_NAME_ICON); getActivity().unregisterReceiver(mBatteryInfoReceiver); super.onPause(); } @@ -181,18 +123,14 @@ public class PowerUsageSummary extends PreferenceFragment implements Runnable { @Override public void onDestroy() { super.onDestroy(); - if (getActivity().isChangingConfigurations()) { - sStatsXfer = mStats; - } else { - BatterySipper.sUidCache.clear(); - } + mStatsHelper.destroy(); } @Override public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { if (preference instanceof BatteryHistoryPreference) { Parcel hist = Parcel.obtain(); - mStats.writeToParcelWithoutUids(hist, 0); + mStatsHelper.getStats().writeToParcelWithoutUids(hist, 0); byte[] histData = hist.marshall(); Bundle args = new Bundle(); args.putByteArray(BatteryHistoryDetail.EXTRA_STATS, histData); @@ -206,145 +144,7 @@ public class PowerUsageSummary extends PreferenceFragment implements Runnable { } PowerGaugePreference pgp = (PowerGaugePreference) preference; BatterySipper sipper = pgp.getInfo(); - Bundle args = new Bundle(); - args.putString(PowerUsageDetail.EXTRA_TITLE, sipper.name); - args.putInt(PowerUsageDetail.EXTRA_PERCENT, (int) - Math.ceil(sipper.getSortValue() * 100 / mTotalPower)); - args.putInt(PowerUsageDetail.EXTRA_GAUGE, (int) - Math.ceil(sipper.getSortValue() * 100 / mMaxPower)); - args.putLong(PowerUsageDetail.EXTRA_USAGE_DURATION, mStatsPeriod); - args.putString(PowerUsageDetail.EXTRA_ICON_PACKAGE, sipper.defaultPackageName); - args.putInt(PowerUsageDetail.EXTRA_ICON_ID, sipper.iconId); - args.putDouble(PowerUsageDetail.EXTRA_NO_COVERAGE, sipper.noCoveragePercent); - if (sipper.uidObj != null) { - args.putInt(PowerUsageDetail.EXTRA_UID, sipper.uidObj.getUid()); - } - args.putSerializable(PowerUsageDetail.EXTRA_DRAIN_TYPE, sipper.drainType); - - int[] types; - double[] values; - switch (sipper.drainType) { - case APP: - case USER: - { - Uid uid = sipper.uidObj; - types = new int[] { - R.string.usage_type_cpu, - R.string.usage_type_cpu_foreground, - R.string.usage_type_wake_lock, - R.string.usage_type_gps, - R.string.usage_type_wifi_running, - R.string.usage_type_data_recv, - R.string.usage_type_data_send, - R.string.usage_type_data_wifi_recv, - R.string.usage_type_data_wifi_send, - R.string.usage_type_audio, - R.string.usage_type_video, - }; - values = new double[] { - sipper.cpuTime, - sipper.cpuFgTime, - sipper.wakeLockTime, - sipper.gpsTime, - sipper.wifiRunningTime, - sipper.mobileRxBytes, - sipper.mobileTxBytes, - sipper.wifiRxBytes, - sipper.wifiTxBytes, - 0, - 0 - }; - - if (sipper.drainType == DrainType.APP) { - Writer result = new StringWriter(); - PrintWriter printWriter = new FastPrintWriter(result, false, 1024); - mStats.dumpLocked(printWriter, "", mStatsType, uid.getUid()); - printWriter.flush(); - args.putString(PowerUsageDetail.EXTRA_REPORT_DETAILS, result.toString()); - - result = new StringWriter(); - printWriter = new FastPrintWriter(result, false, 1024); - mStats.dumpCheckinLocked(printWriter, mStatsType, uid.getUid()); - printWriter.flush(); - args.putString(PowerUsageDetail.EXTRA_REPORT_CHECKIN_DETAILS, - result.toString()); - } - } - break; - case CELL: - { - types = new int[] { - R.string.usage_type_on_time, - R.string.usage_type_no_coverage - }; - values = new double[] { - sipper.usageTime, - sipper.noCoveragePercent - }; - } - break; - case WIFI: - { - types = new int[] { - R.string.usage_type_wifi_running, - R.string.usage_type_cpu, - R.string.usage_type_cpu_foreground, - R.string.usage_type_wake_lock, - R.string.usage_type_data_recv, - R.string.usage_type_data_send, - R.string.usage_type_data_wifi_recv, - R.string.usage_type_data_wifi_send, - }; - values = new double[] { - sipper.usageTime, - sipper.cpuTime, - sipper.cpuFgTime, - sipper.wakeLockTime, - sipper.mobileRxBytes, - sipper.mobileTxBytes, - sipper.wifiRxBytes, - sipper.wifiTxBytes, - }; - } break; - case BLUETOOTH: - { - types = new int[] { - R.string.usage_type_on_time, - R.string.usage_type_cpu, - R.string.usage_type_cpu_foreground, - R.string.usage_type_wake_lock, - R.string.usage_type_data_recv, - R.string.usage_type_data_send, - R.string.usage_type_data_wifi_recv, - R.string.usage_type_data_wifi_send, - }; - values = new double[] { - sipper.usageTime, - sipper.cpuTime, - sipper.cpuFgTime, - sipper.wakeLockTime, - sipper.mobileRxBytes, - sipper.mobileTxBytes, - sipper.wifiRxBytes, - sipper.wifiTxBytes, - }; - } break; - default: - { - types = new int[] { - R.string.usage_type_on_time - }; - values = new double[] { - sipper.usageTime - }; - } - } - args.putIntArray(PowerUsageDetail.EXTRA_DETAIL_TYPES, types); - args.putDoubleArray(PowerUsageDetail.EXTRA_DETAIL_VALUES, values); - PreferenceActivity pa = (PreferenceActivity)getActivity(); - pa.startPreferencePanel(PowerUsageDetail.class.getName(), args, - R.string.details_title, null, null, 0); - + mStatsHelper.startBatteryDetailPage((PreferenceActivity) getActivity(), sipper); return super.onPreferenceTreeClick(preferenceScreen, preference); } @@ -380,7 +180,7 @@ public class PowerUsageSummary extends PreferenceFragment implements Runnable { refreshStats(); return true; case MENU_STATS_REFRESH: - mStats = null; + mStatsHelper.clearStats(); refreshStats(); return true; default: @@ -395,43 +195,32 @@ public class PowerUsageSummary extends PreferenceFragment implements Runnable { } private void refreshStats() { - if (mStats == null) { - load(); - } - mMaxPower = 0; - mTotalPower = 0; - mWifiPower = 0; - mBluetoothPower = 0; - mAppWifiRunning = 0; - mAppListGroup.removeAll(); - mUsageList.clear(); - mWifiSippers.clear(); - mBluetoothSippers.clear(); - mUserSippers.clear(); - mUserPower.clear(); mAppListGroup.setOrderingAsAdded(false); mBatteryStatusPref.setOrder(-2); mAppListGroup.addPreference(mBatteryStatusPref); - BatteryHistoryPreference hist = new BatteryHistoryPreference(getActivity(), mStats); + BatteryHistoryPreference hist = new BatteryHistoryPreference( + getActivity(), mStatsHelper.getStats()); hist.setOrder(-1); mAppListGroup.addPreference(hist); - - if (mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_FULL) < 10) { + + if (mStatsHelper.getPowerProfile().getAveragePower( + PowerProfile.POWER_SCREEN_FULL) < 10) { addNotAvailableMessage(); return; } - processAppUsage(); - processMiscUsage(); - - Collections.sort(mUsageList); - for (BatterySipper sipper : mUsageList) { + mStatsHelper.refreshStats(); + List usageList = mStatsHelper.getUsageList(); + for (BatterySipper sipper : usageList) { if (sipper.getSortValue() < MIN_POWER_THRESHOLD) continue; - final double percentOfTotal = ((sipper.getSortValue() / mTotalPower) * 100); + final double percentOfTotal = + ((sipper.getSortValue() / mStatsHelper.getTotalPower()) * 100); if (percentOfTotal < 1) continue; - PowerGaugePreference pref = new PowerGaugePreference(getActivity(), sipper.getIcon(), sipper); - final double percentOfMax = (sipper.getSortValue() * 100) / mMaxPower; + PowerGaugePreference pref = + new PowerGaugePreference(getActivity(), sipper.getIcon(), sipper); + final double percentOfMax = + (sipper.getSortValue() * 100) / mStatsHelper.getMaxPower(); sipper.percent = percentOfTotal; pref.setTitle(sipper.name); pref.setOrder(Integer.MAX_VALUE - (int) sipper.getSortValue()); // Invert the order @@ -442,494 +231,16 @@ public class PowerUsageSummary extends PreferenceFragment implements Runnable { mAppListGroup.addPreference(pref); if (mAppListGroup.getPreferenceCount() > (MAX_ITEMS_TO_LIST+1)) break; } - 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)getActivity().getSystemService( - Context.SENSOR_SERVICE); - final int which = mStatsType; - final int speedSteps = mPowerProfile.getNumSpeedSteps(); - final double[] powerCpuNormal = new double[speedSteps]; - final long[] cpuSpeedStepTimes = new long[speedSteps]; - for (int p = 0; p < speedSteps; p++) { - powerCpuNormal[p] = mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_ACTIVE, p); - } - final double mobilePowerPerByte = getMobilePowerPerByte(); - final double wifiPowerPerByte = getWifiPowerPerByte(); - long uSecTime = mStats.computeBatteryRealtime(SystemClock.elapsedRealtime() * 1000, which); - long appWakelockTime = 0; - BatterySipper osApp = null; - mStatsPeriod = uSecTime; - SparseArray uidStats = mStats.getUidStats(); - final int NU = uidStats.size(); - for (int iu = 0; iu < NU; iu++) { - Uid u = uidStats.valueAt(iu); - double p; // in mAs - double power = 0; // in mAs - double highestDrain = 0; - String packageWithHighestDrain = null; - //mUsageList.add(new AppUsage(u.getUid(), new double[] {power})); - Map processStats = u.getProcessStats(); - long cpuTime = 0; - long cpuFgTime = 0; - long wakelockTime = 0; - long gpsTime = 0; - if (DEBUG) Log.i(TAG, "UID " + u.getUid()); - if (processStats.size() > 0) { - // Process CPU time - for (Map.Entry ent - : processStats.entrySet()) { - Uid.Proc ps = ent.getValue(); - final long userTime = ps.getUserTime(which); - final long systemTime = ps.getSystemTime(which); - final long foregroundTime = ps.getForegroundTime(which); - cpuFgTime += foregroundTime * 10; // convert to millis - final long tmpCpuTime = (userTime + systemTime) * 10; // convert to millis - int totalTimeAtSpeeds = 0; - // Get the total first - for (int step = 0; step < speedSteps; step++) { - cpuSpeedStepTimes[step] = ps.getTimeAtCpuSpeedStep(step, which); - totalTimeAtSpeeds += cpuSpeedStepTimes[step]; - } - if (totalTimeAtSpeeds == 0) totalTimeAtSpeeds = 1; - // Then compute the ratio of time spent at each speed - double processPower = 0; - for (int step = 0; step < speedSteps; step++) { - double ratio = (double) cpuSpeedStepTimes[step] / totalTimeAtSpeeds; - processPower += ratio * tmpCpuTime * powerCpuNormal[step]; - } - cpuTime += tmpCpuTime; - if (DEBUG && processPower != 0) { - Log.i(TAG, String.format("process %s, cpu power=%.2f", - ent.getKey(), processPower / 1000)); - } - power += processPower; - if (packageWithHighestDrain == null - || packageWithHighestDrain.startsWith("*")) { - highestDrain = processPower; - packageWithHighestDrain = ent.getKey(); - } else if (highestDrain < processPower - && !ent.getKey().startsWith("*")) { - highestDrain = processPower; - packageWithHighestDrain = ent.getKey(); - } - } - } - 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; - if (DEBUG && power != 0) Log.i(TAG, String.format("total cpu power=%.2f", power)); - - // Process wake lock usage - Map wakelockStats = u.getWakelockStats(); - for (Map.Entry wakelockEntry - : wakelockStats.entrySet()) { - Uid.Wakelock wakelock = wakelockEntry.getValue(); - // Only care about partial wake locks since full wake locks - // are canceled when the user turns the screen off. - BatteryStats.Timer timer = wakelock.getWakeTime(BatteryStats.WAKE_TYPE_PARTIAL); - if (timer != null) { - wakelockTime += timer.getTotalTimeLocked(uSecTime, which); - } - } - wakelockTime /= 1000; // convert to millis - appWakelockTime += wakelockTime; - - // Add cost of holding a wake lock - p = (wakelockTime - * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE)) / 1000; - power += p; - if (DEBUG && p != 0) Log.i(TAG, String.format("wakelock power=%.2f", p)); - - // Add cost of mobile traffic - final long mobileRx = u.getNetworkActivityCount(NETWORK_MOBILE_RX_BYTES, mStatsType); - final long mobileTx = u.getNetworkActivityCount(NETWORK_MOBILE_TX_BYTES, mStatsType); - p = (mobileRx + mobileTx) * mobilePowerPerByte; - power += p; - if (DEBUG && p != 0) Log.i(TAG, String.format("mobile power=%.2f", p)); - - // Add cost of wifi traffic - final long wifiRx = u.getNetworkActivityCount(NETWORK_WIFI_RX_BYTES, mStatsType); - final long wifiTx = u.getNetworkActivityCount(NETWORK_WIFI_TX_BYTES, mStatsType); - p = (wifiRx + wifiTx) * wifiPowerPerByte; - power += p; - if (DEBUG && p != 0) Log.i(TAG, String.format("wifi power=%.2f", p)); - - // Add cost of keeping WIFI running. - long wifiRunningTimeMs = u.getWifiRunningTime(uSecTime, which) / 1000; - mAppWifiRunning += wifiRunningTimeMs; - p = (wifiRunningTimeMs - * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)) / 1000; - power += p; - if (DEBUG && p != 0) Log.i(TAG, String.format("wifi running power=%.2f", p)); - - // Add cost of WIFI scans - long wifiScanTimeMs = u.getWifiScanTime(uSecTime, which) / 1000; - p = (wifiScanTimeMs - * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_SCAN)) / 1000; - power += p; - if (DEBUG && p != 0) Log.i(TAG, String.format("wifi scanning power=%.2f", p)); - - // Process Sensor usage - Map sensorStats = u.getSensorStats(); - for (Map.Entry sensorEntry - : sensorStats.entrySet()) { - Uid.Sensor sensor = sensorEntry.getValue(); - int sensorHandle = sensor.getHandle(); - BatteryStats.Timer timer = sensor.getSensorTime(); - long sensorTime = timer.getTotalTimeLocked(uSecTime, which) / 1000; - double multiplier = 0; - switch (sensorHandle) { - case Uid.Sensor.GPS: - multiplier = mPowerProfile.getAveragePower(PowerProfile.POWER_GPS_ON); - gpsTime = sensorTime; - break; - default: - List sensorList = sensorManager.getSensorList( - android.hardware.Sensor.TYPE_ALL); - for (android.hardware.Sensor s : sensorList) { - if (s.getHandle() == sensorHandle) { - multiplier = s.getPower(); - break; - } - } - } - p = (multiplier * sensorTime) / 1000; - power += p; - if (DEBUG && p != 0) { - Log.i(TAG, String.format("sensor %s power=%.2f", sensor.toString(), p)); - } - } - - if (DEBUG) Log.i(TAG, String.format("UID %d total power=%.2f", u.getUid(), power)); - - // Add the app to the list if it is consuming power - boolean isOtherUser = false; - final int userId = UserHandle.getUserId(u.getUid()); - if (power != 0 || u.getUid() == 0) { - BatterySipper app = new BatterySipper(getActivity(), mRequestQueue, mHandler, - packageWithHighestDrain, DrainType.APP, 0, u, - new double[] {power}); - app.cpuTime = cpuTime; - app.gpsTime = gpsTime; - app.wifiRunningTime = wifiRunningTimeMs; - app.cpuFgTime = cpuFgTime; - app.wakeLockTime = wakelockTime; - app.mobileRxBytes = mobileRx; - app.mobileTxBytes = mobileTx; - app.wifiRxBytes = wifiRx; - app.wifiTxBytes = wifiTx; - if (u.getUid() == Process.WIFI_UID) { - mWifiSippers.add(app); - } else if (u.getUid() == Process.BLUETOOTH_UID) { - mBluetoothSippers.add(app); - } else if (userId != UserHandle.myUserId() - && UserHandle.getAppId(u.getUid()) >= Process.FIRST_APPLICATION_UID) { - isOtherUser = true; - List list = mUserSippers.get(userId); - if (list == null) { - list = new ArrayList(); - mUserSippers.put(userId, list); - } - list.add(app); - } else { - mUsageList.add(app); - } - if (u.getUid() == 0) { - osApp = app; - } - } - if (power != 0) { - if (u.getUid() == Process.WIFI_UID) { - mWifiPower += power; - } else if (u.getUid() == Process.BLUETOOTH_UID) { - mBluetoothPower += power; - } else if (isOtherUser) { - Double userPower = mUserPower.get(userId); - if (userPower == null) { - userPower = power; - } else { - userPower += power; - } - mUserPower.put(userId, userPower); - } else { - if (power > mMaxPower) mMaxPower = power; - mTotalPower += power; - } - } - } - - // The device has probably been awake for longer than the screen on - // time and application wake lock time would account for. Assign - // this remainder to the OS, if possible. - if (osApp != null) { - long wakeTimeMillis = mStats.computeBatteryUptime( - SystemClock.uptimeMillis() * 1000, which) / 1000; - wakeTimeMillis -= appWakelockTime + (mStats.getScreenOnTime( - SystemClock.elapsedRealtime(), which) / 1000); - if (wakeTimeMillis > 0) { - double power = (wakeTimeMillis - * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE)) / 1000; - if (DEBUG) Log.i(TAG, "OS wakeLockTime " + wakeTimeMillis + " power " + power); - osApp.wakeLockTime += wakeTimeMillis; - osApp.value += power; - osApp.values[0] += power; - if (osApp.value > mMaxPower) mMaxPower = osApp.value; - mTotalPower += power; - } - } - } - - private void addPhoneUsage(long uSecNow) { - long phoneOnTimeMs = mStats.getPhoneOnTime(uSecNow, mStatsType) / 1000; - double phoneOnPower = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE) - * phoneOnTimeMs / 1000; - addEntry(getActivity().getString(R.string.power_phone), DrainType.PHONE, phoneOnTimeMs, - R.drawable.ic_settings_voice_calls, phoneOnPower); - } - - private void addScreenUsage(long uSecNow) { - double power = 0; - 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++) { - double screenBinPower = screenFullPower * (i + 0.5f) - / BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; - long brightnessTime = mStats.getScreenBrightnessTime(i, uSecNow, mStatsType) / 1000; - power += screenBinPower * brightnessTime; - if (DEBUG) { - Log.i(TAG, "Screen bin power = " + (int) screenBinPower + ", time = " - + brightnessTime); - } - } - power /= 1000; // To seconds - addEntry(getActivity().getString(R.string.power_screen), DrainType.SCREEN, screenOnTimeMs, - R.drawable.ic_settings_display, power); - } - - private void addRadioUsage(long uSecNow) { - double power = 0; - final int BINS = SignalStrength.NUM_SIGNAL_STRENGTH_BINS; - long signalTimeMs = 0; - for (int i = 0; i < BINS; i++) { - long strengthTimeMs = mStats.getPhoneSignalStrengthTime(i, uSecNow, mStatsType) / 1000; - power += strengthTimeMs / 1000 - * mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ON, i); - signalTimeMs += strengthTimeMs; - } - long scanningTimeMs = mStats.getPhoneSignalScanningTime(uSecNow, mStatsType) / 1000; - power += scanningTimeMs / 1000 * mPowerProfile.getAveragePower( - PowerProfile.POWER_RADIO_SCANNING); - BatterySipper bs = - addEntry(getActivity().getString(R.string.power_cell), DrainType.CELL, - signalTimeMs, R.drawable.ic_settings_cell_standby, power); - if (signalTimeMs != 0) { - bs.noCoveragePercent = mStats.getPhoneSignalStrengthTime(0, uSecNow, mStatsType) - / 1000 * 100.0 / signalTimeMs; - } - } - - private void aggregateSippers(BatterySipper bs, List from, String tag) { - for (int i=0; i sippers = mUserSippers.valueAt(i); - UserInfo info = mUm.getUserInfo(userId); - Drawable icon; - String name; - if (info != null) { - icon = UserUtils.getUserIcon(context, mUm, info, getResources()); - name = info != null ? info.name : null; - if (name == null) { - name = Integer.toString(info.id); - } - name = context.getResources().getString( - R.string.running_process_item_user_label, name); - } else { - icon = null; - name = context.getResources().getString( - R.string.running_process_item_removed_user_label); - } - double power = mUserPower.get(userId); - BatterySipper bs = addEntry(name, DrainType.USER, 0, 0, power); - bs.icon = icon; - aggregateSippers(bs, sippers, "User"); - } - } - - /** - * Return estimated power (in mAs) of sending a byte with the mobile radio. - */ - private double getMobilePowerPerByte() { - final long MOBILE_BPS = 200000; // TODO: Extract average bit rates from system - final double MOBILE_POWER = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE) - / 3600; - - final long mobileRx = mStats.getNetworkActivityCount(NETWORK_MOBILE_RX_BYTES, mStatsType); - final long mobileTx = mStats.getNetworkActivityCount(NETWORK_MOBILE_TX_BYTES, mStatsType); - final long mobileData = mobileRx + mobileTx; - - final long radioDataUptimeMs = mStats.getRadioDataUptime() / 1000; - final long mobileBps = radioDataUptimeMs != 0 - ? mobileData * 8 * 1000 / radioDataUptimeMs - : MOBILE_BPS; - - return MOBILE_POWER / (mobileBps / 8); - } - - /** - * Return estimated power (in mAs) of sending a byte with the Wi-Fi radio. - */ - private double getWifiPowerPerByte() { - final long WIFI_BPS = 1000000; // TODO: Extract average bit rates from system - final double WIFI_POWER = mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ACTIVE) - / 3600; - return WIFI_POWER / (WIFI_BPS / 8); - } - - private void processMiscUsage() { - final int which = mStatsType; - long uSecTime = SystemClock.elapsedRealtime() * 1000; - final long uSecNow = mStats.computeBatteryRealtime(uSecTime, which); - final long timeSinceUnplugged = uSecNow; - if (DEBUG) { - Log.i(TAG, "Uptime since last unplugged = " + (timeSinceUnplugged / 1000)); - } - - addUserUsage(); - addPhoneUsage(uSecNow); - addScreenUsage(uSecNow); - addWiFiUsage(uSecNow); - addBluetoothUsage(uSecNow); - addIdleUsage(uSecNow); // Not including cellular idle power - // Don't compute radio usage if it's a wifi-only device - if (!com.android.settings.Utils.isWifiOnly(getActivity())) { - addRadioUsage(uSecNow); - } - } - - private BatterySipper addEntry(String label, DrainType drainType, long time, int iconId, - double power) { - if (power > mMaxPower) mMaxPower = power; - mTotalPower += power; - BatterySipper bs = new BatterySipper(getActivity(), mRequestQueue, mHandler, - label, drainType, iconId, null, new double[] {power}); - bs.usageTime = time; - bs.iconId = iconId; - mUsageList.add(bs); - return bs; - } - - private void load() { - try { - byte[] data = mBatteryInfo.getStatistics(); - Parcel parcel = Parcel.obtain(); - parcel.unmarshall(data, 0, data.length); - parcel.setDataPosition(0); - mStats = com.android.internal.os.BatteryStatsImpl.CREATOR - .createFromParcel(parcel); - mStats.distributeWorkLocked(BatteryStats.STATS_SINCE_CHARGED); - } catch (RemoteException e) { - Log.e(TAG, "RemoteException:", e); - } - } - - public void run() { - while (true) { - BatterySipper bs; - synchronized (mRequestQueue) { - if (mRequestQueue.isEmpty() || mAbort) { - mHandler.sendEmptyMessage(MSG_REPORT_FULLY_DRAWN); - mRequestThread = null; - return; - } - bs = mRequestQueue.remove(0); - } - bs.getNameIcon(); - } - } - - static final int MSG_UPDATE_NAME_ICON = 1; - static final int MSG_REPORT_FULLY_DRAWN = 2; - Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { - case MSG_UPDATE_NAME_ICON: + case BatteryStatsHelper.MSG_UPDATE_NAME_ICON: BatterySipper bs = (BatterySipper) msg.obj; - PowerGaugePreference pgp = + PowerGaugePreference pgp = (PowerGaugePreference) findPreference( Integer.toString(bs.uidObj.getUid())); if (pgp != null) { @@ -937,7 +248,7 @@ public class PowerUsageSummary extends PreferenceFragment implements Runnable { pgp.setTitle(bs.name); } break; - case MSG_REPORT_FULLY_DRAWN: + case BatteryStatsHelper.MSG_REPORT_FULLY_DRAWN: getActivity().reportFullyDrawn(); break; } diff --git a/src/com/android/settings/location/LocationMode.java b/src/com/android/settings/location/LocationMode.java index 4cb3a5ec309..70011261b12 100644 --- a/src/com/android/settings/location/LocationMode.java +++ b/src/com/android/settings/location/LocationMode.java @@ -53,17 +53,11 @@ public class LocationMode extends LocationSettingsBase public void onResume() { super.onResume(); createPreferenceHierarchy(); - mHighAccuracy.resume(); - mBatterySaving.resume(); - mSensorsOnly.resume(); } @Override public void onPause() { super.onPause(); - mHighAccuracy.pause(); - mBatterySaving.pause(); - mSensorsOnly.pause(); } private PreferenceScreen createPreferenceHierarchy() { diff --git a/src/com/android/settings/location/LocationSettings.java b/src/com/android/settings/location/LocationSettings.java index 2cbb7989989..e349ad4f266 100644 --- a/src/com/android/settings/location/LocationSettings.java +++ b/src/com/android/settings/location/LocationSettings.java @@ -19,23 +19,23 @@ package com.android.settings.location; import android.app.ActionBar; import android.app.Activity; import android.content.Intent; +import android.os.Bundle; import android.preference.Preference; import android.preference.PreferenceActivity; import android.preference.PreferenceCategory; import android.preference.PreferenceScreen; -import android.util.Log; import android.view.Gravity; import android.widget.CompoundButton; import android.widget.Switch; import com.android.settings.R; +import com.android.settings.fuelgauge.BatteryStatsHelper; /** * Location access settings. */ public class LocationSettings extends LocationSettingsBase implements CompoundButton.OnCheckedChangeListener { - private static final String TAG = LocationSettings.class.getSimpleName(); /** Key for preference screen "Mode" */ private static final String KEY_LOCATION_MODE = "location_mode"; /** Key for preference category "Recent location requests" */ @@ -49,6 +49,8 @@ public class LocationSettings extends LocationSettingsBase private PreferenceCategory mRecentLocationRequests; private PreferenceCategory mLocationServices; + private BatteryStatsHelper mStatsHelper; + public LocationSettings() { mValidListener = false; } @@ -59,6 +61,18 @@ public class LocationSettings extends LocationSettingsBase createPreferenceHierarchy(); } + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + mStatsHelper = new BatteryStatsHelper(activity, null); + } + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + mStatsHelper.create(icicle); + } + @Override public void onResume() { super.onResume(); @@ -73,6 +87,13 @@ public class LocationSettings extends LocationSettingsBase super.onPause(); mValidListener = false; mSwitch.setOnCheckedChangeListener(null); + mStatsHelper.pause(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + mStatsHelper.destroy(); } private PreferenceScreen createPreferenceHierarchy() { @@ -88,9 +109,8 @@ public class LocationSettings extends LocationSettingsBase new Preference.OnPreferenceClickListener() { @Override public boolean onPreferenceClick(Preference preference) { - PreferenceActivity preferenceActivity = - (PreferenceActivity) getActivity(); - preferenceActivity.startPreferencePanel( + PreferenceActivity activity = (PreferenceActivity) getActivity(); + activity.startPreferencePanel( LocationMode.class.getName(), null, R.string.location_mode_screen_title, null, LocationSettings.this, 0); @@ -101,30 +121,24 @@ public class LocationSettings extends LocationSettingsBase (PreferenceCategory) root.findPreference(KEY_RECENT_LOCATION_REQUESTS); mLocationServices = (PreferenceCategory) root.findPreference(KEY_LOCATION_SERVICES); - Activity activity = getActivity(); - - RecentLocationApps recentApps = new RecentLocationApps(activity); + PreferenceActivity activity = (PreferenceActivity) getActivity(); + RecentLocationApps recentApps = new RecentLocationApps(activity, mStatsHelper); recentApps.fillAppList(mRecentLocationRequests); SettingsInjector.addInjectedSettings(mLocationServices, activity, getPreferenceManager()); - if (activity instanceof PreferenceActivity) { - PreferenceActivity preferenceActivity = (PreferenceActivity) activity; - // Only show the master switch when we're not in multi-pane mode, and not being used as - // Setup Wizard. - if (preferenceActivity.onIsHidingHeaders() || !preferenceActivity.onIsMultiPane()) { - final int padding = activity.getResources().getDimensionPixelSize( - R.dimen.action_bar_switch_padding); - mSwitch.setPaddingRelative(0, 0, padding, 0); - activity.getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM, - ActionBar.DISPLAY_SHOW_CUSTOM); - activity.getActionBar().setCustomView(mSwitch, new ActionBar.LayoutParams( - ActionBar.LayoutParams.WRAP_CONTENT, - ActionBar.LayoutParams.WRAP_CONTENT, - Gravity.CENTER_VERTICAL | Gravity.END)); - } - } else { - Log.wtf(TAG, "Current activity is not an instance of PreferenceActivity!"); + // Only show the master switch when we're not in multi-pane mode, and not being used as + // Setup Wizard. + if (activity.onIsHidingHeaders() || !activity.onIsMultiPane()) { + final int padding = activity.getResources().getDimensionPixelSize( + R.dimen.action_bar_switch_padding); + mSwitch.setPaddingRelative(0, 0, padding, 0); + activity.getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM, + ActionBar.DISPLAY_SHOW_CUSTOM); + activity.getActionBar().setCustomView(mSwitch, new ActionBar.LayoutParams( + ActionBar.LayoutParams.WRAP_CONTENT, + ActionBar.LayoutParams.WRAP_CONTENT, + Gravity.CENTER_VERTICAL | Gravity.END)); } setHasOptionsMenu(true); diff --git a/src/com/android/settings/location/RadioButtonPreference.java b/src/com/android/settings/location/RadioButtonPreference.java index f87bd2205a4..5589dfa57d3 100644 --- a/src/com/android/settings/location/RadioButtonPreference.java +++ b/src/com/android/settings/location/RadioButtonPreference.java @@ -19,9 +19,7 @@ package com.android.settings.location; import android.content.Context; import android.preference.CheckBoxPreference; import android.util.AttributeSet; -import android.view.LayoutInflater; import android.view.View; -import android.view.ViewGroup; import android.widget.TextView; import com.android.settings.R; @@ -37,8 +35,6 @@ import com.android.settings.R; * uncheck all the other preferences, you should do that by code yourself. */ public class RadioButtonPreference extends CheckBoxPreference { - private boolean mValidListener; - public interface OnClickListener { public abstract void onRadioButtonClicked(RadioButtonPreference emiter); } @@ -48,7 +44,6 @@ public class RadioButtonPreference extends CheckBoxPreference { public RadioButtonPreference(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); setWidgetLayoutResource(R.layout.preference_widget_radiobutton); - mValidListener = false; } public RadioButtonPreference(Context context, AttributeSet attrs) { @@ -63,14 +58,6 @@ public class RadioButtonPreference extends CheckBoxPreference { mListener = listener; } - public void pause() { - mValidListener = false; - } - - public void resume() { - mValidListener = true; - } - @Override public void onClick() { if (mListener != null) { diff --git a/src/com/android/settings/location/RecentLocationApps.java b/src/com/android/settings/location/RecentLocationApps.java index 113928f948c..fe206b5f617 100644 --- a/src/com/android/settings/location/RecentLocationApps.java +++ b/src/com/android/settings/location/RecentLocationApps.java @@ -20,14 +20,22 @@ import android.app.AppOpsManager; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.PreferenceActivity; import android.preference.PreferenceCategory; import android.preference.PreferenceManager; import android.preference.PreferenceScreen; import android.util.Log; import com.android.settings.R; +import com.android.settings.applications.InstalledAppDetails; +import com.android.settings.fuelgauge.BatterySipper; +import com.android.settings.fuelgauge.BatteryStatsHelper; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * Retrieves the information of applications which accessed location recently. @@ -37,12 +45,52 @@ public class RecentLocationApps { private static final int RECENT_TIME_INTERVAL_MILLIS = 15 * 60 * 1000; - private Context mContext; - PackageManager mPackageManager; + private final PreferenceActivity mActivity; + private final BatteryStatsHelper mStatsHelper; + private final PackageManager mPackageManager; - public RecentLocationApps(Context context) { - mContext = context; - mPackageManager = context.getPackageManager(); + // Stores all the packages that requested location within the designated interval + // key - package name of the app + // value - whether the app has requested high power location + + public RecentLocationApps(PreferenceActivity activity, BatteryStatsHelper sipperUtil) { + mActivity = activity; + mPackageManager = activity.getPackageManager(); + mStatsHelper = sipperUtil; + } + + private class UidEntryClickedListener + implements Preference.OnPreferenceClickListener { + private BatterySipper mSipper; + + public UidEntryClickedListener(BatterySipper sipper) { + mSipper = sipper; + } + + @Override + public boolean onPreferenceClick(Preference preference) { + mStatsHelper.startBatteryDetailPage(mActivity, mSipper); + return true; + } + } + + private class PackageEntryClickedListener + implements Preference.OnPreferenceClickListener { + private String mPackage; + + public PackageEntryClickedListener(String packageName) { + mPackage = packageName; + } + + @Override + public boolean onPreferenceClick(Preference preference) { + // start new fragment to display extended information + Bundle args = new Bundle(); + args.putString(InstalledAppDetails.ARG_PACKAGE_NAME, mPackage); + mActivity.startPreferencePanel(InstalledAppDetails.class.getName(), args, + R.string.application_info_label, null, null, 0); + return true; + } } /** @@ -50,17 +98,68 @@ public class RecentLocationApps { * specified time. */ public void fillAppList(PreferenceCategory container) { + HashMap packageMap = new HashMap(); AppOpsManager aoManager = - (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); + (AppOpsManager) mActivity.getSystemService(Context.APP_OPS_SERVICE); List appOps = aoManager.getPackagesForOps( new int[] { AppOpsManager.OP_MONITOR_LOCATION, - AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION, + AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION, }); PreferenceManager preferenceManager = container.getPreferenceManager(); long now = System.currentTimeMillis(); for (AppOpsManager.PackageOps ops : appOps) { - processPackageOps(now, container, preferenceManager, ops); + processPackageOps(now, container, preferenceManager, ops, packageMap); + } + + mStatsHelper.refreshStats(); + List usageList = mStatsHelper.getUsageList(); + for (BatterySipper sipper : usageList) { + sipper.loadNameAndIcon(); + String[] packages = sipper.getPackages(); + if (packages == null) { + continue; + } + for (String curPackage : packages) { + if (packageMap.containsKey(curPackage)) { + PreferenceScreen screen = preferenceManager.createPreferenceScreen(mActivity); + screen.setIcon(sipper.getIcon()); + screen.setTitle(sipper.getLabel()); + if (packageMap.get(curPackage)) { + screen.setSummary(R.string.location_high_battery_use); + } else { + screen.setSummary(R.string.location_low_battery_use); + } + container.addPreference(screen); + screen.setOnPreferenceClickListener(new UidEntryClickedListener(sipper)); + packageMap.remove(curPackage); + break; + } + } + } + + // Typically there shouldn't be any entry left in the HashMap. But if there are any, add + // them to the list and link them to the app info page. + for (Map.Entry entry : packageMap.entrySet()) { + try { + PreferenceScreen screen = preferenceManager.createPreferenceScreen(mActivity); + ApplicationInfo appInfo = mPackageManager.getApplicationInfo( + entry.getKey(), PackageManager.GET_META_DATA); + screen.setIcon(mPackageManager.getApplicationIcon(appInfo)); + screen.setTitle(mPackageManager.getApplicationLabel(appInfo)); + // if used both high and low battery within the time interval, show as "high + // battery" + if (entry.getValue()) { + screen.setSummary(R.string.location_high_battery_use); + } else { + screen.setSummary(R.string.location_low_battery_use); + } + screen.setOnPreferenceClickListener( + new PackageEntryClickedListener(entry.getKey())); + container.addPreference(screen); + } catch (PackageManager.NameNotFoundException e) { + // ignore the current app and move on to the next. + } } } @@ -68,7 +167,8 @@ public class RecentLocationApps { long now, PreferenceCategory container, PreferenceManager preferenceManager, - AppOpsManager.PackageOps ops) { + AppOpsManager.PackageOps ops, + HashMap packageMap) { String packageName = ops.getPackageName(); List entries = ops.getOps(); boolean highBattery = false; @@ -96,22 +196,6 @@ public class RecentLocationApps { return; } - try { - PreferenceScreen screen = preferenceManager.createPreferenceScreen(mContext); - ApplicationInfo appInfo = mPackageManager.getApplicationInfo( - packageName, PackageManager.GET_META_DATA); - screen.setIcon(mPackageManager.getApplicationIcon(appInfo)); - screen.setTitle(mPackageManager.getApplicationLabel(appInfo)); - // if used both high and low battery within the time interval, show as "high - // battery" - if (highBattery) { - screen.setSummary(R.string.location_high_battery_use); - } else { - screen.setSummary(R.string.location_low_battery_use); - } - container.addPreference(screen); - } catch (PackageManager.NameNotFoundException e) { - // ignore the current app and move on to the next. - } + packageMap.put(packageName, highBattery); } }