BatteryService.java revision c428aae6429c3fd5e2037c3793af399d9f6e23bf
1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server;
18
19import com.android.internal.app.IBatteryStats;
20import com.android.server.am.BatteryStatsService;
21
22import android.app.ActivityManagerNative;
23import android.content.ContentResolver;
24import android.content.Context;
25import android.content.Intent;
26import android.content.pm.PackageManager;
27import android.os.BatteryManager;
28import android.os.Binder;
29import android.os.FileUtils;
30import android.os.IBinder;
31import android.os.DropBoxManager;
32import android.os.RemoteException;
33import android.os.ServiceManager;
34import android.os.SystemClock;
35import android.os.UEventObserver;
36import android.os.UserHandle;
37import android.provider.Settings;
38import android.util.EventLog;
39import android.util.Slog;
40
41import java.io.File;
42import java.io.FileDescriptor;
43import java.io.FileOutputStream;
44import java.io.IOException;
45import java.io.PrintWriter;
46
47
48/**
49 * <p>BatteryService monitors the charging status, and charge level of the device
50 * battery.  When these values change this service broadcasts the new values
51 * to all {@link android.content.BroadcastReceiver IntentReceivers} that are
52 * watching the {@link android.content.Intent#ACTION_BATTERY_CHANGED
53 * BATTERY_CHANGED} action.</p>
54 * <p>The new values are stored in the Intent data and can be retrieved by
55 * calling {@link android.content.Intent#getExtra Intent.getExtra} with the
56 * following keys:</p>
57 * <p>&quot;scale&quot; - int, the maximum value for the charge level</p>
58 * <p>&quot;level&quot; - int, charge level, from 0 through &quot;scale&quot; inclusive</p>
59 * <p>&quot;status&quot; - String, the current charging status.<br />
60 * <p>&quot;health&quot; - String, the current battery health.<br />
61 * <p>&quot;present&quot; - boolean, true if the battery is present<br />
62 * <p>&quot;icon-small&quot; - int, suggested small icon to use for this state</p>
63 * <p>&quot;plugged&quot; - int, 0 if the device is not plugged in; 1 if plugged
64 * into an AC power adapter; 2 if plugged in via USB.</p>
65 * <p>&quot;voltage&quot; - int, current battery voltage in millivolts</p>
66 * <p>&quot;temperature&quot; - int, current battery temperature in tenths of
67 * a degree Centigrade</p>
68 * <p>&quot;technology&quot; - String, the type of battery installed, e.g. "Li-ion"</p>
69 */
70public final class BatteryService extends Binder {
71    private static final String TAG = BatteryService.class.getSimpleName();
72
73    private static final boolean DEBUG = false;
74
75    private static final int BATTERY_SCALE = 100;    // battery capacity is a percentage
76
77    // Used locally for determining when to make a last ditch effort to log
78    // discharge stats before the device dies.
79    private int mCriticalBatteryLevel;
80
81    private static final int DUMP_MAX_LENGTH = 24 * 1024;
82    private static final String[] DUMPSYS_ARGS = new String[] { "--checkin", "-u" };
83    private static final String BATTERY_STATS_SERVICE_NAME = "batteryinfo";
84
85    private static final String DUMPSYS_DATA_PATH = "/data/system/";
86
87    // This should probably be exposed in the API, though it's not critical
88    private static final int BATTERY_PLUGGED_NONE = 0;
89
90    private final Context mContext;
91    private final IBatteryStats mBatteryStats;
92
93    private final Object mLock = new Object();
94
95    /* Begin native fields: All of these fields are set by native code. */
96    private boolean mAcOnline;
97    private boolean mUsbOnline;
98    private boolean mWirelessOnline;
99    private int mBatteryStatus;
100    private int mBatteryHealth;
101    private boolean mBatteryPresent;
102    private int mBatteryLevel;
103    private int mBatteryVoltage;
104    private int mBatteryTemperature;
105    private String mBatteryTechnology;
106    private boolean mBatteryLevelCritical;
107    /* End native fields. */
108
109    private int mLastBatteryStatus;
110    private int mLastBatteryHealth;
111    private boolean mLastBatteryPresent;
112    private int mLastBatteryLevel;
113    private int mLastBatteryVoltage;
114    private int mLastBatteryTemperature;
115    private boolean mLastBatteryLevelCritical;
116
117    private int mInvalidCharger;
118    private int mLastInvalidCharger;
119
120    private int mLowBatteryWarningLevel;
121    private int mLowBatteryCloseWarningLevel;
122    private int mShutdownBatteryTemperature;
123
124    private int mPlugType;
125    private int mLastPlugType = -1; // Extra state so we can detect first run
126
127    private long mDischargeStartTime;
128    private int mDischargeStartLevel;
129
130    private boolean mUpdatesStopped;
131
132    private Led mLed;
133
134    private boolean mSentLowBatteryBroadcast = false;
135
136    private native void native_update();
137
138    public BatteryService(Context context, LightsService lights) {
139        mContext = context;
140        mLed = new Led(context, lights);
141        mBatteryStats = BatteryStatsService.getService();
142
143        mCriticalBatteryLevel = mContext.getResources().getInteger(
144                com.android.internal.R.integer.config_criticalBatteryWarningLevel);
145        mLowBatteryWarningLevel = mContext.getResources().getInteger(
146                com.android.internal.R.integer.config_lowBatteryWarningLevel);
147        mLowBatteryCloseWarningLevel = mContext.getResources().getInteger(
148                com.android.internal.R.integer.config_lowBatteryCloseWarningLevel);
149        mShutdownBatteryTemperature = mContext.getResources().getInteger(
150                com.android.internal.R.integer.config_shutdownBatteryTemperature);
151
152        mPowerSupplyObserver.startObserving("SUBSYSTEM=power_supply");
153
154        // watch for invalid charger messages if the invalid_charger switch exists
155        if (new File("/sys/devices/virtual/switch/invalid_charger/state").exists()) {
156            mInvalidChargerObserver.startObserving(
157                    "DEVPATH=/devices/virtual/switch/invalid_charger");
158        }
159
160        // set initial status
161        synchronized (mLock) {
162            updateLocked();
163        }
164    }
165
166    void systemReady() {
167        // check our power situation now that it is safe to display the shutdown dialog.
168        synchronized (mLock) {
169            shutdownIfNoPowerLocked();
170            shutdownIfOverTempLocked();
171        }
172    }
173
174    /**
175     * Returns true if the device is plugged into any of the specified plug types.
176     */
177    public boolean isPowered(int plugTypeSet) {
178        synchronized (mLock) {
179            return isPoweredLocked(plugTypeSet);
180        }
181    }
182
183    private boolean isPoweredLocked(int plugTypeSet) {
184        // assume we are powered if battery state is unknown so
185        // the "stay on while plugged in" option will work.
186        if (mBatteryStatus == BatteryManager.BATTERY_STATUS_UNKNOWN) {
187            return true;
188        }
189        if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_AC) != 0 && mAcOnline) {
190            return true;
191        }
192        if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_USB) != 0 && mUsbOnline) {
193            return true;
194        }
195        if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_WIRELESS) != 0 && mWirelessOnline) {
196            return true;
197        }
198        return false;
199    }
200
201    /**
202     * Returns the current plug type.
203     */
204    public int getPlugType() {
205        synchronized (mLock) {
206            return mPlugType;
207        }
208    }
209
210    /**
211     * Returns battery level as a percentage.
212     */
213    public int getBatteryLevel() {
214        synchronized (mLock) {
215            return mBatteryLevel;
216        }
217    }
218
219    /**
220     * Returns true if battery level is below the first warning threshold.
221     */
222    public boolean isBatteryLow() {
223        synchronized (mLock) {
224            return mBatteryPresent && mBatteryLevel <= mLowBatteryWarningLevel;
225        }
226    }
227
228    private void shutdownIfNoPowerLocked() {
229        // shut down gracefully if our battery is critically low and we are not powered.
230        // wait until the system has booted before attempting to display the shutdown dialog.
231        if (mBatteryLevel == 0 && !isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY)
232                && 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.startActivityAsUser(intent, UserHandle.CURRENT);
237        }
238    }
239
240    private void shutdownIfOverTempLocked() {
241        // shut down gracefully if temperature is too high (> 68.0C by default)
242        // wait until the system has booted before attempting to display the
243        // shutdown dialog.
244        if (mBatteryTemperature > mShutdownBatteryTemperature
245                && ActivityManagerNative.isSystemReady()) {
246            Intent intent = new Intent(Intent.ACTION_REQUEST_SHUTDOWN);
247            intent.putExtra(Intent.EXTRA_KEY_CONFIRM, false);
248            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
249            mContext.startActivityAsUser(intent, UserHandle.CURRENT);
250        }
251    }
252
253    private void updateLocked() {
254        if (!mUpdatesStopped) {
255            // Update the values of mAcOnline, et. all.
256            native_update();
257
258            // Process the new values.
259            processValuesLocked();
260        }
261    }
262
263    private void processValuesLocked() {
264        boolean logOutlier = false;
265        long dischargeDuration = 0;
266
267        mBatteryLevelCritical = (mBatteryLevel <= mCriticalBatteryLevel);
268        if (mAcOnline) {
269            mPlugType = BatteryManager.BATTERY_PLUGGED_AC;
270        } else if (mUsbOnline) {
271            mPlugType = BatteryManager.BATTERY_PLUGGED_USB;
272        } else if (mWirelessOnline) {
273            mPlugType = BatteryManager.BATTERY_PLUGGED_WIRELESS;
274        } else {
275            mPlugType = BATTERY_PLUGGED_NONE;
276        }
277
278        if (DEBUG) {
279            Slog.d(TAG, "Processing new values: "
280                    + "mAcOnline=" + mAcOnline
281                    + ", mUsbOnline=" + mUsbOnline
282                    + ", mWirelessOnline=" + mWirelessOnline
283                    + ", mBatteryStatus=" + mBatteryStatus
284                    + ", mBatteryHealth=" + mBatteryHealth
285                    + ", mBatteryPresent=" + mBatteryPresent
286                    + ", mBatteryLevel=" + mBatteryLevel
287                    + ", mBatteryTechnology=" + mBatteryTechnology
288                    + ", mBatteryVoltage=" + mBatteryVoltage
289                    + ", mBatteryTemperature=" + mBatteryTemperature
290                    + ", mBatteryLevelCritical=" + mBatteryLevelCritical
291                    + ", mPlugType=" + mPlugType);
292        }
293
294        // Let the battery stats keep track of the current level.
295        try {
296            mBatteryStats.setBatteryState(mBatteryStatus, mBatteryHealth,
297                    mPlugType, mBatteryLevel, mBatteryTemperature,
298                    mBatteryVoltage);
299        } catch (RemoteException e) {
300            // Should never happen.
301        }
302
303        shutdownIfNoPowerLocked();
304        shutdownIfOverTempLocked();
305
306        if (mBatteryStatus != mLastBatteryStatus ||
307                mBatteryHealth != mLastBatteryHealth ||
308                mBatteryPresent != mLastBatteryPresent ||
309                mBatteryLevel != mLastBatteryLevel ||
310                mPlugType != mLastPlugType ||
311                mBatteryVoltage != mLastBatteryVoltage ||
312                mBatteryTemperature != mLastBatteryTemperature ||
313                mInvalidCharger != mLastInvalidCharger) {
314
315            if (mPlugType != mLastPlugType) {
316                if (mLastPlugType == BATTERY_PLUGGED_NONE) {
317                    // discharging -> charging
318
319                    // There's no value in this data unless we've discharged at least once and the
320                    // battery level has changed; so don't log until it does.
321                    if (mDischargeStartTime != 0 && mDischargeStartLevel != mBatteryLevel) {
322                        dischargeDuration = SystemClock.elapsedRealtime() - mDischargeStartTime;
323                        logOutlier = true;
324                        EventLog.writeEvent(EventLogTags.BATTERY_DISCHARGE, dischargeDuration,
325                                mDischargeStartLevel, mBatteryLevel);
326                        // make sure we see a discharge event before logging again
327                        mDischargeStartTime = 0;
328                    }
329                } else if (mPlugType == BATTERY_PLUGGED_NONE) {
330                    // charging -> discharging or we just powered up
331                    mDischargeStartTime = SystemClock.elapsedRealtime();
332                    mDischargeStartLevel = mBatteryLevel;
333                }
334            }
335            if (mBatteryStatus != mLastBatteryStatus ||
336                    mBatteryHealth != mLastBatteryHealth ||
337                    mBatteryPresent != mLastBatteryPresent ||
338                    mPlugType != mLastPlugType) {
339                EventLog.writeEvent(EventLogTags.BATTERY_STATUS,
340                        mBatteryStatus, mBatteryHealth, mBatteryPresent ? 1 : 0,
341                        mPlugType, mBatteryTechnology);
342            }
343            if (mBatteryLevel != mLastBatteryLevel ||
344                    mBatteryVoltage != mLastBatteryVoltage ||
345                    mBatteryTemperature != mLastBatteryTemperature) {
346                EventLog.writeEvent(EventLogTags.BATTERY_LEVEL,
347                        mBatteryLevel, mBatteryVoltage, mBatteryTemperature);
348            }
349            if (mBatteryLevelCritical && !mLastBatteryLevelCritical &&
350                    mPlugType == BATTERY_PLUGGED_NONE) {
351                // We want to make sure we log discharge cycle outliers
352                // if the battery is about to die.
353                dischargeDuration = SystemClock.elapsedRealtime() - mDischargeStartTime;
354                logOutlier = true;
355            }
356
357            final boolean plugged = mPlugType != BATTERY_PLUGGED_NONE;
358            final boolean oldPlugged = mLastPlugType != BATTERY_PLUGGED_NONE;
359
360            /* The ACTION_BATTERY_LOW broadcast is sent in these situations:
361             * - is just un-plugged (previously was plugged) and battery level is
362             *   less than or equal to WARNING, or
363             * - is not plugged and battery level falls to WARNING boundary
364             *   (becomes <= mLowBatteryWarningLevel).
365             */
366            final boolean sendBatteryLow = !plugged
367                    && mBatteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
368                    && mBatteryLevel <= mLowBatteryWarningLevel
369                    && (oldPlugged || mLastBatteryLevel > mLowBatteryWarningLevel);
370
371            sendIntentLocked();
372
373            // Separate broadcast is sent for power connected / not connected
374            // since the standard intent will not wake any applications and some
375            // applications may want to have smart behavior based on this.
376            Intent statusIntent = new Intent();
377            statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
378            if (mPlugType != 0 && mLastPlugType == 0) {
379                statusIntent.setAction(Intent.ACTION_POWER_CONNECTED);
380                mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL);
381            }
382            else if (mPlugType == 0 && mLastPlugType != 0) {
383                statusIntent.setAction(Intent.ACTION_POWER_DISCONNECTED);
384                mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL);
385            }
386
387            if (sendBatteryLow) {
388                mSentLowBatteryBroadcast = true;
389                statusIntent.setAction(Intent.ACTION_BATTERY_LOW);
390                mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL);
391            } else if (mSentLowBatteryBroadcast && mLastBatteryLevel >= mLowBatteryCloseWarningLevel) {
392                mSentLowBatteryBroadcast = false;
393                statusIntent.setAction(Intent.ACTION_BATTERY_OKAY);
394                mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL);
395            }
396
397            // Update the battery LED
398            mLed.updateLightsLocked();
399
400            // This needs to be done after sendIntent() so that we get the lastest battery stats.
401            if (logOutlier && dischargeDuration != 0) {
402                logOutlierLocked(dischargeDuration);
403            }
404
405            mLastBatteryStatus = mBatteryStatus;
406            mLastBatteryHealth = mBatteryHealth;
407            mLastBatteryPresent = mBatteryPresent;
408            mLastBatteryLevel = mBatteryLevel;
409            mLastPlugType = mPlugType;
410            mLastBatteryVoltage = mBatteryVoltage;
411            mLastBatteryTemperature = mBatteryTemperature;
412            mLastBatteryLevelCritical = mBatteryLevelCritical;
413            mLastInvalidCharger = mInvalidCharger;
414        }
415    }
416
417    private void sendIntentLocked() {
418        //  Pack up the values and broadcast them to everyone
419        Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);
420        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
421                | Intent.FLAG_RECEIVER_REPLACE_PENDING);
422
423        int icon = getIconLocked(mBatteryLevel);
424
425        intent.putExtra(BatteryManager.EXTRA_STATUS, mBatteryStatus);
426        intent.putExtra(BatteryManager.EXTRA_HEALTH, mBatteryHealth);
427        intent.putExtra(BatteryManager.EXTRA_PRESENT, mBatteryPresent);
428        intent.putExtra(BatteryManager.EXTRA_LEVEL, mBatteryLevel);
429        intent.putExtra(BatteryManager.EXTRA_SCALE, BATTERY_SCALE);
430        intent.putExtra(BatteryManager.EXTRA_ICON_SMALL, icon);
431        intent.putExtra(BatteryManager.EXTRA_PLUGGED, mPlugType);
432        intent.putExtra(BatteryManager.EXTRA_VOLTAGE, mBatteryVoltage);
433        intent.putExtra(BatteryManager.EXTRA_TEMPERATURE, mBatteryTemperature);
434        intent.putExtra(BatteryManager.EXTRA_TECHNOLOGY, mBatteryTechnology);
435        intent.putExtra(BatteryManager.EXTRA_INVALID_CHARGER, mInvalidCharger);
436
437        if (DEBUG) {
438            Slog.d(TAG, "Sending ACTION_BATTERY_CHANGED.  level:" + mBatteryLevel +
439                    ", scale:" + BATTERY_SCALE + ", status:" + mBatteryStatus +
440                    ", health:" + mBatteryHealth +  ", present:" + mBatteryPresent +
441                    ", voltage: " + mBatteryVoltage +
442                    ", temperature: " + mBatteryTemperature +
443                    ", technology: " + mBatteryTechnology +
444                    ", AC powered:" + mAcOnline + ", USB powered:" + mUsbOnline +
445                    ", Wireless powered:" + mWirelessOnline +
446                    ", icon:" + icon  + ", invalid charger:" + mInvalidCharger);
447        }
448
449        ActivityManagerNative.broadcastStickyIntent(intent, null, UserHandle.USER_ALL);
450    }
451
452    private void logBatteryStatsLocked() {
453        IBinder batteryInfoService = ServiceManager.getService(BATTERY_STATS_SERVICE_NAME);
454        if (batteryInfoService == null) return;
455
456        DropBoxManager db = (DropBoxManager) mContext.getSystemService(Context.DROPBOX_SERVICE);
457        if (db == null || !db.isTagEnabled("BATTERY_DISCHARGE_INFO")) return;
458
459        File dumpFile = null;
460        FileOutputStream dumpStream = null;
461        try {
462            // dump the service to a file
463            dumpFile = new File(DUMPSYS_DATA_PATH + BATTERY_STATS_SERVICE_NAME + ".dump");
464            dumpStream = new FileOutputStream(dumpFile);
465            batteryInfoService.dump(dumpStream.getFD(), DUMPSYS_ARGS);
466            FileUtils.sync(dumpStream);
467
468            // add dump file to drop box
469            db.addFile("BATTERY_DISCHARGE_INFO", dumpFile, DropBoxManager.IS_TEXT);
470        } catch (RemoteException e) {
471            Slog.e(TAG, "failed to dump battery service", e);
472        } catch (IOException e) {
473            Slog.e(TAG, "failed to write dumpsys file", e);
474        } finally {
475            // make sure we clean up
476            if (dumpStream != null) {
477                try {
478                    dumpStream.close();
479                } catch (IOException e) {
480                    Slog.e(TAG, "failed to close dumpsys output stream");
481                }
482            }
483            if (dumpFile != null && !dumpFile.delete()) {
484                Slog.e(TAG, "failed to delete temporary dumpsys file: "
485                        + dumpFile.getAbsolutePath());
486            }
487        }
488    }
489
490    private void logOutlierLocked(long duration) {
491        ContentResolver cr = mContext.getContentResolver();
492        String dischargeThresholdString = Settings.Global.getString(cr,
493                Settings.Global.BATTERY_DISCHARGE_THRESHOLD);
494        String durationThresholdString = Settings.Global.getString(cr,
495                Settings.Global.BATTERY_DISCHARGE_DURATION_THRESHOLD);
496
497        if (dischargeThresholdString != null && durationThresholdString != null) {
498            try {
499                long durationThreshold = Long.parseLong(durationThresholdString);
500                int dischargeThreshold = Integer.parseInt(dischargeThresholdString);
501                if (duration <= durationThreshold &&
502                        mDischargeStartLevel - mBatteryLevel >= dischargeThreshold) {
503                    // If the discharge cycle is bad enough we want to know about it.
504                    logBatteryStatsLocked();
505                }
506                if (DEBUG) Slog.v(TAG, "duration threshold: " + durationThreshold +
507                        " discharge threshold: " + dischargeThreshold);
508                if (DEBUG) Slog.v(TAG, "duration: " + duration + " discharge: " +
509                        (mDischargeStartLevel - mBatteryLevel));
510            } catch (NumberFormatException e) {
511                Slog.e(TAG, "Invalid DischargeThresholds GService string: " +
512                        durationThresholdString + " or " + dischargeThresholdString);
513                return;
514            }
515        }
516    }
517
518    private int getIconLocked(int level) {
519        if (mBatteryStatus == BatteryManager.BATTERY_STATUS_CHARGING) {
520            return com.android.internal.R.drawable.stat_sys_battery_charge;
521        } else if (mBatteryStatus == BatteryManager.BATTERY_STATUS_DISCHARGING) {
522            return com.android.internal.R.drawable.stat_sys_battery;
523        } else if (mBatteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING
524                || mBatteryStatus == BatteryManager.BATTERY_STATUS_FULL) {
525            if (isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY)
526                    && mBatteryLevel >= 100) {
527                return com.android.internal.R.drawable.stat_sys_battery_charge;
528            } else {
529                return com.android.internal.R.drawable.stat_sys_battery;
530            }
531        } else {
532            return com.android.internal.R.drawable.stat_sys_battery_unknown;
533        }
534    }
535
536    @Override
537    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
538        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
539                != PackageManager.PERMISSION_GRANTED) {
540
541            pw.println("Permission Denial: can't dump Battery service from from pid="
542                    + Binder.getCallingPid()
543                    + ", uid=" + Binder.getCallingUid());
544            return;
545        }
546
547        synchronized (mLock) {
548            if (args == null || args.length == 0 || "-a".equals(args[0])) {
549                pw.println("Current Battery Service state:");
550                if (mUpdatesStopped) {
551                    pw.println("  (UPDATES STOPPED -- use 'reset' to restart)");
552                }
553                pw.println("  AC powered: " + mAcOnline);
554                pw.println("  USB powered: " + mUsbOnline);
555                pw.println("  Wireless powered: " + mWirelessOnline);
556                pw.println("  status: " + mBatteryStatus);
557                pw.println("  health: " + mBatteryHealth);
558                pw.println("  present: " + mBatteryPresent);
559                pw.println("  level: " + mBatteryLevel);
560                pw.println("  scale: " + BATTERY_SCALE);
561                pw.println("  voltage:" + mBatteryVoltage);
562                pw.println("  temperature: " + mBatteryTemperature);
563                pw.println("  technology: " + mBatteryTechnology);
564            } else if (args.length == 3 && "set".equals(args[0])) {
565                String key = args[1];
566                String value = args[2];
567                try {
568                    boolean update = true;
569                    if ("ac".equals(key)) {
570                        mAcOnline = Integer.parseInt(value) != 0;
571                    } else if ("usb".equals(key)) {
572                        mUsbOnline = Integer.parseInt(value) != 0;
573                    } else if ("wireless".equals(key)) {
574                        mWirelessOnline = Integer.parseInt(value) != 0;
575                    } else if ("status".equals(key)) {
576                        mBatteryStatus = Integer.parseInt(value);
577                    } else if ("level".equals(key)) {
578                        mBatteryLevel = Integer.parseInt(value);
579                    } else if ("invalid".equals(key)) {
580                        mInvalidCharger = Integer.parseInt(value);
581                    } else {
582                        pw.println("Unknown set option: " + key);
583                        update = false;
584                    }
585                    if (update) {
586                        mUpdatesStopped = true;
587                        processValuesLocked();
588                    }
589                } catch (NumberFormatException ex) {
590                    pw.println("Bad value: " + value);
591                }
592            } else if (args.length == 1 && "reset".equals(args[0])) {
593                mUpdatesStopped = false;
594                updateLocked();
595            } else {
596                pw.println("Dump current battery state, or:");
597                pw.println("  set ac|usb|wireless|status|level|invalid <value>");
598                pw.println("  reset");
599            }
600        }
601    }
602
603    private final UEventObserver mPowerSupplyObserver = new UEventObserver() {
604        @Override
605        public void onUEvent(UEventObserver.UEvent event) {
606            synchronized (mLock) {
607                updateLocked();
608            }
609        }
610    };
611
612    private final UEventObserver mInvalidChargerObserver = new UEventObserver() {
613        @Override
614        public void onUEvent(UEventObserver.UEvent event) {
615            final int invalidCharger = "1".equals(event.get("SWITCH_STATE")) ? 1 : 0;
616            synchronized (mLock) {
617                if (mInvalidCharger != invalidCharger) {
618                    mInvalidCharger = invalidCharger;
619                    updateLocked();
620                }
621            }
622        }
623    };
624
625    private final class Led {
626        private final LightsService.Light mBatteryLight;
627
628        private final int mBatteryLowARGB;
629        private final int mBatteryMediumARGB;
630        private final int mBatteryFullARGB;
631        private final int mBatteryLedOn;
632        private final int mBatteryLedOff;
633
634        public Led(Context context, LightsService lights) {
635            mBatteryLight = lights.getLight(LightsService.LIGHT_ID_BATTERY);
636
637            mBatteryLowARGB = context.getResources().getInteger(
638                    com.android.internal.R.integer.config_notificationsBatteryLowARGB);
639            mBatteryMediumARGB = context.getResources().getInteger(
640                    com.android.internal.R.integer.config_notificationsBatteryMediumARGB);
641            mBatteryFullARGB = context.getResources().getInteger(
642                    com.android.internal.R.integer.config_notificationsBatteryFullARGB);
643            mBatteryLedOn = context.getResources().getInteger(
644                    com.android.internal.R.integer.config_notificationsBatteryLedOn);
645            mBatteryLedOff = context.getResources().getInteger(
646                    com.android.internal.R.integer.config_notificationsBatteryLedOff);
647        }
648
649        /**
650         * Synchronize on BatteryService.
651         */
652        public void updateLightsLocked() {
653            final int level = mBatteryLevel;
654            final int status = mBatteryStatus;
655            if (level < mLowBatteryWarningLevel) {
656                if (status == BatteryManager.BATTERY_STATUS_CHARGING) {
657                    // Solid red when battery is charging
658                    mBatteryLight.setColor(mBatteryLowARGB);
659                } else {
660                    // Flash red when battery is low and not charging
661                    mBatteryLight.setFlashing(mBatteryLowARGB, LightsService.LIGHT_FLASH_TIMED,
662                            mBatteryLedOn, mBatteryLedOff);
663                }
664            } else if (status == BatteryManager.BATTERY_STATUS_CHARGING
665                    || status == BatteryManager.BATTERY_STATUS_FULL) {
666                if (status == BatteryManager.BATTERY_STATUS_FULL || level >= 90) {
667                    // Solid green when full or charging and nearly full
668                    mBatteryLight.setColor(mBatteryFullARGB);
669                } else {
670                    // Solid orange when charging and halfway full
671                    mBatteryLight.setColor(mBatteryMediumARGB);
672                }
673            } else {
674                // No lights if not charging and not low
675                mBatteryLight.turnOff();
676            }
677        }
678    }
679}
680