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