summaryrefslogtreecommitdiffstats
path: root/services/core/java/com/android/server/BatteryService.java
diff options
context:
space:
mode:
Diffstat (limited to 'services/core/java/com/android/server/BatteryService.java')
-rw-r--r--services/core/java/com/android/server/BatteryService.java753
1 files changed, 753 insertions, 0 deletions
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
new file mode 100644
index 0000000..cc9055d
--- /dev/null
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -0,0 +1,753 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.os.BatteryStats;
+import com.android.internal.app.IBatteryStats;
+import com.android.server.am.BatteryStatsService;
+import com.android.server.lights.Light;
+import com.android.server.lights.LightsManager;
+
+import android.app.ActivityManagerNative;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.BatteryManager;
+import android.os.BatteryProperties;
+import android.os.Binder;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.IBatteryPropertiesListener;
+import android.os.IBatteryPropertiesRegistrar;
+import android.os.IBinder;
+import android.os.DropBoxManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.UEventObserver;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.EventLog;
+import android.util.Slog;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+/**
+ * <p>BatteryService monitors the charging status, and charge level of the device
+ * battery. When these values change this service broadcasts the new values
+ * to all {@link android.content.BroadcastReceiver IntentReceivers} that are
+ * watching the {@link android.content.Intent#ACTION_BATTERY_CHANGED
+ * BATTERY_CHANGED} action.</p>
+ * <p>The new values are stored in the Intent data and can be retrieved by
+ * calling {@link android.content.Intent#getExtra Intent.getExtra} with the
+ * following keys:</p>
+ * <p>&quot;scale&quot; - int, the maximum value for the charge level</p>
+ * <p>&quot;level&quot; - int, charge level, from 0 through &quot;scale&quot; inclusive</p>
+ * <p>&quot;status&quot; - String, the current charging status.<br />
+ * <p>&quot;health&quot; - String, the current battery health.<br />
+ * <p>&quot;present&quot; - boolean, true if the battery is present<br />
+ * <p>&quot;icon-small&quot; - int, suggested small icon to use for this state</p>
+ * <p>&quot;plugged&quot; - int, 0 if the device is not plugged in; 1 if plugged
+ * into an AC power adapter; 2 if plugged in via USB.</p>
+ * <p>&quot;voltage&quot; - int, current battery voltage in millivolts</p>
+ * <p>&quot;temperature&quot; - int, current battery temperature in tenths of
+ * a degree Centigrade</p>
+ * <p>&quot;technology&quot; - String, the type of battery installed, e.g. "Li-ion"</p>
+ *
+ * <p>
+ * The battery service may be called by the power manager while holding its locks so
+ * we take care to post all outcalls into the activity manager to a handler.
+ *
+ * FIXME: Ideally the power manager would perform all of its calls into the battery
+ * service asynchronously itself.
+ * </p>
+ */
+public final class BatteryService extends Binder {
+ private static final String TAG = BatteryService.class.getSimpleName();
+
+ private static final boolean DEBUG = false;
+
+ private static final int BATTERY_SCALE = 100; // battery capacity is a percentage
+
+ // Used locally for determining when to make a last ditch effort to log
+ // discharge stats before the device dies.
+ private int mCriticalBatteryLevel;
+
+ private static final int DUMP_MAX_LENGTH = 24 * 1024;
+ private static final String[] DUMPSYS_ARGS = new String[] { "--checkin", "--unplugged" };
+
+ private static final String DUMPSYS_DATA_PATH = "/data/system/";
+
+ // This should probably be exposed in the API, though it's not critical
+ private static final int BATTERY_PLUGGED_NONE = 0;
+
+ private final Context mContext;
+ private final IBatteryStats mBatteryStats;
+ private final Handler mHandler;
+
+ private final Object mLock = new Object();
+
+ private BatteryProperties mBatteryProps;
+ private boolean mBatteryLevelCritical;
+ private int mLastBatteryStatus;
+ private int mLastBatteryHealth;
+ private boolean mLastBatteryPresent;
+ private int mLastBatteryLevel;
+ private int mLastBatteryVoltage;
+ private int mLastBatteryTemperature;
+ private boolean mLastBatteryLevelCritical;
+
+ private int mInvalidCharger;
+ private int mLastInvalidCharger;
+
+ private int mLowBatteryWarningLevel;
+ private int mLowBatteryCloseWarningLevel;
+ private int mShutdownBatteryTemperature;
+
+ private int mPlugType;
+ private int mLastPlugType = -1; // Extra state so we can detect first run
+
+ private long mDischargeStartTime;
+ private int mDischargeStartLevel;
+
+ private boolean mUpdatesStopped;
+
+ private Led mLed;
+
+ private boolean mSentLowBatteryBroadcast = false;
+
+ public BatteryService(Context context, LightsManager lightsManager) {
+ mContext = context;
+ mHandler = new Handler(true /*async*/);
+ mLed = new Led(context, lightsManager);
+ mBatteryStats = BatteryStatsService.getService();
+
+ mCriticalBatteryLevel = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_criticalBatteryWarningLevel);
+ mLowBatteryWarningLevel = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_lowBatteryWarningLevel);
+ mLowBatteryCloseWarningLevel = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_lowBatteryCloseWarningLevel);
+ mShutdownBatteryTemperature = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_shutdownBatteryTemperature);
+
+ // watch for invalid charger messages if the invalid_charger switch exists
+ if (new File("/sys/devices/virtual/switch/invalid_charger/state").exists()) {
+ mInvalidChargerObserver.startObserving(
+ "DEVPATH=/devices/virtual/switch/invalid_charger");
+ }
+
+ IBinder b = ServiceManager.getService("batterypropreg");
+ final IBatteryPropertiesRegistrar batteryPropertiesRegistrar =
+ IBatteryPropertiesRegistrar.Stub.asInterface(b);
+ try {
+ batteryPropertiesRegistrar.registerListener(new BatteryListener());
+ } catch (RemoteException e) {
+ // Should never happen.
+ }
+ }
+
+ void systemReady() {
+ // check our power situation now that it is safe to display the shutdown dialog.
+ synchronized (mLock) {
+ shutdownIfNoPowerLocked();
+ shutdownIfOverTempLocked();
+ }
+ }
+
+ /**
+ * Returns true if the device is plugged into any of the specified plug types.
+ */
+ public boolean isPowered(int plugTypeSet) {
+ synchronized (mLock) {
+ return isPoweredLocked(plugTypeSet);
+ }
+ }
+
+ private boolean isPoweredLocked(int plugTypeSet) {
+ // assume we are powered if battery state is unknown so
+ // the "stay on while plugged in" option will work.
+ if (mBatteryProps.batteryStatus == BatteryManager.BATTERY_STATUS_UNKNOWN) {
+ return true;
+ }
+ if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_AC) != 0 && mBatteryProps.chargerAcOnline) {
+ return true;
+ }
+ if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_USB) != 0 && mBatteryProps.chargerUsbOnline) {
+ return true;
+ }
+ if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_WIRELESS) != 0 && mBatteryProps.chargerWirelessOnline) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns the current plug type.
+ */
+ public int getPlugType() {
+ synchronized (mLock) {
+ return mPlugType;
+ }
+ }
+
+ /**
+ * Returns battery level as a percentage.
+ */
+ public int getBatteryLevel() {
+ synchronized (mLock) {
+ return mBatteryProps.batteryLevel;
+ }
+ }
+
+ /**
+ * Returns true if battery level is below the first warning threshold.
+ */
+ public boolean isBatteryLow() {
+ synchronized (mLock) {
+ return mBatteryProps.batteryPresent && mBatteryProps.batteryLevel <= mLowBatteryWarningLevel;
+ }
+ }
+
+ /**
+ * Returns a non-zero value if an unsupported charger is attached.
+ */
+ public int getInvalidCharger() {
+ synchronized (mLock) {
+ return mInvalidCharger;
+ }
+ }
+
+ private void shutdownIfNoPowerLocked() {
+ // shut down gracefully if our battery is critically low and we are not powered.
+ // wait until the system has booted before attempting to display the shutdown dialog.
+ if (mBatteryProps.batteryLevel == 0 && !isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY)) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (ActivityManagerNative.isSystemReady()) {
+ Intent intent = new Intent(Intent.ACTION_REQUEST_SHUTDOWN);
+ intent.putExtra(Intent.EXTRA_KEY_CONFIRM, false);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+ }
+ }
+ });
+ }
+ }
+
+ private void shutdownIfOverTempLocked() {
+ // shut down gracefully if temperature is too high (> 68.0C by default)
+ // wait until the system has booted before attempting to display the
+ // shutdown dialog.
+ if (mBatteryProps.batteryTemperature > mShutdownBatteryTemperature) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (ActivityManagerNative.isSystemReady()) {
+ Intent intent = new Intent(Intent.ACTION_REQUEST_SHUTDOWN);
+ intent.putExtra(Intent.EXTRA_KEY_CONFIRM, false);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+ }
+ }
+ });
+ }
+ }
+
+ private void update(BatteryProperties props) {
+ synchronized (mLock) {
+ if (!mUpdatesStopped) {
+ mBatteryProps = props;
+ // Process the new values.
+ processValuesLocked();
+ }
+ }
+ }
+
+ private void processValuesLocked() {
+ boolean logOutlier = false;
+ long dischargeDuration = 0;
+
+ mBatteryLevelCritical = (mBatteryProps.batteryLevel <= mCriticalBatteryLevel);
+ if (mBatteryProps.chargerAcOnline) {
+ mPlugType = BatteryManager.BATTERY_PLUGGED_AC;
+ } else if (mBatteryProps.chargerUsbOnline) {
+ mPlugType = BatteryManager.BATTERY_PLUGGED_USB;
+ } else if (mBatteryProps.chargerWirelessOnline) {
+ mPlugType = BatteryManager.BATTERY_PLUGGED_WIRELESS;
+ } else {
+ mPlugType = BATTERY_PLUGGED_NONE;
+ }
+
+ if (DEBUG) {
+ Slog.d(TAG, "Processing new values: "
+ + "chargerAcOnline=" + mBatteryProps.chargerAcOnline
+ + ", chargerUsbOnline=" + mBatteryProps.chargerUsbOnline
+ + ", chargerWirelessOnline=" + mBatteryProps.chargerWirelessOnline
+ + ", batteryStatus=" + mBatteryProps.batteryStatus
+ + ", batteryHealth=" + mBatteryProps.batteryHealth
+ + ", batteryPresent=" + mBatteryProps.batteryPresent
+ + ", batteryLevel=" + mBatteryProps.batteryLevel
+ + ", batteryTechnology=" + mBatteryProps.batteryTechnology
+ + ", batteryVoltage=" + mBatteryProps.batteryVoltage
+ + ", batteryCurrentNow=" + mBatteryProps.batteryCurrentNow
+ + ", batteryChargeCounter=" + mBatteryProps.batteryChargeCounter
+ + ", batteryTemperature=" + mBatteryProps.batteryTemperature
+ + ", mBatteryLevelCritical=" + mBatteryLevelCritical
+ + ", mPlugType=" + mPlugType);
+ }
+
+ // Let the battery stats keep track of the current level.
+ try {
+ mBatteryStats.setBatteryState(mBatteryProps.batteryStatus, mBatteryProps.batteryHealth,
+ mPlugType, mBatteryProps.batteryLevel, mBatteryProps.batteryTemperature,
+ mBatteryProps.batteryVoltage);
+ } catch (RemoteException e) {
+ // Should never happen.
+ }
+
+ shutdownIfNoPowerLocked();
+ shutdownIfOverTempLocked();
+
+ if (mBatteryProps.batteryStatus != mLastBatteryStatus ||
+ mBatteryProps.batteryHealth != mLastBatteryHealth ||
+ mBatteryProps.batteryPresent != mLastBatteryPresent ||
+ mBatteryProps.batteryLevel != mLastBatteryLevel ||
+ mPlugType != mLastPlugType ||
+ mBatteryProps.batteryVoltage != mLastBatteryVoltage ||
+ mBatteryProps.batteryTemperature != mLastBatteryTemperature ||
+ mInvalidCharger != mLastInvalidCharger) {
+
+ if (mPlugType != mLastPlugType) {
+ if (mLastPlugType == BATTERY_PLUGGED_NONE) {
+ // discharging -> charging
+
+ // There's no value in this data unless we've discharged at least once and the
+ // battery level has changed; so don't log until it does.
+ if (mDischargeStartTime != 0 && mDischargeStartLevel != mBatteryProps.batteryLevel) {
+ dischargeDuration = SystemClock.elapsedRealtime() - mDischargeStartTime;
+ logOutlier = true;
+ EventLog.writeEvent(EventLogTags.BATTERY_DISCHARGE, dischargeDuration,
+ mDischargeStartLevel, mBatteryProps.batteryLevel);
+ // make sure we see a discharge event before logging again
+ mDischargeStartTime = 0;
+ }
+ } else if (mPlugType == BATTERY_PLUGGED_NONE) {
+ // charging -> discharging or we just powered up
+ mDischargeStartTime = SystemClock.elapsedRealtime();
+ mDischargeStartLevel = mBatteryProps.batteryLevel;
+ }
+ }
+ if (mBatteryProps.batteryStatus != mLastBatteryStatus ||
+ mBatteryProps.batteryHealth != mLastBatteryHealth ||
+ mBatteryProps.batteryPresent != mLastBatteryPresent ||
+ mPlugType != mLastPlugType) {
+ EventLog.writeEvent(EventLogTags.BATTERY_STATUS,
+ mBatteryProps.batteryStatus, mBatteryProps.batteryHealth, mBatteryProps.batteryPresent ? 1 : 0,
+ mPlugType, mBatteryProps.batteryTechnology);
+ }
+ if (mBatteryProps.batteryLevel != mLastBatteryLevel) {
+ // Don't do this just from voltage or temperature changes, that is
+ // too noisy.
+ EventLog.writeEvent(EventLogTags.BATTERY_LEVEL,
+ mBatteryProps.batteryLevel, mBatteryProps.batteryVoltage, mBatteryProps.batteryTemperature);
+ }
+ if (mBatteryLevelCritical && !mLastBatteryLevelCritical &&
+ mPlugType == BATTERY_PLUGGED_NONE) {
+ // We want to make sure we log discharge cycle outliers
+ // if the battery is about to die.
+ dischargeDuration = SystemClock.elapsedRealtime() - mDischargeStartTime;
+ logOutlier = true;
+ }
+
+ final boolean plugged = mPlugType != BATTERY_PLUGGED_NONE;
+ final boolean oldPlugged = mLastPlugType != BATTERY_PLUGGED_NONE;
+
+ /* The ACTION_BATTERY_LOW broadcast is sent in these situations:
+ * - is just un-plugged (previously was plugged) and battery level is
+ * less than or equal to WARNING, or
+ * - is not plugged and battery level falls to WARNING boundary
+ * (becomes <= mLowBatteryWarningLevel).
+ */
+ final boolean sendBatteryLow = !plugged
+ && mBatteryProps.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
+ && mBatteryProps.batteryLevel <= mLowBatteryWarningLevel
+ && (oldPlugged || mLastBatteryLevel > mLowBatteryWarningLevel);
+
+ sendIntentLocked();
+
+ // Separate broadcast is sent for power connected / not connected
+ // since the standard intent will not wake any applications and some
+ // applications may want to have smart behavior based on this.
+ if (mPlugType != 0 && mLastPlugType == 0) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ Intent statusIntent = new Intent(Intent.ACTION_POWER_CONNECTED);
+ statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL);
+ }
+ });
+ }
+ else if (mPlugType == 0 && mLastPlugType != 0) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ Intent statusIntent = new Intent(Intent.ACTION_POWER_DISCONNECTED);
+ statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL);
+ }
+ });
+ }
+
+ if (sendBatteryLow) {
+ mSentLowBatteryBroadcast = true;
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ Intent statusIntent = new Intent(Intent.ACTION_BATTERY_LOW);
+ statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL);
+ }
+ });
+ } else if (mSentLowBatteryBroadcast && mLastBatteryLevel >= mLowBatteryCloseWarningLevel) {
+ mSentLowBatteryBroadcast = false;
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ Intent statusIntent = new Intent(Intent.ACTION_BATTERY_OKAY);
+ statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL);
+ }
+ });
+ }
+
+ // Update the battery LED
+ mLed.updateLightsLocked();
+
+ // This needs to be done after sendIntent() so that we get the lastest battery stats.
+ if (logOutlier && dischargeDuration != 0) {
+ logOutlierLocked(dischargeDuration);
+ }
+
+ mLastBatteryStatus = mBatteryProps.batteryStatus;
+ mLastBatteryHealth = mBatteryProps.batteryHealth;
+ mLastBatteryPresent = mBatteryProps.batteryPresent;
+ mLastBatteryLevel = mBatteryProps.batteryLevel;
+ mLastPlugType = mPlugType;
+ mLastBatteryVoltage = mBatteryProps.batteryVoltage;
+ mLastBatteryTemperature = mBatteryProps.batteryTemperature;
+ mLastBatteryLevelCritical = mBatteryLevelCritical;
+ mLastInvalidCharger = mInvalidCharger;
+ }
+ }
+
+ private void sendIntentLocked() {
+ // Pack up the values and broadcast them to everyone
+ final Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
+ | Intent.FLAG_RECEIVER_REPLACE_PENDING);
+
+ int icon = getIconLocked(mBatteryProps.batteryLevel);
+
+ intent.putExtra(BatteryManager.EXTRA_STATUS, mBatteryProps.batteryStatus);
+ intent.putExtra(BatteryManager.EXTRA_HEALTH, mBatteryProps.batteryHealth);
+ intent.putExtra(BatteryManager.EXTRA_PRESENT, mBatteryProps.batteryPresent);
+ intent.putExtra(BatteryManager.EXTRA_LEVEL, mBatteryProps.batteryLevel);
+ intent.putExtra(BatteryManager.EXTRA_SCALE, BATTERY_SCALE);
+ intent.putExtra(BatteryManager.EXTRA_ICON_SMALL, icon);
+ intent.putExtra(BatteryManager.EXTRA_PLUGGED, mPlugType);
+ intent.putExtra(BatteryManager.EXTRA_VOLTAGE, mBatteryProps.batteryVoltage);
+ intent.putExtra(BatteryManager.EXTRA_TEMPERATURE, mBatteryProps.batteryTemperature);
+ intent.putExtra(BatteryManager.EXTRA_TECHNOLOGY, mBatteryProps.batteryTechnology);
+ intent.putExtra(BatteryManager.EXTRA_INVALID_CHARGER, mInvalidCharger);
+
+ if (DEBUG) {
+ Slog.d(TAG, "Sending ACTION_BATTERY_CHANGED. level:" + mBatteryProps.batteryLevel +
+ ", scale:" + BATTERY_SCALE + ", status:" + mBatteryProps.batteryStatus +
+ ", health:" + mBatteryProps.batteryHealth + ", present:" + mBatteryProps.batteryPresent +
+ ", voltage: " + mBatteryProps.batteryVoltage +
+ ", temperature: " + mBatteryProps.batteryTemperature +
+ ", technology: " + mBatteryProps.batteryTechnology +
+ ", AC powered:" + mBatteryProps.chargerAcOnline + ", USB powered:" + mBatteryProps.chargerUsbOnline +
+ ", Wireless powered:" + mBatteryProps.chargerWirelessOnline +
+ ", icon:" + icon + ", invalid charger:" + mInvalidCharger);
+ }
+
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ ActivityManagerNative.broadcastStickyIntent(intent, null, UserHandle.USER_ALL);
+ }
+ });
+ }
+
+ private void logBatteryStatsLocked() {
+ IBinder batteryInfoService = ServiceManager.getService(BatteryStats.SERVICE_NAME);
+ if (batteryInfoService == null) return;
+
+ DropBoxManager db = (DropBoxManager) mContext.getSystemService(Context.DROPBOX_SERVICE);
+ if (db == null || !db.isTagEnabled("BATTERY_DISCHARGE_INFO")) return;
+
+ File dumpFile = null;
+ FileOutputStream dumpStream = null;
+ try {
+ // dump the service to a file
+ dumpFile = new File(DUMPSYS_DATA_PATH + BatteryStats.SERVICE_NAME + ".dump");
+ dumpStream = new FileOutputStream(dumpFile);
+ batteryInfoService.dump(dumpStream.getFD(), DUMPSYS_ARGS);
+ FileUtils.sync(dumpStream);
+
+ // add dump file to drop box
+ db.addFile("BATTERY_DISCHARGE_INFO", dumpFile, DropBoxManager.IS_TEXT);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "failed to dump battery service", e);
+ } catch (IOException e) {
+ Slog.e(TAG, "failed to write dumpsys file", e);
+ } finally {
+ // make sure we clean up
+ if (dumpStream != null) {
+ try {
+ dumpStream.close();
+ } catch (IOException e) {
+ Slog.e(TAG, "failed to close dumpsys output stream");
+ }
+ }
+ if (dumpFile != null && !dumpFile.delete()) {
+ Slog.e(TAG, "failed to delete temporary dumpsys file: "
+ + dumpFile.getAbsolutePath());
+ }
+ }
+ }
+
+ private void logOutlierLocked(long duration) {
+ ContentResolver cr = mContext.getContentResolver();
+ String dischargeThresholdString = Settings.Global.getString(cr,
+ Settings.Global.BATTERY_DISCHARGE_THRESHOLD);
+ String durationThresholdString = Settings.Global.getString(cr,
+ Settings.Global.BATTERY_DISCHARGE_DURATION_THRESHOLD);
+
+ if (dischargeThresholdString != null && durationThresholdString != null) {
+ try {
+ long durationThreshold = Long.parseLong(durationThresholdString);
+ int dischargeThreshold = Integer.parseInt(dischargeThresholdString);
+ if (duration <= durationThreshold &&
+ mDischargeStartLevel - mBatteryProps.batteryLevel >= dischargeThreshold) {
+ // If the discharge cycle is bad enough we want to know about it.
+ logBatteryStatsLocked();
+ }
+ if (DEBUG) Slog.v(TAG, "duration threshold: " + durationThreshold +
+ " discharge threshold: " + dischargeThreshold);
+ if (DEBUG) Slog.v(TAG, "duration: " + duration + " discharge: " +
+ (mDischargeStartLevel - mBatteryProps.batteryLevel));
+ } catch (NumberFormatException e) {
+ Slog.e(TAG, "Invalid DischargeThresholds GService string: " +
+ durationThresholdString + " or " + dischargeThresholdString);
+ return;
+ }
+ }
+ }
+
+ private int getIconLocked(int level) {
+ if (mBatteryProps.batteryStatus == BatteryManager.BATTERY_STATUS_CHARGING) {
+ return com.android.internal.R.drawable.stat_sys_battery_charge;
+ } else if (mBatteryProps.batteryStatus == BatteryManager.BATTERY_STATUS_DISCHARGING) {
+ return com.android.internal.R.drawable.stat_sys_battery;
+ } else if (mBatteryProps.batteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING
+ || mBatteryProps.batteryStatus == BatteryManager.BATTERY_STATUS_FULL) {
+ if (isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY)
+ && mBatteryProps.batteryLevel >= 100) {
+ return com.android.internal.R.drawable.stat_sys_battery_charge;
+ } else {
+ return com.android.internal.R.drawable.stat_sys_battery;
+ }
+ } else {
+ return com.android.internal.R.drawable.stat_sys_battery_unknown;
+ }
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+ != PackageManager.PERMISSION_GRANTED) {
+
+ pw.println("Permission Denial: can't dump Battery service from from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid());
+ return;
+ }
+
+ synchronized (mLock) {
+ if (args == null || args.length == 0 || "-a".equals(args[0])) {
+ pw.println("Current Battery Service state:");
+ if (mUpdatesStopped) {
+ pw.println(" (UPDATES STOPPED -- use 'reset' to restart)");
+ }
+ pw.println(" AC powered: " + mBatteryProps.chargerAcOnline);
+ pw.println(" USB powered: " + mBatteryProps.chargerUsbOnline);
+ pw.println(" Wireless powered: " + mBatteryProps.chargerWirelessOnline);
+ pw.println(" status: " + mBatteryProps.batteryStatus);
+ pw.println(" health: " + mBatteryProps.batteryHealth);
+ pw.println(" present: " + mBatteryProps.batteryPresent);
+ pw.println(" level: " + mBatteryProps.batteryLevel);
+ pw.println(" scale: " + BATTERY_SCALE);
+ pw.println(" voltage: " + mBatteryProps.batteryVoltage);
+
+ if (mBatteryProps.batteryCurrentNow != Integer.MIN_VALUE) {
+ pw.println(" current now: " + mBatteryProps.batteryCurrentNow);
+ }
+
+ if (mBatteryProps.batteryChargeCounter != Integer.MIN_VALUE) {
+ pw.println(" charge counter: " + mBatteryProps.batteryChargeCounter);
+ }
+
+ pw.println(" temperature: " + mBatteryProps.batteryTemperature);
+ pw.println(" technology: " + mBatteryProps.batteryTechnology);
+ } else if (args.length == 3 && "set".equals(args[0])) {
+ String key = args[1];
+ String value = args[2];
+ try {
+ boolean update = true;
+ if ("ac".equals(key)) {
+ mBatteryProps.chargerAcOnline = Integer.parseInt(value) != 0;
+ } else if ("usb".equals(key)) {
+ mBatteryProps.chargerUsbOnline = Integer.parseInt(value) != 0;
+ } else if ("wireless".equals(key)) {
+ mBatteryProps.chargerWirelessOnline = Integer.parseInt(value) != 0;
+ } else if ("status".equals(key)) {
+ mBatteryProps.batteryStatus = Integer.parseInt(value);
+ } else if ("level".equals(key)) {
+ mBatteryProps.batteryLevel = Integer.parseInt(value);
+ } else if ("invalid".equals(key)) {
+ mInvalidCharger = Integer.parseInt(value);
+ } else {
+ pw.println("Unknown set option: " + key);
+ update = false;
+ }
+ if (update) {
+ long ident = Binder.clearCallingIdentity();
+ try {
+ mUpdatesStopped = true;
+ processValuesLocked();
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ } catch (NumberFormatException ex) {
+ pw.println("Bad value: " + value);
+ }
+ } else if (args.length == 1 && "reset".equals(args[0])) {
+ long ident = Binder.clearCallingIdentity();
+ try {
+ mUpdatesStopped = false;
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ } else {
+ pw.println("Dump current battery state, or:");
+ pw.println(" set ac|usb|wireless|status|level|invalid <value>");
+ pw.println(" reset");
+ }
+ }
+ }
+
+ private final UEventObserver mInvalidChargerObserver = new UEventObserver() {
+ @Override
+ public void onUEvent(UEventObserver.UEvent event) {
+ final int invalidCharger = "1".equals(event.get("SWITCH_STATE")) ? 1 : 0;
+ synchronized (mLock) {
+ if (mInvalidCharger != invalidCharger) {
+ mInvalidCharger = invalidCharger;
+ }
+ }
+ }
+ };
+
+ private final class Led {
+ private final Light mBatteryLight;
+
+ private final int mBatteryLowARGB;
+ private final int mBatteryMediumARGB;
+ private final int mBatteryFullARGB;
+ private final int mBatteryLedOn;
+ private final int mBatteryLedOff;
+
+ public Led(Context context, LightsManager lights) {
+ mBatteryLight = lights.getLight(LightsManager.LIGHT_ID_BATTERY);
+
+ mBatteryLowARGB = context.getResources().getInteger(
+ com.android.internal.R.integer.config_notificationsBatteryLowARGB);
+ mBatteryMediumARGB = context.getResources().getInteger(
+ com.android.internal.R.integer.config_notificationsBatteryMediumARGB);
+ mBatteryFullARGB = context.getResources().getInteger(
+ com.android.internal.R.integer.config_notificationsBatteryFullARGB);
+ mBatteryLedOn = context.getResources().getInteger(
+ com.android.internal.R.integer.config_notificationsBatteryLedOn);
+ mBatteryLedOff = context.getResources().getInteger(
+ com.android.internal.R.integer.config_notificationsBatteryLedOff);
+ }
+
+ /**
+ * Synchronize on BatteryService.
+ */
+ public void updateLightsLocked() {
+ final int level = mBatteryProps.batteryLevel;
+ final int status = mBatteryProps.batteryStatus;
+ if (level < mLowBatteryWarningLevel) {
+ if (status == BatteryManager.BATTERY_STATUS_CHARGING) {
+ // Solid red when battery is charging
+ mBatteryLight.setColor(mBatteryLowARGB);
+ } else {
+ // Flash red when battery is low and not charging
+ mBatteryLight.setFlashing(mBatteryLowARGB, Light.LIGHT_FLASH_TIMED,
+ mBatteryLedOn, mBatteryLedOff);
+ }
+ } else if (status == BatteryManager.BATTERY_STATUS_CHARGING
+ || status == BatteryManager.BATTERY_STATUS_FULL) {
+ if (status == BatteryManager.BATTERY_STATUS_FULL || level >= 90) {
+ // Solid green when full or charging and nearly full
+ mBatteryLight.setColor(mBatteryFullARGB);
+ } else {
+ // Solid orange when charging and halfway full
+ mBatteryLight.setColor(mBatteryMediumARGB);
+ }
+ } else {
+ // No lights if not charging and not low
+ mBatteryLight.turnOff();
+ }
+ }
+ }
+
+ private final class BatteryListener extends IBatteryPropertiesListener.Stub {
+ @Override
+ public void batteryPropertiesChanged(BatteryProperties props) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ BatteryService.this.update(props);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+}