• 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 
40 import com.android.systemui.R;
41 import com.android.systemui.SystemUI;
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_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_BATTERY_SETTINGS = "PNW.batterySettings";
65     private static final String ACTION_START_SAVER = "PNW.startSaver";
66     private static final String ACTION_DISMISSED_WARNING = "PNW.dismissedWarning";
67 
68     private static final AudioAttributes AUDIO_ATTRIBUTES = new AudioAttributes.Builder()
69             .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
70             .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
71             .build();
72 
73     private final Context mContext;
74     private final NotificationManager mNoMan;
75     private final PowerManager mPowerMan;
76     private final Handler mHandler = new Handler();
77     private final Receiver mReceiver = new Receiver();
78     private final Intent mOpenBatterySettings = settings(Intent.ACTION_POWER_USAGE_SUMMARY);
79 
80     private int mBatteryLevel;
81     private int mBucket;
82     private long mScreenOffTime;
83     private int mShowing;
84 
85     private long mBucketDroppedNegativeTimeMs;
86 
87     private boolean mWarning;
88     private boolean mPlaySound;
89     private boolean mInvalidCharger;
90     private SystemUIDialog mSaverConfirmation;
91 
PowerNotificationWarnings(Context context, PhoneStatusBar phoneStatusBar)92     public PowerNotificationWarnings(Context context, PhoneStatusBar phoneStatusBar) {
93         mContext = context;
94         mNoMan = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
95         mPowerMan = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
96         mReceiver.init();
97     }
98 
99     @Override
dump(PrintWriter pw)100     public void dump(PrintWriter pw) {
101         pw.print("mWarning="); pw.println(mWarning);
102         pw.print("mPlaySound="); pw.println(mPlaySound);
103         pw.print("mInvalidCharger="); pw.println(mInvalidCharger);
104         pw.print("mShowing="); pw.println(SHOWING_STRINGS[mShowing]);
105         pw.print("mSaverConfirmation="); pw.println(mSaverConfirmation != null ? "not null" : null);
106     }
107 
108     @Override
update(int batteryLevel, int bucket, long screenOffTime)109     public void update(int batteryLevel, int bucket, long screenOffTime) {
110         mBatteryLevel = batteryLevel;
111         if (bucket >= 0) {
112             mBucketDroppedNegativeTimeMs = 0;
113         } else if (bucket < mBucket) {
114             mBucketDroppedNegativeTimeMs = System.currentTimeMillis();
115         }
116         mBucket = bucket;
117         mScreenOffTime = screenOffTime;
118     }
119 
updateNotification()120     private void updateNotification() {
121         if (DEBUG) Slog.d(TAG, "updateNotification mWarning=" + mWarning + " mPlaySound="
122                 + mPlaySound + " mInvalidCharger=" + mInvalidCharger);
123         if (mInvalidCharger) {
124             showInvalidChargerNotification();
125             mShowing = SHOWING_INVALID_CHARGER;
126         } else if (mWarning) {
127             showWarningNotification();
128             mShowing = SHOWING_WARNING;
129         } else {
130             mNoMan.cancelAsUser(TAG_NOTIFICATION, R.id.notification_power, UserHandle.ALL);
131             mShowing = SHOWING_NOTHING;
132         }
133     }
134 
showInvalidChargerNotification()135     private void showInvalidChargerNotification() {
136         final Notification.Builder nb = new Notification.Builder(mContext)
137                 .setSmallIcon(R.drawable.ic_power_low)
138                 .setWhen(0)
139                 .setShowWhen(false)
140                 .setOngoing(true)
141                 .setContentTitle(mContext.getString(R.string.invalid_charger_title))
142                 .setContentText(mContext.getString(R.string.invalid_charger_text))
143                 .setPriority(Notification.PRIORITY_MAX)
144                 .setVisibility(Notification.VISIBILITY_PUBLIC)
145                 .setColor(mContext.getColor(
146                         com.android.internal.R.color.system_notification_accent_color));
147         SystemUI.overrideNotificationAppName(mContext, nb);
148         final Notification n = nb.build();
149         mNoMan.notifyAsUser(TAG_NOTIFICATION, R.id.notification_power, n, UserHandle.ALL);
150     }
151 
showWarningNotification()152     private void showWarningNotification() {
153         final int textRes = R.string.battery_low_percent_format;
154         final String percentage = NumberFormat.getPercentInstance().format((double) mBatteryLevel / 100.0);
155         final Notification.Builder nb = new Notification.Builder(mContext)
156                 .setSmallIcon(R.drawable.ic_power_low)
157                 // Bump the notification when the bucket dropped.
158                 .setWhen(mBucketDroppedNegativeTimeMs)
159                 .setShowWhen(false)
160                 .setContentTitle(mContext.getString(R.string.battery_low_title))
161                 .setContentText(mContext.getString(textRes, percentage))
162                 .setOnlyAlertOnce(true)
163                 .setDeleteIntent(pendingBroadcast(ACTION_DISMISSED_WARNING))
164                 .setPriority(Notification.PRIORITY_MAX)
165                 .setVisibility(Notification.VISIBILITY_PUBLIC)
166                 .setColor(mContext.getColor(
167                         com.android.internal.R.color.battery_saver_mode_color));
168         if (hasBatterySettings()) {
169             nb.setContentIntent(pendingBroadcast(ACTION_SHOW_BATTERY_SETTINGS));
170         }
171         nb.addAction(0,
172                 mContext.getString(R.string.battery_saver_start_action),
173                 pendingBroadcast(ACTION_START_SAVER));
174         if (mPlaySound) {
175             attachLowBatterySound(nb);
176             mPlaySound = false;
177         }
178         SystemUI.overrideNotificationAppName(mContext, nb);
179         mNoMan.notifyAsUser(TAG_NOTIFICATION, R.id.notification_power, nb.build(), UserHandle.ALL);
180     }
181 
pendingActivity(Intent intent)182     private PendingIntent pendingActivity(Intent intent) {
183         return PendingIntent.getActivityAsUser(mContext,
184                 0, intent, 0, null, UserHandle.CURRENT);
185     }
186 
pendingBroadcast(String action)187     private PendingIntent pendingBroadcast(String action) {
188         return PendingIntent.getBroadcastAsUser(mContext,
189                 0, new Intent(action), 0, UserHandle.CURRENT);
190     }
191 
settings(String action)192     private static Intent settings(String action) {
193         return new Intent(action).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
194                 | Intent.FLAG_ACTIVITY_MULTIPLE_TASK
195                 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
196                 | Intent.FLAG_ACTIVITY_NO_HISTORY
197                 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
198     }
199 
200     @Override
isInvalidChargerWarningShowing()201     public boolean isInvalidChargerWarningShowing() {
202         return mInvalidCharger;
203     }
204 
205     @Override
updateLowBatteryWarning()206     public void updateLowBatteryWarning() {
207         updateNotification();
208     }
209 
210     @Override
dismissLowBatteryWarning()211     public void dismissLowBatteryWarning() {
212         if (DEBUG) Slog.d(TAG, "dismissing low battery warning: level=" + mBatteryLevel);
213         dismissLowBatteryNotification();
214     }
215 
dismissLowBatteryNotification()216     private void dismissLowBatteryNotification() {
217         if (mWarning) Slog.i(TAG, "dismissing low battery notification");
218         mWarning = false;
219         updateNotification();
220     }
221 
hasBatterySettings()222     private boolean hasBatterySettings() {
223         return mOpenBatterySettings.resolveActivity(mContext.getPackageManager()) != null;
224     }
225 
226     @Override
showLowBatteryWarning(boolean playSound)227     public void showLowBatteryWarning(boolean playSound) {
228         Slog.i(TAG,
229                 "show low battery warning: level=" + mBatteryLevel
230                 + " [" + mBucket + "] playSound=" + playSound);
231         mPlaySound = playSound;
232         mWarning = true;
233         updateNotification();
234     }
235 
attachLowBatterySound(Notification.Builder b)236     private void attachLowBatterySound(Notification.Builder b) {
237         final ContentResolver cr = mContext.getContentResolver();
238 
239         final int silenceAfter = Settings.Global.getInt(cr,
240                 Settings.Global.LOW_BATTERY_SOUND_TIMEOUT, 0);
241         final long offTime = SystemClock.elapsedRealtime() - mScreenOffTime;
242         if (silenceAfter > 0
243                 && mScreenOffTime > 0
244                 && offTime > silenceAfter) {
245             Slog.i(TAG, "screen off too long (" + offTime + "ms, limit " + silenceAfter
246                     + "ms): not waking up the user with low battery sound");
247             return;
248         }
249 
250         if (DEBUG) {
251             Slog.d(TAG, "playing low battery sound. pick-a-doop!"); // WOMP-WOMP is deprecated
252         }
253 
254         if (Settings.Global.getInt(cr, Settings.Global.POWER_SOUNDS_ENABLED, 1) == 1) {
255             final String soundPath = Settings.Global.getString(cr,
256                     Settings.Global.LOW_BATTERY_SOUND);
257             if (soundPath != null) {
258                 final Uri soundUri = Uri.parse("file://" + soundPath);
259                 if (soundUri != null) {
260                     b.setSound(soundUri, AUDIO_ATTRIBUTES);
261                     if (DEBUG) Slog.d(TAG, "playing sound " + soundUri);
262                 }
263             }
264         }
265     }
266 
267     @Override
dismissInvalidChargerWarning()268     public void dismissInvalidChargerWarning() {
269         dismissInvalidChargerNotification();
270     }
271 
dismissInvalidChargerNotification()272     private void dismissInvalidChargerNotification() {
273         if (mInvalidCharger) Slog.i(TAG, "dismissing invalid charger notification");
274         mInvalidCharger = false;
275         updateNotification();
276     }
277 
278     @Override
showInvalidChargerWarning()279     public void showInvalidChargerWarning() {
280         mInvalidCharger = true;
281         updateNotification();
282     }
283 
284     @Override
userSwitched()285     public void userSwitched() {
286         updateNotification();
287     }
288 
showStartSaverConfirmation()289     private void showStartSaverConfirmation() {
290         if (mSaverConfirmation != null) return;
291         final SystemUIDialog d = new SystemUIDialog(mContext);
292         d.setTitle(R.string.battery_saver_confirmation_title);
293         d.setMessage(com.android.internal.R.string.battery_saver_description);
294         d.setNegativeButton(android.R.string.cancel, null);
295         d.setPositiveButton(R.string.battery_saver_confirmation_ok, mStartSaverMode);
296         d.setShowForAllUsers(true);
297         d.setOnDismissListener(new OnDismissListener() {
298             @Override
299             public void onDismiss(DialogInterface dialog) {
300                 mSaverConfirmation = null;
301             }
302         });
303         d.show();
304         mSaverConfirmation = d;
305     }
306 
setSaverMode(boolean mode)307     private void setSaverMode(boolean mode) {
308         mPowerMan.setPowerSaveMode(mode);
309     }
310 
311     private final class Receiver extends BroadcastReceiver {
312 
init()313         public void init() {
314             IntentFilter filter = new IntentFilter();
315             filter.addAction(ACTION_SHOW_BATTERY_SETTINGS);
316             filter.addAction(ACTION_START_SAVER);
317             filter.addAction(ACTION_DISMISSED_WARNING);
318             mContext.registerReceiverAsUser(this, UserHandle.ALL, filter,
319                     android.Manifest.permission.STATUS_BAR_SERVICE, mHandler);
320         }
321 
322         @Override
onReceive(Context context, Intent intent)323         public void onReceive(Context context, Intent intent) {
324             final String action = intent.getAction();
325             Slog.i(TAG, "Received " + action);
326             if (action.equals(ACTION_SHOW_BATTERY_SETTINGS)) {
327                 dismissLowBatteryNotification();
328                 mContext.startActivityAsUser(mOpenBatterySettings, UserHandle.CURRENT);
329             } else if (action.equals(ACTION_START_SAVER)) {
330                 dismissLowBatteryNotification();
331                 showStartSaverConfirmation();
332             } else if (action.equals(ACTION_DISMISSED_WARNING)) {
333                 dismissLowBatteryWarning();
334             }
335         }
336     }
337 
338     private final OnClickListener mStartSaverMode = new OnClickListener() {
339         @Override
340         public void onClick(DialogInterface dialog, int which) {
341             AsyncTask.execute(new Runnable() {
342                 @Override
343                 public void run() {
344                     setSaverMode(true);
345                 }
346             });
347         }
348     };
349 }
350