PowerNotificationWarnings.java revision 8de4311c51229efbe2f2d0afbf298982c5cadd96
1/*
2 * Copyright (C) 2014 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.Notification;
20import android.app.NotificationManager;
21import android.app.PendingIntent;
22import android.content.BroadcastReceiver;
23import android.content.ContentResolver;
24import android.content.Context;
25import android.content.DialogInterface;
26import android.content.DialogInterface.OnClickListener;
27import android.content.DialogInterface.OnDismissListener;
28import android.content.Intent;
29import android.content.IntentFilter;
30import android.media.AudioAttributes;
31import android.net.Uri;
32import android.os.AsyncTask;
33import android.os.Handler;
34import android.os.SystemClock;
35import android.os.UserHandle;
36import android.provider.Settings;
37import android.util.Slog;
38import android.view.View;
39
40import com.android.systemui.R;
41import com.android.systemui.statusbar.phone.PhoneStatusBar;
42import com.android.systemui.statusbar.phone.SystemUIDialog;
43
44import java.io.PrintWriter;
45
46public class PowerNotificationWarnings implements PowerUI.WarningsUI {
47    private static final String TAG = PowerUI.TAG + ".Notification";
48    private static final boolean DEBUG = PowerUI.DEBUG;
49
50    private static final String TAG_NOTIFICATION = "low_battery";
51    private static final int ID_NOTIFICATION = 100;
52    private static final int AUTO_DISMISS_MS = 10000;
53
54    private static final int SHOWING_NOTHING = 0;
55    private static final int SHOWING_WARNING = 1;
56    private static final int SHOWING_SAVER = 2;
57    private static final int SHOWING_INVALID_CHARGER = 3;
58    private static final String[] SHOWING_STRINGS = {
59        "SHOWING_NOTHING",
60        "SHOWING_WARNING",
61        "SHOWING_SAVER",
62        "SHOWING_INVALID_CHARGER",
63    };
64
65    private static final String ACTION_SHOW_FALLBACK_WARNING = "PNW.warningFallback";
66    private static final String ACTION_SHOW_FALLBACK_CHARGER = "PNW.chargerFallback";
67    private static final String ACTION_SHOW_BATTERY_SETTINGS = "PNW.batterySettings";
68    private static final String ACTION_START_SAVER = "PNW.startSaver";
69    private static final String ACTION_STOP_SAVER = "PNW.stopSaver";
70
71    private static final AudioAttributes AUDIO_ATTRIBUTES = new AudioAttributes.Builder()
72            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
73            .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
74            .build();
75
76    private final Context mContext;
77    private final NotificationManager mNoMan;
78    private final Handler mHandler = new Handler();
79    private final PowerDialogWarnings mFallbackDialogs;
80    private final Receiver mReceiver = new Receiver();
81    private final Intent mOpenBatterySettings = settings(Intent.ACTION_POWER_USAGE_SUMMARY);
82    private final Intent mOpenSaverSettings = settings(Settings.ACTION_BATTERY_SAVER_SETTINGS);
83
84    private int mBatteryLevel;
85    private int mBucket;
86    private long mScreenOffTime;
87    private int mShowing;
88
89    private long mBucketDroppedNegativeTimeMs;
90
91    private boolean mSaver;
92    private boolean mWarning;
93    private boolean mPlaySound;
94    private boolean mInvalidCharger;
95    private SystemUIDialog mSaverConfirmation;
96
97    public PowerNotificationWarnings(Context context, PhoneStatusBar phoneStatusBar) {
98        mContext = context;
99        mNoMan = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
100        mFallbackDialogs = new PowerDialogWarnings(context, phoneStatusBar);
101        mReceiver.init();
102    }
103
104    @Override
105    public void dump(PrintWriter pw) {
106        pw.print("mSaver="); pw.println(mSaver);
107        pw.print("mWarning="); pw.println(mWarning);
108        pw.print("mPlaySound="); pw.println(mPlaySound);
109        pw.print("mInvalidCharger="); pw.println(mInvalidCharger);
110        pw.print("mShowing="); pw.println(SHOWING_STRINGS[mShowing]);
111        pw.print("mSaverConfirmation="); pw.println(mSaverConfirmation != null ? "not null" : null);
112    }
113
114    @Override
115    public void update(int batteryLevel, int bucket, long screenOffTime) {
116        mBatteryLevel = batteryLevel;
117        if (bucket >= 0) {
118            mBucketDroppedNegativeTimeMs = 0;
119        } else if (bucket < mBucket) {
120            mBucketDroppedNegativeTimeMs = System.currentTimeMillis();
121        }
122        mBucket = bucket;
123        mScreenOffTime = screenOffTime;
124        mFallbackDialogs.update(batteryLevel, bucket, screenOffTime);
125    }
126
127    @Override
128    public void showSaverMode(boolean mode) {
129        mSaver = mode;
130        if (mSaver && mSaverConfirmation != null) {
131            mSaverConfirmation.dismiss();
132        }
133        updateNotification();
134    }
135
136    private void updateNotification() {
137        if (DEBUG) Slog.d(TAG, "updateNotification mWarning=" + mWarning
138                + " mSaver=" + mSaver + " mInvalidCharger=" + mInvalidCharger);
139        if (mInvalidCharger) {
140            showInvalidChargerNotification();
141            mShowing = SHOWING_INVALID_CHARGER;
142        } else if (mWarning) {
143            showWarningNotification();
144            mShowing = SHOWING_WARNING;
145        } else if (mSaver) {
146            showSaverNotification();
147            mShowing = SHOWING_SAVER;
148        } else {
149            mNoMan.cancel(TAG_NOTIFICATION, ID_NOTIFICATION);
150            mShowing = SHOWING_NOTHING;
151        }
152    }
153
154    private void showInvalidChargerNotification() {
155        final Notification.Builder nb = new Notification.Builder(mContext)
156                .setSmallIcon(R.drawable.ic_power_low)
157                .setWhen(0)
158                .setShowWhen(false)
159                .setOngoing(true)
160                .setContentTitle(mContext.getString(R.string.invalid_charger_title))
161                .setContentText(mContext.getString(R.string.invalid_charger_text))
162                .setPriority(Notification.PRIORITY_MAX)
163                .setCategory(Notification.CATEGORY_SYSTEM)
164                .setVisibility(Notification.VISIBILITY_PUBLIC)
165                .setFullScreenIntent(pendingBroadcast(ACTION_SHOW_FALLBACK_CHARGER), true);
166        final Notification n = nb.build();
167        if (n.headsUpContentView != null) {
168            n.headsUpContentView.setViewVisibility(com.android.internal.R.id.right_icon, View.GONE);
169        }
170        mNoMan.notifyAsUser(TAG_NOTIFICATION, ID_NOTIFICATION, n, UserHandle.CURRENT);
171    }
172
173    private void showWarningNotification() {
174        final int textRes = mSaver ? R.string.battery_low_percent_format_saver_started
175                : R.string.battery_low_percent_format;
176        final Notification.Builder nb = new Notification.Builder(mContext)
177                .setSmallIcon(R.drawable.ic_power_low)
178                // Bump the notification when the bucket dropped.
179                .setWhen(mBucketDroppedNegativeTimeMs)
180                .setShowWhen(false)
181                .setContentTitle(mContext.getString(R.string.battery_low_title))
182                .setContentText(mContext.getString(textRes, mBatteryLevel))
183                .setOngoing(true)
184                .setOnlyAlertOnce(true)
185                .setPriority(Notification.PRIORITY_MAX)
186                .setCategory(Notification.CATEGORY_SYSTEM)
187                .setVisibility(Notification.VISIBILITY_PUBLIC)
188                .setFullScreenIntent(pendingBroadcast(ACTION_SHOW_FALLBACK_WARNING), true);
189        if (hasBatterySettings()) {
190            nb.setContentIntent(pendingBroadcast(ACTION_SHOW_BATTERY_SETTINGS));
191        }
192        if (!mSaver) {
193            nb.addAction(0,
194                    mContext.getString(R.string.battery_saver_start_action),
195                    pendingBroadcast(ACTION_START_SAVER));
196        } else {
197            addStopSaverAction(nb);
198        }
199        if (mPlaySound) {
200            attachLowBatterySound(nb);
201        }
202        final Notification n = nb.build();
203        if (n.headsUpContentView != null) {
204            n.headsUpContentView.setViewVisibility(com.android.internal.R.id.right_icon, View.GONE);
205        }
206        mNoMan.notifyAsUser(TAG_NOTIFICATION, ID_NOTIFICATION, n, UserHandle.CURRENT);
207    }
208
209    private void showSaverNotification() {
210        final Notification.Builder nb = new Notification.Builder(mContext)
211                .setSmallIcon(R.drawable.ic_power_saver)
212                .setContentTitle(mContext.getString(R.string.battery_saver_notification_title))
213                .setContentText(mContext.getString(R.string.battery_saver_notification_text))
214                .setOngoing(true)
215                .setShowWhen(false)
216                .setCategory(Notification.CATEGORY_SYSTEM)
217                .setVisibility(Notification.VISIBILITY_PUBLIC);
218        addStopSaverAction(nb);
219        if (hasSaverSettings()) {
220            nb.setContentIntent(pendingActivity(mOpenSaverSettings));
221        }
222        mNoMan.notifyAsUser(TAG_NOTIFICATION, ID_NOTIFICATION, nb.build(), UserHandle.CURRENT);
223    }
224
225    private void addStopSaverAction(Notification.Builder nb) {
226        nb.addAction(0,
227                mContext.getString(R.string.battery_saver_notification_action_text),
228                pendingBroadcast(ACTION_STOP_SAVER));
229    }
230
231    private void dismissSaverNotification() {
232        if (mSaver) Slog.i(TAG, "dismissing saver notification");
233        mSaver = false;
234        updateNotification();
235    }
236
237    private PendingIntent pendingActivity(Intent intent) {
238        return PendingIntent.getActivityAsUser(mContext,
239                0, intent, 0, null, UserHandle.CURRENT);
240    }
241
242    private PendingIntent pendingBroadcast(String action) {
243        return PendingIntent.getBroadcastAsUser(mContext,
244                0, new Intent(action), 0, UserHandle.CURRENT);
245    }
246
247    private static Intent settings(String action) {
248        return new Intent(action).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
249                | Intent.FLAG_ACTIVITY_MULTIPLE_TASK
250                | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
251                | Intent.FLAG_ACTIVITY_NO_HISTORY
252                | Intent.FLAG_ACTIVITY_CLEAR_TOP);
253    }
254
255    @Override
256    public boolean isInvalidChargerWarningShowing() {
257        return mInvalidCharger;
258    }
259
260    @Override
261    public void updateLowBatteryWarning() {
262        updateNotification();
263        mFallbackDialogs.updateLowBatteryWarning();
264    }
265
266    @Override
267    public void dismissLowBatteryWarning() {
268        if (DEBUG) Slog.d(TAG, "dismissing low battery warning: level=" + mBatteryLevel);
269        dismissLowBatteryNotification();
270        mFallbackDialogs.dismissLowBatteryWarning();
271    }
272
273    private void dismissLowBatteryNotification() {
274        if (mWarning) Slog.i(TAG, "dismissing low battery notification");
275        mWarning = false;
276        updateNotification();
277    }
278
279    private boolean hasBatterySettings() {
280        return mOpenBatterySettings.resolveActivity(mContext.getPackageManager()) != null;
281    }
282
283    private boolean hasSaverSettings() {
284        return mOpenSaverSettings.resolveActivity(mContext.getPackageManager()) != null;
285    }
286
287    @Override
288    public void showLowBatteryWarning(boolean playSound) {
289        Slog.i(TAG,
290                "show low battery warning: level=" + mBatteryLevel
291                + " [" + mBucket + "]");
292        mPlaySound = playSound;
293        mWarning = true;
294        updateNotification();
295        mHandler.removeCallbacks(mDismissLowBatteryNotification);
296        mHandler.postDelayed(mDismissLowBatteryNotification, AUTO_DISMISS_MS);
297    }
298
299    private void attachLowBatterySound(Notification.Builder b) {
300        final ContentResolver cr = mContext.getContentResolver();
301
302        final int silenceAfter = Settings.Global.getInt(cr,
303                Settings.Global.LOW_BATTERY_SOUND_TIMEOUT, 0);
304        final long offTime = SystemClock.elapsedRealtime() - mScreenOffTime;
305        if (silenceAfter > 0
306                && mScreenOffTime > 0
307                && offTime > silenceAfter) {
308            Slog.i(TAG, "screen off too long (" + offTime + "ms, limit " + silenceAfter
309                    + "ms): not waking up the user with low battery sound");
310            return;
311        }
312
313        if (DEBUG) {
314            Slog.d(TAG, "playing low battery sound. pick-a-doop!"); // WOMP-WOMP is deprecated
315        }
316
317        if (Settings.Global.getInt(cr, Settings.Global.POWER_SOUNDS_ENABLED, 1) == 1) {
318            final String soundPath = Settings.Global.getString(cr,
319                    Settings.Global.LOW_BATTERY_SOUND);
320            if (soundPath != null) {
321                final Uri soundUri = Uri.parse("file://" + soundPath);
322                if (soundUri != null) {
323                    b.setSound(soundUri, AUDIO_ATTRIBUTES);
324                    if (DEBUG) Slog.d(TAG, "playing sound " + soundUri);
325                }
326            }
327        }
328    }
329
330    @Override
331    public void dismissInvalidChargerWarning() {
332        dismissInvalidChargerNotification();
333        mFallbackDialogs.dismissInvalidChargerWarning();
334    }
335
336    private void dismissInvalidChargerNotification() {
337        if (mInvalidCharger) Slog.i(TAG, "dismissing invalid charger notification");
338        mInvalidCharger = false;
339        updateNotification();
340    }
341
342    @Override
343    public void showInvalidChargerWarning() {
344        mInvalidCharger = true;
345        updateNotification();
346    }
347
348    private void showStartSaverConfirmation() {
349        if (mSaverConfirmation != null) return;
350        final SystemUIDialog d = new SystemUIDialog(mContext);
351        d.setTitle(R.string.battery_saver_confirmation_title);
352        d.setMessage(com.android.internal.R.string.battery_saver_description);
353        d.setNegativeButton(android.R.string.cancel, null);
354        d.setPositiveButton(R.string.battery_saver_confirmation_ok, mStartSaverMode);
355        d.setShowForAllUsers(true);
356        d.setOnDismissListener(new OnDismissListener() {
357            @Override
358            public void onDismiss(DialogInterface dialog) {
359                mSaverConfirmation = null;
360            }
361        });
362        d.show();
363        mSaverConfirmation = d;
364    }
365
366    private void setSaverSetting(boolean mode) {
367        final int val = mode ? 1 : 0;
368        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.LOW_POWER_MODE, val);
369    }
370
371    private final class Receiver extends BroadcastReceiver {
372
373        public void init() {
374            IntentFilter filter = new IntentFilter();
375            filter.addAction(ACTION_SHOW_FALLBACK_WARNING);
376            filter.addAction(ACTION_SHOW_FALLBACK_CHARGER);
377            filter.addAction(ACTION_SHOW_BATTERY_SETTINGS);
378            filter.addAction(ACTION_START_SAVER);
379            filter.addAction(ACTION_STOP_SAVER);
380            mContext.registerReceiver(this, filter, null, mHandler);
381        }
382
383        @Override
384        public void onReceive(Context context, Intent intent) {
385            final String action = intent.getAction();
386            Slog.i(TAG, "Received " + action);
387            if (action.equals(ACTION_SHOW_FALLBACK_WARNING)) {
388                dismissLowBatteryNotification();
389                mFallbackDialogs.showLowBatteryWarning(false /*playSound*/);
390            } else if (action.equals(ACTION_SHOW_FALLBACK_CHARGER)) {
391                dismissInvalidChargerNotification();
392                mFallbackDialogs.showInvalidChargerWarning();
393            } else if (action.equals(ACTION_SHOW_BATTERY_SETTINGS)) {
394                dismissLowBatteryNotification();
395                mContext.startActivityAsUser(mOpenBatterySettings, UserHandle.CURRENT);
396            } else if (action.equals(ACTION_START_SAVER)) {
397                dismissLowBatteryNotification();
398                showStartSaverConfirmation();
399            } else if (action.equals(ACTION_STOP_SAVER)) {
400                dismissSaverNotification();
401                dismissLowBatteryNotification();
402                setSaverSetting(false);
403            }
404        }
405    }
406
407    private final OnClickListener mStartSaverMode = new OnClickListener() {
408        @Override
409        public void onClick(DialogInterface dialog, int which) {
410            AsyncTask.execute(new Runnable() {
411                @Override
412                public void run() {
413                    setSaverSetting(true);
414                }
415            });
416        }
417    };
418
419    private final Runnable mDismissLowBatteryNotification = new Runnable() {
420        @Override
421        public void run() {
422            dismissLowBatteryNotification();
423        }
424    };
425}
426