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