一、Recovery系统简介

  Recovery模式指的是一种可以对安卓机内部的数据或系统进行修改的模式(类似于windows PE或DOS)。在这个模式下我们可以刷入新的Android系统,或者对已有的系统进行备份或升级,也可以在此模式下恢复出厂设置。系统进入recovery模式后会装载recovery分区,该分区包含recovery.img(与boot.img类似,也包含了标准的内核和根文件系统).进入该模式后主要就是运行了recovery服务(/sbin/recovery)。

二、Recovery系统的启动流程

2.1 进入Recovery系统的三种常见方法:

  1. 在关机情况下,同时按住电源(Power)+ 音量加(Vol +)键,直到出现Recovery界面为止。注:有的系统按键方式可能不同。
  2. 使用安卓辅助工具,如:刷机精灵、360手机助手等等。
  3. 使用adb命令reboot recovery启动。

2.2 从reboot启动到Recovery服务的流程

 在Bootloader开始如果没有组合键按下,就从MISC分区读取BCB块的command字段(在主系统时已经将“boot-recovery”写入)。然后就以Recovery模式开始启动。与正常启动不同的是Recovery模式下加载的镜像是recovery.img。这个镜像同boot.img类似,也包含了标准的内核和根文件系统。其后就与正常的系统启动类似,也是启动内核,然后启动文件系统。在进入文件系统后会执行/init,init的配置文件在bootable/recovery/etc/init.rc。这个文件的主要作用就是:

  1. 设置环境变量。
  2. 建立etc连接。
  3. 新建目录,备用。
  4. 挂载/tmp为内存文件系统tmpfs
  5. 启动recovery(/sbin/recovery)服务。
  6. 启动adbd服务(用于调试)。

这里最重要的当然就是启动recovery服务了。

重启到recovery模式的流程图如下:

android reboot 排查 安卓reboot system_ota

三、Recovery系统的框架结构

3.1 源码路径和主要原文件

 在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/目录;

3.2 Recovery模式的三个部分

Recovery的工作需要整个软件平台的配合,从通信架构上来看,主要有三个部分。

  1. MainSystem:即上面提到的正常启动模式(BCB中无命令情况),是引导boot.img启动的系统,Android的正常工作模式。更新时,在这种模式下的操作就是往 /cache/recovery/command 文件中写入ota升级命令及包存放路径。在重启进入Recovery模式之前,会向BCB中写入命令,以便在重启后告诉bootloader进入Recovery模式。
  2. Recovery:系统进入Recovery模式后会装载Recovery分区,该分区包含recovery.img。进入该模式后主要是运行Recovery服务(/sbin/recovery)来做相应的操作(重启、升级update.zip、擦除cache分区等)。
  3. Bootloader:除了正常的加载启动系统之外,还会通过读取MISC分区(BCB)获得来至Main system和Recovery的消息。

3.3 Recovery模式的两个通信接口

 在Recovery服务中上述的三个实体之间的通信是必不可少的,他们相互之间又有以下两个通信接口。

3.3.1 通过CACHE分区中的三个文件通信

Recovery通过/cache/recovery/目录下的三个文件与mian system通信。具体如下

  1. /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


退出和重启

  1. /cache/recovery/last_log:Recovery模式在工作中的log打印。在recovery服务运行过程中,stdout以及stderr会重定位到/tmp/recovery.log在recovery退出之前会将其转存到/cache/recovery/last_log中,供查看。
  2. /cache/recovery/intent:Recovery传递给Main system的信息。列如反馈升级是否成功。

3.3.2 通过BCB(Bootloader Control Block)通信

 BCB是bootloader与Recovery的通信接口,也是Bootloader与Main system之间的通信接口。存储在flash中的MISC分区,占用三个page,其本身就是一个结构体,具体成员以及各成员含义如下:

struct bootloader_message{
	char command[32];
	char status[32];
	char recovery[1024];
};
  1. command成员:其可能的取值我们在上文已经分析过了,即当我们想要在重启进入Recovery模式时,Main System会将boot-recovery命令写入。另外在退出Recovery时,会清除这个成员的值,防止重启时再次进入Recovery模式。
  2. status:在完成相应的更新后,Bootloader会将执行结果写入到这个字段。
  3. recovery:可被Main System写入,也可被Recovery服务程序写入。该文件的内容格式为:
