diff options
| author | 2017-11-15 21:32:50 +0000 | |
|---|---|---|
| committer | 2017-11-15 21:32:50 +0000 | |
| commit | a786f00f069c33aab5171f615cb3e35ed1755ede (patch) | |
| tree | bd3584aa0d1609ae717662b47996b77a9e18ba15 | |
| parent | 0620c306511cf12d85a8bc8fba97145cd8cd0a21 (diff) | |
| parent | 66a7812bc90b5be7702fe5743d257fddcdb48201 (diff) | |
Merge "Refactor battery saver logic + add "per device" setting"
11 files changed, 642 insertions, 135 deletions
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 16e7f30e9c76..6decc3050f64 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -9478,6 +9478,16 @@ public final class Settings { public static final String BATTERY_SAVER_CONSTANTS = "battery_saver_constants"; /** + * Battery Saver device specific settings + * This is encoded as a key=value list, separated by commas. + * See {@link com.android.server.power.BatterySaverPolicy} for the details. + * + * @hide + */ + public static final String BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS = + "battery_saver_device_specific_constants"; + + /** * Battery anomaly detection specific settings * This is encoded as a key=value list, separated by commas. * wakeup_blacklisted_tags is a string, encoded as a set of tags, encoded via diff --git a/core/java/android/util/KeyValueListParser.java b/core/java/android/util/KeyValueListParser.java index be531ff35991..d50395e223e5 100644 --- a/core/java/android/util/KeyValueListParser.java +++ b/core/java/android/util/KeyValueListParser.java @@ -147,4 +147,18 @@ public class KeyValueListParser { } return def; } + + /** + * @return the number of keys. + */ + public int size() { + return mValues.size(); + } + + /** + * @return the key at {@code index}. Use with {@link #size()} to enumerate all key-value pairs. + */ + public String keyAt(int index) { + return mValues.keyAt(index); + } } diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 1879ab719bb7..edd793d89afa 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3164,4 +3164,6 @@ <!-- Corner radius of system dialogs --> <dimen name="config_dialogCornerRadius">2dp</dimen> + + <string translatable="false" name="config_batterySaverDeviceSpecificConfig"></string> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index e476d893b900..54970851f3c1 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3150,4 +3150,5 @@ <!-- From media projection --> <java-symbol type="string" name="config_mediaProjectionPermissionDialogComponent" /> + <java-symbol type="string" name="config_batterySaverDeviceSpecificConfig" /> </resources> diff --git a/services/core/java/com/android/server/power/BatterySaverPolicy.java b/services/core/java/com/android/server/power/BatterySaverPolicy.java index 0c73fe85d08e..15121b809f95 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 @@ public class BatterySaverPolicy extends ContentObserver { 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 @@ public class BatterySaverPolicy extends ContentObserver { */ 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( + 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; + } - mContentResolver.registerContentObserver(Settings.Global.getUriFor( - Settings.Global.BATTERY_SAVER_CONSTANTS), false, this); + @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 @@ public class BatterySaverPolicy extends ContentObserver { * @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 class BatterySaverPolicy extends ContentObserver { } } + public ArrayMap<String, String> getFileValues(boolean screenOn) { + synchronized (mLock) { + return screenOn ? mScreenOnFiles : mScreenOffFiles; + } + } + 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)); - - 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); + 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 a153fdfb67c3..a47b8095dae7 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.IVrManager; 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.TimeUtils; 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.Watchdog; 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 @@ public final class PowerManagerService extends SystemService 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 @@ public final class PowerManagerService extends SystemService // 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 @@ public final class PowerManagerService extends SystemService 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 @@ public final class PowerManagerService extends SystemService 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 @@ public final class PowerManagerService extends SystemService mAmbientDisplayConfiguration = new AmbientDisplayConfiguration(mContext); mDisplaySuspendBlocker = null; mWakeLockSuspendBlocker = null; + + mBatterySaverPolicy = batterySaverPolicy; + mBatterySaverController = new BatterySaverController(context, + BackgroundThread.getHandler().getLooper(), batterySaverPolicy); } @Override @@ -752,6 +752,7 @@ public final class PowerManagerService extends SystemService mDisplayManagerInternal.initPowerManagement( mDisplayPowerCallbacks, mHandler, sensorManager); + // Go. readConfigurationLocked(); updateSettingsLocked(); @@ -761,7 +762,9 @@ public final class PowerManagerService extends SystemService 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 @@ public final class PowerManagerService extends SystemService 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 @@ public final class PowerManagerService extends SystemService mIsVrModeEnabled = enabled; } - private void powerHintInternal(int hintId, int data) { + public static void powerHintInternal(int hintId, int data) { nativeSendPowerHint(hintId, data); } @@ -4405,7 +4374,7 @@ public final class PowerManagerService extends SystemService * 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 @@ public final class PowerManagerService extends SystemService @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 000000000000..b3e853838069 --- /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 000000000000..cfe8fc490e0a --- /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 1253d448f3c9..3ac56bb5d8bc 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 50ac41cdf417..5b6225e79f8e 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 @@ package com.android.server.power; 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 @@ public class BatterySaverPolicyTest extends AndroidTestCase { 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 @@ public class BatterySaverPolicyTest extends AndroidTestCase { + "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 @@ public class BatterySaverPolicyTest extends AndroidTestCase { @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 @@ public class BatterySaverPolicyTest extends AndroidTestCase { @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 @@ public class BatterySaverPolicyTest extends AndroidTestCase { @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 @@ public class BatterySaverPolicyTest extends AndroidTestCase { 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 b60d5bf90766..5039e424c4dc 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} */ |