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