android 9.0关机充电图标和字体修改
相关源文件
system/core/healthd/healthd_draw.cpp
system/core/healthd/images/battery_fail.png
system/core/healthd/images/battery_scale.png
/system/core/healthd/ charger.cpp
hardware/interfaces/health/2.0/default/healthd_common.cpp
system/core/healthd/healthd_mode_charger.cpp
bootable/recovery/minui/graphics.cpp
bootable/recovery/minui/font_10x18.h
电量显示百分比字体替换
android默认文字和充电图标分开。
android 9.0的充电图标和电量百分比显示主要在函数 healthd_mode_charger_heartbeat中实现。
void healthd_mode_charger_heartbeat() {
charger* charger = &charger_state;
int64_t now = curr_time_ms();
handle_input_state(charger, now);
handle_power_supply_state(charger, now);
/* do screen update last in case any of the above want to start
* screen transitions (animations, etc)
*/
update_screen_state(charger, now);
}
static void update_screen_state(charger* charger, int64_t now) {
animation* batt_anim = charger->batt_anim;
int disp_time;
if (!batt_anim->run || now < charger->next_screen_transition) return;
if (healthd_draw == nullptr) {
if (healthd_config && healthd_config->screen_on) {
if (!healthd_config->screen_on(batt_prop)) {
LOGV("[%" PRId64 "] leave screen off\n", now);
batt_anim->run = false;
charger->next_screen_transition = -1;
if (charger->charger_connected) request_suspend(true);
return;
}
}
healthd_draw.reset(new HealthdDraw(batt_anim));
#ifndef CHARGER_DISABLE_INIT_BLANK
set_backlight(false);
healthd_draw->blank_screen(true);
#endif
}
/*如果当前显示的循环次数已经为设置的最大cycle,就做灭屏处理,等待下次按下*/
/*power键或者拔掉usb*/
/* animation is over, blank screen and leave */
if (batt_anim->num_cycles > 0 && batt_anim->cur_cycle == batt_anim->num_cycles) {
reset_animation(batt_anim);
charger->next_screen_transition = -1;
set_backlight(false);
healthd_draw->blank_screen(true);
LOGV("[%" PRId64 "] animation done\n", now);
if (charger->charger_connected) request_suspend(true);
return;
}
/*设置一帧,即一个png图片的显示时间*/
disp_time = batt_anim->frames[batt_anim->cur_frame].disp_time;
/*充电休眠过程中按power键或者插第一次刚开始充电时候亮屏*/
/* unblank the screen on first cycle and first frame */
if (batt_anim->cur_cycle == 0 && batt_anim->cur_frame == 0) {
healthd_draw->blank_screen(false);
set_backlight(true);
}
/*当前帧如果是第一帧(按下power键唤醒或者第一次刚开始充电)*/
/*根据实际电量,确认起始帧(即从第几张png图片开始显示),起始*/
/*帧显示时间加first_frame_repeats*/
/* animation starting, set up the animation */
if (batt_anim->cur_frame == 0) {
LOGV("[%" PRId64 "] animation starting\n", now);
if (batt_prop) {
batt_anim->cur_level = batt_prop->batteryLevel;
batt_anim->cur_status = batt_prop->batteryStatus;
if (batt_prop->batteryLevel >= 0 && batt_anim->num_frames != 0) {
/* find first frame given current battery level */
for (int i = 0; i < batt_anim->num_frames; i++) {
if (batt_anim->cur_level >= batt_anim->frames[i].min_level &&
batt_anim->cur_level <= batt_anim->frames[i].max_level) {
batt_anim->cur_frame = i;
break;
}
}
// repeat the first frame first_frame_repeats times
disp_time = batt_anim->frames[batt_anim->cur_frame].disp_time *
batt_anim->first_frame_repeats;
}
}
}
/*设定起始帧之后,开始调用redraw_screen显示充电画面,包括文字*/
/* draw the new frame (@ cur_frame) */
healthd_draw->redraw_screen(charger->batt_anim, charger->surf_unknown);
/* if we don't have anim frames, we only have one image, so just bump
* the cycle counter and exit
*/
if (batt_anim->num_frames == 0 || batt_anim->cur_level < 0) {
LOGW("[%" PRId64 "] animation missing or unknown battery status\n", now);
charger->next_screen_transition = now + BATTERY_UNKNOWN_TIME;
batt_anim->cur_cycle++;
return;
}
/*显示一帧需要等待的耗时*/
/* schedule next screen transition */
charger->next_screen_transition = now + disp_time;
/* advance frame cntr to the next valid frame only if we are charging
* if necessary, advance cycle cntr, and reset frame cntr
*/
if (charger->charger_connected) {
batt_anim->cur_frame++;
while (batt_anim->cur_frame < batt_anim->num_frames &&
(batt_anim->cur_level < batt_anim->frames[batt_anim->cur_frame].min_level ||
batt_anim->cur_level > batt_anim->frames[batt_anim->cur_frame].max_level)) {
batt_anim->cur_frame++;
}
/*第N轮显示结束,进入第N+1轮显示,直到cycle为设置的最大的值,进入灭屏*/
if (batt_anim->cur_frame >= batt_anim->num_frames) {
batt_anim->cur_cycle++;
batt_anim->cur_frame = 0;
/* don't reset the cycle counter, since we use that as a signal
* in a test above to check if animation is over
*/
}
} else {
/* Stop animating if we're not charging.
* If we stop it immediately instead of going through this loop, then
* the animation would stop somewhere in the middle.
*/
batt_anim->cur_frame = 0;
batt_anim->cur_cycle++;
}
}
void HealthdDraw::redraw_screen(const animation* batt_anim, GRSurface* surf_unknown) {
clear_screen();
/* try to display *something* */
if (batt_anim->cur_level < 0 || batt_anim->num_frames == 0)
draw_unknown(surf_unknown);
else
draw_battery(batt_anim);
gr_flip();
}
显示充电图标,电量百分比和时间。
void HealthdDraw::draw_battery(const animation* anim) {
const animation::frame& frame = anim->frames[anim->cur_frame];
if (anim->num_frames != 0) {
draw_surface_centered(frame.surface);
LOGV("drawing frame #%d min_cap=%d time=%d\n", anim->cur_frame,
frame.min_level, frame.disp_time);
}
draw_clock(anim);
draw_percent(anim);
}
显示充电图标的实际为draw_surface_centered,draw_percent为显示文字点亮百分比,默认代码未初始化field.font,即字体font。使用minui默认字体进行显示。
void HealthdDraw::draw_percent(const animation* anim) {
int cur_level = anim->cur_level;
if (anim->cur_status == BATTERY_STATUS_FULL) {
cur_level = 100;
}
if (cur_level < 0) return;
const animation::text_field& field = anim->text_percent;
if (field.font == nullptr || field.font->char_width == 0 ||
field.font->char_height == 0) {
return;
}
std::string str = base::StringPrintf("%d%%", cur_level);
int x, y;
determine_xy(field, str.size(), &x, &y);
LOGV("drawing percent %s %d %d\n", str.c_str(), x, y);
/*设置颜色,根据field.font指定的字体显示百分比,x,y为显示的坐标,显示大小*/
由font指定*/
gr_color(field.color_r, field.color_g, field.color_b, field.color_a);
draw_text(field.font, x, y, str.c_str());
}
由于默认代码field.font为空,不会显示电量百分比,添加以下代码,用字体gr_font显示百分比。gr_font的初始化在gr_init_font中。
/* bootable/recovery/minui/graphics.cpp*/
const GRFont* gr_sys_font() {
return gr_font;
}
gr_init_font() 初始化gr_font主要是两个逻辑。
逻辑一:调用 int res = gr_init_font(“font”, &gr_font);最终会根据name “font”,找到res/images/font.png,依据font.png初始化gr_font,然后return,不会进入逻辑二。font.png的位置在out目录recovery/root/res/images/font.png。recovery模式下,就是走的这个流程。
/*build/make/core/Makefile默认使用bootable/recovery/fonts下的18x32.png初始化
*recovery模式下字体。*/
ifneq (,$(filter xxxhdpi 560dpi xxhdpi 400dpi xhdpi,$(recovery_density)))
recovery_font := $(call include-path-for, recovery)/fonts/18x32.png
else
recovery_font := $(call include-path-for, recovery)/fonts/12x22.png
endif
逻辑二:如果res/images/font.png不存在,就会根据"font_10x18.h"这个头文件定义的font初始化,这个默认字体文件在源码路径bootable/recovery/minui/font_10x18.h。关机充电电量百分比显示的字体格式根据此头文件初始化,替换验证。
/*bootable/recovery/minui/font_10x18.h*/
struct {
unsigned width;
unsigned height;
unsigned char_width;
unsigned char_height;
unsigned char rundata[2973];
} font = {
.width = 960,
.height = 18,
.char_width = 10,
.char_height = 18,
.rundata = {
......
}
static void update_screen_state(charger* charger, int64_t now) {
animation* batt_anim = charger->batt_anim;
int disp_time;
if (!batt_anim->run || now < charger->next_screen_transition) return;
if (healthd_draw == nullptr) {
if (healthd_config && healthd_config->screen_on) {
if (!healthd_config->screen_on(batt_prop)) {
LOGV("[%" PRId64 "] leave screen off\n", now);
batt_anim->run = false;
charger->next_screen_transition = -1;
if (charger->charger_connected) request_suspend(true);
return;
}
}
healthd_draw.reset(new HealthdDraw(batt_anim));
#ifndef CHARGER_DISABLE_INIT_BLANK
set_backlight(false);
healthd_draw->blank_screen(true);
#endif
}
......
}
HealthdDraw::HealthdDraw(animation* anim)
: kSplitScreen(HEALTHD_DRAW_SPLIT_SCREEN),
kSplitOffset(HEALTHD_DRAW_SPLIT_OFFSET) {
gr_init();
......
}
int gr_init() {
gr_init_font();
......
}
static void gr_init_font(void) {
/*逻辑一*/
int res = gr_init_font("font", &gr_font);
if (res == 0) {
return;
}
/*逻辑二*/
printf("failed to read font: res=%d\n", res);
// fall back to the compiled-in font.
gr_font = static_cast<GRFont*>(calloc(1, sizeof(*gr_font)));
gr_font->texture = static_cast<GRSurface*>(malloc(sizeof(*gr_font->texture)));
gr_font->texture->width = font.width;
gr_font->texture->height = font.height;
gr_font->texture->row_bytes = font.width;
gr_font->texture->pixel_bytes = 1;
unsigned char* bits = static_cast<unsigned char*>(malloc(font.width * font.height));
gr_font->texture->data = bits;
unsigned char data;
unsigned char* in = font.rundata;
while ((data = *in++)) {
memset(bits, (data & 0x80) ? 255 : 0, data & 0x7f);
bits += (data & 0x7f);
}
gr_font->char_width = font.char_width;
gr_font->char_height = font.char_height;
}
/*逻辑二recovery 模式下字体初始化,最终找到res/images/font.png*/
int gr_init_font(const char* name, GRFont** dest) {
......
int res = res_create_alpha_surface(name, &(font->texture));
if (res < 0) {
free(font);
return res;
}
......
}
PngHandler::PngHandler(const std::string& name) : error_code_(0), png_fp_(nullptr, fclose) {
std::string res_path = android::base::StringPrintf("/res/images/%s.png", name.c_str());
png_fp_.reset(fopen(res_path.c_str(), "rbe"));
if (!png_fp_) {
error_code_ = -1;
return;
}
......
}
充电图标替换
不同于android6.0,android7.0及以上版本默认关机充电图标已经变更一张battery_scale.png。修改默认关机充电图标,实际上要替换battery_scale.png。
- battery_scale.png文件制作
利用系统源码中的工具bootable/recovery/interlace-frames.py,即能将battery_scale.png拆分成几张png图片,也能将若干张png合成一张battery_scale.png。
- 拆分指令
python interlace-frames.py -d battery_scale.png -o battery.png
结果生成battery00.png~battery05.png共6个PNG文件,对应c源码配置:
static animation::frame default_animation_frames[] = {
{
.disp_time = 750,
.min_level = 0,
.max_level = 19,
.surface = NULL,
},
{
.disp_time = 750,
.min_level = 0,
.max_level = 39,
.surface = NULL,
},
{
.disp_time = 750,
.min_level = 0,
.max_level = 59,
.surface = NULL,
},
{
.disp_time = 750,
.min_level = 0,
.max_level = 79,
.surface = NULL,
},
{
.disp_time = 750,
.min_level = 80,
.max_level = 95,
.surface = NULL,
},
{
.disp_time = 750,
.min_level = 0,
.max_level = 100,
.surface = NULL,
},
};
将生成的battery00.png~battery05.png替换成定制的充电图片,并使用合并指令,重新生成新的battery_scale.png。
- 合并指令
python interlace-frames.py -o battery_scale.png oem/battery00.png oem/battery01.png oem/battery02.png oem/battery03.png oem/battery04.png oem/battery05.png
- 验证方法
adb root
adb remount
setenforce 0
adb push battery_scale.png res/images/charger
adb reboot