1 /* 2 * Copyright (C) 2006 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.server; 18 19 import com.android.internal.app.IBatteryStats; 20 import com.android.server.am.BatteryStatsService; 21 22 import android.app.ActivityManagerNative; 23 import android.content.ContentResolver; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.pm.PackageManager; 27 import android.os.BatteryManager; 28 import android.os.Binder; 29 import android.os.FileUtils; 30 import android.os.IBinder; 31 import android.os.DropBoxManager; 32 import android.os.RemoteException; 33 import android.os.ServiceManager; 34 import android.os.SystemClock; 35 import android.os.UEventObserver; 36 import android.provider.Settings; 37 import android.util.EventLog; 38 import android.util.Slog; 39 40 import java.io.File; 41 import java.io.FileDescriptor; 42 import java.io.FileInputStream; 43 import java.io.FileOutputStream; 44 import java.io.IOException; 45 import java.io.PrintWriter; 46 import java.util.Arrays; 47 48 49 /** 50 * <p>BatteryService monitors the charging status, and charge level of the device 51 * battery. When these values change this service broadcasts the new values 52 * to all {@link android.content.BroadcastReceiver IntentReceivers} that are 53 * watching the {@link android.content.Intent#ACTION_BATTERY_CHANGED 54 * BATTERY_CHANGED} action.</p> 55 * <p>The new values are stored in the Intent data and can be retrieved by 56 * calling {@link android.content.Intent#getExtra Intent.getExtra} with the 57 * following keys:</p> 58 * <p>"scale" - int, the maximum value for the charge level</p> 59 * <p>"level" - int, charge level, from 0 through "scale" inclusive</p> 60 * <p>"status" - String, the current charging status.<br /> 61 * <p>"health" - String, the current battery health.<br /> 62 * <p>"present" - boolean, true if the battery is present<br /> 63 * <p>"icon-small" - int, suggested small icon to use for this state</p> 64 * <p>"plugged" - int, 0 if the device is not plugged in; 1 if plugged 65 * into an AC power adapter; 2 if plugged in via USB.</p> 66 * <p>"voltage" - int, current battery voltage in millivolts</p> 67 * <p>"temperature" - int, current battery temperature in tenths of 68 * a degree Centigrade</p> 69 * <p>"technology" - String, the type of battery installed, e.g. "Li-ion"</p> 70 */ 71 class BatteryService extends Binder { 72 private static final String TAG = BatteryService.class.getSimpleName(); 73 74 private static final boolean LOCAL_LOGV = false; 75 76 static final int BATTERY_SCALE = 100; // battery capacity is a percentage 77 78 // Used locally for determining when to make a last ditch effort to log 79 // discharge stats before the device dies. 80 private int mCriticalBatteryLevel; 81 82 private static final int DUMP_MAX_LENGTH = 24 * 1024; 83 private static final String[] DUMPSYS_ARGS = new String[] { "--checkin", "-u" }; 84 private static final String BATTERY_STATS_SERVICE_NAME = "batteryinfo"; 85 86 private static final String DUMPSYS_DATA_PATH = "/data/system/"; 87 88 // This should probably be exposed in the API, though it's not critical 89 private static final int BATTERY_PLUGGED_NONE = 0; 90 91 private final Context mContext; 92 private final IBatteryStats mBatteryStats; 93 94 private boolean mAcOnline; 95 private boolean mUsbOnline; 96 private int mBatteryStatus; 97 private int mBatteryHealth; 98 private boolean mBatteryPresent; 99 private int mBatteryLevel; 100 private int mBatteryVoltage; 101 private int mBatteryTemperature; 102 private String mBatteryTechnology; 103 private boolean mBatteryLevelCritical; 104 private int mInvalidCharger; 105 106 private int mLastBatteryStatus; 107 private int mLastBatteryHealth; 108 private boolean mLastBatteryPresent; 109 private int mLastBatteryLevel; 110 private int mLastBatteryVoltage; 111 private int mLastBatteryTemperature; 112 private boolean mLastBatteryLevelCritical; 113 private int mLastInvalidCharger; 114 115 private int mLowBatteryWarningLevel; 116 private int mLowBatteryCloseWarningLevel; 117 118 private int mPlugType; 119 private int mLastPlugType = -1; // Extra state so we can detect first run 120 121 private long mDischargeStartTime; 122 private int mDischargeStartLevel; 123 124 private Led mLed; 125 126 private boolean mSentLowBatteryBroadcast = false; 127 128 public BatteryService(Context context, LightsService lights) { 129 mContext = context; 130 mLed = new Led(context, lights); 131 mBatteryStats = BatteryStatsService.getService(); 132 133 mCriticalBatteryLevel = mContext.getResources().getInteger( 134 com.android.internal.R.integer.config_criticalBatteryWarningLevel); 135 mLowBatteryWarningLevel = mContext.getResources().getInteger( 136 com.android.internal.R.integer.config_lowBatteryWarningLevel); 137 mLowBatteryCloseWarningLevel = mContext.getResources().getInteger( 138 com.android.internal.R.integer.config_lowBatteryCloseWarningLevel); 139 140 mPowerSupplyObserver.startObserving("SUBSYSTEM=power_supply"); 141 142 // watch for invalid charger messages if the invalid_charger switch exists 143 if (new File("/sys/devices/virtual/switch/invalid_charger/state").exists()) { 144 mInvalidChargerObserver.startObserving("DEVPATH=/devices/virtual/switch/invalid_charger"); 145 } 146 147 // set initial status 148 update(); 149 } 150 151 final boolean isPowered() { 152 // assume we are powered if battery state is unknown so the "stay on while plugged in" option will work. 153 return (mAcOnline || mUsbOnline || mBatteryStatus == BatteryManager.BATTERY_STATUS_UNKNOWN); 154 } 155 156 final boolean isPowered(int plugTypeSet) { 157 // assume we are powered if battery state is unknown so 158 // the "stay on while plugged in" option will work. 159 if (mBatteryStatus == BatteryManager.BATTERY_STATUS_UNKNOWN) { 160 return true; 161 } 162 if (plugTypeSet == 0) { 163 return false; 164 } 165 int plugTypeBit = 0; 166 if (mAcOnline) { 167 plugTypeBit |= BatteryManager.BATTERY_PLUGGED_AC; 168 } 169 if (mUsbOnline) { 170 plugTypeBit |= BatteryManager.BATTERY_PLUGGED_USB; 171 } 172 return (plugTypeSet & plugTypeBit) != 0; 173 } 174 175 final int getPlugType() { 176 return mPlugType; 177 } 178 179 private UEventObserver mPowerSupplyObserver = new UEventObserver() { 180 @Override 181 public void onUEvent(UEventObserver.UEvent event) { 182 update(); 183 } 184 }; 185 186 private UEventObserver mInvalidChargerObserver = new UEventObserver() { 187 @Override 188 public void onUEvent(UEventObserver.UEvent event) { 189 int invalidCharger = "1".equals(event.get("SWITCH_STATE")) ? 1 : 0; 190 if (mInvalidCharger != invalidCharger) { 191 mInvalidCharger = invalidCharger; 192 update(); 193 } 194 } 195 }; 196 197 // returns battery level as a percentage 198 final int getBatteryLevel() { 199 return mBatteryLevel; 200 } 201 202 void systemReady() { 203 // check our power situation now that it is safe to display the shutdown dialog. 204 shutdownIfNoPower(); 205 shutdownIfOverTemp(); 206 } 207 208 private final void shutdownIfNoPower() { 209 // shut down gracefully if our battery is critically low and we are not powered. 210 // wait until the system has booted before attempting to display the shutdown dialog. 211 if (mBatteryLevel == 0 && !isPowered() && ActivityManagerNative.isSystemReady()) { 212 Intent intent = new Intent(Intent.ACTION_REQUEST_SHUTDOWN); 213 intent.putExtra(Intent.EXTRA_KEY_CONFIRM, false); 214 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 215 mContext.startActivity(intent); 216 } 217 } 218 219 private final void shutdownIfOverTemp() { 220 // shut down gracefully if temperature is too high (> 68.0C) 221 // wait until the system has booted before attempting to display the shutdown dialog. 222 if (mBatteryTemperature > 680 && ActivityManagerNative.isSystemReady()) { 223 Intent intent = new Intent(Intent.ACTION_REQUEST_SHUTDOWN); 224 intent.putExtra(Intent.EXTRA_KEY_CONFIRM, false); 225 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 226 mContext.startActivity(intent); 227 } 228 } 229 230 private native void native_update(); 231 232 private synchronized final void update() { 233 native_update(); 234 processValues(); 235 } 236 237 private void processValues() { 238 boolean logOutlier = false; 239 long dischargeDuration = 0; 240 241 mBatteryLevelCritical = mBatteryLevel <= mCriticalBatteryLevel; 242 if (mAcOnline) { 243 mPlugType = BatteryManager.BATTERY_PLUGGED_AC; 244 } else if (mUsbOnline) { 245 mPlugType = BatteryManager.BATTERY_PLUGGED_USB; 246 } else { 247 mPlugType = BATTERY_PLUGGED_NONE; 248 } 249 250 // Let the battery stats keep track of the current level. 251 try { 252 mBatteryStats.setBatteryState(mBatteryStatus, mBatteryHealth, 253 mPlugType, mBatteryLevel, mBatteryTemperature, 254 mBatteryVoltage); 255 } catch (RemoteException e) { 256 // Should never happen. 257 } 258 259 shutdownIfNoPower(); 260 shutdownIfOverTemp(); 261 262 if (mBatteryStatus != mLastBatteryStatus || 263 mBatteryHealth != mLastBatteryHealth || 264 mBatteryPresent != mLastBatteryPresent || 265 mBatteryLevel != mLastBatteryLevel || 266 mPlugType != mLastPlugType || 267 mBatteryVoltage != mLastBatteryVoltage || 268 mBatteryTemperature != mLastBatteryTemperature || 269 mInvalidCharger != mLastInvalidCharger) { 270 271 if (mPlugType != mLastPlugType) { 272 if (mLastPlugType == BATTERY_PLUGGED_NONE) { 273 // discharging -> charging 274 275 // There's no value in this data unless we've discharged at least once and the 276 // battery level has changed; so don't log until it does. 277 if (mDischargeStartTime != 0 && mDischargeStartLevel != mBatteryLevel) { 278 dischargeDuration = SystemClock.elapsedRealtime() - mDischargeStartTime; 279 logOutlier = true; 280 EventLog.writeEvent(EventLogTags.BATTERY_DISCHARGE, dischargeDuration, 281 mDischargeStartLevel, mBatteryLevel); 282 // make sure we see a discharge event before logging again 283 mDischargeStartTime = 0; 284 } 285 } else if (mPlugType == BATTERY_PLUGGED_NONE) { 286 // charging -> discharging or we just powered up 287 mDischargeStartTime = SystemClock.elapsedRealtime(); 288 mDischargeStartLevel = mBatteryLevel; 289 } 290 } 291 if (mBatteryStatus != mLastBatteryStatus || 292 mBatteryHealth != mLastBatteryHealth || 293 mBatteryPresent != mLastBatteryPresent || 294 mPlugType != mLastPlugType) { 295 EventLog.writeEvent(EventLogTags.BATTERY_STATUS, 296 mBatteryStatus, mBatteryHealth, mBatteryPresent ? 1 : 0, 297 mPlugType, mBatteryTechnology); 298 } 299 if (mBatteryLevel != mLastBatteryLevel || 300 mBatteryVoltage != mLastBatteryVoltage || 301 mBatteryTemperature != mLastBatteryTemperature) { 302 EventLog.writeEvent(EventLogTags.BATTERY_LEVEL, 303 mBatteryLevel, mBatteryVoltage, mBatteryTemperature); 304 } 305 if (mBatteryLevelCritical && !mLastBatteryLevelCritical && 306 mPlugType == BATTERY_PLUGGED_NONE) { 307 // We want to make sure we log discharge cycle outliers 308 // if the battery is about to die. 309 dischargeDuration = SystemClock.elapsedRealtime() - mDischargeStartTime; 310 logOutlier = true; 311 } 312 313 final boolean plugged = mPlugType != BATTERY_PLUGGED_NONE; 314 final boolean oldPlugged = mLastPlugType != BATTERY_PLUGGED_NONE; 315 316 /* The ACTION_BATTERY_LOW broadcast is sent in these situations: 317 * - is just un-plugged (previously was plugged) and battery level is 318 * less than or equal to WARNING, or 319 * - is not plugged and battery level falls to WARNING boundary 320 * (becomes <= mLowBatteryWarningLevel). 321 */ 322 final boolean sendBatteryLow = !plugged 323 && mBatteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN 324 && mBatteryLevel <= mLowBatteryWarningLevel 325 && (oldPlugged || mLastBatteryLevel > mLowBatteryWarningLevel); 326 327 sendIntent(); 328 329 // Separate broadcast is sent for power connected / not connected 330 // since the standard intent will not wake any applications and some 331 // applications may want to have smart behavior based on this. 332 Intent statusIntent = new Intent(); 333 statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 334 if (mPlugType != 0 && mLastPlugType == 0) { 335 statusIntent.setAction(Intent.ACTION_POWER_CONNECTED); 336 mContext.sendBroadcast(statusIntent); 337 } 338 else if (mPlugType == 0 && mLastPlugType != 0) { 339 statusIntent.setAction(Intent.ACTION_POWER_DISCONNECTED); 340 mContext.sendBroadcast(statusIntent); 341 } 342 343 if (sendBatteryLow) { 344 mSentLowBatteryBroadcast = true; 345 statusIntent.setAction(Intent.ACTION_BATTERY_LOW); 346 mContext.sendBroadcast(statusIntent); 347 } else if (mSentLowBatteryBroadcast && mLastBatteryLevel >= mLowBatteryCloseWarningLevel) { 348 mSentLowBatteryBroadcast = false; 349 statusIntent.setAction(Intent.ACTION_BATTERY_OKAY); 350 mContext.sendBroadcast(statusIntent); 351 } 352 353 // Update the battery LED 354 mLed.updateLightsLocked(); 355 356 // This needs to be done after sendIntent() so that we get the lastest battery stats. 357 if (logOutlier && dischargeDuration != 0) { 358 logOutlier(dischargeDuration); 359 } 360 361 mLastBatteryStatus = mBatteryStatus; 362 mLastBatteryHealth = mBatteryHealth; 363 mLastBatteryPresent = mBatteryPresent; 364 mLastBatteryLevel = mBatteryLevel; 365 mLastPlugType = mPlugType; 366 mLastBatteryVoltage = mBatteryVoltage; 367 mLastBatteryTemperature = mBatteryTemperature; 368 mLastBatteryLevelCritical = mBatteryLevelCritical; 369 mLastInvalidCharger = mInvalidCharger; 370 } 371 } 372 373 private final void sendIntent() { 374 // Pack up the values and broadcast them to everyone 375 Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED); 376 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY 377 | Intent.FLAG_RECEIVER_REPLACE_PENDING); 378 379 int icon = getIcon(mBatteryLevel); 380 381 intent.putExtra(BatteryManager.EXTRA_STATUS, mBatteryStatus); 382 intent.putExtra(BatteryManager.EXTRA_HEALTH, mBatteryHealth); 383 intent.putExtra(BatteryManager.EXTRA_PRESENT, mBatteryPresent); 384 intent.putExtra(BatteryManager.EXTRA_LEVEL, mBatteryLevel); 385 intent.putExtra(BatteryManager.EXTRA_SCALE, BATTERY_SCALE); 386 intent.putExtra(BatteryManager.EXTRA_ICON_SMALL, icon); 387 intent.putExtra(BatteryManager.EXTRA_PLUGGED, mPlugType); 388 intent.putExtra(BatteryManager.EXTRA_VOLTAGE, mBatteryVoltage); 389 intent.putExtra(BatteryManager.EXTRA_TEMPERATURE, mBatteryTemperature); 390 intent.putExtra(BatteryManager.EXTRA_TECHNOLOGY, mBatteryTechnology); 391 intent.putExtra(BatteryManager.EXTRA_INVALID_CHARGER, mInvalidCharger); 392 393 if (false) { 394 Slog.d(TAG, "level:" + mBatteryLevel + 395 " scale:" + BATTERY_SCALE + " status:" + mBatteryStatus + 396 " health:" + mBatteryHealth + " present:" + mBatteryPresent + 397 " voltage: " + mBatteryVoltage + 398 " temperature: " + mBatteryTemperature + 399 " technology: " + mBatteryTechnology + 400 " AC powered:" + mAcOnline + " USB powered:" + mUsbOnline + 401 " icon:" + icon + " invalid charger:" + mInvalidCharger); 402 } 403 404 ActivityManagerNative.broadcastStickyIntent(intent, null); 405 } 406 407 private final void logBatteryStats() { 408 IBinder batteryInfoService = ServiceManager.getService(BATTERY_STATS_SERVICE_NAME); 409 if (batteryInfoService == null) return; 410 411 DropBoxManager db = (DropBoxManager) mContext.getSystemService(Context.DROPBOX_SERVICE); 412 if (db == null || !db.isTagEnabled("BATTERY_DISCHARGE_INFO")) return; 413 414 File dumpFile = null; 415 FileOutputStream dumpStream = null; 416 try { 417 // dump the service to a file 418 dumpFile = new File(DUMPSYS_DATA_PATH + BATTERY_STATS_SERVICE_NAME + ".dump"); 419 dumpStream = new FileOutputStream(dumpFile); 420 batteryInfoService.dump(dumpStream.getFD(), DUMPSYS_ARGS); 421 FileUtils.sync(dumpStream); 422 423 // add dump file to drop box 424 db.addFile("BATTERY_DISCHARGE_INFO", dumpFile, DropBoxManager.IS_TEXT); 425 } catch (RemoteException e) { 426 Slog.e(TAG, "failed to dump battery service", e); 427 } catch (IOException e) { 428 Slog.e(TAG, "failed to write dumpsys file", e); 429 } finally { 430 // make sure we clean up 431 if (dumpStream != null) { 432 try { 433 dumpStream.close(); 434 } catch (IOException e) { 435 Slog.e(TAG, "failed to close dumpsys output stream"); 436 } 437 } 438 if (dumpFile != null && !dumpFile.delete()) { 439 Slog.e(TAG, "failed to delete temporary dumpsys file: " 440 + dumpFile.getAbsolutePath()); 441 } 442 } 443 } 444 445 private final void logOutlier(long duration) { 446 ContentResolver cr = mContext.getContentResolver(); 447 String dischargeThresholdString = Settings.Secure.getString(cr, 448 Settings.Secure.BATTERY_DISCHARGE_THRESHOLD); 449 String durationThresholdString = Settings.Secure.getString(cr, 450 Settings.Secure.BATTERY_DISCHARGE_DURATION_THRESHOLD); 451 452 if (dischargeThresholdString != null && durationThresholdString != null) { 453 try { 454 long durationThreshold = Long.parseLong(durationThresholdString); 455 int dischargeThreshold = Integer.parseInt(dischargeThresholdString); 456 if (duration <= durationThreshold && 457 mDischargeStartLevel - mBatteryLevel >= dischargeThreshold) { 458 // If the discharge cycle is bad enough we want to know about it. 459 logBatteryStats(); 460 } 461 if (LOCAL_LOGV) Slog.v(TAG, "duration threshold: " + durationThreshold + 462 " discharge threshold: " + dischargeThreshold); 463 if (LOCAL_LOGV) Slog.v(TAG, "duration: " + duration + " discharge: " + 464 (mDischargeStartLevel - mBatteryLevel)); 465 } catch (NumberFormatException e) { 466 Slog.e(TAG, "Invalid DischargeThresholds GService string: " + 467 durationThresholdString + " or " + dischargeThresholdString); 468 return; 469 } 470 } 471 } 472 473 private final int getIcon(int level) { 474 if (mBatteryStatus == BatteryManager.BATTERY_STATUS_CHARGING) { 475 return com.android.internal.R.drawable.stat_sys_battery_charge; 476 } else if (mBatteryStatus == BatteryManager.BATTERY_STATUS_DISCHARGING) { 477 return com.android.internal.R.drawable.stat_sys_battery; 478 } else if (mBatteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING 479 || mBatteryStatus == BatteryManager.BATTERY_STATUS_FULL) { 480 if (isPowered() && mBatteryLevel >= 100) { 481 return com.android.internal.R.drawable.stat_sys_battery_charge; 482 } else { 483 return com.android.internal.R.drawable.stat_sys_battery; 484 } 485 } else { 486 return com.android.internal.R.drawable.stat_sys_battery_unknown; 487 } 488 } 489 490 @Override 491 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 492 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) 493 != PackageManager.PERMISSION_GRANTED) { 494 495 pw.println("Permission Denial: can't dump Battery service from from pid=" 496 + Binder.getCallingPid() 497 + ", uid=" + Binder.getCallingUid()); 498 return; 499 } 500 501 if (args == null || args.length == 0 || "-a".equals(args[0])) { 502 synchronized (this) { 503 pw.println("Current Battery Service state:"); 504 pw.println(" AC powered: " + mAcOnline); 505 pw.println(" USB powered: " + mUsbOnline); 506 pw.println(" status: " + mBatteryStatus); 507 pw.println(" health: " + mBatteryHealth); 508 pw.println(" present: " + mBatteryPresent); 509 pw.println(" level: " + mBatteryLevel); 510 pw.println(" scale: " + BATTERY_SCALE); 511 pw.println(" voltage:" + mBatteryVoltage); 512 pw.println(" temperature: " + mBatteryTemperature); 513 pw.println(" technology: " + mBatteryTechnology); 514 } 515 } else if (false) { 516 // DO NOT SUBMIT WITH THIS TURNED ON 517 if (args.length == 3 && "set".equals(args[0])) { 518 String key = args[1]; 519 String value = args[2]; 520 try { 521 boolean update = true; 522 if ("ac".equals(key)) { 523 mAcOnline = Integer.parseInt(value) != 0; 524 } else if ("usb".equals(key)) { 525 mUsbOnline = Integer.parseInt(value) != 0; 526 } else if ("status".equals(key)) { 527 mBatteryStatus = Integer.parseInt(value); 528 } else if ("level".equals(key)) { 529 mBatteryLevel = Integer.parseInt(value); 530 } else if ("invalid".equals(key)) { 531 mInvalidCharger = Integer.parseInt(value); 532 } else { 533 update = false; 534 } 535 if (update) { 536 processValues(); 537 } 538 } catch (NumberFormatException ex) { 539 pw.println("Bad value: " + value); 540 } 541 } 542 } 543 } 544 545 class Led { 546 private LightsService mLightsService; 547 private LightsService.Light mBatteryLight; 548 549 private int mBatteryLowARGB; 550 private int mBatteryMediumARGB; 551 private int mBatteryFullARGB; 552 private int mBatteryLedOn; 553 private int mBatteryLedOff; 554 555 private boolean mBatteryCharging; 556 private boolean mBatteryLow; 557 private boolean mBatteryFull; 558 559 Led(Context context, LightsService lights) { 560 mLightsService = lights; 561 mBatteryLight = lights.getLight(LightsService.LIGHT_ID_BATTERY); 562 563 mBatteryLowARGB = mContext.getResources().getInteger( 564 com.android.internal.R.integer.config_notificationsBatteryLowARGB); 565 mBatteryMediumARGB = mContext.getResources().getInteger( 566 com.android.internal.R.integer.config_notificationsBatteryMediumARGB); 567 mBatteryFullARGB = mContext.getResources().getInteger( 568 com.android.internal.R.integer.config_notificationsBatteryFullARGB); 569 mBatteryLedOn = mContext.getResources().getInteger( 570 com.android.internal.R.integer.config_notificationsBatteryLedOn); 571 mBatteryLedOff = mContext.getResources().getInteger( 572 com.android.internal.R.integer.config_notificationsBatteryLedOff); 573 } 574 575 /** 576 * Synchronize on BatteryService. 577 */ 578 void updateLightsLocked() { 579 final int level = mBatteryLevel; 580 final int status = mBatteryStatus; 581 if (level < mLowBatteryWarningLevel) { 582 if (status == BatteryManager.BATTERY_STATUS_CHARGING) { 583 // Solid red when battery is charging 584 mBatteryLight.setColor(mBatteryLowARGB); 585 } else { 586 // Flash red when battery is low and not charging 587 mBatteryLight.setFlashing(mBatteryLowARGB, LightsService.LIGHT_FLASH_TIMED, 588 mBatteryLedOn, mBatteryLedOff); 589 } 590 } else if (status == BatteryManager.BATTERY_STATUS_CHARGING 591 || status == BatteryManager.BATTERY_STATUS_FULL) { 592 if (status == BatteryManager.BATTERY_STATUS_FULL || level >= 90) { 593 // Solid green when full or charging and nearly full 594 mBatteryLight.setColor(mBatteryFullARGB); 595 } else { 596 // Solid orange when charging and halfway full 597 mBatteryLight.setColor(mBatteryMediumARGB); 598 } 599 } else { 600 // No lights if not charging and not low 601 mBatteryLight.turnOff(); 602 } 603 } 604 } 605 } 606 607