blob: fd04970d50fe4b9a61585d89fa69e8f83a89fe02 [file] [log] [blame]
/*
* 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.server.power.batterysaver;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.content.ContentResolver;
import android.content.res.Resources;
import android.provider.Settings.Global;
import android.test.mock.MockContext;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.google.common.base.Objects;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.HashMap;
/**
atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
public class BatterySaverStateMachineTest {
private MyMockContext mMockContext;
private ContentResolver mMockContextResolver;
private BatterySaverController mMockBatterySaverController;
private Device mDevice;
private TestableBatterySaverStateMachine mTarget;
private Resources mMockResources;
private class MyMockContext extends MockContext {
@Override
public ContentResolver getContentResolver() {
return mMockContextResolver;
}
@Override
public Resources getResources() {
return mMockResources;
}
}
private DevicePersistedState mPersistedState;
private class DevicePersistedState {
// Current battery level.
public int batteryLevel = 100;
// Whether battery level is currently low or not.
public boolean batteryLow = false;
// Whether the device is plugged in or not.
public boolean powered = false;
// Global settings.
public final HashMap<String, Integer> global = new HashMap<>();
}
/**
* This class simulates a device's volatile status that will be reset by {@link #initDevice()}.
*/
private class Device {
public boolean batterySaverEnabled = false;
public int getLowPowerModeTriggerLevel() {
return mPersistedState.global.getOrDefault(Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0);
}
public void setBatteryLevel(int level) {
mPersistedState.batteryLevel = level;
if (mPersistedState.batteryLevel <= Math.max(15, getLowPowerModeTriggerLevel())) {
mPersistedState.batteryLow = true;
} else if (mPersistedState.batteryLow
&& (mPersistedState.batteryLevel >= (getLowPowerModeTriggerLevel() + 5))) {
mPersistedState.batteryLow = false;
}
pushBatteryStatus();
}
public void setPowered(boolean newPowered) {
mPersistedState.powered = newPowered;
pushBatteryStatus();
}
public void pushBatteryStatus() {
mTarget.setBatteryStatus(mPersistedState.powered, mPersistedState.batteryLevel,
mPersistedState.batteryLow);
}
public void pushGlobalSettings() {
mTarget.setSettingsLocked(
mPersistedState.global.getOrDefault(Global.LOW_POWER_MODE, 0) != 0,
mPersistedState.global.getOrDefault(Global.LOW_POWER_MODE_STICKY, 0) != 0,
mDevice.getLowPowerModeTriggerLevel());
}
public void putGlobalSetting(String key, int value) {
mPersistedState.global.put(key, value);
pushGlobalSettings();
}
public int getGlobalSetting(String key, int defValue) {
return mPersistedState.global.getOrDefault(key, defValue);
}
}
/**
* Test target class.
*/
private class TestableBatterySaverStateMachine extends BatterySaverStateMachine {
public TestableBatterySaverStateMachine() {
super(new Object(), mMockContext, mMockBatterySaverController);
}
@Override
protected void putGlobalSetting(String key, int value) {
if (Objects.equal(mPersistedState.global.get(key), value)) {
return;
}
mDevice.putGlobalSetting(key, value);
}
@Override
protected int getGlobalSetting(String key, int defValue) {
return mDevice.getGlobalSetting(key, defValue);
}
@Override
void runOnBgThread(Runnable r) {
r.run();
}
@Override
void runOnBgThreadLazy(Runnable r, int delayMillis) {
r.run();
}
}
@Before
public void setUp() {
mMockContext = new MyMockContext();
mMockContextResolver = mock(ContentResolver.class);
mMockBatterySaverController = mock(BatterySaverController.class);
mMockResources = mock(Resources.class);
doAnswer((inv) -> mDevice.batterySaverEnabled = inv.getArgument(0))
.when(mMockBatterySaverController).enableBatterySaver(anyBoolean(), anyInt());
when(mMockBatterySaverController.isEnabled())
.thenAnswer((inv) -> mDevice.batterySaverEnabled);
when(mMockResources.getBoolean(
com.android.internal.R.bool.config_batterySaverStickyBehaviourDisabled))
.thenReturn(false);
mPersistedState = new DevicePersistedState();
initDevice();
}
private void initDevice() {
mDevice = new Device();
mTarget = new TestableBatterySaverStateMachine();
mDevice.pushBatteryStatus();
mTarget.onBootCompleted();
}
@Test
public void testNoAutoBatterySaver() {
assertEquals(0, mDevice.getLowPowerModeTriggerLevel());
assertEquals(false, mDevice.batterySaverEnabled);
assertEquals(100, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
mDevice.setBatteryLevel(90);
assertEquals(false, mDevice.batterySaverEnabled);
assertEquals(90, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
mDevice.setBatteryLevel(50);
assertEquals(false, mDevice.batterySaverEnabled);
assertEquals(50, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
mDevice.setBatteryLevel(16);
assertEquals(false, mDevice.batterySaverEnabled);
assertEquals(16, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
// When LOW_POWER_MODE_TRIGGER_LEVEL is 0, 15% will still trigger low-battery, but
// BS wont be enabled.
mDevice.setBatteryLevel(15);
assertEquals(false, mDevice.batterySaverEnabled);
assertEquals(15, mPersistedState.batteryLevel);
assertEquals(true, mPersistedState.batteryLow);
mDevice.setBatteryLevel(10);
assertEquals(false, mDevice.batterySaverEnabled);
assertEquals(10, mPersistedState.batteryLevel);
assertEquals(true, mPersistedState.batteryLow);
// Manually enable BS.
mTarget.setBatterySaverEnabledManually(true);
assertEquals(true, mDevice.batterySaverEnabled);
assertEquals(10, mPersistedState.batteryLevel);
assertEquals(true, mPersistedState.batteryLow);
mDevice.setBatteryLevel(50);
assertEquals(true, mDevice.batterySaverEnabled);
assertEquals(50, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
// Start charging. It'll disable BS.
mDevice.setPowered(true);
assertEquals(false, mDevice.batterySaverEnabled);
assertEquals(50, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
mDevice.setBatteryLevel(60);
assertEquals(false, mDevice.batterySaverEnabled);
assertEquals(60, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
// Unplug.
mDevice.setPowered(false);
assertEquals(true, mDevice.batterySaverEnabled);
assertEquals(60, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
mDevice.setBatteryLevel(10);
assertEquals(true, mDevice.batterySaverEnabled);
assertEquals(10, mPersistedState.batteryLevel);
assertEquals(true, mPersistedState.batteryLow);
mDevice.setBatteryLevel(80);
assertEquals(true, mDevice.batterySaverEnabled);
assertEquals(80, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
// Reboot the device.
initDevice();
assertEquals(true, mDevice.batterySaverEnabled); // Sticky.
assertEquals(80, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
mDevice.setBatteryLevel(30);
initDevice();
assertEquals(true, mDevice.batterySaverEnabled); // Still sticky.
assertEquals(30, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
mTarget.setBatterySaverEnabledManually(false);
assertEquals(false, mDevice.batterySaverEnabled);
assertEquals(30, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
initDevice(); // reboot.
assertEquals(false, mDevice.batterySaverEnabled);
assertEquals(30, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
}
@Test
public void testAutoBatterySaver() {
mDevice.putGlobalSetting(Global.LOW_POWER_MODE_TRIGGER_LEVEL, 50);
assertEquals(false, mDevice.batterySaverEnabled);
assertEquals(100, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
mDevice.setBatteryLevel(90);
assertEquals(false, mDevice.batterySaverEnabled);
assertEquals(90, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
mDevice.setBatteryLevel(51);
assertEquals(false, mDevice.batterySaverEnabled);
assertEquals(51, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
// Hit the threshold. BS should be enabled.
mDevice.setBatteryLevel(50);
assertEquals(true, mDevice.batterySaverEnabled);
assertEquals(50, mPersistedState.batteryLevel);
assertEquals(true, mPersistedState.batteryLow);
// Battery goes up, but until it hits 55%, we still keep BS on.
mDevice.setBatteryLevel(54);
assertEquals(true, mDevice.batterySaverEnabled);
assertEquals(54, mPersistedState.batteryLevel);
assertEquals(true, mPersistedState.batteryLow);
// 50% + 5%, now BS will be off.
mDevice.setBatteryLevel(55);
assertEquals(false, mDevice.batterySaverEnabled);
assertEquals(55, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
mDevice.setBatteryLevel(40);
assertEquals(true, mDevice.batterySaverEnabled);
assertEquals(40, mPersistedState.batteryLevel);
assertEquals(true, mPersistedState.batteryLow);
mDevice.setPowered(true);
assertEquals(false, mDevice.batterySaverEnabled);
assertEquals(40, mPersistedState.batteryLevel);
assertEquals(true, mPersistedState.batteryLow);
mDevice.setPowered(false);
assertEquals(true, mDevice.batterySaverEnabled);
assertEquals(40, mPersistedState.batteryLevel);
assertEquals(true, mPersistedState.batteryLow);
mTarget.setBatterySaverEnabledManually(false); // Manually disable -> snooze.
assertEquals(false, mDevice.batterySaverEnabled);
assertEquals(40, mPersistedState.batteryLevel);
assertEquals(true, mPersistedState.batteryLow);
mDevice.setBatteryLevel(30);
assertEquals(false, mDevice.batterySaverEnabled);
assertEquals(30, mPersistedState.batteryLevel);
assertEquals(true, mPersistedState.batteryLow);
// Plug in and out, snooze will reset.
mDevice.setPowered(true);
mDevice.setPowered(false);
assertEquals(true, mDevice.batterySaverEnabled);
assertEquals(30, mPersistedState.batteryLevel);
assertEquals(true, mPersistedState.batteryLow);
mDevice.setPowered(true);
mDevice.setBatteryLevel(60);
assertEquals(false, mDevice.batterySaverEnabled);
assertEquals(60, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
mDevice.setPowered(false);
assertEquals(false, mDevice.batterySaverEnabled);
assertEquals(60, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
mDevice.setBatteryLevel(50);
assertEquals(true, mDevice.batterySaverEnabled);
assertEquals(50, mPersistedState.batteryLevel);
assertEquals(true, mPersistedState.batteryLow);
mDevice.setBatteryLevel(70);
assertEquals(false, mDevice.batterySaverEnabled);
assertEquals(70, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
// Bump ump the threshold.
mDevice.putGlobalSetting(Global.LOW_POWER_MODE_TRIGGER_LEVEL, 70);
mDevice.setBatteryLevel(mPersistedState.batteryLevel);
assertEquals(true, mDevice.batterySaverEnabled);
assertEquals(70, mPersistedState.batteryLevel);
assertEquals(true, mPersistedState.batteryLow);
// Then down.
mDevice.putGlobalSetting(Global.LOW_POWER_MODE_TRIGGER_LEVEL, 60);
mDevice.setBatteryLevel(mPersistedState.batteryLevel);
assertEquals(false, mDevice.batterySaverEnabled);
assertEquals(70, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
// Reboot in low state -> automatically enable BS.
mDevice.setPowered(false);
mDevice.setBatteryLevel(30);
mTarget.setBatterySaverEnabledManually(false);
assertEquals(false, mDevice.batterySaverEnabled);
assertEquals(30, mPersistedState.batteryLevel);
assertEquals(true, mPersistedState.batteryLow);
initDevice();
assertEquals(true, mDevice.batterySaverEnabled);
assertEquals(30, mPersistedState.batteryLevel);
assertEquals(true, mPersistedState.batteryLow);
}
@Test
public void testAutoBatterySaver_withSticky() {
mDevice.putGlobalSetting(Global.LOW_POWER_MODE_TRIGGER_LEVEL, 50);
mTarget.setBatterySaverEnabledManually(true);
assertEquals(true, mDevice.batterySaverEnabled);
assertEquals(100, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
mDevice.setBatteryLevel(30);
assertEquals(true, mDevice.batterySaverEnabled);
assertEquals(30, mPersistedState.batteryLevel);
assertEquals(true, mPersistedState.batteryLow);
mDevice.setBatteryLevel(80);
assertEquals(true, mDevice.batterySaverEnabled); // Still enabled.
assertEquals(80, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
mDevice.setPowered(true);
assertEquals(false, mDevice.batterySaverEnabled);
assertEquals(80, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
mDevice.setBatteryLevel(30);
assertEquals(false, mDevice.batterySaverEnabled);
assertEquals(30, mPersistedState.batteryLevel);
assertEquals(true, mPersistedState.batteryLow);
mDevice.setPowered(false);
assertEquals(true, mDevice.batterySaverEnabled); // Restores BS.
assertEquals(30, mPersistedState.batteryLevel);
assertEquals(true, mPersistedState.batteryLow);
mDevice.setPowered(true);
mDevice.setBatteryLevel(90);
assertEquals(false, mDevice.batterySaverEnabled);
assertEquals(90, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
initDevice();
assertEquals(false, mDevice.batterySaverEnabled);
assertEquals(90, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
mDevice.setPowered(false);
assertEquals(true, mDevice.batterySaverEnabled);
assertEquals(90, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
mTarget.setBatterySaverEnabledManually(false);
assertEquals(false, mDevice.batterySaverEnabled);
assertEquals(90, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
initDevice();
assertEquals(false, mDevice.batterySaverEnabled);
assertEquals(90, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
}
@Test
public void testAutoBatterySaver_withStickyDisabled() {
when(mMockResources.getBoolean(
com.android.internal.R.bool.config_batterySaverStickyBehaviourDisabled))
.thenReturn(true);
initDevice();
mDevice.putGlobalSetting(Global.LOW_POWER_MODE_TRIGGER_LEVEL, 50);
mTarget.setBatterySaverEnabledManually(true);
assertEquals(true, mDevice.batterySaverEnabled);
assertEquals(100, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
mDevice.setBatteryLevel(30);
assertEquals(true, mDevice.batterySaverEnabled);
assertEquals(30, mPersistedState.batteryLevel);
assertEquals(true, mPersistedState.batteryLow);
mDevice.setBatteryLevel(80);
assertEquals(false, mDevice.batterySaverEnabled); // Not sticky.
assertEquals(80, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
mDevice.setPowered(true);
assertEquals(false, mDevice.batterySaverEnabled);
assertEquals(80, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
mDevice.setBatteryLevel(30);
assertEquals(false, mDevice.batterySaverEnabled);
assertEquals(30, mPersistedState.batteryLevel);
assertEquals(true, mPersistedState.batteryLow);
mDevice.setPowered(false);
assertEquals(true, mDevice.batterySaverEnabled); // Restores BS.
assertEquals(30, mPersistedState.batteryLevel);
assertEquals(true, mPersistedState.batteryLow);
mDevice.setPowered(true);
mDevice.setBatteryLevel(90);
assertEquals(false, mDevice.batterySaverEnabled);
assertEquals(90, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
initDevice();
assertEquals(false, mDevice.batterySaverEnabled);
assertEquals(90, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
mDevice.setPowered(false);
assertEquals(false, mDevice.batterySaverEnabled);
assertEquals(90, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
mTarget.setBatterySaverEnabledManually(false);
assertEquals(false, mDevice.batterySaverEnabled);
assertEquals(90, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
initDevice();
assertEquals(false, mDevice.batterySaverEnabled);
assertEquals(90, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
}
@Test
public void testNoAutoBatterySaver_fromAdb() {
assertEquals(0, mDevice.getLowPowerModeTriggerLevel());
assertEquals(false, mDevice.batterySaverEnabled);
assertEquals(100, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
mDevice.setBatteryLevel(90);
assertEquals(false, mDevice.batterySaverEnabled);
assertEquals(90, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
// Enable
mDevice.putGlobalSetting(Global.LOW_POWER_MODE, 1);
assertEquals(true, mDevice.batterySaverEnabled);
assertEquals(90, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
// Disable
mDevice.putGlobalSetting(Global.LOW_POWER_MODE, 0);
assertEquals(false, mDevice.batterySaverEnabled);
assertEquals(90, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
// Enable again
mDevice.putGlobalSetting(Global.LOW_POWER_MODE, 1);
assertEquals(true, mDevice.batterySaverEnabled);
assertEquals(90, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
// Reboot -- setting BS from adb is also sticky.
initDevice();
assertEquals(true, mDevice.batterySaverEnabled);
assertEquals(90, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
}
}