From ca102facf0aa11a8205c0bf66979d40878f8d508 Mon Sep 17 00:00:00 2001 From: jackqdyulei Date: Tue, 2 Jan 2018 14:19:06 -0800 Subject: [PATCH 1/2] 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 --- res/values/strings.xml | 15 +++ .../settings/fuelgauge/BatteryUtils.java | 25 +++++ .../settings/fuelgauge/PowerUsageSummary.java | 23 +---- .../batterytip/BatteryTipLoader.java | 3 + .../detectors/HighUsageDetector.java | 84 +++++++++++++++++ .../batterytip/tips/HighUsageTip.java | 90 ++++++++++++++++++ .../settings/fuelgauge/BatteryUtilsTest.java | 33 +++++++ .../fuelgauge/PowerUsageSummaryTest.java | 38 -------- .../detectors/HighUsageDetectorTest.java | 94 +++++++++++++++++++ 9 files changed, 346 insertions(+), 59 deletions(-) create mode 100644 src/com/android/settings/fuelgauge/batterytip/detectors/HighUsageDetector.java create mode 100644 src/com/android/settings/fuelgauge/batterytip/tips/HighUsageTip.java create mode 100644 tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/HighUsageDetectorTest.java diff --git a/res/values/strings.xml b/res/values/strings.xml index 79faf9b013e..033211790d9 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 68677fab2de..0952f1f095d 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 0315f032e66..507043f1557 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); @@ -393,26 +394,6 @@ public class PowerUsageSummary extends PowerUsageBase implements OnLongClickList return new AnomalyDetectionPolicy(getContext()); } - @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); diff --git a/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java b/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java index 9c3f48c0a6b..a1db57a409a 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 00000000000..5c2ecad1e01 --- /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 00000000000..38f2a26c943 --- /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 1393d5718b0..844aca4c4a5 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 272890939aa..6fecf3ce928 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java @@ -247,34 +247,6 @@ public class PowerUsageSummaryTest { assertThat(mFragment.mShowAllApps).isEqualTo(!isShowApps); } - @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(); @@ -284,16 +256,6 @@ public class PowerUsageSummaryTest { assertThat(mLastFullChargePref.getSubtitle()).isEqualTo("2 hr. ago"); } - @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; 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 00000000000..2a719916fb0 --- /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(); + } +} From fde637ff603ef3d28cacb221a749f3d4653760b8 Mon Sep 17 00:00:00 2001 From: jackqdyulei Date: Tue, 2 Jan 2018 14:51:01 -0800 Subject: [PATCH 2/2] Add BatteryTipDialogFragment. This DialogFragment handles all the tip related dialogs. This cl makes: 1. All the tips parcelable. 2. Add dialog for HighUsageTip. It also need adapter to populate app list in dialog. 3. Add and update tests Bug: 70570352 Test: RunSettingsRoboTests Change-Id: Ie4c986172cfc73d8746abc7457d966c8600c6145 --- res/layout/app_high_usage_item.xml | 48 ++++++++ res/layout/recycler_view.xml | 23 ++++ src/com/android/settings/Utils.java | 15 +++ .../fuelgauge/PowerUsageAnomalyDetails.java | 9 +- .../settings/fuelgauge/PowerUsageSummary.java | 2 +- .../batterytip/BatteryTipDialogFragment.java | 104 ++++++++++++++++++ .../BatteryTipPreferenceController.java | 15 ++- .../batterytip/HighUsageAdapter.java | 87 +++++++++++++++ .../fuelgauge/batterytip/HighUsageApp.java | 64 +++++++++++ .../detectors/HighUsageDetector.java | 8 +- .../fuelgauge/batterytip/tips/BatteryTip.java | 42 +++++-- .../batterytip/tips/HighUsageTip.java | 72 +++++++----- .../batterytip/tips/LowBatteryTip.java | 26 +++-- .../fuelgauge/batterytip/tips/SummaryTip.java | 25 +++-- .../src/com/android/settings/UtilsTest.java | 25 ++++- .../PowerUsageAnomalyDetailsTest.java | 13 --- .../BatteryTipPreferenceControllerTest.java | 2 +- .../batterytip/tips/BatteryTipTest.java | 39 ++++++- .../batterytip/tips/HighUsageTipTest.java | 74 +++++++++++++ 19 files changed, 601 insertions(+), 92 deletions(-) create mode 100755 res/layout/app_high_usage_item.xml create mode 100644 res/layout/recycler_view.xml create mode 100644 src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragment.java create mode 100644 src/com/android/settings/fuelgauge/batterytip/HighUsageAdapter.java create mode 100644 src/com/android/settings/fuelgauge/batterytip/HighUsageApp.java create mode 100644 tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/HighUsageTipTest.java diff --git a/res/layout/app_high_usage_item.xml b/res/layout/app_high_usage_item.xml new file mode 100755 index 00000000000..473315f2ade --- /dev/null +++ b/res/layout/app_high_usage_item.xml @@ -0,0 +1,48 @@ + + + + + + + + diff --git a/res/layout/recycler_view.xml b/res/layout/recycler_view.xml new file mode 100644 index 00000000000..a7dabe5d720 --- /dev/null +++ b/res/layout/recycler_view.xml @@ -0,0 +1,23 @@ + + + diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java index cd64799f804..ad951216781 100644 --- a/src/com/android/settings/Utils.java +++ b/src/com/android/settings/Utils.java @@ -94,6 +94,7 @@ import android.text.TextUtils; import android.text.format.DateUtils; import android.text.style.TtsSpan; import android.util.ArraySet; +import android.util.IconDrawableFactory; import android.util.Log; import android.util.TypedValue; import android.view.LayoutInflater; @@ -1382,4 +1383,18 @@ public final class Utils extends com.android.settingslib.Utils { } return new BitmapDrawable(null, bitmap); } + + /** + * Get the {@link Drawable} that represents the app icon + */ + public static Drawable getBadgedIcon(IconDrawableFactory iconDrawableFactory, + PackageManager packageManager, String packageName, int userId) { + try { + final ApplicationInfo appInfo = packageManager.getApplicationInfo(packageName, + PackageManager.GET_META_DATA); + return iconDrawableFactory.getBadgedIcon(appInfo, userId); + } catch (PackageManager.NameNotFoundException e) { + return packageManager.getDefaultActivityIcon(); + } + } } diff --git a/src/com/android/settings/fuelgauge/PowerUsageAnomalyDetails.java b/src/com/android/settings/fuelgauge/PowerUsageAnomalyDetails.java index 0d73511e84d..143733db2b8 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageAnomalyDetails.java +++ b/src/com/android/settings/fuelgauge/PowerUsageAnomalyDetails.java @@ -31,6 +31,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.nano.MetricsProto; import com.android.settings.R; import com.android.settings.SettingsActivity; +import com.android.settings.Utils; import com.android.settings.dashboard.DashboardFragment; import com.android.settings.fuelgauge.anomaly.Anomaly; import com.android.settings.fuelgauge.anomaly.AnomalyDialogFragment; @@ -151,12 +152,6 @@ public class PowerUsageAnomalyDetails extends DashboardFragment implements @VisibleForTesting Drawable getBadgedIcon(String packageName, int userId) { - try { - final ApplicationInfo appInfo = mPackageManager.getApplicationInfo(packageName, - PackageManager.GET_META_DATA); - return mIconDrawableFactory.getBadgedIcon(appInfo, userId); - } catch (PackageManager.NameNotFoundException e) { - return mPackageManager.getDefaultActivityIcon(); - } + return Utils.getBadgedIcon(mIconDrawableFactory, mPackageManager, packageName, userId); } } diff --git a/src/com/android/settings/fuelgauge/PowerUsageSummary.java b/src/com/android/settings/fuelgauge/PowerUsageSummary.java index 507043f1557..205ac0b15a8 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageSummary.java +++ b/src/com/android/settings/fuelgauge/PowerUsageSummary.java @@ -266,7 +266,7 @@ public class PowerUsageSummary extends PowerUsageBase implements OnLongClickList KEY_APP_LIST, lifecycle, activity, this); controllers.add(mBatteryAppListPreferenceController); mBatteryTipPreferenceController = new BatteryTipPreferenceController(context, - KEY_BATTERY_TIP, this); + KEY_BATTERY_TIP, this, this); controllers.add(mBatteryTipPreferenceController); controllers.add(new BatterySaverController(context, getLifecycle())); controllers.add(new BatteryPercentagePreferenceController(context)); diff --git a/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragment.java b/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragment.java new file mode 100644 index 00000000000..3e091b3b44c --- /dev/null +++ b/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragment.java @@ -0,0 +1,104 @@ +/* + * 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; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.annotation.VisibleForTesting; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; + +import com.android.settings.R; +import com.android.settings.core.instrumentation.InstrumentedDialogFragment; +import com.android.settings.fuelgauge.batterytip.BatteryTipPreferenceController.BatteryTipListener; +import com.android.settings.fuelgauge.batterytip.tips.BatteryTip; +import com.android.settings.fuelgauge.batterytip.tips.HighUsageTip; + +/** + * Dialog Fragment to show action dialog for each anomaly + */ +public class BatteryTipDialogFragment extends InstrumentedDialogFragment implements + DialogInterface.OnClickListener { + + private static final String ARG_BATTERY_TIP = "battery_tip"; + + @VisibleForTesting + BatteryTip mBatteryTip; + + public static BatteryTipDialogFragment newInstance(BatteryTip batteryTip) { + BatteryTipDialogFragment dialogFragment = new BatteryTipDialogFragment(); + + Bundle args = new Bundle(1); + args.putParcelable(ARG_BATTERY_TIP, batteryTip); + dialogFragment.setArguments(args); + + return dialogFragment; + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final Bundle bundle = getArguments(); + final Context context = getContext(); + + mBatteryTip = bundle.getParcelable(ARG_BATTERY_TIP); + + switch (mBatteryTip.getType()) { + case BatteryTip.TipType.SUMMARY: + case BatteryTip.TipType.LOW_BATTERY: + //TODO(b/70570352): add dialog + return null; + case BatteryTip.TipType.HIGH_DEVICE_USAGE: + final HighUsageTip highUsageTip = (HighUsageTip) mBatteryTip; + final RecyclerView view = (RecyclerView) LayoutInflater.from(context).inflate( + R.layout.recycler_view, + null); + view.setLayoutManager(new LinearLayoutManager(context)); + view.setAdapter(new HighUsageAdapter(context, + highUsageTip.getHighUsageAppList())); + + return new AlertDialog.Builder(context) + .setMessage(getString(R.string.battery_tip_dialog_message, + highUsageTip.getScreenTimeMs())) + .setView(view) + .setPositiveButton(android.R.string.ok, null) + .create(); + default: + throw new IllegalArgumentException("unknown type " + mBatteryTip.getType()); + } + } + + @Override + public int getMetricsCategory() { + //TODO(b/70570352): add correct metric id + return 0; + } + + @Override + public void onClick(DialogInterface dialog, int which) { + final BatteryTipListener lsn = (BatteryTipListener) getTargetFragment(); + if (lsn == null) { + return; + } + mBatteryTip.action(); + lsn.onBatteryTipHandled(mBatteryTip); + } + +} diff --git a/src/com/android/settings/fuelgauge/batterytip/BatteryTipPreferenceController.java b/src/com/android/settings/fuelgauge/batterytip/BatteryTipPreferenceController.java index f6114052cf6..9aa70c55264 100644 --- a/src/com/android/settings/fuelgauge/batterytip/BatteryTipPreferenceController.java +++ b/src/com/android/settings/fuelgauge/batterytip/BatteryTipPreferenceController.java @@ -18,6 +18,7 @@ package com.android.settings.fuelgauge.batterytip; import android.content.Context; import android.support.annotation.VisibleForTesting; +import android.support.v14.preference.PreferenceFragment; import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceGroup; import android.support.v7.preference.PreferenceScreen; @@ -34,6 +35,9 @@ import java.util.Map; * Controller in charge of the battery tip group */ public class BatteryTipPreferenceController extends BasePreferenceController { + private static final String TAG = "BatteryTipPreferenceController"; + private static final int REQUEST_ANOMALY_ACTION = 0; + private BatteryTipListener mBatteryTipListener; private List mBatteryTips; private Map mBatteryTipMap; @@ -41,16 +45,18 @@ public class BatteryTipPreferenceController extends BasePreferenceController { PreferenceGroup mPreferenceGroup; @VisibleForTesting Context mPrefContext; + PreferenceFragment mFragment; public BatteryTipPreferenceController(Context context, String preferenceKey) { - this(context, preferenceKey, null); + this(context, preferenceKey, null, null); } public BatteryTipPreferenceController(Context context, String preferenceKey, - BatteryTipListener batteryTipListener) { + PreferenceFragment fragment, BatteryTipListener batteryTipListener) { super(context, preferenceKey); mBatteryTipListener = batteryTipListener; mBatteryTipMap = new HashMap<>(); + mFragment = fragment; } @Override @@ -96,7 +102,10 @@ public class BatteryTipPreferenceController extends BasePreferenceController { final BatteryTip batteryTip = mBatteryTipMap.get(preference.getKey()); if (batteryTip != null) { if (batteryTip.shouldShowDialog()) { - // build and show the dialog + BatteryTipDialogFragment dialogFragment = BatteryTipDialogFragment.newInstance( + batteryTip); + dialogFragment.setTargetFragment(mFragment, REQUEST_ANOMALY_ACTION); + dialogFragment.show(mFragment.getFragmentManager(), TAG); } else { batteryTip.action(); if (mBatteryTipListener != null) { diff --git a/src/com/android/settings/fuelgauge/batterytip/HighUsageAdapter.java b/src/com/android/settings/fuelgauge/batterytip/HighUsageAdapter.java new file mode 100644 index 00000000000..8b743946648 --- /dev/null +++ b/src/com/android/settings/fuelgauge/batterytip/HighUsageAdapter.java @@ -0,0 +1,87 @@ +/* + * 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; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.UserHandle; +import android.support.v7.widget.RecyclerView; +import android.util.IconDrawableFactory; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import com.android.settings.R; +import com.android.settings.Utils; + +import java.util.List; + +/** + * Adapter for the high usage app list + */ +public class HighUsageAdapter extends RecyclerView.Adapter { + private final Context mContext; + private final IconDrawableFactory mIconDrawableFactory; + private final PackageManager mPackageManager; + private final List mHighUsageAppList; + + public static class ViewHolder extends RecyclerView.ViewHolder { + public View view; + public ImageView appIcon; + public TextView appName; + public TextView appTime; + + public ViewHolder(View v) { + super(v); + view = v; + appIcon = v.findViewById(R.id.app_icon); + appName = v.findViewById(R.id.app_name); + appTime = v.findViewById(R.id.app_screen_time); + } + } + + public HighUsageAdapter(Context context, List highUsageAppList) { + mContext = context; + mHighUsageAppList = highUsageAppList; + mIconDrawableFactory = IconDrawableFactory.newInstance(context); + mPackageManager = context.getPackageManager(); + } + + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + final View view = LayoutInflater.from(mContext).inflate(R.layout.app_high_usage_item, + parent, false); + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(ViewHolder holder, int position) { + final HighUsageApp app = mHighUsageAppList.get(position); + holder.appIcon.setImageDrawable( + Utils.getBadgedIcon(mIconDrawableFactory, mPackageManager, app.packageName, + UserHandle.myUserId())); + holder.appName.setText(Utils.getApplicationLabel(mContext, app.packageName)); + holder.appTime.setText(Utils.formatElapsedTime(mContext, app.screenOnTimeMs, false)); + } + + @Override + public int getItemCount() { + return mHighUsageAppList.size(); + } +} \ No newline at end of file diff --git a/src/com/android/settings/fuelgauge/batterytip/HighUsageApp.java b/src/com/android/settings/fuelgauge/batterytip/HighUsageApp.java new file mode 100644 index 00000000000..f75ecf0e322 --- /dev/null +++ b/src/com/android/settings/fuelgauge/batterytip/HighUsageApp.java @@ -0,0 +1,64 @@ +/* + * 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; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Class representing app with high screen usage + */ +public class HighUsageApp implements Comparable, Parcelable { + public final String packageName; + public final long screenOnTimeMs; + + public HighUsageApp(String packageName, long screenOnTimeMs) { + this.packageName = packageName; + this.screenOnTimeMs = screenOnTimeMs; + } + + private HighUsageApp(Parcel in) { + packageName = in.readString(); + screenOnTimeMs = in.readLong(); + } + + @Override + public int compareTo(HighUsageApp o) { + return Long.compare(screenOnTimeMs, o.screenOnTimeMs); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(packageName); + dest.writeLong(screenOnTimeMs); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public HighUsageApp createFromParcel(Parcel in) { + return new HighUsageApp(in); + } + + public HighUsageApp[] newArray(int size) { + return new HighUsageApp[size]; + } + }; +} \ No newline at end of file diff --git a/src/com/android/settings/fuelgauge/batterytip/detectors/HighUsageDetector.java b/src/com/android/settings/fuelgauge/batterytip/detectors/HighUsageDetector.java index 5c2ecad1e01..237f430c575 100644 --- a/src/com/android/settings/fuelgauge/batterytip/detectors/HighUsageDetector.java +++ b/src/com/android/settings/fuelgauge/batterytip/detectors/HighUsageDetector.java @@ -26,6 +26,7 @@ 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.HighUsageApp; import com.android.settings.fuelgauge.batterytip.tips.BatteryTip; import com.android.settings.fuelgauge.batterytip.tips.HighUsageTip; import com.android.settings.fuelgauge.batterytip.tips.SummaryTip; @@ -41,7 +42,7 @@ import java.util.List; public class HighUsageDetector implements BatteryTipDetector { private BatteryTipPolicy mPolicy; private BatteryStatsHelper mBatteryStatsHelper; - private List mHighUsageAppList; + private List mHighUsageAppList; private Context mContext; @VisibleForTesting BatteryUtils mBatteryUtils; @@ -67,7 +68,7 @@ public class HighUsageDetector implements BatteryTipDetector { final long foregroundTimeMs = mBatteryUtils.getProcessTimeMs( BatteryUtils.StatusType.FOREGROUND, batterySipper.uidObj, BatteryStats.STATS_SINCE_CHARGED); - mHighUsageAppList.add(new HighUsageTip.HighUsageApp( + mHighUsageAppList.add(new HighUsageApp( mBatteryUtils.getPackageName(batterySipper.getUid()), foregroundTimeMs)); } @@ -78,7 +79,6 @@ public class HighUsageDetector implements BatteryTipDetector { Collections.sort(mHighUsageAppList, Collections.reverseOrder()); } - return new HighUsageTip(Utils.formatElapsedTime(mContext, screenUsageTimeMs, false), - mHighUsageAppList); + return new HighUsageTip(screenUsageTimeMs, mHighUsageAppList); } } diff --git a/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTip.java b/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTip.java index 17e395ef185..eadd0e1a811 100644 --- a/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTip.java +++ b/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTip.java @@ -16,8 +16,9 @@ package com.android.settings.fuelgauge.batterytip.tips; -import android.app.Dialog; import android.content.Context; +import android.os.Parcel; +import android.os.Parcelable; import android.support.annotation.IdRes; import android.support.annotation.IntDef; import android.support.v7.preference.Preference; @@ -31,7 +32,7 @@ import java.lang.annotation.RetentionPolicy; * Each {@link BatteryTip} contains basic data(e.g. title, summary, icon) as well as the * pre-defined action(e.g. turn on battery saver) */ -public abstract class BatteryTip implements Comparable { +public abstract class BatteryTip implements Comparable, Parcelable { @Retention(RetentionPolicy.SOURCE) @IntDef({StateType.NEW, StateType.HANDLED, @@ -62,12 +63,34 @@ public abstract class BatteryTip implements Comparable { private static final String KEY_PREFIX = "key_battery_tip"; - @TipType protected int mType; - @StateType protected int mState; protected boolean mShowDialog; + BatteryTip(Parcel in) { + mType = in.readInt(); + mState = in.readInt(); + mShowDialog = in.readBoolean(); + } + + BatteryTip(int type, int state, boolean showDialog) { + mType = type; + mState = state; + mShowDialog = showDialog; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mType); + dest.writeInt(mState); + dest.writeBoolean(mShowDialog); + } + public abstract CharSequence getTitle(Context context); public abstract CharSequence getSummary(Context context); @@ -77,6 +100,7 @@ public abstract class BatteryTip implements Comparable { /** * Update the current {@link #mState} using the new {@code tip}. + * * @param tip used to update */ public abstract void updateState(BatteryTip tip); @@ -86,12 +110,6 @@ public abstract class BatteryTip implements Comparable { */ public abstract void action(); - /** - * Build the dialog to display either the info about {@link BatteryTip} or confirmation - * about the action. - */ - public abstract Dialog buildDialog(); - public Preference buildPreference(Context context) { Preference preference = new Preference(context); @@ -110,6 +128,10 @@ public abstract class BatteryTip implements Comparable { return KEY_PREFIX + mType; } + public int getType() { + return mType; + } + @StateType public int getState() { return mState; diff --git a/src/com/android/settings/fuelgauge/batterytip/tips/HighUsageTip.java b/src/com/android/settings/fuelgauge/batterytip/tips/HighUsageTip.java index 38f2a26c943..001a48eedc3 100644 --- a/src/com/android/settings/fuelgauge/batterytip/tips/HighUsageTip.java +++ b/src/com/android/settings/fuelgauge/batterytip/tips/HighUsageTip.java @@ -16,10 +16,14 @@ package com.android.settings.fuelgauge.batterytip.tips; -import android.app.Dialog; import android.content.Context; +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.VisibleForTesting; import com.android.settings.R; +import com.android.settings.Utils; +import com.android.settings.fuelgauge.batterytip.HighUsageApp; import java.util.List; @@ -28,15 +32,29 @@ import java.util.List; */ public class HighUsageTip extends BatteryTip { - private final CharSequence mScreenTimeText; - private final List mHighUsageAppList; + private final long mScreenTimeMs; + @VisibleForTesting + final List mHighUsageAppList; - public HighUsageTip(CharSequence screenTimeText, List appList) { - mShowDialog = true; - mScreenTimeText = screenTimeText; - mType = TipType.HIGH_DEVICE_USAGE; + public HighUsageTip(long screenTimeMs, List appList) { + super(TipType.HIGH_DEVICE_USAGE, appList.isEmpty() ? StateType.INVISIBLE : StateType.NEW, + true /* showDialog */); + mScreenTimeMs = screenTimeMs; mHighUsageAppList = appList; - mState = appList.isEmpty() ? StateType.INVISIBLE : StateType.NEW; + } + + @VisibleForTesting + HighUsageTip(Parcel in) { + super(in); + mScreenTimeMs = in.readLong(); + mHighUsageAppList = in.createTypedArrayList(HighUsageApp.CREATOR); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeLong(mScreenTimeMs); + dest.writeTypedList(mHighUsageAppList); } @Override @@ -46,7 +64,8 @@ public class HighUsageTip extends BatteryTip { @Override public CharSequence getSummary(Context context) { - return context.getString(R.string.battery_tip_high_usage_summary, mScreenTimeText); + return context.getString(R.string.battery_tip_high_usage_summary, + Utils.formatElapsedTime(context, mScreenTimeMs, false)); } @Override @@ -64,27 +83,22 @@ public class HighUsageTip extends BatteryTip { // do nothing } - @Override - public Dialog buildDialog() { - //TODO(b/70570352): build the real dialog - return null; + public long getScreenTimeMs() { + return mScreenTimeMs; } - /** - * 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); - } + public List getHighUsageAppList() { + return mHighUsageAppList; } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public BatteryTip createFromParcel(Parcel in) { + return new HighUsageTip(in); + } + + public BatteryTip[] newArray(int size) { + return new HighUsageTip[size]; + } + }; + } diff --git a/src/com/android/settings/fuelgauge/batterytip/tips/LowBatteryTip.java b/src/com/android/settings/fuelgauge/batterytip/tips/LowBatteryTip.java index 8605fbb6319..4a207e0b126 100644 --- a/src/com/android/settings/fuelgauge/batterytip/tips/LowBatteryTip.java +++ b/src/com/android/settings/fuelgauge/batterytip/tips/LowBatteryTip.java @@ -16,8 +16,9 @@ package com.android.settings.fuelgauge.batterytip.tips; -import android.app.Dialog; import android.content.Context; +import android.os.Parcel; +import android.os.Parcelable; import com.android.settings.R; @@ -27,9 +28,11 @@ import com.android.settings.R; public class LowBatteryTip extends BatteryTip { public LowBatteryTip(@StateType int state) { - mShowDialog = false; - mState = state; - mType = TipType.LOW_BATTERY; + super(TipType.LOW_BATTERY, state, false /* showDialog */); + } + + private LowBatteryTip(Parcel in) { + super(in); } @Override @@ -57,9 +60,14 @@ public class LowBatteryTip extends BatteryTip { // do nothing } - @Override - public Dialog buildDialog() { - //TODO(b/70570352): create the dialog for low battery tip and add test - return null; - } + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public BatteryTip createFromParcel(Parcel in) { + return new LowBatteryTip(in); + } + + public BatteryTip[] newArray(int size) { + return new LowBatteryTip[size]; + } + }; + } diff --git a/src/com/android/settings/fuelgauge/batterytip/tips/SummaryTip.java b/src/com/android/settings/fuelgauge/batterytip/tips/SummaryTip.java index 2a2deabfa8f..51019a8d35d 100644 --- a/src/com/android/settings/fuelgauge/batterytip/tips/SummaryTip.java +++ b/src/com/android/settings/fuelgauge/batterytip/tips/SummaryTip.java @@ -16,8 +16,9 @@ package com.android.settings.fuelgauge.batterytip.tips; -import android.app.Dialog; import android.content.Context; +import android.os.Parcel; +import android.os.Parcelable; import com.android.settings.R; @@ -27,9 +28,11 @@ import com.android.settings.R; public class SummaryTip extends BatteryTip { public SummaryTip(@StateType int state) { - mShowDialog = false; - mState = state; - mType = TipType.SUMMARY; + super(TipType.SUMMARY, state, false /* showDialog */); + } + + private SummaryTip(Parcel in) { + super(in); } @Override @@ -57,9 +60,13 @@ public class SummaryTip extends BatteryTip { // do nothing } - @Override - public Dialog buildDialog() { - //TODO(b/70570352): create the dialog for summary tip and add test - return null; - } + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public BatteryTip createFromParcel(Parcel in) { + return new SummaryTip(in); + } + + public BatteryTip[] newArray(int size) { + return new SummaryTip[size]; + } + }; } diff --git a/tests/robotests/src/com/android/settings/UtilsTest.java b/tests/robotests/src/com/android/settings/UtilsTest.java index f8134578117..fb571bb5cf3 100644 --- a/tests/robotests/src/com/android/settings/UtilsTest.java +++ b/tests/robotests/src/com/android/settings/UtilsTest.java @@ -4,13 +4,16 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.RETURNS_DEEP_STUBS; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.ComponentName; import android.content.Context; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.net.ConnectivityManager; import android.net.LinkAddress; @@ -25,6 +28,7 @@ import android.os.storage.VolumeInfo; import android.text.SpannableStringBuilder; import android.text.format.DateUtils; import android.text.style.TtsSpan; +import android.util.IconDrawableFactory; import android.widget.EditText; import android.widget.TextView; @@ -46,8 +50,8 @@ import java.util.List; @RunWith(SettingsRobolectricTestRunner.class) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) public class UtilsTest { - private static final String PACKAGE_NAME = "com.android.app"; + private static final int USER_ID = 1; @Mock private WifiManager wifiManager; @@ -59,6 +63,12 @@ public class UtilsTest { private DevicePolicyManagerWrapper mDevicePolicyManager; @Mock private UserManager mUserManager; + @Mock + private PackageManager mPackageManager; + @Mock + private IconDrawableFactory mIconDrawableFactory; + @Mock + private ApplicationInfo mApplicationInfo; private Context mContext; @Before @@ -332,4 +342,17 @@ public class UtilsTest { assertThat(editText.getSelectionEnd()).isEqualTo(length); } + + @Test + public void testGetBadgedIcon_usePackageNameAndUserId() throws + PackageManager.NameNotFoundException { + doReturn(mApplicationInfo).when(mPackageManager).getApplicationInfo(PACKAGE_NAME, + PackageManager.GET_META_DATA); + + Utils.getBadgedIcon(mIconDrawableFactory, mPackageManager, PACKAGE_NAME, USER_ID); + + // Verify that it uses the correct user id + verify(mIconDrawableFactory).getBadgedIcon(mApplicationInfo, USER_ID); + } + } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageAnomalyDetailsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageAnomalyDetailsTest.java index c992d0aef63..8aa0659671d 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageAnomalyDetailsTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageAnomalyDetailsTest.java @@ -67,7 +67,6 @@ public class PowerUsageAnomalyDetailsTest { private static final String PACKAGE_NAME_1 = "com.android.app1"; private static final String PACKAGE_NAME_2 = "com.android.app2"; private static final String PACKAGE_NAME_3 = "com.android.app3"; - private static final int USER_ID = 1; @Mock private SettingsActivity mSettingsActivity; @@ -198,16 +197,4 @@ public class PowerUsageAnomalyDetailsTest { assertThat(mBundle.getParcelableArrayList( PowerUsageAnomalyDetails.EXTRA_ANOMALY_LIST)).isEqualTo(mAnomalyList); } - - @Test - public void testGetBadgedIcon_usePackageNameAndUserId() throws - PackageManager.NameNotFoundException { - doReturn(mApplicationInfo).when(mPackageManager).getApplicationInfo(PACKAGE_NAME_1, - PackageManager.GET_META_DATA); - - mFragment.getBadgedIcon(PACKAGE_NAME_1, USER_ID); - - // Verify that it uses the correct user id - verify(mIconDrawableFactory).getBadgedIcon(mApplicationInfo, USER_ID); - } } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipPreferenceControllerTest.java index 944587fb21e..9f0c61fc9c8 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipPreferenceControllerTest.java @@ -85,7 +85,7 @@ public class BatteryTipPreferenceControllerTest { mNewBatteryTips.add(new SummaryTip(BatteryTip.StateType.INVISIBLE)); mBatteryTipPreferenceController = new BatteryTipPreferenceController(mContext, KEY_PREF, - mBatteryTipListener); + null, mBatteryTipListener); mBatteryTipPreferenceController.mPreferenceGroup = mPreferenceGroup; mBatteryTipPreferenceController.mPrefContext = mContext; } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTipTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTipTest.java index eec8e760914..1c6c8683c04 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTipTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTipTest.java @@ -19,6 +19,8 @@ import static com.google.common.truth.Truth.assertThat; import android.app.Dialog; import android.content.Context; +import android.os.Parcel; +import android.os.Parcelable; import android.support.annotation.IdRes; import android.support.v7.preference.Preference; @@ -58,10 +60,32 @@ public class BatteryTipTest { assertThat(preference.getIcon()).isEqualTo(mContext.getDrawable(ICON_ID)); } + @Test + public void testParcelable() { + final BatteryTip batteryTip = new TestBatteryTip(); + + Parcel parcel = Parcel.obtain(); + batteryTip.writeToParcel(parcel, batteryTip.describeContents()); + parcel.setDataPosition(0); + + final BatteryTip parcelTip = new TestBatteryTip(parcel); + + assertThat(parcelTip.getTitle(mContext)).isEqualTo(TITLE); + assertThat(parcelTip.getSummary(mContext)).isEqualTo(SUMMARY); + assertThat(parcelTip.getIconId()).isEqualTo(ICON_ID); + } + /** * Used to test the non abstract methods in {@link TestBatteryTip} */ - public class TestBatteryTip extends BatteryTip { + public static class TestBatteryTip extends BatteryTip { + TestBatteryTip() { + super(TipType.SUMMARY, StateType.NEW, true); + } + + TestBatteryTip(Parcel in) { + super(in); + } @Override public String getTitle(Context context) { @@ -88,10 +112,15 @@ public class BatteryTipTest { // do nothing } - @Override - public Dialog buildDialog() { - return null; - } + public final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public BatteryTip createFromParcel(Parcel in) { + return new TestBatteryTip(in); + } + + public BatteryTip[] newArray(int size) { + return new TestBatteryTip[size]; + } + }; } } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/HighUsageTipTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/HighUsageTipTest.java new file mode 100644 index 00000000000..e2f8a26cc7e --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/HighUsageTipTest.java @@ -0,0 +1,74 @@ +/* + * 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 static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.os.Parcel; +import android.text.format.DateUtils; + +import com.android.settings.TestConfig; +import com.android.settings.fuelgauge.batterytip.HighUsageApp; +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class HighUsageTipTest { + private static final String PACKAGE_NAME = "com.android.app"; + private static final long SCREEN_TIME = 30 * DateUtils.MINUTE_IN_MILLIS; + + private Context mContext; + private HighUsageTip mBatteryTip; + private List mUsageAppList; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + + mUsageAppList = new ArrayList<>(); + mUsageAppList.add(new HighUsageApp(PACKAGE_NAME, SCREEN_TIME)); + mBatteryTip = new HighUsageTip(SCREEN_TIME, mUsageAppList); + } + + @Test + public void testParcelable() { + + Parcel parcel = Parcel.obtain(); + mBatteryTip.writeToParcel(parcel, mBatteryTip.describeContents()); + parcel.setDataPosition(0); + + final HighUsageTip parcelTip = new HighUsageTip(parcel); + + assertThat(parcelTip.getTitle(mContext)).isEqualTo("Phone used heavily"); + assertThat(parcelTip.getType()).isEqualTo(BatteryTip.TipType.HIGH_DEVICE_USAGE); + assertThat(parcelTip.getState()).isEqualTo(BatteryTip.StateType.NEW); + assertThat(parcelTip.getScreenTimeMs()).isEqualTo(SCREEN_TIME); + assertThat(parcelTip.mHighUsageAppList.size()).isEqualTo(1); + final HighUsageApp app = parcelTip.mHighUsageAppList.get(0); + assertThat(app.packageName).isEqualTo(PACKAGE_NAME); + assertThat(app.screenOnTimeMs).isEqualTo(SCREEN_TIME); + } +}