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