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