PowerUI.java revision dea6462aab31049d1f1055314491bc33a6f16b0d
1/*
2 * Copyright (C) 2008 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
17package com.android.systemui.power;
18
19import android.app.AlertDialog;
20import android.content.BroadcastReceiver;
21import android.content.ContentResolver;
22import android.content.Context;
23import android.content.DialogInterface;
24import android.content.Intent;
25import android.content.IntentFilter;
26import android.media.AudioManager;
27import android.media.Ringtone;
28import android.media.RingtoneManager;
29import android.net.Uri;
30import android.os.BatteryManager;
31import android.os.Handler;
32import android.os.PowerManager;
33import android.os.SystemClock;
34import android.os.UserHandle;
35import android.provider.Settings;
36import android.util.Slog;
37import android.view.View;
38import android.view.WindowManager;
39import android.widget.TextView;
40
41import com.android.systemui.R;
42import com.android.systemui.SystemUI;
43
44import java.io.FileDescriptor;
45import java.io.PrintWriter;
46import java.util.Arrays;
47
48public class PowerUI extends SystemUI {
49    static final String TAG = "PowerUI";
50
51    static final boolean DEBUG = false;
52
53    Handler mHandler = new Handler();
54
55    int mBatteryLevel = 100;
56    int mBatteryStatus = BatteryManager.BATTERY_STATUS_UNKNOWN;
57    int mPlugType = 0;
58    int mInvalidCharger = 0;
59
60    int mLowBatteryAlertCloseLevel;
61    int[] mLowBatteryReminderLevels = new int[2];
62
63    AlertDialog mInvalidChargerDialog;
64    AlertDialog mLowBatteryDialog;
65    TextView mBatteryLevelTextView;
66
67    private long mScreenOffTime = -1;
68
69    public void start() {
70
71        mLowBatteryAlertCloseLevel = mContext.getResources().getInteger(
72                com.android.internal.R.integer.config_lowBatteryCloseWarningLevel);
73        mLowBatteryReminderLevels[0] = mContext.getResources().getInteger(
74                com.android.internal.R.integer.config_lowBatteryWarningLevel);
75        mLowBatteryReminderLevels[1] = mContext.getResources().getInteger(
76                com.android.internal.R.integer.config_criticalBatteryWarningLevel);
77
78        final PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
79        mScreenOffTime = pm.isScreenOn() ? -1 : SystemClock.elapsedRealtime();
80
81        // Register for Intent broadcasts for...
82        IntentFilter filter = new IntentFilter();
83        filter.addAction(Intent.ACTION_BATTERY_CHANGED);
84        filter.addAction(Intent.ACTION_SCREEN_OFF);
85        filter.addAction(Intent.ACTION_SCREEN_ON);
86        mContext.registerReceiver(mIntentReceiver, filter, null, mHandler);
87    }
88
89    /**
90     * Buckets the battery level.
91     *
92     * The code in this function is a little weird because I couldn't comprehend
93     * the bucket going up when the battery level was going down. --joeo
94     *
95     * 1 means that the battery is "ok"
96     * 0 means that the battery is between "ok" and what we should warn about.
97     * less than 0 means that the battery is low
98     */
99    private int findBatteryLevelBucket(int level) {
100        if (level >= mLowBatteryAlertCloseLevel) {
101            return 1;
102        }
103        if (level >= mLowBatteryReminderLevels[0]) {
104            return 0;
105        }
106        final int N = mLowBatteryReminderLevels.length;
107        for (int i=N-1; i>=0; i--) {
108            if (level <= mLowBatteryReminderLevels[i]) {
109                return -1-i;
110            }
111        }
112        throw new RuntimeException("not possible!");
113    }
114
115    private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
116        @Override
117        public void onReceive(Context context, Intent intent) {
118            String action = intent.getAction();
119            if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
120                final int oldBatteryLevel = mBatteryLevel;
121                mBatteryLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 100);
122                final int oldBatteryStatus = mBatteryStatus;
123                mBatteryStatus = intent.getIntExtra(BatteryManager.EXTRA_STATUS,
124                        BatteryManager.BATTERY_STATUS_UNKNOWN);
125                final int oldPlugType = mPlugType;
126                mPlugType = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 1);
127                final int oldInvalidCharger = mInvalidCharger;
128                mInvalidCharger = intent.getIntExtra(BatteryManager.EXTRA_INVALID_CHARGER, 0);
129
130                final boolean plugged = mPlugType != 0;
131                final boolean oldPlugged = oldPlugType != 0;
132
133                int oldBucket = findBatteryLevelBucket(oldBatteryLevel);
134                int bucket = findBatteryLevelBucket(mBatteryLevel);
135
136                if (DEBUG) {
137                    Slog.d(TAG, "buckets   ....." + mLowBatteryAlertCloseLevel
138                            + " .. " + mLowBatteryReminderLevels[0]
139                            + " .. " + mLowBatteryReminderLevels[1]);
140                    Slog.d(TAG, "level          " + oldBatteryLevel + " --> " + mBatteryLevel);
141                    Slog.d(TAG, "status         " + oldBatteryStatus + " --> " + mBatteryStatus);
142                    Slog.d(TAG, "plugType       " + oldPlugType + " --> " + mPlugType);
143                    Slog.d(TAG, "invalidCharger " + oldInvalidCharger + " --> " + mInvalidCharger);
144                    Slog.d(TAG, "bucket         " + oldBucket + " --> " + bucket);
145                    Slog.d(TAG, "plugged        " + oldPlugged + " --> " + plugged);
146                }
147
148                if (oldInvalidCharger == 0 && mInvalidCharger != 0) {
149                    Slog.d(TAG, "showing invalid charger warning");
150                    showInvalidChargerDialog();
151                    return;
152                } else if (oldInvalidCharger != 0 && mInvalidCharger == 0) {
153                    dismissInvalidChargerDialog();
154                } else if (mInvalidChargerDialog != null) {
155                    // if invalid charger is showing, don't show low battery
156                    return;
157                }
158
159                if (!plugged
160                        && (bucket < oldBucket || oldPlugged)
161                        && mBatteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
162                        && bucket < 0) {
163                    showLowBatteryWarning();
164
165                    // only play SFX when the dialog comes up or the bucket changes
166                    if (bucket != oldBucket || oldPlugged) {
167                        playLowBatterySound();
168                    }
169                } else if (plugged || (bucket > oldBucket && bucket > 0)) {
170                    dismissLowBatteryWarning();
171                } else if (mBatteryLevelTextView != null) {
172                    showLowBatteryWarning();
173                }
174            } else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
175                mScreenOffTime = SystemClock.elapsedRealtime();
176            } else if (Intent.ACTION_SCREEN_ON.equals(action)) {
177                mScreenOffTime = -1;
178            } else {
179                Slog.w(TAG, "unknown intent: " + intent);
180            }
181        }
182    };
183
184    void dismissLowBatteryWarning() {
185        if (mLowBatteryDialog != null) {
186            Slog.i(TAG, "closing low battery warning: level=" + mBatteryLevel);
187            mLowBatteryDialog.dismiss();
188        }
189    }
190
191    void showLowBatteryWarning() {
192        Slog.i(TAG,
193                ((mBatteryLevelTextView == null) ? "showing" : "updating")
194                + " low battery warning: level=" + mBatteryLevel
195                + " [" + findBatteryLevelBucket(mBatteryLevel) + "]");
196
197        CharSequence levelText = mContext.getString(
198                R.string.battery_low_percent_format, mBatteryLevel);
199
200        if (mBatteryLevelTextView != null) {
201            mBatteryLevelTextView.setText(levelText);
202        } else {
203            View v = View.inflate(mContext, R.layout.battery_low, null);
204            mBatteryLevelTextView = (TextView)v.findViewById(R.id.level_percent);
205
206            mBatteryLevelTextView.setText(levelText);
207
208            AlertDialog.Builder b = new AlertDialog.Builder(mContext);
209                b.setCancelable(true);
210                b.setTitle(R.string.battery_low_title);
211                b.setView(v);
212                b.setIconAttribute(android.R.attr.alertDialogIcon);
213                b.setPositiveButton(android.R.string.ok, null);
214
215            final Intent intent = new Intent(Intent.ACTION_POWER_USAGE_SUMMARY);
216            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
217                    | Intent.FLAG_ACTIVITY_MULTIPLE_TASK
218                    | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
219                    | Intent.FLAG_ACTIVITY_NO_HISTORY);
220            if (intent.resolveActivity(mContext.getPackageManager()) != null) {
221                b.setNegativeButton(R.string.battery_low_why,
222                        new DialogInterface.OnClickListener() {
223                    @Override
224                    public void onClick(DialogInterface dialog, int which) {
225                        mContext.startActivityAsUser(intent, UserHandle.CURRENT);
226                        dismissLowBatteryWarning();
227                    }
228                });
229            }
230
231            AlertDialog d = b.create();
232            d.setOnDismissListener(new DialogInterface.OnDismissListener() {
233                    @Override
234                    public void onDismiss(DialogInterface dialog) {
235                        mLowBatteryDialog = null;
236                        mBatteryLevelTextView = null;
237                    }
238                });
239            d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
240            d.getWindow().getAttributes().privateFlags |=
241                    WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
242            d.show();
243            mLowBatteryDialog = d;
244        }
245    }
246
247    void playLowBatterySound() {
248        final ContentResolver cr = mContext.getContentResolver();
249
250        final int silenceAfter = Settings.Global.getInt(cr,
251                Settings.Global.LOW_BATTERY_SOUND_TIMEOUT, 0);
252        final long offTime = SystemClock.elapsedRealtime() - mScreenOffTime;
253        if (silenceAfter > 0
254                && mScreenOffTime > 0
255                && offTime > silenceAfter) {
256            Slog.i(TAG, "screen off too long (" + offTime + "ms, limit " + silenceAfter
257                    + "ms): not waking up the user with low battery sound");
258            return;
259        }
260
261        if (DEBUG) {
262            Slog.d(TAG, "playing low battery sound. pick-a-doop!"); // WOMP-WOMP is deprecated
263        }
264
265        if (Settings.Global.getInt(cr, Settings.Global.POWER_SOUNDS_ENABLED, 1) == 1) {
266            final String soundPath = Settings.Global.getString(cr,
267                    Settings.Global.LOW_BATTERY_SOUND);
268            if (soundPath != null) {
269                final Uri soundUri = Uri.parse("file://" + soundPath);
270                if (soundUri != null) {
271                    final Ringtone sfx = RingtoneManager.getRingtone(mContext, soundUri);
272                    if (sfx != null) {
273                        sfx.setStreamType(AudioManager.STREAM_SYSTEM);
274                        sfx.play();
275                    }
276                }
277            }
278        }
279    }
280
281    void dismissInvalidChargerDialog() {
282        if (mInvalidChargerDialog != null) {
283            mInvalidChargerDialog.dismiss();
284        }
285    }
286
287    void showInvalidChargerDialog() {
288        Slog.d(TAG, "showing invalid charger dialog");
289
290        dismissLowBatteryWarning();
291
292        AlertDialog.Builder b = new AlertDialog.Builder(mContext);
293            b.setCancelable(true);
294            b.setMessage(R.string.invalid_charger);
295            b.setIconAttribute(android.R.attr.alertDialogIcon);
296            b.setPositiveButton(android.R.string.ok, null);
297
298        AlertDialog d = b.create();
299            d.setOnDismissListener(new DialogInterface.OnDismissListener() {
300                    public void onDismiss(DialogInterface dialog) {
301                        mInvalidChargerDialog = null;
302                        mBatteryLevelTextView = null;
303                    }
304                });
305
306        d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
307        d.show();
308        mInvalidChargerDialog = d;
309    }
310
311    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
312        pw.print("mLowBatteryAlertCloseLevel=");
313        pw.println(mLowBatteryAlertCloseLevel);
314        pw.print("mLowBatteryReminderLevels=");
315        pw.println(Arrays.toString(mLowBatteryReminderLevels));
316        pw.print("mInvalidChargerDialog=");
317        pw.println(mInvalidChargerDialog == null ? "null" : mInvalidChargerDialog.toString());
318        pw.print("mLowBatteryDialog=");
319        pw.println(mLowBatteryDialog == null ? "null" : mLowBatteryDialog.toString());
320        pw.print("mBatteryLevel=");
321        pw.println(Integer.toString(mBatteryLevel));
322        pw.print("mBatteryStatus=");
323        pw.println(Integer.toString(mBatteryStatus));
324        pw.print("mPlugType=");
325        pw.println(Integer.toString(mPlugType));
326        pw.print("mInvalidCharger=");
327        pw.println(Integer.toString(mInvalidCharger));
328        pw.print("mScreenOffTime=");
329        pw.print(mScreenOffTime);
330        if (mScreenOffTime >= 0) {
331            pw.print(" (");
332            pw.print(SystemClock.elapsedRealtime() - mScreenOffTime);
333            pw.print(" ago)");
334        }
335        pw.println();
336        pw.print("soundTimeout=");
337        pw.println(Settings.Global.getInt(mContext.getContentResolver(),
338                Settings.Global.LOW_BATTERY_SOUND_TIMEOUT, 0));
339        pw.print("bucket: ");
340        pw.println(Integer.toString(findBatteryLevelBucket(mBatteryLevel)));
341    }
342}
343
344