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