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.Handler;
31import android.os.IBinder;
32import android.os.DropBoxManager;
33import android.os.RemoteException;
34import android.os.ServiceManager;
35import android.os.SystemClock;
36import android.os.UEventObserver;
37import android.os.UserHandle;
38import android.provider.Settings;
39import android.util.EventLog;
40import android.util.Slog;
41
42import java.io.File;
43import java.io.FileDescriptor;
44import java.io.FileOutputStream;
45import java.io.IOException;
46import java.io.PrintWriter;
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 *
71 * <p>
72 * The battery service may be called by the power manager while holding its locks so
73 * we take care to post all outcalls into the activity manager to a handler.
74 *
75 * FIXME: Ideally the power manager would perform all of its calls into the battery
76 * service asynchronously itself.
77 * </p>
78 */
79public final class BatteryService extends Binder {
80    private static final String TAG = BatteryService.class.getSimpleName();
81
82    private static final boolean DEBUG = false;
83
84    private static final int BATTERY_SCALE = 100;    // battery capacity is a percentage
85
86    // Used locally for determining when to make a last ditch effort to log
87    // discharge stats before the device dies.
88    private int mCriticalBatteryLevel;
89
90    private static final int DUMP_MAX_LENGTH = 24 * 1024;
91    private static final String[] DUMPSYS_ARGS = new String[] { "--checkin", "-u" };
92    private static final String BATTERY_STATS_SERVICE_NAME = "batteryinfo";
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    private void shutdownIfNoPowerLocked() {
240        // shut down gracefully if our battery is critically low and we are not powered.
241        // wait until the system has booted before attempting to display the shutdown dialog.
242        if (mBatteryLevel == 0 && !isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY)) {
243            mHandler.post(new Runnable() {
244                @Override
245                public void run() {
246                    if (ActivityManagerNative.isSystemReady()) {
247                        Intent intent = new Intent(Intent.ACTION_REQUEST_SHUTDOWN);
248                        intent.putExtra(Intent.EXTRA_KEY_CONFIRM, false);
249                        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
250                        mContext.startActivityAsUser(intent, UserHandle.CURRENT);
251                    }
252                }
253            });
254        }
255    }
256
257    private void shutdownIfOverTempLocked() {
258        // shut down gracefully if temperature is too high (> 68.0C by default)
259        // wait until the system has booted before attempting to display the
260        // shutdown dialog.
261        if (mBatteryTemperature > mShutdownBatteryTemperature) {
262            mHandler.post(new Runnable() {
263                @Override
264                public void run() {
265                    if (ActivityManagerNative.isSystemReady()) {
266                        Intent intent = new Intent(Intent.ACTION_REQUEST_SHUTDOWN);
267                        intent.putExtra(Intent.EXTRA_KEY_CONFIRM, false);
268                        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
269                        mContext.startActivityAsUser(intent, UserHandle.CURRENT);
270                    }
271                }
272            });
273        }
274    }
275
276    private void updateLocked() {
277        if (!mUpdatesStopped) {
278            // Update the values of mAcOnline, et. all.
279            native_update();
280
281            // Process the new values.
282            processValuesLocked();
283        }
284    }
285
286    private void processValuesLocked() {
287        boolean logOutlier = false;
288        long dischargeDuration = 0;
289
290        mBatteryLevelCritical = (mBatteryLevel <= mCriticalBatteryLevel);
291        if (mAcOnline) {
292            mPlugType = BatteryManager.BATTERY_PLUGGED_AC;
293        } else if (mUsbOnline) {
294            mPlugType = BatteryManager.BATTERY_PLUGGED_USB;
295        } else if (mWirelessOnline) {
296            mPlugType = BatteryManager.BATTERY_PLUGGED_WIRELESS;
297        } else {
298            mPlugType = BATTERY_PLUGGED_NONE;
299        }
300
301        if (DEBUG) {
302            Slog.d(TAG, "Processing new values: "
303                    + "mAcOnline=" + mAcOnline
304                    + ", mUsbOnline=" + mUsbOnline
305                    + ", mWirelessOnline=" + mWirelessOnline
306                    + ", mBatteryStatus=" + mBatteryStatus
307                    + ", mBatteryHealth=" + mBatteryHealth
308                    + ", mBatteryPresent=" + mBatteryPresent
309                    + ", mBatteryLevel=" + mBatteryLevel
310                    + ", mBatteryTechnology=" + mBatteryTechnology
311                    + ", mBatteryVoltage=" + mBatteryVoltage
312                    + ", mBatteryTemperature=" + mBatteryTemperature
313                    + ", mBatteryLevelCritical=" + mBatteryLevelCritical
314                    + ", mPlugType=" + mPlugType);
315        }
316
317        // Let the battery stats keep track of the current level.
318        try {
319            mBatteryStats.setBatteryState(mBatteryStatus, mBatteryHealth,
320                    mPlugType, mBatteryLevel, mBatteryTemperature,
321                    mBatteryVoltage);
322        } catch (RemoteException e) {
323            // Should never happen.
324        }
325
326        shutdownIfNoPowerLocked();
327        shutdownIfOverTempLocked();
328
329        if (mBatteryStatus != mLastBatteryStatus ||
330                mBatteryHealth != mLastBatteryHealth ||
331                mBatteryPresent != mLastBatteryPresent ||
332                mBatteryLevel != mLastBatteryLevel ||
333                mPlugType != mLastPlugType ||
334                mBatteryVoltage != mLastBatteryVoltage ||
335                mBatteryTemperature != mLastBatteryTemperature ||
336                mInvalidCharger != mLastInvalidCharger) {
337
338            if (mPlugType != mLastPlugType) {
339                if (mLastPlugType == BATTERY_PLUGGED_NONE) {
340                    // discharging -> charging
341
342                    // There's no value in this data unless we've discharged at least once and the
343                    // battery level has changed; so don't log until it does.
344                    if (mDischargeStartTime != 0 && mDischargeStartLevel != mBatteryLevel) {
345                        dischargeDuration = SystemClock.elapsedRealtime() - mDischargeStartTime;
346                        logOutlier = true;
347                        EventLog.writeEvent(EventLogTags.BATTERY_DISCHARGE, dischargeDuration,
348                                mDischargeStartLevel, mBatteryLevel);
349                        // make sure we see a discharge event before logging again
350                        mDischargeStartTime = 0;
351                    }
352                } else if (mPlugType == BATTERY_PLUGGED_NONE) {
353                    // charging -> discharging or we just powered up
354                    mDischargeStartTime = SystemClock.elapsedRealtime();
355                    mDischargeStartLevel = mBatteryLevel;
356                }
357            }
358            if (mBatteryStatus != mLastBatteryStatus ||
359                    mBatteryHealth != mLastBatteryHealth ||
360                    mBatteryPresent != mLastBatteryPresent ||
361                    mPlugType != mLastPlugType) {
362                EventLog.writeEvent(EventLogTags.BATTERY_STATUS,
363                        mBatteryStatus, mBatteryHealth, mBatteryPresent ? 1 : 0,
364                        mPlugType, mBatteryTechnology);
365            }
366            if (mBatteryLevel != mLastBatteryLevel ||
367                    mBatteryVoltage != mLastBatteryVoltage ||
368                    mBatteryTemperature != mLastBatteryTemperature) {
369                EventLog.writeEvent(EventLogTags.BATTERY_LEVEL,
370                        mBatteryLevel, mBatteryVoltage, mBatteryTemperature);
371            }
372            if (mBatteryLevelCritical && !mLastBatteryLevelCritical &&
373                    mPlugType == BATTERY_PLUGGED_NONE) {
374                // We want to make sure we log discharge cycle outliers
375                // if the battery is about to die.
376                dischargeDuration = SystemClock.elapsedRealtime() - mDischargeStartTime;
377                logOutlier = true;
378            }
379
380            final boolean plugged = mPlugType != BATTERY_PLUGGED_NONE;
381            final boolean oldPlugged = mLastPlugType != BATTERY_PLUGGED_NONE;
382
383            /* The ACTION_BATTERY_LOW broadcast is sent in these situations:
384             * - is just un-plugged (previously was plugged) and battery level is
385             *   less than or equal to WARNING, or
386             * - is not plugged and battery level falls to WARNING boundary
387             *   (becomes <= mLowBatteryWarningLevel).
388             */
389            final boolean sendBatteryLow = !plugged
390                    && mBatteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
391                    && mBatteryLevel <= mLowBatteryWarningLevel
392                    && (oldPlugged || mLastBatteryLevel > mLowBatteryWarningLevel);
393
394            sendIntentLocked();
395
396            // Separate broadcast is sent for power connected / not connected
397            // since the standard intent will not wake any applications and some
398            // applications may want to have smart behavior based on this.
399            if (mPlugType != 0 && mLastPlugType == 0) {
400                mHandler.post(new Runnable() {
401                    @Override
402                    public void run() {
403                        Intent statusIntent = new Intent(Intent.ACTION_POWER_CONNECTED);
404                        statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
405                        mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL);
406                    }
407                });
408            }
409            else if (mPlugType == 0 && mLastPlugType != 0) {
410                mHandler.post(new Runnable() {
411                    @Override
412                    public void run() {
413                        Intent statusIntent = new Intent(Intent.ACTION_POWER_DISCONNECTED);
414                        statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
415                        mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL);
416                    }
417                });
418            }
419
420            if (sendBatteryLow) {
421                mSentLowBatteryBroadcast = true;
422                mHandler.post(new Runnable() {
423                    @Override
424                    public void run() {
425                        Intent statusIntent = new Intent(Intent.ACTION_BATTERY_LOW);
426                        statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
427                        mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL);
428                    }
429                });
430            } else if (mSentLowBatteryBroadcast && mLastBatteryLevel >= mLowBatteryCloseWarningLevel) {
431                mSentLowBatteryBroadcast = false;
432                mHandler.post(new Runnable() {
433                    @Override
434                    public void run() {
435                        Intent statusIntent = new Intent(Intent.ACTION_BATTERY_OKAY);
436                        statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
437                        mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL);
438                    }
439                });
440            }
441
442            // Update the battery LED
443            mLed.updateLightsLocked();
444
445            // This needs to be done after sendIntent() so that we get the lastest battery stats.
446            if (logOutlier && dischargeDuration != 0) {
447                logOutlierLocked(dischargeDuration);
448            }
449
450            mLastBatteryStatus = mBatteryStatus;
451            mLastBatteryHealth = mBatteryHealth;
452            mLastBatteryPresent = mBatteryPresent;
453            mLastBatteryLevel = mBatteryLevel;
454            mLastPlugType = mPlugType;
455            mLastBatteryVoltage = mBatteryVoltage;
456            mLastBatteryTemperature = mBatteryTemperature;
457            mLastBatteryLevelCritical = mBatteryLevelCritical;
458            mLastInvalidCharger = mInvalidCharger;
459        }
460    }
461
462    private void sendIntentLocked() {
463        //  Pack up the values and broadcast them to everyone
464        final Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);
465        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
466                | Intent.FLAG_RECEIVER_REPLACE_PENDING);
467
468        int icon = getIconLocked(mBatteryLevel);
469
470        intent.putExtra(BatteryManager.EXTRA_STATUS, mBatteryStatus);
471        intent.putExtra(BatteryManager.EXTRA_HEALTH, mBatteryHealth);
472        intent.putExtra(BatteryManager.EXTRA_PRESENT, mBatteryPresent);
473        intent.putExtra(BatteryManager.EXTRA_LEVEL, mBatteryLevel);
474        intent.putExtra(BatteryManager.EXTRA_SCALE, BATTERY_SCALE);
475        intent.putExtra(BatteryManager.EXTRA_ICON_SMALL, icon);
476        intent.putExtra(BatteryManager.EXTRA_PLUGGED, mPlugType);
477        intent.putExtra(BatteryManager.EXTRA_VOLTAGE, mBatteryVoltage);
478        intent.putExtra(BatteryManager.EXTRA_TEMPERATURE, mBatteryTemperature);
479        intent.putExtra(BatteryManager.EXTRA_TECHNOLOGY, mBatteryTechnology);
480        intent.putExtra(BatteryManager.EXTRA_INVALID_CHARGER, mInvalidCharger);
481
482        if (DEBUG) {
483            Slog.d(TAG, "Sending ACTION_BATTERY_CHANGED.  level:" + mBatteryLevel +
484                    ", scale:" + BATTERY_SCALE + ", status:" + mBatteryStatus +
485                    ", health:" + mBatteryHealth +  ", present:" + mBatteryPresent +
486                    ", voltage: " + mBatteryVoltage +
487                    ", temperature: " + mBatteryTemperature +
488                    ", technology: " + mBatteryTechnology +
489                    ", AC powered:" + mAcOnline + ", USB powered:" + mUsbOnline +
490                    ", Wireless powered:" + mWirelessOnline +
491                    ", icon:" + icon  + ", invalid charger:" + mInvalidCharger);
492        }
493
494        mHandler.post(new Runnable() {
495            @Override
496            public void run() {
497                ActivityManagerNative.broadcastStickyIntent(intent, null, UserHandle.USER_ALL);
498            }
499        });
500    }
501
502    private void logBatteryStatsLocked() {
503        IBinder batteryInfoService = ServiceManager.getService(BATTERY_STATS_SERVICE_NAME);
504        if (batteryInfoService == null) return;
505
506        DropBoxManager db = (DropBoxManager) mContext.getSystemService(Context.DROPBOX_SERVICE);
507        if (db == null || !db.isTagEnabled("BATTERY_DISCHARGE_INFO")) return;
508
509        File dumpFile = null;
510        FileOutputStream dumpStream = null;
511        try {
512            // dump the service to a file
513            dumpFile = new File(DUMPSYS_DATA_PATH + BATTERY_STATS_SERVICE_NAME + ".dump");
514            dumpStream = new FileOutputStream(dumpFile);
515            batteryInfoService.dump(dumpStream.getFD(), DUMPSYS_ARGS);
516            FileUtils.sync(dumpStream);
517
518            // add dump file to drop box
519            db.addFile("BATTERY_DISCHARGE_INFO", dumpFile, DropBoxManager.IS_TEXT);
520        } catch (RemoteException e) {
521            Slog.e(TAG, "failed to dump battery service", e);
522        } catch (IOException e) {
523            Slog.e(TAG, "failed to write dumpsys file", e);
524        } finally {
525            // make sure we clean up
526            if (dumpStream != null) {
527                try {
528                    dumpStream.close();
529                } catch (IOException e) {
530                    Slog.e(TAG, "failed to close dumpsys output stream");
531                }
532            }
533            if (dumpFile != null && !dumpFile.delete()) {
534                Slog.e(TAG, "failed to delete temporary dumpsys file: "
535                        + dumpFile.getAbsolutePath());
536            }
537        }
538    }
539
540    private void logOutlierLocked(long duration) {
541        ContentResolver cr = mContext.getContentResolver();
542        String dischargeThresholdString = Settings.Global.getString(cr,
543                Settings.Global.BATTERY_DISCHARGE_THRESHOLD);
544        String durationThresholdString = Settings.Global.getString(cr,
545                Settings.Global.BATTERY_DISCHARGE_DURATION_THRESHOLD);
546
547        if (dischargeThresholdString != null && durationThresholdString != null) {
548            try {
549                long durationThreshold = Long.parseLong(durationThresholdString);
550                int dischargeThreshold = Integer.parseInt(dischargeThresholdString);
551                if (duration <= durationThreshold &&
552                        mDischargeStartLevel - mBatteryLevel >= dischargeThreshold) {
553                    // If the discharge cycle is bad enough we want to know about it.
554                    logBatteryStatsLocked();
555                }
556                if (DEBUG) Slog.v(TAG, "duration threshold: " + durationThreshold +
557                        " discharge threshold: " + dischargeThreshold);
558                if (DEBUG) Slog.v(TAG, "duration: " + duration + " discharge: " +
559                        (mDischargeStartLevel - mBatteryLevel));
560            } catch (NumberFormatException e) {
561                Slog.e(TAG, "Invalid DischargeThresholds GService string: " +
562                        durationThresholdString + " or " + dischargeThresholdString);
563                return;
564            }
565        }
566    }
567
568    private int getIconLocked(int level) {
569        if (mBatteryStatus == BatteryManager.BATTERY_STATUS_CHARGING) {
570            return com.android.internal.R.drawable.stat_sys_battery_charge;
571        } else if (mBatteryStatus == BatteryManager.BATTERY_STATUS_DISCHARGING) {
572            return com.android.internal.R.drawable.stat_sys_battery;
573        } else if (mBatteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING
574                || mBatteryStatus == BatteryManager.BATTERY_STATUS_FULL) {
575            if (isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY)
576                    && mBatteryLevel >= 100) {
577                return com.android.internal.R.drawable.stat_sys_battery_charge;
578            } else {
579                return com.android.internal.R.drawable.stat_sys_battery;
580            }
581        } else {
582            return com.android.internal.R.drawable.stat_sys_battery_unknown;
583        }
584    }
585
586    @Override
587    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
588        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
589                != PackageManager.PERMISSION_GRANTED) {
590
591            pw.println("Permission Denial: can't dump Battery service from from pid="
592                    + Binder.getCallingPid()
593                    + ", uid=" + Binder.getCallingUid());
594            return;
595        }
596
597        synchronized (mLock) {
598            if (args == null || args.length == 0 || "-a".equals(args[0])) {
599                pw.println("Current Battery Service state:");
600                if (mUpdatesStopped) {
601                    pw.println("  (UPDATES STOPPED -- use 'reset' to restart)");
602                }
603                pw.println("  AC powered: " + mAcOnline);
604                pw.println("  USB powered: " + mUsbOnline);
605                pw.println("  Wireless powered: " + mWirelessOnline);
606                pw.println("  status: " + mBatteryStatus);
607                pw.println("  health: " + mBatteryHealth);
608                pw.println("  present: " + mBatteryPresent);
609                pw.println("  level: " + mBatteryLevel);
610                pw.println("  scale: " + BATTERY_SCALE);
611                pw.println("  voltage:" + mBatteryVoltage);
612                pw.println("  temperature: " + mBatteryTemperature);
613                pw.println("  technology: " + mBatteryTechnology);
614            } else if (args.length == 3 && "set".equals(args[0])) {
615                String key = args[1];
616                String value = args[2];
617                try {
618                    boolean update = true;
619                    if ("ac".equals(key)) {
620                        mAcOnline = Integer.parseInt(value) != 0;
621                    } else if ("usb".equals(key)) {
622                        mUsbOnline = Integer.parseInt(value) != 0;
623                    } else if ("wireless".equals(key)) {
624                        mWirelessOnline = Integer.parseInt(value) != 0;
625                    } else if ("status".equals(key)) {
626                        mBatteryStatus = Integer.parseInt(value);
627                    } else if ("level".equals(key)) {
628                        mBatteryLevel = Integer.parseInt(value);
629                    } else if ("invalid".equals(key)) {
630                        mInvalidCharger = Integer.parseInt(value);
631                    } else {
632                        pw.println("Unknown set option: " + key);
633                        update = false;
634                    }
635                    if (update) {
636                        mUpdatesStopped = true;
637                        processValuesLocked();
638                    }
639                } catch (NumberFormatException ex) {
640                    pw.println("Bad value: " + value);
641                }
642            } else if (args.length == 1 && "reset".equals(args[0])) {
643                mUpdatesStopped = false;
644                updateLocked();
645            } else {
646                pw.println("Dump current battery state, or:");
647                pw.println("  set ac|usb|wireless|status|level|invalid <value>");
648                pw.println("  reset");
649            }
650        }
651    }
652
653    private final UEventObserver mPowerSupplyObserver = new UEventObserver() {
654        @Override
655        public void onUEvent(UEventObserver.UEvent event) {
656            synchronized (mLock) {
657                updateLocked();
658            }
659        }
660    };
661
662    private final UEventObserver mInvalidChargerObserver = new UEventObserver() {
663        @Override
664        public void onUEvent(UEventObserver.UEvent event) {
665            final int invalidCharger = "1".equals(event.get("SWITCH_STATE")) ? 1 : 0;
666            synchronized (mLock) {
667                if (mInvalidCharger != invalidCharger) {
668                    mInvalidCharger = invalidCharger;
669                    updateLocked();
670                }
671            }
672        }
673    };
674
675    private final class Led {
676        private final LightsService.Light mBatteryLight;
677
678        private final int mBatteryLowARGB;
679        private final int mBatteryMediumARGB;
680        private final int mBatteryFullARGB;
681        private final int mBatteryLedOn;
682        private final int mBatteryLedOff;
683
684        public Led(Context context, LightsService lights) {
685            mBatteryLight = lights.getLight(LightsService.LIGHT_ID_BATTERY);
686
687            mBatteryLowARGB = context.getResources().getInteger(
688                    com.android.internal.R.integer.config_notificationsBatteryLowARGB);
689            mBatteryMediumARGB = context.getResources().getInteger(
690                    com.android.internal.R.integer.config_notificationsBatteryMediumARGB);
691            mBatteryFullARGB = context.getResources().getInteger(
692                    com.android.internal.R.integer.config_notificationsBatteryFullARGB);
693            mBatteryLedOn = context.getResources().getInteger(
694                    com.android.internal.R.integer.config_notificationsBatteryLedOn);
695            mBatteryLedOff = context.getResources().getInteger(
696                    com.android.internal.R.integer.config_notificationsBatteryLedOff);
697        }
698
699        /**
700         * Synchronize on BatteryService.
701         */
702        public void updateLightsLocked() {
703            final int level = mBatteryLevel;
704            final int status = mBatteryStatus;
705            if (level < mLowBatteryWarningLevel) {
706                if (status == BatteryManager.BATTERY_STATUS_CHARGING) {
707                    // Solid red when battery is charging
708                    mBatteryLight.setColor(mBatteryLowARGB);
709                } else {
710                    // Flash red when battery is low and not charging
711                    mBatteryLight.setFlashing(mBatteryLowARGB, LightsService.LIGHT_FLASH_TIMED,
712                            mBatteryLedOn, mBatteryLedOff);
713                }
714            } else if (status == BatteryManager.BATTERY_STATUS_CHARGING
715                    || status == BatteryManager.BATTERY_STATUS_FULL) {
716                if (status == BatteryManager.BATTERY_STATUS_FULL || level >= 90) {
717                    // Solid green when full or charging and nearly full
718                    mBatteryLight.setColor(mBatteryFullARGB);
719                } else {
720                    // Solid orange when charging and halfway full
721                    mBatteryLight.setColor(mBatteryMediumARGB);
722                }
723            } else {
724                // No lights if not charging and not low
725                mBatteryLight.turnOff();
726            }
727        }
728    }
729}
730