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 17 package com.android.settingslib.fuelgauge; 18 19 import static android.provider.Settings.Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED; 20 import static android.provider.Settings.Secure.LOW_POWER_WARNING_ACKNOWLEDGED; 21 22 import static com.android.settingslib.fuelgauge.BatterySaverLogging.ACTION_SAVER_STATE_MANUAL_UPDATE; 23 import static com.android.settingslib.fuelgauge.BatterySaverLogging.EXTRA_POWER_SAVE_MODE_MANUAL_ENABLED; 24 import static com.android.settingslib.fuelgauge.BatterySaverLogging.EXTRA_POWER_SAVE_MODE_MANUAL_ENABLED_REASON; 25 import static com.android.settingslib.fuelgauge.BatterySaverLogging.SaverManualEnabledReason; 26 27 import android.app.KeyguardManager; 28 import android.content.ContentResolver; 29 import android.content.Context; 30 import android.content.Intent; 31 import android.os.Bundle; 32 import android.os.PowerManager; 33 import android.provider.Settings; 34 import android.provider.Settings.Global; 35 import android.provider.Settings.Secure; 36 import android.util.KeyValueListParser; 37 import android.util.Log; 38 import android.util.Slog; 39 40 import androidx.core.util.Function; 41 42 import com.android.settingslib.flags.Flags; 43 44 /** 45 * Utilities related to battery saver. 46 */ 47 public class BatterySaverUtils { 48 49 private static final String TAG = "BatterySaverUtils"; 50 /** 51 * When set to "true" the notification will be a generic confirm message instead of asking the 52 * user if they want to turn on battery saver. If set to false the dialog will specifically 53 * talk about battery saver without giving the option of turning it on. The only button visible 54 * will be a generic confirmation button to acknowledge the dialog. 55 */ 56 public static final String EXTRA_CONFIRM_TEXT_ONLY = "extra_confirm_only"; 57 /** 58 * Ignored if {@link #EXTRA_CONFIRM_TEXT_ONLY} is "false". Can be set to any of the values in 59 * {@link PowerManager.AutoPowerSaveModeTriggers}. If set the dialog will set the power 60 * save mode trigger to the specified value after the user acknowledges the trigger. 61 */ 62 public static final String EXTRA_POWER_SAVE_MODE_TRIGGER = "extra_power_save_mode_trigger"; 63 /** 64 * Ignored if {@link #EXTRA_CONFIRM_TEXT_ONLY} is "false". can be set to any value between 65 * 0-100 that will be used if {@link #EXTRA_POWER_SAVE_MODE_TRIGGER} is 66 * {@link PowerManager#POWER_SAVE_MODE_TRIGGER_PERCENTAGE}. 67 */ 68 public static final String EXTRA_POWER_SAVE_MODE_TRIGGER_LEVEL = 69 "extra_power_save_mode_trigger_level"; 70 71 /** Battery saver schedule keys. */ 72 public static final String KEY_NO_SCHEDULE = "key_battery_saver_no_schedule"; 73 public static final String KEY_PERCENTAGE = "key_battery_saver_percentage"; 74 BatterySaverUtils()75 private BatterySaverUtils() { 76 } 77 78 private static final boolean DEBUG = false; 79 80 private static final String SYSUI_PACKAGE = "com.android.systemui"; 81 82 /** Broadcast action for SystemUI to show the battery saver confirmation dialog. */ 83 public static final String ACTION_SHOW_START_SAVER_CONFIRMATION = "PNW.startSaverConfirmation"; 84 85 /** 86 * Broadcast action for SystemUI to show the notification that suggests turning on 87 * automatic battery saver. 88 */ 89 public static final String ACTION_SHOW_AUTO_SAVER_SUGGESTION 90 = "PNW.autoSaverSuggestion"; 91 92 private static class Parameters { 93 private final Context mContext; 94 95 /** 96 * We show the auto battery saver suggestion notification when the user manually enables 97 * battery saver for the START_NTH time through the END_NTH time. 98 * (We won't show it for END_NTH + 1 time and after.) 99 */ 100 private static final int AUTO_SAVER_SUGGESTION_START_NTH = 4; 101 private static final int AUTO_SAVER_SUGGESTION_END_NTH = 8; 102 103 public final int startNth; 104 public final int endNth; 105 Parameters(Context context)106 public Parameters(Context context) { 107 mContext = context; 108 109 final String newValue = Global.getString(mContext.getContentResolver(), 110 Global.LOW_POWER_MODE_SUGGESTION_PARAMS); 111 final KeyValueListParser parser = new KeyValueListParser(','); 112 try { 113 parser.setString(newValue); 114 } catch (IllegalArgumentException e) { 115 Slog.wtf(TAG, "Bad constants: " + newValue); 116 } 117 startNth = parser.getInt("start_nth", AUTO_SAVER_SUGGESTION_START_NTH); 118 endNth = parser.getInt("end_nth", AUTO_SAVER_SUGGESTION_END_NTH); 119 } 120 } 121 122 /** 123 * Enable / disable battery saver by user request. 124 * - If it's the first time and needFirstTimeWarning, show the first time dialog. 125 * - If it's 4th time through 8th time, show the schedule suggestion notification. 126 * 127 * @param enable true to enable battery saver. 128 * @return true if the request succeeded. 129 */ setPowerSaveMode(Context context, boolean enable, boolean needFirstTimeWarning, @SaverManualEnabledReason int reason)130 public static synchronized boolean setPowerSaveMode(Context context, 131 boolean enable, boolean needFirstTimeWarning, @SaverManualEnabledReason int reason) { 132 if (DEBUG) { 133 Log.d(TAG, "Battery saver turning " + (enable ? "ON" : "OFF") + ", reason: " + reason); 134 } 135 final ContentResolver cr = context.getContentResolver(); 136 final PowerManager powerManager = context.getSystemService(PowerManager.class); 137 138 if (Flags.extremePowerLowStateVulnerability()) { 139 var keyguardManager = context.getSystemService(KeyguardManager.class); 140 if (enable 141 && needFirstTimeWarning 142 && keyguardManager != null 143 && keyguardManager.isDeviceLocked()) { 144 var result = powerManager.setPowerSaveModeEnabled(true); 145 Log.d(TAG, "Device is locked, setPowerSaveModeEnabled by default. " + result); 146 return result; 147 } 148 } 149 150 final Bundle confirmationExtras = new Bundle(1); 151 confirmationExtras.putBoolean(EXTRA_CONFIRM_TEXT_ONLY, false); 152 if (enable && needFirstTimeWarning 153 && maybeShowBatterySaverConfirmation(context, confirmationExtras)) { 154 return false; 155 } 156 if (enable && !needFirstTimeWarning) { 157 setBatterySaverConfirmationAcknowledged(context); 158 } 159 160 if (powerManager.setPowerSaveModeEnabled(enable)) { 161 if (enable) { 162 final int count = 163 Secure.getInt(cr, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, 0) + 1; 164 Secure.putInt(cr, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, count); 165 166 final Parameters parameters = new Parameters(context); 167 168 if ((count >= parameters.startNth) 169 && (count <= parameters.endNth) 170 && Global.getInt(cr, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0) == 0 171 && Secure.getInt(cr, 172 Secure.SUPPRESS_AUTO_BATTERY_SAVER_SUGGESTION, 0) == 0) { 173 sendSystemUiBroadcast(context, ACTION_SHOW_AUTO_SAVER_SUGGESTION, 174 confirmationExtras); 175 } 176 } 177 recordBatterySaverEnabledReason(context, enable, reason); 178 return true; 179 } 180 return false; 181 } 182 183 /** 184 * Shows the battery saver confirmation warning if it hasn't been acknowledged by the user in 185 * the past before. Various extras can be provided that will change the behavior of this 186 * notification as well as the ui for it. 187 * 188 * @param context A valid context 189 * @param extras Any extras to include in the intent to trigger this confirmation that will 190 * help the system disambiguate what to show/do 191 * @return True if it showed the notification because it has not been previously acknowledged. 192 * @see #EXTRA_CONFIRM_TEXT_ONLY 193 * @see #EXTRA_POWER_SAVE_MODE_TRIGGER 194 * @see #EXTRA_POWER_SAVE_MODE_TRIGGER_LEVEL 195 */ maybeShowBatterySaverConfirmation(Context context, Bundle extras)196 public static boolean maybeShowBatterySaverConfirmation(Context context, Bundle extras) { 197 if (isBatterySaverConfirmationHasBeenShowedBefore(context)) { 198 // Already shown. 199 return false; 200 } 201 sendSystemUiBroadcast(context, ACTION_SHOW_START_SAVER_CONFIRMATION, extras); 202 return true; 203 } 204 205 /** 206 * Returns {@code true} if the battery saver confirmation warning has been acknowledged by the 207 * user in the past before. 208 */ isBatterySaverConfirmationHasBeenShowedBefore(Context context)209 public static boolean isBatterySaverConfirmationHasBeenShowedBefore(Context context) { 210 Function<String, Integer> secureGetInt = 211 key -> Secure.getInt(context.getContentResolver(), key, /* def= */ 0); 212 return secureGetInt.apply(LOW_POWER_WARNING_ACKNOWLEDGED) != 0 213 && secureGetInt.apply(EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED) != 0; 214 } 215 recordBatterySaverEnabledReason(Context context, boolean enable, @SaverManualEnabledReason int reason)216 private static void recordBatterySaverEnabledReason(Context context, boolean enable, 217 @SaverManualEnabledReason int reason) { 218 final Bundle enabledReasonExtras = new Bundle(2); 219 enabledReasonExtras.putInt(EXTRA_POWER_SAVE_MODE_MANUAL_ENABLED_REASON, reason); 220 enabledReasonExtras.putBoolean(EXTRA_POWER_SAVE_MODE_MANUAL_ENABLED, enable); 221 sendSystemUiBroadcast(context, ACTION_SAVER_STATE_MANUAL_UPDATE, enabledReasonExtras); 222 } 223 sendSystemUiBroadcast(Context context, String action, Bundle extras)224 private static void sendSystemUiBroadcast(Context context, String action, Bundle extras) { 225 final Intent intent = new Intent(action); 226 intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND); 227 intent.setPackage(SYSUI_PACKAGE); 228 intent.putExtras(extras); 229 context.sendBroadcast(intent); 230 } 231 setBatterySaverConfirmationAcknowledged(Context context)232 private static void setBatterySaverConfirmationAcknowledged(Context context) { 233 Secure.putInt(context.getContentResolver(), 234 Secure.LOW_POWER_WARNING_ACKNOWLEDGED, 1); 235 Secure.putInt(context.getContentResolver(), 236 Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, 1); 237 } 238 239 /** 240 * Don't show the automatic battery suggestion notification in the future. 241 */ suppressAutoBatterySaver(Context context)242 public static void suppressAutoBatterySaver(Context context) { 243 Secure.putInt(context.getContentResolver(), 244 Secure.SUPPRESS_AUTO_BATTERY_SAVER_SUGGESTION, 1); 245 } 246 247 /** 248 * Set the automatic battery saver trigger level to {@code level}. 249 */ setAutoBatterySaverTriggerLevel(Context context, int level)250 public static void setAutoBatterySaverTriggerLevel(Context context, int level) { 251 if (level > 0) { 252 suppressAutoBatterySaver(context); 253 } 254 Global.putInt(context.getContentResolver(), Global.LOW_POWER_MODE_TRIGGER_LEVEL, level); 255 } 256 257 /** 258 * Set the automatic battery saver trigger level to {@code level}, but only when 259 * automatic battery saver isn't enabled yet. 260 */ ensureAutoBatterySaver(Context context, int level)261 public static void ensureAutoBatterySaver(Context context, int level) { 262 if (Global.getInt(context.getContentResolver(), Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0) 263 == 0) { 264 setAutoBatterySaverTriggerLevel(context, level); 265 } 266 } 267 268 /** 269 * Reverts battery saver schedule mode to none if routine mode is selected. 270 * 271 * @param context a valid context 272 */ revertScheduleToNoneIfNeeded(Context context)273 public static void revertScheduleToNoneIfNeeded(Context context) { 274 ContentResolver resolver = context.getContentResolver(); 275 final int currentMode = Global.getInt(resolver, Global.AUTOMATIC_POWER_SAVE_MODE, 276 PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE); 277 if (currentMode == PowerManager.POWER_SAVE_MODE_TRIGGER_DYNAMIC) { 278 Global.putInt(resolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0); 279 Global.putInt(resolver, Global.AUTOMATIC_POWER_SAVE_MODE, 280 PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE); 281 } 282 } 283 284 /** 285 * Gets battery saver schedule mode. 286 * 287 * @param context a valid context 288 * @return battery saver schedule key 289 */ getBatterySaverScheduleKey(Context context)290 public static String getBatterySaverScheduleKey(Context context) { 291 final ContentResolver resolver = context.getContentResolver(); 292 final int mode = Settings.Global.getInt(resolver, Global.AUTOMATIC_POWER_SAVE_MODE, 293 PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE); 294 if (mode == PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE) { 295 final int threshold = 296 Settings.Global.getInt(resolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0); 297 return threshold <= 0 ? KEY_NO_SCHEDULE : KEY_PERCENTAGE; 298 } 299 revertScheduleToNoneIfNeeded(context); 300 return KEY_NO_SCHEDULE; 301 } 302 303 /** 304 * Sets battery saver schedule mode. 305 * 306 * @param context a valid context 307 * @param scheduleKey {@link #KEY_NO_SCHEDULE} and {@link #KEY_PERCENTAGE} 308 * @param triggerLevel for automatic battery saver trigger level 309 */ setBatterySaverScheduleMode(Context context, String scheduleKey, int triggerLevel)310 public static void setBatterySaverScheduleMode(Context context, String scheduleKey, 311 int triggerLevel) { 312 final ContentResolver resolver = context.getContentResolver(); 313 switch (scheduleKey) { 314 case KEY_NO_SCHEDULE: 315 Settings.Global.putInt(resolver, Global.AUTOMATIC_POWER_SAVE_MODE, 316 PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE); 317 Settings.Global.putInt(resolver, Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0); 318 break; 319 case KEY_PERCENTAGE: 320 Settings.Global.putInt(resolver, Global.AUTOMATIC_POWER_SAVE_MODE, 321 PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE); 322 Settings.Global.putInt(resolver, 323 Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, triggerLevel); 324 break; 325 default: 326 throw new IllegalStateException("Not a valid schedule key"); 327 } 328 } 329 } 330