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