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