Recovery模式指的是一种可以对安卓机内部的数据或系统进行修改的模式(类似于windows PE或DOS)。在这个模式下我们可以刷入新的Android系统,或者对已有的系统进行备份或升级,也可以在此模式下恢复出厂设置。系统进入recovery模式后会装载recovery分区,该分区包含recovery.img(与boot.img类似,也包含了标准的内核和根文件系统).进入该模式后主要就是运行了recovery服务(/sbin/recovery)。
在Bootloader开始如果没有组合键按下,就从MISC分区读取BCB块的command字段(在主系统时已经将“boot-recovery”写入)。然后就以Recovery模式开始启动。与正常启动不同的是Recovery模式下加载的镜像是recovery.img。这个镜像同boot.img类似,也包含了标准的内核和根文件系统。其后就与正常的系统启动类似,也是启动内核,然后启动文件系统。在进入文件系统后会执行/init,init的配置文件在bootable/recovery/etc/init.rc。这个文件的主要作用就是:
这里最重要的当然就是启动recovery服务了。
重启到recovery模式的流程图如下:
在Android源码环境中,recovery的源码主要在bootable/recovery文件夹下,另外在device目录下,会根据各个设备定制自己的接口以及UI界面。
在bootable/recovery目录下,主要的源文件有:
LOCAL_SRC_FILES := \
adb_install.cpp \ //设置usb驱动,升级系统
asn1_decoder.cpp \ //解码asn1格式
device.cpp \ //recovery的头部显示和列表项,和通过make_device方法实现一个device设备
fuse_sdcard_provider.cpp \ //加载升级文件升级
recovery.cpp \ //会最先执行recovery.cpp中的main方法,及清除data等方法
roots.cpp \ //进行进行分区挂载操作
rotate_logs.cpp \ //mstar添加的文件
screen_ui.cpp \ //界面的绘制文件,初始化UI等
ui.cpp \ //初始化输入设备,如初始化按键,背光等
verifier.cpp \ //签名验证的功能实现方法
wear_ui.cpp \ // 继承于ScreenRecoveryUI的UI
wear_touch.cpp \ //界面的触摸事件响应
该部分代码在编译后,会统一输出到 out/recovery/root/目录;
Recovery的工作需要整个软件平台的配合,从通信架构上来看,主要有三个部分。
在Recovery服务中上述的三个实体之间的通信是必不可少的,他们相互之间又有以下两个通信接口。
Recovery通过/cache/recovery/目录下的三个文件与mian system通信。具体如下
/cache/recovery/command:这个文件保存着Main system传给Recovery的命令行,每一行就是一条命令,下表给出一些常用的命令及其含义:
命令 | 取值 | 含义 |
---|---|---|
send_intent | 字符串 | Recovery结束后将字符串写到这里, 然后写入/cache/recovery/intent,比如升级结果 |
update_package | 路径 | 安装OTA升级包的路径 |
wipe_data | 无 | 擦除userdata以及cache,然后重启 |
wipe_cache | 无 | 擦除cache,然后重启 |
set_encrypted_filesystem | on | off |
just_exit | 无 | 退出和重启 |
/cache/recovery/last_log:Recovery模式在工作中的log打印。在recovery服务运行过程中,stdout以及stderr会重定位到/tmp/recovery.log在recovery退出之前会将其转存到/cache/recovery/last_log中,供查看。
/cache/recovery/intent:Recovery传递给Main system的信息。列如反馈升级是否成功。
BCB是bootloader与Recovery的通信接口,也是Bootloader与Main system之间的通信接口。存储在flash中的MISC分区,占用三个page,其本身就是一个结构体,具体成员以及各成员含义如下:
struct bootloader_message{
char command[32];
char status[32];
char recovery[1024];
};
“recovery\n
<recovery command>\n
<recovery command>”
该文件存储的就是一个字符串,必须以recovery\n开头,否则这个字段的所有内容域会被忽略。“recovery\n”
之后的部分,是/cache/recovery/command
支持的命令。可以将其理解为Recovery操作过程中对命令操作的备份。Recovery对其操作的过程为:先读取BCB的recovery字段然后读取/cache/recovery /command
,然后将二者重新写回BCB的recovery字段,这样在进入Main system之前,确保操作被执行。在操作之后进入Main system之前,Recovery又会清空BCB的command域和recovery域,这样确保重启后不再进入Recovery模式。
注意!这里比较容易弄混淆的点:
BCB中的command和/cache/recovery/command的内容不等价,且不同类型。
在进入文件系统后会执行bootable/recovery/etc/init.rc,在init.rc中下面代码可知,进入recovery模式后会执行sbin/recovery,此文件是bootable/recovery.cpp生成的(查看Android.mk可知),所以recovery.cpp是recovery模式的入口。
service recovery /sbin/recovery
seclabel u:r:recovery:s0
因为recovery.cpp的main函数太长了,这里分块分析recovery的主要源码,其实在main函数中主要做了下面几件事情:
recovery.cpp的main方法执行的流程图大概如下:
在recovery的main方法中首先判断命令行参数是否为–adbd,如果有则执行minadbd_main函数,这样是为了方便使用adb sideload命令,如果参数为-adbd的话,那么它会变成精简版adbd,只支持sideload命令。
if (argc == 2 && strcmp(argv[1], "--adbd") == 0) {
minadbd_main();
return 0;
}
重定向标准输出和标准出错log到/tmp/recovery.log这个文件里,这个文件是临时log文件,在recovery模式finish的时候会将这个文件里面的log保存到/cache/recovery/last_log中。为了方便调试,可以将临时log重定位到控制台输出,修改参数:static const char ``*TEMPORARY_LOG_FILE = ``"/dev/console"``;
redirect_stdio(TEMPORARY_LOG_FILE);
之后会调用roots.cpp文件中的load_volume_table()
方法来初始化并装载recovery的分区表到fstab结构体中,load_volume_table()
方法如下:
roots.cpp
void load_volume_table()
{
int i;
int ret;
//加载分区表到fstab,具体就是去加载/etc/recovery.fstab这个文件,是Vold进程中的函数
fstab = fs_mgr_read_fstab_default();
if (!fstab) {
LOG(ERROR) << "failed to read default fstab";
return;
}
//将对应的信息加入到一条链表中
ret = fs_mgr_add_entry(fstab, "/tmp", "ramdisk", "ramdisk");
//如果load到的分区表为空,则做释放操作
if (ret < 0 ) {
LOG(ERROR) << "failed to add /tmp entry to fstab";
fs_mgr_free_fstab(fstab);
fstab = NULL;
return;
}
//打印分区表信息,这类信息在recovery启动的时候在log中可以看到,具体形式如下:
//编号| 挂载节点| 文件系统类型| 块设备| 长度
printf("recovery filesystem table\n");
printf("=========================\n");
for (i = 0; i < fstab->num_entries; ++i) {
Volume* v = &fstab->recs[i];
printf(" %d %s %s %s %lld\n", i, v->mount_point, v->fs_type,
v->blk_device, v->length);
}
printf("\n");
}
上面提到的Vold进程是在kernel初始化的时候启动的,所有的热插拔设备都是通过Vold 进程挂载的,Vold的入口是/system/vold/main.cpp文件的main函数,fs_mgr_read_fstab_default()
方法就是去解析/etc/recovery.fstab这个文件,上面具体log如下:
0 /vendor ext4 /dev/block/platform/mstar_mci.0/by-name/vendor 0
[ 12.959471] 1 /system ext4 /dev/block/platform/mstar_mci.0/by-name/system 0
[ 12.959476] 2 /system ext4 /dev/block/platform/mstar_mci.0/by-name/system 0
[ 12.959491] 3 /data ext4 /dev/block/platform/mstar_mci.0/by-name/userdata 0
[ 12.959496] 4 /cache ext4 /dev/block/platform/mstar_mci.0/by-name/cache 0
[ 12.959501] 5 /vendor ext4 /dev/block/platform/mstar_mci.0/by-name/vendor 0
[ 12.959506] 6 /tvservice ext4 /dev/block/platform/mstar_mci.0/by-name/tvservice 0
[ 12.959511] 7 /tvconfig ext4 /dev/block/platform/mstar_mci.0/by-name/tvconfig 0
[ 12.959517] 8 /tvdatabase ext4 /dev/block/platform/mstar_mci.0/by-name/tvdatabase 0
[ 12.959522] 9 /tvcustomer ext4 /dev/block/platform/mstar_mci.0/by-name/tvcustomer 0
[ 12.959527] 10 /tvcertificate ext4 /dev/block/platform/mstar_mci.0/by-name/tvcertificate 0
[ 12.959532] 11 /boot1 emmc /dev/block/mmcblk0boot0 0
[ 12.959537] 12 /boot2 emmc /dev/block/mmcblk0boot1 0
[ 12.959542] 13 /MBOOT emmc /dev/block/platform/mstar_mci.0/by-name/MBOOT 0
[ 12.959547] 14 /MPOOL emmc /dev/block/platform/mstar_mci.0/by-name/MPOOL 0
[ 12.959552] 15 /misc emmc /dev/block/platform/mstar_mci.0/by-name/misc 0
[ 12.959556] 16 /recovery emmc /dev/block/platform/mstar_mci.0/by-name/recovery 0
[ 12.959561] 17 /boot emmc /dev/block/platform/mstar_mci.0/by-name/boot 0
[ 12.959566] 18 /tee emmc /dev/block/platform/mstar_mci.0/by-name/tee 0
[ 12.959571] 19 /RTPM emmc /dev/block/platform/mstar_mci.0/by-name/RTPM 0
[ 12.959576] 20 /dtb emmc /dev/block/platform/mstar_mci.0/by-name/dtb 0
[ 12.959581] 21 /optee emmc /dev/block/platform/mstar_mci.0/by-name/optee 0
[ 12.959586] 22 /armfw emmc /dev/block/platform/mstar_mci.0/by-name/armfw 0
[ 12.959591] 23 auto auto /devices/platform/mstar_fcie* 0
[ 12.959595] 24 auto auto /devices/platform/mstar_sdio* 0
[ 12.959600] 25 auto auto /devices/Mstar-ehci* 0
[ 12.959605] 26 auto auto /devices/Mstar-xhci* 0
[ 12.959610] 27 /tmp ramdisk ramdisk 0
挂载完相应的分区以后,就需要获取命令参数,因为只有挂载了对应的分区,才能访问到记录操作命令的/cache/recovery/command这个文件及BCB块,如果分区都没找到,那么当然就找不到分区上的文件,挂载分区这个步骤是至关重要的。
//从上面建立的分区表信息中读取是否有cache分区,因为log等重要信息都存在cache分区里
has_cache = volume_for_path(CACHE_ROOT) != nullptr;
// MStar Android Patch Begin
if(has_cache){
//mstar添加的确定是否有cache分区的方法
ensure_path_mounted(CACHE_ROOT);
}
// MStar Android Patch End
在main方法中通过get_args方法获取启动参数。
//从传入的参数或/cache/recovery/command文件中得到相应的命令
std::vector<std::string> args = get_args(argc, argv);
recovery和bootloader要通过/misc才能相互通信,对应的信息数据结构体为bootloader_message;get_args(argc,argv)方法如下:
struct bootloader_message{
char command[32];//bootloader 启动时读取改数据,决定是否进入recovery模式
char status[32];//由bootloader进行更新,标识升级的结果;
char recovery[768];//recovery要执行的命令,recovery从中读取信息;
char stage[32]; // 恢复字段,它仅用于存储恢复命令行
char reserved[1148]; // 保留字段
};
static std::vector<std::string> get_args(const int argc, char** const argv) {
CHECK_GT(argc, 0);
bootloader_message boot = {
};//参数结构体
std::string err;
if (!read_bootloader_message(&boot, &err)) {
// 从BCB中获取参数,这里有可能是为空的情况。
LOG(ERROR) << err;
// If fails, leave a zeroed bootloader_message.
boot = {
};
}
...
// 将启动recovery时的参数放入args,这里至少有一个/sbin/recovery元素
std::vector<std::string> args(argv, argv + argc);
// 去解析recovery字段的值,然后写入到args中
if (args.size() == 1) {
boot.recovery[sizeof(boot.recovery) - 1] = '\0'; // Ensure termination
std::string boot_recovery(boot.recovery);
std::vector<std::string> tokens = android::base::Split(boot_recovery, "\n");
if (!tokens.empty() && tokens[0] == "recovery") {
for (auto it = tokens.begin() + 1; it != tokens.end(); it++) {
// Skip empty and '\0'-filled tokens.
if (!it->empty() && (*it)[0] != '\0') args.push_back(std::move(*it));
}
LOG(INFO) << "Got " << args.size() << " arguments from boot message";
} else if (boot.recovery[0] != 0) {
LOG(ERROR) << "Bad boot message: \"" << boot_recovery << "\"";
}
}
// 如果上述情况为空,则从/cache/recovery/command获取参数,其中COMMAND_FILE=/cache/recovery/command
if (args.size() == 1 && has_cache) {
std::string content;
if (ensure_path_mounted(COMMAND_FILE) == 0 &&
android::base::ReadFileToString(COMMAND_FILE, &content)) {
std::vector<std::string> tokens = android::base::Split(content, "\n");
// All the arguments in COMMAND_FILE are needed (unlike the BCB message,
// COMMAND_FILE doesn't use filename as the first argument).
for (auto it = tokens.begin(); it != tokens.end(); it++) {
// Skip empty and '\0'-filled tokens.
if (!it->empty() && (*it)[0] != '\0') args.push_back(std::move(*it));
}
LOG(INFO) << "Got " << args.size() << " arguments from " << COMMAND_FILE;
}
}
//将启动参数写入到BCB块的recovery字段中
std::vector<std::string> options(args.cbegin() + 1, args.cend());
if (!update_bootloader_message(options, &err)) {
LOG(ERROR) << "Failed to set BCB message: " << err;
}
return args;
}
get_args()函数的主要作用是建立recovery的启动参数,如果系统启动recovery时已经传递了启动参数,那么这个函数只是把启动参数的内容复制到函数的参数boot对象中,否则函数会首先从/misc分区中获取命令字符串来构建启动参数。如果/misc分区下没有内容,则尝试打开/cache/recovery/command文件并读取文件的内容来建立启动参数。从这个函数我们可以看到,更新系统最简单的方式是把更新命令写到/cache/recovery/command文件中。
get_args()函数的结尾调用了update_bootloader_message()函数,函数的作用是把启动参数的信息又保存到了/misc分区的BCB的recovery字段,以及给command字段添加boot-recovery命令。这样做的目的是防止升级过程中发生崩溃,这样重启后仍然可以从/misc分区中读取更新的命令,继续进行更新操作。这也是为什么get_args()函数要从几个地方读取启动参数的原因。
之后通过while循环解析获取到的参数,并把对应的功能设置为true或者给相应的变量赋值获取到对应的命令,后面会根据变量来执行对应的操作。
while ((arg = getopt_long(args_to_parse.size(), args_to_parse.data(), "", OPTIONS,
&option_index)) != -1) {
switch (arg) {
case 'n': android::base::ParseInt(optarg, &retry_count, 0); break;
case 'u': update_package = optarg; break;
case 'w': should_wipe_data = true; break;
case 'c': should_wipe_cache = true; break;
case 't': show_text = true; break;
case 's': sideload = true; break;
case 'a': sideload = true; sideload_auto_reboot = true; break;
case 'x': just_exit = true; break;
case 'l': locale = optarg; break;
case 'p': shutdown_after = true; break;
case 'r': reason = optarg; break;
case 'e': security_update = true; break;
case 0: {
std::string option = OPTIONS[option_index].name;
if (option == "wipe_ab") {
should_wipe_ab = true;
} else if (option == "wipe_package_size") {
android::base::ParseUint(optarg, &wipe_package_size);
} else if (option == "prompt_and_wipe_data") {
should_prompt_and_wipe_data = true;
}
break;
}
// MStar Android Patch Begin
case 'd': dev_uuid = optarg; break;
case 'b': dev_label= optarg; break;
// MStar Android Patch Begin
case '?':
LOG(ERROR) << "Invalid command argument";
continue;
}
}
这个方法就是去判断/cache/recovery/last_locale文件是否存在,如果存在就读取里面的值,获取到的内容关系到显示那个国家的语言,如果没有获取到locale就使用默认的语言,848中的默认语言是英语。
if (locale.empty()) {
if (has_cache) {
locale = load_locale_from_cache();
}
if (locale.empty()) {
locale = DEFAULT_LOCALE;
}
}
加载UI界面的流程大概有下面几步:
Recovery中显示UI界面的framebuffer使用的是minui库,该库在网上也能查到相应的方法说明,下面会有详细介绍。
这里主要做了这几件事情:
Device* device = make_device();//新建一个Device设备
if (android::base::GetBoolProperty("ro.boot.quiescent", false)) {
//如果是静态UI模式则进入这里
printf("Quiescent recovery mode.\n");
ui = new StubRecoveryUI();
} else {
ui = device->GetUI();//获取到ScreenRecoveryUI
if (!ui->Init(locale)) {
//调用ScreenRecoveryUI::init方法
printf("Failed to initialize UI, use stub UI instead.\n");
ui = new StubRecoveryUI();
}
}
// Set background string to "installing security update" for security update,
// otherwise set it to "installing system update".
// 设置背景字符串为“正在安装安全跟新”或者“正在安装系统更新”,这个在后面会根据状态更新的
ui->SetSystemUpdateText(security_update);
int st_cur, st_max;
if (!stage.empty() && sscanf(stage.c_str(), "%d/%d", &st_cur, &st_max) == 2) {
ui->SetStage(st_cur, st_max);
}
ui->SetBackground(RecoveryUI::NONE);//设置背景,这里没有背景
if (show_text) ui->ShowText(true); // 判断界面上是否能显示字符
//设置selinux权限
sehandle = selinux_android_file_context_handle();
selinux_android_set_sehandle(sehandle);
if (!sehandle) {
ui->Print("Warning: No file_contexts\n");
}
//虚函数,什么都没有做
device->StartRecovery();
printf("Command:");
for (const auto& arg : args) {
printf(" \"%s\"", arg.c_str());
}
printf("\n\n");
这里获取到的UI是ScreenRecoveryUI,所以调用的是ScreenRecoveryUI::Init,代码在screen_ui.cpp中,ScreenRecoveryUI是继承于RecoveryUI的,这个方法里面会去初始化RecoveryUI和minui的图形显示,之后就是加载图片资源为surface对象,并创建一个子线程用来更新升级的进度条。
bool ScreenRecoveryUI::Init(const std::string& locale) {
RecoveryUI::Init(locale);//调用RecoveryUI的Init方法,这个方法里面会设置语言,及初始化输入设备和事件,并创建一个子线程去监听输入事件
if (!InitTextParams()) {
//这里初始化文本参数,以及调用minui中的Init方法初始化图形显示,主要是打开设备、分配内存、初始化一些参数 ;
return false;
}
//设置屏幕密度
density_ = static_cast<float>(android::base::GetIntProperty("ro.sf.lcd_density", 160)) / 160.f;
// Are we portrait or landscape?
layout_ = (gr_fb_width() > gr_fb_height()) ? LANDSCAPE : PORTRAIT;
// Are we the large variant of our base layout?
if (gr_fb_height() > PixelsFromDp(800)) ++layout_;
text_ = Alloc2d(text_rows_, text_cols_ + 1);
file_viewer_text_ = Alloc2d(text_rows_, text_cols_ + 1);
menu_ = Alloc2d(text_rows_, text_cols_ + 1);
text_col_ = text_row_ = 0;
text_top_ = 1;
//LoadBitmap()方法将png生成surface, 每个png图片对应一个surface
LoadBitmap("icon_error", &error_icon);
LoadBitmap("progress_empty", &progressBarEmpty);
LoadBitmap("progress_fill", &progressBarFill);
LoadBitmap("stage_empty", &stageMarkerEmpty);
LoadBitmap("stage_fill", &stageMarkerFill);
// Background text for "installing_update" could be "installing update"
// or "installing security update". It will be set after UI init according
// to commands in BCB.
installing_text = nullptr;
LoadLocalizedBitmap("erasing_text", &erasing_text);
//LoadLocalizedBitmap()将相应文字所在的图片中的text信息根据当前的locale提取出来,生成对应的surface
LoadLocalizedBitmap("no_command_text", &no_command_text);
LoadLocalizedBitmap("error_text", &error_text);
LoadAnimation();//这里是去加载升级动画的那个圆动画
//创建一个线程,在该循环中不停地检测progressBarType来决定是不是要更新进度条
pthread_create(&progress_thread_, nullptr, ProgressThreadStartRoutine, this);
return true;
}
这个方法里面会设置语言,及初始化输入设备和事件,并创建一个子线程去监听输入事件,这里就不详细介绍了;
bool RecoveryUI::Init(const std::string& locale) {
// Set up the locale info.
SetLocale(locale);//设置文本语言
//初始化输入设备
ev_init(std::bind(&RecoveryUI::OnInputEvent, this, std::placeholders::_1, std::placeholders::_2));
ev_iterate_available_keys(std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1));
if (!InitScreensaver()) {
LOG(INFO) << "Screensaver disabled";
}
//创建一个线程来监听输入事件
pthread_create(&input_thread_, nullptr, InputThreadLoop, nullptr);
return true;
}
InitTextParams方法中先是调用了gr_init方法来初始化minui库,其实就是调用的minui\graphics.cpp文件的Init方法初始化画笔,然后调用minui\graphics_fbdev.cpp的Init方法初始化图形显示,其主要做用就是打开设备、分配内存、初始化一些参数 ,之后再初始化了一些文本显示参数;
screen_ui.cpp文件:
bool ScreenRecoveryUI::InitTextParams() {
if (gr_init() < 0) {
//这里就是调用minui\graphics.cpp中的Init方法
return false;
}
gr_font_size(gr_sys_font(), &char_width_, &char_height_);//初始化字体大小等
text_rows_ = gr_fb_height() / char_height_;
text_cols_ = gr_fb_width() / char_width_;
return true;
}
-----------------------------------------------------------------------------------------
minui\graphics_fbdev.cpp文件:
GRSurface* MinuiBackendFbdev::Init() {
int fd = open("/dev/graphics/fb0", O_RDWR);//打开/dev/graphics/fb0设备,并要读写权限
if (fd == -1) {
//打开失败
perror("cannot open fb0");
return nullptr;
}
fb_fix_screeninfo fi;
if (ioctl(fd, FBIOGET_FSCREENINFO, &fi) < 0) {
//获取FrameScreen信息到fi
perror("failed to get fb0 info");
close(fd);
return nullptr;
}
if (ioctl(fd, FBIOGET_VSCREENINFO, &vi) < 0) {
//获取VideoScreen信息到vi
perror("failed to get fb0 info");
close(fd);
return nullptr;
}
// We print this out for informational purposes only, but
// throughout we assume that the framebuffer device uses an RGBX
// pixel format. This is the case for every development device I
// have access to. For some of those devices (eg, hammerhead aka
// Nexus 5), FBIOGET_VSCREENINFO *reports* that it wants a
// different format (XBGR) but actually produces the correct
// results on the display when you write RGBX.
//
// If you have a device that actually *needs* another pixel format
// (ie, BGRX, or 565), patches welcome...
printf(
"fb0 reports (possibly inaccurate):\n"
" vi.bits_per_pixel = %d\n"
" vi.red.offset = %3d .length = %3d\n"
" vi.green.offset = %3d .length = %3d\n"
" vi.blue.offset = %3d .length = %3d\n",
vi.bits_per_pixel, vi.red.offset, vi.red.length, vi.green.offset, vi.green.length,
vi.blue.offset, vi.blue.length);
//映射FrameBuffer到bits
void* bits = mmap(0, fi.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (bits == MAP_FAILED) {
perror("failed to mmap framebuffer");
close(fd);
return nullptr;
}
//初始化bits
memset(bits, 0, fi.smem_len);
//设置FrameBuffer的参数
gr_framebuffer[0].width = vi.xres;
gr_framebuffer[0].height = vi.yres;
gr_framebuffer[0].row_bytes = fi.line_length;
gr_framebuffer[0].pixel_bytes = vi.bits_per_pixel / 8;
gr_framebuffer[0].data = static_cast<uint8_t*>(bits);
//初始化gr_framebuffer[0].data
memset(gr_framebuffer[0].data, 0, gr_framebuffer[0].height * gr_framebuffer[0].row_bytes);
/* check if we can use double buffering */
//检查是否可以用双缓冲
if (vi.yres * fi.line_length * 2 <= fi.smem_len) {
double_buffered = true;
memcpy(gr_framebuffer + 1, gr_framebuffer, sizeof(GRSurface));
gr_framebuffer[1].data =
gr_framebuffer[0].data + gr_framebuffer[0].height * gr_framebuffer[0].row_bytes;
gr_draw = gr_framebuffer + 1;
} else {
double_buffered = false;
// Without double-buffering, we allocate RAM for a buffer to
// draw in, and then "flipping" the buffer consists of a
// memcpy from the buffer we allocated to the framebuffer.
//初始化GRSurface,将帧缓存中的数据复制到gr_draw中
gr_draw = static_cast<GRSurface*>(malloc(sizeof(GRSurface)));
memcpy(gr_draw, gr_framebuffer, sizeof(GRSurface));
gr_draw->data = static_cast<unsigned char*>(malloc(gr_draw->height * gr_draw->row_bytes));
if (!gr_draw->data) {
perror("failed to allocate in-memory surface");
return nullptr;
}
}
//初始化gr_draw的data数据
memset(gr_draw->data, 0, gr_draw->height * gr_draw->row_bytes);
fb_fd = fd;
//设置显示帧缓存区
SetDisplayedFramebuffer(0);
printf("framebuffer: %d (%d x %d)\n", fb_fd, gr_draw->width, gr_draw->height);
Blank(true);
Blank(false);
return gr_draw;
}
int gr_init(void); /* 初始化图形显示,主要是打开设备、分配内存、初始化一些参数 */
void gr_exit(void); /* 注销图形显示,关闭设备并释放内存 */
int gr_fb_width(void); /* 获取屏幕的宽度 */
int gr_fb_height(void); /* 获取屏幕的高度 */
gr_pixel *gr_fb_data(void); /* 获取显示数据缓存的地址 */
void gr_flip(void); /* 刷新显示内容 */
void gr_fb_blank(bool blank); /* 清屏 */
void gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a); /* 设置字体颜色 */
void gr_fill(int x, int y, int w, int h); /* 填充矩形区域,参数分别代表起始坐标、矩形区域大小 */
int gr_text(int x, int y, const char *s); /* 显示字符串 */
int gr_measure(const char *s); /* 获取字符串在默认字库中占用的像素长度 */
void gr_font_size(int *x, int *y); /* 获取当前字库一个字符所占的长宽 */
void gr_blit(gr_surface source, int sx, int sy, int w, int h, int dx, int dy); /* 填充由source指定的图片 */
unsigned int gr_get_width(gr_surface surface); /* 获取图片宽度 */
unsigned int gr_get_height(gr_surface surface); /* 获取图片高度 */
/* 根据图片创建显示资源数据,name为图片在mk文件指定的相对路径 */
int res_create_surface(const char* name, gr_surface* pSurface);
void res_free_surface(gr_surface surface); /* 释放资源数据 */
再加载完UI模式之后,如果在开始读取的控制参数为空的话就会执行到prompt_and_wait方法,prompt_and_wait()函数是个死循环,开始显示recovery选项 并处理用户通过按键或者触摸屏的选项,如Reboot system等。
Device::BuiltinAction after = shutdown_after ? Device::SHUTDOWN : Device::REBOOT;
//status为none,代表没有命令,会执行此if语句
if ((status != INSTALL_SUCCESS && status != INSTALL_SKIPPED && !sideload_auto_reboot) ||
ui->IsTextVisible()) {
//prompt_and_wait()函数是个死循环 开始显示recovery选项 并处理用户通过按键或者触摸屏的选项,如Reboot system等
//这里返回的temp就是操作的Action
Device::BuiltinAction temp = prompt_and_wait(device, status);
if (temp != Device::NO_ACTION) {
after = temp;
}
}
prompt_and_wait(device,status)函数具体如下:
// Returns REBOOT, SHUTDOWN, or REBOOT_BOOTLOADER. Returning NO_ACTION means to take the default,
// which is to reboot or shutdown depending on if the --shutdown_after flag was passed to recovery.
static Device::BuiltinAction prompt_and_wait(Device* device, int status) {
//一个死循环
for (;;) {
//finish_recovery()方法做的事情是,清除清除BCB中的命令,保存当前的locale语言信息到/cache,并保存log到/cache
//这里相当于启动选择菜单的初始化
finish_recovery();
// MStar Android Patch Begin
//确保有cache分区
ensure_path_mounted(CACHE_ROOT);
// MStar Android Patch End
switch (status) {
case INSTALL_SUCCESS:
case INSTALL_NONE:
ui->SetBackground(RecoveryUI::NO_COMMAND);
break;
case INSTALL_ERROR:
case INSTALL_CORRUPT:
ui->SetBackground(RecoveryUI::ERROR);
break;
}
//设置进度条为空
ui->SetProgressType(RecoveryUI::EMPTY);
//获取选择的item选项的Action
int chosen_item = get_menu_selection(nullptr, device->GetMenuItems(), false, 0, device);
// Device-specific code may take some action here. It may return one of the core actions
// handled in the switch statement below.
Device::BuiltinAction chosen_action =
(chosen_item == -1) ? Device::REBOOT : device->InvokeMenuItem(chosen_item);
bool should_wipe_cache = false;
//根据Action来执行操作
switch (chosen_action) {
case Device::NO_ACTION:
break;
case Device::REBOOT:
case Device::SHUTDOWN:
case Device::REBOOT_BOOTLOADER:
//返回item的Action
return chosen_action;
case Device::WIPE_DATA:
if (ui->IsTextVisible()) {
if (ask_to_wipe_data(device)) {
wipe_data(device);
}
} else {
wipe_data(device);
return Device::NO_ACTION;
}
break;
// MStar Android Patch Begin
case Device::APPLY_CACHE:
{
// why do unmount system?one case:from setting select local upgrade,enter recovery mode do OTA upgrade,upgrading system
// plug U disk,system partition is mountting;then use IR select "apply update from cache' in recovery mode.
// do OTA upgrade again,if dont unmount system,then when execute OTA upgrade-script to fromat system partition,it will fail.
// Associated with the mantis 0515614
ensure_path_unmounted("/system");
status = apply_from_cache(device, &should_wipe_cache);
if (status == INSTALL_SUCCESS && should_wipe_cache) {
if (!wipe_cache(false, device)) {
status = INSTALL_ERROR;
}
}
if (status != INSTALL_SUCCESS) {
ui->SetBackground(RecoveryUI::ERROR);
ui->Print("Installation aborted.\n");
copy_logs();
} else if (!ui->IsTextVisible()) {
return Device::NO_ACTION; // reboot if logs aren't visible
} else {
ui->Print("\nInstall from cache complete.\n");
}
}
break;
// MStar Android Patch End
case Device::WIPE_CACHE:
wipe_cache(ui->IsTextVisible(), device);
if (!ui->IsTextVisible()) return Device::NO_ACTION;
break;
case Device::APPLY_ADB_SIDELOAD:
case Device::APPLY_SDCARD:
{
bool adb = (chosen_action == Device::APPLY_ADB_SIDELOAD);
if (adb) {
status = apply_from_adb(ui, &should_wipe_cache, TEMPORARY_INSTALL_FILE);
} else {
//status = apply_from_sdcard(device, &should_wipe_cache);
// MStar Android Patch Begin
status = apply_from_external_stroage(device, &should_wipe_cache);
// MStar Android Patch End
}
if (status == INSTALL_SUCCESS && should_wipe_cache) {
if (!wipe_cache(false, device)) {
status = INSTALL_ERROR;
}
}
if (status != INSTALL_SUCCESS) {
ui->SetBackground(RecoveryUI::ERROR);
ui->Print("Installation aborted.\n");
copy_logs();
} else if (!ui->IsTextVisible()) {
return Device::NO_ACTION; // reboot if logs aren't visible
} else {
ui->Print("\nInstall from %s complete.\n", adb ? "ADB" : "SD card");
}
}
break;
case Device::VIEW_RECOVERY_LOGS:
choose_recovery_file(device);
break;
case Device::RUN_GRAPHICS_TEST:
run_graphics_test();
break;
case Device::MOUNT_SYSTEM:
// For a system image built with the root directory (i.e. system_root_image == "true"), we
// mount it to /system_root, and symlink /system to /system_root/system to make adb shell
// work (the symlink is created through the build system). (Bug: 22855115)
if (android::base::GetBoolProperty("ro.build.system_root_image", false)) {
if (ensure_path_mounted_at("/", "/system_root") != -1) {
ui->Print("Mounted /system.\n");
}
} else {
if (ensure_path_mounted("/system") != -1) {
ui->Print("Mounted /system.\n");
}
}
break;
}
}
}
在通过prompt_and_wait函数处理用户通过按键或者触摸屏的选项后,会返回当前选项的Action,然后赋值给after,在main函数中会通过after的值来确定怎么结束recovery模式;
if ((status != INSTALL_SUCCESS && status != INSTALL_SKIPPED && !sideload_auto_reboot) ||
ui->IsTextVisible()) {
Device::BuiltinAction temp = prompt_and_wait(device, status);//这里返回的item
if (temp != Device::NO_ACTION) {
after = temp;
}
}
// Save logs and clean up before rebooting or shutting down.
// 保存日志到/cache,清除BCB中的命令,保存locale语言信息
finish_recovery();
switch (after) {
//如果是关机则会设置ANDROID_RB_PROPERTY为shutdown
case Device::SHUTDOWN:
ui->Print("Shutting down...\n");
android::base::SetProperty(ANDROID_RB_PROPERTY, "shutdown,");
break;
//重启到bootloader
case Device::REBOOT_BOOTLOADER:
ui->Print("Rebooting to bootloader...\n");
android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,bootloader");
break;
//其他的默认重启
default:
ui->Print("Rebooting...\n");
reboot("reboot,");
break;
}
在应用层层面的ota升级包的下载、校验以及最后通过framework层接口发起安装过程这里就不详细介绍了。在这里,主要介绍进入Recovery模式后,OTA包的升级过程。
首先,在应用层下载升级包后,会调用RecoverySystem.installPackage(Context context, File packageFile)函数来发起安装过程,这个过程主要的原理就是往 /cache/recovery/command 写入ota升级命令及包存放路径,然后重启到recovery模式,升级命令大概为update_package=/mnt/sdcard/update.zip
,重启到recovery就是在BCB中的command字段写入boot-recovery
。
进入Recovery模式后OTA包升级的时序图如下:
进入recovery模式后在recovery.cpp的main函数中会通过get_args(argc,argv)方法读取控制参数,这里再4.4章节有讲到,这个方法会去读取/cacha/recovery/command文件构建启动参数,然后在while循序中会解析控制参数,OTA升级的话解析参数后会设置update_package不为空,然后再main函数中就会进入如下流程:
//update_package参数不为空的话进入
if (update_package != NULL) {
// It's not entirely true that we will modify the flash. But we want
// to log the update attempt since update_package is non-NULL.
modified_flash = true;
//这里是判断电量是否允许进行升级
if (!is_battery_ok()) {
ui->Print("battery capacity is not enough for installing package, needed is %d%%\n",
BATTERY_OK_PERCENTAGE);
// Log the error code to last_install when installation skips due to
// low battery.
log_failure_code(kLowBattery, update_package);
status = INSTALL_SKIPPED;
} else if (bootreason_in_blacklist()) {
//这里是判断是否是从需要跳过升级的意图启动的
// Skip update-on-reboot when bootreason is kernel_panic or similar
ui->Print("bootreason is in the blacklist; skip OTA installation\n");
log_failure_code(kBootreasonInBlacklist, update_package);
status = INSTALL_SKIPPED;
} else {
//ota升级流程会进入install_package方法
status = install_package(update_package, &should_wipe_cache,
TEMPORARY_INSTALL_FILE, true, retry_count);
//判断是否升级成功及是否清除了cache
if (status == INSTALL_SUCCESS && should_wipe_cache) {
wipe_cache(false, device);
}
//如果升级失败则进入
if (status != INSTALL_SUCCESS) {
ui->Print("Installation aborted.\n");
// When I/O error happens, reboot and retry installation EIO_RETRY_COUNT
// times before we abandon this OTA update.
if (status == INSTALL_RETRY && retry_count < EIO_RETRY_COUNT) {
copy_logs();
set_retry_bootloader_message(retry_count, args);
// Print retry count on screen.
ui->Print("Retry attempt %d\n", retry_count);
// Reboot and retry the update
if (!reboot("reboot,recovery")) {
ui->Print("Reboot failed\n");
} else {
while (true) {
pause();
}
}
}
// If this is an eng or userdebug build, then automatically
// turn the text display on if the script fails so the error
// message is visible.
if (is_ro_debuggable()) {
ui->ShowText(true);
}
}
}
}
从上面的流程看,会进入install_package方法。
install_package方法就是设置安装框架然后调用了really_install_package方法。之后还有一些log输出,最后返回升级的结果。
int install_package(const char* path, bool* wipe_cache, const char* install_file,
bool needs_mount, int retry_count)
{
modified_flash = true;
auto start = std::chrono::system_clock::now();//记录开始时间
int start_temperature = GetMaxValueFromThermalZone();
int max_temperature = start_temperature;
int result;
std::vector<std::string> log_buffer;
//这里设置安装框架,如果设置失败则放回error
if (setup_install_mounts() != 0) {
LOG(ERROR) << "failed to set up expected mounts for install; aborting";
result = INSTALL_ERROR;
} else {
//成功就进入really_install_package方法
result = really_install_package(path, wipe_cache, needs_mount, log_buffer, retry_count,
&max_temperature);
}
//这里是计算升级用的时间
std::chrono::duration<double> duration = std::chrono::system_clock::now() - start;
int time_total = static_cast<int>(duration.count());
//这里后面的代码都是log输出,所以省略掉
//这后面会根据升级结果将结果临时写入到
//static const char *TEMPORARY_INSTALL_FILE = "/tmp/last_install"文件中去,
//在退出recovery模式前会将结果复制到
//static const char *LAST_INSTALL_FILE = "/cache/recovery/last_install"文件中去,
//以此来判断是否升级成功
//格式如下:
//前两行必须是软件包名称和升级结果
/*std::vector log_header = {
path,
result == INSTALL_SUCCESS ? "1" : "0",
"time_total: " + std::to_string(time_total),
"retry: " + std::to_string(retry_count),
};*/
.....
return result;
}
在really_install_package中主要做了以下几件事:
1、设置UI的背景,并显示进度条,然后通过uuid获取到指定的设备,这里设置了ProgressType后,前面4.6.2章节创建的更新进度条的子线程就会开始更新进度条了。
static int really_install_package(const char *path, bool* wipe_cache, bool needs_mount,
std::vector<std::string>& log_buffer, int retry_count, int* max_temperature)
{
ui->SetBackground(RecoveryUI::INSTALLING_UPDATE);
ui->Print("Finding update package...\n");
// Give verification half the progress bar...
ui->SetProgressType(RecoveryUI::DETERMINATE);
ui->ShowProgress(VERIFICATION_PROGRESS_FRACTION, VERIFICATION_PROGRESS_TIME);
LOG(INFO) << "Update location: " << path;
// MStar Android Patch Begin
if ((0 == strncmp(path, "/mnt/", strlen("/mnt/"))) || (0 == strncmp(path, "/storage/", strlen("/storage/")))){
if (dev_uuid == NULL) {
LOG(ERROR) << "dev_uuid is %s\n" << dev_uuid;
return INSTALL_NONE;
}
// some USB devices is so slow, so we have to sleep 10s, in order to get uuid/label successfully
sleep(10);
ui->Print("confirm uuid and package path\n");
char dev_path[128] = "\0";
// get device by specified uuid and label
if (-1 == get_device_path(dev_uuid, dev_label, dev_path)) {
LOG(ERROR) << "Can't find device of uuid(#%s#) and label(#%s#)\n" << dev_uuid << dev_label;
return INSTALL_NONE;
}
if (-1 == mount_usb_device((char *)path, dev_path)){
return INSTALL_NONE;
}
}
.....
}
2、确保升级包所在的分区已经挂载
static int really_install_package(const char *path, bool* wipe_cache, bool needs_mount,
std::vector<std::string>& log_buffer, int retry_count, int* max_temperature)
{
.....
if (path && needs_mount) {
if (path[0] == '@') {
ensure_path_mounted(path+1);
} else {
ensure_path_mounted(path);
}
}
.....
}
3、获取升级包地址
static int really_install_package(const char *path, bool* wipe_cache, bool needs_mount,
std::vector<std::string>& log_buffer, int retry_count, int* max_temperature)
{
.....
MemMapping map;
if (!map.MapFile(path)) {
LOG(ERROR) << "failed to map file";
return INSTALL_CORRUPT;
}
.....
}
4、验证升级包签名
对update.zip包检查时大致会分三步:
static int really_install_package(const char *path, bool* wipe_cache, bool needs_mount,
std::vector<std::string>& log_buffer, int retry_count, int* max_temperature)
{
.....
// Verify package.
if (!verify_package(map.addr, map.length)) {
log_buffer.push_back(android::base::StringPrintf("error: %d", kZipVerificationFailure));
sysReleaseMap(&map);
return INSTALL_CORRUPT;
}
.....
}
5、打开升级包
static int really_install_package(const char *path, bool* wipe_cache, bool needs_mount,
std::vector<std::string>& log_buffer, int retry_count, int* max_temperature)
{
.....
// Try to open the package.
ZipArchiveHandle zip;
int err = OpenArchiveFromMemory(map.addr, map.length, path, &zip);
if (err != 0) {
LOG(ERROR) << "Can't open " << path << " : " << ErrorCodeString(err);
log_buffer.push_back(android::base::StringPrintf("error: %d", kZipOpenFailure));
sysReleaseMap(&map);
CloseArchive(zip);
return INSTALL_CORRUPT;
}
.....
}
6、验证软件包的兼容性,这里是去验证OTA包中的compatibility.zip文件,如果文件不存在则直接返回true
static int really_install_package(const char *path, bool* wipe_cache, bool needs_mount,
std::vector<std::string>& log_buffer, int retry_count, int* max_temperature)
{
.....
// Additionally verify the compatibility of the package.
if (!verify_package_compatibility(zip)) {
log_buffer.push_back(android::base::StringPrintf("error: %d", kPackageCompatibilityFailure));
sysReleaseMap(&map);
CloseArchive(zip);
return INSTALL_CORRUPT;
}
.....
}
7、执行升级脚本文件,开始升级,并返回升级结果
static int really_install_package(const char *path, bool* wipe_cache, bool needs_mount,
std::vector<std::string>& log_buffer, int retry_count, int* max_temperature)
{
.....
ui->SetEnableReboot(false);
int result = try_update_binary(path, zip, wipe_cache, log_buffer, retry_count, max_temperature);
ui->SetEnableReboot(true);
ui->Print("\n");
sysReleaseMap(&map);
CloseArchive(zip);
return result;
}
这里看到最终会调到try_update_binary方法,try_update_binary是真正实现对升级包进行升级的函数。
总的来说,try_update_binary主要做了以下几个操作:
static int try_update_binary(const char* path, ZipArchiveHandle zip, bool* wipe_cache,
std::vector<std::string>& log_buffer, int retry_count,
int* max_temperature) {
read_source_target_build(zip, log_buffer);//这里是去读取上一步打开的updata文件
int pipefd[2];
pipe(pipefd);
std::vector<std::string> args;
//去解析updata文件,其中包括解析update_binary存放路径、Recovery版本号、升级包存放路径等数据,然后存放在args中
int ret = update_binary_command(path, zip, retry_count, pipefd[1], &args);
if (ret) {
close(pipefd[0]);
close(pipefd[1]);
return ret;
}
//将args中的数据赋值给chr_args,方便后面子进程使用execv去调用update-binary执行升级操作
const char* chr_args[args.size() + 1];
chr_args[args.size()] = nullptr;
for (size_t i = 0; i < args.size(); i++) {
chr_args[i] = args[i].c_str();
}
pid_t pid = fork();//fork一个子进程
if (pid == -1) {
//fork失败
close(pipefd[0]);
close(pipefd[1]);
PLOG(ERROR) << "Failed to fork update binary";
return INSTALL_ERROR;
}
if (pid == 0) {
//fork成功
umask(022);
close(pipefd[0]);
//调用execv去调用update-binary执行升级操作
execv(chr_args[0], const_cast<char**>(chr_args));
// Bug: 34769056
// We shouldn't use LOG/PLOG in the forked process, since they may cause
// the child process to hang. This deadlock results from an improperly
// copied mutex in the ui functions.
fprintf(stdout, "E:Can't run %s (%s)\n", chr_args[0], strerror(errno));
_exit(EXIT_FAILURE);
}
close(pipefd[1]);
//后面就是输出log和执行相关命令的操作了
std::thread temperature_logger(log_max_temperature, max_temperature);
*wipe_cache = false;
bool retry_update = false;
char buffer[1024];
// 打开pipe管道
FILE* from_child = fdopen(pipefd[0], "r");
// 通过pipe管道进行信息交互
while (fgets(buffer, sizeof(buffer), from_child) != nullptr) {
//这里通过line.find_first_of(" \n");来获取一条命令
std::string line(buffer);
size_t space = line.find_first_of(" \n");
std::string command(line.substr(0, space));
if (command.empty()) continue;
// Get rid of the leading and trailing space and/or newline.
std::string args = space == std::string::npos ? "" : android::base::Trim(line.substr(space));
if (command == "show_progress") {
//设置进度条进度,有动画效果的,这些命令的意思后面有一个表格说明
// MStar Android Patch Begin
// check usb device is unpluged or not.
// if usb device is unpluged during installing ota upgrade package, we should send fail message to user.
if ((0 == strncmp(path, "/mnt/", strlen("/mnt/"))) || (0 == strncmp(path, "/storage/", strlen("/storage/")))){
if (-1 == check_usb_device(path)){
LOG(ERROR) << "Donot find storage equipment %s, usb device may be unpluged!\n" << path;
return INSTALL_NONE;
}
}
// MStar Android Patch End
std::vector<std::string> tokens = android::base::Split(args, " ");
double fraction;
int seconds;
if (tokens.size() == 2 && android::base::ParseDouble(tokens[0].c_str(), &fraction) &&
android::base::ParseInt(tokens[1], &seconds)) {
ui->ShowProgress(fraction * (1 - VERIFICATION_PROGRESS_FRACTION), seconds);
} else {
LOG(ERROR) << "invalid \"progress\" parameters: " << line;
}
} else if (command == "set_progress") {
//设置进度条进度
std::vector<std::string> tokens = android::base::Split(args, " ");
double fraction;
if (tokens.size() == 1 && android::base::ParseDouble(tokens[0].c_str(), &fraction)) {
ui->SetProgress(fraction);
} else {
LOG(ERROR) << "invalid \"set_progress\" parameters: " << line;
}
} else if (command == "ui_print") {
ui->PrintOnScreenOnly("%s\n", args.c_str());
fflush(stdout);
} else if (command == "wipe_cache") {
//设置清除缓存为true
*wipe_cache = true;
} else if (command == "clear_display") {
ui->SetBackground(RecoveryUI::NONE);
} else if (command == "enable_reboot") {
// packages can explicitly request that they want the user
// to be able to reboot during installation (useful for
// debugging packages that don't exit).
ui->SetEnableReboot(true);
} else if (command == "retry_update") {
retry_update = true;
} else if (command == "log") {
if (!args.empty()) {
// Save the logging request from updater and write to last_install later.
log_buffer.push_back(args);
} else {
LOG(ERROR) << "invalid \"log\" parameters: " << line;
}
} else {
LOG(ERROR) << "unknown command [" << command << "]";
}
}
fclose(from_child);
int status;
waitpid(pid, &status, 0);
finish_log_temperature.notify_one();
temperature_logger.join();
if (retry_update) {
//安装重试
return INSTALL_RETRY;
}
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
LOG(ERROR) << "Error in " << path << " (Status " << WEXITSTATUS(status) << ")";
//安装失败,返回INSTALL_ERROR
return INSTALL_ERROR;
}
//安装成功
return INSTALL_SUCCESS;
}
在update_script常用的命令如下:
命令 | 含义 |
---|---|
mount | 挂载分区 |
format | 格式化分区 |
show_progress | 设置进度条百分比 |
set_progress | 设置进度条百分比,两个都是设置百分比, 区别是前者以动画的形式,可以设置时间, 表示在多少秒内进度匀速跳转到设定的百分比; 而后者是立即跳转到设定的比例 |
package_extract_dir | 解压缩文件夹到指定目录 |
package_extract_file | 解压缩文件到指定路径 |
retouch_binaries | 更新可执行文件的修改日期到最新 |
set_perm | 设置文件的权限(类似于chmod) |
delete | 删除文件 |
write_raw_image | 写入二进制文件,像boot.img就是用这个直接写入 |
apply_patch_check | 校验patch文件 |
apply_patch_space | 校验cache分区空间,是否足够安装patch |
apply_patch | 安装patch |
sha1_check | 校验文件sha1码 |
在update-binary程序执行过程中会去调用updater\updater.cpp文件下得main函数去注册脚本中的语句处理函数,即识别脚本中命令的函数,主要有以下几类:
如RegisterInstallFunctions方法如下:
updater\install.cpp
void RegisterInstallFunctions() {
RegisterFunction("mount", MountFn);
RegisterFunction("is_mounted", IsMountedFn);
RegisterFunction("unmount", UnmountFn);
RegisterFunction("format", FormatFn);
RegisterFunction("show_progress", ShowProgressFn);
RegisterFunction("set_progress", SetProgressFn);
RegisterFunction("package_extract_dir", PackageExtractDirFn);
RegisterFunction("package_extract_file", PackageExtractFileFn);
RegisterFunction("getprop", GetPropFn);
RegisterFunction("file_getprop", FileGetPropFn);
RegisterFunction("apply_patch", ApplyPatchFn);
RegisterFunction("apply_patch_check", ApplyPatchCheckFn);
RegisterFunction("apply_patch_space", ApplyPatchSpaceFn);
RegisterFunction("wipe_block_device", WipeBlockDeviceFn);
RegisterFunction("read_file", ReadFileFn);
RegisterFunction("sha1_check", Sha1CheckFn);
RegisterFunction("write_value", WriteValueFn);
RegisterFunction("wipe_cache", WipeCacheFn);
RegisterFunction("ui_print", UIPrintFn);
RegisterFunction("run_program", RunProgramFn);
RegisterFunction("reboot_now", RebootNowFn);
RegisterFunction("get_stage", GetStageFn);
RegisterFunction("set_stage", SetStageFn);
RegisterFunction("enable_reboot", EnableRebootFn);
RegisterFunction("tune2fs", Tune2FsFn);
}
在update-binary程序执行完成之后就是回到main函数执行finish_recovery,然后重启了,这里参考5.1章节。
在前面4.6章节有介绍过,recovery的显示是通过minui库直接打开操作/dev/graphics/fb0文件直接显示framebuffer的,上面有介绍minui库的一些方法,在这里我们需要修改下面两个方法:
int gr_init(void); /* 初始化图形显示,主要是打开设备、分配内存、初始化一些参数 */
void gr_flip(void); /* 刷新显示内容 */
其实旋转Recovery界面总共只需要两步:
在加载UI模式的时候会去初始化minui库,也就是会调用gr_init然后返回一个GRSurface,为什么要旋转GRSurface呢,比如我的屏幕内核里面读出来的是1024X768的大小,在gr_init中调换了高和宽返回的GRSurface都是768X1024的大小了,其他文件都是从这里获取GRSurface的,所以生成的画面都是在768X1024的GRSurface中的。
gr_init就是执行的minui\graphics_fbdev.cpp的init方法,我们添加一个旋转GRSurface的方法,这里可以把GRSurface想象成画布,这个方法全部的代码参考4.6.4章节。
GRSurface* MinuiBackendFbdev::Init() {
int fd = open("/dev/graphics/fb0", O_RDWR);
if (fd == -1) {
perror("cannot open fb0");
return nullptr;
}
.....
memset(gr_draw->data, 0, gr_draw->height * gr_draw->row_bytes);
fb_fd = fd;
SetDisplayedFramebuffer(0);
printf("framebuffer: %d (%d x %d)\n", fb_fd, gr_draw->width, gr_draw->height);
Blank(true);
Blank(false);
//主要就是修改的这里
//return gr_draw;
//hekh add for rotateCanvas
return rotate_canvas_get(gr_draw);
}
--------------------------------------------------------------------------------------
GRSurface *rotate_canvas_get(GRSurface *gr_draw)
{
// Initialize the canvas, if it was not exist.
if (gr_canvas==NULL)
rotate_canvas_init(gr_draw);
return gr_canvas;
}
#define swap(x, y, type) {type z; z=x; x=y; y=z;}
void rotate_canvas_init(GRSurface *gr_draw)
{
gr_canvas = &__gr_canvas;
memcpy(gr_canvas, gr_draw, sizeof(GRSurface));
// 旋转90度和270度就交互宽高
if (rotate_config(gr_draw)%2) {
swap(gr_canvas->width, gr_canvas->height, int);
gr_canvas->row_bytes = gr_canvas->width * gr_canvas->pixel_bytes;
}
gr_canvas->data = (unsigned char*) malloc(gr_canvas->height * gr_canvas->row_bytes);
if (gr_canvas->data == NULL) {
printf("[graphics] rotate_canvas_init() malloc gr_canvas->data failed\n");
gr_canvas = NULL;
return;
}
memset(gr_canvas->data, 0, gr_canvas->height * gr_canvas->row_bytes);
print_surface_info(gr_draw, "gr_draw");
print_surface_info(gr_canvas, "gr_canvas");
}
--------------------------------------------------------------------------------------
static int rotate_config(GRSurface *gr_draw)
{
if (rotate_index<0)
{
if (gr_draw->pixel_bytes != 4) rotate_index=0; // support 4 bytes pixel only
else if (0 == strncmp(android::base::GetProperty("persist.sys.rotation", "0").c_str(), "90", 2)) rotate_index=1;
else if (0 == strncmp(android::base::GetProperty("persist.sys.rotation", "0").c_str(), "180", 3)) rotate_index=2;
else if (0 == strncmp(android::base::GetProperty("persist.sys.rotation", "0").c_str(), "270", 3)) rotate_index=3;
else rotate_index=0;
}
return rotate_index;
}
在旋转了GRSurface的宽高后,所生成的画面都是根据768x1024生成的,但是方向却还没有变,而且这个buf是不可以直接传入内核的,因为内核的屏幕大小是1024x768并没有改变,所以我们需要把画面的数据转成内核可以直接使用的,而刷新数据是在gr_flip中的,也就是minui\graphics_fbdev.cpp文件中的flip方法。
GRSurface* MinuiBackendFbdev::Flip() {
//hekh add for rotateCanvas
//主要添加的这个方法,传入的第一个参数是要传入内核的GRSurface,所以他的宽高和屏幕一样,
//第二个参数就是旋转了宽高的GRSurface,但内部的数据方向还没有改变
rotate_surface(gr_draw, rotate_canvas_get(gr_draw));
if (double_buffered) {
// Change gr_draw to point to the buffer currently displayed,
// then flip the driver so we're displaying the other buffer
// instead.
gr_draw = gr_framebuffer + displayed_buffer;
SetDisplayedFramebuffer(1 - displayed_buffer);
} else {
// Copy from the in-memory surface to the framebuffer.
memcpy(gr_framebuffer[0].data, gr_draw->data, gr_draw->height * gr_draw->row_bytes);
}
//return gr_draw;
//hekh add for rotateCanvas
//这里还是返回旋转了宽高的GRSurface
return rotate_canvas_get(gr_draw);
}
---------------------------------------------------------------------------------------
void rotate_surface(GRSurface *dst, GRSurface *src)
{
rotate_surface_t rotate;
rotate=rotate_func[rotate_config(dst)];
rotate(dst, src);
}
typedef void (*rotate_surface_t) (GRSurface *, GRSurface *);
rotate_surface_t rotate_func[4]=
{
rotate_surface_0,
rotate_surface_90,
rotate_surface_180,
rotate_surface_270
};
//这里就看看旋转90度的数据转换是怎么转换的
static void rotate_surface_90(GRSurface *dst, GRSurface *src)
{
int w, k, h;
unsigned int *src_pixel;
unsigned int *dst_pixel;
for (h=0; h<dst->height; h++) {
for (w=0, k=src->height-1; w<dst->width; w++, k--) {
dst_pixel = (unsigned int *)(dst->data + dst->row_bytes*h);
src_pixel = (unsigned int *)(src->data + src->row_bytes*k);
*(dst_pixel+w)=*(src_pixel+h);
}
}
}
转换framebuffer的数据具体的操作流程如下图:
说明:第一个循环h=0,w=0,k=src->height-1,dst->data + dst->row_bytes*h=data,这个data是一个指针指向的是dst的图形数据的一个字节,所以dst_pixel就指向了蓝色的那个字节,row_bytes是每一行的字节数,同理src_pixel就指向了src中蓝色的字节,然后因为w和h都是0,所以就把两个蓝色字节的数据交换了,第二次循环,h=0;w=1;k=src->height-2;所以同理dst_pixel还是指向的蓝色字节,src_pixel指向的src中的黄色字节,然后dst_pixel+w就是指向的dst中的黄色字节,h为0,所以src_pixel+h还是src中的黄色字节,然后交换数据。等到执行完成之后src中的图像数据就变为了dst中的图像数据的方向了,然后传入内核显示,这样图像就顺时针旋转了90度。
这样旋转recovery界面的修改就完成了,但是创建的旋转后的GRSurface在退出recover的时候是需要清理的,为了方便,就新建了一个文件来存储旋转的代码,然后在minui\graphics_fbdev.cpp中来调用会方便一点,修改如下:
新建graphic_rotate.cpp和graphic_rotate.h文件到minui目录下:
graphic_rotate.cpp的内容如下:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "graphics.h"
#include
GRSurface __gr_canvas;
GRSurface* gr_canvas = NULL;
int rotate_index=-1;
static void print_surface_info(GRSurface *s, const char *name)
{
printf("[graphics] %s > Height:%d, Width:%d, PixelBytes:%d, RowBytes:%d, Size:%d, Data: 0x%08" PRIxPTR "\n",
name, s->height, s->width, s->pixel_bytes, s->row_bytes, s->height* s->row_bytes, (uintptr_t) s->data);
}
static int rotate_config(GRSurface *gr_draw)
{
if (rotate_index<0)
{
if (gr_draw->pixel_bytes != 4) rotate_index=0; // support 4 bytes pixel only
else if (0 == strncmp(android::base::GetProperty("persist.sys.rotation", "0").c_str(), "90", 2)) rotate_index=1;
else if (0 == strncmp(android::base::GetProperty("persist.sys.rotation", "0").c_str(), "180", 3)) rotate_index=2;
else if (0 == strncmp(android::base::GetProperty("persist.sys.rotation", "0").c_str(), "270", 3)) rotate_index=3;
else rotate_index=0;
}
return rotate_index;
}
#define swap(x, y, type) {type z; z=x; x=y; y=z;}
void rotate_canvas_init(GRSurface *gr_draw)
{
gr_canvas = &__gr_canvas;
memcpy(gr_canvas, gr_draw, sizeof(GRSurface));
if (rotate_config(gr_draw)%2) {
swap(gr_canvas->width, gr_canvas->height, int);
gr_canvas->row_bytes = gr_canvas->width * gr_canvas->pixel_bytes;
}
gr_canvas->data = (unsigned char*) malloc(gr_canvas->height * gr_canvas->row_bytes);
if (gr_canvas->data == NULL) {
printf("[graphics] rotate_canvas_init() malloc gr_canvas->data failed\n");
gr_canvas = NULL;
return;
}
memset(gr_canvas->data, 0, gr_canvas->height * gr_canvas->row_bytes);
print_surface_info(gr_draw, "gr_draw");
print_surface_info(gr_canvas, "gr_canvas");
}
// Cleanup the canvas
void rotate_canvas_exit(void)
{
if (gr_canvas) {
if (gr_canvas->data)
free(gr_canvas->data);
free(gr_canvas);
}
gr_canvas=NULL;
}
// Return the canvas object
GRSurface *rotate_canvas_get(GRSurface *gr_draw)
{
// Initialize the canvas, if it was not exist.
if (gr_canvas==NULL)
rotate_canvas_init(gr_draw);
return gr_canvas;
//return gr_draw;
}
// Surface Rotate Routines
static void rotate_surface_0(GRSurface *dst, GRSurface *src)
{
memcpy(dst->data, src->data, src->height*src->row_bytes);
}
static void rotate_surface_270(GRSurface *dst, GRSurface *src)
{
int v, w, h;
unsigned int *src_pixel;
unsigned int *dst_pixel;
for (h=0, v=src->width-1; h<dst->height; h++, v--) {
for (w=0; w<dst->width; w++) {
dst_pixel = (unsigned int *)(dst->data + dst->row_bytes*h);
src_pixel = (unsigned int *)(src->data + src->row_bytes*w);
*(dst_pixel+w)=*(src_pixel+v);
}
}
}
static void rotate_surface_180(GRSurface *dst, GRSurface *src)
{
int v, w, k, h;
unsigned int *src_pixel;
unsigned int *dst_pixel;
for (h=0, k=src->height-1; h<dst->height && k>=0 ; h++, k--) {
dst_pixel = (unsigned int *)(dst->data + dst->row_bytes*h);
src_pixel = (unsigned int *)(src->data + src->row_bytes*k);
for (w=0, v=src->width-1; w<dst->width && v>=0; w++, v--) {
*(dst_pixel+w)=*(src_pixel+v);
}
}
}
static void rotate_surface_90(GRSurface *dst, GRSurface *src)
{
int w, k, h;
unsigned int *src_pixel;
unsigned int *dst_pixel;
for (h=0; h<dst->height; h++) {
for (w=0, k=src->height-1; w<dst->width; w++, k--) {
dst_pixel = (unsigned int *)(dst->data + dst->row_bytes*h);
src_pixel = (unsigned int *)(src->data + src->row_bytes*k);
*(dst_pixel+w)=*(src_pixel+h);
}
}
}
typedef void (*rotate_surface_t) (GRSurface *, GRSurface *);
rotate_surface_t rotate_func[4]=
{
rotate_surface_0,
rotate_surface_90,
rotate_surface_180,
rotate_surface_270
};
// rotate and copy src* surface to dst surface
void rotate_surface(GRSurface *dst, GRSurface *src)
{
rotate_surface_t rotate;
rotate=rotate_func[rotate_config(dst)];
rotate(dst, src);
}
graphic_rotate.h文件内容如下:
#ifndef GRAPHICS_ROTATE_H_
#define GRAPHICS_ROTATE_H_
void rotate_canvas_exit(void);
void rotate_canvas_init(GRSurface *gr_draw);
void rotate_surface(GRSurface *dst, GRSurface *src);
GRSurface *rotate_canvas_get(GRSurface *gr_draw);
#endif
在minui\graphics_fbdev.cpp中的修改如下:
diff --git a/bootable/recovery/minui/graphics_fbdev.cpp b/bootable/recovery/minui/graphics_fbdev.cpp
old mode 100644
new mode 100755
index 746f42a..ae7a7f0
--- a/bootable/recovery/minui/graphics_fbdev.cpp
+++ b/bootable/recovery/minui/graphics_fbdev.cpp
@@ -27,7 +27,9 @@
#include
#include "minui/minui.h"
-
+//hekh add for rotateCanvas
+#include "graphic_rotate.h"
+
MinuiBackendFbdev::MinuiBackendFbdev() : gr_draw(nullptr), fb_fd(-1) {
}
void MinuiBackendFbdev::Blank(bool blank) {
@@ -134,14 +136,19 @@ GRSurface* MinuiBackendFbdev::Init() {
SetDisplayedFramebuffer(0);
printf("framebuffer: %d (%d x %d)\n", fb_fd, gr_draw->width, gr_draw->height);
-
Blank(true);
Blank(false);
-
- return gr_draw;
+
+ //return gr_draw;
+ //hekh add for rotateCanvas
+ return rotate_canvas_get(gr_draw);
}
GRSurface* MinuiBackendFbdev::Flip() {
+ //hekh add for rotateCanvas
+ rotate_surface(gr_draw, rotate_canvas_get(gr_draw));
if (double_buffered) {
// Change gr_draw to point to the buffer currently displayed,
// then flip the driver so we're displaying the other buffer
@@ -152,13 +159,16 @@ GRSurface* MinuiBackendFbdev::Flip() {
// Copy from the in-memory surface to the framebuffer.
memcpy(gr_framebuffer[0].data, gr_draw->data, gr_draw->height * gr_draw->row_bytes);
}
- return gr_draw;
+ //return gr_draw;
+ //hekh add for rotateCanvas
+ return rotate_canvas_get(gr_draw);
}
MinuiBackendFbdev::~MinuiBackendFbdev() {
close(fb_fd);
fb_fd = -1;
-
+ //hekh add for rotateCanvas
+ rotate_canvas_exit();
if (!double_buffered && gr_draw) {
free(gr_draw->data);
free(gr_draw);
然后在minui目录下的Android.mk文件中添加
diff --git a/bootable/recovery/minui/Android.mk b/bootable/recovery/minui/Android.mk
old mode 100644
new mode 100755
index 4dfc65f..43444f1
--- a/bootable/recovery/minui/Android.mk
+++ b/bootable/recovery/minui/Android.mk
@@ -23,10 +23,14 @@ LOCAL_SRC_FILES := \
graphics_fbdev.cpp \
resources.cpp \
+#hekh add
+LOCAL_SRC_FILES += graphic_rotate.cpp
+
LOCAL_WHOLE_STATIC_LIBRARIES := \
libadf \
libdrm \
- libsync_recovery
+ libsync_recovery \
+ libbase
LOCAL_STATIC_LIBRARIES := \
libpng \
recovery的logo是指升级过程中的动画界面,原生的系统是一个安卓小机器人的动图,848上就是那个在动的圆,其实升级过程中的动图logo就是一组图片,然后循环播放这一组图片,848上加载升级logo的图片代码在4.6.2章节中的LoadAnimation方法加载的,具体代码如下:
void ScreenRecoveryUI::LoadAnimation() {
std::unique_ptr<DIR, decltype(&closedir)> dir(opendir("/res/images"), closedir);
dirent* de;
std::vector<std::string> intro_frame_names;
std::vector<std::string> loop_frame_names;
while ((de = readdir(dir.get())) != nullptr) {
int value, num_chars;
if (sscanf(de->d_name, "intro%d%n.png", &value, &num_chars) == 1) {
intro_frame_names.emplace_back(de->d_name, num_chars);
} else if (sscanf(de->d_name, "loop%d%n.png", &value, &num_chars) == 1) {
loop_frame_names.emplace_back(de->d_name, num_chars);
}
}
.....
}
这里可以看到加载的是loop%d%n.png的图片,所以要换logo的话直接制作一组logo的图片替换调loop%d%n.png图片就可以了,loop图片在res-hdpi\images\目录下,具体名字就是loop00000.png、loop00001.png这样的,logo动画是多张8位深度png的图片,在linux下用imagemaic工具convert转换生成,具体命令如下:
convert src.png -depth 8 -colorspace gray dst.png
同理字符的修改和添加也是差不多的,加载字符资源的方法如下:
void ScreenRecoveryUI::LoadLocalizedBitmap(const char* filename, GRSurface** surface) {
int result = res_create_localized_alpha_surface(filename, locale_.c_str(), surface);
if (result < 0) {
LOG(ERROR) << "couldn't load bitmap " << filename << " (error " << result << ")";
}
}
这里也是去读取相应字体的图片资源,如读取“installing_text”字符,就会去读取res-hdpi\images\installing_text.png这个图片的资源,这个图片的内容大概如下:
这个图片资源很长,加载的时候会根据前面4.6章节设置的locale来加载相应的文字,然后生成surface资源,然后显示;
生成这个图片资源的方法是:
可以参考:Android recovery图片资源制作
在4.6.3章节初始化输入设备后会创建一个子线程来监听按键,之后会分发到device.cpp下面的HandleMenuKey方法中,详细代码如下:
int Device::HandleMenuKey(int key, bool visible) {
if (!visible) {
return kNoAction;
}
+ printf("key=%d",key);
+ const int KEYCODE_1 = 2;
switch (key) {
case KEY_DOWN:
case KEY_VOLUMEDOWN:
return kHighlightDown;
case KEY_UP:
case KEY_VOLUMEUP:
return kHighlightUp;
+ case KEYCODE_1:
case KEY_ENTER:
case KEY_POWER:
return kInvokeItem;
default:
// If you have all of the above buttons, any other buttons
// are ignored. Otherwise, any button cycles the highlight.
return ui_->HasThreeButtons() ? kNoAction : kHighlightDown;
}
}
在这里就可以更改按键策略了,比如添加数字键1为选择键的话则按上面的代码更改就可以了,这里需要注意的是,recovery模式下的键值和android的键值不同,每个按键的键值具体可以查看2000_6a848_dtmb_Oreo_Smart\bionic\libc\kernel\uapi\linux\input-event-codes.h文件里面的定义。
Recovery显示的菜单选项都是在device.cpp中添加的,添加一个旋转选项对应的代码如下:
static const char* MENU_ITEMS[] = {
"Reboot system now",
"Reboot to bootloader",
"Apply update from ADB",
"Apply update from SD card",
//MStar patch begin
"Apply update from CACHE",
//MStar patch end
"Wipe data/factory reset",
#ifndef AB_OTA_UPDATER
"Wipe cache partition",
#endif // !AB_OTA_UPDATER
"Mount /system",
"View recovery logs",
"Run graphics test",
"Power off",
+ //Hekh patch begin
+ "Rotate",
+ //Hekh patch end
NULL,
};
然后添加选择它后的action:
static const Device::BuiltinAction MENU_ACTIONS[] = {
Device::REBOOT,
Device::REBOOT_BOOTLOADER,
Device::APPLY_ADB_SIDELOAD,
Device::APPLY_SDCARD,
//MStar patch begin
Device::APPLY_CACHE,
//MStar patch end
Device::WIPE_DATA,
#ifndef AB_OTA_UPDATER
Device::WIPE_CACHE,
#endif // !AB_OTA_UPDATER
Device::MOUNT_SYSTEM,
Device::VIEW_RECOVERY_LOGS,
Device::RUN_GRAPHICS_TEST,
Device::SHUTDOWN,
+ //Hekh patch begin
+ Device::ROTATE,
+ //Hekh patch end
};
然后在会在prompt_and_wait方法中通过GetMenuItems获取到ACTION,prompt_and_wait方法在4.7章节有介绍,然后在prompt_and_wait方法中添加获取到的Action为ROTATE时的实现,这里就是设置persist.sys.rotation属性后重新调用minui的gr_init方法就可以了。