• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.systemui.power;
18 
19 import android.app.Notification;
20 import android.app.NotificationManager;
21 import android.app.PendingIntent;
22 import android.content.BroadcastReceiver;
23 import android.content.ContentResolver;
24 import android.content.Context;
25 import android.content.DialogInterface;
26 import android.content.DialogInterface.OnClickListener;
27 import android.content.DialogInterface.OnDismissListener;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.media.AudioAttributes;
31 import android.net.Uri;
32 import android.os.AsyncTask;
33 import android.os.Handler;
34 import android.os.PowerManager;
35 import android.os.SystemClock;
36 import android.os.UserHandle;
37 import android.provider.Settings;
38 import android.util.Slog;
39 import android.view.View;
40 
41 import com.android.systemui.R;
42 import com.android.systemui.statusbar.phone.PhoneStatusBar;
43 import com.android.systemui.statusbar.phone.SystemUIDialog;
44 
45 import java.io.PrintWriter;
46 import java.text.NumberFormat;
47 
48 public 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 
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_BATTERY_SETTINGS = "PNW.batterySettings";
66     private static final String ACTION_START_SAVER = "PNW.startSaver";
67     private static final String ACTION_STOP_SAVER = "PNW.stopSaver";
68     private static final String ACTION_DISMISSED_WARNING = "PNW.dismissedWarning";
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 
PowerNotificationWarnings(Context context, PhoneStatusBar phoneStatusBar)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
dump(PrintWriter pw)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
update(int batteryLevel, int bucket, long screenOffTime)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
showSaverMode(boolean mode)126     public void showSaverMode(boolean mode) {
127         mSaver = mode;
128         if (mSaver && mSaverConfirmation != null) {
129             mSaverConfirmation.dismiss();
130         }
131         updateNotification();
132     }
133 
updateNotification()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.cancelAsUser(TAG_NOTIFICATION, R.id.notification_power, UserHandle.ALL);
148             mShowing = SHOWING_NOTHING;
149         }
150     }
151 
showInvalidChargerNotification()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.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, R.id.notification_power, n, UserHandle.ALL);
169     }
170 
showWarningNotification()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                 .setDeleteIntent(pendingBroadcast(ACTION_DISMISSED_WARNING))
184                 .setPriority(Notification.PRIORITY_MAX)
185                 .setVisibility(Notification.VISIBILITY_PUBLIC)
186                 .setColor(mContext.getColor(
187                         com.android.internal.R.color.battery_saver_mode_color));
188         if (hasBatterySettings()) {
189             nb.setContentIntent(pendingBroadcast(ACTION_SHOW_BATTERY_SETTINGS));
190         }
191         if (!mSaver) {
192             nb.addAction(0,
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             mPlaySound = false;
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, R.id.notification_power, n, UserHandle.ALL);
207     }
208 
showSaverNotification()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                 .setVisibility(Notification.VISIBILITY_PUBLIC)
217                 .setColor(mContext.getColor(
218                         com.android.internal.R.color.battery_saver_mode_color));
219         addStopSaverAction(nb);
220         if (hasSaverSettings()) {
221             nb.setContentIntent(pendingActivity(mOpenSaverSettings));
222         }
223         mNoMan.notifyAsUser(TAG_NOTIFICATION, R.id.notification_power, nb.build(), UserHandle.ALL);
224     }
225 
addStopSaverAction(Notification.Builder nb)226     private void addStopSaverAction(Notification.Builder nb) {
227         nb.addAction(0,
228                 mContext.getString(R.string.battery_saver_notification_action_text),
229                 pendingBroadcast(ACTION_STOP_SAVER));
230     }
231 
dismissSaverNotification()232     private void dismissSaverNotification() {
233         if (mSaver) Slog.i(TAG, "dismissing saver notification");
234         mSaver = false;
235         updateNotification();
236     }
237 
pendingActivity(Intent intent)238     private PendingIntent pendingActivity(Intent intent) {
239         return PendingIntent.getActivityAsUser(mContext,
240                 0, intent, 0, null, UserHandle.CURRENT);
241     }
242 
pendingBroadcast(String action)243     private PendingIntent pendingBroadcast(String action) {
244         return PendingIntent.getBroadcastAsUser(mContext,
245                 0, new Intent(action), 0, UserHandle.CURRENT);
246     }
247 
settings(String action)248     private static Intent settings(String action) {
249         return new Intent(action).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
250                 | Intent.FLAG_ACTIVITY_MULTIPLE_TASK
251                 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
252                 | Intent.FLAG_ACTIVITY_NO_HISTORY
253                 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
254     }
255 
256     @Override
isInvalidChargerWarningShowing()257     public boolean isInvalidChargerWarningShowing() {
258         return mInvalidCharger;
259     }
260 
261     @Override
updateLowBatteryWarning()262     public void updateLowBatteryWarning() {
263         updateNotification();
264     }
265 
266     @Override
dismissLowBatteryWarning()267     public void dismissLowBatteryWarning() {
268         if (DEBUG) Slog.d(TAG, "dismissing low battery warning: level=" + mBatteryLevel);
269         dismissLowBatteryNotification();
270     }
271 
dismissLowBatteryNotification()272     private void dismissLowBatteryNotification() {
273         if (mWarning) Slog.i(TAG, "dismissing low battery notification");
274         mWarning = false;
275         updateNotification();
276     }
277 
hasBatterySettings()278     private boolean hasBatterySettings() {
279         return mOpenBatterySettings.resolveActivity(mContext.getPackageManager()) != null;
280     }
281 
hasSaverSettings()282     private boolean hasSaverSettings() {
283         return mOpenSaverSettings.resolveActivity(mContext.getPackageManager()) != null;
284     }
285 
286     @Override
showLowBatteryWarning(boolean playSound)287     public void showLowBatteryWarning(boolean playSound) {
288         Slog.i(TAG,
289                 "show low battery warning: level=" + mBatteryLevel
290                 + " [" + mBucket + "] playSound=" + playSound);
291         mPlaySound = playSound;
292         mWarning = true;
293         updateNotification();
294     }
295 
attachLowBatterySound(Notification.Builder b)296     private void attachLowBatterySound(Notification.Builder b) {
297         final ContentResolver cr = mContext.getContentResolver();
298 
299         final int silenceAfter = Settings.Global.getInt(cr,
300                 Settings.Global.LOW_BATTERY_SOUND_TIMEOUT, 0);
301         final long offTime = SystemClock.elapsedRealtime() - mScreenOffTime;
302         if (silenceAfter > 0
303                 && mScreenOffTime > 0
304                 && offTime > silenceAfter) {
305             Slog.i(TAG, "screen off too long (" + offTime + "ms, limit " + silenceAfter
306                     + "ms): not waking up the user with low battery sound");
307             return;
308         }
309 
310         if (DEBUG) {
311             Slog.d(TAG, "playing low battery sound. pick-a-doop!"); // WOMP-WOMP is deprecated
312         }
313 
314         if (Settings.Global.getInt(cr, Settings.Global.POWER_SOUNDS_ENABLED, 1) == 1) {
315             final String soundPath = Settings.Global.getString(cr,
316                     Settings.Global.LOW_BATTERY_SOUND);
317             if (soundPath != null) {
318                 final Uri soundUri = Uri.parse("file://" + soundPath);
319                 if (soundUri != null) {
320                     b.setSound(soundUri, AUDIO_ATTRIBUTES);
321                     if (DEBUG) Slog.d(TAG, "playing sound " + soundUri);
322                 }
323             }
324         }
325     }
326 
327     @Override
dismissInvalidChargerWarning()328     public void dismissInvalidChargerWarning() {
329         dismissInvalidChargerNotification();
330     }
331 
dismissInvalidChargerNotification()332     private void dismissInvalidChargerNotification() {
333         if (mInvalidCharger) Slog.i(TAG, "dismissing invalid charger notification");
334         mInvalidCharger = false;
335         updateNotification();
336     }
337 
338     @Override
showInvalidChargerWarning()339     public void showInvalidChargerWarning() {
340         mInvalidCharger = true;
341         updateNotification();
342     }
343 
344     @Override
userSwitched()345     public void userSwitched() {
346         updateNotification();
347     }
348 
showStartSaverConfirmation()349     private void showStartSaverConfirmation() {
350         if (mSaverConfirmation != null) return;
351         final SystemUIDialog d = new SystemUIDialog(mContext);
352         d.setTitle(R.string.battery_saver_confirmation_title);
353         d.setMessage(com.android.internal.R.string.battery_saver_description);
354         d.setNegativeButton(android.R.string.cancel, null);
355         d.setPositiveButton(R.string.battery_saver_confirmation_ok, mStartSaverMode);
356         d.setShowForAllUsers(true);
357         d.setOnDismissListener(new OnDismissListener() {
358             @Override
359             public void onDismiss(DialogInterface dialog) {
360                 mSaverConfirmation = null;
361             }
362         });
363         d.show();
364         mSaverConfirmation = d;
365     }
366 
setSaverMode(boolean mode)367     private void setSaverMode(boolean mode) {
368         mPowerMan.setPowerSaveMode(mode);
369     }
370 
371     private final class Receiver extends BroadcastReceiver {
372 
init()373         public void init() {
374             IntentFilter filter = new IntentFilter();
375             filter.addAction(ACTION_SHOW_BATTERY_SETTINGS);
376             filter.addAction(ACTION_START_SAVER);
377             filter.addAction(ACTION_STOP_SAVER);
378             filter.addAction(ACTION_DISMISSED_WARNING);
379             mContext.registerReceiverAsUser(this, UserHandle.ALL, filter,
380                     android.Manifest.permission.STATUS_BAR_SERVICE, mHandler);
381         }
382 
383         @Override
onReceive(Context context, Intent intent)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_BATTERY_SETTINGS)) {
388                 dismissLowBatteryNotification();
389                 mContext.startActivityAsUser(mOpenBatterySettings, UserHandle.CURRENT);
390             } else if (action.equals(ACTION_START_SAVER)) {
391                 dismissLowBatteryNotification();
392                 showStartSaverConfirmation();
393             } else if (action.equals(ACTION_STOP_SAVER)) {
394                 dismissSaverNotification();
395                 dismissLowBatteryNotification();
396                 setSaverMode(false);
397             } else if (action.equals(ACTION_DISMISSED_WARNING)) {
398                 dismissLowBatteryWarning();
399             }
400         }
401     }
402 
403     private final OnClickListener mStartSaverMode = new OnClickListener() {
404         @Override
405         public void onClick(DialogInterface dialog, int which) {
406             AsyncTask.execute(new Runnable() {
407                 @Override
408                 public void run() {
409                     setSaverMode(true);
410                 }
411             });
412         }
413     };
414 }
415