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