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 static com.android.server.power.batterysaver.BatterySaverController.reasonToString;
19 
20 import android.annotation.NonNull;
21 import android.annotation.StringRes;
22 import android.app.Notification;
23 import android.app.NotificationChannel;
24 import android.app.NotificationManager;
25 import android.app.PendingIntent;
26 import android.content.ContentResolver;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.res.Resources;
30 import android.database.ContentObserver;
31 import android.os.BatterySaverPolicyConfig;
32 import android.os.Handler;
33 import android.os.PowerManager;
34 import android.os.SystemClock;
35 import android.os.UserHandle;
36 import android.provider.Settings;
37 import android.util.Slog;
38 import android.util.proto.ProtoOutputStream;
39 
40 import com.android.internal.R;
41 import com.android.internal.annotations.GuardedBy;
42 import com.android.internal.annotations.VisibleForTesting;
43 import com.android.internal.os.BackgroundThread;
44 import com.android.server.EventLogTags;
45 import com.android.server.power.BatterySaverStateMachineProto;
46 
47 import java.io.PrintWriter;
48 import java.text.NumberFormat;
49 
50 /**
51  * Decides when to enable / disable battery saver.
52  *
53  * IMPORTANT: This class shares the power manager lock, which is very low in the lock hierarchy.
54  * Do not call out with the lock held. (Settings provider is okay.)
55  *
56  * Test: atest com.android.server.power.batterysaver.BatterySaverStateMachineTest
57  *
58  * Current state machine. This can be visualized using Graphviz:
59    <pre>
60 
61    digraph {
62      STATE_OFF
63      STATE_MANUAL_ON [label="STATE_MANUAL_ON\nTurned on manually by the user"]
64      STATE_AUTOMATIC_ON [label="STATE_AUTOMATIC_ON\nTurned on automatically by the system"]
65      STATE_OFF_AUTOMATIC_SNOOZED [
66        label="STATE_OFF_AUTOMATIC_SNOOZED\nTurned off manually by the user."
67            + " The system should not turn it back on automatically."
68      ]
69      STATE_PENDING_STICKY_ON [
70        label="STATE_PENDING_STICKY_ON\n"
71            + " Turned on manually by the user and then plugged in. Will turn back on after unplug."
72      ]
73 
74      STATE_OFF -> STATE_MANUAL_ON [label="manual"]
75      STATE_OFF -> STATE_AUTOMATIC_ON [label="Auto on AND charge <= auto threshold"]
76 
77      STATE_MANUAL_ON -> STATE_OFF [label="manual\nOR\nPlugged & sticky disabled"]
78      STATE_MANUAL_ON -> STATE_PENDING_STICKY_ON [label="Plugged & sticky enabled"]
79 
80      STATE_PENDING_STICKY_ON -> STATE_MANUAL_ON [label="Unplugged & sticky enabled"]
81      STATE_PENDING_STICKY_ON -> STATE_OFF [
82        label="Sticky disabled\nOR\nSticky auto off enabled AND charge >= sticky auto off threshold"
83      ]
84 
85      STATE_AUTOMATIC_ON -> STATE_OFF [label="Plugged"]
86      STATE_AUTOMATIC_ON -> STATE_OFF_AUTOMATIC_SNOOZED [label="Manual"]
87 
88      STATE_OFF_AUTOMATIC_SNOOZED -> STATE_OFF [label="Plug\nOR\nCharge > auto threshold"]
89      STATE_OFF_AUTOMATIC_SNOOZED -> STATE_MANUAL_ON [label="manual"]
90 
91      </pre>
92    }
93  */
94 public class BatterySaverStateMachine {
95     private static final String TAG = "BatterySaverStateMachine";
96     private static final String DYNAMIC_MODE_NOTIF_CHANNEL_ID = "dynamic_mode_notification";
97     private static final String BATTERY_SAVER_NOTIF_CHANNEL_ID = "battery_saver_channel";
98     private static final int DYNAMIC_MODE_NOTIFICATION_ID = 1992;
99     private static final int STICKY_AUTO_DISABLED_NOTIFICATION_ID = 1993;
100     private final Object mLock;
101 
102     private static final boolean DEBUG = BatterySaverPolicy.DEBUG;
103 
104     private static final long ADAPTIVE_CHANGE_TIMEOUT_MS = 24 * 60 * 60 * 1000L;
105 
106     /** Turn off adaptive battery saver if the device has charged above this level. */
107     private static final int ADAPTIVE_AUTO_DISABLE_BATTERY_LEVEL = 80;
108 
109     private static final int STATE_OFF = BatterySaverStateMachineProto.STATE_OFF;
110 
111     /** Turned on manually by the user. */
112     private static final int STATE_MANUAL_ON = BatterySaverStateMachineProto.STATE_MANUAL_ON;
113 
114     /** Turned on automatically by the system. */
115     private static final int STATE_AUTOMATIC_ON = BatterySaverStateMachineProto.STATE_AUTOMATIC_ON;
116 
117     /** Turned off manually by the user. The system should not turn it back on automatically. */
118     private static final int STATE_OFF_AUTOMATIC_SNOOZED =
119             BatterySaverStateMachineProto.STATE_OFF_AUTOMATIC_SNOOZED;
120 
121     /** Turned on manually by the user and then plugged in. Will turn back on after unplug. */
122     private static final int STATE_PENDING_STICKY_ON =
123             BatterySaverStateMachineProto.STATE_PENDING_STICKY_ON;
124 
125     private final Context mContext;
126     private final BatterySaverController mBatterySaverController;
127 
128     /** Whether the system has booted. */
129     @GuardedBy("mLock")
130     private boolean mBootCompleted;
131 
132     /** Whether global settings have been loaded already. */
133     @GuardedBy("mLock")
134     private boolean mSettingsLoaded;
135 
136     /** Whether the first battery status has arrived. */
137     @GuardedBy("mLock")
138     private boolean mBatteryStatusSet;
139 
140     @GuardedBy("mLock")
141     private int mState;
142 
143     /** Whether the device is connected to any power source. */
144     @GuardedBy("mLock")
145     private boolean mIsPowered;
146 
147     /** Current battery level in %, 0-100. (Currently only used in dumpsys.) */
148     @GuardedBy("mLock")
149     private int mBatteryLevel;
150 
151     /** Whether the battery level is considered to be "low" or not. */
152     @GuardedBy("mLock")
153     private boolean mIsBatteryLevelLow;
154 
155     /** Previously known value of Settings.Global.LOW_POWER_MODE. */
156     @GuardedBy("mLock")
157     private boolean mSettingBatterySaverEnabled;
158 
159     /** Previously known value of Settings.Global.LOW_POWER_MODE_STICKY. */
160     @GuardedBy("mLock")
161     private boolean mSettingBatterySaverEnabledSticky;
162 
163     /** Config flag to track if battery saver's sticky behaviour is disabled. */
164     private final boolean mBatterySaverStickyBehaviourDisabled;
165 
166     /**
167      * Whether or not to end sticky battery saver upon reaching a level specified by
168      * {@link #mSettingBatterySaverStickyAutoDisableThreshold}.
169      */
170     @GuardedBy("mLock")
171     private boolean mSettingBatterySaverStickyAutoDisableEnabled;
172 
173     /**
174      * The battery level at which to end sticky battery saver. Only useful if
175      * {@link #mSettingBatterySaverStickyAutoDisableEnabled} is {@code true}.
176      */
177     @GuardedBy("mLock")
178     private int mSettingBatterySaverStickyAutoDisableThreshold;
179 
180     /** Config flag to track default disable threshold for Dynamic Power Savings enabled battery
181      * saver. */
182     @GuardedBy("mLock")
183     private final int mDynamicPowerSavingsDefaultDisableThreshold;
184 
185     /**
186      * Previously known value of Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL.
187      * (Currently only used in dumpsys.)
188      */
189     @GuardedBy("mLock")
190     private int mSettingBatterySaverTriggerThreshold;
191 
192     /** Previously known value of Settings.Global.AUTOMATIC_POWER_SAVE_MODE. */
193     @GuardedBy("mLock")
194     private int mSettingAutomaticBatterySaver;
195 
196     /** When to disable battery saver again if it was enabled due to an external suggestion.
197      *  Corresponds to Settings.Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD.
198      */
199     @GuardedBy("mLock")
200     private int mDynamicPowerSavingsDisableThreshold;
201 
202     /**
203      * Whether we've received a suggestion that battery saver should be on from an external app.
204      * Updates when Settings.Global.DYNAMIC_POWER_SAVINGS_ENABLED changes.
205      */
206     @GuardedBy("mLock")
207     private boolean mDynamicPowerSavingsBatterySaver;
208 
209     /**
210      * Last reason passed to {@link #enableBatterySaverLocked}.
211      */
212     @GuardedBy("mLock")
213     private int mLastChangedIntReason;
214 
215     /**
216      * Last reason passed to {@link #enableBatterySaverLocked}.
217      */
218     @GuardedBy("mLock")
219     private String mLastChangedStrReason;
220 
221     /**
222      * The last time adaptive battery saver was changed by an external service, using elapsed
223      * realtime as the timebase.
224      */
225     @GuardedBy("mLock")
226     private long mLastAdaptiveBatterySaverChangedExternallyElapsed;
227 
228     private final ContentObserver mSettingsObserver = new ContentObserver(null) {
229         @Override
230         public void onChange(boolean selfChange) {
231             synchronized (mLock) {
232                 refreshSettingsLocked();
233             }
234         }
235     };
236 
BatterySaverStateMachine(Object lock, Context context, BatterySaverController batterySaverController)237     public BatterySaverStateMachine(Object lock,
238             Context context, BatterySaverController batterySaverController) {
239         mLock = lock;
240         mContext = context;
241         mBatterySaverController = batterySaverController;
242         mState = STATE_OFF;
243 
244         mBatterySaverStickyBehaviourDisabled = mContext.getResources().getBoolean(
245                 com.android.internal.R.bool.config_batterySaverStickyBehaviourDisabled);
246         mDynamicPowerSavingsDefaultDisableThreshold = mContext.getResources().getInteger(
247                 com.android.internal.R.integer.config_dynamicPowerSavingsDefaultDisableThreshold);
248     }
249 
250     /** @return true if the automatic percentage based mode should be used */
isAutomaticModeActiveLocked()251     private boolean isAutomaticModeActiveLocked() {
252         return mSettingAutomaticBatterySaver == PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE
253                 && mSettingBatterySaverTriggerThreshold > 0;
254     }
255 
256     /**
257      * The returned value won't necessarily make sense if {@link #isAutomaticModeActiveLocked()}
258      * returns {@code false}.
259      *
260      * @return true if the battery level is below automatic's threshold.
261      */
isInAutomaticLowZoneLocked()262     private boolean isInAutomaticLowZoneLocked() {
263         return mIsBatteryLevelLow;
264     }
265 
266     /** @return true if the dynamic mode should be used */
isDynamicModeActiveLocked()267     private boolean isDynamicModeActiveLocked() {
268         return mSettingAutomaticBatterySaver == PowerManager.POWER_SAVE_MODE_TRIGGER_DYNAMIC
269                 && mDynamicPowerSavingsBatterySaver;
270     }
271 
272     /**
273      * The returned value won't necessarily make sense if {@link #isDynamicModeActiveLocked()}
274      * returns {@code false}.
275      *
276      * @return true if the battery level is below dynamic's threshold.
277      */
isInDynamicLowZoneLocked()278     private boolean isInDynamicLowZoneLocked() {
279         return mBatteryLevel <= mDynamicPowerSavingsDisableThreshold;
280     }
281 
282     /**
283      * {@link com.android.server.power.PowerManagerService} calls it when the system is booted.
284      */
onBootCompleted()285     public void onBootCompleted() {
286         if (DEBUG) {
287             Slog.d(TAG, "onBootCompleted");
288         }
289         // Just booted. We don't want LOW_POWER_MODE to be persisted, so just always clear it.
290         putGlobalSetting(Settings.Global.LOW_POWER_MODE, 0);
291 
292         // This is called with the power manager lock held. Don't do anything that may call to
293         // upper services. (e.g. don't call into AM directly)
294         // So use a BG thread.
295         runOnBgThread(() -> {
296 
297             final ContentResolver cr = mContext.getContentResolver();
298             cr.registerContentObserver(Settings.Global.getUriFor(
299                     Settings.Global.LOW_POWER_MODE),
300                     false, mSettingsObserver, UserHandle.USER_SYSTEM);
301             cr.registerContentObserver(Settings.Global.getUriFor(
302                     Settings.Global.LOW_POWER_MODE_STICKY),
303                     false, mSettingsObserver, UserHandle.USER_SYSTEM);
304             cr.registerContentObserver(Settings.Global.getUriFor(
305                     Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL),
306                     false, mSettingsObserver, UserHandle.USER_SYSTEM);
307             cr.registerContentObserver(Settings.Global.getUriFor(
308                     Settings.Global.AUTOMATIC_POWER_SAVE_MODE),
309                     false, mSettingsObserver, UserHandle.USER_SYSTEM);
310             cr.registerContentObserver(Settings.Global.getUriFor(
311                     Settings.Global.DYNAMIC_POWER_SAVINGS_ENABLED),
312                     false, mSettingsObserver, UserHandle.USER_SYSTEM);
313             cr.registerContentObserver(Settings.Global.getUriFor(
314                     Settings.Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD),
315                     false, mSettingsObserver, UserHandle.USER_SYSTEM);
316             cr.registerContentObserver(Settings.Global.getUriFor(
317                     Settings.Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED),
318                     false, mSettingsObserver, UserHandle.USER_SYSTEM);
319             cr.registerContentObserver(Settings.Global.getUriFor(
320                     Settings.Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL),
321                     false, mSettingsObserver, UserHandle.USER_SYSTEM);
322 
323 
324             synchronized (mLock) {
325                 final boolean lowPowerModeEnabledSticky = getGlobalSetting(
326                         Settings.Global.LOW_POWER_MODE_STICKY, 0) != 0;
327 
328                 if (lowPowerModeEnabledSticky) {
329                     mState = STATE_PENDING_STICKY_ON;
330                 }
331 
332                 mBootCompleted = true;
333 
334                 refreshSettingsLocked();
335 
336                 doAutoBatterySaverLocked();
337             }
338         });
339     }
340 
341     /**
342      * Run a {@link Runnable} on a background handler.
343      */
344     @VisibleForTesting
runOnBgThread(Runnable r)345     void runOnBgThread(Runnable r) {
346         BackgroundThread.getHandler().post(r);
347     }
348 
349     /**
350      * Run a {@link Runnable} on a background handler, but lazily. If the same {@link Runnable} is
351      * already registered, it'll be first removed before being re-posted.
352      */
353     @VisibleForTesting
runOnBgThreadLazy(Runnable r, int delayMillis)354     void runOnBgThreadLazy(Runnable r, int delayMillis) {
355         final Handler h = BackgroundThread.getHandler();
356         h.removeCallbacks(r);
357         h.postDelayed(r, delayMillis);
358     }
359 
360     @GuardedBy("mLock")
refreshSettingsLocked()361     private void refreshSettingsLocked() {
362         final boolean lowPowerModeEnabled = getGlobalSetting(
363                 Settings.Global.LOW_POWER_MODE, 0) != 0;
364         final boolean lowPowerModeEnabledSticky = getGlobalSetting(
365                 Settings.Global.LOW_POWER_MODE_STICKY, 0) != 0;
366         final boolean dynamicPowerSavingsBatterySaver = getGlobalSetting(
367                 Settings.Global.DYNAMIC_POWER_SAVINGS_ENABLED, 0) != 0;
368         final int lowPowerModeTriggerLevel = getGlobalSetting(
369                 Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0);
370         final int automaticBatterySaverMode = getGlobalSetting(
371                 Settings.Global.AUTOMATIC_POWER_SAVE_MODE,
372                 PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE);
373         final int dynamicPowerSavingsDisableThreshold = getGlobalSetting(
374                 Settings.Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD,
375                 mDynamicPowerSavingsDefaultDisableThreshold);
376         final boolean isStickyAutoDisableEnabled = getGlobalSetting(
377                 Settings.Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED, 1) != 0;
378         final int stickyAutoDisableThreshold = getGlobalSetting(
379                 Settings.Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL, 90);
380 
381         setSettingsLocked(lowPowerModeEnabled, lowPowerModeEnabledSticky,
382                 lowPowerModeTriggerLevel,
383                 isStickyAutoDisableEnabled, stickyAutoDisableThreshold,
384                 automaticBatterySaverMode,
385                 dynamicPowerSavingsBatterySaver, dynamicPowerSavingsDisableThreshold);
386     }
387 
388     /**
389      * {@link com.android.server.power.PowerManagerService} calls it when relevant global settings
390      * have changed.
391      *
392      * Note this will be called before {@link #onBootCompleted} too.
393      */
394     @GuardedBy("mLock")
395     @VisibleForTesting
setSettingsLocked(boolean batterySaverEnabled, boolean batterySaverEnabledSticky, int batterySaverTriggerThreshold, boolean isStickyAutoDisableEnabled, int stickyAutoDisableThreshold, int automaticBatterySaver, boolean dynamicPowerSavingsBatterySaver, int dynamicPowerSavingsDisableThreshold)396     void setSettingsLocked(boolean batterySaverEnabled, boolean batterySaverEnabledSticky,
397             int batterySaverTriggerThreshold,
398             boolean isStickyAutoDisableEnabled, int stickyAutoDisableThreshold,
399             int automaticBatterySaver,
400             boolean dynamicPowerSavingsBatterySaver, int dynamicPowerSavingsDisableThreshold) {
401         if (DEBUG) {
402             Slog.d(TAG, "setSettings: enabled=" + batterySaverEnabled
403                     + " sticky=" + batterySaverEnabledSticky
404                     + " threshold=" + batterySaverTriggerThreshold
405                     + " stickyAutoDisableEnabled=" + isStickyAutoDisableEnabled
406                     + " stickyAutoDisableThreshold=" + stickyAutoDisableThreshold
407                     + " automaticBatterySaver=" + automaticBatterySaver
408                     + " dynamicPowerSavingsBatterySaver=" + dynamicPowerSavingsBatterySaver
409                     + " dynamicPowerSavingsDisableThreshold="
410                     + dynamicPowerSavingsDisableThreshold);
411         }
412 
413         mSettingsLoaded = true;
414 
415         // Set sensible limits.
416         stickyAutoDisableThreshold = Math.max(stickyAutoDisableThreshold,
417                 batterySaverTriggerThreshold);
418 
419         final boolean enabledChanged = mSettingBatterySaverEnabled != batterySaverEnabled;
420         final boolean stickyChanged =
421                 mSettingBatterySaverEnabledSticky != batterySaverEnabledSticky;
422         final boolean thresholdChanged
423                 = mSettingBatterySaverTriggerThreshold != batterySaverTriggerThreshold;
424         final boolean stickyAutoDisableEnabledChanged =
425                 mSettingBatterySaverStickyAutoDisableEnabled != isStickyAutoDisableEnabled;
426         final boolean stickyAutoDisableThresholdChanged =
427                 mSettingBatterySaverStickyAutoDisableThreshold != stickyAutoDisableThreshold;
428         final boolean automaticModeChanged = mSettingAutomaticBatterySaver != automaticBatterySaver;
429         final boolean dynamicPowerSavingsThresholdChanged =
430                 mDynamicPowerSavingsDisableThreshold != dynamicPowerSavingsDisableThreshold;
431         final boolean dynamicPowerSavingsBatterySaverChanged =
432                 mDynamicPowerSavingsBatterySaver != dynamicPowerSavingsBatterySaver;
433 
434         if (!(enabledChanged || stickyChanged || thresholdChanged || automaticModeChanged
435                 || stickyAutoDisableEnabledChanged || stickyAutoDisableThresholdChanged
436                 || dynamicPowerSavingsThresholdChanged || dynamicPowerSavingsBatterySaverChanged)) {
437             return;
438         }
439 
440         mSettingBatterySaverEnabled = batterySaverEnabled;
441         mSettingBatterySaverEnabledSticky = batterySaverEnabledSticky;
442         mSettingBatterySaverTriggerThreshold = batterySaverTriggerThreshold;
443         mSettingBatterySaverStickyAutoDisableEnabled = isStickyAutoDisableEnabled;
444         mSettingBatterySaverStickyAutoDisableThreshold = stickyAutoDisableThreshold;
445         mSettingAutomaticBatterySaver = automaticBatterySaver;
446         mDynamicPowerSavingsDisableThreshold = dynamicPowerSavingsDisableThreshold;
447         mDynamicPowerSavingsBatterySaver = dynamicPowerSavingsBatterySaver;
448 
449         if (thresholdChanged) {
450             // To avoid spamming the event log, we throttle logging here.
451             runOnBgThreadLazy(mThresholdChangeLogger, 2000);
452         }
453 
454         if (!mSettingBatterySaverStickyAutoDisableEnabled) {
455             hideStickyDisabledNotification();
456         }
457 
458         if (enabledChanged) {
459             final String reason = batterySaverEnabled
460                     ? "Global.low_power changed to 1" : "Global.low_power changed to 0";
461             enableBatterySaverLocked(/*enable=*/ batterySaverEnabled, /*manual=*/ true,
462                     BatterySaverController.REASON_SETTING_CHANGED, reason);
463         } else {
464             doAutoBatterySaverLocked();
465         }
466     }
467 
468     private final Runnable mThresholdChangeLogger = () -> {
469         EventLogTags.writeBatterySaverSetting(mSettingBatterySaverTriggerThreshold);
470     };
471 
472     /**
473      * {@link com.android.server.power.PowerManagerService} calls it when battery state changes.
474      *
475      * Note this may be called before {@link #onBootCompleted} too.
476      */
setBatteryStatus(boolean newPowered, int newLevel, boolean newBatteryLevelLow)477     public void setBatteryStatus(boolean newPowered, int newLevel, boolean newBatteryLevelLow) {
478         if (DEBUG) {
479             Slog.d(TAG, "setBatteryStatus: powered=" + newPowered + " level=" + newLevel
480                     + " low=" + newBatteryLevelLow);
481         }
482         synchronized (mLock) {
483             mBatteryStatusSet = true;
484 
485             final boolean poweredChanged = mIsPowered != newPowered;
486             final boolean levelChanged = mBatteryLevel != newLevel;
487             final boolean lowChanged = mIsBatteryLevelLow != newBatteryLevelLow;
488 
489             if (!(poweredChanged || levelChanged || lowChanged)) {
490                 return;
491             }
492 
493             mIsPowered = newPowered;
494             mBatteryLevel = newLevel;
495             mIsBatteryLevelLow = newBatteryLevelLow;
496 
497             doAutoBatterySaverLocked();
498         }
499     }
500 
501     /**
502      * Enable or disable the current adaptive battery saver policy. This may not change what's in
503      * effect if full battery saver is also enabled.
504      */
setAdaptiveBatterySaverEnabled(boolean enabled)505     public boolean setAdaptiveBatterySaverEnabled(boolean enabled) {
506         if (DEBUG) {
507             Slog.d(TAG, "setAdaptiveBatterySaverEnabled: enabled=" + enabled);
508         }
509         synchronized (mLock) {
510             mLastAdaptiveBatterySaverChangedExternallyElapsed = SystemClock.elapsedRealtime();
511             return mBatterySaverController.setAdaptivePolicyEnabledLocked(
512                     enabled, BatterySaverController.REASON_ADAPTIVE_DYNAMIC_POWER_SAVINGS_CHANGED);
513         }
514     }
515 
516     /**
517      * Change the adaptive battery saver policy.
518      */
setAdaptiveBatterySaverPolicy(BatterySaverPolicyConfig config)519     public boolean setAdaptiveBatterySaverPolicy(BatterySaverPolicyConfig config) {
520         if (DEBUG) {
521             Slog.d(TAG, "setAdaptiveBatterySaverPolicy: config=" + config);
522         }
523 
524         synchronized (mLock) {
525             mLastAdaptiveBatterySaverChangedExternallyElapsed = SystemClock.elapsedRealtime();
526             return mBatterySaverController.setAdaptivePolicyLocked(config,
527                     BatterySaverController.REASON_ADAPTIVE_DYNAMIC_POWER_SAVINGS_CHANGED);
528         }
529     }
530 
531     /**
532      * Decide whether to auto-start / stop battery saver.
533      */
534     @GuardedBy("mLock")
doAutoBatterySaverLocked()535     private void doAutoBatterySaverLocked() {
536         if (DEBUG) {
537             Slog.d(TAG, "doAutoBatterySaverLocked: mBootCompleted=" + mBootCompleted
538                     + " mSettingsLoaded=" + mSettingsLoaded
539                     + " mBatteryStatusSet=" + mBatteryStatusSet
540                     + " mState=" + mState
541                     + " mIsBatteryLevelLow=" + mIsBatteryLevelLow
542                     + " mIsPowered=" + mIsPowered
543                     + " mSettingAutomaticBatterySaver=" + mSettingAutomaticBatterySaver
544                     + " mSettingBatterySaverEnabledSticky=" + mSettingBatterySaverEnabledSticky
545                     + " mSettingBatterySaverStickyAutoDisableEnabled="
546                     + mSettingBatterySaverStickyAutoDisableEnabled);
547         }
548         if (!(mBootCompleted && mSettingsLoaded && mBatteryStatusSet)) {
549             return; // Not fully initialized yet.
550         }
551 
552         updateStateLocked(false, false);
553 
554         // Adaptive control.
555         if (SystemClock.elapsedRealtime() - mLastAdaptiveBatterySaverChangedExternallyElapsed
556                 > ADAPTIVE_CHANGE_TIMEOUT_MS) {
557             mBatterySaverController.setAdaptivePolicyEnabledLocked(
558                     false, BatterySaverController.REASON_TIMEOUT);
559             mBatterySaverController.resetAdaptivePolicyLocked(
560                     BatterySaverController.REASON_TIMEOUT);
561         } else if (mIsPowered && mBatteryLevel >= ADAPTIVE_AUTO_DISABLE_BATTERY_LEVEL) {
562             mBatterySaverController.setAdaptivePolicyEnabledLocked(false,
563                     BatterySaverController.REASON_PLUGGED_IN);
564         }
565     }
566 
567     /**
568      * Update the state machine based on the current settings and battery/charge status.
569      *
570      * @param manual Whether the change was made by the user.
571      * @param enable Whether the user wants to turn battery saver on or off. Is only used if {@param
572      *               manual} is true.
573      */
574     @GuardedBy("mLock")
updateStateLocked(boolean manual, boolean enable)575     private void updateStateLocked(boolean manual, boolean enable) {
576         if (!manual && !(mBootCompleted && mSettingsLoaded && mBatteryStatusSet)) {
577             return; // Not fully initialized yet.
578         }
579 
580         switch (mState) {
581             case STATE_OFF: {
582                 if (!mIsPowered) {
583                     if (manual) {
584                         if (!enable) {
585                             Slog.e(TAG, "Tried to disable BS when it's already OFF");
586                             return;
587                         }
588                         enableBatterySaverLocked(/*enable*/ true, /*manual*/ true,
589                                 BatterySaverController.REASON_MANUAL_ON);
590                         hideStickyDisabledNotification();
591                         mState = STATE_MANUAL_ON;
592                     } else if (isAutomaticModeActiveLocked() && isInAutomaticLowZoneLocked()) {
593                         enableBatterySaverLocked(/*enable*/ true, /*manual*/ false,
594                                 BatterySaverController.REASON_PERCENTAGE_AUTOMATIC_ON);
595                         hideStickyDisabledNotification();
596                         mState = STATE_AUTOMATIC_ON;
597                     } else if (isDynamicModeActiveLocked() && isInDynamicLowZoneLocked()) {
598                         enableBatterySaverLocked(/*enable*/ true, /*manual*/ false,
599                                 BatterySaverController.REASON_DYNAMIC_POWER_SAVINGS_AUTOMATIC_ON);
600                         hideStickyDisabledNotification();
601                         mState = STATE_AUTOMATIC_ON;
602                     }
603                 }
604                 break;
605             }
606 
607             case STATE_MANUAL_ON: {
608                 if (manual) {
609                     if (enable) {
610                         Slog.e(TAG, "Tried to enable BS when it's already MANUAL_ON");
611                         return;
612                     }
613                     enableBatterySaverLocked(/*enable*/ false, /*manual*/ true,
614                             BatterySaverController.REASON_MANUAL_OFF);
615                     mState = STATE_OFF;
616                 } else if (mIsPowered) {
617                     enableBatterySaverLocked(/*enable*/ false, /*manual*/ false,
618                             BatterySaverController.REASON_PLUGGED_IN);
619                     if (mSettingBatterySaverEnabledSticky
620                             && !mBatterySaverStickyBehaviourDisabled) {
621                         mState = STATE_PENDING_STICKY_ON;
622                     } else {
623                         mState = STATE_OFF;
624                     }
625                 }
626                 break;
627             }
628 
629             case STATE_AUTOMATIC_ON: {
630                 if (mIsPowered) {
631                     enableBatterySaverLocked(/*enable*/ false, /*manual*/ false,
632                             BatterySaverController.REASON_PLUGGED_IN);
633                     mState = STATE_OFF;
634                 } else if (manual) {
635                     if (enable) {
636                         Slog.e(TAG, "Tried to enable BS when it's already AUTO_ON");
637                         return;
638                     }
639                     enableBatterySaverLocked(/*enable*/ false, /*manual*/ true,
640                             BatterySaverController.REASON_MANUAL_OFF);
641                     // When battery saver is disabled manually (while battery saver is enabled)
642                     // when the battery level is low, we "snooze" BS -- i.e. disable auto battery
643                     // saver.
644                     // We resume auto-BS once the battery level is not low, or the device is
645                     // plugged in.
646                     mState = STATE_OFF_AUTOMATIC_SNOOZED;
647                 } else if (isAutomaticModeActiveLocked() && !isInAutomaticLowZoneLocked()) {
648                     enableBatterySaverLocked(/*enable*/ false, /*manual*/ false,
649                             BatterySaverController.REASON_PERCENTAGE_AUTOMATIC_OFF);
650                     mState = STATE_OFF;
651                 } else if (isDynamicModeActiveLocked() && !isInDynamicLowZoneLocked()) {
652                     enableBatterySaverLocked(/*enable*/ false, /*manual*/ false,
653                             BatterySaverController.REASON_DYNAMIC_POWER_SAVINGS_AUTOMATIC_OFF);
654                     mState = STATE_OFF;
655                 } else if (!isAutomaticModeActiveLocked() && !isDynamicModeActiveLocked()) {
656                     enableBatterySaverLocked(/*enable*/ false, /*manual*/ false,
657                             BatterySaverController.REASON_SETTING_CHANGED);
658                     mState = STATE_OFF;
659                 }
660                 break;
661             }
662 
663             case STATE_OFF_AUTOMATIC_SNOOZED: {
664                 if (manual) {
665                     if (!enable) {
666                         Slog.e(TAG, "Tried to disable BS when it's already AUTO_SNOOZED");
667                         return;
668                     }
669                     enableBatterySaverLocked(/*enable*/ true, /*manual*/ true,
670                             BatterySaverController.REASON_MANUAL_ON);
671                     mState = STATE_MANUAL_ON;
672                 } else if (mIsPowered // Plugging in resets snooze.
673                         || (isAutomaticModeActiveLocked() && !isInAutomaticLowZoneLocked())
674                         || (isDynamicModeActiveLocked() && !isInDynamicLowZoneLocked())
675                         || (!isAutomaticModeActiveLocked() && !isDynamicModeActiveLocked())) {
676                     mState = STATE_OFF;
677                 }
678                 break;
679             }
680 
681             case STATE_PENDING_STICKY_ON: {
682                 if (manual) {
683                     // This shouldn't be possible. We'll only be in this state when the device is
684                     // plugged in, so the user shouldn't be able to manually change state.
685                     Slog.e(TAG, "Tried to manually change BS state from PENDING_STICKY_ON");
686                     return;
687                 }
688                 final boolean shouldTurnOffSticky = mSettingBatterySaverStickyAutoDisableEnabled
689                         && mBatteryLevel >= mSettingBatterySaverStickyAutoDisableThreshold;
690                 final boolean isStickyDisabled =
691                         mBatterySaverStickyBehaviourDisabled || !mSettingBatterySaverEnabledSticky;
692                 if (isStickyDisabled || shouldTurnOffSticky) {
693                     mState = STATE_OFF;
694                     setStickyActive(false);
695                     triggerStickyDisabledNotification();
696                 } else if (!mIsPowered) {
697                     // Re-enable BS.
698                     enableBatterySaverLocked(/*enable*/ true, /*manual*/ true,
699                             BatterySaverController.REASON_STICKY_RESTORE);
700                     mState = STATE_MANUAL_ON;
701                 }
702                 break;
703             }
704 
705             default:
706                 Slog.wtf(TAG, "Unknown state: " + mState);
707                 break;
708         }
709     }
710 
711     @VisibleForTesting
getState()712     int getState() {
713         synchronized (mLock) {
714             return mState;
715         }
716     }
717 
718     /**
719      * {@link com.android.server.power.PowerManagerService} calls it when
720      * {@link android.os.PowerManager#setPowerSaveModeEnabled} is called.
721      *
722      * Note this could? be called before {@link #onBootCompleted} too.
723      */
setBatterySaverEnabledManually(boolean enabled)724     public void setBatterySaverEnabledManually(boolean enabled) {
725         if (DEBUG) {
726             Slog.d(TAG, "setBatterySaverEnabledManually: enabled=" + enabled);
727         }
728         synchronized (mLock) {
729             updateStateLocked(true, enabled);
730             // TODO: maybe turn off adaptive if it's on and advertiseIsEnabled is true and
731             //  enabled is false
732         }
733     }
734 
735     @GuardedBy("mLock")
enableBatterySaverLocked(boolean enable, boolean manual, int intReason)736     private void enableBatterySaverLocked(boolean enable, boolean manual, int intReason) {
737         enableBatterySaverLocked(enable, manual, intReason, reasonToString(intReason));
738     }
739 
740     /**
741      * Actually enable / disable battery saver. Write the new state to the global settings
742      * and propagate it to {@link #mBatterySaverController}.
743      */
744     @GuardedBy("mLock")
enableBatterySaverLocked(boolean enable, boolean manual, int intReason, String strReason)745     private void enableBatterySaverLocked(boolean enable, boolean manual, int intReason,
746             String strReason) {
747         if (DEBUG) {
748             Slog.d(TAG, "enableBatterySaver: enable=" + enable + " manual=" + manual
749                     + " reason=" + strReason + "(" + intReason + ")");
750         }
751         final boolean wasEnabled = mBatterySaverController.isFullEnabled();
752 
753         if (wasEnabled == enable) {
754             if (DEBUG) {
755                 Slog.d(TAG, "Already " + (enable ? "enabled" : "disabled"));
756             }
757             return;
758         }
759         if (enable && mIsPowered) {
760             if (DEBUG) Slog.d(TAG, "Can't enable: isPowered");
761             return;
762         }
763         mLastChangedIntReason = intReason;
764         mLastChangedStrReason = strReason;
765 
766         mSettingBatterySaverEnabled = enable;
767         putGlobalSetting(Settings.Global.LOW_POWER_MODE, enable ? 1 : 0);
768 
769         if (manual) {
770             setStickyActive(!mBatterySaverStickyBehaviourDisabled && enable);
771         }
772         mBatterySaverController.enableBatterySaver(enable, intReason);
773 
774         // Handle triggering the notification to show/hide when appropriate
775         if (intReason == BatterySaverController.REASON_DYNAMIC_POWER_SAVINGS_AUTOMATIC_ON) {
776             triggerDynamicModeNotification();
777         } else if (!enable) {
778             hideDynamicModeNotification();
779         }
780 
781         if (DEBUG) {
782             Slog.d(TAG, "Battery saver: Enabled=" + enable
783                     + " manual=" + manual
784                     + " reason=" + strReason + "(" + intReason + ")");
785         }
786     }
787 
788     @VisibleForTesting
triggerDynamicModeNotification()789     void triggerDynamicModeNotification() {
790         // The current lock is the PowerManager lock, which sits very low in the service lock
791         // hierarchy. We shouldn't call out to NotificationManager with the PowerManager lock.
792         runOnBgThread(() -> {
793             NotificationManager manager = mContext.getSystemService(NotificationManager.class);
794             ensureNotificationChannelExists(manager, DYNAMIC_MODE_NOTIF_CHANNEL_ID,
795                     R.string.dynamic_mode_notification_channel_name);
796 
797             manager.notifyAsUser(TAG, DYNAMIC_MODE_NOTIFICATION_ID,
798                     buildNotification(DYNAMIC_MODE_NOTIF_CHANNEL_ID,
799                             mContext.getResources().getString(
800                                     R.string.dynamic_mode_notification_title),
801                             R.string.dynamic_mode_notification_summary,
802                             Intent.ACTION_POWER_USAGE_SUMMARY),
803                     UserHandle.ALL);
804         });
805     }
806 
807     @VisibleForTesting
triggerStickyDisabledNotification()808     void triggerStickyDisabledNotification() {
809         // The current lock is the PowerManager lock, which sits very low in the service lock
810         // hierarchy. We shouldn't call out to NotificationManager with the PowerManager lock.
811         runOnBgThread(() -> {
812             NotificationManager manager = mContext.getSystemService(NotificationManager.class);
813             ensureNotificationChannelExists(manager, BATTERY_SAVER_NOTIF_CHANNEL_ID,
814                     R.string.battery_saver_notification_channel_name);
815 
816             final String percentage = NumberFormat.getPercentInstance()
817                     .format((double) mBatteryLevel / 100.0);
818             manager.notifyAsUser(TAG, STICKY_AUTO_DISABLED_NOTIFICATION_ID,
819                     buildNotification(BATTERY_SAVER_NOTIF_CHANNEL_ID,
820                             mContext.getResources().getString(
821                                     R.string.battery_saver_charged_notification_title, percentage),
822                             R.string.battery_saver_off_notification_summary,
823                             Settings.ACTION_BATTERY_SAVER_SETTINGS),
824                     UserHandle.ALL);
825         });
826     }
827 
ensureNotificationChannelExists(NotificationManager manager, @NonNull String channelId, @StringRes int nameId)828     private void ensureNotificationChannelExists(NotificationManager manager,
829             @NonNull String channelId, @StringRes int nameId) {
830         NotificationChannel channel = new NotificationChannel(
831                 channelId, mContext.getText(nameId), NotificationManager.IMPORTANCE_DEFAULT);
832         channel.setSound(null, null);
833         channel.setBlockableSystem(true);
834         manager.createNotificationChannel(channel);
835     }
836 
buildNotification(@onNull String channelId, @NonNull String title, @StringRes int summaryId, @NonNull String intentAction)837     private Notification buildNotification(@NonNull String channelId, @NonNull String title,
838             @StringRes int summaryId, @NonNull String intentAction) {
839         Resources res = mContext.getResources();
840         Intent intent = new Intent(intentAction);
841         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
842         PendingIntent batterySaverIntent = PendingIntent.getActivity(
843                 mContext, 0 /* requestCode */, intent, PendingIntent.FLAG_UPDATE_CURRENT);
844         final String summary = res.getString(summaryId);
845 
846         return new Notification.Builder(mContext, channelId)
847                 .setSmallIcon(R.drawable.ic_battery)
848                 .setContentTitle(title)
849                 .setContentText(summary)
850                 .setContentIntent(batterySaverIntent)
851                 .setStyle(new Notification.BigTextStyle().bigText(summary))
852                 .setOnlyAlertOnce(true)
853                 .setAutoCancel(true)
854                 .build();
855     }
856 
hideDynamicModeNotification()857     private void hideDynamicModeNotification() {
858         hideNotification(DYNAMIC_MODE_NOTIFICATION_ID);
859     }
860 
hideStickyDisabledNotification()861     private void hideStickyDisabledNotification() {
862         hideNotification(STICKY_AUTO_DISABLED_NOTIFICATION_ID);
863     }
864 
hideNotification(int notificationId)865     private void hideNotification(int notificationId) {
866         // The current lock is the PowerManager lock, which sits very low in the service lock
867         // hierarchy. We shouldn't call out to NotificationManager with the PowerManager lock.
868         runOnBgThread(() -> {
869             NotificationManager manager = mContext.getSystemService(NotificationManager.class);
870             manager.cancelAsUser(TAG, notificationId, UserHandle.ALL);
871         });
872     }
873 
setStickyActive(boolean active)874     private void setStickyActive(boolean active) {
875         mSettingBatterySaverEnabledSticky = active;
876         putGlobalSetting(Settings.Global.LOW_POWER_MODE_STICKY,
877                 mSettingBatterySaverEnabledSticky ? 1 : 0);
878     }
879 
880     @VisibleForTesting
putGlobalSetting(String key, int value)881     protected void putGlobalSetting(String key, int value) {
882         Settings.Global.putInt(mContext.getContentResolver(), key, value);
883     }
884 
885     @VisibleForTesting
getGlobalSetting(String key, int defValue)886     protected int getGlobalSetting(String key, int defValue) {
887         return Settings.Global.getInt(mContext.getContentResolver(), key, defValue);
888     }
889 
dump(PrintWriter pw)890     public void dump(PrintWriter pw) {
891         synchronized (mLock) {
892             pw.println();
893             pw.println("Battery saver state machine:");
894 
895             pw.print("  Enabled=");
896             pw.println(mBatterySaverController.isEnabled());
897             pw.print("    full=");
898             pw.println(mBatterySaverController.isFullEnabled());
899             pw.print("    adaptive=");
900             pw.print(mBatterySaverController.isAdaptiveEnabled());
901             if (mBatterySaverController.isAdaptiveEnabled()) {
902                 pw.print(" (advertise=");
903                 pw.print(
904                         mBatterySaverController.getBatterySaverPolicy().shouldAdvertiseIsEnabled());
905                 pw.print(")");
906             }
907             pw.println();
908             pw.print("  mState=");
909             pw.println(mState);
910 
911             pw.print("  mLastChangedIntReason=");
912             pw.println(mLastChangedIntReason);
913             pw.print("  mLastChangedStrReason=");
914             pw.println(mLastChangedStrReason);
915 
916             pw.print("  mBootCompleted=");
917             pw.println(mBootCompleted);
918             pw.print("  mSettingsLoaded=");
919             pw.println(mSettingsLoaded);
920             pw.print("  mBatteryStatusSet=");
921             pw.println(mBatteryStatusSet);
922 
923             pw.print("  mIsPowered=");
924             pw.println(mIsPowered);
925             pw.print("  mBatteryLevel=");
926             pw.println(mBatteryLevel);
927             pw.print("  mIsBatteryLevelLow=");
928             pw.println(mIsBatteryLevelLow);
929 
930             pw.print("  mSettingBatterySaverEnabled=");
931             pw.println(mSettingBatterySaverEnabled);
932             pw.print("  mSettingBatterySaverEnabledSticky=");
933             pw.println(mSettingBatterySaverEnabledSticky);
934             pw.print("  mSettingBatterySaverStickyAutoDisableEnabled=");
935             pw.println(mSettingBatterySaverStickyAutoDisableEnabled);
936             pw.print("  mSettingBatterySaverStickyAutoDisableThreshold=");
937             pw.println(mSettingBatterySaverStickyAutoDisableThreshold);
938             pw.print("  mSettingBatterySaverTriggerThreshold=");
939             pw.println(mSettingBatterySaverTriggerThreshold);
940             pw.print("  mBatterySaverStickyBehaviourDisabled=");
941             pw.println(mBatterySaverStickyBehaviourDisabled);
942 
943             pw.print("  mLastAdaptiveBatterySaverChangedExternallyElapsed=");
944             pw.println(mLastAdaptiveBatterySaverChangedExternallyElapsed);
945         }
946     }
947 
dumpProto(ProtoOutputStream proto, long tag)948     public void dumpProto(ProtoOutputStream proto, long tag) {
949         synchronized (mLock) {
950             final long token = proto.start(tag);
951 
952             proto.write(BatterySaverStateMachineProto.ENABLED,
953                     mBatterySaverController.isEnabled());
954             proto.write(BatterySaverStateMachineProto.STATE, mState);
955             proto.write(BatterySaverStateMachineProto.IS_FULL_ENABLED,
956                     mBatterySaverController.isFullEnabled());
957             proto.write(BatterySaverStateMachineProto.IS_ADAPTIVE_ENABLED,
958                     mBatterySaverController.isAdaptiveEnabled());
959             proto.write(BatterySaverStateMachineProto.SHOULD_ADVERTISE_IS_ENABLED,
960                     mBatterySaverController.getBatterySaverPolicy().shouldAdvertiseIsEnabled());
961 
962             proto.write(BatterySaverStateMachineProto.BOOT_COMPLETED, mBootCompleted);
963             proto.write(BatterySaverStateMachineProto.SETTINGS_LOADED, mSettingsLoaded);
964             proto.write(BatterySaverStateMachineProto.BATTERY_STATUS_SET, mBatteryStatusSet);
965 
966 
967             proto.write(BatterySaverStateMachineProto.IS_POWERED, mIsPowered);
968             proto.write(BatterySaverStateMachineProto.BATTERY_LEVEL, mBatteryLevel);
969             proto.write(BatterySaverStateMachineProto.IS_BATTERY_LEVEL_LOW, mIsBatteryLevelLow);
970 
971             proto.write(BatterySaverStateMachineProto.SETTING_BATTERY_SAVER_ENABLED,
972                     mSettingBatterySaverEnabled);
973             proto.write(BatterySaverStateMachineProto.SETTING_BATTERY_SAVER_ENABLED_STICKY,
974                     mSettingBatterySaverEnabledSticky);
975             proto.write(BatterySaverStateMachineProto.SETTING_BATTERY_SAVER_TRIGGER_THRESHOLD,
976                     mSettingBatterySaverTriggerThreshold);
977             proto.write(
978                     BatterySaverStateMachineProto.SETTING_BATTERY_SAVER_STICKY_AUTO_DISABLE_ENABLED,
979                     mSettingBatterySaverStickyAutoDisableEnabled);
980             proto.write(
981                     BatterySaverStateMachineProto
982                             .SETTING_BATTERY_SAVER_STICKY_AUTO_DISABLE_THRESHOLD,
983                     mSettingBatterySaverStickyAutoDisableThreshold);
984 
985             proto.write(
986                     BatterySaverStateMachineProto
987                             .LAST_ADAPTIVE_BATTERY_SAVER_CHANGED_EXTERNALLY_ELAPSED,
988                     mLastAdaptiveBatterySaverChangedExternallyElapsed);
989 
990             proto.end(token);
991         }
992     }
993 }
994