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