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 17 package com.android.server; 18 19 import com.android.internal.app.IBatteryStats; 20 import com.android.server.am.BatteryStatsService; 21 22 import android.app.ActivityManagerNative; 23 import android.content.ContentResolver; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.pm.PackageManager; 27 import android.os.BatteryManager; 28 import android.os.Binder; 29 import android.os.IBinder; 30 import android.os.RemoteException; 31 import android.os.ServiceManager; 32 import android.os.SystemClock; 33 import android.os.UEventObserver; 34 import android.provider.Checkin; 35 import android.provider.Settings; 36 import android.util.EventLog; 37 import android.util.Log; 38 39 import java.io.File; 40 import java.io.FileDescriptor; 41 import java.io.FileInputStream; 42 import java.io.FileOutputStream; 43 import java.io.IOException; 44 import java.io.PrintWriter; 45 46 47 /** 48 * <p>BatteryService monitors the charging status, and charge level of the device 49 * battery. When these values change this service broadcasts the new values 50 * to all {@link android.content.BroadcastReceiver IntentReceivers} that are 51 * watching the {@link android.content.Intent#ACTION_BATTERY_CHANGED 52 * BATTERY_CHANGED} action.</p> 53 * <p>The new values are stored in the Intent data and can be retrieved by 54 * calling {@link android.content.Intent#getExtra Intent.getExtra} with the 55 * following keys:</p> 56 * <p>"scale" - int, the maximum value for the charge level</p> 57 * <p>"level" - int, charge level, from 0 through "scale" inclusive</p> 58 * <p>"status" - String, the current charging status.<br /> 59 * <p>"health" - String, the current battery health.<br /> 60 * <p>"present" - boolean, true if the battery is present<br /> 61 * <p>"icon-small" - int, suggested small icon to use for this state</p> 62 * <p>"plugged" - int, 0 if the device is not plugged in; 1 if plugged 63 * into an AC power adapter; 2 if plugged in via USB.</p> 64 * <p>"voltage" - int, current battery voltage in millivolts</p> 65 * <p>"temperature" - int, current battery temperature in tenths of 66 * a degree Centigrade</p> 67 * <p>"technology" - String, the type of battery installed, e.g. "Li-ion"</p> 68 */ 69 class BatteryService extends Binder { 70 private static final String TAG = BatteryService.class.getSimpleName(); 71 72 private static final boolean LOCAL_LOGV = false; 73 74 static final int LOG_BATTERY_LEVEL = 2722; 75 static final int LOG_BATTERY_STATUS = 2723; 76 static final int LOG_BATTERY_DISCHARGE_STATUS = 2730; 77 78 static final int BATTERY_SCALE = 100; // battery capacity is a percentage 79 80 // Used locally for determining when to make a last ditch effort to log 81 // discharge stats before the device dies. 82 private static final int CRITICAL_BATTERY_LEVEL = 4; 83 84 private static final int DUMP_MAX_LENGTH = 24 * 1024; 85 private static final String[] DUMPSYS_ARGS = new String[] { "--checkin", "-u" }; 86 private static final String BATTERY_STATS_SERVICE_NAME = "batteryinfo"; 87 88 private static final String DUMPSYS_DATA_PATH = "/data/system/"; 89 90 // This should probably be exposed in the API, though it's not critical 91 private static final int BATTERY_PLUGGED_NONE = 0; 92 93 private final Context mContext; 94 private final IBatteryStats mBatteryStats; 95 96 private boolean mAcOnline; 97 private boolean mUsbOnline; 98 private int mBatteryStatus; 99 private int mBatteryHealth; 100 private boolean mBatteryPresent; 101 private int mBatteryLevel; 102 private int mBatteryVoltage; 103 private int mBatteryTemperature; 104 private String mBatteryTechnology; 105 private boolean mBatteryLevelCritical; 106 107 private int mLastBatteryStatus; 108 private int mLastBatteryHealth; 109 private boolean mLastBatteryPresent; 110 private int mLastBatteryLevel; 111 private int mLastBatteryVoltage; 112 private int mLastBatteryTemperature; 113 private boolean mLastBatteryLevelCritical; 114 115 private int mLowBatteryWarningLevel; 116 private int mLowBatteryCloseWarningLevel; 117 118 private int mPlugType; 119 private int mLastPlugType = -1; // Extra state so we can detect first run 120 121 private long mDischargeStartTime; 122 private int mDischargeStartLevel; 123 124 private boolean mSentLowBatteryBroadcast = false; 125 BatteryService(Context context)126 public BatteryService(Context context) { 127 mContext = context; 128 mBatteryStats = BatteryStatsService.getService(); 129 130 mLowBatteryWarningLevel = mContext.getResources().getInteger( 131 com.android.internal.R.integer.config_lowBatteryWarningLevel); 132 mLowBatteryCloseWarningLevel = mContext.getResources().getInteger( 133 com.android.internal.R.integer.config_lowBatteryCloseWarningLevel); 134 135 mUEventObserver.startObserving("SUBSYSTEM=power_supply"); 136 137 // set initial status 138 update(); 139 } 140 isPowered()141 final boolean isPowered() { 142 // assume we are powered if battery state is unknown so the "stay on while plugged in" option will work. 143 return (mAcOnline || mUsbOnline || mBatteryStatus == BatteryManager.BATTERY_STATUS_UNKNOWN); 144 } 145 isPowered(int plugTypeSet)146 final boolean isPowered(int plugTypeSet) { 147 // assume we are powered if battery state is unknown so 148 // the "stay on while plugged in" option will work. 149 if (mBatteryStatus == BatteryManager.BATTERY_STATUS_UNKNOWN) { 150 return true; 151 } 152 if (plugTypeSet == 0) { 153 return false; 154 } 155 int plugTypeBit = 0; 156 if (mAcOnline) { 157 plugTypeBit |= BatteryManager.BATTERY_PLUGGED_AC; 158 } 159 if (mUsbOnline) { 160 plugTypeBit |= BatteryManager.BATTERY_PLUGGED_USB; 161 } 162 return (plugTypeSet & plugTypeBit) != 0; 163 } 164 getPlugType()165 final int getPlugType() { 166 return mPlugType; 167 } 168 169 private UEventObserver mUEventObserver = new UEventObserver() { 170 @Override 171 public void onUEvent(UEventObserver.UEvent event) { 172 update(); 173 } 174 }; 175 176 // returns battery level as a percentage getBatteryLevel()177 final int getBatteryLevel() { 178 return mBatteryLevel; 179 } 180 systemReady()181 void systemReady() { 182 // check our power situation now that it is safe to display the shutdown dialog. 183 shutdownIfNoPower(); 184 } 185 shutdownIfNoPower()186 private final void shutdownIfNoPower() { 187 // shut down gracefully if our battery is critically low and we are not powered. 188 // wait until the system has booted before attempting to display the shutdown dialog. 189 if (mBatteryLevel == 0 && !isPowered() && ActivityManagerNative.isSystemReady()) { 190 Intent intent = new Intent(Intent.ACTION_REQUEST_SHUTDOWN); 191 intent.putExtra(Intent.EXTRA_KEY_CONFIRM, false); 192 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 193 mContext.startActivity(intent); 194 } 195 } 196 native_update()197 private native void native_update(); 198 update()199 private synchronized final void update() { 200 native_update(); 201 202 boolean logOutlier = false; 203 long dischargeDuration = 0; 204 205 shutdownIfNoPower(); 206 207 mBatteryLevelCritical = mBatteryLevel <= CRITICAL_BATTERY_LEVEL; 208 if (mAcOnline) { 209 mPlugType = BatteryManager.BATTERY_PLUGGED_AC; 210 } else if (mUsbOnline) { 211 mPlugType = BatteryManager.BATTERY_PLUGGED_USB; 212 } else { 213 mPlugType = BATTERY_PLUGGED_NONE; 214 } 215 if (mBatteryStatus != mLastBatteryStatus || 216 mBatteryHealth != mLastBatteryHealth || 217 mBatteryPresent != mLastBatteryPresent || 218 mBatteryLevel != mLastBatteryLevel || 219 mPlugType != mLastPlugType || 220 mBatteryVoltage != mLastBatteryVoltage || 221 mBatteryTemperature != mLastBatteryTemperature) { 222 223 if (mPlugType != mLastPlugType) { 224 if (mLastPlugType == BATTERY_PLUGGED_NONE) { 225 // discharging -> charging 226 227 // There's no value in this data unless we've discharged at least once and the 228 // battery level has changed; so don't log until it does. 229 if (mDischargeStartTime != 0 && mDischargeStartLevel != mBatteryLevel) { 230 dischargeDuration = SystemClock.elapsedRealtime() - mDischargeStartTime; 231 logOutlier = true; 232 EventLog.writeEvent(LOG_BATTERY_DISCHARGE_STATUS, dischargeDuration, 233 mDischargeStartLevel, mBatteryLevel); 234 // make sure we see a discharge event before logging again 235 mDischargeStartTime = 0; 236 } 237 } else if (mPlugType == BATTERY_PLUGGED_NONE) { 238 // charging -> discharging or we just powered up 239 mDischargeStartTime = SystemClock.elapsedRealtime(); 240 mDischargeStartLevel = mBatteryLevel; 241 } 242 } 243 if (mBatteryStatus != mLastBatteryStatus || 244 mBatteryHealth != mLastBatteryHealth || 245 mBatteryPresent != mLastBatteryPresent || 246 mPlugType != mLastPlugType) { 247 EventLog.writeEvent(LOG_BATTERY_STATUS, 248 mBatteryStatus, mBatteryHealth, mBatteryPresent ? 1 : 0, 249 mPlugType, mBatteryTechnology); 250 } 251 if (mBatteryLevel != mLastBatteryLevel || 252 mBatteryVoltage != mLastBatteryVoltage || 253 mBatteryTemperature != mLastBatteryTemperature) { 254 EventLog.writeEvent(LOG_BATTERY_LEVEL, 255 mBatteryLevel, mBatteryVoltage, mBatteryTemperature); 256 } 257 if (mBatteryLevel != mLastBatteryLevel && mPlugType == BATTERY_PLUGGED_NONE) { 258 // If the battery level has changed and we are on battery, update the current level. 259 // This is used for discharge cycle tracking so this shouldn't be updated while the 260 // battery is charging. 261 try { 262 mBatteryStats.recordCurrentLevel(mBatteryLevel); 263 } catch (RemoteException e) { 264 // Should never happen. 265 } 266 } 267 if (mBatteryLevelCritical && !mLastBatteryLevelCritical && 268 mPlugType == BATTERY_PLUGGED_NONE) { 269 // We want to make sure we log discharge cycle outliers 270 // if the battery is about to die. 271 dischargeDuration = SystemClock.elapsedRealtime() - mDischargeStartTime; 272 logOutlier = true; 273 } 274 275 final boolean plugged = mPlugType != BATTERY_PLUGGED_NONE; 276 final boolean oldPlugged = mLastPlugType != BATTERY_PLUGGED_NONE; 277 278 /* The ACTION_BATTERY_LOW broadcast is sent in these situations: 279 * - is just un-plugged (previously was plugged) and battery level is 280 * less than or equal to WARNING, or 281 * - is not plugged and battery level falls to WARNING boundary 282 * (becomes <= mLowBatteryWarningLevel). 283 */ 284 final boolean sendBatteryLow = !plugged 285 && mBatteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN 286 && mBatteryLevel <= mLowBatteryWarningLevel 287 && (oldPlugged || mLastBatteryLevel > mLowBatteryWarningLevel); 288 289 sendIntent(); 290 291 // Separate broadcast is sent for power connected / not connected 292 // since the standard intent will not wake any applications and some 293 // applications may want to have smart behavior based on this. 294 Intent statusIntent = new Intent(); 295 statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 296 if (mPlugType != 0 && mLastPlugType == 0) { 297 statusIntent.setAction(Intent.ACTION_POWER_CONNECTED); 298 mContext.sendBroadcast(statusIntent); 299 } 300 else if (mPlugType == 0 && mLastPlugType != 0) { 301 statusIntent.setAction(Intent.ACTION_POWER_DISCONNECTED); 302 mContext.sendBroadcast(statusIntent); 303 } 304 305 if (sendBatteryLow) { 306 mSentLowBatteryBroadcast = true; 307 statusIntent.setAction(Intent.ACTION_BATTERY_LOW); 308 mContext.sendBroadcast(statusIntent); 309 } else if (mSentLowBatteryBroadcast && mLastBatteryLevel >= mLowBatteryCloseWarningLevel) { 310 mSentLowBatteryBroadcast = false; 311 statusIntent.setAction(Intent.ACTION_BATTERY_OKAY); 312 mContext.sendBroadcast(statusIntent); 313 } 314 315 // This needs to be done after sendIntent() so that we get the lastest battery stats. 316 if (logOutlier && dischargeDuration != 0) { 317 logOutlier(dischargeDuration); 318 } 319 320 mLastBatteryStatus = mBatteryStatus; 321 mLastBatteryHealth = mBatteryHealth; 322 mLastBatteryPresent = mBatteryPresent; 323 mLastBatteryLevel = mBatteryLevel; 324 mLastPlugType = mPlugType; 325 mLastBatteryVoltage = mBatteryVoltage; 326 mLastBatteryTemperature = mBatteryTemperature; 327 mLastBatteryLevelCritical = mBatteryLevelCritical; 328 } 329 } 330 sendIntent()331 private final void sendIntent() { 332 // Pack up the values and broadcast them to everyone 333 Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED); 334 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); 335 try { 336 mBatteryStats.setOnBattery(mPlugType == BATTERY_PLUGGED_NONE, mBatteryLevel); 337 } catch (RemoteException e) { 338 // Should never happen. 339 } 340 341 int icon = getIcon(mBatteryLevel); 342 343 intent.putExtra(BatteryManager.EXTRA_STATUS, mBatteryStatus); 344 intent.putExtra(BatteryManager.EXTRA_HEALTH, mBatteryHealth); 345 intent.putExtra(BatteryManager.EXTRA_PRESENT, mBatteryPresent); 346 intent.putExtra(BatteryManager.EXTRA_LEVEL, mBatteryLevel); 347 intent.putExtra(BatteryManager.EXTRA_SCALE, BATTERY_SCALE); 348 intent.putExtra(BatteryManager.EXTRA_ICON_SMALL, icon); 349 intent.putExtra(BatteryManager.EXTRA_PLUGGED, mPlugType); 350 intent.putExtra(BatteryManager.EXTRA_VOLTAGE, mBatteryVoltage); 351 intent.putExtra(BatteryManager.EXTRA_TEMPERATURE, mBatteryTemperature); 352 intent.putExtra(BatteryManager.EXTRA_TECHNOLOGY, mBatteryTechnology); 353 354 if (false) { 355 Log.d(TAG, "updateBattery level:" + mBatteryLevel + 356 " scale:" + BATTERY_SCALE + " status:" + mBatteryStatus + 357 " health:" + mBatteryHealth + " present:" + mBatteryPresent + 358 " voltage: " + mBatteryVoltage + 359 " temperature: " + mBatteryTemperature + 360 " technology: " + mBatteryTechnology + 361 " AC powered:" + mAcOnline + " USB powered:" + mUsbOnline + 362 " icon:" + icon ); 363 } 364 365 ActivityManagerNative.broadcastStickyIntent(intent, null); 366 } 367 logBatteryStats()368 private final void logBatteryStats() { 369 370 IBinder batteryInfoService = ServiceManager.getService(BATTERY_STATS_SERVICE_NAME); 371 if (batteryInfoService != null) { 372 byte[] buffer = new byte[DUMP_MAX_LENGTH]; 373 File dumpFile = null; 374 FileOutputStream dumpStream = null; 375 try { 376 // dump the service to a file 377 dumpFile = new File(DUMPSYS_DATA_PATH + BATTERY_STATS_SERVICE_NAME + ".dump"); 378 dumpStream = new FileOutputStream(dumpFile); 379 batteryInfoService.dump(dumpStream.getFD(), DUMPSYS_ARGS); 380 dumpStream.getFD().sync(); 381 382 // read dumped file above into buffer truncated to DUMP_MAX_LENGTH 383 // and insert into events table. 384 int length = (int) Math.min(dumpFile.length(), DUMP_MAX_LENGTH); 385 FileInputStream fileInputStream = new FileInputStream(dumpFile); 386 int nread = fileInputStream.read(buffer, 0, length); 387 if (nread > 0) { 388 Checkin.logEvent(mContext.getContentResolver(), 389 Checkin.Events.Tag.BATTERY_DISCHARGE_INFO, 390 new String(buffer, 0, nread)); 391 if (LOCAL_LOGV) Log.v(TAG, "dumped " + nread + "b from " + 392 batteryInfoService + "to log"); 393 if (LOCAL_LOGV) Log.v(TAG, "actual dump:" + new String(buffer, 0, nread)); 394 } 395 } catch (RemoteException e) { 396 Log.e(TAG, "failed to dump service '" + BATTERY_STATS_SERVICE_NAME + 397 "':" + e); 398 } catch (IOException e) { 399 Log.e(TAG, "failed to write dumpsys file: " + e); 400 } finally { 401 // make sure we clean up 402 if (dumpStream != null) { 403 try { 404 dumpStream.close(); 405 } catch (IOException e) { 406 Log.e(TAG, "failed to close dumpsys output stream"); 407 } 408 } 409 if (dumpFile != null && !dumpFile.delete()) { 410 Log.e(TAG, "failed to delete temporary dumpsys file: " 411 + dumpFile.getAbsolutePath()); 412 } 413 } 414 } 415 } 416 logOutlier(long duration)417 private final void logOutlier(long duration) { 418 ContentResolver cr = mContext.getContentResolver(); 419 String dischargeThresholdString = Settings.Gservices.getString(cr, 420 Settings.Gservices.BATTERY_DISCHARGE_THRESHOLD); 421 String durationThresholdString = Settings.Gservices.getString(cr, 422 Settings.Gservices.BATTERY_DISCHARGE_DURATION_THRESHOLD); 423 424 if (dischargeThresholdString != null && durationThresholdString != null) { 425 try { 426 long durationThreshold = Long.parseLong(durationThresholdString); 427 int dischargeThreshold = Integer.parseInt(dischargeThresholdString); 428 if (duration <= durationThreshold && 429 mDischargeStartLevel - mBatteryLevel >= dischargeThreshold) { 430 // If the discharge cycle is bad enough we want to know about it. 431 logBatteryStats(); 432 } 433 if (LOCAL_LOGV) Log.v(TAG, "duration threshold: " + durationThreshold + 434 " discharge threshold: " + dischargeThreshold); 435 if (LOCAL_LOGV) Log.v(TAG, "duration: " + duration + " discharge: " + 436 (mDischargeStartLevel - mBatteryLevel)); 437 } catch (NumberFormatException e) { 438 Log.e(TAG, "Invalid DischargeThresholds GService string: " + 439 durationThresholdString + " or " + dischargeThresholdString); 440 return; 441 } 442 } 443 } 444 getIcon(int level)445 private final int getIcon(int level) { 446 if (mBatteryStatus == BatteryManager.BATTERY_STATUS_CHARGING) { 447 return com.android.internal.R.drawable.stat_sys_battery_charge; 448 } else if (mBatteryStatus == BatteryManager.BATTERY_STATUS_DISCHARGING || 449 mBatteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING || 450 mBatteryStatus == BatteryManager.BATTERY_STATUS_FULL) { 451 return com.android.internal.R.drawable.stat_sys_battery; 452 } else { 453 return com.android.internal.R.drawable.stat_sys_battery_unknown; 454 } 455 } 456 457 @Override dump(FileDescriptor fd, PrintWriter pw, String[] args)458 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 459 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) 460 != PackageManager.PERMISSION_GRANTED) { 461 462 pw.println("Permission Denial: can't dump Battery service from from pid=" 463 + Binder.getCallingPid() 464 + ", uid=" + Binder.getCallingUid()); 465 return; 466 } 467 468 synchronized (this) { 469 pw.println("Current Battery Service state:"); 470 pw.println(" AC powered: " + mAcOnline); 471 pw.println(" USB powered: " + mUsbOnline); 472 pw.println(" status: " + mBatteryStatus); 473 pw.println(" health: " + mBatteryHealth); 474 pw.println(" present: " + mBatteryPresent); 475 pw.println(" level: " + mBatteryLevel); 476 pw.println(" scale: " + BATTERY_SCALE); 477 pw.println(" voltage:" + mBatteryVoltage); 478 pw.println(" temperature: " + mBatteryTemperature); 479 pw.println(" technology: " + mBatteryTechnology); 480 } 481 } 482 } 483