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