• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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>&quot;scale&quot; - int, the maximum value for the charge level</p>
57  * <p>&quot;level&quot; - int, charge level, from 0 through &quot;scale&quot; inclusive</p>
58  * <p>&quot;status&quot; - String, the current charging status.<br />
59  * <p>&quot;health&quot; - String, the current battery health.<br />
60  * <p>&quot;present&quot; - boolean, true if the battery is present<br />
61  * <p>&quot;icon-small&quot; - int, suggested small icon to use for this state</p>
62  * <p>&quot;plugged&quot; - 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>&quot;voltage&quot; - int, current battery voltage in millivolts</p>
65  * <p>&quot;temperature&quot; - int, current battery temperature in tenths of
66  * a degree Centigrade</p>
67  * <p>&quot;technology&quot; - 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