Home | History | Annotate | Download | only in power
      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 
     47 public 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 
     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 
     69     private static final AudioAttributes AUDIO_ATTRIBUTES = new AudioAttributes.Builder()
     70             .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
     71             .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
     72             .build();
     73 
     74     private final Context mContext;
     75     private final NotificationManager mNoMan;
     76     private final PowerManager mPowerMan;
     77     private final Handler mHandler = new Handler();
     78     private final Receiver mReceiver = new Receiver();
     79     private final Intent mOpenBatterySettings = settings(Intent.ACTION_POWER_USAGE_SUMMARY);
     80     private final Intent mOpenSaverSettings = settings(Settings.ACTION_BATTERY_SAVER_SETTINGS);
     81 
     82     private int mBatteryLevel;
     83     private int mBucket;
     84     private long mScreenOffTime;
     85     private int mShowing;
     86 
     87     private long mBucketDroppedNegativeTimeMs;
     88 
     89     private boolean mSaver;
     90     private boolean mWarning;
     91     private boolean mPlaySound;
     92     private boolean mInvalidCharger;
     93     private SystemUIDialog mSaverConfirmation;
     94 
     95     public PowerNotificationWarnings(Context context, PhoneStatusBar phoneStatusBar) {
     96         mContext = context;
     97         mNoMan = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
     98         mPowerMan = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
     99         mReceiver.init();
    100     }
    101 
    102     @Override
    103     public void dump(PrintWriter pw) {
    104         pw.print("mSaver="); pw.println(mSaver);
    105         pw.print("mWarning="); pw.println(mWarning);
    106         pw.print("mPlaySound="); pw.println(mPlaySound);
    107         pw.print("mInvalidCharger="); pw.println(mInvalidCharger);
    108         pw.print("mShowing="); pw.println(SHOWING_STRINGS[mShowing]);
    109         pw.print("mSaverConfirmation="); pw.println(mSaverConfirmation != null ? "not null" : null);
    110     }
    111 
    112     @Override
    113     public void update(int batteryLevel, int bucket, long screenOffTime) {
    114         mBatteryLevel = batteryLevel;
    115         if (bucket >= 0) {
    116             mBucketDroppedNegativeTimeMs = 0;
    117         } else if (bucket < mBucket) {
    118             mBucketDroppedNegativeTimeMs = System.currentTimeMillis();
    119         }
    120         mBucket = bucket;
    121         mScreenOffTime = screenOffTime;
    122     }
    123 
    124     @Override
    125     public void showSaverMode(boolean mode) {
    126         mSaver = mode;
    127         if (mSaver && mSaverConfirmation != null) {
    128             mSaverConfirmation.dismiss();
    129         }
    130         updateNotification();
    131     }
    132 
    133     private void updateNotification() {
    134         if (DEBUG) Slog.d(TAG, "updateNotification mWarning=" + mWarning + " mPlaySound="
    135                 + mPlaySound + " mSaver=" + mSaver + " mInvalidCharger=" + mInvalidCharger);
    136         if (mInvalidCharger) {
    137             showInvalidChargerNotification();
    138             mShowing = SHOWING_INVALID_CHARGER;
    139         } else if (mWarning) {
    140             showWarningNotification();
    141             mShowing = SHOWING_WARNING;
    142         } else if (mSaver) {
    143             showSaverNotification();
    144             mShowing = SHOWING_SAVER;
    145         } else {
    146             mNoMan.cancel(TAG_NOTIFICATION, ID_NOTIFICATION);
    147             mShowing = SHOWING_NOTHING;
    148         }
    149     }
    150 
    151     private void showInvalidChargerNotification() {
    152         final Notification.Builder nb = new Notification.Builder(mContext)
    153                 .setSmallIcon(R.drawable.ic_power_low)
    154                 .setWhen(0)
    155                 .setShowWhen(false)
    156                 .setOngoing(true)
    157                 .setContentTitle(mContext.getString(R.string.invalid_charger_title))
    158                 .setContentText(mContext.getString(R.string.invalid_charger_text))
    159                 .setPriority(Notification.PRIORITY_MAX)
    160                 .setCategory(Notification.CATEGORY_SYSTEM)
    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 Notification.Builder nb = new Notification.Builder(mContext)
    175                 .setSmallIcon(R.drawable.ic_power_low)
    176                 // Bump the notification when the bucket dropped.
    177                 .setWhen(mBucketDroppedNegativeTimeMs)
    178                 .setShowWhen(false)
    179                 .setContentTitle(mContext.getString(R.string.battery_low_title))
    180                 .setContentText(mContext.getString(textRes, mBatteryLevel))
    181                 .setOnlyAlertOnce(true)
    182                 .setPriority(Notification.PRIORITY_MAX)
    183                 .setCategory(Notification.CATEGORY_SYSTEM)
    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                 .setCategory(Notification.CATEGORY_SYSTEM)
    216                 .setVisibility(Notification.VISIBILITY_PUBLIC)
    217                 .setColor(mContext.getResources().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, ID_NOTIFICATION, nb.build(), UserHandle.CURRENT);
    224     }
    225 
    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 
    232     private void dismissSaverNotification() {
    233         if (mSaver) Slog.i(TAG, "dismissing saver notification");
    234         mSaver = false;
    235         updateNotification();
    236     }
    237 
    238     private PendingIntent pendingActivity(Intent intent) {
    239         return PendingIntent.getActivityAsUser(mContext,
    240                 0, intent, 0, null, UserHandle.CURRENT);
    241     }
    242 
    243     private PendingIntent pendingBroadcast(String action) {
    244         return PendingIntent.getBroadcastAsUser(mContext,
    245                 0, new Intent(action), 0, UserHandle.CURRENT);
    246     }
    247 
    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
    257     public boolean isInvalidChargerWarningShowing() {
    258         return mInvalidCharger;
    259     }
    260 
    261     @Override
    262     public void updateLowBatteryWarning() {
    263         updateNotification();
    264     }
    265 
    266     @Override
    267     public void dismissLowBatteryWarning() {
    268         if (DEBUG) Slog.d(TAG, "dismissing low battery warning: level=" + mBatteryLevel);
    269         dismissLowBatteryNotification();
    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 + "] playSound=" + playSound);
    291         mPlaySound = playSound;
    292         mWarning = true;
    293         updateNotification();
    294     }
    295 
    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
    328     public void dismissInvalidChargerWarning() {
    329         dismissInvalidChargerNotification();
    330     }
    331 
    332     private void dismissInvalidChargerNotification() {
    333         if (mInvalidCharger) Slog.i(TAG, "dismissing invalid charger notification");
    334         mInvalidCharger = false;
    335         updateNotification();
    336     }
    337 
    338     @Override
    339     public void showInvalidChargerWarning() {
    340         mInvalidCharger = true;
    341         updateNotification();
    342     }
    343 
    344     private void showStartSaverConfirmation() {
    345         if (mSaverConfirmation != null) return;
    346         final SystemUIDialog d = new SystemUIDialog(mContext);
    347         d.setTitle(R.string.battery_saver_confirmation_title);
    348         d.setMessage(com.android.internal.R.string.battery_saver_description);
    349         d.setNegativeButton(android.R.string.cancel, null);
    350         d.setPositiveButton(R.string.battery_saver_confirmation_ok, mStartSaverMode);
    351         d.setShowForAllUsers(true);
    352         d.setOnDismissListener(new OnDismissListener() {
    353             @Override
    354             public void onDismiss(DialogInterface dialog) {
    355                 mSaverConfirmation = null;
    356             }
    357         });
    358         d.show();
    359         mSaverConfirmation = d;
    360     }
    361 
    362     private void setSaverMode(boolean mode) {
    363         mPowerMan.setPowerSaveMode(mode);
    364     }
    365 
    366     private final class Receiver extends BroadcastReceiver {
    367 
    368         public void init() {
    369             IntentFilter filter = new IntentFilter();
    370             filter.addAction(ACTION_SHOW_BATTERY_SETTINGS);
    371             filter.addAction(ACTION_START_SAVER);
    372             filter.addAction(ACTION_STOP_SAVER);
    373             mContext.registerReceiver(this, filter, null, mHandler);
    374         }
    375 
    376         @Override
    377         public void onReceive(Context context, Intent intent) {
    378             final String action = intent.getAction();
    379             Slog.i(TAG, "Received " + action);
    380             if (action.equals(ACTION_SHOW_BATTERY_SETTINGS)) {
    381                 dismissLowBatteryNotification();
    382                 mContext.startActivityAsUser(mOpenBatterySettings, UserHandle.CURRENT);
    383             } else if (action.equals(ACTION_START_SAVER)) {
    384                 dismissLowBatteryNotification();
    385                 showStartSaverConfirmation();
    386             } else if (action.equals(ACTION_STOP_SAVER)) {
    387                 dismissSaverNotification();
    388                 dismissLowBatteryNotification();
    389                 setSaverMode(false);
    390             }
    391         }
    392     }
    393 
    394     private final OnClickListener mStartSaverMode = new OnClickListener() {
    395         @Override
    396         public void onClick(DialogInterface dialog, int which) {
    397             AsyncTask.execute(new Runnable() {
    398                 @Override
    399                 public void run() {
    400                     setSaverMode(true);
    401                 }
    402             });
    403         }
    404     };
    405 }
    406