“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的内容不等价,且不同类型。

  • BCB中的command:决定了bootloader该去引导启动android系统还是recovery系统
  • BCB中的recovery:这个字段就是操作命令的备份,此处内容和/cache/recovery/command内容是等价的。

Recovery系统的三个部分和两个通信接口的示意图如下:

android reboot 排查 安卓reboot system_android reboot 排查_02

四、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函数中主要做了下面几件事情:

  • 设置adb进程。
  • log重定向到recovery.log。
  • 装载分区表,填充fstab结构体。
  • 读取控制参数。
  • 加载语言显示。
  • 加载UI模型。
  • 死循环prompt_and_wait,等待操作;
  • 退出recovery模式

 recovery.cpp的main方法执行的流程图大概如下:

android reboot 排查 安卓reboot system_android reboot 排查_03

4.1 设置adb进程

 在recovery的main方法中首先判断命令行参数是否为–adbd,如果有则执行minadbd_main函数,这样是为了方便使用adb sideload命令,如果参数为-adbd的话,那么它会变成精简版adbd,只支持sideload命令。

if (argc == 2 && strcmp(argv[1], "--adbd") == 0) {
        minadbd_main();
        return 0;
    }

4.2 输出log重定向到recovery.log

 重定向标准输出和标准出错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);

4.3 装载分区表

 之后会调用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

4.4 读取控制参数

 在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;
        }
    }

4.5 加载显示语言

 这个方法就是去判断/cache/recovery/last_locale文件是否存在,如果存在就读取里面的值,获取到的内容关系到显示那个国家的语言,如果没有获取到locale就使用默认的语言,848中的默认语言是英语。

if (locale.empty()) {
        if (has_cache) {
            locale = load_locale_from_cache();
        }

        if (locale.empty()) {
            locale = DEFAULT_LOCALE;
        }
    }

4.6 加载UI模式

加载UI界面的流程大概有下面几步:

  1. 新建一个Device类的对象;
  2. 调用Device类的GetUI()返回一个RecoveryUI对象,这里应该返回的是ScreenRecoveryUI,ScreenRecoveryUI继承于RecoveryUI;
  3. 调用Init()初始化UI;
  4. 调用RecoveryUi的init方法去设置国家语言,然后初始化输入设备,并创建一个线程用于监听输入事件;
  5. 调用minui库的gr_init方法初始化图形显示,主要是打开设备、分配内存、初始化一些参数;
  6. 通过LoadBitmap()加载png图片生成surface对象
  7. 创建一个子线程更新progress进度条;
  8. 调用SetBackground方法设置背景图片;

 Recovery中显示UI界面的framebuffer使用的是minui库,该库在网上也能查到相应的方法说明,下面会有详细介绍。

4.6.1 在main方法中

这里主要做了这几件事情:

  • 新建一个device设备;
  • 获取到UI;
  • 调用UI的Init方法进行初始化;
  • 设置背景;
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");

4.6.2 调用UI的Init方法初始化

 这里获取到的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;
}

4.6.3 RecoveryUI::Init方法

 这个方法里面会设置语言,及初始化输入设备和事件,并创建一个子线程去监听输入事件,这里就不详细介绍了;

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;
}

4.6.4 InitTextParams方法及初始化图形显示

 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;
}

4.6.5 minui库的方法说明

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);       /* 释放资源数据 */

4.7 死循环prompt_and_wait

 再加载完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;
    }
  }
}

4.8 退出Recovery

 在通过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;
    }

五、Recovery最主要的功能-升级

 在应用层层面的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包升级的时序图如下:

android reboot 排查 安卓reboot system_ci_04

5.1 recovery.cpp的main方法

 进入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方法。

5.2 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;
}

5.3 really_install_package

在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包检查时大致会分三步:

  • 检验SF文件与RSA文件是否匹配;
  • 检验MANIFEST.MF与签名文件中的digest是否一致;
  • 检验包中的文件与MANIFEST中所描述的是否一致
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是真正实现对升级包进行升级的函数。

