Merge "Implement new BS warning / notification flow (1/2)" into pi-dev
am: ff61d8872c
Change-Id: I7d3709fcc5b51833c1747398ae488a872ce7a147
This commit is contained in:
@@ -7761,6 +7761,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.
|
||||
*
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user