From 00b667a64efaa3fa9fddc6f80716a655543196da Mon Sep 17 00:00:00 2001 From: Dmitri Plotnikov Date: Wed, 5 May 2021 14:19:05 -0700 Subject: [PATCH] BatteryStatsViewer UI improvements: 1. DEVICE and ALL_APPS are now combined in one screen 2. Totals are displayed at the top 3. Column headers are shown conditionally 4. New icon for totals 5. Swipe-to-refresh Bug: 187370721 Test: mp :BatteryStatsViewer && adb shell am start -n com.android.frameworks.core.batterystatsviewer/.BatteryConsumerPickerActivity Change-Id: Ic250fe54b3fe3c54ff461218f126fc83f9921fd9 --- .../BatteryStatsViewer/Android.bp | 1 + .../res/drawable/gm_sum_24.xml | 10 + .../layout/battery_consumer_entry_layout.xml | 7 +- ...attery_consumer_picker_activity_layout.xml | 33 --- .../layout/battery_consumer_picker_layout.xml | 15 +- .../layout/battery_stats_viewer_layout.xml | 121 +++++---- .../BatteryConsumerData.java | 242 +++++++++++++----- .../BatteryConsumerInfoHelper.java | 179 +++++++------ .../BatteryConsumerPickerActivity.java | 66 +++-- .../BatteryStatsViewerActivity.java | 193 +++++++++----- 10 files changed, 533 insertions(+), 334 deletions(-) create mode 100644 core/tests/batterystatstests/BatteryStatsViewer/res/drawable/gm_sum_24.xml delete mode 100644 core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_picker_activity_layout.xml diff --git a/core/tests/batterystatstests/BatteryStatsViewer/Android.bp b/core/tests/batterystatstests/BatteryStatsViewer/Android.bp index abac56bbe4d58..6046a76f65cd3 100644 --- a/core/tests/batterystatstests/BatteryStatsViewer/Android.bp +++ b/core/tests/batterystatstests/BatteryStatsViewer/Android.bp @@ -15,6 +15,7 @@ android_app { "androidx.appcompat_appcompat", "androidx.cardview_cardview", "androidx.recyclerview_recyclerview", + "androidx.swiperefreshlayout_swiperefreshlayout", "com.google.android.material_material", ], platform_apis: true, diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/gm_sum_24.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/gm_sum_24.xml new file mode 100644 index 0000000000000..3d29102627fb7 --- /dev/null +++ b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/gm_sum_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_entry_layout.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_entry_layout.xml index 98fc581f3420c..be0e135af23af 100644 --- a/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_entry_layout.xml +++ b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_entry_layout.xml @@ -30,7 +30,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" - android:layout_marginEnd="8dp"/> + android:layout_marginEnd="8dp" + android:paddingBottom="8dp"/> - - - - - - - - diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_picker_layout.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_picker_layout.xml index bea38c18c20bf..f35a210cae4b4 100644 --- a/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_picker_layout.xml +++ b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_picker_layout.xml @@ -14,22 +14,15 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - + android:layout_height="match_parent"/> - - \ No newline at end of file + diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_stats_viewer_layout.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_stats_viewer_layout.xml index 24d193c492191..cf50d2ad1e91b 100644 --- a/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_stats_viewer_layout.xml +++ b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_stats_viewer_layout.xml @@ -14,94 +14,89 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - + - + android:visibility="invisible"> - + android:layout_marginTop="10dp" + android:layout_marginEnd="10dp" + android:layout_marginBottom="10dp" + android:layout_marginStart="10dp" + android:padding="20dp"> - + + + + + + + + + + + - - - - - - - + - - - - - - - - + android:gravity="center" + android:visibility="gone" + android:text="No battery stats available"/> + diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java index 24b164bcb8c0c..c2071357bbddd 100644 --- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java +++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java @@ -28,29 +28,54 @@ import java.util.List; public class BatteryConsumerData { + public static final String UID_BATTERY_CONSUMER_ID_PREFIX = "APP|"; + public static final String AGGREGATE_BATTERY_CONSUMER_ID = "SYS|"; + enum EntryType { - POWER_MODELED, - POWER_MEASURED, - POWER_CUSTOM, - DURATION, + UID_TOTAL_POWER, + UID_POWER_MODELED, + UID_POWER_MEASURED, + UID_POWER_CUSTOM, + UID_DURATION, + DEVICE_TOTAL_POWER, + DEVICE_POWER_MODELED, + DEVICE_POWER_MEASURED, + DEVICE_POWER_CUSTOM, + DEVICE_DURATION, + } + + enum ConsumerType { + UID_BATTERY_CONSUMER, + DEVICE_POWER_COMPONENT, } public static class Entry { - public String title; public EntryType entryType; - public double value; - public double total; - public boolean isSystemBatteryConsumer; + public String title; + public double value1; + public double value2; } - private final BatteryConsumerInfoHelper.BatteryConsumerInfo mBatteryConsumerInfo; + private BatteryConsumerInfoHelper.BatteryConsumerInfo mBatteryConsumerInfo; private final List mEntries = new ArrayList<>(); public BatteryConsumerData(Context context, List batteryUsageStatsList, String batteryConsumerId) { + switch (getConsumerType(batteryConsumerId)) { + case UID_BATTERY_CONSUMER: + populateForUidBatteryConsumer(context, batteryUsageStatsList, batteryConsumerId); + break; + case DEVICE_POWER_COMPONENT: + populateForAggregateBatteryConsumer(context, batteryUsageStatsList); + break; + } + } + + private void populateForUidBatteryConsumer( + Context context, List batteryUsageStatsList, + String batteryConsumerId) { BatteryUsageStats batteryUsageStats = batteryUsageStatsList.get(0); BatteryUsageStats modeledBatteryUsageStats = batteryUsageStatsList.get(1); - BatteryConsumer requestedBatteryConsumer = getRequestedBatteryConsumer(batteryUsageStats, batteryConsumerId); BatteryConsumer requestedModeledBatteryConsumer = getRequestedBatteryConsumer( @@ -62,7 +87,7 @@ public class BatteryConsumerData { } mBatteryConsumerInfo = BatteryConsumerInfoHelper.makeBatteryConsumerInfo( - requestedBatteryConsumer, batteryConsumerId, context.getPackageManager()); + batteryUsageStats, batteryConsumerId, context.getPackageManager()); double[] totalPowerByComponentMah = new double[BatteryConsumer.POWER_COMPONENT_COUNT]; double[] totalModeledPowerByComponentMah = @@ -77,56 +102,152 @@ public class BatteryConsumerData { computeTotalPowerForCustomComponent(batteryUsageStats, totalCustomPowerByComponentMah); computeTotalDuration(batteryUsageStats, totalDurationByComponentMs); + if (isPowerProfileModelsOnly(requestedBatteryConsumer)) { + addEntry("Consumed", EntryType.UID_TOTAL_POWER, + requestedBatteryConsumer.getConsumedPower(), + batteryUsageStats.getAggregateBatteryConsumer( + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS) + .getConsumedPower()); + } else { + addEntry("Consumed (measured)", EntryType.UID_TOTAL_POWER, + requestedBatteryConsumer.getConsumedPower(), + batteryUsageStats.getAggregateBatteryConsumer( + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS) + .getConsumedPower()); + addEntry("Consumed (modeled)", EntryType.UID_TOTAL_POWER, + requestedModeledBatteryConsumer.getConsumedPower(), + modeledBatteryUsageStats.getAggregateBatteryConsumer( + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS) + .getConsumedPower()); + } + for (int component = 0; component < BatteryConsumer.POWER_COMPONENT_COUNT; component++) { final String metricTitle = getPowerMetricTitle(component); final int powerModel = requestedBatteryConsumer.getPowerModel(component); if (powerModel == BatteryConsumer.POWER_MODEL_POWER_PROFILE) { - addEntry(metricTitle, EntryType.POWER_MODELED, + addEntry(metricTitle, EntryType.UID_POWER_MODELED, requestedBatteryConsumer.getConsumedPower(component), - totalPowerByComponentMah[component], - mBatteryConsumerInfo.isSystemBatteryConsumer); + totalPowerByComponentMah[component] + ); } else { - addEntry(metricTitle + " (measured)", EntryType.POWER_MEASURED, + addEntry(metricTitle + " (measured)", EntryType.UID_POWER_MEASURED, requestedBatteryConsumer.getConsumedPower(component), - totalPowerByComponentMah[component], - mBatteryConsumerInfo.isSystemBatteryConsumer); - addEntry(metricTitle + " (modeled)", EntryType.POWER_MODELED, + totalPowerByComponentMah[component] + ); + addEntry(metricTitle + " (modeled)", EntryType.UID_POWER_MODELED, requestedModeledBatteryConsumer.getConsumedPower(component), - totalModeledPowerByComponentMah[component], - mBatteryConsumerInfo.isSystemBatteryConsumer); + totalModeledPowerByComponentMah[component] + ); } } for (int component = 0; component < customComponentCount; component++) { final String name = requestedBatteryConsumer.getCustomPowerComponentName( BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + component); - addEntry(name + " (custom)", EntryType.POWER_CUSTOM, + addEntry(name + " (custom)", EntryType.UID_POWER_CUSTOM, requestedBatteryConsumer.getConsumedPowerForCustomComponent( BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + component), - totalCustomPowerByComponentMah[component], - mBatteryConsumerInfo.isSystemBatteryConsumer); + totalCustomPowerByComponentMah[component] + ); } for (int component = 0; component < BatteryConsumer.POWER_COMPONENT_COUNT; component++) { final String metricTitle = getTimeMetricTitle(component); - addEntry(metricTitle, EntryType.DURATION, + addEntry(metricTitle, EntryType.UID_DURATION, requestedBatteryConsumer.getUsageDurationMillis(component), - totalDurationByComponentMs[component], - mBatteryConsumerInfo.isSystemBatteryConsumer); + totalDurationByComponentMs[component] + ); } + + mBatteryConsumerInfo = BatteryConsumerInfoHelper.makeBatteryConsumerInfo(batteryUsageStats, + batteryConsumerId, context.getPackageManager()); + } + + private void populateForAggregateBatteryConsumer(Context context, + List batteryUsageStatsList) { + BatteryUsageStats batteryUsageStats = batteryUsageStatsList.get(0); + BatteryUsageStats modeledBatteryUsageStats = batteryUsageStatsList.get(1); + + final BatteryConsumer deviceBatteryConsumer = + batteryUsageStats.getAggregateBatteryConsumer( + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE); + BatteryConsumer appsBatteryConsumer = + batteryUsageStats.getAggregateBatteryConsumer( + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS); + + BatteryConsumer modeledDeviceBatteryConsumer = + modeledBatteryUsageStats.getAggregateBatteryConsumer( + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE); + BatteryConsumer modeledAppsBatteryConsumer = + modeledBatteryUsageStats.getAggregateBatteryConsumer( + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS); + + if (isPowerProfileModelsOnly(deviceBatteryConsumer)) { + addEntry("Consumed", EntryType.DEVICE_TOTAL_POWER, + deviceBatteryConsumer.getConsumedPower(), + appsBatteryConsumer.getConsumedPower()); + } else { + addEntry("Consumed (measured)", EntryType.DEVICE_TOTAL_POWER, + deviceBatteryConsumer.getConsumedPower(), + appsBatteryConsumer.getConsumedPower()); + addEntry("Consumed (modeled)", EntryType.DEVICE_TOTAL_POWER, + modeledDeviceBatteryConsumer.getConsumedPower(), + modeledAppsBatteryConsumer.getConsumedPower()); + } + + mBatteryConsumerInfo = BatteryConsumerInfoHelper.makeBatteryConsumerInfo(batteryUsageStats, + AGGREGATE_BATTERY_CONSUMER_ID, context.getPackageManager()); + + + for (int component = 0; component < BatteryConsumer.POWER_COMPONENT_COUNT; component++) { + final String metricTitle = getPowerMetricTitle(component); + final int powerModel = deviceBatteryConsumer.getPowerModel(component); + if (powerModel == BatteryConsumer.POWER_MODEL_POWER_PROFILE) { + addEntry(metricTitle, EntryType.DEVICE_POWER_MODELED, + deviceBatteryConsumer.getConsumedPower(component), + appsBatteryConsumer.getConsumedPower(component)); + } else { + addEntry(metricTitle + " (measured)", EntryType.DEVICE_POWER_MEASURED, + deviceBatteryConsumer.getConsumedPower(component), + appsBatteryConsumer.getConsumedPower(component)); + addEntry(metricTitle + " (modeled)", EntryType.DEVICE_POWER_MODELED, + modeledDeviceBatteryConsumer.getConsumedPower(component), + modeledAppsBatteryConsumer.getConsumedPower(component)); + } + } + + final int customComponentCount = + deviceBatteryConsumer.getCustomPowerComponentCount(); + for (int component = 0; component < customComponentCount; component++) { + final String name = deviceBatteryConsumer.getCustomPowerComponentName( + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + component); + addEntry(name + " (custom)", EntryType.DEVICE_POWER_CUSTOM, + deviceBatteryConsumer.getConsumedPowerForCustomComponent( + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + component), + appsBatteryConsumer.getConsumedPowerForCustomComponent( + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + component)); + } + + for (int component = 0; component < BatteryConsumer.POWER_COMPONENT_COUNT; component++) { + final String metricTitle = getTimeMetricTitle(component); + addEntry(metricTitle, EntryType.DEVICE_DURATION, + deviceBatteryConsumer.getUsageDurationMillis(component), 0); + } + } + + private boolean isPowerProfileModelsOnly(BatteryConsumer batteryConsumer) { + for (int component = 0; component < BatteryConsumer.POWER_COMPONENT_COUNT; component++) { + if (batteryConsumer.getPowerModel(component) + != BatteryConsumer.POWER_MODEL_POWER_PROFILE) { + return false; + } + } + return true; } private BatteryConsumer getRequestedBatteryConsumer(BatteryUsageStats batteryUsageStats, String batteryConsumerId) { - for (int scope = 0; - scope < BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT; - scope++) { - if (batteryConsumerId(scope).equals(batteryConsumerId)) { - return batteryUsageStats.getAggregateBatteryConsumer(scope); - } - } - - for (BatteryConsumer consumer : batteryUsageStats.getUidBatteryConsumers()) { + for (UidBatteryConsumer consumer : batteryUsageStats.getUidBatteryConsumers()) { if (batteryConsumerId(consumer).equals(batteryConsumerId)) { return consumer; } @@ -136,17 +257,27 @@ public class BatteryConsumerData { } static String getPowerMetricTitle(int componentId) { - final String componentName = DebugUtils.constantToString(BatteryConsumer.class, - "POWER_COMPONENT_", componentId); - return componentName.charAt(0) + componentName.substring(1).toLowerCase().replace('_', ' ') - + " power"; + return getPowerComponentName(componentId); } static String getTimeMetricTitle(int componentId) { - final String componentName = DebugUtils.constantToString(BatteryConsumer.class, - "POWER_COMPONENT_", componentId); - return componentName.charAt(0) + componentName.substring(1).toLowerCase().replace('_', ' ') - + " time"; + return getPowerComponentName(componentId) + " time"; + } + + private static String getPowerComponentName(int componentId) { + switch (componentId) { + case BatteryConsumer.POWER_COMPONENT_CPU: + return "CPU"; + case BatteryConsumer.POWER_COMPONENT_GNSS: + return "GNSS"; + case BatteryConsumer.POWER_COMPONENT_WIFI: + return "Wi-Fi"; + default: + String componentName = DebugUtils.constantToString(BatteryConsumer.class, + "POWER_COMPONENT_", componentId); + return componentName.charAt(0) + componentName.substring(1).toLowerCase() + .replace('_', ' '); + } } private void computeTotalPower(BatteryUsageStats batteryUsageStats, @@ -183,14 +314,12 @@ public class BatteryConsumerData { } } - private void addEntry(String title, EntryType entryType, double amount, double totalAmount, - boolean isSystemBatteryConsumer) { + private void addEntry(String title, EntryType entryType, double value1, double value2) { Entry entry = new Entry(); entry.title = title; entry.entryType = entryType; - entry.value = amount; - entry.total = totalAmount; - entry.isSystemBatteryConsumer = isSystemBatteryConsumer; + entry.value1 = value1; + entry.value2 = value2; mEntries.add(entry); } @@ -202,18 +331,15 @@ public class BatteryConsumerData { return mEntries; } - public static String batteryConsumerId(BatteryConsumer consumer) { - if (consumer instanceof UidBatteryConsumer) { - return "APP|" - + UserHandle.getUserId(((UidBatteryConsumer) consumer).getUid()) + "|" - + ((UidBatteryConsumer) consumer).getUid(); - } else { - return ""; + public static ConsumerType getConsumerType(String batteryConsumerId) { + if (batteryConsumerId.startsWith(UID_BATTERY_CONSUMER_ID_PREFIX)) { + return ConsumerType.UID_BATTERY_CONSUMER; } + return ConsumerType.DEVICE_POWER_COMPONENT; } - public static String batteryConsumerId( - @BatteryUsageStats.AggregateBatteryConsumerScope int scope) { - return "SYS|" + scope; + public static String batteryConsumerId(UidBatteryConsumer consumer) { + return UID_BATTERY_CONSUMER_ID_PREFIX + UserHandle.getUserId(consumer.getUid()) + "|" + + consumer.getUid(); } } \ No newline at end of file diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerInfoHelper.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerInfoHelper.java index f2d6bca28136c..c6d71c3f573a9 100644 --- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerInfoHelper.java +++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerInfoHelper.java @@ -16,18 +16,16 @@ package com.android.frameworks.core.batterystatsviewer; -import static com.android.frameworks.core.batterystatsviewer.BatteryConsumerData.batteryConsumerId; - import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import android.os.BatteryConsumer; import android.os.BatteryUsageStats; import android.os.Process; import android.os.UidBatteryConsumer; -import android.util.DebugUtils; import androidx.annotation.NonNull; +import java.util.List; + class BatteryConsumerInfoHelper { private static final String SYSTEM_SERVER_PACKAGE_NAME = "android"; @@ -39,93 +37,110 @@ class BatteryConsumerInfoHelper { public ApplicationInfo iconInfo; public CharSequence packages; public CharSequence details; - public boolean isSystemBatteryConsumer; + public BatteryConsumerData.ConsumerType consumerType; } @NonNull public static BatteryConsumerInfo makeBatteryConsumerInfo( - @NonNull BatteryConsumer batteryConsumer, String batteryConsumerId, + @NonNull BatteryUsageStats batteryUsageStats, String batteryConsumerId, PackageManager packageManager) { - BatteryConsumerInfo info = new BatteryConsumerInfo(); - info.id = batteryConsumerId; - info.powerMah = batteryConsumer.getConsumedPower(); - - if (batteryConsumer instanceof UidBatteryConsumer) { - final UidBatteryConsumer uidBatteryConsumer = (UidBatteryConsumer) batteryConsumer; - int uid = uidBatteryConsumer.getUid(); - info.details = String.format("UID: %d", uid); - String packageWithHighestDrain = uidBatteryConsumer.getPackageWithHighestDrain(); - if (uid == Process.ROOT_UID) { - info.label = ""; - } else { - String[] packages = packageManager.getPackagesForUid(uid); - String primaryPackageName = null; - if (uid == Process.SYSTEM_UID) { - primaryPackageName = SYSTEM_SERVER_PACKAGE_NAME; - } else if (packages != null) { - for (String name : packages) { - primaryPackageName = name; - if (name.equals(packageWithHighestDrain)) { - break; - } + BatteryConsumerData.ConsumerType consumerType = BatteryConsumerData.getConsumerType( + batteryConsumerId); + switch (consumerType) { + case UID_BATTERY_CONSUMER: + final List consumers = + batteryUsageStats.getUidBatteryConsumers(); + for (UidBatteryConsumer consumer : consumers) { + if (BatteryConsumerData.batteryConsumerId(consumer).equals(batteryConsumerId)) { + return makeBatteryConsumerInfo(consumer, packageManager); } } - - if (primaryPackageName != null) { - try { - ApplicationInfo applicationInfo = - packageManager.getApplicationInfo(primaryPackageName, 0); - info.label = applicationInfo.loadLabel(packageManager); - info.iconInfo = applicationInfo; - } catch (PackageManager.NameNotFoundException e) { - info.label = primaryPackageName; - } - } else if (packageWithHighestDrain != null) { - info.label = packageWithHighestDrain; - } - - if (packages != null && packages.length > 0) { - StringBuilder sb = new StringBuilder(); - if (primaryPackageName != null) { - sb.append(primaryPackageName); - } - for (String packageName : packages) { - if (packageName.equals(primaryPackageName)) { - continue; - } - - if (sb.length() != 0) { - sb.append(", "); - } - sb.append(packageName); - } - - info.packages = sb; - } - } - // Default the app icon to System Server. This includes root, dex2oat and other UIDs. - if (info.iconInfo == null) { - try { - info.iconInfo = - packageManager.getApplicationInfo(SYSTEM_SERVER_PACKAGE_NAME, 0); - } catch (PackageManager.NameNotFoundException nameNotFoundException) { - // Won't happen - } - } - } else { - for (int scope = 0; - scope < BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT; - scope++) { - if (batteryConsumerId(scope).equals(batteryConsumerId)) { - final String name = DebugUtils.constantToString(BatteryUsageStats.class, - "AGGREGATE_BATTERY_CONSUMER_SCOPE_", scope) - .replace('_', ' '); - info.label = name; - break; - } - } + break; + case DEVICE_POWER_COMPONENT: + return makeAggregateBatteryConsumerInfo(batteryUsageStats); } + BatteryConsumerInfo info = new BatteryConsumerInfo(); + info.id = batteryConsumerId; + return info; + } + + private static BatteryConsumerInfo makeBatteryConsumerInfo( + UidBatteryConsumer uidBatteryConsumer, PackageManager packageManager) { + BatteryConsumerInfo info = new BatteryConsumerInfo(); + info.consumerType = BatteryConsumerData.ConsumerType.UID_BATTERY_CONSUMER; + info.id = BatteryConsumerData.batteryConsumerId(uidBatteryConsumer); + info.powerMah = uidBatteryConsumer.getConsumedPower(); + int uid = uidBatteryConsumer.getUid(); + info.details = String.format("UID: %d", uid); + String packageWithHighestDrain = uidBatteryConsumer.getPackageWithHighestDrain(); + if (uid == Process.ROOT_UID) { + info.label = ""; + } else { + String[] packages = packageManager.getPackagesForUid(uid); + String primaryPackageName = null; + if (uid == Process.SYSTEM_UID) { + primaryPackageName = SYSTEM_SERVER_PACKAGE_NAME; + } else if (packages != null) { + for (String name : packages) { + primaryPackageName = name; + if (name.equals(packageWithHighestDrain)) { + break; + } + } + } + + if (primaryPackageName != null) { + try { + ApplicationInfo applicationInfo = + packageManager.getApplicationInfo(primaryPackageName, 0); + info.label = applicationInfo.loadLabel(packageManager); + info.iconInfo = applicationInfo; + } catch (PackageManager.NameNotFoundException e) { + info.label = primaryPackageName; + } + } else if (packageWithHighestDrain != null) { + info.label = packageWithHighestDrain; + } + + if (packages != null && packages.length > 0) { + StringBuilder sb = new StringBuilder(); + if (primaryPackageName != null) { + sb.append(primaryPackageName); + } + for (String packageName : packages) { + if (packageName.equals(primaryPackageName)) { + continue; + } + + if (sb.length() != 0) { + sb.append(", "); + } + sb.append(packageName); + } + + info.packages = sb; + } + } + // Default the app icon to System Server. This includes root, dex2oat and other UIDs. + if (info.iconInfo == null) { + try { + info.iconInfo = + packageManager.getApplicationInfo(SYSTEM_SERVER_PACKAGE_NAME, 0); + } catch (PackageManager.NameNotFoundException nameNotFoundException) { + // Won't happen + } + } + return info; + } + + private static BatteryConsumerInfo makeAggregateBatteryConsumerInfo( + BatteryUsageStats batteryUsageStats) { + BatteryConsumerInfo info = new BatteryConsumerInfo(); + info.consumerType = BatteryConsumerData.ConsumerType.DEVICE_POWER_COMPONENT; + info.id = BatteryConsumerData.AGGREGATE_BATTERY_CONSUMER_ID; + info.powerMah = batteryUsageStats.getConsumedPower(); + info.label = "Device"; return info; } } diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerPickerActivity.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerPickerActivity.java index 9e63a350df41a..4469168a77b46 100644 --- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerPickerActivity.java +++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerPickerActivity.java @@ -21,6 +21,7 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.os.BatteryStatsManager; import android.os.BatteryUsageStats; +import android.os.BatteryUsageStatsQuery; import android.os.Bundle; import android.os.UidBatteryConsumer; import android.view.LayoutInflater; @@ -35,10 +36,12 @@ import androidx.loader.app.LoaderManager; import androidx.loader.content.Loader; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import com.android.settingslib.utils.AsyncLoaderCompat; import java.util.ArrayList; +import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Locale; @@ -50,10 +53,11 @@ import java.util.Locale; public class BatteryConsumerPickerActivity extends ComponentActivity { private static final String PREF_SELECTED_BATTERY_CONSUMER = "batteryConsumerId"; private static final int BATTERY_STATS_REFRESH_RATE_MILLIS = 60 * 1000; + private static final String FORCE_FRESH_STATS = "force_fresh_stats"; private BatteryConsumerListAdapter mBatteryConsumerListAdapter; private RecyclerView mAppList; - private View mLoadingView; - private final Runnable mBatteryStatsRefresh = this::loadBatteryStats; + private SwipeRefreshLayout mSwipeRefreshLayout; + private final Runnable mBatteryStatsRefresh = this::refreshPeriodically; private interface OnBatteryConsumerSelectedListener { void onBatteryConsumerSelected(String batteryConsumerId); @@ -64,8 +68,11 @@ public class BatteryConsumerPickerActivity extends ComponentActivity { super.onCreate(icicle); setContentView(R.layout.battery_consumer_picker_layout); - mLoadingView = findViewById(R.id.loading_view); + mSwipeRefreshLayout = findViewById(R.id.swipe_refresh); + mSwipeRefreshLayout.setColorSchemeResources(android.R.color.holo_green_light); + mSwipeRefreshLayout.setRefreshing(true); + mSwipeRefreshLayout.setOnRefreshListener(this::onRefresh); mAppList = findViewById(R.id.list_view); mAppList.setLayoutManager(new LinearLayoutManager(this)); mBatteryConsumerListAdapter = @@ -97,7 +104,7 @@ public class BatteryConsumerPickerActivity extends ComponentActivity { @Override protected void onResume() { super.onResume(); - loadBatteryStats(); + refreshPeriodically(); } @Override @@ -106,42 +113,54 @@ public class BatteryConsumerPickerActivity extends ComponentActivity { getMainThreadHandler().removeCallbacks(mBatteryStatsRefresh); } - private void loadBatteryStats() { - LoaderManager.getInstance(this).restartLoader(0, null, - new BatteryConsumerListLoaderCallbacks()); + private void refreshPeriodically() { + loadBatteryUsageStats(false); getMainThreadHandler().postDelayed(mBatteryStatsRefresh, BATTERY_STATS_REFRESH_RATE_MILLIS); } + private void onRefresh() { + loadBatteryUsageStats(true); + } + + private void loadBatteryUsageStats(boolean forceFreshStats) { + Bundle args = new Bundle(); + args.putBoolean(FORCE_FRESH_STATS, forceFreshStats); + LoaderManager.getInstance(this).restartLoader(0, args, + new BatteryConsumerListLoaderCallbacks()); + } + private static class BatteryConsumerListLoader extends AsyncLoaderCompat> { private final BatteryStatsManager mBatteryStatsManager; private final PackageManager mPackageManager; + private final boolean mForceFreshStats; - BatteryConsumerListLoader(Context context) { + BatteryConsumerListLoader(Context context, boolean forceFreshStats) { super(context); mBatteryStatsManager = context.getSystemService(BatteryStatsManager.class); mPackageManager = context.getPackageManager(); + mForceFreshStats = forceFreshStats; } @Override public List loadInBackground() { - final BatteryUsageStats batteryUsageStats = mBatteryStatsManager.getBatteryUsageStats(); + final BatteryUsageStatsQuery query = mForceFreshStats + ? new BatteryUsageStatsQuery.Builder().setMaxStatsAgeMs(0).build() + : BatteryUsageStatsQuery.DEFAULT; + final BatteryUsageStats batteryUsageStats = + mBatteryStatsManager.getBatteryUsageStats(query); List batteryConsumerList = new ArrayList<>(); - for (int scope = 0; - scope < BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT; - scope++) { - batteryConsumerList.add( - BatteryConsumerInfoHelper.makeBatteryConsumerInfo( - batteryUsageStats.getAggregateBatteryConsumer(scope), - BatteryConsumerData.batteryConsumerId(scope), - mPackageManager)); - } + batteryConsumerList.add( + BatteryConsumerInfoHelper.makeBatteryConsumerInfo( + batteryUsageStats, + BatteryConsumerData.AGGREGATE_BATTERY_CONSUMER_ID, + mPackageManager)); for (UidBatteryConsumer consumer : batteryUsageStats.getUidBatteryConsumers()) { batteryConsumerList.add( - BatteryConsumerInfoHelper.makeBatteryConsumerInfo(consumer, + BatteryConsumerInfoHelper.makeBatteryConsumerInfo(batteryUsageStats, BatteryConsumerData.batteryConsumerId(consumer), mPackageManager)); } @@ -166,7 +185,8 @@ public class BatteryConsumerPickerActivity extends ComponentActivity { @Override public Loader> onCreateLoader(int id, Bundle args) { - return new BatteryConsumerListLoader(BatteryConsumerPickerActivity.this); + return new BatteryConsumerListLoader(BatteryConsumerPickerActivity.this, + args.getBoolean(FORCE_FRESH_STATS)); } @Override @@ -174,8 +194,7 @@ public class BatteryConsumerPickerActivity extends ComponentActivity { @NonNull Loader> loader, List batteryConsumerList) { mBatteryConsumerListAdapter.setBatteryConsumerList(batteryConsumerList); - mAppList.setVisibility(View.VISIBLE); - mLoadingView.setVisibility(View.GONE); + mSwipeRefreshLayout.setRefreshing(false); } @Override @@ -187,7 +206,8 @@ public class BatteryConsumerPickerActivity extends ComponentActivity { public class BatteryConsumerListAdapter extends RecyclerView.Adapter { private final OnBatteryConsumerSelectedListener mListener; - private List mBatteryConsumerList; + private List mBatteryConsumerList = + Collections.emptyList(); public BatteryConsumerListAdapter(OnBatteryConsumerSelectedListener listener) { mListener = listener; diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryStatsViewerActivity.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryStatsViewerActivity.java index bb75be4f5ba8a..33ce6bf0bdb5a 100644 --- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryStatsViewerActivity.java +++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryStatsViewerActivity.java @@ -36,6 +36,7 @@ import androidx.loader.app.LoaderManager.LoaderCallbacks; import androidx.loader.content.Loader; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import com.android.settingslib.utils.AsyncLoaderCompat; @@ -48,10 +49,10 @@ public class BatteryStatsViewerActivity extends ComponentActivity { private static final int BATTERY_STATS_REFRESH_RATE_MILLIS = 60 * 1000; private static final int MILLIS_IN_MINUTE = 60000; - private static final int LOADER_BATTERY_USAGE_STATS = 1; + private static final String FORCE_FRESH_STATS = "force_fresh_stats"; private BatteryStatsDataAdapter mBatteryStatsDataAdapter; - private final Runnable mBatteryStatsRefresh = this::loadBatteryStats; + private final Runnable mBatteryStatsRefresh = this::refreshPeriodically; private String mBatteryConsumerId; private TextView mTitleView; private TextView mDetailsView; @@ -59,7 +60,8 @@ public class BatteryStatsViewerActivity extends ComponentActivity { private TextView mPackagesView; private View mHeadingsView; private RecyclerView mBatteryConsumerDataView; - private View mLoadingView; + private SwipeRefreshLayout mSwipeRefreshLayout; + private View mCardView; private View mEmptyView; private List mBatteryUsageStats; @@ -71,6 +73,12 @@ public class BatteryStatsViewerActivity extends ComponentActivity { setContentView(R.layout.battery_stats_viewer_layout); + mSwipeRefreshLayout = findViewById(R.id.swipe_refresh); + mSwipeRefreshLayout.setColorSchemeResources(android.R.color.holo_green_light); + mSwipeRefreshLayout.setRefreshing(true); + mSwipeRefreshLayout.setOnRefreshListener(this::onRefresh); + + mCardView = findViewById(R.id.app_card); mTitleView = findViewById(android.R.id.title); mDetailsView = findViewById(R.id.details); mIconView = findViewById(android.R.id.icon); @@ -82,18 +90,13 @@ public class BatteryStatsViewerActivity extends ComponentActivity { mBatteryStatsDataAdapter = new BatteryStatsDataAdapter(); mBatteryConsumerDataView.setAdapter(mBatteryStatsDataAdapter); - mLoadingView = findViewById(R.id.loading_view); mEmptyView = findViewById(R.id.empty_view); - - LoaderManager loaderManager = LoaderManager.getInstance(this); - loaderManager.restartLoader(LOADER_BATTERY_USAGE_STATS, null, - new BatteryUsageStatsLoaderCallbacks()); } @Override protected void onResume() { super.onResume(); - loadBatteryStats(); + refreshPeriodically(); } @Override @@ -102,32 +105,46 @@ public class BatteryStatsViewerActivity extends ComponentActivity { getMainThreadHandler().removeCallbacks(mBatteryStatsRefresh); } - private void loadBatteryStats() { - LoaderManager loaderManager = LoaderManager.getInstance(this); - loaderManager.restartLoader(LOADER_BATTERY_USAGE_STATS, null, - new BatteryUsageStatsLoaderCallbacks()); + private void refreshPeriodically() { + loadBatteryUsageStats(false); getMainThreadHandler().postDelayed(mBatteryStatsRefresh, BATTERY_STATS_REFRESH_RATE_MILLIS); } + private void onRefresh() { + loadBatteryUsageStats(true); + } + + private void loadBatteryUsageStats(boolean forceFreshStats) { + Bundle args = new Bundle(); + args.putBoolean(FORCE_FRESH_STATS, forceFreshStats); + LoaderManager.getInstance(this).restartLoader(0, args, + new BatteryUsageStatsLoaderCallbacks()); + } + private static class BatteryUsageStatsLoader extends AsyncLoaderCompat> { private final BatteryStatsManager mBatteryStatsManager; + private final boolean mForceFreshStats; - BatteryUsageStatsLoader(Context context) { + BatteryUsageStatsLoader(Context context, boolean forceFreshStats) { super(context); mBatteryStatsManager = context.getSystemService(BatteryStatsManager.class); + mForceFreshStats = forceFreshStats; } @Override public List loadInBackground() { + final int maxStatsAgeMs = mForceFreshStats ? 0 : BATTERY_STATS_REFRESH_RATE_MILLIS; final BatteryUsageStatsQuery queryDefault = new BatteryUsageStatsQuery.Builder() .includePowerModels() + .setMaxStatsAgeMs(maxStatsAgeMs) .build(); final BatteryUsageStatsQuery queryPowerProfileModeledOnly = new BatteryUsageStatsQuery.Builder() .powerProfileModeledOnly() .includePowerModels() + .setMaxStatsAgeMs(maxStatsAgeMs) .build(); return mBatteryStatsManager.getBatteryUsageStats( List.of(queryDefault, queryPowerProfileModeledOnly)); @@ -143,7 +160,8 @@ public class BatteryStatsViewerActivity extends ComponentActivity { @NonNull @Override public Loader> onCreateLoader(int id, Bundle args) { - return new BatteryUsageStatsLoader(BatteryStatsViewerActivity.this); + return new BatteryUsageStatsLoader(BatteryStatsViewerActivity.this, + args.getBoolean(FORCE_FRESH_STATS)); } @Override @@ -194,7 +212,8 @@ public class BatteryStatsViewerActivity extends ComponentActivity { mPackagesView.setVisibility(View.GONE); } - if (batteryConsumerInfo.isSystemBatteryConsumer) { + if (batteryConsumerInfo.consumerType + == BatteryConsumerData.ConsumerType.DEVICE_POWER_COMPONENT) { mHeadingsView.setVisibility(View.VISIBLE); } else { mHeadingsView.setVisibility(View.GONE); @@ -210,7 +229,8 @@ public class BatteryStatsViewerActivity extends ComponentActivity { mBatteryConsumerDataView.setVisibility(View.VISIBLE); } - mLoadingView.setVisibility(View.GONE); + mCardView.setVisibility(View.VISIBLE); + mSwipeRefreshLayout.setRefreshing(false); } private static class BatteryStatsDataAdapter extends @@ -218,16 +238,16 @@ public class BatteryStatsViewerActivity extends ComponentActivity { public static class ViewHolder extends RecyclerView.ViewHolder { public ImageView iconImageView; public TextView titleTextView; - public TextView amountTextView; - public TextView percentTextView; + public TextView value1TextView; + public TextView value2TextView; ViewHolder(View itemView) { super(itemView); iconImageView = itemView.findViewById(R.id.icon); titleTextView = itemView.findViewById(R.id.title); - amountTextView = itemView.findViewById(R.id.amount); - percentTextView = itemView.findViewById(R.id.percent); + value1TextView = itemView.findViewById(R.id.value1); + value2TextView = itemView.findViewById(R.id.value2); } } @@ -255,57 +275,108 @@ public class BatteryStatsViewerActivity extends ComponentActivity { @Override public void onBindViewHolder(@NonNull ViewHolder viewHolder, int position) { BatteryConsumerData.Entry entry = mEntries.get(position); + switch (entry.entryType) { - case POWER_MODELED: - viewHolder.titleTextView.setText(entry.title); - viewHolder.amountTextView.setText( - String.format(Locale.getDefault(), "%.1f mAh", entry.value)); - viewHolder.iconImageView.setImageResource(R.drawable.gm_calculate_24); - viewHolder.itemView.setBackgroundResource( + case UID_TOTAL_POWER: + setTitleIconAndBackground(viewHolder, entry.title, + R.drawable.gm_sum_24, 0); + setPowerText(viewHolder.value1TextView, entry.value1); + setProportionText(viewHolder.value2TextView, entry); + break; + case UID_POWER_MODELED: + setTitleIconAndBackground(viewHolder, entry.title, + R.drawable.gm_calculate_24, R.color.battery_consumer_bg_power_profile); + setPowerText(viewHolder.value1TextView, entry.value1); + setProportionText(viewHolder.value2TextView, entry); break; - case POWER_MEASURED: - viewHolder.titleTextView.setText(entry.title); - viewHolder.amountTextView.setText( - String.format(Locale.getDefault(), "%.1f mAh", entry.value)); - viewHolder.iconImageView.setImageResource(R.drawable.gm_amp_24); - viewHolder.itemView.setBackgroundResource( + case UID_POWER_MEASURED: + setTitleIconAndBackground(viewHolder, entry.title, + R.drawable.gm_amp_24, R.color.battery_consumer_bg_measured_energy); + setPowerText(viewHolder.value1TextView, entry.value1); + setProportionText(viewHolder.value2TextView, entry); break; - case POWER_CUSTOM: - viewHolder.titleTextView.setText(entry.title); - viewHolder.amountTextView.setText( - String.format(Locale.getDefault(), "%.1f mAh", entry.value)); - viewHolder.iconImageView.setImageResource(R.drawable.gm_custom_24); - viewHolder.itemView.setBackgroundResource( + case UID_POWER_CUSTOM: + setTitleIconAndBackground(viewHolder, entry.title, + R.drawable.gm_custom_24, R.color.battery_consumer_bg_measured_energy); + setPowerText(viewHolder.value1TextView, entry.value1); + setProportionText(viewHolder.value2TextView, entry); break; - case DURATION: - viewHolder.titleTextView.setText(entry.title); - final long durationMs = (long) entry.value; - CharSequence text; - if (durationMs < MILLIS_IN_MINUTE) { - text = String.format(Locale.getDefault(), "%,d ms", durationMs); - } else { - text = String.format(Locale.getDefault(), "%,d m %d s", - durationMs / MILLIS_IN_MINUTE, - (durationMs % MILLIS_IN_MINUTE) / 1000); - } - - viewHolder.amountTextView.setText(text); - viewHolder.iconImageView.setImageResource(R.drawable.gm_timer_24); - viewHolder.itemView.setBackground(null); + case UID_DURATION: + setTitleIconAndBackground(viewHolder, entry.title, + R.drawable.gm_timer_24, 0); + setDurationText(viewHolder.value1TextView, (long) entry.value1); + setProportionText(viewHolder.value2TextView, entry); + break; + case DEVICE_TOTAL_POWER: + setTitleIconAndBackground(viewHolder, entry.title, + R.drawable.gm_sum_24, 0); + setPowerText(viewHolder.value1TextView, entry.value1); + setPowerText(viewHolder.value2TextView, entry.value2); + break; + case DEVICE_POWER_MODELED: + setTitleIconAndBackground(viewHolder, entry.title, + R.drawable.gm_calculate_24, + R.color.battery_consumer_bg_power_profile); + setPowerText(viewHolder.value1TextView, entry.value1); + setPowerText(viewHolder.value2TextView, entry.value2); + break; + case DEVICE_POWER_MEASURED: + setTitleIconAndBackground(viewHolder, entry.title, + R.drawable.gm_amp_24, + R.color.battery_consumer_bg_measured_energy); + setPowerText(viewHolder.value1TextView, entry.value1); + setPowerText(viewHolder.value2TextView, entry.value2); + break; + case DEVICE_POWER_CUSTOM: + setTitleIconAndBackground(viewHolder, entry.title, + R.drawable.gm_custom_24, + R.color.battery_consumer_bg_measured_energy); + setPowerText(viewHolder.value1TextView, entry.value1); + setPowerText(viewHolder.value2TextView, entry.value2); + break; + case DEVICE_DURATION: + setTitleIconAndBackground(viewHolder, entry.title, + R.drawable.gm_timer_24, 0); + setDurationText(viewHolder.value1TextView, (long) entry.value1); + viewHolder.value2TextView.setVisibility(View.GONE); break; } + } - double proportion; - if (entry.isSystemBatteryConsumer) { - proportion = entry.value != 0 ? entry.total * 100 / entry.value : 0; - } else { - proportion = entry.total != 0 ? entry.value * 100 / entry.total : 0; - } - viewHolder.percentTextView.setText( + private void setTitleIconAndBackground(ViewHolder viewHolder, String title, int icon, + int background) { + viewHolder.titleTextView.setText(title); + viewHolder.iconImageView.setImageResource(icon); + viewHolder.itemView.setBackgroundResource(background); + } + + private void setProportionText(TextView textView, BatteryConsumerData.Entry entry) { + final double proportion = entry.value2 != 0 ? entry.value1 * 100 / entry.value2 : 0; + textView.setText( String.format(Locale.getDefault(), "%.1f%%", proportion)); + textView.setVisibility(View.VISIBLE); + } + + private void setPowerText(TextView textView, double powerMah) { + textView.setText(String.format(Locale.getDefault(), "%.1f", powerMah)); + textView.setVisibility(View.VISIBLE); + } + + private void setDurationText(TextView textView, long durationMs) { + CharSequence text; + if (durationMs < MILLIS_IN_MINUTE) { + text = String.format(Locale.getDefault(), "%,d ms", durationMs); + } else { + text = String.format(Locale.getDefault(), "%,d m %d s", + durationMs / MILLIS_IN_MINUTE, + (durationMs % MILLIS_IN_MINUTE) / 1000); + } + + textView.setText(text); + textView.setVisibility(View.VISIBLE); } } }