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