diff options
author | 2018-03-21 22:32:33 +0000 | |
---|---|---|
committer | 2018-03-21 22:32:33 +0000 | |
commit | ff61d8872c938b6223c6d4516bcc6112b1fe68ed (patch) | |
tree | 8fd69bec41fd4aabacb70bee900a6191461e9f1c | |
parent | b2637dac492e916c0a91982a7f2a48d84d9f7fae (diff) | |
parent | 16a0dd2d5eeef320a80afe130e6c1e2946e8d2f1 (diff) |
Merge "Implement new BS warning / notification flow (1/2)" into pi-dev
8 files changed, 280 insertions, 36 deletions
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index b9aad113c677..8cb8b0ed0ad4 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -7762,6 +7762,22 @@ public final class Settings { public static final String BLUETOOTH_ON_WHILE_DRIVING = "bluetooth_on_while_driving"; /** + * The number of times (integer) the user has manually enabled battery saver. + * @hide + */ + public static final String LOW_POWER_MANUAL_ACTIVATION_COUNT = + "low_power_manual_activation_count"; + + /** + * Whether the "first time battery saver warning" dialog needs to be shown (0: default) + * or not (1). + * + * @hide + */ + public static final String LOW_POWER_WARNING_ACKNOWLEDGED = + "low_power_warning_acknowledged"; + + /** * This are the settings to be backed up. * * NOTE: Settings are backed up and restored in the order they appear diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index aac4092db08f..1b110701cce0 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -4475,7 +4475,7 @@ <string name="package_deleted_device_owner">Deleted by your admin</string> <!-- [CHAR_LIMIT=NONE] Battery saver: Feature description --> - <string name="battery_saver_description">To help improve battery life, Battery Saver reduces your device’s performance and limits vibration, location services, and most background data. Email, messaging, and other apps that rely on syncing may not update unless you open them.\n\nBattery Saver turns off automatically when your device is charging.</string> + <string name="battery_saver_description">To extend battery life, Battery Saver reduces your device\'s performance and limits or turns off vibration, location services, and background data. Email, messaging, and other apps that rely on syncing may not update unless you open them.\n\nBattery Saver turns off automatically when your device is charging.</string> <!-- [CHAR_LIMIT=NONE] Data saver: Feature description --> <string name="data_saver_description">To help reduce data usage, Data Saver prevents some apps from sending or receiving data in the background. An app you’re currently using can access data, but may do so less frequently. This may mean, for example, that images don’t display until you tap them.</string> diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java index 2036e2f95225..deafbd3271ef 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -581,7 +581,9 @@ public class SettingsBackupTest { Settings.Secure.KEYGUARD_SLICE_URI, Settings.Secure.PARENTAL_CONTROL_ENABLED, Settings.Secure.PARENTAL_CONTROL_REDIRECT_URL, - Settings.Secure.BLUETOOTH_ON_WHILE_DRIVING); + Settings.Secure.BLUETOOTH_ON_WHILE_DRIVING, + Settings.Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, + Settings.Secure.LOW_POWER_WARNING_ACKNOWLEDGED); @Test public void systemSettingsBackedUpOrBlacklisted() { diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java new file mode 100644 index 000000000000..e2c7747e3231 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java @@ -0,0 +1,91 @@ +/* + * 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.settingslib.fuelgauge; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.os.PowerManager; +import android.provider.Settings.Secure; +import android.util.Log; + +/** + * Utilities related to battery saver. + */ +public class BatterySaverUtils { + private static final String TAG = "BatterySaverUtils"; + + private BatterySaverUtils() { + } + + private static final boolean DEBUG = false; + + // Broadcast action for SystemUI to show the battery saver confirmation dialog. + public static final String ACTION_SHOW_START_SAVER_CONFIRMATION = "PNW.startSaverConfirmation"; + + /** + * Enable / disable battery saver by user request. + * - If it's the first time and needFirstTimeWarning, show the first time dialog. + * - If it's 4th time through 8th time, show the schedule suggestion notification. + * + * @param enable true to disable battery saver. + * + * @return true if the request succeeded. + */ + public static synchronized boolean setPowerSaveMode(Context context, + boolean enable, boolean needFirstTimeWarning) { + if (DEBUG) { + Log.d(TAG, "Battery saver turning " + (enable ? "ON" : "OFF")); + } + final ContentResolver cr = context.getContentResolver(); + + if (enable && needFirstTimeWarning && maybeShowBatterySaverConfirmation(context)) { + return false; + } + if (enable && !needFirstTimeWarning) { + setBatterySaverConfirmationAcknowledged(context); + } + + if (context.getSystemService(PowerManager.class).setPowerSaveMode(enable)) { + if (enable) { + Secure.putInt(cr, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, + Secure.getInt(cr, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, 0) + 1); + + // TODO If enabling, and the count is between 4 and 8 (inclusive), then + // show the "battery saver schedule suggestion" notification. + } + + return true; + } + return false; + } + + private static boolean maybeShowBatterySaverConfirmation(Context context) { + if (Secure.getInt(context.getContentResolver(), + Secure.LOW_POWER_WARNING_ACKNOWLEDGED, 0) != 0) { + return false; // Already shown. + } + final Intent i = new Intent(ACTION_SHOW_START_SAVER_CONFIRMATION); + context.sendBroadcast(i); + + return true; + } + + private static void setBatterySaverConfirmationAcknowledged(Context context) { + Secure.putInt(context.getContentResolver(), Secure.LOW_POWER_WARNING_ACKNOWLEDGED, 1); + } +} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java new file mode 100644 index 000000000000..b33df3031ce9 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java @@ -0,0 +1,152 @@ +/* + * 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.settingslib.fuelgauge; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.os.PowerManager; +import android.provider.Settings.Secure; + +import com.android.settingslib.SettingsLibRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + + +@RunWith(SettingsLibRobolectricTestRunner.class) +public class BatterySaverUtilsTest { + @Mock + Context mMockContext; + + @Mock + ContentResolver mMockResolver; + + @Mock + PowerManager mMockPowerManager; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + when(mMockContext.getContentResolver()).thenReturn(mMockResolver); + when(mMockContext.getSystemService(eq(PowerManager.class))).thenReturn(mMockPowerManager); + when(mMockPowerManager.setPowerSaveMode(anyBoolean())).thenReturn(true); + } + + @Test + public void testSetPowerSaveMode_enable_firstCall_needWarning() throws Exception { + Secure.putString(mMockResolver, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, "null"); + Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null"); + + assertEquals(false, BatterySaverUtils.setPowerSaveMode(mMockContext, true, true)); + + verify(mMockContext, times(1)).sendBroadcast(any(Intent.class)); + verify(mMockPowerManager, times(0)).setPowerSaveMode(anyBoolean()); + + // They shouldn't have changed. + assertEquals(-1, + Secure.getInt(mMockResolver, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, -1)); + assertEquals(-2, + Secure.getInt(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, -2)); + } + + @Test + public void testSetPowerSaveMode_enable_secondCall_needWarning() throws Exception { + Secure.putInt(mMockResolver, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, 1); // Already acked. + Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null"); + + assertEquals(true, BatterySaverUtils.setPowerSaveMode(mMockContext, true, true)); + + verify(mMockContext, times(0)).sendBroadcast(any(Intent.class)); + verify(mMockPowerManager, times(1)).setPowerSaveMode(eq(true)); + + assertEquals(1, Secure.getInt(mMockResolver, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, -1)); + assertEquals(1, Secure.getInt(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, -2)); + } + + @Test + public void testSetPowerSaveMode_enable_thridCall_needWarning() throws Exception { + Secure.putInt(mMockResolver, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, 1); // Already acked. + Secure.putInt(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, 1); + + assertEquals(true, BatterySaverUtils.setPowerSaveMode(mMockContext, true, true)); + + verify(mMockContext, times(0)).sendBroadcast(any(Intent.class)); + verify(mMockPowerManager, times(1)).setPowerSaveMode(eq(true)); + + assertEquals(1, Secure.getInt(mMockResolver, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, -1)); + assertEquals(2, Secure.getInt(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, -2)); + } + + @Test + public void testSetPowerSaveMode_enable_firstCall_noWarning() throws Exception { + Secure.putString(mMockResolver, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, "null"); + Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null"); + + assertEquals(true, BatterySaverUtils.setPowerSaveMode(mMockContext, true, false)); + + verify(mMockContext, times(0)).sendBroadcast(any(Intent.class)); + verify(mMockPowerManager, times(1)).setPowerSaveMode(eq(true)); + + assertEquals(1, Secure.getInt(mMockResolver, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, -1)); + assertEquals(1, Secure.getInt(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, -2)); + } + + @Test + public void testSetPowerSaveMode_disable_firstCall_noWarning() throws Exception { + Secure.putString(mMockResolver, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, "null"); + Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null"); + + // When disabling, needFirstTimeWarning doesn't matter. + assertEquals(true, BatterySaverUtils.setPowerSaveMode(mMockContext, false, false)); + + verify(mMockContext, times(0)).sendBroadcast(any(Intent.class)); + verify(mMockPowerManager, times(1)).setPowerSaveMode(eq(false)); + + assertEquals(-1, Secure.getInt(mMockResolver, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, -1)); + assertEquals(-2, + Secure.getInt(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, -2)); + } + + @Test + public void testSetPowerSaveMode_disable_firstCall_needWarning() throws Exception { + Secure.putString(mMockResolver, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, "null"); + Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null"); + + // When disabling, needFirstTimeWarning doesn't matter. + assertEquals(true, BatterySaverUtils.setPowerSaveMode(mMockContext, false, true)); + + verify(mMockContext, times(0)).sendBroadcast(any(Intent.class)); + verify(mMockPowerManager, times(1)).setPowerSaveMode(eq(false)); + + assertEquals(-1, Secure.getInt(mMockResolver, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, -1)); + assertEquals(-2, + Secure.getInt(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, -2)); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java index 9a3a825b138c..b89e15da6729 100644 --- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java +++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java @@ -27,7 +27,6 @@ import android.content.DialogInterface.OnDismissListener; import android.content.Intent; import android.content.IntentFilter; import android.media.AudioAttributes; -import android.os.AsyncTask; import android.os.Handler; import android.os.Looper; import android.os.PowerManager; @@ -37,6 +36,7 @@ import android.util.Slog; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.settingslib.Utils; +import com.android.settingslib.fuelgauge.BatterySaverUtils; import com.android.settingslib.utils.PowerUtil; import com.android.systemui.R; import com.android.systemui.SystemUI; @@ -72,6 +72,8 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { "PNW.clickedThermalShutdownWarning"; private static final String ACTION_DISMISSED_THERMAL_SHUTDOWN_WARNING = "PNW.dismissedThermalShutdownWarning"; + private static final String ACTION_SHOW_START_SAVER_CONFIRMATION = + BatterySaverUtils.ACTION_SHOW_START_SAVER_CONFIRMATION; private static final AudioAttributes AUDIO_ATTRIBUTES = new AudioAttributes.Builder() .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) @@ -404,7 +406,7 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { d.setTitle(R.string.battery_saver_confirmation_title); d.setMessage(com.android.internal.R.string.battery_saver_description); d.setNegativeButton(android.R.string.cancel, null); - d.setPositiveButton(R.string.battery_saver_confirmation_ok, mStartSaverMode); + d.setPositiveButton(R.string.battery_saver_confirmation_ok, mStartSaverModeNoConfirmation); d.setShowForAllUsers(true); d.setOnDismissListener(new OnDismissListener() { @Override @@ -416,8 +418,8 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { mSaverConfirmation = d; } - private void setSaverMode(boolean mode) { - mPowerMan.setPowerSaveMode(mode); + private void setSaverMode(boolean mode, boolean needFirstTimeWarning) { + BatterySaverUtils.setPowerSaveMode(mContext, mode, needFirstTimeWarning); } private final class Receiver extends BroadcastReceiver { @@ -431,8 +433,9 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { filter.addAction(ACTION_DISMISSED_TEMP_WARNING); filter.addAction(ACTION_CLICKED_THERMAL_SHUTDOWN_WARNING); filter.addAction(ACTION_DISMISSED_THERMAL_SHUTDOWN_WARNING); + filter.addAction(ACTION_SHOW_START_SAVER_CONFIRMATION); mContext.registerReceiverAsUser(this, UserHandle.ALL, filter, - android.Manifest.permission.STATUS_BAR_SERVICE, mHandler); + android.Manifest.permission.DEVICE_POWER, mHandler); } @Override @@ -443,6 +446,9 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { dismissLowBatteryNotification(); mContext.startActivityAsUser(mOpenBatterySettings, UserHandle.CURRENT); } else if (action.equals(ACTION_START_SAVER)) { + setSaverMode(true, true); + dismissLowBatteryNotification(); + } else if (action.equals(ACTION_SHOW_START_SAVER_CONFIRMATION)) { dismissLowBatteryNotification(); showStartSaverConfirmation(); } else if (action.equals(ACTION_DISMISSED_WARNING)) { @@ -461,15 +467,6 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { } } - private final OnClickListener mStartSaverMode = new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - AsyncTask.execute(new Runnable() { - @Override - public void run() { - setSaverMode(true); - } - }); - } - }; + private final OnClickListener mStartSaverModeNoConfirmation = + (dialog, which) -> setSaverMode(true, false); } diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java index ac86c8ae097d..f08219a8f742 100644 --- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java +++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java @@ -137,24 +137,9 @@ public class PowerUI extends SystemUI { void updateBatteryWarningLevels() { int critLevel = mContext.getResources().getInteger( com.android.internal.R.integer.config_criticalBatteryWarningLevel); - - final ContentResolver resolver = mContext.getContentResolver(); - final int defWarnLevel = mContext.getResources().getInteger( + int warnLevel = mContext.getResources().getInteger( com.android.internal.R.integer.config_lowBatteryWarningLevel); - final int lowPowerModeTriggerLevel = Settings.Global.getInt(resolver, - Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, defWarnLevel); - - // Note LOW_POWER_MODE_TRIGGER_LEVEL can take any value between 0 and 100, but - // for the UI purposes, let's cap it at 15% -- i.e. even if the trigger level is higher - // like 50%, let's not show the "low battery" notification until it hits - // config_lowBatteryWarningLevel, which is 15% by default. - // LOW_POWER_MODE_TRIGGER_LEVEL is still used in other places as-is. For example, if it's - // 50, then battery saver kicks in when the battery level hits 50%. - int warnLevel = Math.min(defWarnLevel, lowPowerModeTriggerLevel); - - if (warnLevel == 0) { - warnLevel = defWarnLevel; - } + if (warnLevel < critLevel) { warnLevel = critLevel; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java index 49f880ce3320..7221efab0bb2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java @@ -28,6 +28,7 @@ import android.os.PowerSaveState; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; +import com.android.settingslib.fuelgauge.BatterySaverUtils; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -93,7 +94,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC @Override public void setPowerSaveMode(boolean powerSave) { - mPowerManager.setPowerSaveMode(powerSave); + BatterySaverUtils.setPowerSaveMode(mContext, powerSave, /*needFirstTimeWarning*/ true); } @Override |