Home | History | Annotate | Download | only in server
      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>&quot;scale&quot; - int, the maximum value for the charge level</p>
     59  * <p>&quot;level&quot; - int, charge level, from 0 through &quot;scale&quot; inclusive</p>
     60  * <p>&quot;status&quot; - String, the current charging status.<br />
     61  * <p>&quot;health&quot; - String, the current battery health.<br />
     62  * <p>&quot;present&quot; - boolean, true if the battery is present<br />
     63  * <p>&quot;icon-small&quot; - int, suggested small icon to use for this state</p>
     64  * <p>&quot;plugged&quot; - 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>&quot;voltage&quot; - int, current battery voltage in millivolts</p>
     67  * <p>&quot;temperature&quot; - int, current battery temperature in tenths of
     68  * a degree Centigrade</p>
     69  * <p>&quot;technology&quot; - 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