diff --git a/res/drawable/ic_perm_device_information_red_24dp.xml b/res/drawable/ic_perm_device_information_red_24dp.xml
new file mode 100644
index 00000000000..135e212411a
--- /dev/null
+++ b/res/drawable/ic_perm_device_information_red_24dp.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 65f73c81bfc..c94eb1ba3b4 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -4742,6 +4742,10 @@
Battery is in good shape
Apps are behaving normally
+
+ Low battery capacity
+
+ Battery can\'t provide good battery life
Stop app?
diff --git a/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java b/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java
index b8cb6c48b15..9c3f48c0a6b 100644
--- a/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java
+++ b/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java
@@ -17,13 +17,21 @@
package com.android.settings.fuelgauge.batterytip;
import android.content.Context;
+import android.support.annotation.VisibleForTesting;
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.LowBatteryDetector;
+import com.android.settings.fuelgauge.batterytip.detectors.SummaryDetector;
import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
+import com.android.settings.fuelgauge.batterytip.tips.LowBatteryTip;
import com.android.settings.fuelgauge.batterytip.tips.SummaryTip;
import com.android.settingslib.utils.AsyncLoader;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
/**
@@ -36,18 +44,31 @@ public class BatteryTipLoader extends AsyncLoader> {
private static final boolean USE_FAKE_DATA = false;
private BatteryStatsHelper mBatteryStatsHelper;
+ private BatteryUtils mBatteryUtils;
+ @VisibleForTesting
+ int mVisibleTips;
public BatteryTipLoader(Context context, BatteryStatsHelper batteryStatsHelper) {
super(context);
mBatteryStatsHelper = batteryStatsHelper;
+ mBatteryUtils = BatteryUtils.getInstance(context);
}
@Override
public List loadInBackground() {
- List tips = new ArrayList<>();
+ if (USE_FAKE_DATA) {
+ return getFakeData();
+ }
+ final List tips = new ArrayList<>();
+ final BatteryTipPolicy policy = new BatteryTipPolicy(getContext());
+ final BatteryInfo batteryInfo = mBatteryUtils.getBatteryInfo(mBatteryStatsHelper, TAG);
+ mVisibleTips = 0;
- //TODO(b/70570352): add battery tip detectors
- tips.add(new SummaryTip(BatteryTip.StateType.NEW));
+ addBatteryTipFromDetector(tips, new LowBatteryDetector(policy, batteryInfo));
+ // Add summary detector at last since it need other detectors to update the mVisibleTips
+ addBatteryTipFromDetector(tips, new SummaryDetector(policy, mVisibleTips));
+
+ Collections.sort(tips);
return tips;
}
@@ -55,4 +76,20 @@ public class BatteryTipLoader extends AsyncLoader> {
protected void onDiscardResult(List result) {
}
+ private List getFakeData() {
+ final List tips = new ArrayList<>();
+ tips.add(new SummaryTip(BatteryTip.StateType.NEW));
+ tips.add(new LowBatteryTip(BatteryTip.StateType.NEW));
+
+ return tips;
+ }
+
+ @VisibleForTesting
+ void addBatteryTipFromDetector(final List tips,
+ final BatteryTipDetector detector) {
+ final BatteryTip batteryTip = detector.detect();
+ mVisibleTips += batteryTip.isVisible() ? 1 : 0;
+ tips.add(batteryTip);
+ }
+
}
diff --git a/src/com/android/settings/fuelgauge/batterytip/detectors/BatteryTipDetector.java b/src/com/android/settings/fuelgauge/batterytip/detectors/BatteryTipDetector.java
new file mode 100644
index 00000000000..cb38e40faff
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batterytip/detectors/BatteryTipDetector.java
@@ -0,0 +1,28 @@
+/*
+ * 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 com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
+
+public interface BatteryTipDetector {
+ /**
+ * Detect and update the status of {@link BatteryTip}
+ *
+ * @return a not null {@link BatteryTip}
+ */
+ BatteryTip detect();
+}
diff --git a/src/com/android/settings/fuelgauge/batterytip/detectors/LowBatteryDetector.java b/src/com/android/settings/fuelgauge/batterytip/detectors/LowBatteryDetector.java
new file mode 100644
index 00000000000..2a6302efbc5
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batterytip/detectors/LowBatteryDetector.java
@@ -0,0 +1,46 @@
+/*
+ * 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 android.text.format.DateUtils;
+
+import com.android.settings.fuelgauge.BatteryInfo;
+import com.android.settings.fuelgauge.batterytip.BatteryTipPolicy;
+import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
+import com.android.settings.fuelgauge.batterytip.tips.LowBatteryTip;
+
+/**
+ * Detect whether the battery is too low
+ */
+public class LowBatteryDetector implements BatteryTipDetector {
+ private BatteryInfo mBatteryInfo;
+ private BatteryTipPolicy mPolicy;
+
+ public LowBatteryDetector(BatteryTipPolicy policy, BatteryInfo batteryInfo) {
+ mPolicy = policy;
+ mBatteryInfo = batteryInfo;
+ }
+
+ @Override
+ public BatteryTip detect() {
+ // Show it if battery life is less than mPolicy.lowBatteryHour
+ final boolean isShown = mPolicy.lowBatteryEnabled && mBatteryInfo.discharging
+ && mBatteryInfo.remainingTimeUs < mPolicy.lowBatteryHour * DateUtils.HOUR_IN_MILLIS;
+ return new LowBatteryTip(
+ isShown ? BatteryTip.StateType.NEW : BatteryTip.StateType.INVISIBLE);
+ }
+}
diff --git a/src/com/android/settings/fuelgauge/batterytip/detectors/SummaryDetector.java b/src/com/android/settings/fuelgauge/batterytip/detectors/SummaryDetector.java
new file mode 100644
index 00000000000..8c1783bd662
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batterytip/detectors/SummaryDetector.java
@@ -0,0 +1,44 @@
+/*
+ * 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 com.android.settings.fuelgauge.batterytip.BatteryTipPolicy;
+import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
+import com.android.settings.fuelgauge.batterytip.tips.SummaryTip;
+
+/**
+ * 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 SummaryDetector implements BatteryTipDetector {
+ private BatteryTipPolicy mPolicy;
+ private int mVisibleTips;
+
+ public SummaryDetector(BatteryTipPolicy policy, int visibleTips) {
+ mPolicy = policy;
+ mVisibleTips = visibleTips;
+ }
+
+ @Override
+ public BatteryTip detect() {
+ // Show it if there is no other tips shown
+ final int state = mPolicy.summaryEnabled && mVisibleTips == 0
+ ? BatteryTip.StateType.NEW
+ : BatteryTip.StateType.INVISIBLE;
+ return new SummaryTip(state);
+ }
+}
diff --git a/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTip.java b/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTip.java
index e6332724874..17e395ef185 100644
--- a/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTip.java
+++ b/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTip.java
@@ -31,7 +31,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 {
+public abstract class BatteryTip implements Comparable {
@Retention(RetentionPolicy.SOURCE)
@IntDef({StateType.NEW,
StateType.HANDLED,
@@ -114,4 +114,13 @@ public abstract class BatteryTip {
public int getState() {
return mState;
}
+
+ public boolean isVisible() {
+ return mState != StateType.INVISIBLE;
+ }
+
+ @Override
+ public int compareTo(BatteryTip o) {
+ return mType - o.mType;
+ }
}
diff --git a/src/com/android/settings/fuelgauge/batterytip/tips/LowBatteryTip.java b/src/com/android/settings/fuelgauge/batterytip/tips/LowBatteryTip.java
new file mode 100644
index 00000000000..8605fbb6319
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batterytip/tips/LowBatteryTip.java
@@ -0,0 +1,65 @@
+/*
+ * 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.tips;
+
+import android.app.Dialog;
+import android.content.Context;
+
+import com.android.settings.R;
+
+/**
+ * Tip to show current battery life is short
+ */
+public class LowBatteryTip extends BatteryTip {
+
+ public LowBatteryTip(@StateType int state) {
+ mShowDialog = false;
+ mState = state;
+ mType = TipType.LOW_BATTERY;
+ }
+
+ @Override
+ public CharSequence getTitle(Context context) {
+ return context.getString(R.string.battery_tip_low_battery_title);
+ }
+
+ @Override
+ public CharSequence getSummary(Context context) {
+ return context.getString(R.string.battery_tip_low_battery_summary);
+ }
+
+ @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): create the dialog for low battery tip and add test
+ return null;
+ }
+}
diff --git a/src/com/android/settings/fuelgauge/batterytip/tips/SummaryTip.java b/src/com/android/settings/fuelgauge/batterytip/tips/SummaryTip.java
index ab2a6c3f197..2a2deabfa8f 100644
--- a/src/com/android/settings/fuelgauge/batterytip/tips/SummaryTip.java
+++ b/src/com/android/settings/fuelgauge/batterytip/tips/SummaryTip.java
@@ -29,6 +29,7 @@ public class SummaryTip extends BatteryTip {
public SummaryTip(@StateType int state) {
mShowDialog = false;
mState = state;
+ mType = TipType.SUMMARY;
}
@Override
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoaderTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoaderTest.java
new file mode 100644
index 00000000000..e4e8eef5721
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoaderTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+
+import android.content.Context;
+
+import com.android.internal.os.BatteryStatsHelper;
+import com.android.settings.TestConfig;
+import com.android.settings.fuelgauge.batterytip.detectors.BatteryTipDetector;
+import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
+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 java.util.ArrayList;
+import java.util.List;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class BatteryTipLoaderTest {
+ @Mock
+ private BatteryStatsHelper mBatteryStatsHelper;
+ @Mock
+ private BatteryTipDetector mBatteryTipDetector;
+ @Mock
+ private BatteryTip mBatteryTip;
+ private Context mContext;
+ private BatteryTipLoader mBatteryTipLoader;
+ private List mBatteryTips;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mContext = RuntimeEnvironment.application;
+ doReturn(mBatteryTip).when(mBatteryTipDetector).detect();
+ mBatteryTipLoader = new BatteryTipLoader(mContext, mBatteryStatsHelper);
+ mBatteryTips = new ArrayList<>();
+ }
+
+ @Test
+ public void testAddBatteryTipFromDetector_tipVisible_addAndUpdateCount() {
+ doReturn(true).when(mBatteryTip).isVisible();
+ mBatteryTipLoader.mVisibleTips = 0;
+
+ mBatteryTipLoader.addBatteryTipFromDetector(mBatteryTips, mBatteryTipDetector);
+
+ assertThat(mBatteryTips.contains(mBatteryTip)).isTrue();
+ assertThat(mBatteryTipLoader.mVisibleTips).isEqualTo(1);
+ }
+
+ @Test
+ public void testAddBatteryTipFromDetector_tipInvisible_doNotAddCount() {
+ doReturn(false).when(mBatteryTip).isVisible();
+ mBatteryTipLoader.mVisibleTips = 0;
+
+ mBatteryTipLoader.addBatteryTipFromDetector(mBatteryTips, mBatteryTipDetector);
+
+ assertThat(mBatteryTips.contains(mBatteryTip)).isTrue();
+ assertThat(mBatteryTipLoader.mVisibleTips).isEqualTo(0);
+ }
+
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/LowBatteryDetectorTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/LowBatteryDetectorTest.java
new file mode 100644
index 00000000000..4866a6a7348
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/LowBatteryDetectorTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.spy;
+
+import android.content.Context;
+import android.text.format.DateUtils;
+
+import com.android.settings.TestConfig;
+import com.android.settings.fuelgauge.BatteryInfo;
+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;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class LowBatteryDetectorTest {
+ private Context mContext;
+ @Mock
+ private BatteryInfo mBatteryInfo;
+ private BatteryTipPolicy mPolicy;
+ private LowBatteryDetector mLowBatteryDetector;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mContext = RuntimeEnvironment.application;
+ mPolicy = spy(new BatteryTipPolicy(mContext));
+ mLowBatteryDetector = new LowBatteryDetector(mPolicy, mBatteryInfo);
+ }
+
+ @Test
+ public void testDetect_disabledByPolicy_tipInvisible() {
+ ReflectionHelpers.setField(mPolicy, "lowBatteryEnabled", false);
+
+ assertThat(mLowBatteryDetector.detect().isVisible()).isFalse();
+ }
+
+ @Test
+ public void testDetect_shortBatteryLife_tipVisible() {
+ mBatteryInfo.discharging = true;
+ mBatteryInfo.remainingTimeUs = 1 * DateUtils.MINUTE_IN_MILLIS;
+
+ assertThat(mLowBatteryDetector.detect().isVisible()).isTrue();
+ }
+
+ @Test
+ public void testDetect_longBatteryLife_tipInvisible() {
+ mBatteryInfo.discharging = true;
+ mBatteryInfo.remainingTimeUs = 1 * DateUtils.DAY_IN_MILLIS;
+
+ assertThat(mLowBatteryDetector.detect().isVisible()).isFalse();
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/SummaryDetectorTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/SummaryDetectorTest.java
new file mode 100644
index 00000000000..389a6c3fc2a
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/SummaryDetectorTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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.spy;
+
+import android.content.Context;
+
+import com.android.settings.TestConfig;
+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.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class SummaryDetectorTest {
+ private Context mContext;
+ private BatteryTipPolicy mPolicy;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mContext = RuntimeEnvironment.application;
+ mPolicy = spy(new BatteryTipPolicy(mContext));
+ }
+
+ @Test
+ public void testDetect_disabledByPolicy_tipInvisible() {
+ ReflectionHelpers.setField(mPolicy, "summaryEnabled", false);
+ SummaryDetector detector = new SummaryDetector(mPolicy, 0 /* visibleTips */);
+
+ assertThat(detector.detect().isVisible()).isFalse();
+ }
+
+ @Test
+ public void testDetect_noOtherTips_tipVisible() {
+ SummaryDetector detector = new SummaryDetector(mPolicy, 0 /* visibleTips */);
+
+ assertThat(detector.detect().isVisible()).isTrue();
+ }
+
+ @Test
+ public void testDetect_hasOtherTips_tipInVisible() {
+ SummaryDetector detector = new SummaryDetector(mPolicy, 1 /* visibleTips */);
+
+ assertThat(detector.detect().isVisible()).isFalse();
+ }
+}