5.4 try_update_binary()真正实现升级的函数

总的来说,try_update_binary主要做了以下几个操作:

  1. update_binary_command:解析升级包,将一些文件描述信息保存到args中。
  2. fork创建一个子进程 , 使用系统调用函数execv( ) 去执行/tmp/update-binary程序,
  3. update-binary: 这个是Recovery OTA升级的核心程序,是一个二进制文件,实现代码位于系统源码bootable/updater.cpp。其实质是相当于一个脚本解释器,能够识别updater-script中描述的操作并执行。
  4. updater-script:updater-script是我们升级时所具体使用到的脚本文件,具体描述了更新过程,它主要用以控制升级流程的主要逻辑。具体位置位于升级包中/META-INF/com/google/android/update-script,在我们制作升级包的时候产生。在升级的时候,由update_binary程序从升级包里面解压到内存文件系统的/tmp/update_script中,并按照update_script里面的命令,对系统进行升级。
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函数去注册脚本中的语句处理函数,即识别脚本中命令的函数,主要有以下几类:

  • RegisterBuiltins():注册程序中控制流程的语句,如ifelse、assert、abort、stdout等。
  • RegisterInstallFunctions():实际安装过程中安装所需的功能函数,比如mount、format、set_progress、set_perm等等。
  • RegisterDeviceExtensions():与设备相关的额外添加項,在源码中并没有任何实现。
  • FinishRegistration():结束注册。

如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章节。

六、常见的客制化

6.1 Recovery的界面旋转适配横竖屏

 在前面4.6章节有介绍过,recovery的显示是通过minui库直接打开操作/dev/graphics/fb0文件直接显示framebuffer的,上面有介绍minui库的一些方法,在这里我们需要修改下面两个方法:

int gr_init(void);             /* 初始化图形显示,主要是打开设备、分配内存、初始化一些参数 */
void gr_flip(void);            /* 刷新显示内容 */

其实旋转Recovery界面总共只需要两步:

  1. 旋转gr_init中初始化的GRSurface的画面长宽及data内存大小;
  2. 旋转gr_flip中GRSurface中data里面的图形数据然后传入内核显示;

6.1.1 旋转gr_init中的GRSurface的长宽

 在加载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;
}

6.1.2 旋转GRSurface绘制的数据

 在旋转了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的数据具体的操作流程如下图:

android reboot 排查 安卓reboot system_android_05


 说明:第一个循环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 \

6.2 Recovery升级界面的logo更改及字符修改和添加

6.2.1 升级界面的logo替换

 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

6.2.2 升级界面字符修改及添加

同理字符的修改和添加也是差不多的,加载字符资源的方法如下:

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这个图片的资源,这个图片的内容大概如下:

android reboot 排查 安卓reboot system_重启_06


 这个图片资源很长,加载的时候会根据前面4.6章节设置的locale来加载相应的文字,然后生成surface资源,然后显示;

 生成这个图片资源的方法是:

  1. 修改/tools/recovery_l10n/res/values/string.xml 中的字符串,如果有其他语言,比如中文,修改values-zh-rCN比如修改为Installing Lbb update…;
  2. 单独编译这个APK mmm ./bootable/recovery/tools/recovery_l10n/;
  3. 安装apk后点击go按钮就会生成图片到./data/data/com.android.recovery_l10n/files/目录;
  4. 然后在linux下执行pngcrush -c 0 text-out.png output.png 获得压缩后的png;
  5. 然后放入/recovery/res/image/下就可以了;

可以参考:Android recovery图片资源制作

6.3 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文件里面的定义。

6.4 Recovery的菜单选项添加

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方法就可以了。

七、小知识

  1. 如果recovery有问题启动不了,那么会卡在开机log的第二帧上;
  2. recovery就是recovery.cpp编译生成的可执行文件(具体可以看recovery下的Android.mk);
  3. 平时使用的adb就是PC端adb通过socket连接sbin/adbd,执行的命令都是这个adbd执行的;