赞
踩
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的内容不等价,且不同类型。
Recovery系统的三个部分和两个通信接口的示意图如下:
在进入文件系统后会执行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<std::string> 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 <stdbool.h> #include <stdlib.h> #include <unistd.h> #include <inttypes.h> #include <fcntl.h> #include <stdio.h> #include <sys/cdefs.h> #include <sys/ioctl.h> #include <sys/mman.h> #include <sys/types.h> #include <linux/fb.h> #include <linux/kd.h> #include "graphics.h" #include <android-base/properties.h> 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 <unistd.h> #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资源,然后显示;
生成这个图片资源的方法是:
在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方法就可以了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。