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