android11关机安卓充电的UI定制化

引言

首先上一张安卓充电的图片:
安卓关机状态下有两种充电模式:uboot-charge和android-charge,可通过dts配置使用哪一种充电模式。
dts配置中uboot-charge和android-charge是互斥的,如下配置的是开启android-charge:

kernel/arch/arm64/boot/dts/rockchip/rk3566_xxproject.dts
在这里插入图片描述

本片主要讲解安卓充电的定制化。

安卓充电流程讲解

InitAnimation解析animation.txt配置文件

实现安卓充电的是一个名为charger的文件,源码位置:system/core/healthd
程序启动时首先会通过InitAnimation函数根据animation.txt解析得到图片和字体文件的路径。animation.txt内容如下:

#动画循环次数 首帧显示次数 动画压缩文件名(charge_scale是多张图片合成的一张图片)
animation: 3 1 charge_scale
#fail文件名
fail: fail_scale
#c c r g b a 字体文件名
clock_display: c c 255 255 255 255 font 
percent_display: c c 255 255 255 255 font 
#电量20以下显示的图片
frame: 500 0 19
#电量40以下显示的图片
frame: 600 0 39
frame: 700 0 59
frame: 750 0 79
frame: 750 0 89
frame: 750 0 100

Charger实现在system/core/healthd/healthd_mode_charger.cpp

