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