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