static constexpr const char* product_animation_desc_path = "/product/etc/res/values/charger/animation.txt";
static constexpr const char* product_animation_root = "/product/etc/res/images/";void Charger::InitAnimation() {bool parse_success;std::string content;if (base::ReadFileToString(product_animation_desc_path, &content)) {parse_success = parse_animation_desc(content, &batt_anim_);batt_anim_.set_resource_root(product_animation_root);} else if (base::ReadFileToString(animation_desc_path, &content)) {parse_success = parse_animation_desc(content, &batt_anim_);} else {LOGW("Could not open animation description at %s\n", animation_desc_path);parse_success = false;}
//parse_animation_desc实现在system/core/healthd/AnimationParser.cpp
bool parse_animation_desc(const std::string& content, animation* anim) {static constexpr const char* animation_prefix = "animation: ";static constexpr const char* fail_prefix = "fail: ";static constexpr const char* clock_prefix = "clock_display: ";static constexpr const char* percent_prefix = "percent_display: ";std::vector<animation::frame> frames;for (const auto& line : base::Split(content, "\n")) {animation::frame frame;const char* rest;if (can_ignore_line(line.c_str())) {continue;} else if (remove_prefix(line, animation_prefix, &rest)) {int start = 0, end = 0;if (sscanf(rest, "%d %d %n%*s%n", &anim->num_cycles, &anim->first_frame_repeats,&start, &end) != 2 ||end == 0) {LOGE("Bad animation format: %s\n", line.c_str());return false;} else {anim->animation_file.assign(&rest[start], end - start);}} else if (remove_prefix(line, fail_prefix, &rest)) {anim->fail_file.assign(rest);} else if (remove_prefix(line, clock_prefix, &rest)) {if (!parse_text_field(rest, &anim->text_clock)) {LOGE("Bad clock_display format: %s\n", line.c_str());return false;}} else if (remove_prefix(line, percent_prefix, &rest)) {if (!parse_text_field(rest, &anim->text_percent)) {LOGE("Bad percent_display format: %s\n", line.c_str());return false;}} else if (sscanf(line.c_str(), " frame: %d %d %d",&frame.disp_time, &frame.min_level, &frame.max_level) == 3) {frames.push_back(std::move(frame));} else {LOGE("Malformed animation description line: %s\n", line.c_str());return false;}}if (anim->animation_file.empty() || frames.empty()) {LOGE("Bad animation description. Provide the 'animation: ' line and at least one 'frame: ' ""line.\n");return false;}anim->num_frames = frames.size();anim->frames = new animation::frame[frames.size()];std::copy(frames.begin(), frames.end(), anim->frames);return true;
}
//parse_text_field实现在system/core/healthd/AnimationParser.cpp
bool parse_text_field(const char* in, animation::text_field* field) {int* x = &field->pos_x;int* y = &field->pos_y;int* r = &field->color_r;int* g = &field->color_g;int* b = &field->color_b;int* a = &field->color_a;int start = 0, end = 0;if (sscanf(in, "c c %d %d %d %d %n%*s%n", r, g, b, a, &start, &end) == 4) {*x = CENTER_VAL;*y = CENTER_VAL;} else if (sscanf(in, "c %d %d %d %d %d %n%*s%n", y, r, g, b, a, &start, &end) == 5) {*x = CENTER_VAL;} else if (sscanf(in, "%d c %d %d %d %d %n%*s%n", x, r, g, b, a, &start, &end) == 5) {*y = CENTER_VAL;} else if (sscanf(in, "%d %d %d %d %d %d %n%*s%n", x, y, r, g, b, a, &start, &end) != 6) {return false;}if (end == 0) return false;field->font_file.assign(&in[start], end - start);return true;
}

初始化GRSurface

根据上一步解析得到的图片的路径转化成绘图表面

void Charger::Init(struct healthd_config* config) {...InitAnimation();ret = res_create_display_surface(batt_anim_.fail_file.c_str(), &surf_unknown_);if (ret < 0) {LOGE("Cannot load custom battery_fail image. Reverting to built in: %d\n", ret);ret = res_create_display_surface("charger/battery_fail", &surf_unknown_);if (ret < 0) {LOGE("Cannot load built in battery_fail image\n");surf_unknown_ = NULL;}}GRSurface** scale_frames;int scale_count;int scale_fps;  // Not in use (charger/battery_scale doesn't have FPS text// chunk). We are using hard-coded frame.disp_time instead.ret = res_create_multi_display_surface(batt_anim_.animation_file.c_str(), &scale_count,&scale_fps, &scale_frames);if (ret < 0) {LOGE("Cannot load battery_scale image\n");batt_anim_.num_frames = 0;batt_anim_.num_cycles = 1;} else if (scale_count != batt_anim_.num_frames) {LOGE("battery_scale image has unexpected frame count (%d, expected %d)\n", scale_count,batt_anim_.num_frames);batt_anim_.num_frames = 0;batt_anim_.num_cycles = 1;} else {for (i = 0; i < batt_anim_.num_frames; i++) {batt_anim_.frames[i].surface = scale_frames[i];}}

绘制电量图片、电量百分比和时间文字,用的minui框架

void Charger::UpdateScreenState(int64_t now) {...if (healthd_draw_ == nullptr) {...//初始化healthd_draw_healthd_draw_.reset(new HealthdDraw(&batt_anim_));if (android::sysprop::ChargerProperties::disable_init_blank().value_or(false)) {healthd_draw_->blank_screen(true);screen_blanked_ = true;}}//执行具体的绘制healthd_draw_->redraw_screen(&batt_anim_, surf_unknown_);...
}HealthdDraw::HealthdDraw(animation* anim): kSplitScreen(get_split_screen()), kSplitOffset(get_split_offset()) {int ret = gr_init();if (ret < 0) {LOGE("gr_init failed\n");graphics_available = false;return;}graphics_available = true;sys_font = gr_sys_font();if (sys_font == nullptr) {LOGW("No system font, screen fallback text not available\n");} else {gr_font_size(sys_font, &char_width_, &char_height_);}screen_width_ = gr_fb_width() / (kSplitScreen ? 2 : 1);screen_height_ = gr_fb_height();int res;if (!anim->text_clock.font_file.empty() &&(res = gr_init_font(anim->text_clock.font_file.c_str(), &anim->text_clock.font)) < 0) {LOGE("Could not load time font (%d)\n", res);}if (!anim->text_percent.font_file.empty() &&(res = gr_init_font(anim->text_percent.font_file.c_str(), &anim->text_percent.font)) < 0) {LOGE("Could not load percent font (%d)\n", res);}
}void HealthdDraw::redraw_screen(const animation* batt_anim, GRSurface* surf_unknown) {if (!graphics_available) return;clear_screen();/* try to display *something* */if (batt_anim->cur_status == BATTERY_STATUS_UNKNOWN || batt_anim->cur_level < 0 ||batt_anim->num_frames == 0)draw_unknown(surf_unknown);elsedraw_battery(batt_anim);gr_flip();
}void HealthdDraw::draw_battery(const animation* anim) {if (!graphics_available) return;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);
}void HealthdDraw::draw_percent(const animation* anim) {if (!graphics_available) return;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);gr_color(field.color_r, field.color_g, field.color_b, field.color_a);draw_text(field.font, x, y, str.c_str());
}

HealthdDraw实现在system/core/healthd/healthd_draw.cpp

电量刷新和事件响应

充电状体下会监听power按键、充电器插拔事件和电量更新事件。
监听power按键实现在HandleInputState函数,按下power键会触发重新显示充电动画。

void Charger::HandleInputState(int64_t now) {//监听power按键ProcessKey(KEY_POWER, now);if (next_key_check_ != -1 && now > next_key_check_) next_key_check_ = -1;
}void Charger::ProcessKey(int code, int64_t now) {key_state* key = &keys_[code];if (code == KEY_POWER) {if (key->down) {int64_t reboot_timeout = key->timestamp + POWER_ON_KEY_TIME;if (now >= reboot_timeout) {/* We do not currently support booting from charger mode onall devices. Check the property and continue booting or rebootaccordingly. */if (property_get_bool("ro.enable_boot_charger_mode", false)) {LOGW("[%" PRId64 "] booting from charger mode\n", now);property_set("sys.boot_from_charger_mode", "1");} else {if (batt_anim_.cur_level >= boot_min_cap_) {LOGW("[%" PRId64 "] rebooting\n", now);reboot(RB_AUTOBOOT);} else {LOGV("[%" PRId64"] ignore power-button press, battery level ""less than minimum\n",now);}}} else {/* if the key is pressed but timeout hasn't expired,* make sure we wake up at the right-ish time to check*/SetNextKeyCheck(key, POWER_ON_KEY_TIME);/* Turn on the display and kick animation on power-key press* rather than on key release*/kick_animation(&batt_anim_);request_suspend(false);}} else {/* if the power key got released, force screen state cycle */if (key->pending) {kick_animation(&batt_anim_);request_suspend(false);}}}key->pending = false;
}

如果想要监听更多的按键事件,只需要在HandleInputState函数中新增ProcessKey(KEY_xxx, now),然后在ProcessKey实现对应键值的逻辑即可。

充电器插拔回调到HandlePowerSupplyState函数

void Charger::HandlePowerSupplyState(int64_t now) {int timer_shutdown = UNPLUGGED_SHUTDOWN_TIME;if (!have_battery_state_) return;if (!charger_online()) {//断开充电器...} else {//插入充电器...}
}

电量刷新会回调到OnHealthInfoChanged函数

void Charger::OnHealthInfoChanged(const HealthInfo_2_1& health_info) {set_charger_online(health_info);if (!have_battery_state_) {have_battery_state_ = true;next_screen_transition_ = curr_time_ms() - 1;request_suspend(false);reset_animation(&batt_anim_);kick_animation(&batt_anim_);}health_info_ = health_info.legacy.legacy;AdjustWakealarmPeriods(charger_online());
}

在rk3566 android11中动画执行完后,如果电量刷新了不会触发界面的刷新。如要实现电量实时更新到界面,在此方法中新增逻辑即可,下面贴下我实现电量实时刷新的patch

diff --git a/healthd/healthd_mode_charger.cpp b/healthd/healthd_mode_charger.cpp
--- a/healthd/healthd_mode_charger.cpp	(revision 6ae575fc403d2504435366ac34ff233e537e78bd)
+++ b/healthd/healthd_mode_charger.cpp	(revision 1122ab003e599072fa194f23b593fbd4ad84205e)
@@ -617,6 +617,15 @@reset_animation(&batt_anim_);kick_animation(&batt_anim_);}
+    //huanghp add: refresh screen when batteryLevel changed
+    if (health_info_.batteryLevel != health_info.legacy.legacy.batteryLevel){
+        LOGV("batteryLevel changed : %d\n",health_info.legacy.legacy.batteryLevel);
+        request_suspend(false);
+        reset_animation(&batt_anim_);
+        kick_animation(&batt_anim_);
+    }
+    //huanghp end;health_info_ = health_info.legacy.legacy;AdjustWakealarmPeriods(charger_online());

源码更新了后可以单编charger,ado root && adb remount后替换charger文件重启机器就能看到效果,不需要刷机。对于下面的充电图标和字体也是找到对应目录直接替换后重启就可以看效果。

充电图标替换

修改默认关机充电图标实际上要替换battery_scale.png,charge_scale.png实际是由多张图片合成的一张图片。
在这里插入图片描述
对应c源码配置

void Charger::InitDefaultAnimationFrames() {owned_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,},};
}

