summaryrefslogtreecommitdiff
path: root/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
blob: 34da30555fb3893b215520f1f689be63532287cf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.settingslib.fuelgauge;

import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.PowerManager;
import android.os.UserHandle;
import android.provider.Settings.Global;
import android.provider.Settings.Secure;
import android.text.TextUtils;
import android.util.KeyValueListParser;
import android.util.Log;
import android.util.Slog;

/**
 * Utilities related to battery saver.
 */
public class BatterySaverUtils {

    private static final String TAG = "BatterySaverUtils";
    /**
     * When set to "true" the notification will be a generic confirm message instead of asking the
     * user if they want to turn on battery saver. If set to false the dialog will specifically
     * talk about battery saver without giving the option of turning it on. The only button visible
     * will be a generic confirmation button to acknowledge the dialog.
     */
    public static final String EXTRA_CONFIRM_TEXT_ONLY = "extra_confirm_only";
    /**
     * Ignored if {@link #EXTRA_CONFIRM_TEXT_ONLY} is "false". Can be set to any of the values in
     * {@link PowerManager.AutoPowerSaveModeTriggers}. If set the dialog will set the power
     * save mode trigger to the specified value after the user acknowledges the trigger.
     */
    public static final String EXTRA_POWER_SAVE_MODE_TRIGGER = "extra_power_save_mode_trigger";
    /**
     * Ignored if {@link #EXTRA_CONFIRM_TEXT_ONLY} is "false". can be set to any value between
     * 0-100 that will be used if {@link #EXTRA_POWER_SAVE_MODE_TRIGGER} is
     * {@link PowerManager#POWER_SAVE_MODE_TRIGGER_PERCENTAGE}.
     */
    public static final String EXTRA_POWER_SAVE_MODE_TRIGGER_LEVEL =
            "extra_power_save_mode_trigger_level";

    private BatterySaverUtils() {
    }

    private static final boolean DEBUG = false;

    private static final String SYSUI_PACKAGE = "com.android.systemui";

    /** Broadcast action for SystemUI to show the battery saver confirmation dialog. */
    public static final String ACTION_SHOW_START_SAVER_CONFIRMATION = "PNW.startSaverConfirmation";

    /**
     * Broadcast action for SystemUI to show the notification that suggests turning on
     * automatic battery saver.
     */
    public static final String ACTION_SHOW_AUTO_SAVER_SUGGESTION
            = "PNW.autoSaverSuggestion";

    private static class Parameters {
        private final Context mContext;

        /**
         * We show the auto battery saver suggestion notification when the user manually enables
         * battery saver for the START_NTH time through the END_NTH time.
         * (We won't show it for END_NTH + 1 time and after.)
         */
        private static final int AUTO_SAVER_SUGGESTION_START_NTH = 4;
        private static final int AUTO_SAVER_SUGGESTION_END_NTH = 8;

        public final int startNth;
        public final int endNth;

        public Parameters(Context context) {
            mContext = context;

            final String newValue = Global.getString(mContext.getContentResolver(),
                    Global.LOW_POWER_MODE_SUGGESTION_PARAMS);
            final KeyValueListParser parser = new KeyValueListParser(',');
            try {
                parser.setString(newValue);
            } catch (IllegalArgumentException e) {
                Slog.wtf(TAG, "Bad constants: " + newValue);
            }
            startNth = parser.getInt("start_nth", AUTO_SAVER_SUGGESTION_START_NTH);
            endNth = parser.getInt("end_nth", AUTO_SAVER_SUGGESTION_END_NTH);
        }
    }

    /**
     * Enable / disable battery saver by user request.
     * - If it's the first time and needFirstTimeWarning, show the first time dialog.
     * - If it's 4th time through 8th time, show the schedule suggestion notification.
     *
     * @param enable true to enable battery saver.
     *
     * @return true if the request succeeded.
     */
    public static synchronized boolean setPowerSaveMode(Context context,
            boolean enable, boolean needFirstTimeWarning) {
        if (DEBUG) {
            Log.d(TAG, "Battery saver turning " + (enable ? "ON" : "OFF"));
        }
        final ContentResolver cr = context.getContentResolver();

        final Bundle confirmationExtras = new Bundle(1);
        confirmationExtras.putBoolean(EXTRA_CONFIRM_TEXT_ONLY, false);
        if (enable && needFirstTimeWarning
                && maybeShowBatterySaverConfirmation(context, confirmationExtras)) {
            return false;
        }
        if (enable && !needFirstTimeWarning) {
            setBatterySaverConfirmationAcknowledged(context);
        }

        if (context.getSystemService(PowerManager.class).setPowerSaveModeEnabled(enable)) {
            if (enable) {
                final int count =
                        Secure.getInt(cr, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, 0) + 1;
                Secure.putInt(cr, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, count);

                final Parameters parameters = new Parameters(context);

                if ((count >= parameters.startNth)
                        && (count <= parameters.endNth)
                        && Global.getInt(cr, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0) == 0
                        && Secure.getInt(cr,
                        Secure.SUPPRESS_AUTO_BATTERY_SAVER_SUGGESTION, 0) == 0) {
                    showAutoBatterySaverSuggestion(context, confirmationExtras);
                }
            }

            return true;
        }
        return false;
    }

