BatteryService.java revision 4ca7f1e2811dc889e526de6c3d30bac8501c23d2
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 (true) {
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                mBatteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING ||
478                mBatteryStatus == BatteryManager.BATTERY_STATUS_FULL) {
479            return com.android.internal.R.drawable.stat_sys_battery;
480        } else {
481            return com.android.internal.R.drawable.stat_sys_battery_unknown;
482        }
483    }
484
485    @Override
486    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
487        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
488                != PackageManager.PERMISSION_GRANTED) {
489
490            pw.println("Permission Denial: can't dump Battery service from from pid="
491                    + Binder.getCallingPid()
492                    + ", uid=" + Binder.getCallingUid());
493            return;
494        }
495
496        if (args == null || args.length == 0) {
497            synchronized (this) {
498                pw.println("Current Battery Service state:");
499                pw.println("  AC powered: " + mAcOnline);
500                pw.println("  USB powered: " + mUsbOnline);
501                pw.println("  status: " + mBatteryStatus);
502                pw.println("  health: " + mBatteryHealth);
503                pw.println("  present: " + mBatteryPresent);
504                pw.println("  level: " + mBatteryLevel);
505                pw.println("  scale: " + BATTERY_SCALE);
506                pw.println("  voltage:" + mBatteryVoltage);
507                pw.println("  temperature: " + mBatteryTemperature);
508                pw.println("  technology: " + mBatteryTechnology);
509            }
510        } else if (false) {
511            // DO NOT SUBMIT WITH THIS TURNED ON
512            if (args.length == 3 && "set".equals(args[0])) {
513                String key = args[1];
514                String value = args[2];
515                try {
516                    boolean update = true;
517                    if ("ac".equals(key)) {
518                        mAcOnline = Integer.parseInt(value) != 0;
519                    } else if ("usb".equals(key)) {
520                        mUsbOnline = Integer.parseInt(value) != 0;
521                    } else if ("status".equals(key)) {
522                        mBatteryStatus = Integer.parseInt(value);
523                    } else if ("level".equals(key)) {
524                        mBatteryLevel = Integer.parseInt(value);
525                    } else if ("invalid".equals(key)) {
526                        mInvalidCharger = Integer.parseInt(value);
527                    } else {
528                        update = false;
529                    }
530                    if (update) {
531                        processValues();
532                    }
533                } catch (NumberFormatException ex) {
534                    pw.println("Bad value: " + value);
535                }
536            }
537        }
538    }
539
540    class Led {
541        private LightsService mLightsService;
542        private LightsService.Light mBatteryLight;
543
544        private int mBatteryLowARGB;
545        private int mBatteryMediumARGB;
546        private int mBatteryFullARGB;
547        private int mBatteryLedOn;
548        private int mBatteryLedOff;
549
550        private boolean mBatteryCharging;
551        private boolean mBatteryLow;
552        private boolean mBatteryFull;
553
554        Led(Context context, LightsService lights) {
555            mLightsService = lights;
556            mBatteryLight = lights.getLight(LightsService.LIGHT_ID_BATTERY);
557
558            mBatteryLowARGB = mContext.getResources().getInteger(
559                    com.android.internal.R.integer.config_notificationsBatteryLowARGB);
560            mBatteryMediumARGB = mContext.getResources().getInteger(
561                    com.android.internal.R.integer.config_notificationsBatteryMediumARGB);
562            mBatteryFullARGB = mContext.getResources().getInteger(
563                    com.android.internal.R.integer.config_notificationsBatteryFullARGB);
564            mBatteryLedOn = mContext.getResources().getInteger(
565                    com.android.internal.R.integer.config_notificationsBatteryLedOn);
566            mBatteryLedOff = mContext.getResources().getInteger(
567                    com.android.internal.R.integer.config_notificationsBatteryLedOff);
568        }
569
570        /**
571         * Synchronize on BatteryService.
572         */
573        void updateLightsLocked() {
574            final int level = mBatteryLevel;
575            final int status = mBatteryStatus;
576            if (level < mLowBatteryWarningLevel) {
577                if (status == BatteryManager.BATTERY_STATUS_CHARGING) {
578                    // Solid red when battery is charging
579                    mBatteryLight.setColor(mBatteryLowARGB);
580                } else {
581                    // Flash red when battery is low and not charging
582                    mBatteryLight.setFlashing(mBatteryLowARGB, LightsService.LIGHT_FLASH_TIMED,
583                            mBatteryLedOn, mBatteryLedOff);
584                }
585            } else if (status == BatteryManager.BATTERY_STATUS_CHARGING
586                    || status == BatteryManager.BATTERY_STATUS_FULL) {
587                if (status == BatteryManager.BATTERY_STATUS_FULL || level >= 90) {
588                    // Solid green when full or charging and nearly full
589                    mBatteryLight.setColor(mBatteryFullARGB);
590                } else {
591                    // Solid orange when charging and halfway full
592                    mBatteryLight.setColor(mBatteryMediumARGB);
593                }
594            } else {
595                // No lights if not charging and not low
596                mBatteryLight.turnOff();
597            }
598        }
599    }
600}
601
602