• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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