1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.server.power.batterysaver;
17 
18 import android.content.ContentResolver;
19 import android.content.Context;
20 import android.database.ContentObserver;
21 import android.os.Handler;
22 import android.os.UserHandle;
23 import android.provider.Settings;
24 import android.provider.Settings.Global;
25 import android.util.Slog;
26 import android.util.proto.ProtoOutputStream;
27 
28 import com.android.internal.annotations.GuardedBy;
29 import com.android.internal.annotations.VisibleForTesting;
30 import com.android.internal.os.BackgroundThread;
31 import com.android.server.EventLogTags;
32 import com.android.server.power.BatterySaverPolicy;
33 import com.android.server.power.BatterySaverStateMachineProto;
34 
35 import java.io.PrintWriter;
36 
37 /**
38  * Decides when to enable / disable battery saver.
39  *
40  * IMPORTANT: This class shares the power manager lock, which is very low in the lock hierarchy.
41  * Do not call out with the lock held. (Settings provider is okay.)
42  *
43  * Test:
44   atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java
45  */
46 public class BatterySaverStateMachine {
47     private static final String TAG = "BatterySaverStateMachine";
48     private final Object mLock;
49 
50     private static final boolean DEBUG = BatterySaverPolicy.DEBUG;
51 
52     private final Context mContext;
53     private final BatterySaverController mBatterySaverController;
54 
55     /** Whether the system has booted. */
56     @GuardedBy("mLock")
57     private boolean mBootCompleted;
58 
59     /** Whether global settings have been loaded already. */
60     @GuardedBy("mLock")
61     private boolean mSettingsLoaded;
62 
63     /** Whether the first battery status has arrived. */
64     @GuardedBy("mLock")
65     private boolean mBatteryStatusSet;
66 
67     /** Whether the device is connected to any power source. */
68     @GuardedBy("mLock")
69     private boolean mIsPowered;
70 
71     /** Current battery level in %, 0-100. (Currently only used in dumpsys.) */
72     @GuardedBy("mLock")
73     private int mBatteryLevel;
74 
75     /** Whether the battery level is considered to be "low" or not.*/
76     @GuardedBy("mLock")
77     private boolean mIsBatteryLevelLow;
78 
79     /** Previously known value of Global.LOW_POWER_MODE. */
80     @GuardedBy("mLock")
81     private boolean mSettingBatterySaverEnabled;
82 
83     /** Previously known value of Global.LOW_POWER_MODE_STICKY. */
84     @GuardedBy("mLock")
85     private boolean mSettingBatterySaverEnabledSticky;
86 
87     /**
88      * Previously known value of Global.LOW_POWER_MODE_TRIGGER_LEVEL.
89      * (Currently only used in dumpsys.)
90      */
91     @GuardedBy("mLock")
92     private int mSettingBatterySaverTriggerThreshold;
93 
94     /**
95      * Whether BS has been manually disabled while the battery level is low, in which case we
96      * shouldn't auto re-enable it until the battery level is not low.
97      */
98     @GuardedBy("mLock")
99     private boolean mBatterySaverSnoozing;
100 
101     /**
102      * Last reason passed to {@link #enableBatterySaverLocked}.
103      */
104     @GuardedBy("mLock")
105     private int mLastChangedIntReason;
106 
107     /**
108      * Last reason passed to {@link #enableBatterySaverLocked}.
109      */
110     @GuardedBy("mLock")
111     private String mLastChangedStrReason;
112 
113     private final ContentObserver mSettingsObserver = new ContentObserver(null) {
114         @Override
115         public void onChange(boolean selfChange) {
116             synchronized (mLock) {
117                 refreshSettingsLocked();
118             }
119         }
120     };
121 
BatterySaverStateMachine(Object lock, Context context, BatterySaverController batterySaverController)122     public BatterySaverStateMachine(Object lock,
123             Context context, BatterySaverController batterySaverController) {
124         mLock = lock;
125         mContext = context;
126         mBatterySaverController = batterySaverController;
127     }
128 
isBatterySaverEnabled()129     private boolean isBatterySaverEnabled() {
130         return mBatterySaverController.isEnabled();
131     }
132 
isAutoBatterySaverConfigured()133     private boolean isAutoBatterySaverConfigured() {
134         return mSettingBatterySaverTriggerThreshold > 0;
135     }
136 
137     /**
138      * {@link com.android.server.power.PowerManagerService} calls it when the system is booted.
139      */
onBootCompleted()140     public void onBootCompleted() {
141         if (DEBUG) {
142             Slog.d(TAG, "onBootCompleted");
143         }
144         // Just booted. We don't want LOW_POWER_MODE to be persisted, so just always clear it.
145         putGlobalSetting(Global.LOW_POWER_MODE, 0);
146 
147         // This is called with the power manager lock held. Don't do anything that may call to
148         // upper services. (e.g. don't call into AM directly)
149         // So use a BG thread.
150         runOnBgThread(() -> {
151 
152             final ContentResolver cr = mContext.getContentResolver();
153             cr.registerContentObserver(Settings.Global.getUriFor(
154                     Settings.Global.LOW_POWER_MODE),
155                     false, mSettingsObserver, UserHandle.USER_SYSTEM);
156             cr.registerContentObserver(Settings.Global.getUriFor(
157                     Settings.Global.LOW_POWER_MODE_STICKY),
158                     false, mSettingsObserver, UserHandle.USER_SYSTEM);
159             cr.registerContentObserver(Settings.Global.getUriFor(
160                     Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL),
161                     false, mSettingsObserver, UserHandle.USER_SYSTEM);
162 
163             synchronized (mLock) {
164 
165                 mBootCompleted = true;
166 
167                 refreshSettingsLocked();
168 
169                 doAutoBatterySaverLocked();
170             }
171         });
172     }
173 
174     /**
175      * Run a {@link Runnable} on a background handler.
176      */
177     @VisibleForTesting
runOnBgThread(Runnable r)178     void runOnBgThread(Runnable r) {
179         BackgroundThread.getHandler().post(r);
180     }
181 
182     /**
183      * Run a {@link Runnable} on a background handler, but lazily. If the same {@link Runnable},
184      * it'll be first removed before a new one is posted.
185      */
186     @VisibleForTesting
runOnBgThreadLazy(Runnable r, int delayMillis)187     void runOnBgThreadLazy(Runnable r, int delayMillis) {
188         final Handler h = BackgroundThread.getHandler();
189         h.removeCallbacks(r);
190         h.postDelayed(r, delayMillis);
191     }
192 
refreshSettingsLocked()193     void refreshSettingsLocked() {
194         final boolean lowPowerModeEnabled = getGlobalSetting(
195                 Settings.Global.LOW_POWER_MODE, 0) != 0;
196         final boolean lowPowerModeEnabledSticky = getGlobalSetting(
197                 Settings.Global.LOW_POWER_MODE_STICKY, 0) != 0;
198         final int lowPowerModeTriggerLevel = getGlobalSetting(
199                 Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0);
200 
201         setSettingsLocked(lowPowerModeEnabled, lowPowerModeEnabledSticky,
202                 lowPowerModeTriggerLevel);
203     }
204 
205     /**
206      * {@link com.android.server.power.PowerManagerService} calls it when relevant global settings
207      * have changed.
208      *
209      * Note this will be called before {@link #onBootCompleted} too.
210      */
211     @VisibleForTesting
setSettingsLocked(boolean batterySaverEnabled, boolean batterySaverEnabledSticky, int batterySaverTriggerThreshold)212     void setSettingsLocked(boolean batterySaverEnabled, boolean batterySaverEnabledSticky,
213             int batterySaverTriggerThreshold) {
214         if (DEBUG) {
215             Slog.d(TAG, "setSettings: enabled=" + batterySaverEnabled
216                     + " sticky=" + batterySaverEnabledSticky
217                     + " threshold=" + batterySaverTriggerThreshold);
218         }
219 
220         mSettingsLoaded = true;
221 
222         final boolean enabledChanged = mSettingBatterySaverEnabled != batterySaverEnabled;
223         final boolean stickyChanged =
224                 mSettingBatterySaverEnabledSticky != batterySaverEnabledSticky;
225         final boolean thresholdChanged
226                 = mSettingBatterySaverTriggerThreshold != batterySaverTriggerThreshold;
227 
228         if (!(enabledChanged || stickyChanged || thresholdChanged)) {
229             return;
230         }
231 
232         mSettingBatterySaverEnabled = batterySaverEnabled;
233         mSettingBatterySaverEnabledSticky = batterySaverEnabledSticky;
234         mSettingBatterySaverTriggerThreshold = batterySaverTriggerThreshold;
235 
236         if (thresholdChanged) {
237             // To avoid spamming the event log, we throttle logging here.
238             runOnBgThreadLazy(mThresholdChangeLogger, 2000);
239         }
240 
241         if (enabledChanged) {
242             final String reason = batterySaverEnabled
243                     ? "Global.low_power changed to 1" : "Global.low_power changed to 0";
244             enableBatterySaverLocked(/*enable=*/ batterySaverEnabled, /*manual=*/ true,
245                     BatterySaverController.REASON_SETTING_CHANGED, reason);
246         }
247     }
248 
249     private final Runnable mThresholdChangeLogger = () -> {
250         EventLogTags.writeBatterySaverSetting(mSettingBatterySaverTriggerThreshold);
251     };
252 
253     /**
254      * {@link com.android.server.power.PowerManagerService} calls it when battery state changes.
255      *
256      * Note this may be called before {@link #onBootCompleted} too.
257      */
setBatteryStatus(boolean newPowered, int newLevel, boolean newBatteryLevelLow)258     public void setBatteryStatus(boolean newPowered, int newLevel, boolean newBatteryLevelLow) {
259         if (DEBUG) {
260             Slog.d(TAG, "setBatteryStatus: powered=" + newPowered + " level=" + newLevel
261                     + " low=" + newBatteryLevelLow);
262         }
263         synchronized (mLock) {
264             mBatteryStatusSet = true;
265 
266             final boolean poweredChanged = mIsPowered != newPowered;
267             final boolean levelChanged = mBatteryLevel != newLevel;
268             final boolean lowChanged = mIsBatteryLevelLow != newBatteryLevelLow;
269 
270             if (!(poweredChanged || levelChanged || lowChanged)) {
271                 return;
272             }
273 
274             mIsPowered = newPowered;
275             mBatteryLevel = newLevel;
276             mIsBatteryLevelLow = newBatteryLevelLow;
277 
278             doAutoBatterySaverLocked();
279         }
280     }
281 
282     /**
283      * Decide whether to auto-start / stop battery saver.
284      */
doAutoBatterySaverLocked()285     private void doAutoBatterySaverLocked() {
286         if (DEBUG) {
287             Slog.d(TAG, "doAutoBatterySaverLocked: mBootCompleted=" + mBootCompleted
288                     + " mSettingsLoaded=" + mSettingsLoaded
289                     + " mBatteryStatusSet=" + mBatteryStatusSet
290                     + " mIsBatteryLevelLow=" + mIsBatteryLevelLow
291                     + " mBatterySaverSnoozing=" + mBatterySaverSnoozing
292                     + " mIsPowered=" + mIsPowered
293                     + " mSettingBatterySaverEnabledSticky=" + mSettingBatterySaverEnabledSticky);
294         }
295         if (!(mBootCompleted && mSettingsLoaded && mBatteryStatusSet)) {
296             return; // Not fully initialized yet.
297         }
298         if (!mIsBatteryLevelLow) {
299             updateSnoozingLocked(false, "Battery not low");
300         }
301         if (mIsPowered) {
302             updateSnoozingLocked(false, "Plugged in");
303             enableBatterySaverLocked(/*enable=*/ false, /*manual=*/ false,
304                     BatterySaverController.REASON_PLUGGED_IN,
305                     "Plugged in");
306 
307         } else if (mSettingBatterySaverEnabledSticky) {
308             // Re-enable BS.
309             enableBatterySaverLocked(/*enable=*/ true, /*manual=*/ true,
310                     BatterySaverController.REASON_STICKY_RESTORE,
311                     "Sticky restore");
312 
313         } else if (mIsBatteryLevelLow) {
314             if (!mBatterySaverSnoozing && isAutoBatterySaverConfigured()) {
315                 enableBatterySaverLocked(/*enable=*/ true, /*manual=*/ false,
316                         BatterySaverController.REASON_AUTOMATIC_ON,
317                         "Auto ON");
318             }
319         } else { // Battery not low
320             enableBatterySaverLocked(/*enable=*/ false, /*manual=*/ false,
321                     BatterySaverController.REASON_AUTOMATIC_OFF,
322                     "Auto OFF");
323         }
324     }
325 
326     /**
327      * {@link com.android.server.power.PowerManagerService} calls it when
328      * {@link android.os.PowerManager#setPowerSaveMode} is called.
329      *
330      * Note this could? be called before {@link #onBootCompleted} too.
331      */
setBatterySaverEnabledManually(boolean enabled)332     public void setBatterySaverEnabledManually(boolean enabled) {
333         if (DEBUG) {
334             Slog.d(TAG, "setBatterySaverEnabledManually: enabled=" + enabled);
335         }
336         synchronized (mLock) {
337             enableBatterySaverLocked(/*enable=*/ enabled, /*manual=*/ true,
338                     (enabled ? BatterySaverController.REASON_MANUAL_ON
339                             : BatterySaverController.REASON_MANUAL_OFF),
340                     (enabled ? "Manual ON" : "Manual OFF"));
341         }
342     }
343 
344     /**
345      * Actually enable / disable battery saver. Write the new state to the global settings
346      * and propagate it to {@link #mBatterySaverController}.
347      */
enableBatterySaverLocked(boolean enable, boolean manual, int intReason, String strReason)348     private void enableBatterySaverLocked(boolean enable, boolean manual, int intReason,
349             String strReason) {
350         if (DEBUG) {
351             Slog.d(TAG, "enableBatterySaver: enable=" + enable + " manual=" + manual
352                     + " reason=" + strReason + "(" + intReason + ")");
353         }
354         final boolean wasEnabled = mBatterySaverController.isEnabled();
355 
356         if (wasEnabled == enable) {
357             if (DEBUG) {
358                 Slog.d(TAG, "Already " + (enable ? "enabled" : "disabled"));
359             }
360             return;
361         }
362         if (enable && mIsPowered) {
363             if (DEBUG) Slog.d(TAG, "Can't enable: isPowered");
364             return;
365         }
366         mLastChangedIntReason = intReason;
367         mLastChangedStrReason = strReason;
368 
369         if (manual) {
370             if (enable) {
371                 updateSnoozingLocked(false, "Manual snooze OFF");
372             } else {
373                 // When battery saver is disabled manually (while battery saver is enabled)
374                 // when the battery level is low, we "snooze" BS -- i.e. disable auto battery saver.
375                 // We resume auto-BS once the battery level is not low, or the device is plugged in.
376                 if (isBatterySaverEnabled() && mIsBatteryLevelLow) {
377                     updateSnoozingLocked(true, "Manual snooze");
378                 }
379             }
380         }
381 
382         mSettingBatterySaverEnabled = enable;
383         putGlobalSetting(Global.LOW_POWER_MODE, enable ? 1 : 0);
384 
385         if (manual) {
386             mSettingBatterySaverEnabledSticky = enable;
387             putGlobalSetting(Global.LOW_POWER_MODE_STICKY, enable ? 1 : 0);
388         }
389         mBatterySaverController.enableBatterySaver(enable, intReason);
390 
391         if (DEBUG) {
392             Slog.d(TAG, "Battery saver: Enabled=" + enable
393                     + " manual=" + manual
394                     + " reason=" + strReason + "(" + intReason + ")");
395         }
396     }
397 
updateSnoozingLocked(boolean snoozing, String reason)398     private void updateSnoozingLocked(boolean snoozing, String reason) {
399         if (mBatterySaverSnoozing == snoozing) {
400             return;
401         }
402         if (DEBUG) Slog.d(TAG, "Snooze: " + (snoozing ? "start" : "stop")  + " reason=" + reason);
403         mBatterySaverSnoozing = snoozing;
404     }
405 
406     @VisibleForTesting
putGlobalSetting(String key, int value)407     protected void putGlobalSetting(String key, int value) {
408         Global.putInt(mContext.getContentResolver(), key, value);
409     }
410 
411     @VisibleForTesting
getGlobalSetting(String key, int defValue)412     protected int getGlobalSetting(String key, int defValue) {
413         return Global.getInt(mContext.getContentResolver(), key, defValue);
414     }
415 
dump(PrintWriter pw)416     public void dump(PrintWriter pw) {
417         synchronized (mLock) {
418             pw.println();
419             pw.println("Battery saver state machine:");
420 
421             pw.print("  Enabled=");
422             pw.println(mBatterySaverController.isEnabled());
423 
424             pw.print("  mLastChangedIntReason=");
425             pw.println(mLastChangedIntReason);
426             pw.print("  mLastChangedStrReason=");
427             pw.println(mLastChangedStrReason);
428 
429             pw.print("  mBootCompleted=");
430             pw.println(mBootCompleted);
431             pw.print("  mSettingsLoaded=");
432             pw.println(mSettingsLoaded);
433             pw.print("  mBatteryStatusSet=");
434             pw.println(mBatteryStatusSet);
435 
436             pw.print("  mBatterySaverSnoozing=");
437             pw.println(mBatterySaverSnoozing);
438 
439             pw.print("  mIsPowered=");
440             pw.println(mIsPowered);
441             pw.print("  mBatteryLevel=");
442             pw.println(mBatteryLevel);
443             pw.print("  mIsBatteryLevelLow=");
444             pw.println(mIsBatteryLevelLow);
445 
446             pw.print("  mSettingBatterySaverEnabled=");
447             pw.println(mSettingBatterySaverEnabled);
448             pw.print("  mSettingBatterySaverEnabledSticky=");
449             pw.println(mSettingBatterySaverEnabledSticky);
450             pw.print("  mSettingBatterySaverTriggerThreshold=");
451             pw.println(mSettingBatterySaverTriggerThreshold);
452         }
453     }
454 
dumpProto(ProtoOutputStream proto, long tag)455     public void dumpProto(ProtoOutputStream proto, long tag) {
456         synchronized (mLock) {
457             final long token = proto.start(tag);
458 
459             proto.write(BatterySaverStateMachineProto.ENABLED,
460                     mBatterySaverController.isEnabled());
461 
462             proto.write(BatterySaverStateMachineProto.BOOT_COMPLETED, mBootCompleted);
463             proto.write(BatterySaverStateMachineProto.SETTINGS_LOADED, mSettingsLoaded);
464             proto.write(BatterySaverStateMachineProto.BATTERY_STATUS_SET, mBatteryStatusSet);
465 
466             proto.write(BatterySaverStateMachineProto.BATTERY_SAVER_SNOOZING,
467                     mBatterySaverSnoozing);
468 
469             proto.write(BatterySaverStateMachineProto.IS_POWERED, mIsPowered);
470             proto.write(BatterySaverStateMachineProto.BATTERY_LEVEL, mBatteryLevel);
471             proto.write(BatterySaverStateMachineProto.IS_BATTERY_LEVEL_LOW, mIsBatteryLevelLow);
472 
473             proto.write(BatterySaverStateMachineProto.SETTING_BATTERY_SAVER_ENABLED,
474                     mSettingBatterySaverEnabled);
475             proto.write(BatterySaverStateMachineProto.SETTING_BATTERY_SAVER_ENABLED_STICKY,
476                     mSettingBatterySaverEnabledSticky);
477             proto.write(BatterySaverStateMachineProto.SETTING_BATTERY_SAVER_TRIGGER_THRESHOLD,
478                     mSettingBatterySaverTriggerThreshold);
479 
480             proto.end(token);
481         }
482     }
483 }
484