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