    /**
     * Shows the battery saver confirmation warning if it hasn't been acknowledged by the user in
     * the past before. Various extras can be provided that will change the behavior of this
     * notification as well as the ui for it.
     * @param context A valid context
     * @param extras Any extras to include in the intent to trigger this confirmation that will
     * help the system disambiguate what to show/do
     *
     * @return True if it showed the notification because it has not been previously acknowledged.
     * @see #EXTRA_CONFIRM_TEXT_ONLY
     * @see #EXTRA_POWER_SAVE_MODE_TRIGGER
     * @see #EXTRA_POWER_SAVE_MODE_TRIGGER_LEVEL
     */
    public static boolean maybeShowBatterySaverConfirmation(Context context, Bundle extras) {
        if (Secure.getInt(context.getContentResolver(),
                Secure.LOW_POWER_WARNING_ACKNOWLEDGED, 0) != 0) {
            return false; // Already shown.
        }
        context.sendBroadcast(
                getSystemUiBroadcast(ACTION_SHOW_START_SAVER_CONFIRMATION, extras));
        return true;
    }

    private static void showAutoBatterySaverSuggestion(Context context, Bundle extras) {
        context.sendBroadcast(getSystemUiBroadcast(ACTION_SHOW_AUTO_SAVER_SUGGESTION, extras));
    }

    private static Intent getSystemUiBroadcast(String action, Bundle extras) {
        final Intent i = new Intent(action);
        i.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
        i.setPackage(SYSUI_PACKAGE);
        i.putExtras(extras);
        return i;
    }

    private static void setBatterySaverConfirmationAcknowledged(Context context) {
        Secure.putIntForUser(context.getContentResolver(), Secure.LOW_POWER_WARNING_ACKNOWLEDGED, 1,
                UserHandle.USER_CURRENT);
    }

    /**
     * Don't show the automatic battery suggestion notification in the future.
     */
    public static void suppressAutoBatterySaver(Context context) {
        Secure.putInt(context.getContentResolver(),
                Secure.SUPPRESS_AUTO_BATTERY_SAVER_SUGGESTION, 1);
    }

    /**
     * Set the automatic battery saver trigger level to {@code level}.
     */
    public static void setAutoBatterySaverTriggerLevel(Context context, int level) {
        if (level > 0) {
            suppressAutoBatterySaver(context);
        }
        Global.putInt(context.getContentResolver(), Global.LOW_POWER_MODE_TRIGGER_LEVEL, level);
    }

    /**
     * Set the automatic battery saver trigger level to {@code level}, but only when
     * automatic battery saver isn't enabled yet.
     */
    public static void ensureAutoBatterySaver(Context context, int level) {
        if (Global.getInt(context.getContentResolver(), Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0)
                == 0) {
            setAutoBatterySaverTriggerLevel(context, level);
        }
    }

    /**
     * Reverts battery saver schedule mode to none if we are in a bad state where routine mode
     * is selected but no app is configured to actually provide the signal.
     * @param context a valid context
     */
    public static void revertScheduleToNoneIfNeeded(Context context) {
        ContentResolver resolver = context.getContentResolver();
        final int currentMode = Global.getInt(resolver, Global.AUTOMATIC_POWER_SAVE_MODE,
                PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE);
        boolean providerConfigured = !TextUtils.isEmpty(context.getString(
                com.android.internal.R.string.config_batterySaverScheduleProvider));
        if (currentMode == PowerManager.POWER_SAVE_MODE_TRIGGER_DYNAMIC && !providerConfigured) {
            Global.putInt(resolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0);
            Global.putInt(resolver, Global.AUTOMATIC_POWER_SAVE_MODE,
                    PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE);
        }
    }
}