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); + } +}