合成和拆分charge_scale用到的脚本:bootable/recovery/interlace-frames.py

#合成命令
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
#拆分命令
python interlace-frames.py -d battery_scale.png -o battery.png

font.png字体文件替换

在这里插入图片描述
在这里插入图片描述
bootable/recovery/fonts目录下默认有些不同大小的字体文件,官方的说法是字体都是用font
Inconsolata自动生成的。

The images in this directory were generated using the font
Inconsolata, which is released under the OFL license and was obtained
from:
https://code.google.com/p/googlefontdirectory/source/browse/ofl/inconsolata/

打开链接发现内容不在了,没有找到制作字体的工具。
因此如果要使用更大字号的字体,就需要自己想办法制作字体,这里我从stackoverflow找到个
可以自动生成的python脚本,试了生成的字体可以使用。

'auto generate font png'
from PIL import Image, ImageDraw, ImageFont
import os
def draw_png(name, font_size = 40):font_reg  = ImageFont.truetype(name + '-Regular' + '.ttf', font_size)font_bold = ImageFont.truetype(name + '-Bold' + '.ttf', font_size)text=r''' !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'''text_width, text_height = font_bold.getsize(text)max_w = 0max_h = 0for c in text:w, h = font_bold.getsize(c)if w > max_w:max_w = wif h > max_h:max_h = hprint max_w, max_himage = Image.new(mode='L', size=(max_w*96, max_h*2))draw_table = ImageDraw.Draw(im=image)i = 0for c in text:text_width, text_height = font_bold.getsize(c)print c , text_width, text_heightdraw_table.text(xy=(max_w*i, 0), text=c, fill='#ffffff', font=font_reg, anchor="mm", align="center")draw_table.text(xy=(max_w*i, max_h), text=c, fill='#ffffff', font=font_bold, anchor="mm",align="center")i = i + 1image.show()image.save( name + '.png', 'PNG')image.close()if __name__ == "__main__":print('running:')try:draw_png('Roboto',100)except Exception as e:print( ' ERR: ', e)

