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;