From 62d6a9dca71cf1cedb34fdefc488b9463f5adc46 Mon Sep 17 00:00:00 2001 From: jackqdyulei Date: Wed, 10 Jan 2018 14:29:57 -0800 Subject: [PATCH] Add early warning tip and detector When battery cannot make to usual charging time, this tip would suggest user to turn on battery saver. This tip has two visible state(NEW, HANDLED) and display different information. This cl also adds an action to turn on the battery saver. Bug: 70570352 Test: RunSettingsRoboTests Change-Id: I0e96554df12a0d6508c27174e16d8dca7f4e1fce --- .../ic_perm_device_information_green_24dp.xml | 26 +++++ res/values/strings.xml | 8 ++ .../batterytip/BatteryTipLoader.java | 7 +- .../fuelgauge/batterytip/BatteryTipUtils.java | 3 + .../actions/BatterySaverAction.java | 37 +++++++ .../detectors/EarlyWarningDetector.java | 64 +++++++++++ .../batterytip/tips/EarlyWarningTip.java | 93 ++++++++++++++++ .../detectors/EarlyWarningDetectorTest.java | 101 ++++++++++++++++++ .../batterytip/tips/EarlyWarningTipTest.java | 86 +++++++++++++++ 9 files changed, 423 insertions(+), 2 deletions(-) create mode 100644 res/drawable/ic_perm_device_information_green_24dp.xml create mode 100644 src/com/android/settings/fuelgauge/batterytip/actions/BatterySaverAction.java create mode 100644 src/com/android/settings/fuelgauge/batterytip/detectors/EarlyWarningDetector.java create mode 100644 src/com/android/settings/fuelgauge/batterytip/tips/EarlyWarningTip.java create mode 100644 tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/EarlyWarningDetectorTest.java create mode 100644 tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/EarlyWarningTipTest.java diff --git a/res/drawable/ic_perm_device_information_green_24dp.xml b/res/drawable/ic_perm_device_information_green_24dp.xml new file mode 100644 index 00000000000..a2b9354dfbc --- /dev/null +++ b/res/drawable/ic_perm_device_information_green_24dp.xml @@ -0,0 +1,26 @@ + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index 46411e6f09e..57eb3142530 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -4813,6 +4813,14 @@ Turn on smart battery manager Turn on to optimize battery usage + + Turn on Low Battery Mode + + Extend your battery life + + Low Battery Mode is on + + Some features are limited Phone used heavily diff --git a/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java b/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java index fc6aa57d6ef..8c0f54afc73 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.EarlyWarningDetector; import com.android.settings.fuelgauge.batterytip.detectors.HighUsageDetector; import com.android.settings.fuelgauge.batterytip.detectors.LowBatteryDetector; import com.android.settings.fuelgauge.batterytip.detectors.SmartBatteryDetector; @@ -64,13 +65,15 @@ public class BatteryTipLoader extends AsyncLoader> { final List tips = new ArrayList<>(); final BatteryTipPolicy policy = new BatteryTipPolicy(getContext()); final BatteryInfo batteryInfo = mBatteryUtils.getBatteryInfo(mBatteryStatsHelper, TAG); + final Context context = getContext(); mVisibleTips = 0; addBatteryTipFromDetector(tips, new LowBatteryDetector(policy, batteryInfo)); addBatteryTipFromDetector(tips, - new HighUsageDetector(getContext(), policy, mBatteryStatsHelper)); + new HighUsageDetector(context, policy, mBatteryStatsHelper)); addBatteryTipFromDetector(tips, - new SmartBatteryDetector(policy, getContext().getContentResolver())); + new SmartBatteryDetector(policy, context.getContentResolver())); + addBatteryTipFromDetector(tips, new EarlyWarningDetector(policy, context)); // 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/BatteryTipUtils.java b/src/com/android/settings/fuelgauge/batterytip/BatteryTipUtils.java index d10fa371529..5781afd61f2 100644 --- a/src/com/android/settings/fuelgauge/batterytip/BatteryTipUtils.java +++ b/src/com/android/settings/fuelgauge/batterytip/BatteryTipUtils.java @@ -19,6 +19,7 @@ package com.android.settings.fuelgauge.batterytip; import android.app.Fragment; import com.android.settings.SettingsActivity; +import com.android.settings.fuelgauge.batterytip.actions.BatterySaverAction; import com.android.settings.fuelgauge.batterytip.actions.BatteryTipAction; import com.android.settings.fuelgauge.batterytip.actions.SmartBatteryAction; import com.android.settings.fuelgauge.batterytip.tips.BatteryTip; @@ -40,6 +41,8 @@ public class BatteryTipUtils { switch (batteryTip.getType()) { case BatteryTip.TipType.SMART_BATTERY_MANAGER: return new SmartBatteryAction(settingsActivity, fragment); + case BatteryTip.TipType.BATTERY_SAVER: + return new BatterySaverAction(settingsActivity.getApplicationContext()); default: return null; } diff --git a/src/com/android/settings/fuelgauge/batterytip/actions/BatterySaverAction.java b/src/com/android/settings/fuelgauge/batterytip/actions/BatterySaverAction.java new file mode 100644 index 00000000000..310d3f86534 --- /dev/null +++ b/src/com/android/settings/fuelgauge/batterytip/actions/BatterySaverAction.java @@ -0,0 +1,37 @@ +/* + * 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.actions; + +import android.content.Context; +import android.os.PowerManager; + +public class BatterySaverAction extends BatteryTipAction { + private PowerManager mPowerManager; + + public BatterySaverAction(Context context) { + super(context); + mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); + } + + /** + * Handle the action when user clicks positive button + */ + @Override + public void handlePositiveAction() { + mPowerManager.setPowerSaveMode(true); + } +} diff --git a/src/com/android/settings/fuelgauge/batterytip/detectors/EarlyWarningDetector.java b/src/com/android/settings/fuelgauge/batterytip/detectors/EarlyWarningDetector.java new file mode 100644 index 00000000000..cb23e946d58 --- /dev/null +++ b/src/com/android/settings/fuelgauge/batterytip/detectors/EarlyWarningDetector.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.detectors; + +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.BatteryManager; +import android.os.PowerManager; + +import com.android.settings.fuelgauge.PowerUsageFeatureProvider; +import com.android.settings.fuelgauge.batterytip.BatteryTipPolicy; +import com.android.settings.fuelgauge.batterytip.tips.BatteryTip; +import com.android.settings.fuelgauge.batterytip.tips.EarlyWarningTip; +import com.android.settings.overlay.FeatureFactory; + +/** + * Detector whether to early warning tip. + */ +public class EarlyWarningDetector implements BatteryTipDetector { + private BatteryTipPolicy mPolicy; + private PowerManager mPowerManager; + private Context mContext; + private PowerUsageFeatureProvider mPowerUsageFeatureProvider; + + public EarlyWarningDetector(BatteryTipPolicy policy, Context context) { + mPolicy = policy; + mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); + mContext = context; + mPowerUsageFeatureProvider = FeatureFactory.getFactory( + context).getPowerUsageFeatureProvider(context); + } + + @Override + public BatteryTip detect() { + final Intent batteryBroadcast = mContext.registerReceiver(null, + new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); + final boolean discharging = + batteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) == 0; + final boolean powerSaveModeOn = mPowerManager.isPowerSaveMode(); + final boolean earlyWarning = mPowerUsageFeatureProvider.getEarlyWarningSignal(mContext, + EarlyWarningDetector.class.getName()); + + final int state = + mPolicy.batterySaverTipEnabled && !powerSaveModeOn && discharging && earlyWarning + ? BatteryTip.StateType.NEW + : BatteryTip.StateType.INVISIBLE; + return new EarlyWarningTip(state, powerSaveModeOn); + } +} diff --git a/src/com/android/settings/fuelgauge/batterytip/tips/EarlyWarningTip.java b/src/com/android/settings/fuelgauge/batterytip/tips/EarlyWarningTip.java new file mode 100644 index 00000000000..f8d8fa19079 --- /dev/null +++ b/src/com/android/settings/fuelgauge/batterytip/tips/EarlyWarningTip.java @@ -0,0 +1,93 @@ +/* + * 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.content.Context; +import android.os.Parcel; + +import com.android.settings.R; + +/** + * Tip to show early warning if battery couldn't make to usual charging time + */ +public class EarlyWarningTip extends BatteryTip { + private boolean mPowerSaveModeOn; + + public EarlyWarningTip(@StateType int state, boolean powerSaveModeOn) { + super(TipType.BATTERY_SAVER, state, false /* showDialog */); + mPowerSaveModeOn = powerSaveModeOn; + } + + public EarlyWarningTip(Parcel in) { + super(in); + mPowerSaveModeOn = in.readBoolean(); + } + + @Override + public CharSequence getTitle(Context context) { + return context.getString( + mState == StateType.HANDLED + ? R.string.battery_tip_early_heads_up_done_title + : R.string.battery_tip_early_heads_up_title); + } + + @Override + public CharSequence getSummary(Context context) { + return context.getString( + mState == StateType.HANDLED + ? R.string.battery_tip_early_heads_up_done_summary + : R.string.battery_tip_early_heads_up_summary); + } + + @Override + public int getIconId() { + return mState == StateType.HANDLED + ? R.drawable.ic_perm_device_information_green_24dp + : R.drawable.ic_battery_alert_24dp; + } + + @Override + public void updateState(BatteryTip tip) { + final EarlyWarningTip earlyHeadsUpTip = (EarlyWarningTip) tip; + if (mPowerSaveModeOn != earlyHeadsUpTip.mPowerSaveModeOn) { + mPowerSaveModeOn = earlyHeadsUpTip.mPowerSaveModeOn; + mState = earlyHeadsUpTip.mPowerSaveModeOn ? StateType.HANDLED : StateType.NEW; + } else if (mState != StateType.HANDLED) { + mState = earlyHeadsUpTip.getState(); + } + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeBoolean(mPowerSaveModeOn); + } + + public boolean isPowerSaveModeOn() { + return mPowerSaveModeOn; + } + + public static final Creator CREATOR = new Creator() { + public BatteryTip createFromParcel(Parcel in) { + return new EarlyWarningTip(in); + } + + public BatteryTip[] newArray(int size) { + return new EarlyWarningTip[size]; + } + }; +} diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/EarlyWarningDetectorTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/EarlyWarningDetectorTest.java new file mode 100644 index 00000000000..ace6da91e4b --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/EarlyWarningDetectorTest.java @@ -0,0 +1,101 @@ +/* + * 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 static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; + +import android.content.Context; +import android.content.Intent; +import android.os.BatteryManager; +import android.os.PowerManager; + +import com.android.settings.TestConfig; +import com.android.settings.fuelgauge.batterytip.BatteryTipPolicy; +import com.android.settings.testutils.FakeFeatureFactory; +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 EarlyWarningDetectorTest { + private Context mContext; + private BatteryTipPolicy mPolicy; + private EarlyWarningDetector mEarlyWarningDetector; + @Mock + private Intent mIntent; + @Mock + private PowerManager mPowerManager; + private FakeFeatureFactory mFakeFeatureFactory; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mContext = spy(RuntimeEnvironment.application); + mPolicy = spy(new BatteryTipPolicy(mContext)); + doReturn(mPowerManager).when(mContext).getSystemService(Context.POWER_SERVICE); + doReturn(mIntent).when(mContext).registerReceiver(any(), any()); + doReturn(0).when(mIntent).getIntExtra(BatteryManager.EXTRA_PLUGGED, -1); + mFakeFeatureFactory = FakeFeatureFactory.setupForTest(); + doReturn(true).when(mFakeFeatureFactory.powerUsageFeatureProvider).getEarlyWarningSignal( + any(), any()); + + mEarlyWarningDetector = new EarlyWarningDetector(mPolicy, mContext); + } + + @Test + public void testDetect_policyDisabled_tipInvisible() { + ReflectionHelpers.setField(mPolicy, "batterySaverTipEnabled", false); + + assertThat(mEarlyWarningDetector.detect().isVisible()).isFalse(); + } + + @Test + public void testDetect_batterySaverOn_tipInvisible() { + doReturn(true).when(mPowerManager).isPowerSaveMode(); + + assertThat(mEarlyWarningDetector.detect().isVisible()).isFalse(); + } + + @Test + public void testDetect_charging_tipInvisible() { + doReturn(1).when(mIntent).getIntExtra(BatteryManager.EXTRA_PLUGGED, -1); + + assertThat(mEarlyWarningDetector.detect().isVisible()).isFalse(); + } + + @Test + public void testDetect_noEarlyWarning_tipInvisible() { + doReturn(false).when(mFakeFeatureFactory.powerUsageFeatureProvider).getEarlyWarningSignal( + any(), any()); + + assertThat(mEarlyWarningDetector.detect().isVisible()).isFalse(); + } + +} diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/EarlyWarningTipTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/EarlyWarningTipTest.java new file mode 100644 index 00000000000..66d5f810826 --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/EarlyWarningTipTest.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.fuelgauge.batterytip.tips; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.os.Parcel; + +import com.android.settings.R; +import com.android.settings.TestConfig; +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; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class EarlyWarningTipTest { + private Context mContext; + private EarlyWarningTip mEarlyWarningTip; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + mEarlyWarningTip = new EarlyWarningTip(BatteryTip.StateType.NEW, + false /* powerSaveModeOn */); + } + + @Test + public void testParcelable() { + Parcel parcel = Parcel.obtain(); + mEarlyWarningTip.writeToParcel(parcel, mEarlyWarningTip.describeContents()); + parcel.setDataPosition(0); + + final EarlyWarningTip parcelTip = new EarlyWarningTip(parcel); + + assertThat(parcelTip.isPowerSaveModeOn()).isFalse(); + } + + @Test + public void testInfo_stateNew_displayPowerModeInfo() { + final EarlyWarningTip tip = new EarlyWarningTip(BatteryTip.StateType.NEW, + false /* powerModeOn */); + + assertThat(tip.getTitle(mContext)).isEqualTo("Turn on Low Battery Mode"); + assertThat(tip.getSummary(mContext)).isEqualTo("Extend your battery life"); + assertThat(tip.getIconId()).isEqualTo(R.drawable.ic_battery_alert_24dp); + } + + @Test + public void testInfo_stateHandled_displayPowerModeHandledInfo() { + final EarlyWarningTip tip = new EarlyWarningTip(BatteryTip.StateType.HANDLED, + false /* powerModeOn */); + + assertThat(tip.getTitle(mContext)).isEqualTo("Low Battery Mode is on"); + assertThat(tip.getSummary(mContext)).isEqualTo("Some features are limited"); + assertThat(tip.getIconId()).isEqualTo(R.drawable.ic_perm_device_information_green_24dp); + } + + @Test + public void testUpdate_powerModeTurnedOn_typeBecomeHandled() { + final EarlyWarningTip nextTip = new EarlyWarningTip(BatteryTip.StateType.INVISIBLE, + true /* powerModeOn */); + + mEarlyWarningTip.updateState(nextTip); + + assertThat(mEarlyWarningTip.getState()).isEqualTo(BatteryTip.StateType.HANDLED); + } +}