字体文件直接在aosp源码目录查找find ./ -name *.ttf |grep Roboto

参考:

  • https://blog.csdn.net/lmpt90/article/details/103390395
  • https://stackoverflow.com/questions/65180151/how-to-generate-a-font-image-used-in-android-power-off-charging-animation

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/44368.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

忘记海康网络摄像机IP

海康网络摄像机的使用&#xff1a; 海康网络摄像机的使用 解决电脑无法通过网线直连海康摄像机的问题 使用vlc显示海康网络摄像机的视频 忘记海康网络摄像机IP 一、引言 如果忘记了海康网络摄像机的IP&#xff0c;可以通过下载海康的设备网络搜索软件“SADP”解决。 二…

【CSS3】04-标准流 + 浮动 + flex布局

本文介绍浮动与flex布局。 目录 1. 标准流 2. 浮动 2.1 基本使用 特点 脱标 2.2 清除浮动 2.2.1 额外标签法 2.2.2 单伪元素法 2.2.3 双伪元素法(推荐) 2.2.4 overflow(最简单) 3. flex布局 3.1 组成 3.2 主轴与侧轴对齐方式 3.2.1 主轴 3.2.2 侧轴 3.3 修改主…

百度自动驾驶:我的学习笔记

自动驾驶新人之旅(9.0版) 第一课&#xff1a;初识自动驾驶技术 1. 自动驾驶技术概述 2. 自动驾驶人才需求与挑战 3. 如何使用Apollo学习自动驾驶[上机学习] 4. 如何使用Apollo学习自动驾驶[上车学习] 第二课&#xff1a;入门自动驾驶技术 1. Apollo车云研发流程 2. Lin…

并发编程之FutureTask.get()阻塞陷阱:深度解析线程池CPU飚高问题排查与解决方案

FutureTask.get方法阻塞陷阱&#xff1a;深度解析线程池CPU飚高问题排查与解决方法 FutureTask.get()方法阻塞陷阱&#xff1a;深度解析线程池CPU飚高问题排查与解决方法1、情景复现1.1 线程池工作原理1.2 业务场景模拟1.3 运行结果1.4 发现问题&#xff1a;线程池没有被关闭1.…

记录vite引入sass预编译报错error during build: [vite:css] [sass] Undefined variable.问题

