OSDN Git Service

Add high usage battery tip
authorjackqdyulei <jackqdyulei@google.com>
Tue, 2 Jan 2018 22:19:06 +0000 (14:19 -0800)
committerjackqdyulei <jackqdyulei@google.com>
Tue, 9 Jan 2018 23:00:41 +0000 (15:00 -0800)
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
src/com/android/settings/fuelgauge/BatteryUtils.java
src/com/android/settings/fuelgauge/PowerUsageSummary.java
src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java
src/com/android/settings/fuelgauge/batterytip/detectors/HighUsageDetector.java [new file with mode: 0644]
src/com/android/settings/fuelgauge/batterytip/tips/HighUsageTip.java [new file with mode: 0644]
tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java
tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java
tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/HighUsageDetectorTest.java [new file with mode: 0644]

index 79faf9b..0332117 100644 (file)
     <string name="battery_tip_low_battery_title">Low battery capacity</string>
     <!-- Summary for the low battery tip [CHAR LIMIT=NONE] -->
     <string name="battery_tip_low_battery_summary">Battery can\'t provide good battery life</string>
+    <!-- Title for the battery high usage tip [CHAR LIMIT=NONE] -->
+    <string name="battery_tip_high_usage_title" product="default">Phone used heavily</string>
+    <!-- Title for the battery high usage tip [CHAR LIMIT=NONE] -->
+    <string name="battery_tip_high_usage_title" product="tablet">Tablet used heavily</string>
+    <!-- Title for the battery high usage tip [CHAR LIMIT=NONE] -->
+    <string name="battery_tip_high_usage_title" product="device">Device used heavily</string>
+    <!-- Summary for the battery high usage tip, which presents how many hours the device been used since last full charge [CHAR LIMIT=NONE] -->
+    <string name="battery_tip_high_usage_summary">About <xliff:g id="hour">%1$s</xliff:g> used since last full charge</string>
+    <!-- Message for battery tip dialog to show the status about the battery [CHAR LIMIT=NONE] -->
+    <string name="battery_tip_dialog_message" product="default">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 <xliff:g id="hour">%1$s</xliff:g> since last full charge.\n\n Total usage:</string>
+    <!-- Message for battery tip dialog to show the status about the battery [CHAR LIMIT=NONE] -->
+    <string name="battery_tip_dialog_message" product="tablet">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 <xliff:g id="hour">%1$s</xliff:g> since last full charge.\n\n Total usage:</string>
+    <!-- Message for battery tip dialog to show the status about the battery [CHAR LIMIT=NONE] -->
+    <string name="battery_tip_dialog_message" product="device">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 <xliff:g id="hour">%1$s</xliff:g> since last full charge.\n\n Total usage:</string>
+
     <!-- Title for the smart battery manager preference [CHAR LIMIT=NONE] -->
     <string name="smart_battery_manager_title">Smart battery manager</string>
     <!-- Title for the smart battery toggle [CHAR LIMIT=NONE] -->
index 68677fa..0952f1f 100644 (file)
@@ -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<BatterySipper> 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;
     }
index 0315f03..507043f 100644 (file)
@@ -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<BatterySipper> 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);
index 9c3f48c..a1db57a 100644 (file)
@@ -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<List<BatteryTip>> {
         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 (file)
index 0000000..5c2ecad
--- /dev/null
@@ -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<HighUsageTip.HighUsageApp> 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<BatterySipper> 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 (file)
index 0000000..38f2a26
--- /dev/null
@@ -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<HighUsageApp> mHighUsageAppList;
+
+    public HighUsageTip(CharSequence screenTimeText, List<HighUsageApp> 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<HighUsageApp> {
+        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);
+        }
+    }
+}
index 1393d57..844aca4 100644 (file)
@@ -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<BatterySipper> 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);
+    }
 }
index 2728909..6fecf3c 100644 (file)
@@ -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<BatterySipper>());
-        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<String> 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 (file)
index 0000000..2a71991
--- /dev/null
@@ -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<BatterySipper> 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();
+    }
+}