diff --git a/res/drawable/ic_battery_saver_accent_24dp.xml b/res/drawable/ic_battery_saver_accent_24dp.xml
new file mode 100644
index 00000000000..c8def54883d
--- /dev/null
+++ b/res/drawable/ic_battery_saver_accent_24dp.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 88fe4eb6a53..9698dcb440e 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -9091,6 +9091,12 @@
Impacts what you hear and see
+
+ Battery Saver is on
+
+
+ Features restricted
+
Mobile data is off
diff --git a/src/com/android/settings/homepage/contextualcards/conditional/BatterySaverConditionController.java b/src/com/android/settings/homepage/contextualcards/conditional/BatterySaverConditionController.java
new file mode 100644
index 00000000000..bce7c5dd981
--- /dev/null
+++ b/src/com/android/settings/homepage/contextualcards/conditional/BatterySaverConditionController.java
@@ -0,0 +1,108 @@
+/*
+ * 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.homepage.contextualcards.conditional;
+
+import android.content.Context;
+import android.os.PowerManager;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.R;
+import com.android.settings.core.SubSettingLauncher;
+import com.android.settings.fuelgauge.BatterySaverReceiver;
+import com.android.settings.fuelgauge.batterysaver.BatterySaverSettings;
+import com.android.settings.homepage.contextualcards.ContextualCard;
+import com.android.settingslib.fuelgauge.BatterySaverUtils;
+
+import java.util.Objects;
+
+public class BatterySaverConditionController implements ConditionalCardController,
+ BatterySaverReceiver.BatterySaverListener {
+ static final int ID = Objects.hash("BatterySaverConditionController");
+
+ private final Context mAppContext;
+ private final ConditionManager mConditionManager;
+ private final BatterySaverReceiver mReceiver;
+ private final PowerManager mPowerManager;
+
+ public BatterySaverConditionController(Context appContext, ConditionManager conditionManager) {
+ mAppContext = appContext;
+ mConditionManager = conditionManager;
+ mPowerManager = appContext.getSystemService(PowerManager.class);
+ mReceiver = new BatterySaverReceiver(appContext);
+ mReceiver.setBatterySaverListener(this);
+ }
+
+ @Override
+ public long getId() {
+ return ID;
+ }
+
+ @Override
+ public boolean isDisplayable() {
+ return mPowerManager.isPowerSaveMode();
+ }
+
+ @Override
+ public void onPrimaryClick(Context context) {
+ new SubSettingLauncher(context)
+ .setDestination(BatterySaverSettings.class.getName())
+ .setSourceMetricsCategory(MetricsProto.MetricsEvent.DASHBOARD_SUMMARY)
+ .setTitleRes(R.string.battery_saver)
+ .launch();
+ }
+
+ @Override
+ public void onActionClick() {
+ BatterySaverUtils.setPowerSaveMode(mAppContext, false,
+ /*needFirstTimeWarning*/ false);
+ }
+
+ @Override
+ public ContextualCard buildContextualCard() {
+ return new ConditionalContextualCard.Builder()
+ .setConditionId(ID)
+ .setMetricsConstant(MetricsProto.MetricsEvent.SETTINGS_CONDITION_BATTERY_SAVER)
+ .setActionText(mAppContext.getText(R.string.condition_turn_off))
+ .setName(mAppContext.getPackageName() + "/"
+ + mAppContext.getText(R.string.condition_battery_title))
+ .setTitleText(mAppContext.getText(R.string.condition_battery_title).toString())
+ .setSummaryText(mAppContext.getText(R.string.condition_battery_summary).toString())
+ .setIconDrawable(mAppContext.getDrawable(R.drawable.ic_battery_saver_accent_24dp))
+ .setIsHalfWidth(true)
+ .build();
+ }
+
+ @Override
+ public void startMonitoringStateChange() {
+ mReceiver.setListening(true);
+ }
+
+ @Override
+ public void stopMonitoringStateChange() {
+ mReceiver.setListening(false);
+ }
+
+ @Override
+ public void onPowerSaveModeChanged() {
+ mConditionManager.onConditionChanged();
+ }
+
+ @Override
+ public void onBatteryChanged(boolean pluggedIn) {
+
+ }
+}
diff --git a/src/com/android/settings/homepage/contextualcards/conditional/ConditionManager.java b/src/com/android/settings/homepage/contextualcards/conditional/ConditionManager.java
index 39f490312cf..c741b98c359 100644
--- a/src/com/android/settings/homepage/contextualcards/conditional/ConditionManager.java
+++ b/src/com/android/settings/homepage/contextualcards/conditional/ConditionManager.java
@@ -154,6 +154,7 @@ public class ConditionManager {
mCardControllers.add(new AirplaneModeConditionController(mAppContext, this /* manager */));
mCardControllers.add(
new BackgroundDataConditionController(mAppContext, this /* manager */));
+ mCardControllers.add(new BatterySaverConditionController(mAppContext, this /* manager */));
mCardControllers.add(new CellularDataConditionController(mAppContext, this /* manager */));
mCardControllers.add(new DndConditionCardController(mAppContext, this /* manager */));
mCardControllers.add(new HotspotConditionController(mAppContext, this /* manager */));
diff --git a/src/com/android/settings/homepage/contextualcards/deviceinfo/BatterySlice.java b/src/com/android/settings/homepage/contextualcards/deviceinfo/BatteryInfoSlice.java
similarity index 96%
rename from src/com/android/settings/homepage/contextualcards/deviceinfo/BatterySlice.java
rename to src/com/android/settings/homepage/contextualcards/deviceinfo/BatteryInfoSlice.java
index 5271e128502..41095a48601 100644
--- a/src/com/android/settings/homepage/contextualcards/deviceinfo/BatterySlice.java
+++ b/src/com/android/settings/homepage/contextualcards/deviceinfo/BatteryInfoSlice.java
@@ -42,15 +42,15 @@ import com.android.settings.slices.SliceBuilderUtils;
/**
* Utility class to build a Battery Slice, and handle all associated actions.
*/
-public class BatterySlice implements CustomSliceable {
- private static final String TAG = "BatterySlice";
+public class BatteryInfoSlice implements CustomSliceable {
+ private static final String TAG = "BatteryInfoSlice";
private final Context mContext;
private BatteryInfo mBatteryInfo;
private boolean mIsBatteryInfoLoading;
- public BatterySlice(Context context) {
+ public BatteryInfoSlice(Context context) {
mContext = context;
}
diff --git a/src/com/android/settings/homepage/contextualcards/slices/BatteryFixSlice.java b/src/com/android/settings/homepage/contextualcards/slices/BatteryFixSlice.java
index 531501b3b5e..c0cfb3f1430 100644
--- a/src/com/android/settings/homepage/contextualcards/slices/BatteryFixSlice.java
+++ b/src/com/android/settings/homepage/contextualcards/slices/BatteryFixSlice.java
@@ -25,7 +25,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
-import android.util.Log;
import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
@@ -49,6 +48,7 @@ import com.android.settings.slices.SliceBackgroundWorker;
import com.android.settings.slices.SliceBuilderUtils;
import com.android.settingslib.utils.ThreadUtils;
+import java.util.Arrays;
import java.util.List;
public class BatteryFixSlice implements CustomSliceable {
@@ -58,6 +58,11 @@ public class BatteryFixSlice implements CustomSliceable {
@VisibleForTesting
static final String KEY_CURRENT_TIPS_TYPE = "current_tip_type";
+ private static final List UNIMPORTANT_BATTERY_TIPS = Arrays.asList(
+ BatteryTip.TipType.SUMMARY,
+ BatteryTip.TipType.BATTERY_SAVER
+ );
+
private static final String TAG = "BatteryFixSlice";
private final Context mContext;
@@ -78,7 +83,7 @@ public class BatteryFixSlice implements CustomSliceable {
.setAccentColor(-1);
// TipType.SUMMARY is battery good
- if (readBatteryTipAvailabilityCache(mContext) == BatteryTip.TipType.SUMMARY) {
+ if (UNIMPORTANT_BATTERY_TIPS.contains(readBatteryTipAvailabilityCache(mContext))) {
return buildBatteryGoodSlice(sliceBuilder, true);
}
@@ -91,19 +96,21 @@ public class BatteryFixSlice implements CustomSliceable {
}
for (BatteryTip batteryTip : batteryTips) {
- if (batteryTip.getState() != BatteryTip.StateType.INVISIBLE) {
- final IconCompat icon = IconCompat.createWithResource(mContext, batteryTip.getIconId());
- final SliceAction primaryAction = SliceAction.createDeeplink(getPrimaryAction(),
- icon,
- ListBuilder.ICON_IMAGE,
- batteryTip.getTitle(mContext));
- sliceBuilder.addRow(new RowBuilder()
- .setTitleItem(icon, ListBuilder.ICON_IMAGE)
- .setTitle(batteryTip.getTitle(mContext))
- .setSubtitle(batteryTip.getSummary(mContext))
- .setPrimaryAction(primaryAction));
- break;
+ if (batteryTip.getState() == BatteryTip.StateType.INVISIBLE) {
+ continue;
}
+ final IconCompat icon = IconCompat.createWithResource(mContext,
+ batteryTip.getIconId());
+ final SliceAction primaryAction = SliceAction.createDeeplink(getPrimaryAction(),
+ icon,
+ ListBuilder.ICON_IMAGE,
+ batteryTip.getTitle(mContext));
+ sliceBuilder.addRow(new RowBuilder()
+ .setTitleItem(icon, ListBuilder.ICON_IMAGE)
+ .setTitle(batteryTip.getTitle(mContext))
+ .setSubtitle(batteryTip.getSummary(mContext))
+ .setPrimaryAction(primaryAction));
+ break;
}
return sliceBuilder.build();
}
diff --git a/src/com/android/settings/slices/CustomSliceManager.java b/src/com/android/settings/slices/CustomSliceManager.java
index bb47df28622..24ee680b7c7 100644
--- a/src/com/android/settings/slices/CustomSliceManager.java
+++ b/src/com/android/settings/slices/CustomSliceManager.java
@@ -23,7 +23,7 @@ import android.util.ArrayMap;
import androidx.annotation.VisibleForTesting;
import com.android.settings.flashlight.FlashlightSlice;
-import com.android.settings.homepage.contextualcards.deviceinfo.BatterySlice;
+import com.android.settings.homepage.contextualcards.deviceinfo.BatteryInfoSlice;
import com.android.settings.homepage.contextualcards.deviceinfo.DataUsageSlice;
import com.android.settings.homepage.contextualcards.deviceinfo.DeviceInfoSlice;
import com.android.settings.homepage.contextualcards.deviceinfo.EmergencyInfoSlice;
@@ -106,7 +106,7 @@ public class CustomSliceManager {
private void addSlices() {
mUriMap.put(CustomSliceRegistry.BATTERY_FIX_SLICE_URI, BatteryFixSlice.class);
- mUriMap.put(CustomSliceRegistry.BATTERY_INFO_SLICE_URI, BatterySlice.class);
+ mUriMap.put(CustomSliceRegistry.BATTERY_INFO_SLICE_URI, BatteryInfoSlice.class);
mUriMap.put(CustomSliceRegistry.BLUETOOTH_DEVICES_SLICE_URI, BluetoothDevicesSlice.class);
mUriMap.put(CustomSliceRegistry.CONTEXTUAL_WIFI_SLICE_URI, ContextualWifiSlice.class);
mUriMap.put(CustomSliceRegistry.DATA_USAGE_SLICE_URI, DataUsageSlice.class);
diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/conditional/BatterySaverConditionControllerTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/conditional/BatterySaverConditionControllerTest.java
new file mode 100644
index 00000000000..e4ca6c09722
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/conditional/BatterySaverConditionControllerTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.homepage.contextualcards.conditional;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.content.IntentFilter;
+import android.os.PowerManager;
+
+import com.android.settings.fuelgauge.BatterySaverReceiver;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.shadows.ShadowPowerManager;
+
+@RunWith(RobolectricTestRunner.class)
+public class BatterySaverConditionControllerTest {
+ @Mock
+ private ConditionManager mConditionManager;
+
+ private ShadowPowerManager mPowerManager;
+ private Context mContext;
+ private BatterySaverConditionController mController;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = spy(RuntimeEnvironment.application);
+ mPowerManager = Shadows.shadowOf(mContext.getSystemService(PowerManager.class));
+ mController = new BatterySaverConditionController(mContext, mConditionManager);
+ }
+
+ @Test
+ public void startMonitor_shouldRegisterReceiver() {
+ mController.startMonitoringStateChange();
+
+ verify(mContext).registerReceiver(any(BatterySaverReceiver.class), any(IntentFilter.class));
+ }
+
+ @Test
+ public void stopMonitor_shouldUnregisterReceiver() {
+ mController.startMonitoringStateChange();
+ mController.stopMonitoringStateChange();
+
+ verify(mContext).unregisterReceiver(any(BatterySaverReceiver.class));
+ }
+
+ @Test
+ public void isDisplayable_PowerSaverOn_true() {
+ mPowerManager.setIsPowerSaveMode(true);
+
+ assertThat(mController.isDisplayable()).isTrue();
+ }
+
+ @Test
+ public void isDisplayable_PowerSaverOff_false() {
+ mPowerManager.setIsPowerSaveMode(false);
+
+ assertThat(mController.isDisplayable()).isFalse();
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/deviceinfo/BatterySliceTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/deviceinfo/BatteryInfoSliceTest.java
similarity index 84%
rename from tests/robotests/src/com/android/settings/homepage/contextualcards/deviceinfo/BatterySliceTest.java
rename to tests/robotests/src/com/android/settings/homepage/contextualcards/deviceinfo/BatteryInfoSliceTest.java
index 289a57d74c0..ff276d64d54 100644
--- a/tests/robotests/src/com/android/settings/homepage/contextualcards/deviceinfo/BatterySliceTest.java
+++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/deviceinfo/BatteryInfoSliceTest.java
@@ -40,10 +40,10 @@ import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
@RunWith(RobolectricTestRunner.class)
-public class BatterySliceTest {
+public class BatteryInfoSliceTest {
private Context mContext;
- private BatterySlice mBatterySlice;
+ private BatteryInfoSlice mBatteryInfoSlice;
@Before
public void setUp() {
@@ -52,16 +52,16 @@ public class BatterySliceTest {
// Set-up specs for SliceMetadata.
SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS);
- mBatterySlice = spy(new BatterySlice(mContext));
+ mBatteryInfoSlice = spy(new BatteryInfoSlice(mContext));
}
@Test
public void getSlice_shouldBeCorrectSliceContent() {
- doNothing().when(mBatterySlice).loadBatteryInfo();
- doReturn("10%").when(mBatterySlice).getBatteryPercentString();
- doReturn("test").when(mBatterySlice).getSummary();
+ doNothing().when(mBatteryInfoSlice).loadBatteryInfo();
+ doReturn("10%").when(mBatteryInfoSlice).getBatteryPercentString();
+ doReturn("test").when(mBatteryInfoSlice).getSummary();
- final Slice slice = mBatterySlice.getSlice();
+ final Slice slice = mBatteryInfoSlice.getSlice();
final SliceMetadata metadata = SliceMetadata.from(mContext, slice);
assertThat(metadata.getTitle()).isEqualTo(
diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/BatteryFixSliceTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/BatteryFixSliceTest.java
index ff08c6aa312..1c299cb6c40 100644
--- a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/BatteryFixSliceTest.java
+++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/BatteryFixSliceTest.java
@@ -26,6 +26,8 @@ import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
import android.content.SharedPreferences;
+import androidx.slice.Slice;
+import androidx.slice.SliceMetadata;
import androidx.slice.SliceProvider;
import androidx.slice.widget.SliceLiveData;
@@ -55,11 +57,13 @@ import java.util.List;
public class BatteryFixSliceTest {
private Context mContext;
+ private BatteryFixSlice mSlice;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
+ mSlice = new BatteryFixSlice(mContext);
// Set-up specs for SliceMetadata.
SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS);
@@ -71,7 +75,7 @@ public class BatteryFixSliceTest {
}
@Test
- public void readBatteryTipfromPref_readCorrectValue() {
+ public void readBatteryTipFromPref_readCorrectValue() {
int target = 111;
final SharedPreferences.Editor editor = mContext.getSharedPreferences(PREFS,
MODE_PRIVATE).edit();
@@ -90,7 +94,7 @@ public class BatteryFixSliceTest {
public void updateBatteryTipAvailabilityCache_writeCorrectValue() {
final List tips = new ArrayList<>();
tips.add(new LowBatteryTip(BatteryTip.StateType.INVISIBLE, false, ""));
- tips.add(new EarlyWarningTip(BatteryTip.StateType.HANDLED, false));
+ tips.add(new EarlyWarningTip(BatteryTip.StateType.NEW, false));
ShadowBatteryTipLoader.setBatteryTips(tips);
BatteryFixSlice.updateBatteryTipAvailabilityCache(mContext);
@@ -99,6 +103,23 @@ public class BatteryFixSliceTest {
BatteryTip.TipType.BATTERY_SAVER);
}
+ @Test
+ @Config(shadows = {
+ ShadowBatteryStatsHelperLoader.class,
+ ShadowBatteryTipLoader.class
+ })
+ public void getSlice_unimportantSlice_shouldSkip() {
+ final List tips = new ArrayList<>();
+ tips.add(new LowBatteryTip(BatteryTip.StateType.INVISIBLE, false, ""));
+ tips.add(new EarlyWarningTip(BatteryTip.StateType.NEW, false));
+ ShadowBatteryTipLoader.setBatteryTips(tips);
+
+ BatteryFixSlice.updateBatteryTipAvailabilityCache(mContext);
+ final Slice slice = mSlice.getSlice();
+
+ assertThat(SliceMetadata.from(mContext, slice).isErrorSlice()).isTrue();
+ }
+
@Implements(BatteryStatsHelperLoader.class)
public static class ShadowBatteryStatsHelperLoader {