Android 4.4 電池電量顯示相關。
最近遇到個新的需求,當電池溫度過高以及OVP狀態的時候,在使用者介面提示資訊。
電池電源資訊相關在Framework層/
與電池電量資訊等相關類:
1.BatteryManager.java
2.BatteryProperties.java
3.BatteryStates.java
4.PowerUI.java
5.LightService.java
6.PowerManager.java
7.BatteryService.java
這裡先分析BatteryService.java。
原始碼路徑:
***/framework/base/services/java/com/android/server/BatteryService.java
BatteryService.java程式碼,必要的地方做了處理。
/* * 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 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>"scale" - int, the maximum value for the charge level</p> * <p>"level" - int, charge level, from 0 through "scale" inclusive</p> * <p>"status" - String, the current charging status.<br /> * <p>"health" - String, the current battery health.<br /> * <p>"present" - boolean, true if the battery is present<br /> * <p>"icon-small" - int, suggested small icon to use for this state</p> * <p>"plugged" - 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>"voltage" - int, current battery voltage in millivolts</p> * <p>"temperature" - int, current battery temperature in tenths of * a degree Centigrade</p> * <p>"technology" - 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 //沒有充電狀態則顯示0 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; private BatteryListener mBatteryPropertiesListener; private IBatteryPropertiesRegistrar mBatteryPropertiesRegistrar; public BatteryService(Context context, LightsService lights) { mContext = context; mHandler = new Handler(true /*async*/); mLed = new Led(context, lights); mBatteryStats = BatteryStatsService.getService(); mCriticalBatteryLevel = mContext.getResources().getInteger( com.android.internal.R.integer.config_criticalBatteryWarningLevel); //res/res/values/config.xml:547: <integer name="config_lowBatteryWarningLevel">15</integer> mLowBatteryWarningLevel = mContext.getResources().getInteger( com.android.internal.R.integer.config_lowBatteryWarningLevel); //res/res/values/config.xml:550: <integer name="config_lowBatteryCloseWarningLevel">20</integer> 接近警戒值。 mLowBatteryCloseWarningLevel = mContext.getResources().getInteger( com.android.internal.R.integer.config_lowBatteryCloseWarningLevel); //res/res/values/config.xml:544: <integer name="config_shutdownBatteryTemperature">680</integer>//68度?為什麼? 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"); } //註冊監聽。實時更新電池電量資訊,底層傳遞上來的電池資訊有變化時,則上層會做響應的處理。 //從這裡可以看出回撥介面中呼叫的update()方法應該是比較頻繁的。 mBatteryPropertiesListener = new BatteryListener(); IBinder b = ServiceManager.getService("batterypropreg"); mBatteryPropertiesRegistrar = IBatteryPropertiesRegistrar.Stub.asInterface(b); try { mBatteryPropertiesRegistrar.registerListener(mBatteryPropertiesListener); } catch (RemoteException e) { // Should never happen. } } //該方法在SystemServer.java中呼叫。開機過程。 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即BatteryProperties,該類封裝了電量資訊的一些引數。實現了Parcelable介面。 mBatteryProps = props; // Process the new values. processValuesLocked(); } } } //這裡是處理電量等邏輯的主要位置。 private void processValuesLocked() { boolean logOutlier = false; long dischargeDuration = 0; mBatteryLevelCritical = (mBatteryProps.batteryLevel <= mCriticalBatteryLevel); //當前的充電方式。AC,USB,WIRELESS(第三種有點高大上) 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(); //mBatteryProps儲存之前的電量資訊,這裡做對比當資料資訊有變化的時候則表示電池狀態有變化。 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) { //plugType前後變化,充電狀態--->非充電狀態,非充電狀態---->充電狀態 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; } } //列印Log,忽略++++++++++++++++++begin 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; } ///列印Log,忽略++++++++++++++++++end final boolean plugged = mPlugType != BATTERY_PLUGGED_NONE;//當前的充電狀態 final boolean oldPlugged = mLastPlugType != BATTERY_PLUGGED_NONE;//上次的充電狀態 //處理低電量狀態。傳送廣播,彈出AlertDialog,提示使用者電池電量低。 /* 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(); //++++++++++++++++power connected / not connected begin+++++++++++++++++++++++++++++++++++++++++++++ //充電電源連線與充電電源拔出。 // 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() { //android4.4/framework/base/services/java/com/android/server/EntropyMixer.java:100 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); } }); } //++++++++++++++power connected / not connected end+++++++++++++++++++++++++++++++++++++++++++++++ //當前是否處於低電量狀態 //這兩個中ACTION_BATTERY_LOW和ACTION_BATTERY_OKAY沒有找到對應的處理,因此先忽略。這裡的ACTION_BATTERY_LOW //與低電量的顯示沒有多大關係. 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) { //當處於低電量時傳送ACTION_BATTERY_LOW廣播後,使用者插上了電源充電,此時,電池電量增加變化,如果變化到大於了20%,則會發送該廣播電池狀態正常OK狀態。 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; } } //傳送廣播ACTION:ACTION_BATTERY_CHANGED,傳送當前電量資訊,更新UI顯示,該廣播的處理在SystemUI中的PowerUI.java中。 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中,主動傳送給SystemUI,讓其顯示。 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; } } } //獲取當前電池圖示顯示的drawableID //BATTERY_STATUS_DISCHARGING和BATTERY_STATUS_NOT_CHARGING應該差不多,因為從邏輯裡面看兩者狀態下都是用了相同的drawable資源。 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 LightsService.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, LightsService lights) { mBatteryLight = lights.getLight(LightsService.LIGHT_ID_BATTERY); //./core/res/res/values/config.xml:562: <integer name="config_notificationsBatteryLowARGB">0xFFFF0000</integer> mBatteryLowARGB = context.getResources().getInteger( com.android.internal.R.integer.config_notificationsBatteryLowARGB); //./core/res/res/values/config.xml:565: <integer name="config_notificationsBatteryMediumARGB">0xFFFFFF00</integer> mBatteryMediumARGB = context.getResources().getInteger( com.android.internal.R.integer.config_notificationsBatteryMediumARGB); //./core/res/res/values/config.xml:568: <integer name="config_notificationsBatteryFullARGB">0xFF00FF00</integer> mBatteryFullARGB = context.getResources().getInteger( com.android.internal.R.integer.config_notificationsBatteryFullARGB); //./core/res/res/values/config.xml:571: <integer name="config_notificationsBatteryLedOn">125</integer> mBatteryLedOn = context.getResources().getInteger( com.android.internal.R.integer.config_notificationsBatteryLedOn); //./core/res/res/values/config.xml:577: <integer name="config_notificationsBatteryLedOff">2875</integer> 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, LightsService.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 { public void batteryPropertiesChanged(BatteryProperties props) { BatteryService.this.update(props); } } }
上面看完了,BatteryService.java的原始碼,感覺不多, 因此這裡就平時使用手機的體驗來看看相關的一些具體實現吧。反正原始碼在手,隨便你看。
1.手機電量過低,彈出來的dialog資訊框是從哪裡實現的?
低電量時的廣播實際上就是BatteryService的sendIntentLocked() 方法中ACTION為ACTION_BATTERY_CHANGED的廣播,在SystemUI裡面接收引數判斷處理。
這個直接在PowerUI.java中檢視。從接收廣播,到解析資料,然後針對資料的各個情況做處理。
private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (action.equals(Intent.ACTION_BATTERY_CHANGED)) { //上一次電量 final int oldBatteryLevel = mBatteryLevel; //最新電量 mBatteryLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 100); //上一次電池狀態 final int oldBatteryStatus = mBatteryStatus; //最新電池狀態 mBatteryStatus = intent.getIntExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_UNKNOWN); //充電型別。AC,USB,WIRELESS final int oldPlugType = mPlugType; mPlugType = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 1); final int oldInvalidCharger = mInvalidCharger; mInvalidCharger = intent.getIntExtra(BatteryManager.EXTRA_INVALID_CHARGER, 0); //當前是否在充電 final boolean plugged = mPlugType != 0; //上一次重新整理,是否在充電狀態。 final boolean oldPlugged = oldPlugType != 0; //從方法來看,貌似就是電池的狀態是Ok的呢還是電量過低, int oldBucket = findBatteryLevelBucket(oldBatteryLevel); int bucket = findBatteryLevelBucket(mBatteryLevel); if (DEBUG) { Slog.d(TAG, "buckets ....." + mLowBatteryAlertCloseLevel + " .. " + mLowBatteryReminderLevels[0] + " .. " + mLowBatteryReminderLevels[1]); Slog.d(TAG, "level " + oldBatteryLevel + " --> " + mBatteryLevel); Slog.d(TAG, "status " + oldBatteryStatus + " --> " + mBatteryStatus); Slog.d(TAG, "plugType " + oldPlugType + " --> " + mPlugType); Slog.d(TAG, "invalidCharger " + oldInvalidCharger + " --> " + mInvalidCharger); Slog.d(TAG, "bucket " + oldBucket + " --> " + bucket); Slog.d(TAG, "plugged " + oldPlugged + " --> " + plugged); } if (oldInvalidCharger == 0 && mInvalidCharger != 0) { Slog.d(TAG, "showing invalid charger warning"); showInvalidChargerDialog(); return; } else if (oldInvalidCharger != 0 && mInvalidCharger == 0) { dismissInvalidChargerDialog(); } else if (mInvalidChargerDialog != null) { // if invalid charger is showing, don't show low battery return; } if (!plugged && (bucket < oldBucket || oldPlugged) && mBatteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN && bucket < 0) { showLowBatteryWarning(); // only play SFX when the dialog comes up or the bucket changes if (bucket != oldBucket || oldPlugged) {//可以體會到,當我們手機沒電了,恰好我們把充電拔了,就會有提示音。 playLowBatterySound(); } } else if (plugged || (bucket > oldBucket && bucket > 0)) { //充電狀態 dismissLowBatteryWarning(); } else if (mBatteryLevelTextView != null) { //顯示低電量dialog showLowBatteryWarning(); } } else if (Intent.ACTION_SCREEN_OFF.equals(action)) { mScreenOffTime = SystemClock.elapsedRealtime(); } else if (Intent.ACTION_SCREEN_ON.equals(action)) { mScreenOffTime = -1; } else { Slog.w(TAG, "unknown intent: " + intent); } } };
程式碼比較清晰,主要是電池資訊的一些情況判斷,需要多多琢磨一下為啥是這樣的。
//顯示低電量Dialog
void showLowBatteryWarning() {
Slog.i(TAG,
((mBatteryLevelTextView == null) ? "showing" : "updating")
+ " low battery warning: level=" + mBatteryLevel
+ " [" + findBatteryLevelBucket(mBatteryLevel) + "]");
CharSequence levelText = mContext.getString(
R.string.battery_low_percent_format, mBatteryLevel);
//這裡的第一個if:因為電池的重新整理頻率比較高,因此如果此時dialog還在顯示狀態中,直接重新整理電量資訊即可。
if (mBatteryLevelTextView != null) {
mBatteryLevelTextView.setText(levelText);
} else {
View v = View.inflate(mContext, R.layout.battery_low, null);
mBatteryLevelTextView = (TextView)v.findViewById(R.id.level_percent);
mBatteryLevelTextView.setText(levelText);
AlertDialog.Builder b = new AlertDialog.Builder(mContext);
b.setCancelable(true);
b.setTitle(R.string.battery_low_title);
b.setView(v);
b.setIconAttribute(android.R.attr.alertDialogIcon);
b.setPositiveButton(android.R.string.ok, null);
final Intent intent = new Intent(Intent.ACTION_POWER_USAGE_SUMMARY);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_MULTIPLE_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
| Intent.FLAG_ACTIVITY_NO_HISTORY);
if (intent.resolveActivity(mContext.getPackageManager()) != null) {
b.setNegativeButton(R.string.battery_low_why,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
mContext.startActivityAsUser(intent, UserHandle.CURRENT);
dismissLowBatteryWarning();
}
});
}
AlertDialog d = b.create();
d.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
mLowBatteryDialog = null;
mBatteryLevelTextView = null;
}
});
//dialog屬性
d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
d.getWindow().getAttributes().privateFlags |=
WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
d.show();
mLowBatteryDialog = d;
}
}
我要做的需求差不多就可以按照低電量的顯示來實現了。這裡省略。
2.拔掉充電器或者USB連線,狀態列的電池圖示是如何更新的?
當插拔資料線的時候,電池圖示的變化,是自定義View Draw出來的。具體在
packages/SystemUI/src/com/android/systemui/BatteryMeterView.java 。
3.低電量關機是如何實現的?
首先我們可以從BatteryService.java中看到有這麼個方法:
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);
}
}
});
}
}
--->搜尋可以找到AndroidMenifest.xml
<activity android:name="com.android.server.ShutdownActivity"
android:permission="android.permission.SHUTDOWN"
android:excludeFromRecents="true">
<intent-filter>
<action android:name="android.intent.action.ACTION_REQUEST_SHUTDOWN" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.REBOOT" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
之前遇到一個問題,我們像從app上去實現關機,具體做法再研究了。
其中有人做了,但是涉及到一個許可權就是
android:permission="android.permission.SHUTDOWN"
其中,android.excludeFromRecents="true" 表示當前開啟的Activity所屬應用,不會顯示在最近使用的列表裡。
/*
* from BatteryService.java
*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);
*
*/
Intent intent = getIntent();
mReboot = Intent.ACTION_REBOOT.equals(intent.getAction());//from BatteryService傳遞過來的是Intent.ACTION_REQUEST_SHUTDOWN,這裡肯定是false。
mConfirm = intent.getBooleanExtra(Intent.EXTRA_KEY_CONFIRM, false);
Slog.i(TAG, "onCreate(): confirm=" + mConfirm);
Thread thr = new Thread("ShutdownActivity") {
@Override
public void run() {
IPowerManager pm = IPowerManager.Stub.asInterface(
ServiceManager.getService(Context.POWER_SERVICE));
try {
//根據Action需求,是重啟還是關機。
if (mReboot) {
//接下來。。。。。
pm.reboot(mConfirm, null, false);
} else {
pm.shutdown(mConfirm, false);
}
} catch (RemoteException e) {
}
}
};
thr.start();
finish();
// Wait for us to tell the power manager to shutdown.
try {
thr.join();
} catch (InterruptedException e) {
}
}
8.app開發過程中,我們需要獲取電池電量資訊,如何來做?
當我們在開發app的時候需要獲取電池資訊,可以仿照BatteryService來做。 因為,BatteryService主動把電池電量資訊傳遞給了SystemUI.
簡單的做法就是:
getApplication().registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
//這裡的intent封裝了我們在BatterySerivce.java中sendIntentLocked()
//封裝的電池電量資訊。
}
}, filter) ;
4.pm.reboot,pm.shutdown 的後續流程是如何實現的?
5.LED燈顯示 LightService
6.電池電量管理的底層研究。
7.BatteryService傳送廣播傳遞過程如何實現的。
比如這樣的
Intent statusIntent = new Intent(Intent.ACTION_POWER_CONNECTED);
statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL);
這裡就先原始碼跟蹤到這裡,下次繼續補充。