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