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