vite.config.ts resolve: {alias: {: path.resolve(__dirname, src),},},css: {// css预处理器preprocessorOptions: {scss: {additionalData: use "/assets/styles/block.scss" as *;,}}},block.scss $colorGreen: #00ff00;index.vue :v-deep .font-size-14{colo…

代码小练习

public class Test3 {public static void main(String[] args) throws ParseException {ArrayList<Integer> listnew ArrayList<>();Scanner scnew Scanner(System.in);while (true){System.out.println("请输入一个整数");String s sc.nextLine();int…

百人会上的蔚小理与「来的刚刚好」的雷军

这就是2025百人会上的蔚小理&#xff0c;努力的李斌、宣扬飞行汽车的何小鹏与大讲开源的李想。那么小米汽车的模式是什么呢&#xff1f;站在蔚小理的肩上。 这就是2025百人会上的蔚小理&#xff0c;努力的李斌、宣扬飞行汽车的何小鹏与大讲开源的李想。那么小米汽车的模式是什么…

日程公布| 第八届地球空间大数据与云计算前沿大会与集中学习(3号通知)

日程公布| 第八届地球空间大数据与云计算前沿大会与集中学习&#xff08;3号通知&#xff09; 日程公布| 第八届地球空间大数据与云计算前沿大会与集中学习&#xff08;3号通知&#xff09;

<em>赚</em><em>钱</em><em>彩</em><em>票</em><em>软</em><em>件</em>

&#xff1c;em&#xff1e;赚&#xff1c;/em&#xff1e;&#xff1c;em&#xff1e;钱&#xff1c;/em&#xff1e;&#xff1c;em&#xff1e;彩&#xff1c;/em&#xff1e;&#xff1c;em&#xff1e;票&#xff1c;/em&#xff1e;&#xff1c;em&#xff1e;软&#xf…

随机2级域名引导页HTML源码

源码介绍 随机2级域名引导页HTML源码,每次点进去都随机一个域名前缀。 修改跳转域名在 350 行代码&#xff0c;源码由HTMLCSSJS组成&#xff0c;记事本打开源码文件可以进行内容文字之类的修改&#xff0c;双击html文件可以本地运行 效果预览 源码免费获取 随机2级域名引导页…

入栈操作-出栈操作

入栈操作 其 入栈操作 汇编代码流程解析如下&#xff1a; 出栈操作 其 出栈操作 汇编代码流程解析如下&#xff1a;

B3637 最长上升子序列

题目链接&#xff1a; 代码如下&#xff1a; #include<bits/stdc.h> #define int long long using namespace std; const int N 5050;int n; int arr[N]; int dp[N]; //dp数组signed main(){cin >> n;for(int i 1; i < n; i) cin >> arr[i];for(int i…

vscode通过root远程连接wsl

参考&#xff1a;vscode远程wsl时默认用root登录_vscode wsl root-CSDN博客

硬件基础--14_电功率

电功率 电功率:指电流在单位时间内做的功(表示用电器消耗电能快慢的一个物理量)。 单位:瓦特(W)&#xff0c;简称瓦。 公式:PUI(U为电压&#xff0c;单位为V&#xff0c;i为电流&#xff0c;单位为A&#xff0c;P为电功率&#xff0c;单位为W)。 单位换算:进位为1000&#xff…

【云服务器 | 下载 FFmpeg】云服务器上下载 ffmpeg + 配置

文章目录 FFmpeg 下载报错&#xff1a;已加载插件&#xff1a;fastestmirror1. 压缩包上传至服务器2. 解压3. 配置4. 添加FFmpeg到环境变量5. FFmpeg的配置5.1 安装 NASM5.2 安装x264 总结 可以看该博客&#xff0c;跟着这个步骤来的&#xff1a;https://blog.csdn.net/Aarstg/…

逆向--ARM64汇编

一、查看寄存器值 bl指令&#xff08;函数调用 bl的时候ret这个才有效&#xff09; 二、 bl 和lr 配合使用才达到函数调用的作用

【wow-rag系列】 task05 Ollama+llamaIndex+流式部署页面

文章目录 1.构建问答引擎2.构建基于FastAPI的后台3.构建流式输出的前端 1.构建问答引擎 新建一个engine.py文件 import os from llama_index.core.node_parser import SentenceSplitter# --------------------- # step 1.设定key、模型url、推理模型名称以及embedding模型名称 …

瑞芯微RKRGA(librga)Buffer API 分析

一、Buffer API 简介 在瑞芯微官方的 librga 库的手册中&#xff0c;有两组配置 buffer 的API&#xff1a; importbuffer 方式&#xff1a; importbuffer_virtualaddr importbuffer_physicaladdr importbuffer_fd wrapbuffer 方式&#xff1a; wrapbuffer_virtualaddr wrapb…

pycharm虚拟环境项目转移后配置解释器

添加解析器提示&#xff1a;无效的 Python SDK 解决方法 在到电脑安装python解析器&#xff0c;复制&#xff1a;python.exe和pythonw.exe 项目虚拟环境venv/Scripts Python解释器添加 项目现有虚拟环境&#xff0c;就可以正常使用

【智能体系统AgentOS】核心九:MCP工具

MCP&#xff08;Master Control Program&#xff09;是计算机控制系统中的核心部分&#xff0c;负责协调和管理整个系统的功能模块。不同的MCP可能会根据具体的应用场景有所不同&#xff0c;但通常有以下几类功能模块&#xff1a; 1. 输入输出&#xff08;I/O&#xff09;模块…