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