From: jackqdyulei Date: Tue, 2 Jan 2018 22:19:06 +0000 (-0800) Subject: Add high usage battery tip X-Git-Tag: android-x86-9.0-r1~122^2~10^2~1 X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=ca102facf0aa11a8205c0bf66979d40878f8d508;p=android-x86%2Fpackages-apps-Settings.git Add high usage battery tip 1. Add both model and detector 2. Move the screen usage method to BatteryUtils so we could reuse it. 3. Add and update the tests Bug: 70570352 Test: RunSettingsRoboTests Change-Id: I6a7248d9d48ee8cb6fc2c18c8c225210d49b6bc9 --- diff --git a/res/values/strings.xml b/res/values/strings.xml index 79faf9b013..033211790d 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -4773,6 +4773,21 @@ Low battery capacity Battery can\'t provide good battery life + + Phone used heavily + + Tablet used heavily + + Device used heavily + + About %1$s used since last full charge + + Your phone was used heavily and this consumed a lot of battery. Your battery is behaving normally.\n\n Your phone was used for about %1$s since last full charge.\n\n Total usage: + + Your tablet was used heavily and this consumed a lot of battery. Your battery is behaving normally.\n\n Your tablet was used for about %1$s since last full charge.\n\n Total usage: + + Your device was used heavily and this consumed a lot of battery. Your battery is behaving normally.\n\n Your device was used for about %1$s since last full charge.\n\n Total usage: + Smart battery manager diff --git a/src/com/android/settings/fuelgauge/BatteryUtils.java b/src/com/android/settings/fuelgauge/BatteryUtils.java index 68677fab2d..0952f1f095 100644 --- a/src/com/android/settings/fuelgauge/BatteryUtils.java +++ b/src/com/android/settings/fuelgauge/BatteryUtils.java @@ -345,6 +345,17 @@ public class BatteryUtils { } + /** + * Calculate the screen usage time since last full charge. + * @param batteryStatsHelper utility class that contains the screen usage data + * @return time in millis + */ + public long calculateScreenUsageTime(BatteryStatsHelper batteryStatsHelper) { + final BatterySipper sipper = findBatterySipperByType( + batteryStatsHelper.getUsageList(), BatterySipper.DrainType.SCREEN); + return sipper != null ? sipper.usageTimeMs : 0; + } + public static void logRuntime(String tag, String message, long startTime) { Log.d(tag, message + ": " + (System.currentTimeMillis() - startTime) + "ms"); } @@ -432,6 +443,20 @@ public class BatteryUtils { return batteryInfo; } + /** + * Find the {@link BatterySipper} with the corresponding {@link BatterySipper.DrainType} + */ + public BatterySipper findBatterySipperByType(List usageList, + BatterySipper.DrainType type) { + for (int i = 0, size = usageList.size(); i < size; i++) { + final BatterySipper sipper = usageList.get(i); + if (sipper.drainType == type) { + return sipper; + } + } + return null; + } + private boolean isDataCorrupted() { return mPackageManager == null || mAppOpsManager == null; } diff --git a/src/com/android/settings/fuelgauge/PowerUsageSummary.java b/src/com/android/settings/fuelgauge/PowerUsageSummary.java index 0315f032e6..507043f155 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageSummary.java +++ b/src/com/android/settings/fuelgauge/PowerUsageSummary.java @@ -369,8 +369,9 @@ public class PowerUsageSummary extends PowerUsageBase implements OnLongClickList restartBatteryInfoLoader(); final long lastFullChargeTime = mBatteryUtils.calculateLastFullChargeTime(mStatsHelper, System.currentTimeMillis()); - updateScreenPreference(); updateLastFullChargePreference(lastFullChargeTime); + mScreenUsagePref.setSubtitle(Utils.formatElapsedTime(getContext(), + mBatteryUtils.calculateScreenUsageTime(mStatsHelper), false)); final CharSequence timeSequence = Utils.formatRelativeTime(context, lastFullChargeTime, false); @@ -394,26 +395,6 @@ public class PowerUsageSummary extends PowerUsageBase implements OnLongClickList } @VisibleForTesting - BatterySipper findBatterySipperByType(List usageList, DrainType type) { - for (int i = 0, size = usageList.size(); i < size; i++) { - final BatterySipper sipper = usageList.get(i); - if (sipper.drainType == type) { - return sipper; - } - } - return null; - } - - @VisibleForTesting - void updateScreenPreference() { - final BatterySipper sipper = findBatterySipperByType( - mStatsHelper.getUsageList(), DrainType.SCREEN); - final long usageTimeMs = sipper != null ? sipper.usageTimeMs : 0; - - mScreenUsagePref.setSubtitle(Utils.formatElapsedTime(getContext(), usageTimeMs, false)); - } - - @VisibleForTesting void updateLastFullChargePreference(long timeMs) { final CharSequence timeSequence = Utils.formatRelativeTime(getContext(), timeMs, false); mLastFullChargePref.setSubtitle(timeSequence); diff --git a/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java b/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java index 9c3f48c0a6..a1db57a409 100644 --- a/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java +++ b/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java @@ -23,6 +23,7 @@ import com.android.internal.os.BatteryStatsHelper; import com.android.settings.fuelgauge.BatteryInfo; import com.android.settings.fuelgauge.BatteryUtils; import com.android.settings.fuelgauge.batterytip.detectors.BatteryTipDetector; +import com.android.settings.fuelgauge.batterytip.detectors.HighUsageDetector; import com.android.settings.fuelgauge.batterytip.detectors.LowBatteryDetector; import com.android.settings.fuelgauge.batterytip.detectors.SummaryDetector; import com.android.settings.fuelgauge.batterytip.tips.BatteryTip; @@ -65,6 +66,8 @@ public class BatteryTipLoader extends AsyncLoader> { mVisibleTips = 0; addBatteryTipFromDetector(tips, new LowBatteryDetector(policy, batteryInfo)); + addBatteryTipFromDetector(tips, + new HighUsageDetector(getContext(), policy, mBatteryStatsHelper)); // Add summary detector at last since it need other detectors to update the mVisibleTips addBatteryTipFromDetector(tips, new SummaryDetector(policy, mVisibleTips)); diff --git a/src/com/android/settings/fuelgauge/batterytip/detectors/HighUsageDetector.java b/src/com/android/settings/fuelgauge/batterytip/detectors/HighUsageDetector.java new file mode 100644 index 0000000000..5c2ecad1e0 --- /dev/null +++ b/src/com/android/settings/fuelgauge/batterytip/detectors/HighUsageDetector.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2018 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.batterytip.detectors; + +import android.content.Context; +import android.os.BatteryStats; +import android.support.annotation.VisibleForTesting; +import android.text.format.DateUtils; + +import com.android.internal.os.BatterySipper; +import com.android.internal.os.BatteryStatsHelper; +import com.android.settings.Utils; +import com.android.settings.fuelgauge.BatteryUtils; +import com.android.settings.fuelgauge.batterytip.BatteryTipPolicy; +import com.android.settings.fuelgauge.batterytip.tips.BatteryTip; +import com.android.settings.fuelgauge.batterytip.tips.HighUsageTip; +import com.android.settings.fuelgauge.batterytip.tips.SummaryTip; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Detector whether to show summary tip. This detector should be executed as the last + * {@link BatteryTipDetector} since it need the most up-to-date {@code visibleTips} + */ +public class HighUsageDetector implements BatteryTipDetector { + private BatteryTipPolicy mPolicy; + private BatteryStatsHelper mBatteryStatsHelper; + private List mHighUsageAppList; + private Context mContext; + @VisibleForTesting + BatteryUtils mBatteryUtils; + + public HighUsageDetector(Context context, BatteryTipPolicy policy, + BatteryStatsHelper batteryStatsHelper) { + mContext = context; + mPolicy = policy; + mBatteryStatsHelper = batteryStatsHelper; + mHighUsageAppList = new ArrayList<>(); + mBatteryUtils = BatteryUtils.getInstance(context); + } + + @Override + public BatteryTip detect() { + final long screenUsageTimeMs = mBatteryUtils.calculateScreenUsageTime(mBatteryStatsHelper); + //TODO(b/70570352): Change it to detect whether battery drops 25% in last 2 hours + if (mPolicy.highUsageEnabled && screenUsageTimeMs > DateUtils.HOUR_IN_MILLIS) { + final List batterySippers = mBatteryStatsHelper.getUsageList(); + for (int i = 0, size = batterySippers.size(); i < size; i++) { + final BatterySipper batterySipper = batterySippers.get(i); + if (!mBatteryUtils.shouldHideSipper(batterySipper)) { + final long foregroundTimeMs = mBatteryUtils.getProcessTimeMs( + BatteryUtils.StatusType.FOREGROUND, batterySipper.uidObj, + BatteryStats.STATS_SINCE_CHARGED); + mHighUsageAppList.add(new HighUsageTip.HighUsageApp( + mBatteryUtils.getPackageName(batterySipper.getUid()), + foregroundTimeMs)); + } + } + + mHighUsageAppList = mHighUsageAppList.subList(0, + Math.min(mPolicy.highUsageAppCount, mHighUsageAppList.size())); + Collections.sort(mHighUsageAppList, Collections.reverseOrder()); + } + + return new HighUsageTip(Utils.formatElapsedTime(mContext, screenUsageTimeMs, false), + mHighUsageAppList); + } +} diff --git a/src/com/android/settings/fuelgauge/batterytip/tips/HighUsageTip.java b/src/com/android/settings/fuelgauge/batterytip/tips/HighUsageTip.java new file mode 100644 index 0000000000..38f2a26c94 --- /dev/null +++ b/src/com/android/settings/fuelgauge/batterytip/tips/HighUsageTip.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2018 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.batterytip.tips; + +import android.app.Dialog; +import android.content.Context; + +import com.android.settings.R; + +import java.util.List; + +/** + * Tip to show general summary about battery life + */ +public class HighUsageTip extends BatteryTip { + + private final CharSequence mScreenTimeText; + private final List mHighUsageAppList; + + public HighUsageTip(CharSequence screenTimeText, List appList) { + mShowDialog = true; + mScreenTimeText = screenTimeText; + mType = TipType.HIGH_DEVICE_USAGE; + mHighUsageAppList = appList; + mState = appList.isEmpty() ? StateType.INVISIBLE : StateType.NEW; + } + + @Override + public CharSequence getTitle(Context context) { + return context.getString(R.string.battery_tip_high_usage_title); + } + + @Override + public CharSequence getSummary(Context context) { + return context.getString(R.string.battery_tip_high_usage_summary, mScreenTimeText); + } + + @Override + public int getIconId() { + return R.drawable.ic_perm_device_information_red_24dp; + } + + @Override + public void updateState(BatteryTip tip) { + mState = tip.mState; + } + + @Override + public void action() { + // do nothing + } + + @Override + public Dialog buildDialog() { + //TODO(b/70570352): build the real dialog + return null; + } + + /** + * Class representing app with high screen usage + */ + public static class HighUsageApp implements Comparable { + public final String packageName; + public final long screenOnTimeMs; + + public HighUsageApp(String packageName, long screenOnTimeMs) { + this.packageName = packageName; + this.screenOnTimeMs = screenOnTimeMs; + } + + @Override + public int compareTo(HighUsageApp o) { + return Long.compare(screenOnTimeMs, o.screenOnTimeMs); + } + } +} diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java index 1393d5718b..844aca4c4a 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java @@ -20,7 +20,9 @@ import static android.os.BatteryStats.Uid.PROCESS_STATE_FOREGROUND; import static android.os.BatteryStats.Uid.PROCESS_STATE_FOREGROUND_SERVICE; import static android.os.BatteryStats.Uid.PROCESS_STATE_TOP; import static android.os.BatteryStats.Uid.PROCESS_STATE_TOP_SLEEPING; + import static com.google.common.truth.Truth.assertThat; + import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyLong; @@ -141,6 +143,7 @@ public class BatteryUtilsTest { private BatteryUtils mBatteryUtils; private FakeFeatureFactory mFeatureFactory; private PowerUsageFeatureProvider mProvider; + private List mUsageList; @Before public void setUp() { @@ -194,6 +197,12 @@ public class BatteryUtilsTest { mBatteryUtils.mPowerUsageFeatureProvider = mProvider; doReturn(0L).when(mBatteryUtils).getForegroundServiceTotalTimeUs( any(BatteryStats.Uid.class), anyLong()); + + mUsageList = new ArrayList<>(); + mUsageList.add(mNormalBatterySipper); + mUsageList.add(mScreenBatterySipper); + mUsageList.add(mCellBatterySipper); + doReturn(mUsageList).when(mBatteryStatsHelper).getUsageList(); } @Test @@ -468,4 +477,28 @@ public class BatteryUtilsTest { verify(mBatteryStatsHelper).refreshStats(BatteryStats.STATS_SINCE_CHARGED, mUserManager.getUserProfiles()); } + + @Test + public void testFindBatterySipperByType_findTypeScreen() { + BatterySipper sipper = mBatteryUtils.findBatterySipperByType(mUsageList, + BatterySipper.DrainType.SCREEN); + + assertThat(sipper).isSameAs(mScreenBatterySipper); + } + + @Test + public void testFindBatterySipperByType_findTypeApp() { + BatterySipper sipper = mBatteryUtils.findBatterySipperByType(mUsageList, + BatterySipper.DrainType.APP); + + assertThat(sipper).isSameAs(mNormalBatterySipper); + } + + @Test + public void testCalculateScreenUsageTime_returnCorrectTime() { + mScreenBatterySipper.usageTimeMs = TIME_EXPECTED_FOREGROUND; + + assertThat(mBatteryUtils.calculateScreenUsageTime(mBatteryStatsHelper)).isEqualTo( + TIME_EXPECTED_FOREGROUND); + } } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java index 272890939a..6fecf3ce92 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java @@ -248,34 +248,6 @@ public class PowerUsageSummaryTest { } @Test - public void testFindBatterySipperByType_findTypeScreen() { - BatterySipper sipper = mFragment.findBatterySipperByType(mUsageList, - BatterySipper.DrainType.SCREEN); - - assertThat(sipper).isSameAs(mScreenBatterySipper); - } - - @Test - public void testFindBatterySipperByType_findTypeApp() { - BatterySipper sipper = mFragment.findBatterySipperByType(mUsageList, - BatterySipper.DrainType.APP); - - assertThat(sipper).isSameAs(mNormalBatterySipper); - } - - @Test - public void testUpdateScreenPreference_showCorrectSummary() { - doReturn(mScreenBatterySipper).when(mFragment).findBatterySipperByType(any(), any()); - doReturn(mRealContext).when(mFragment).getContext(); - final CharSequence expectedSummary = Utils.formatElapsedTime(mRealContext, USAGE_TIME_MS, - false); - - mFragment.updateScreenPreference(); - - assertThat(mScreenUsagePref.getSubtitle()).isEqualTo(expectedSummary); - } - - @Test public void testUpdateLastFullChargePreference_showCorrectSummary() { doReturn(mRealContext).when(mFragment).getContext(); @@ -285,16 +257,6 @@ public class PowerUsageSummaryTest { } @Test - public void testUpdatePreference_usageListEmpty_shouldNotCrash() { - when(mBatteryHelper.getUsageList()).thenReturn(new ArrayList()); - doReturn(STUB_STRING).when(mFragment).getString(anyInt(), any()); - doReturn(mRealContext).when(mFragment).getContext(); - - // Should not crash when update - mFragment.updateScreenPreference(); - } - - @Test public void testNonIndexableKeys_MatchPreferenceKeys() { final Context context = RuntimeEnvironment.application; final List niks = PowerUsageSummary.SEARCH_INDEX_DATA_PROVIDER diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/HighUsageDetectorTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/HighUsageDetectorTest.java new file mode 100644 index 0000000000..2a719916fb --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/HighUsageDetectorTest.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2017 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.batterytip.detectors; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; + +import android.content.Context; +import android.os.BatteryStats; +import android.text.format.DateUtils; + +import com.android.internal.os.BatterySipper; +import com.android.internal.os.BatteryStatsHelper; +import com.android.settings.TestConfig; +import com.android.settings.fuelgauge.BatteryInfo; +import com.android.settings.fuelgauge.BatteryUtils; +import com.android.settings.fuelgauge.batterytip.BatteryTipPolicy; +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.util.ReflectionHelpers; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class HighUsageDetectorTest { + private Context mContext; + @Mock + private BatteryStatsHelper mBatteryStatsHelper; + @Mock + private BatteryUtils mBatteryUtils; + @Mock + private BatterySipper mBatterySipper; + + private BatteryTipPolicy mPolicy; + private HighUsageDetector mHighUsageDetector; + private List mUsageList; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mContext = RuntimeEnvironment.application; + mPolicy = spy(new BatteryTipPolicy(mContext)); + mHighUsageDetector = new HighUsageDetector(mContext, mPolicy, mBatteryStatsHelper); + mHighUsageDetector.mBatteryUtils = mBatteryUtils; + + mUsageList = new ArrayList<>(); + mUsageList.add(mBatterySipper); + } + + @Test + public void testDetect_disabledByPolicy_tipInvisible() { + ReflectionHelpers.setField(mPolicy, "highUsageEnabled", false); + + assertThat(mHighUsageDetector.detect().isVisible()).isFalse(); + } + + @Test + public void testDetect_containsHighUsageApp_tipVisible() { + doReturn(2 * DateUtils.HOUR_IN_MILLIS).when(mBatteryUtils).calculateScreenUsageTime( + mBatteryStatsHelper); + doReturn(mUsageList).when(mBatteryStatsHelper).getUsageList(); + doReturn(DateUtils.HOUR_IN_MILLIS).when(mBatteryUtils).getProcessTimeMs( + BatteryUtils.StatusType.FOREGROUND, mBatterySipper.uidObj, + BatteryStats.STATS_SINCE_CHARGED); + + assertThat(mHighUsageDetector.detect().isVisible()).isTrue(); + } +}