From f094b876615ae84a34269d371ff69f5a176a1ed4 Mon Sep 17 00:00:00 2001 From: jackqdyulei Date: Mon, 27 Mar 2017 14:00:29 -0700 Subject: [PATCH] Add summary for categories in battery advanced page If the category only contains one app, show usage time, otherwise show app with maximum usage. Also add usage time for apps in battery settings page. Bug: 35396770 Test: RunSettingsRoboTests Change-Id: I43fe9c2289535be2c1b95ffded6b52b0ff099589 (cherry picked from commit 3bbaca9c7c863c802f2897ac30daa2c59e2115c1) --- .../fuelgauge/PowerUsageAdvanced.java | 102 +++++++++++++++++- .../settings/fuelgauge/PowerUsageSummary.java | 4 + .../fuelgauge/PowerUsageAdvancedTest.java | 77 +++++++++++-- 3 files changed, 170 insertions(+), 13 deletions(-) diff --git a/src/com/android/settings/fuelgauge/PowerUsageAdvanced.java b/src/com/android/settings/fuelgauge/PowerUsageAdvanced.java index 93e7810bb1d..fe0cf4d77ef 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageAdvanced.java +++ b/src/com/android/settings/fuelgauge/PowerUsageAdvanced.java @@ -13,22 +13,29 @@ */ package com.android.settings.fuelgauge; +import android.app.Activity; import android.content.Context; import android.content.pm.PackageManager; import android.os.BatteryStats; import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.UserManager; import android.provider.SearchIndexableResource; import android.support.annotation.ColorInt; import android.support.annotation.IntDef; import android.support.annotation.NonNull; import android.support.annotation.StringRes; import android.support.annotation.VisibleForTesting; +import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceGroup; + import com.android.internal.logging.nano.MetricsProto; import com.android.internal.os.BatterySipper; import com.android.internal.os.BatterySipper.DrainType; import com.android.internal.os.BatteryStatsHelper; import com.android.settings.R; +import com.android.settings.Utils; import com.android.settings.core.PreferenceController; import com.android.settings.fuelgauge.PowerUsageAdvanced.PowerUsageData.UsageType; import com.android.settings.overlay.FeatureFactory; @@ -64,6 +71,38 @@ public class PowerUsageAdvanced extends PowerUsageBase { private PreferenceGroup mUsageListGroup; private PowerUsageFeatureProvider mPowerUsageFeatureProvider; private PackageManager mPackageManager; + private UserManager mUserManager; + private Map mBatteryDataMap; + + Handler mHandler = new Handler() { + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case BatteryEntry.MSG_UPDATE_NAME_ICON: + final int dischargeAmount = mStatsHelper.getStats().getDischargeAmount( + STATUS_TYPE); + final double totalPower = mStatsHelper.getTotalPower(); + final BatteryEntry entry = (BatteryEntry) msg.obj; + final int usageType = extractUsageType(entry.sipper); + + PowerUsageData usageData = mBatteryDataMap.get(usageType); + Preference pref = findPreference(String.valueOf(usageType)); + if (pref != null && usageData != null) { + updateUsageDataSummary(usageData, totalPower, dischargeAmount); + pref.setSummary(usageData.summary); + } + break; + case BatteryEntry.MSG_REPORT_FULLY_DRAWN: + Activity activity = getActivity(); + if (activity != null) { + activity.reportFullyDrawn(); + } + break; + } + super.handleMessage(msg); + } + }; @Override public void onCreate(Bundle icicle) { @@ -76,6 +115,7 @@ public class PowerUsageAdvanced extends PowerUsageBase { mPowerUsageFeatureProvider = FeatureFactory.getFactory(context) .getPowerUsageFeatureProvider(context); mPackageManager = context.getPackageManager(); + mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); } @Override @@ -84,6 +124,21 @@ public class PowerUsageAdvanced extends PowerUsageBase { refreshStats(); } + @Override + public void onPause() { + BatteryEntry.stopRequestQueue(); + mHandler.removeMessages(BatteryEntry.MSG_UPDATE_NAME_ICON); + super.onPause(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (getActivity().isChangingConfigurations()) { + BatteryEntry.clearUidCache(); + } + } + @Override public int getMetricsCategory() { return MetricsProto.MetricsEvent.FUELGAUGE_BATTERY_HISTORY_DETAIL; @@ -116,15 +171,19 @@ public class PowerUsageAdvanced extends PowerUsageBase { final PowerUsageData batteryData = dataList.get(i); final PowerGaugePreference pref = new PowerGaugePreference(getPrefContext()); + pref.setKey(String.valueOf(batteryData.usageType)); pref.setTitle(batteryData.titleResId); pref.setSummary(batteryData.summary); pref.setPercent(batteryData.percentage); mUsageListGroup.addPreference(pref); } + + BatteryEntry.startRequestQueue(); } @VisibleForTesting - @UsageType int extractUsageType(BatterySipper sipper) { + @UsageType + int extractUsageType(BatterySipper sipper) { final DrainType drainType = sipper.drainType; final int uid = sipper.getUid(); @@ -163,21 +222,56 @@ public class PowerUsageAdvanced extends PowerUsageBase { sipper.mPackages = mPackageManager.getPackagesForUid(sipper.getUid()); final PowerUsageData usageData = batteryDataMap.get(extractUsageType(sipper)); usageData.totalPowerMah += sipper.totalPowerMah; + if (sipper.drainType == DrainType.APP && sipper.usageTimeMs != 0) { + sipper.usageTimeMs = BatteryUtils.getProcessTimeMs(BatteryUtils.StatusType.ALL, + sipper.uidObj, STATUS_TYPE); + } + usageData.totalUsageTimeMs += sipper.usageTimeMs; + usageData.usageList.add(sipper); } - // TODO(b/35396770): add logic to extract the summary final List batteryDataList = new ArrayList<>(batteryDataMap.values()); final int dischargeAmount = statusHelper.getStats().getDischargeAmount(STATUS_TYPE); final double totalPower = statusHelper.getTotalPower(); for (final PowerUsageData usageData : batteryDataList) { usageData.percentage = (usageData.totalPowerMah / totalPower) * dischargeAmount; + updateUsageDataSummary(usageData, totalPower, dischargeAmount); } Collections.sort(batteryDataList); + mBatteryDataMap = batteryDataMap; return batteryDataList; } + @VisibleForTesting + void updateUsageDataSummary(PowerUsageData usageData, double totalPower, int dischargeAmount) { + if (usageData.usageList.size() <= 1) { + usageData.summary = getString(R.string.battery_used_for, + Utils.formatElapsedTime(getContext(), usageData.totalUsageTimeMs, false)); + } else { + BatterySipper sipper = findBatterySipperWithMaxBatteryUsage(usageData.usageList); + BatteryEntry batteryEntry = new BatteryEntry(getContext(), mHandler, mUserManager, + sipper); + final double percentage = (sipper.totalPowerMah / totalPower) * dischargeAmount; + usageData.summary = getString(R.string.battery_used_by, + Utils.formatPercentage(percentage, true), batteryEntry.name); + } + } + + @VisibleForTesting + BatterySipper findBatterySipperWithMaxBatteryUsage(List usageList) { + BatterySipper sipper = usageList.get(0); + for (int i = 1, size = usageList.size(); i < size; i++) { + final BatterySipper comparedSipper = usageList.get(i); + if (comparedSipper.totalPowerMah > sipper.totalPowerMah) { + sipper = comparedSipper; + } + } + + return sipper; + } + @VisibleForTesting void setPackageManager(PackageManager packageManager) { mPackageManager = packageManager; @@ -221,10 +315,12 @@ public class PowerUsageAdvanced extends PowerUsageBase { public String summary; public double percentage; public double totalPowerMah; + public long totalUsageTimeMs; @ColorInt public int iconColor; @UsageType public int usageType; + public List usageList; public PowerUsageData(@UsageType int usageType) { this(usageType, 0); @@ -233,8 +329,10 @@ public class PowerUsageAdvanced extends PowerUsageBase { public PowerUsageData(@UsageType int usageType, double totalPower) { this.usageType = usageType; totalPowerMah = 0; + totalUsageTimeMs = 0; titleResId = getTitleResId(usageType); totalPowerMah = totalPower; + usageList = new ArrayList<>(); } private int getTitleResId(@UsageType int usageType) { diff --git a/src/com/android/settings/fuelgauge/PowerUsageSummary.java b/src/com/android/settings/fuelgauge/PowerUsageSummary.java index b0e8fb0aeaa..67e0c5d736f 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageSummary.java +++ b/src/com/android/settings/fuelgauge/PowerUsageSummary.java @@ -489,6 +489,10 @@ public class PowerUsageSummary extends PowerUsageBase { pref.setTitle(entry.getLabel()); pref.setOrder(i + 1); pref.setPercent(percentOfTotal); + if (sipper.usageTimeMs == 0 && sipper.drainType == DrainType.APP) { + sipper.usageTimeMs = BatteryUtils.getProcessTimeMs( + BatteryUtils.StatusType.FOREGROUND, sipper.uidObj, mStatsType); + } setUsageSummary(pref, usedTime, sipper.usageTimeMs); if ((sipper.drainType != DrainType.APP || sipper.uidObj.getUid() == Process.ROOT_UID) diff --git a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageAdvancedTest.java b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageAdvancedTest.java index eb966047ef7..6ac65000809 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageAdvancedTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageAdvancedTest.java @@ -15,31 +15,40 @@ */ package com.android.settings.fuelgauge; +import android.content.Context; import android.content.pm.PackageManager; -import android.os.Process; + import com.android.internal.os.BatterySipper; import com.android.internal.os.BatterySipper.DrainType; import com.android.internal.os.BatteryStatsHelper; +import com.android.settings.R; import com.android.settings.SettingsRobolectricTestRunner; import com.android.settings.TestConfig; +import com.android.settings.Utils; import com.android.settings.fuelgauge.PowerUsageAdvanced.PowerUsageData; import com.android.settings.fuelgauge.PowerUsageAdvanced.PowerUsageData.UsageType; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Set; import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Matchers.eq; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(SettingsRobolectricTestRunner.class) @@ -53,9 +62,13 @@ public class PowerUsageAdvancedTest { private static final double TYPE_WIFI_USAGE = 0; private static final double TOTAL_USAGE = TYPE_APP_USAGE * 2 + TYPE_BLUETOOTH_USAGE + TYPE_WIFI_USAGE; + private static final double TOTAL_POWER = 500; private static final double PRECISION = 0.001; + private static final String STUB_STRING = "stub_string"; @Mock - private BatterySipper mBatterySipper; + private BatterySipper mNormalBatterySipper; + @Mock + private BatterySipper mMaxBatterySipper; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private BatteryStatsHelper mBatteryStatsHelper; @Mock @@ -63,11 +76,14 @@ public class PowerUsageAdvancedTest { @Mock private PackageManager mPackageManager; private PowerUsageAdvanced mPowerUsageAdvanced; + private PowerUsageData mPowerUsageData; + private Context mShadowContext; @Before public void setUp() { MockitoAnnotations.initMocks(this); - mPowerUsageAdvanced = new PowerUsageAdvanced(); + mShadowContext = RuntimeEnvironment.application; + mPowerUsageAdvanced = spy(new PowerUsageAdvanced()); List batterySippers = new ArrayList<>(); batterySippers.add(new BatterySipper(DrainType.APP, @@ -83,16 +99,24 @@ public class PowerUsageAdvancedTest { DISCHARGE_AMOUNT); when(mBatteryStatsHelper.getUsageList()).thenReturn(batterySippers); when(mBatteryStatsHelper.getTotalPower()).thenReturn(TOTAL_USAGE); + when(mPowerUsageAdvanced.getContext()).thenReturn(mShadowContext); + doReturn(STUB_STRING).when(mPowerUsageAdvanced).getString(anyInt(), any(), any()); + doReturn(STUB_STRING).when(mPowerUsageAdvanced).getString(anyInt(), any()); mPowerUsageAdvanced.setPackageManager(mPackageManager); mPowerUsageAdvanced.setPowerUsageFeatureProvider(mPowerUsageFeatureProvider); + + mPowerUsageData = new PowerUsageData(UsageType.APP); + mMaxBatterySipper.totalPowerMah = TYPE_BLUETOOTH_USAGE; + mMaxBatterySipper.drainType = DrainType.BLUETOOTH; + mNormalBatterySipper.drainType = DrainType.SCREEN; } @Test public void testExtractUsageType_TypeSystem_ReturnSystem() { - mBatterySipper.drainType = DrainType.APP; + mNormalBatterySipper.drainType = DrainType.APP; when(mPowerUsageFeatureProvider.isTypeSystem(any())).thenReturn(true); - assertThat(mPowerUsageAdvanced.extractUsageType(mBatterySipper)) + assertThat(mPowerUsageAdvanced.extractUsageType(mNormalBatterySipper)) .isEqualTo(UsageType.SYSTEM); } @@ -105,19 +129,19 @@ public class PowerUsageAdvancedTest { assertThat(drainTypes.length).isEqualTo(usageTypes.length); for (int i = 0, size = drainTypes.length; i < size; i++) { - mBatterySipper.drainType = drainTypes[i]; - assertThat(mPowerUsageAdvanced.extractUsageType(mBatterySipper)) + mNormalBatterySipper.drainType = drainTypes[i]; + assertThat(mPowerUsageAdvanced.extractUsageType(mNormalBatterySipper)) .isEqualTo(usageTypes[i]); } } @Test public void testExtractUsageType_TypeService_ReturnService() { - mBatterySipper.drainType = DrainType.APP; - when(mBatterySipper.getUid()).thenReturn(FAKE_UID_1); + mNormalBatterySipper.drainType = DrainType.APP; + when(mNormalBatterySipper.getUid()).thenReturn(FAKE_UID_1); when(mPowerUsageFeatureProvider.isTypeService(any())).thenReturn(true); - assertThat(mPowerUsageAdvanced.extractUsageType(mBatterySipper)) + assertThat(mPowerUsageAdvanced.extractUsageType(mNormalBatterySipper)) .isEqualTo(UsageType.SERVICE); } @@ -146,6 +170,37 @@ public class PowerUsageAdvancedTest { } } + @Test + public void testUpdateUsageDataSummary_onlyOneApp_showUsageTime() { + mPowerUsageData.usageList.add(mNormalBatterySipper); + mPowerUsageAdvanced.updateUsageDataSummary(mPowerUsageData, TOTAL_POWER, DISCHARGE_AMOUNT); + + verify(mPowerUsageAdvanced).getString(eq(R.string.battery_used_for), any()); + } + + @Test + public void testUpdateUsageDataSummary_moreThanOneApp_showMaxUsageApp() { + mPowerUsageData.usageList.add(mNormalBatterySipper); + mPowerUsageData.usageList.add(mMaxBatterySipper); + doReturn(mMaxBatterySipper).when(mPowerUsageAdvanced).findBatterySipperWithMaxBatteryUsage( + mPowerUsageData.usageList); + final double percentage = (TYPE_BLUETOOTH_USAGE / TOTAL_POWER) * DISCHARGE_AMOUNT; + mPowerUsageAdvanced.updateUsageDataSummary(mPowerUsageData, TOTAL_POWER, DISCHARGE_AMOUNT); + + verify(mPowerUsageAdvanced).getString(eq(R.string.battery_used_by), + eq(Utils.formatPercentage(percentage, true)), any()); + } + + @Test + public void testFindBatterySipperWithMaxBatteryUsage_findCorrectOne() { + mPowerUsageData.usageList.add(mNormalBatterySipper); + mPowerUsageData.usageList.add(mMaxBatterySipper); + BatterySipper sipper = mPowerUsageAdvanced.findBatterySipperWithMaxBatteryUsage( + mPowerUsageData.usageList); + + assertThat(sipper).isEqualTo(mMaxBatterySipper); + } + @Test public void testInit_ContainsAllUsageType() { final int[] usageTypeSet = mPowerUsageAdvanced.mUsageTypes;