Home | History | Annotate | Download | only in power
      1 /*
      2  * Copyright (C) 2008 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.AlertDialog;
     20 import android.content.BroadcastReceiver;
     21 import android.content.ContentResolver;
     22 import android.content.Context;
     23 import android.content.DialogInterface;
     24 import android.content.Intent;
     25 import android.content.IntentFilter;
     26 import android.media.AudioManager;
     27 import android.media.Ringtone;
     28 import android.media.RingtoneManager;
     29 import android.net.Uri;
     30 import android.os.BatteryManager;
     31 import android.os.Handler;
     32 import android.os.PowerManager;
     33 import android.os.SystemClock;
     34 import android.os.UserHandle;
     35 import android.provider.Settings;
     36 import android.util.Slog;
     37 import android.view.View;
     38 import android.view.WindowManager;
     39 import android.widget.TextView;
     40 
     41 import com.android.systemui.R;
     42 import com.android.systemui.SystemUI;
     43 
     44 import java.io.FileDescriptor;
     45 import java.io.PrintWriter;
     46 import java.util.Arrays;
     47 
     48 public class PowerUI extends SystemUI {
     49     static final String TAG = "PowerUI";
     50 
     51     static final boolean DEBUG = false;
     52 
     53     Handler mHandler = new Handler();
     54 
     55     int mBatteryLevel = 100;
     56     int mBatteryStatus = BatteryManager.BATTERY_STATUS_UNKNOWN;
     57     int mPlugType = 0;
     58     int mInvalidCharger = 0;
     59 
     60     int mLowBatteryAlertCloseLevel;
     61     int[] mLowBatteryReminderLevels = new int[2];
     62 
     63     AlertDialog mInvalidChargerDialog;
     64     AlertDialog mLowBatteryDialog;
     65     TextView mBatteryLevelTextView;
     66 
     67     private long mScreenOffTime = -1;
     68 
     69     public void start() {
     70 
     71         mLowBatteryAlertCloseLevel = mContext.getResources().getInteger(
     72                 com.android.internal.R.integer.config_lowBatteryCloseWarningLevel);
     73         mLowBatteryReminderLevels[0] = mContext.getResources().getInteger(
     74                 com.android.internal.R.integer.config_lowBatteryWarningLevel);
     75         mLowBatteryReminderLevels[1] = mContext.getResources().getInteger(
     76                 com.android.internal.R.integer.config_criticalBatteryWarningLevel);
     77 
     78         final PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
     79         mScreenOffTime = pm.isScreenOn() ? -1 : SystemClock.elapsedRealtime();
     80 
     81         // Register for Intent broadcasts for...
     82         IntentFilter filter = new IntentFilter();
     83         filter.addAction(Intent.ACTION_BATTERY_CHANGED);
     84         filter.addAction(Intent.ACTION_SCREEN_OFF);
     85         filter.addAction(Intent.ACTION_SCREEN_ON);
     86         mContext.registerReceiver(mIntentReceiver, filter, null, mHandler);
     87     }
     88 
     89     /**
     90      * Buckets the battery level.
     91      *
     92      * The code in this function is a little weird because I couldn't comprehend
     93      * the bucket going up when the battery level was going down. --joeo
     94      *
     95      * 1 means that the battery is "ok"
     96      * 0 means that the battery is between "ok" and what we should warn about.
     97      * less than 0 means that the battery is low
     98      */
     99     private int findBatteryLevelBucket(int level) {
    100         if (level >= mLowBatteryAlertCloseLevel) {
    101             return 1;
    102         }
    103         if (level >= mLowBatteryReminderLevels[0]) {
    104             return 0;
    105         }
    106         final int N = mLowBatteryReminderLevels.length;
    107         for (int i=N-1; i>=0; i--) {
    108             if (level <= mLowBatteryReminderLevels[i]) {
    109                 return -1-i;
    110             }
    111         }
    112         throw new RuntimeException("not possible!");
    113     }
    114 
    115     private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
    116         @Override
    117         public void onReceive(Context context, Intent intent) {
    118             String action = intent.getAction();
    119             if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
    120                 final int oldBatteryLevel = mBatteryLevel;
    121                 mBatteryLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 100);
    122                 final int oldBatteryStatus = mBatteryStatus;
    123                 mBatteryStatus = intent.getIntExtra(BatteryManager.EXTRA_STATUS,
    124                         BatteryManager.BATTERY_STATUS_UNKNOWN);
    125                 final int oldPlugType = mPlugType;
    126                 mPlugType = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 1);
    127                 final int oldInvalidCharger = mInvalidCharger;
    128                 mInvalidCharger = intent.getIntExtra(BatteryManager.EXTRA_INVALID_CHARGER, 0);
    129 
    130                 final boolean plugged = mPlugType != 0;
    131                 final boolean oldPlugged = oldPlugType != 0;
    132 
    133                 int oldBucket = findBatteryLevelBucket(oldBatteryLevel);
    134                 int bucket = findBatteryLevelBucket(mBatteryLevel);
    135 
    136                 if (DEBUG) {
    137                     Slog.d(TAG, "buckets   ....." + mLowBatteryAlertCloseLevel
    138                             + " .. " + mLowBatteryReminderLevels[0]
    139                             + " .. " + mLowBatteryReminderLevels[1]);
    140                     Slog.d(TAG, "level          " + oldBatteryLevel + " --> " + mBatteryLevel);
    141                     Slog.d(TAG, "status         " + oldBatteryStatus + " --> " + mBatteryStatus);
    142                     Slog.d(TAG, "plugType       " + oldPlugType + " --> " + mPlugType);
    143                     Slog.d(TAG, "invalidCharger " + oldInvalidCharger + " --> " + mInvalidCharger);
    144                     Slog.d(TAG, "bucket         " + oldBucket + " --> " + bucket);
    145                     Slog.d(TAG, "plugged        " + oldPlugged + " --> " + plugged);
    146                 }
    147 
    148                 if (oldInvalidCharger == 0 && mInvalidCharger != 0) {
    149                     Slog.d(TAG, "showing invalid charger warning");
    150                     showInvalidChargerDialog();
    151                     return;
    152                 } else if (oldInvalidCharger != 0 && mInvalidCharger == 0) {
    153                     dismissInvalidChargerDialog();
    154                 } else if (mInvalidChargerDialog != null) {
    155                     // if invalid charger is showing, don't show low battery
    156                     return;
    157                 }
    158 
    159                 if (!plugged
    160                         && (bucket < oldBucket || oldPlugged)
    161                         && mBatteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
    162                         && bucket < 0) {
    163                     showLowBatteryWarning();
    164 
    165                     // only play SFX when the dialog comes up or the bucket changes
    166                     if (bucket != oldBucket || oldPlugged) {
    167                         playLowBatterySound();
    168                     }
    169                 } else if (plugged || (bucket > oldBucket && bucket > 0)) {
    170                     dismissLowBatteryWarning();
    171                 } else if (mBatteryLevelTextView != null) {
    172                     showLowBatteryWarning();
    173                 }
    174             } else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
    175                 mScreenOffTime = SystemClock.elapsedRealtime();
    176             } else if (Intent.ACTION_SCREEN_ON.equals(action)) {
    177                 mScreenOffTime = -1;
    178             } else {
    179                 Slog.w(TAG, "unknown intent: " + intent);
    180             }
    181         }
    182     };
    183 
    184     void dismissLowBatteryWarning() {
    185         if (mLowBatteryDialog != null) {
    186             Slog.i(TAG, "closing low battery warning: level=" + mBatteryLevel);
    187             mLowBatteryDialog.dismiss();
    188         }
    189     }
    190 
    191     void showLowBatteryWarning() {
    192         Slog.i(TAG,
    193                 ((mBatteryLevelTextView == null) ? "showing" : "updating")
    194                 + " low battery warning: level=" + mBatteryLevel
    195                 + " [" + findBatteryLevelBucket(mBatteryLevel) + "]");
    196 
    197         CharSequence levelText = mContext.getString(
    198                 R.string.battery_low_percent_format, mBatteryLevel);
    199 
    200         if (mBatteryLevelTextView != null) {
    201             mBatteryLevelTextView.setText(levelText);
    202         } else {
    203             View v = View.inflate(mContext, R.layout.battery_low, null);
    204             mBatteryLevelTextView = (TextView)v.findViewById(R.id.level_percent);
    205 
    206             mBatteryLevelTextView.setText(levelText);
    207 
    208             AlertDialog.Builder b = new AlertDialog.Builder(mContext);
    209                 b.setCancelable(true);
    210                 b.setTitle(R.string.battery_low_title);
    211                 b.setView(v);
    212                 b.setIconAttribute(android.R.attr.alertDialogIcon);
    213                 b.setPositiveButton(android.R.string.ok, null);
    214 
    215             final Intent intent = new Intent(Intent.ACTION_POWER_USAGE_SUMMARY);
    216             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
    217                     | Intent.FLAG_ACTIVITY_MULTIPLE_TASK
    218                     | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
    219                     | Intent.FLAG_ACTIVITY_NO_HISTORY);
    220             if (intent.resolveActivity(mContext.getPackageManager()) != null) {
    221                 b.setNegativeButton(R.string.battery_low_why,
    222                         new DialogInterface.OnClickListener() {
    223                     @Override
    224                     public void onClick(DialogInterface dialog, int which) {
    225                         mContext.startActivityAsUser(intent, UserHandle.CURRENT);
    226                         dismissLowBatteryWarning();
    227                     }
    228                 });
    229             }
    230 
    231             AlertDialog d = b.create();
    232             d.setOnDismissListener(new DialogInterface.OnDismissListener() {
    233                     @Override
    234                     public void onDismiss(DialogInterface dialog) {
    235                         mLowBatteryDialog = null;
    236                         mBatteryLevelTextView = null;
    237                     }
    238                 });
    239             d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
    240             d.getWindow().getAttributes().privateFlags |=
    241                     WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
    242             d.show();
    243             mLowBatteryDialog = d;
    244         }
    245     }
    246 
    247     void playLowBatterySound() {
    248         final ContentResolver cr = mContext.getContentResolver();
    249 
    250         final int silenceAfter = Settings.Global.getInt(cr,
    251                 Settings.Global.LOW_BATTERY_SOUND_TIMEOUT, 0);
    252         final long offTime = SystemClock.elapsedRealtime() - mScreenOffTime;
    253         if (silenceAfter > 0
    254                 && mScreenOffTime > 0
    255                 && offTime > silenceAfter) {
    256             Slog.i(TAG, "screen off too long (" + offTime + "ms, limit " + silenceAfter
    257                     + "ms): not waking up the user with low battery sound");
    258             return;
    259         }
    260 
    261         if (DEBUG) {
    262             Slog.d(TAG, "playing low battery sound. pick-a-doop!"); // WOMP-WOMP is deprecated
    263         }
    264 
    265         if (Settings.Global.getInt(cr, Settings.Global.POWER_SOUNDS_ENABLED, 1) == 1) {
    266             final String soundPath = Settings.Global.getString(cr,
    267                     Settings.Global.LOW_BATTERY_SOUND);
    268             if (soundPath != null) {
    269                 final Uri soundUri = Uri.parse("file://" + soundPath);
    270                 if (soundUri != null) {
    271                     final Ringtone sfx = RingtoneManager.getRingtone(mContext, soundUri);
    272                     if (sfx != null) {
    273                         sfx.setStreamType(AudioManager.STREAM_SYSTEM);
    274                         sfx.play();
    275                     }
    276                 }
    277             }
    278         }
    279     }
    280 
    281     void dismissInvalidChargerDialog() {
    282         if (mInvalidChargerDialog != null) {
    283             mInvalidChargerDialog.dismiss();
    284         }
    285     }
    286 
    287     void showInvalidChargerDialog() {
    288         Slog.d(TAG, "showing invalid charger dialog");
    289 
    290         dismissLowBatteryWarning();
    291 
    292         AlertDialog.Builder b = new AlertDialog.Builder(mContext);
    293             b.setCancelable(true);
    294             b.setMessage(R.string.invalid_charger);
    295             b.setIconAttribute(android.R.attr.alertDialogIcon);
    296             b.setPositiveButton(android.R.string.ok, null);
    297 
    298         AlertDialog d = b.create();
    299             d.setOnDismissListener(new DialogInterface.OnDismissListener() {
    300                     public void onDismiss(DialogInterface dialog) {
    301                         mInvalidChargerDialog = null;
    302                         mBatteryLevelTextView = null;
    303                     }
    304                 });
    305 
    306         d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
    307         d.show();
    308         mInvalidChargerDialog = d;
    309     }
    310 
    311     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    312         pw.print("mLowBatteryAlertCloseLevel=");
    313         pw.println(mLowBatteryAlertCloseLevel);
    314         pw.print("mLowBatteryReminderLevels=");
    315         pw.println(Arrays.toString(mLowBatteryReminderLevels));
    316         pw.print("mInvalidChargerDialog=");
    317         pw.println(mInvalidChargerDialog == null ? "null" : mInvalidChargerDialog.toString());
    318         pw.print("mLowBatteryDialog=");
    319         pw.println(mLowBatteryDialog == null ? "null" : mLowBatteryDialog.toString());
    320         pw.print("mBatteryLevel=");
    321         pw.println(Integer.toString(mBatteryLevel));
    322         pw.print("mBatteryStatus=");
    323         pw.println(Integer.toString(mBatteryStatus));
    324         pw.print("mPlugType=");
    325         pw.println(Integer.toString(mPlugType));
    326         pw.print("mInvalidCharger=");
    327         pw.println(Integer.toString(mInvalidCharger));
    328         pw.print("mScreenOffTime=");
    329         pw.print(mScreenOffTime);
    330         if (mScreenOffTime >= 0) {
    331             pw.print(" (");
    332             pw.print(SystemClock.elapsedRealtime() - mScreenOffTime);
    333             pw.print(" ago)");
    334         }
    335         pw.println();
    336         pw.print("soundTimeout=");
    337         pw.println(Settings.Global.getInt(mContext.getContentResolver(),
    338                 Settings.Global.LOW_BATTERY_SOUND_TIMEOUT, 0));
    339         pw.print("bucket: ");
    340         pw.println(Integer.toString(findBatteryLevelBucket(mBatteryLevel)));
    341     }
    342 }
    343 
    344