PowerNotificationWarnings.java revision 5b038b4b2b95e77d37b56fdf423a95d95ece83af
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.PowerManager;
35import android.os.SystemClock;
36import android.os.UserHandle;
37import android.provider.Settings;
38import android.util.Slog;
39import android.view.View;
40
41import com.android.systemui.R;
42import com.android.systemui.statusbar.phone.PhoneStatusBar;
43import com.android.systemui.statusbar.phone.SystemUIDialog;
44
45import java.io.PrintWriter;
46import java.text.NumberFormat;
47
48public class PowerNotificationWarnings implements PowerUI.WarningsUI {
49    private static final String TAG = PowerUI.TAG + ".Notification";
50    private static final boolean DEBUG = PowerUI.DEBUG;
51
52    private static final String TAG_NOTIFICATION = "low_battery";
53    private static final int ID_NOTIFICATION = 100;
54
55    private static final int SHOWING_NOTHING = 0;
56    private static final int SHOWING_WARNING = 1;
57    private static final int SHOWING_SAVER = 2;
58    private static final int SHOWING_INVALID_CHARGER = 3;
59    private static final String[] SHOWING_STRINGS = {
60        "SHOWING_NOTHING",
61        "SHOWING_WARNING",
62        "SHOWING_SAVER",
63        "SHOWING_INVALID_CHARGER",
64    };
65
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 PowerManager mPowerMan;
78    private final Handler mHandler = new Handler();
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, PhoneStatusBar phoneStatusBar) {
97        mContext = context;
98        mNoMan = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
99        mPowerMan = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
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    }
124
125    @Override
126    public void showSaverMode(boolean mode) {
127        mSaver = mode;
128        if (mSaver && mSaverConfirmation != null) {
129            mSaverConfirmation.dismiss();
130        }
131        updateNotification();
132    }
133
134    private void updateNotification() {
135        if (DEBUG) Slog.d(TAG, "updateNotification mWarning=" + mWarning + " mPlaySound="
136                + mPlaySound + " mSaver=" + mSaver + " mInvalidCharger=" + mInvalidCharger);
137        if (mInvalidCharger) {
138            showInvalidChargerNotification();
139            mShowing = SHOWING_INVALID_CHARGER;
140        } else if (mWarning) {
141            showWarningNotification();
142            mShowing = SHOWING_WARNING;
143        } else if (mSaver) {
144            showSaverNotification();
145            mShowing = SHOWING_SAVER;
146        } else {
147            mNoMan.cancel(TAG_NOTIFICATION, ID_NOTIFICATION);
148            mShowing = SHOWING_NOTHING;
149        }
150    }
151
152    private void showInvalidChargerNotification() {
153        final Notification.Builder nb = new Notification.Builder(mContext)
154                .setSmallIcon(R.drawable.ic_power_low)
155                .setWhen(0)
156                .setShowWhen(false)
157                .setOngoing(true)
158                .setContentTitle(mContext.getString(R.string.invalid_charger_title))
159                .setContentText(mContext.getString(R.string.invalid_charger_text))
160                .setPriority(Notification.PRIORITY_MAX)
161                .setVisibility(Notification.VISIBILITY_PUBLIC)
162                .setColor(mContext.getResources().getColor(
163                        com.android.internal.R.color.system_notification_accent_color));
164        final Notification n = nb.build();
165        if (n.headsUpContentView != null) {
166            n.headsUpContentView.setViewVisibility(com.android.internal.R.id.right_icon, View.GONE);
167        }
168        mNoMan.notifyAsUser(TAG_NOTIFICATION, ID_NOTIFICATION, n, UserHandle.CURRENT);
169    }
170
171    private void showWarningNotification() {
172        final int textRes = mSaver ? R.string.battery_low_percent_format_saver_started
173                : R.string.battery_low_percent_format;
174        final String percentage = NumberFormat.getPercentInstance().format((double) mBatteryLevel / 100.0);
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, percentage))
182                .setOnlyAlertOnce(true)
183                .setPriority(Notification.PRIORITY_MAX)
184                .setVisibility(Notification.VISIBILITY_PUBLIC)
185                .setColor(mContext.getResources().getColor(
186                        com.android.internal.R.color.battery_saver_mode_color));
187        if (hasBatterySettings()) {
188            nb.setContentIntent(pendingBroadcast(ACTION_SHOW_BATTERY_SETTINGS));
189        }
190        if (!mSaver) {
191            nb.addAction(0,
192                    mContext.getString(R.string.battery_saver_start_action),
193                    pendingBroadcast(ACTION_START_SAVER));
194        } else {
195            addStopSaverAction(nb);
196        }
197        if (mPlaySound) {
198            attachLowBatterySound(nb);
199            mPlaySound = false;
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                .setVisibility(Notification.VISIBILITY_PUBLIC)
216                .setColor(mContext.getResources().getColor(
217                        com.android.internal.R.color.battery_saver_mode_color));
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    }
264
265    @Override
266    public void dismissLowBatteryWarning() {
267        if (DEBUG) Slog.d(TAG, "dismissing low battery warning: level=" + mBatteryLevel);
268        dismissLowBatteryNotification();
269    }
270
271    private void dismissLowBatteryNotification() {
272        if (mWarning) Slog.i(TAG, "dismissing low battery notification");
273        mWarning = false;
274        updateNotification();
275    }
276
277    private boolean hasBatterySettings() {
278        return mOpenBatterySettings.resolveActivity(mContext.getPackageManager()) != null;
279    }
280
281    private boolean hasSaverSettings() {
282        return mOpenSaverSettings.resolveActivity(mContext.getPackageManager()) != null;
283    }
284
285    @Override
286    public void showLowBatteryWarning(boolean playSound) {
287        Slog.i(TAG,
288                "show low battery warning: level=" + mBatteryLevel
289                + " [" + mBucket + "] playSound=" + playSound);
290        mPlaySound = playSound;
291        mWarning = true;
292        updateNotification();
293    }
294
295    private void attachLowBatterySound(Notification.Builder b) {
296        final ContentResolver cr = mContext.getContentResolver();
297
298        final int silenceAfter = Settings.Global.getInt(cr,
299                Settings.Global.LOW_BATTERY_SOUND_TIMEOUT, 0);
300        final long offTime = SystemClock.elapsedRealtime() - mScreenOffTime;
301        if (silenceAfter > 0
302                && mScreenOffTime > 0
303                && offTime > silenceAfter) {
304            Slog.i(TAG, "screen off too long (" + offTime + "ms, limit " + silenceAfter
305                    + "ms): not waking up the user with low battery sound");
306            return;
307        }
308
309        if (DEBUG) {
310            Slog.d(TAG, "playing low battery sound. pick-a-doop!"); // WOMP-WOMP is deprecated
311        }
312
313        if (Settings.Global.getInt(cr, Settings.Global.POWER_SOUNDS_ENABLED, 1) == 1) {
314            final String soundPath = Settings.Global.getString(cr,
315                    Settings.Global.LOW_BATTERY_SOUND);
316            if (soundPath != null) {
317                final Uri soundUri = Uri.parse("file://" + soundPath);
318                if (soundUri != null) {
319                    b.setSound(soundUri, AUDIO_ATTRIBUTES);
320                    if (DEBUG) Slog.d(TAG, "playing sound " + soundUri);
321                }
322            }
323        }
324    }
325
326    @Override
327    public void dismissInvalidChargerWarning() {
328        dismissInvalidChargerNotification();
329    }
330
331    private void dismissInvalidChargerNotification() {
332        if (mInvalidCharger) Slog.i(TAG, "dismissing invalid charger notification");
333        mInvalidCharger = false;
334        updateNotification();
335    }
336
337    @Override
338    public void showInvalidChargerWarning() {
339        mInvalidCharger = true;
340        updateNotification();
341    }
342
343    private void showStartSaverConfirmation() {
344        if (mSaverConfirmation != null) return;
345        final SystemUIDialog d = new SystemUIDialog(mContext);
346        d.setTitle(R.string.battery_saver_confirmation_title);
347        d.setMessage(com.android.internal.R.string.battery_saver_description);
348        d.setNegativeButton(android.R.string.cancel, null);
349        d.setPositiveButton(R.string.battery_saver_confirmation_ok, mStartSaverMode);
350        d.setShowForAllUsers(true);
351        d.setOnDismissListener(new OnDismissListener() {
352            @Override
353            public void onDismiss(DialogInterface dialog) {
354                mSaverConfirmation = null;
355            }
356        });
357        d.show();
358        mSaverConfirmation = d;
359    }
360
361    private void setSaverMode(boolean mode) {
362        mPowerMan.setPowerSaveMode(mode);
363    }
364
365    private final class Receiver extends BroadcastReceiver {
366
367        public void init() {
368            IntentFilter filter = new IntentFilter();
369            filter.addAction(ACTION_SHOW_BATTERY_SETTINGS);
370            filter.addAction(ACTION_START_SAVER);
371            filter.addAction(ACTION_STOP_SAVER);
372            mContext.registerReceiver(this, filter, null, mHandler);
373        }
374
375        @Override
376        public void onReceive(Context context, Intent intent) {
377            final String action = intent.getAction();
378            Slog.i(TAG, "Received " + action);
379            if (action.equals(ACTION_SHOW_BATTERY_SETTINGS)) {
380                dismissLowBatteryNotification();
381                mContext.startActivityAsUser(mOpenBatterySettings, UserHandle.CURRENT);
382            } else if (action.equals(ACTION_START_SAVER)) {
383                dismissLowBatteryNotification();
384                showStartSaverConfirmation();
385            } else if (action.equals(ACTION_STOP_SAVER)) {
386                dismissSaverNotification();
387                dismissLowBatteryNotification();
388                setSaverMode(false);
389            }
390        }
391    }
392
393    private final OnClickListener mStartSaverMode = new OnClickListener() {
394        @Override
395        public void onClick(DialogInterface dialog, int which) {
396            AsyncTask.execute(new Runnable() {
397                @Override
398                public void run() {
399                    setSaverMode(true);
400                }
401            });
402        }
403    };
404}
405