Home | History | Annotate | Download | only in batterysaver
      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 
    122     public BatterySaverStateMachine(Object lock,
    123             Context context, BatterySaverController batterySaverController) {
    124         mLock = lock;
    125         mContext = context;
    126         mBatterySaverController = batterySaverController;
    127     }
    128 
    129     private boolean isBatterySaverEnabled() {
    130         return mBatterySaverController.isEnabled();
    131     }
    132 
    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      */
    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
    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
    187     void runOnBgThreadLazy(Runnable r, int delayMillis) {
    188         final Handler h = BackgroundThread.getHandler();
    189         h.removeCallbacks(r);
    190         h.postDelayed(r, delayMillis);
    191     }
    192 
    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
    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      */
    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      */
    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      */
    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      */
    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 
    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
    407     protected void putGlobalSetting(String key, int value) {
    408         Global.putInt(mContext.getContentResolver(), key, value);
    409     }
    410 
    411     @VisibleForTesting
    412     protected int getGlobalSetting(String key, int defValue) {
    413         return Global.getInt(mContext.getContentResolver(), key, defValue);
    414     }
    415 
    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 
    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