本文以MTK 平台方案为例,从驱动底层到上层应用来说明在Android系统中对电池保护的机制的设计和实现方法。
驱动层:
在"kernel-4.4/drivers/power/mediatek/battery_common.c" 文件中
电池初始化时会设置电池的最大充电温度:
#if defined(MAX_CHARGE_TEMPERATURE)
batt_cust_data.max_charge_temperature = MAX_CHARGE_TEMPERATURE;
#endif
MAX_CHARGE_TEMPERATURE 在文件中设置的值是50度
drivers/misc/mediatek/include/mt-plat/mt6771/include/mach/mtk_charging.h
#define MAX_CHARGE_TEMPERATURE 50
在函数 mt_battery_notify_VBatTemp_check 中,当 检查到 电池的温度超过MAX_CHARGE_TEMPERATURE这个设置时,就会将g_BatteryNotifyCode值设置为2,我们记住这个数字2,这个数字会在后续的应用中用到
static void mt_battery_notify_VBatTemp_check(void)
{
......
if (BMT_status.temperature >= batt_cust_data.max_charge_temperature) {
g_BatteryNotifyCode |= 0x0002;
battery_log(BAT_LOG_CRTI, "[BATTERY] bat_temp(%d) out of range(too high)\n",
BMT_status.temperature);
}
......
BMT_status.temperature 的值通过读取电池的热感寄存器的值来获取的。
同时对g_BatteryNotifyCode 这个变量进行赋值的还有mt_battery_notify_VBat_check、mt_battery_notify_ICharging_check、mt_battery_notify_TotalChargingTime_check等函数。
g_BatteryNotifyCode 这个变量的值会存到sys文件系统下的一个属性文件中:"/sys/devices/platform/charger/BatteryNotify"
在驱动中对这个文件的读写操作进行实现:
static ssize_t show_BatteryNotify(struct device *dev, struct device_attribute *attr, char *buf)
{
battery_log(BAT_LOG_CRTI, "[Battery] show_BatteryNotify : %x\n", g_BatteryNotifyCode);
return sprintf(buf, "%u\n", g_BatteryNotifyCode);
}
static ssize_t store_BatteryNotify(struct device *dev, struct device_attribute *attr,
const char *buf, size_t size)
{
/*char *pvalue = NULL;*/
int rv;
unsigned long reg_BatteryNotifyCode = 0;
battery_log(BAT_LOG_CRTI, "[Battery] store_BatteryNotify\n");
if (buf != NULL && size != 0) {
battery_log(BAT_LOG_CRTI, "[Battery] buf is %s and size is %Zu\n", buf, size);
rv = kstrtoul(buf, 0, ®_BatteryNotifyCode);
if (rv != 0)
return -EINVAL;
g_BatteryNotifyCode = reg_BatteryNotifyCode;
battery_log(BAT_LOG_CRTI, "[Battery] store code : %x\n", g_BatteryNotifyCode);
}
return size;
}
static DEVICE_ATTR(BatteryNotify, 0664, show_BatteryNotify, store_BatteryNotify);
HAL 层:
这里有一个进程叫做batteryWarning 的进程对电池的情况进行实时监控,其实这个进程监控的就是上面讲解的驱动里面写好的文件
"vendor/mediatek/proprietary/frameworks/opt/batterywarning/batterywarning.cpp"
#define FILE_NAME "/sys/devices/platform/charger/BatteryNotify"
void readType(char* buffer) {
FILE * pFile;
pFile = fopen(FILE_NAME, "r"); //打开sys文件: BatteryNotify
if (pFile == NULL) {
ALOGE("error opening file");
return;
} else {
if (fgets(buffer, MAX_CHAR, pFile) == NULL) {
fclose(pFile);
ALOGE("can not get the string from the file");
return;
}
}
fclose(pFile);
int type = atoi(buffer);
if (type > 0)
{
ALOGD("start activity by send intent to BatteryWarningReceiver, type = %d\n", type);
sendBroadcastMessage(String16(ACTION), type); //将读到的 BatteryNotify 文件值通过广播发送给更上层
}
}
int main()
{
char *buffer = (char*) malloc(MAX_CHAR * sizeof(char));
if (buffer == NULL) {
ALOGD("malloc memory failed");
return 0;
}
while(1) {
readType(buffer); //死循环,隔10秒读取一次
sleep(10);
}
free(buffer);
return 0;
}
#define ACTION "mediatek.intent.action.BATTERY_WARNING"
在readType中,将读到的BatteryNotify的值会通过sendBroadcastMessage 这个函数发送广播ACTION出去,并且是带BatteryNotify的值的参数。
值得注意的是,这里面实现一种在CPP里面 实现发送广播的方法,非常值得借鉴,有兴趣的同学可以好好研究一下
bool sendBroadcastMessage(String16 action, int value)
{
ALOGD("sendBroadcastMessage(): Action: %s, Value: %d ", (char *)(action.string()), value);
sp<IServiceManager> sm = defaultServiceManager();
sp<IBinder> am = sm->getService(String16("activity"));
if (am != NULL) {
Parcel data, reply;
data.writeInterfaceToken(String16("android.app.IActivityManager"));
data.writeStrongBinder(NULL);
// Add for match AMS change on O
data.writeInt32(1);
// intent begin
data.writeString16(action); // action
data.writeInt32(0); // URI data type
data.writeString16(NULL, 0); // type
data.writeInt32(0x04000000); // flags: FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
data.writeString16(NULL, 0); // package name
data.writeString16(NULL, 0); // component name
data.writeInt32(0); // source bound - size
data.writeInt32(0); // categories - size
data.writeInt32(0); // selector - size
data.writeInt32(0); // clipData - size
data.writeInt32(-2); // contentUserHint: -2 -> UserHandle.USER_CURRENT
data.writeInt32(-1); // bundle extras length
data.writeInt32(0x4C444E42); // 'B' 'N' 'D' 'L'
int oldPos = data.dataPosition();
data.writeInt32(1); // size
// data.writeInt32(0); // VAL_STRING, need to remove because of analyze common intent
data.writeString16(String16(TYPE));
data.writeInt32(1); // VAL_INTEGER
data.writeInt32(value);
int newPos = data.dataPosition();
data.setDataPosition(oldPos - 8);
data.writeInt32(newPos - oldPos); // refill bundle extras length
data.setDataPosition(newPos);
...
}
上层APP:
上层有个BatteryWarning 的APP在接收这个广播mediatek.intent.action.BATTERY_WARNING:
"vendor/mediatek/proprietary/packages/apps/BatteryWarning/AndroidManifest.xml"
<receiver android:name="com.mediatek.batterywarning.BatteryWarningReceiver"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
<intent-filter>
<action android:name="mediatek.intent.action.BATTERY_WARNING" />
</intent-filter>
<intent-filter>
<action android:name="mediatek.intent.action.THERMAL_DIAG" />
</intent-filter>
</receiver>
vendor/mediatek/proprietary/packages/apps/BatteryWarning/src/com/mediatek/batterywarning/BatteryWarningReceiver.java
中会对相应广播传来的值进行处理:
public void onReceive(Context context, Intent intent) {
......
if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
Log.d(TAG, action + " clear battery_warning_settings shared preference");
SharedPreferences.Editor editor = getSharedPreferences().edit();
editor.clear();
editor.apply();
} else if (ACTION_BATTERY_WARNING.equals(action)) {
Log.d(TAG, action + " start activity according to shared preference");
int type = intent.getIntExtra("type", -1);
Log.d(TAG, "type = " + type);
type = (int) (Math.log(type) / Math.log(2));
if (type < 0 || type >= BatteryWarningActivity.sWarningTitle.length) {
return;
}
boolean showDialogFlag = getSharedPreferences().getBoolean(
Integer.toString(type), true);
Log.d(TAG, "type = " + type + "showDialogFlag = " + showDialogFlag);
if (showDialogFlag) {
Intent activityIntent = new Intent();
activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_CLEAR_TOP
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
activityIntent.setClass(mContext, BatteryWarningActivity.class);
activityIntent.putExtra(BatteryWarningActivity.KEY_TYPE, type);
mContext.startActivity(activityIntent);
}
} else if (ACTION_THERMAL_WARNING.equals(action)) {
int typeValue = intent.getIntExtra(ThermalWarningActivity.KEY_TYPE, -1);
Log.d(TAG, "typeValue = " + typeValue);
if (typeValue == 0 || typeValue == 1) {
Intent thermalIntent = new Intent();
thermalIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
......
}
摘出一段进行说明:
在条件if (ACTION_BATTERY_WARNING.equals(action)) 中
// 这个地方会对传来的值范围进行判断,如果没有超过BatteryWarningActivity 中的 数组sWarningTitle 的范围值,就可以直接返回了,否则才跳转到BatteryWarningActivity 进行处理。
if (type < 0 || type >= BatteryWarningActivity.sWarningTitle.length) {
return;
}
在BatteryWarningActivity 中,我们就可以看到,当充电charger的状态在如下范围时,就会根据不同的mType 值而显示不同的警告dialog给用户,同时会主动断开充电
private static final int CHARGER_OVER_VOLTAGE_TYPE = 0;
private static final int BATTERY_OVER_TEMPERATURE_TYPE = 1;
private static final int CURRENT_OVER_PROTECTION_TYPE = 2; //这个2就是从内核传过来的电池保护设定值
private static final int BATTERY_OVER_VOLTAGE_TYPE = 3;
private static final int SAFETY_OVER_TIMEOUT_TYPE = 4;
private static final int BATTERY_LOW_TEMPERATURE_TYPE = 5;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
setContentView(R.layout.battery_warning);
getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE,
R.layout.custom_title_1);
Intent intent = getIntent();
mType = intent.getIntExtra(KEY_TYPE, -1);
TextView textView = (TextView) findViewById(R.id.left_text);
textView.setText(getString(sWarningTitle[mType]));
Log.d(TAG, "onCreate, mType is " + mType);
if (mType >= CHARGER_OVER_VOLTAGE_TYPE && mType <= BATTERY_LOW_TEMPERATURE_TYPE) {
showWarningDialog(mType);
registerReceiver(mReceiver, new IntentFilter(
Intent.ACTION_POWER_DISCONNECTED));
} else {
finish();
}
}