Refactor battery saver logic + add "per device" setting
- Extract the battery saver mode transition logic to BatterySaverController.
This now also supports running different code when screen turns on and off.
- BatterySaverPolicy now takes a "per-device configuration" from config.xml,
which can be overwritten via a global setting. We'll use this to set up
max CPU frequencies.
- The actual part to write max CPU frequencies is not finished yet.
Test: atest BatterySaverPolicyTest
Bug: 68769804
Change-Id: Ife38c2cd94ac9902911b005dbbca8b0d0a62e6d7
diff --git a/services/core/java/com/android/server/power/BatterySaverPolicy.java b/services/core/java/com/android/server/power/BatterySaverPolicy.java
index 0c73fe8..15121b8 100644
--- a/services/core/java/com/android/server/power/BatterySaverPolicy.java
+++ b/services/core/java/com/android/server/power/BatterySaverPolicy.java
@@ -15,25 +15,31 @@
*/
package com.android.server.power;
-import android.annotation.IntDef;
import android.content.ContentResolver;
+import android.content.Context;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
-import android.os.PowerManager;
import android.os.PowerManager.ServiceType;
+import android.os.PowerSaveState;
import android.provider.Settings;
+import android.provider.Settings.Global;
+import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.KeyValueListParser;
import android.util.Slog;
-import android.os.PowerSaveState;
+
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.R;
import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
/**
* Class to decide whether to turn on battery saver mode for specific service
+ *
+ * Test: atest BatterySaverPolicyTest
*/
public class BatterySaverPolicy extends ContentObserver {
private static final String TAG = "BatterySaverPolicy";
@@ -60,7 +66,12 @@
private static final String KEY_FORCE_ALL_APPS_STANDBY_ALARMS = "force_all_apps_standby_alarms";
private static final String KEY_OPTIONAL_SENSORS_DISABLED = "optional_sensors_disabled";
- private final KeyValueListParser mParser = new KeyValueListParser(',');
+ private static final String KEY_SCREEN_ON_FILE_PREFIX = "file-on:";
+ private static final String KEY_SCREEN_OFF_FILE_PREFIX = "file-off:";
+
+ private static String mSettings;
+ private static String mDeviceSpecificSettings;
+ private static String mDeviceSpecificSettingsSource; // For dump() only.
/**
* {@code true} if vibration is disabled in battery saver mode.
@@ -159,55 +170,174 @@
*/
private boolean mOptionalSensorsDisabled;
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private Context mContext;
+
+ @GuardedBy("mLock")
private ContentResolver mContentResolver;
+ @GuardedBy("mLock")
+ private final ArrayList<BatterySaverPolicyListener> mListeners = new ArrayList<>();
+
+ /**
+ * List of [Filename -> content] that should be written when battery saver is activated
+ * and the screen is on.
+ *
+ * We use this to change the max CPU frequencies.
+ */
+ @GuardedBy("mLock")
+ private ArrayMap<String, String> mScreenOnFiles;
+
+ /**
+ * List of [Filename -> content] that should be written when battery saver is activated
+ * and the screen is off.
+ *
+ * We use this to change the max CPU frequencies.
+ */
+ @GuardedBy("mLock")
+ private ArrayMap<String, String> mScreenOffFiles;
+
+ public interface BatterySaverPolicyListener {
+ void onBatterySaverPolicyChanged(BatterySaverPolicy policy);
+ }
+
public BatterySaverPolicy(Handler handler) {
super(handler);
}
- public void start(ContentResolver contentResolver) {
- mContentResolver = contentResolver;
+ public void systemReady(Context context) {
+ synchronized (mLock) {
+ mContext = context;
+ mContentResolver = context.getContentResolver();
- mContentResolver.registerContentObserver(Settings.Global.getUriFor(
- Settings.Global.BATTERY_SAVER_CONSTANTS), false, this);
+ mContentResolver.registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.BATTERY_SAVER_CONSTANTS), false, this);
+ mContentResolver.registerContentObserver(Settings.Global.getUriFor(
+ Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS), false, this);
+ }
+ onChange(true, null);
+ }
+
+ public void addListener(BatterySaverPolicyListener listener) {
+ synchronized (mLock) {
+ mListeners.add(listener);
+ }
+ }
+
+ @VisibleForTesting
+ String getGlobalSetting(String key) {
+ return Settings.Global.getString(mContentResolver, key);
+ }
+
+ @VisibleForTesting
+ int getDeviceSpecificConfigResId() {
+ return R.string.config_batterySaverDeviceSpecificConfig;
+ }
+
+ @VisibleForTesting
+ void onChangeForTest() {
onChange(true, null);
}
@Override
public void onChange(boolean selfChange, Uri uri) {
- final String value = Settings.Global.getString(mContentResolver,
- Settings.Global.BATTERY_SAVER_CONSTANTS);
- updateConstants(value);
+ final BatterySaverPolicyListener[] listeners;
+ synchronized (mLock) {
+ // Load the non-device-specific setting.
+ final String setting = getGlobalSetting(Settings.Global.BATTERY_SAVER_CONSTANTS);
+
+ // Load the device specific setting.
+ // We first check the global setting, and if it's empty or the string "null" is set,
+ // use the default value from config.xml.
+ String deviceSpecificSetting = getGlobalSetting(
+ Settings.Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS);
+ mDeviceSpecificSettingsSource =
+ Settings.Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS;
+
+ if (TextUtils.isEmpty(deviceSpecificSetting) || "null".equals(deviceSpecificSetting)) {
+ deviceSpecificSetting =
+ mContext.getString(getDeviceSpecificConfigResId());
+ mDeviceSpecificSettingsSource = "(overlay)";
+ }
+
+ // Update.
+ updateConstantsLocked(setting, deviceSpecificSetting);
+
+ listeners = mListeners.toArray(new BatterySaverPolicyListener[mListeners.size()]);
+ }
+
+ // Notify the listeners.
+ for (BatterySaverPolicyListener listener : listeners) {
+ listener.onBatterySaverPolicyChanged(this);
+ }
}
@VisibleForTesting
- void updateConstants(final String value) {
- synchronized (BatterySaverPolicy.this) {
- try {
- mParser.setString(value);
- } catch (IllegalArgumentException e) {
- Slog.e(TAG, "Bad battery saver constants");
+ void updateConstantsLocked(final String setting, final String deviceSpecificSetting) {
+ mSettings = setting;
+ mDeviceSpecificSettings = deviceSpecificSetting;
+
+ final KeyValueListParser parser = new KeyValueListParser(',');
+
+ // Non-device-specific parameters.
+ try {
+ parser.setString(setting);
+ } catch (IllegalArgumentException e) {
+ Slog.wtf(TAG, "Bad battery saver constants: " + setting);
+ }
+
+ mVibrationDisabled = parser.getBoolean(KEY_VIBRATION_DISABLED, true);
+ mAnimationDisabled = parser.getBoolean(KEY_ANIMATION_DISABLED, true);
+ mSoundTriggerDisabled = parser.getBoolean(KEY_SOUNDTRIGGER_DISABLED, true);
+ mFullBackupDeferred = parser.getBoolean(KEY_FULLBACKUP_DEFERRED, true);
+ mKeyValueBackupDeferred = parser.getBoolean(KEY_KEYVALUE_DEFERRED, true);
+ mFireWallDisabled = parser.getBoolean(KEY_FIREWALL_DISABLED, false);
+ mAdjustBrightnessDisabled = parser.getBoolean(KEY_ADJUST_BRIGHTNESS_DISABLED, false);
+ mAdjustBrightnessFactor = parser.getFloat(KEY_ADJUST_BRIGHTNESS_FACTOR, 0.5f);
+ mDataSaverDisabled = parser.getBoolean(KEY_DATASAVER_DISABLED, true);
+ mForceAllAppsStandbyJobs = parser.getBoolean(KEY_FORCE_ALL_APPS_STANDBY_JOBS, true);
+ mForceAllAppsStandbyAlarms =
+ parser.getBoolean(KEY_FORCE_ALL_APPS_STANDBY_ALARMS, true);
+ mOptionalSensorsDisabled = parser.getBoolean(KEY_OPTIONAL_SENSORS_DISABLED, true);
+
+ // Get default value from Settings.Secure
+ final int defaultGpsMode = Settings.Secure.getInt(mContentResolver, SECURE_KEY_GPS_MODE,
+ GPS_MODE_NO_CHANGE);
+ mGpsMode = parser.getInt(KEY_GPS_MODE, defaultGpsMode);
+
+ // Non-device-specific parameters.
+ try {
+ parser.setString(deviceSpecificSetting);
+ } catch (IllegalArgumentException e) {
+ Slog.wtf(TAG, "Bad device specific battery saver constants: "
+ + deviceSpecificSetting);
+ }
+
+ mScreenOnFiles = collectParams(parser, KEY_SCREEN_ON_FILE_PREFIX);
+ mScreenOffFiles = collectParams(parser, KEY_SCREEN_OFF_FILE_PREFIX);
+ }
+
+ private static ArrayMap<String, String> collectParams(
+ KeyValueListParser parser, String prefix) {
+ final ArrayMap<String, String> ret = new ArrayMap<>();
+
+ for (int i = parser.size() - 1; i >= 0; i--) {
+ final String key = parser.keyAt(i);
+ if (!key.startsWith(prefix)) {
+ continue;
+ }
+ final String path = key.substring(prefix.length());
+
+ if (!(path.startsWith("/sys/") || path.startsWith("/proc"))) {
+ Slog.wtf(TAG, "Invalid path: " + path);
+ continue;
}
- mVibrationDisabled = mParser.getBoolean(KEY_VIBRATION_DISABLED, true);
- mAnimationDisabled = mParser.getBoolean(KEY_ANIMATION_DISABLED, true);
- mSoundTriggerDisabled = mParser.getBoolean(KEY_SOUNDTRIGGER_DISABLED, true);
- mFullBackupDeferred = mParser.getBoolean(KEY_FULLBACKUP_DEFERRED, true);
- mKeyValueBackupDeferred = mParser.getBoolean(KEY_KEYVALUE_DEFERRED, true);
- mFireWallDisabled = mParser.getBoolean(KEY_FIREWALL_DISABLED, false);
- mAdjustBrightnessDisabled = mParser.getBoolean(KEY_ADJUST_BRIGHTNESS_DISABLED, false);
- mAdjustBrightnessFactor = mParser.getFloat(KEY_ADJUST_BRIGHTNESS_FACTOR, 0.5f);
- mDataSaverDisabled = mParser.getBoolean(KEY_DATASAVER_DISABLED, true);
- mForceAllAppsStandbyJobs = mParser.getBoolean(KEY_FORCE_ALL_APPS_STANDBY_JOBS, true);
- mForceAllAppsStandbyAlarms =
- mParser.getBoolean(KEY_FORCE_ALL_APPS_STANDBY_ALARMS, true);
- mOptionalSensorsDisabled = mParser.getBoolean(KEY_OPTIONAL_SENSORS_DISABLED, true);
-
- // Get default value from Settings.Secure
- final int defaultGpsMode = Settings.Secure.getInt(mContentResolver, SECURE_KEY_GPS_MODE,
- GPS_MODE_NO_CHANGE);
- mGpsMode = mParser.getInt(KEY_GPS_MODE, defaultGpsMode);
+ ret.put(path, parser.getString(key, ""));
}
+ return ret;
}
/**
@@ -220,7 +350,7 @@
* @return State data that contains battery saver data
*/
public PowerSaveState getBatterySaverPolicy(@ServiceType int type, boolean realMode) {
- synchronized (BatterySaverPolicy.this) {
+ synchronized (mLock) {
final PowerSaveState.Builder builder = new PowerSaveState.Builder()
.setGlobalBatterySaverEnabled(realMode);
if (!realMode) {
@@ -273,25 +403,57 @@
}
}
- public void dump(PrintWriter pw) {
- pw.println();
- pw.println("Battery saver policy");
- pw.println(" Settings " + Settings.Global.BATTERY_SAVER_CONSTANTS);
- pw.println(" value: " + Settings.Global.getString(mContentResolver,
- Settings.Global.BATTERY_SAVER_CONSTANTS));
+ public ArrayMap<String, String> getFileValues(boolean screenOn) {
+ synchronized (mLock) {
+ return screenOn ? mScreenOnFiles : mScreenOffFiles;
+ }
+ }
- pw.println();
- pw.println(" " + KEY_VIBRATION_DISABLED + "=" + mVibrationDisabled);
- pw.println(" " + KEY_ANIMATION_DISABLED + "=" + mAnimationDisabled);
- pw.println(" " + KEY_FULLBACKUP_DEFERRED + "=" + mFullBackupDeferred);
- pw.println(" " + KEY_KEYVALUE_DEFERRED + "=" + mKeyValueBackupDeferred);
- pw.println(" " + KEY_FIREWALL_DISABLED + "=" + mFireWallDisabled);
- pw.println(" " + KEY_DATASAVER_DISABLED + "=" + mDataSaverDisabled);
- pw.println(" " + KEY_ADJUST_BRIGHTNESS_DISABLED + "=" + mAdjustBrightnessDisabled);
- pw.println(" " + KEY_ADJUST_BRIGHTNESS_FACTOR + "=" + mAdjustBrightnessFactor);
- pw.println(" " + KEY_GPS_MODE + "=" + mGpsMode);
- pw.println(" " + KEY_FORCE_ALL_APPS_STANDBY_JOBS + "=" + mForceAllAppsStandbyJobs);
- pw.println(" " + KEY_FORCE_ALL_APPS_STANDBY_ALARMS + "=" + mForceAllAppsStandbyAlarms);
- pw.println(" " + KEY_OPTIONAL_SENSORS_DISABLED + "=" + mOptionalSensorsDisabled);
+ public void dump(PrintWriter pw) {
+ synchronized (mLock) {
+ pw.println();
+ pw.println("Battery saver policy");
+ pw.println(" Settings " + Settings.Global.BATTERY_SAVER_CONSTANTS);
+ pw.println(" value: " + mSettings);
+ pw.println(" Settings " + mDeviceSpecificSettingsSource);
+ pw.println(" value: " + mDeviceSpecificSettings);
+
+ pw.println();
+ pw.println(" " + KEY_VIBRATION_DISABLED + "=" + mVibrationDisabled);
+ pw.println(" " + KEY_ANIMATION_DISABLED + "=" + mAnimationDisabled);
+ pw.println(" " + KEY_FULLBACKUP_DEFERRED + "=" + mFullBackupDeferred);
+ pw.println(" " + KEY_KEYVALUE_DEFERRED + "=" + mKeyValueBackupDeferred);
+ pw.println(" " + KEY_FIREWALL_DISABLED + "=" + mFireWallDisabled);
+ pw.println(" " + KEY_DATASAVER_DISABLED + "=" + mDataSaverDisabled);
+ pw.println(" " + KEY_ADJUST_BRIGHTNESS_DISABLED + "=" + mAdjustBrightnessDisabled);
+ pw.println(" " + KEY_ADJUST_BRIGHTNESS_FACTOR + "=" + mAdjustBrightnessFactor);
+ pw.println(" " + KEY_GPS_MODE + "=" + mGpsMode);
+ pw.println(" " + KEY_FORCE_ALL_APPS_STANDBY_JOBS + "=" + mForceAllAppsStandbyJobs);
+ pw.println(" " + KEY_FORCE_ALL_APPS_STANDBY_ALARMS + "=" + mForceAllAppsStandbyAlarms);
+ pw.println(" " + KEY_OPTIONAL_SENSORS_DISABLED + "=" + mOptionalSensorsDisabled);
+ pw.println();
+
+ pw.print(" Screen On Files:\n");
+ dumpMap(pw, " ", mScreenOnFiles);
+ pw.println();
+
+ pw.print(" Screen Off Files:\n");
+ dumpMap(pw, " ", mScreenOffFiles);
+ pw.println();
+ }
+ }
+
+ private void dumpMap(PrintWriter pw, String prefix, ArrayMap<String, String> map) {
+ if (map == null) {
+ return;
+ }
+ final int size = map.size();
+ for (int i = 0; i < size; i++) {
+ pw.print(prefix);
+ pw.print(map.keyAt(i));
+ pw.print(": '");
+ pw.print(map.valueAt(i));
+ pw.println("'");
+ }
}
}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index a153fdf..a47b809 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -63,7 +63,6 @@
import android.service.vr.IVrStateCallbacks;
import android.util.EventLog;
import android.util.KeyValueListParser;
-import android.util.Log;
import android.util.PrintWriterPrinter;
import android.util.Slog;
import android.util.SparseArray;
@@ -71,7 +70,6 @@
import android.util.proto.ProtoOutputStream;
import android.view.Display;
import android.view.WindowManagerPolicy;
-import android.widget.Toast;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IAppOpsService;
@@ -92,10 +90,8 @@
import com.android.server.am.BatteryStatsService;
import com.android.server.lights.Light;
import com.android.server.lights.LightsManager;
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileReader;
-import java.io.IOException;
+import com.android.server.power.batterysaver.BatterySaverController;
+
import libcore.util.Objects;
import java.io.FileDescriptor;
@@ -228,6 +224,7 @@
private final PowerManagerHandler mHandler;
private final AmbientDisplayConfiguration mAmbientDisplayConfiguration;
private final BatterySaverPolicy mBatterySaverPolicy;
+ private final BatterySaverController mBatterySaverController;
private LightsManager mLightsManager;
private BatteryManagerInternal mBatteryManagerInternal;
@@ -555,9 +552,6 @@
// True if double tap to wake is enabled
private boolean mDoubleTapWakeEnabled;
- private final ArrayList<PowerManagerInternal.LowPowerModeListener> mLowPowerModeListeners
- = new ArrayList<PowerManagerInternal.LowPowerModeListener>();
-
// True if we are currently in VR Mode.
private boolean mIsVrModeEnabled;
@@ -645,7 +639,10 @@
mHandler = new PowerManagerHandler(mHandlerThread.getLooper());
mConstants = new Constants(mHandler);
mAmbientDisplayConfiguration = new AmbientDisplayConfiguration(mContext);
+
mBatterySaverPolicy = new BatterySaverPolicy(mHandler);
+ mBatterySaverController = new BatterySaverController(mContext,
+ BackgroundThread.get().getLooper(), mBatterySaverPolicy);
synchronized (mLock) {
mWakeLockSuspendBlocker = createSuspendBlockerLocked("PowerManagerService.WakeLocks");
@@ -670,7 +667,6 @@
PowerManagerService(Context context, BatterySaverPolicy batterySaverPolicy) {
super(context);
- mBatterySaverPolicy = batterySaverPolicy;
mContext = context;
mHandlerThread = new ServiceThread(TAG,
Process.THREAD_PRIORITY_DISPLAY, false /*allowIo*/);
@@ -680,6 +676,10 @@
mAmbientDisplayConfiguration = new AmbientDisplayConfiguration(mContext);
mDisplaySuspendBlocker = null;
mWakeLockSuspendBlocker = null;
+
+ mBatterySaverPolicy = batterySaverPolicy;
+ mBatterySaverController = new BatterySaverController(context,
+ BackgroundThread.getHandler().getLooper(), batterySaverPolicy);
}
@Override
@@ -752,6 +752,7 @@
mDisplayManagerInternal.initPowerManagement(
mDisplayPowerCallbacks, mHandler, sensorManager);
+
// Go.
readConfigurationLocked();
updateSettingsLocked();
@@ -761,7 +762,9 @@
final ContentResolver resolver = mContext.getContentResolver();
mConstants.start(resolver);
- mBatterySaverPolicy.start(resolver);
+
+ mBatterySaverController.systemReady();
+ mBatterySaverPolicy.systemReady(mContext);
// Register for settings changes.
resolver.registerContentObserver(Settings.Secure.getUriFor(
@@ -996,43 +999,9 @@
if (mLowPowerModeEnabled != lowPowerModeEnabled) {
mLowPowerModeEnabled = lowPowerModeEnabled;
- powerHintInternal(PowerHint.LOW_POWER, lowPowerModeEnabled ? 1 : 0);
- postAfterBootCompleted(new Runnable() {
- @Override
- public void run() {
- Intent intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGING)
- .putExtra(PowerManager.EXTRA_POWER_SAVE_MODE, mLowPowerModeEnabled)
- .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- mContext.sendBroadcast(intent);
- ArrayList<PowerManagerInternal.LowPowerModeListener> listeners;
- synchronized (mLock) {
- listeners = new ArrayList<PowerManagerInternal.LowPowerModeListener>(
- mLowPowerModeListeners);
- }
- for (int i = 0; i < listeners.size(); i++) {
- final PowerManagerInternal.LowPowerModeListener listener = listeners.get(i);
- final PowerSaveState result =
- mBatterySaverPolicy.getBatterySaverPolicy(
- listener.getServiceType(), lowPowerModeEnabled);
- listener.onLowPowerModeChanged(result);
- }
- intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
- // Send internal version that requires signature permission.
- intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED_INTERNAL);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
- Manifest.permission.DEVICE_POWER);
- // STOPSHIP Remove the toast.
- if (mLowPowerModeEnabled) {
- Toast.makeText(mContext,
- com.android.internal.R.string.battery_saver_warning,
- Toast.LENGTH_LONG).show();
- }
- }
- });
+ postAfterBootCompleted(() ->
+ mBatterySaverController.enableBatterySaver(mLowPowerModeEnabled));
}
}
@@ -3136,7 +3105,7 @@
mIsVrModeEnabled = enabled;
}
- private void powerHintInternal(int hintId, int data) {
+ public static void powerHintInternal(int hintId, int data) {
nativeSendPowerHint(hintId, data);
}
@@ -4405,7 +4374,7 @@
* Gets the reason for the last time the phone had to reboot.
*
* @return The reason the phone last shut down as an int or
- * {@link PowerManager.SHUTDOWN_REASON_UNKNOWN} if the file could not be opened.
+ * {@link PowerManager#SHUTDOWN_REASON_UNKNOWN} if the file could not be opened.
*/
@Override // Binder call
public int getLastShutdownReason() {
@@ -4728,9 +4697,7 @@
@Override
public void registerLowPowerModeObserver(LowPowerModeListener listener) {
- synchronized (mLock) {
- mLowPowerModeListeners.add(listener);
- }
+ mBatterySaverController.addListener(listener);
}
@Override
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
new file mode 100644
index 0000000..b3e8538
--- /dev/null
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2017 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 android.Manifest;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.power.V1_0.PowerHint;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.PowerManagerInternal.LowPowerModeListener;
+import android.os.PowerSaveState;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.widget.Toast;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+import com.android.server.power.BatterySaverPolicy;
+import com.android.server.power.BatterySaverPolicy.BatterySaverPolicyListener;
+import com.android.server.power.PowerManagerService;
+
+import java.util.ArrayList;
+
+/**
+ * Responsible for battery saver mode transition logic.
+ */
+public class BatterySaverController implements BatterySaverPolicyListener {
+ static final String TAG = "BatterySaverController";
+
+ static final boolean DEBUG = false; // DO NOT MERGE WITH TRUE
+
+ private final Object mLock = new Object();
+ private final Context mContext;
+ private final MyHandler mHandler;
+ private final FileUpdater mFileUpdater;
+
+ private PowerManager mPowerManager;
+
+ private final BatterySaverPolicy mBatterySaverPolicy;
+
+ @GuardedBy("mLock")
+ private final ArrayList<LowPowerModeListener> mListeners = new ArrayList<>();
+
+ @GuardedBy("mLock")
+ private boolean mEnabled;
+
+ /**
+ * Keep track of the previous enabled state, which we use to decide when to send broadcasts,
+ * which we don't want to send only when the screen state changes.
+ */
+ @GuardedBy("mLock")
+ private boolean mWasEnabled;
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ switch (intent.getAction()) {
+ case Intent.ACTION_SCREEN_ON:
+ case Intent.ACTION_SCREEN_OFF:
+ mHandler.postStateChanged();
+ break;
+ }
+ }
+ };
+
+ /**
+ * Constructor.
+ */
+ public BatterySaverController(Context context, Looper looper, BatterySaverPolicy policy) {
+ mContext = context;
+ mHandler = new MyHandler(looper);
+ mBatterySaverPolicy = policy;
+ mBatterySaverPolicy.addListener(this);
+ mFileUpdater = new FileUpdater(context);
+ }
+
+ /**
+ * Add a listener.
+ */
+ public void addListener(LowPowerModeListener listener) {
+ synchronized (mLock) {
+ mListeners.add(listener);
+ }
+ }
+
+ /**
+ * Called by {@link PowerManagerService} on system ready..
+ */
+ public void systemReady() {
+ final IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+ mContext.registerReceiver(mReceiver, filter);
+ }
+
+ private PowerManager getPowerManager() {
+ if (mPowerManager == null) {
+ mPowerManager =
+ Preconditions.checkNotNull(mContext.getSystemService(PowerManager.class));
+ }
+ return mPowerManager;
+ }
+
+ @Override
+ public void onBatterySaverPolicyChanged(BatterySaverPolicy policy) {
+ mHandler.postStateChanged();
+ }
+
+ private class MyHandler extends Handler {
+ private final int MSG_STATE_CHANGED = 1;
+
+ public MyHandler(Looper looper) {
+ super(looper);
+ }
+
+ public void postStateChanged() {
+ obtainMessage(MSG_STATE_CHANGED).sendToTarget();
+ }
+
+ @Override
+ public void dispatchMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_STATE_CHANGED:
+ handleBatterySaverStateChanged();
+ break;
+ }
+ }
+ }
+
+ /**
+ * Called by {@link PowerManagerService} to update the battery saver stete.
+ */
+ public void enableBatterySaver(boolean enable) {
+ synchronized (mLock) {
+ if (mEnabled == enable) {
+ return;
+ }
+ mEnabled = enable;
+
+ mHandler.postStateChanged();
+ }
+ }
+
+ /**
+ * Dispatch power save events to the listeners.
+ *
+ * This is always called on the handler thread.
+ */
+ void handleBatterySaverStateChanged() {
+ final LowPowerModeListener[] listeners;
+
+ final boolean wasEnabled;
+ final boolean enabled;
+ final boolean isScreenOn = getPowerManager().isInteractive();
+ final ArrayMap<String, String> fileValues;
+
+ synchronized (mLock) {
+ Slog.i(TAG, "Battery saver enabled: screen on=" + isScreenOn);
+
+ listeners = mListeners.toArray(new LowPowerModeListener[mListeners.size()]);
+ wasEnabled = mWasEnabled;
+ enabled = mEnabled;
+
+ if (enabled) {
+ fileValues = mBatterySaverPolicy.getFileValues(isScreenOn);
+ } else {
+ fileValues = null;
+ }
+ }
+
+ PowerManagerService.powerHintInternal(PowerHint.LOW_POWER, enabled ? 1 : 0);
+
+ if (enabled) {
+ // STOPSHIP Remove the toast.
+ Toast.makeText(mContext,
+ com.android.internal.R.string.battery_saver_warning,
+ Toast.LENGTH_LONG).show();
+ }
+
+ if (fileValues == null || fileValues.size() == 0) {
+ mFileUpdater.restoreDefault();
+ } else {
+ mFileUpdater.writeFiles(fileValues);
+ }
+
+ if (enabled != wasEnabled) {
+ if (DEBUG) {
+ Slog.i(TAG, "Sending broadcasts for mode: " + enabled);
+ }
+
+ // Send the broadcasts and notify the listeners. We only do this when the battery saver
+ // mode changes, but not when only the screen state changes.
+ Intent intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGING)
+ .putExtra(PowerManager.EXTRA_POWER_SAVE_MODE, enabled)
+ .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ mContext.sendBroadcast(intent);
+
+ intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+
+ // Send internal version that requires signature permission.
+ intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED_INTERNAL);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
+ Manifest.permission.DEVICE_POWER);
+
+
+ for (LowPowerModeListener listener : listeners) {
+ final PowerSaveState result =
+ mBatterySaverPolicy.getBatterySaverPolicy(
+ listener.getServiceType(), enabled);
+ listener.onLowPowerModeChanged(result);
+ }
+ }
+
+ synchronized (mLock) {
+ mWasEnabled = enabled;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/power/batterysaver/FileUpdater.java b/services/core/java/com/android/server/power/batterysaver/FileUpdater.java
new file mode 100644
index 0000000..cfe8fc4
--- /dev/null
+++ b/services/core/java/com/android/server/power/batterysaver/FileUpdater.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2017 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 android.content.Context;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+/**
+ * Used by {@link BatterySaverController} to write values to /sys/ (and possibly /proc/ too) files
+ * with retry and to restore the original values.
+ *
+ * TODO Implement it
+ */
+public class FileUpdater {
+ private static final String TAG = BatterySaverController.TAG;
+
+ private static final boolean DEBUG = BatterySaverController.DEBUG;
+
+ private final Object mLock = new Object();
+ private final Context mContext;
+
+ public FileUpdater(Context context) {
+ mContext = context;
+ }
+
+ public void writeFiles(ArrayMap<String, String> fileValues) {
+ if (DEBUG) {
+ final int size = fileValues.size();
+ for (int i = 0; i < size; i++) {
+ Slog.d(TAG, "Writing '" + fileValues.valueAt(i)
+ + "' to '" + fileValues.keyAt(i) + "'");
+ }
+ }
+ }
+
+ public void restoreDefault() {
+ if (DEBUG) {
+ Slog.d(TAG, "Resetting file default values");
+ }
+ }
+}
diff --git a/services/tests/servicestests/res/values/strings.xml b/services/tests/servicestests/res/values/strings.xml
index 1253d44..3ac56bb 100644
--- a/services/tests/servicestests/res/values/strings.xml
+++ b/services/tests/servicestests/res/values/strings.xml
@@ -28,4 +28,8 @@
<string name="test_account_type2_authenticator_label">AccountManagerService Test Account Type2</string>
<string name="test_account_type1">com.android.server.accounts.account_manager_service_test.account.type1</string>
<string name="test_account_type2">com.android.server.accounts.account_manager_service_test.account.type2</string>
+
+ <string name="config_batterySaverDeviceSpecificConfig_1"></string>
+ <string name="config_batterySaverDeviceSpecificConfig_2">file-off:/sys/a=1,file-off:/sys/b=2</string>
+ <string name="config_batterySaverDeviceSpecificConfig_3">file-off:/sys/a=3,file-on:/proc/c=4,/abc=3</string>
</resources>
diff --git a/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java b/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java
index 50ac41c..5b6225e7 100644
--- a/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java
@@ -18,8 +18,13 @@
import android.os.PowerManager.ServiceType;
import android.os.PowerSaveState;
import android.os.Handler;
+import android.provider.Settings;
+import android.provider.Settings.Global;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
+import android.util.ArrayMap;
+
+import com.android.frameworks.servicestests.R;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -36,7 +41,7 @@
private static final float DEFAULT_BRIGHTNESS_FACTOR = 0.5f;
private static final float PRECISION = 0.001f;
private static final int GPS_MODE = 0;
- private static final int DEFAULT_GPS_MODE = 1;
+ private static final int DEFAULT_GPS_MODE = 0;
private static final String BATTERY_SAVER_CONSTANTS = "vibration_disabled=true,"
+ "animation_disabled=false,"
+ "soundtrigger_disabled=true,"
@@ -49,15 +54,34 @@
+ "gps_mode=0";
private static final String BATTERY_SAVER_INCORRECT_CONSTANTS = "vi*,!=,,true";
+ private class BatterySaverPolicyForTest extends BatterySaverPolicy {
+ public BatterySaverPolicyForTest(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ String getGlobalSetting(String key) {
+ return mMockGlobalSettings.get(key);
+ }
+
+ @Override
+ int getDeviceSpecificConfigResId() {
+ return mDeviceSpecificConfigResId;
+ }
+ }
+
@Mock
Handler mHandler;
- private BatterySaverPolicy mBatterySaverPolicy;
+ private BatterySaverPolicyForTest mBatterySaverPolicy;
+
+ private final ArrayMap<String, String> mMockGlobalSettings = new ArrayMap<>();
+ private int mDeviceSpecificConfigResId = R.string.config_batterySaverDeviceSpecificConfig_1;
public void setUp() throws Exception {
super.setUp();
MockitoAnnotations.initMocks(this);
- mBatterySaverPolicy = new BatterySaverPolicy(mHandler);
- mBatterySaverPolicy.start(getContext().getContentResolver());
+ mBatterySaverPolicy = new BatterySaverPolicyForTest(mHandler);
+ mBatterySaverPolicy.systemReady(getContext());
}
@SmallTest
@@ -102,7 +126,7 @@
@SmallTest
public void testGetBatterySaverPolicy_PolicyDataSaver_DefaultValueCorrect() {
- mBatterySaverPolicy.updateConstants("");
+ mBatterySaverPolicy.updateConstantsLocked("", "");
final PowerSaveState batterySaverStateOn =
mBatterySaverPolicy.getBatterySaverPolicy(ServiceType.DATA_SAVER, BATTERY_SAVER_ON);
assertThat(batterySaverStateOn.batterySaverEnabled).isFalse();
@@ -132,7 +156,7 @@
@SmallTest
public void testUpdateConstants_getCorrectData() {
- mBatterySaverPolicy.updateConstants(BATTERY_SAVER_CONSTANTS);
+ mBatterySaverPolicy.updateConstantsLocked(BATTERY_SAVER_CONSTANTS, "");
final PowerSaveState vibrationState =
mBatterySaverPolicy.getBatterySaverPolicy(ServiceType.VIBRATION, BATTERY_SAVER_ON);
@@ -177,12 +201,12 @@
@SmallTest
public void testUpdateConstants_IncorrectData_NotCrash() {
//Should not crash
- mBatterySaverPolicy.updateConstants(BATTERY_SAVER_INCORRECT_CONSTANTS);
- mBatterySaverPolicy.updateConstants(null);
+ mBatterySaverPolicy.updateConstantsLocked(BATTERY_SAVER_INCORRECT_CONSTANTS, "");
+ mBatterySaverPolicy.updateConstantsLocked(null, "");
}
private void testServiceDefaultValue(@ServiceType int type) {
- mBatterySaverPolicy.updateConstants("");
+ mBatterySaverPolicy.updateConstantsLocked("", "");
final PowerSaveState batterySaverStateOn =
mBatterySaverPolicy.getBatterySaverPolicy(type, BATTERY_SAVER_ON);
assertThat(batterySaverStateOn.batterySaverEnabled).isTrue();
@@ -191,4 +215,37 @@
mBatterySaverPolicy.getBatterySaverPolicy(type, BATTERY_SAVER_OFF);
assertThat(batterySaverStateOff.batterySaverEnabled).isFalse();
}
+
+ public void testDeviceSpecific() {
+ mDeviceSpecificConfigResId = R.string.config_batterySaverDeviceSpecificConfig_1;
+ mMockGlobalSettings.put(Global.BATTERY_SAVER_CONSTANTS, "");
+ mMockGlobalSettings.put(Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS, "");
+
+ mBatterySaverPolicy.onChangeForTest();
+ assertThat(mBatterySaverPolicy.getFileValues(true).toString()).isEqualTo("{}");
+ assertThat(mBatterySaverPolicy.getFileValues(false).toString()).isEqualTo("{}");
+
+
+ mDeviceSpecificConfigResId = R.string.config_batterySaverDeviceSpecificConfig_2;
+
+ mBatterySaverPolicy.onChangeForTest();
+ assertThat(mBatterySaverPolicy.getFileValues(true).toString()).isEqualTo("{}");
+ assertThat(mBatterySaverPolicy.getFileValues(false).toString())
+ .isEqualTo("{/sys/a=1, /sys/b=2}");
+
+
+ mDeviceSpecificConfigResId = R.string.config_batterySaverDeviceSpecificConfig_3;
+
+ mBatterySaverPolicy.onChangeForTest();
+ assertThat(mBatterySaverPolicy.getFileValues(true).toString()).isEqualTo("{/proc/c=4}");
+ assertThat(mBatterySaverPolicy.getFileValues(false).toString()).isEqualTo("{/sys/a=3}");
+
+
+ mMockGlobalSettings.put(Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS,
+ "file-on:/proc/z=4");
+
+ mBatterySaverPolicy.onChangeForTest();
+ assertThat(mBatterySaverPolicy.getFileValues(true).toString()).isEqualTo("{/proc/z=4}");
+ assertThat(mBatterySaverPolicy.getFileValues(false).toString()).isEqualTo("{}");
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index b60d5bf..5039e42 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -16,32 +16,28 @@
package com.android.server.power;
-import android.content.Context;
+import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP;
+import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.when;
+
import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
import android.os.PowerManager;
import android.os.PowerSaveState;
import android.os.SystemProperties;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
-import android.text.TextUtils;
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.OutputStreamWriter;
+
+import com.android.server.power.batterysaver.BatterySaverController;
+
import org.junit.Rule;
-import org.junit.rules.TemporaryFolder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP;
-import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE;
-import static android.os.PowerManagerInternal.WAKEFULNESS_DOZING;
-import static android.os.PowerManagerInternal.WAKEFULNESS_DREAMING;
-import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.when;
-
/**
* Tests for {@link com.android.server.power.PowerManagerService}
*/