美文网首页Android开发
Android Recovery 源码解析和界面定制

Android Recovery 源码解析和界面定制

作者: forty_seven | 来源:发表于2017-08-10 20:24 被阅读701次

    本文我们主要了解学习Android系统中recovery系统的功能知识与系统实现源码。

    目录

    1. Recovery主要功能
    2. 源码路径和主要原文件
    3. recovery.cpp命令行参数
    4. main 函数
    5. 界面定制实现Recovery UI
    6. 实现头部显示和列表项
    7. 实现ScreenRecoveryUI
    8. 实现设备类
    9. 添加编译实现

    1.Recovery主要功能

    深入了解recovery源码前,先浏览下recovery能够给我们提供哪些功能;

    • 首先是我们熟悉的恢复工厂设置 –> wipe_data wipe_cache
    • 刷升级包,可以通过sdcard升级,通常说的卡刷,有些还提供ADB sideload升级;
    • 可以进行系统的系统的OTA升级,本质上同手动刷包一样;
    2.源码路径和主要原文件

    在Android源码环境中,recovery的源码主要在bootable/recovery文件下,另外再device目录下,会根据各个设备定制自己的接口以及UI界面,也就是文章后半部分分析的界面定制的内容;

    bootable/recovery目录下,主要的源文件有:

    LOCAL_SRC_FILES := \     
       adb_install.cpp \     
       asn1_decoder.cpp \     
       bootloader.cpp \     
       device.cpp \     
       fuse_sdcard_provider.c \    
       install.cpp \     
       recovery.cpp \     
       roots.cpp \     
       screen_ui.cpp \     
       ui.cpp \     
       verifier.cpp \
    

    该部分代码在编译后,会统一输出到 out/target/product/arbutus/recovery/root/目录;

    3.recovery.cpp命令行参数

    recovery最后是编译成一个可执行的命令,放在recovery文件系统中的/sbin/recovery;所以我们可以在终端中直接运行该命令,具体的参数如下:

    --send_intent=anystring   - 传递给recovery的信息 
    --adbd   - adb sideload升级 
    --update_package=path   - 指定OTA升级包 
    --wipe_data   - 清楚用户数据并重启 
    --wipe_cache   - 清楚缓存并重启 
    --set_encrypted_filesystem=on|off   - 使能或者关闭文件系统加密 
    --just_exit   - 退出并重启
    
    4.main 函数

    从main入口函数分析recovery的主要源码:
    输出重定向

    redirect_stdio(TEMPORARY_LOG_FILE);
        //redirect log to serial output
    #ifdef LogToSerial
        freopen("/dev/ttyFIQ0", "a", stdout); setbuf(stdout, NULL);
        freopen("/dev/ttyFIQ0", "a", stderr); setbuf(stderr, NULL);
    #endif
    

    这部分代码很容易理解,主要作用是输出log到/tem/recovery.log文件中
    执行adb sideload分支

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

    判断命令行参数是否为–adbd,并执行adb_main函数,这部分代码在后续adb_install.cpp中分析;
    填充fstab结构体
    在main函数中调用 load_volume_table(),读取/etc/recovery.emmc.fstab文件内容,并填充fstab结构体,但是并没有执行挂载操作;
    load_volume_table函数在roots.cpp文件中,也是很容易理解:

    void load_volume_table()
    {
        ...
        int emmcState = getEmmcState();//判断是否为emmc设备
        if(emmcState) {
            fstab = fs_mgr_read_fstab("/etc/recovery.emmc.fstab");
        }else {
            fstab = fs_mgr_read_fstab("/etc/recovery.fstab");
        }
        ...
        //读取文件中每个条目内容,填充fstab结构体
        ret = fs_mgr_add_entry(fstab, "/tmp", "ramdisk", "ramdisk");
        ...
        //日志打印fstable信息
        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");
    }
    

    读取控制参数
    recovery 和 bootloader 必须通过内存的一个特定分区,才能进行相互的通信,这个分区一般是/misc
    对应的信息数据结构体为bootloader_message
    参照源码中bootloader_message的注释

    struct bootloader_message {
        char command[32];//bootloader 启动时读取改数据,决定是否进入recovery模式
        char status[32];//由bootloader进行更新,标识升级的结果;
        char recovery[768];//由Android系统进行写入,recovery从中读取信息;
        char stage[32];
        char reserved[224];
    };
    

    recovery 根据命令行参数,再从/misc分区中解析出对应的参数,进行后续的操作,具体的调用函数为get_args(&argc, &argv)

    static void
    get_args(int *argc, char ***argv) {
        struct bootloader_message boot;//参数结构体
        memset(&boot, 0, sizeof(boot));
        get_bootloader_message(&boot);  // 具体的读取信息的函数,可能为空的情况
        stage = strndup(boot.stage, sizeof(boot.stage));
        ...
    
         // 如果上述情况为空,则从/cache/recovery/command获取参数,其中COMMAND_FILE=/cache/recovery/command
        if (*argc <= 1) {
            FILE *fp = fopen_path(COMMAND_FILE, "r");
            if (fp != NULL) {
                char *token;
                char *argv0 = (*argv)[0];
                *argv = (char **) malloc(sizeof(char *) * MAX_ARGS);
                (*argv)[0] = argv0;  // use the same program name
    
                char buf[MAX_ARG_LENGTH];
                for (*argc = 1; *argc < MAX_ARGS; ++*argc) {
                    if (!fgets(buf, sizeof(buf), fp)) break;
                    token = strtok(buf, "\r\n");
                    if (token != NULL) {
                        (*argv)[*argc] = strdup(token);  // Strip newline.
                    } else {
                        --*argc;
                    }
                }
    
                check_and_fclose(fp, COMMAND_FILE);
                LOGI("Got arguments from %s\n", COMMAND_FILE);
            }
        }
    
        //把从/cache/recovery/command获取参数重新写回到/misc分区
        // --> write the arguments we have back into the bootloader control block
        // always boot into recovery after this (until finish_recovery() is called)
        strlcpy(boot.command, "boot-recovery", sizeof(boot.command));
        strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery));
        int i;
        for (i = 1; i < *argc; ++i) {
            strlcat(boot.recovery, (*argv)[i], sizeof(boot.recovery));
            strlcat(boot.recovery, "\n", sizeof(boot.recovery));
        }
        set_bootloader_message(&boot);
    }
    

    解析命令行参数

    while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -1) {
            switch (arg) {
            case 'f': factory_mode = optarg; bFactoryMode = true; break;
            case 'i': send_intent = optarg; break;
            case 'u': update_package = optarg; break;
            case 'w': should_wipe_data = true; break;
            case 'k':  update_rkimage = optarg;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 'g': {
                if (stage == NULL || *stage == '\0') {
                    char buffer[20] = "1/";
                    strncat(buffer, optarg, sizeof(buffer)-3);
                    stage = strdup(buffer);
                }
                break;
            }
            case 'f'+'w': //fw_update
                if((optarg)&&(!sdboot_update_package)){
                sdboot_update_package = strdup(optarg);
                }
                break;
            case 'd': //demo_copy
                if((optarg)&&(! demo_copy_path)){
                    demo_copy_path = strdup(optarg);
                }
                break;
            case 'p': shutdown_after = true; break;
            case 'r': reason = optarg; break;
            case 'w'+'a': { should_wipe_all = should_wipe_data = should_wipe_cache = true;show_text = true;} break;
            case '?':
                LOGE("Invalid command argument\n");
                continue;
            }
        }
    

    这部分代码很简单,就是通过getopt_long进行命令行参数的解析并赋值;
    显示界面和功能选项
    接下来就是创建device,显示对应UI界面和功能选项;

        Device* device = make_device();//可以自己实现一个设备
        ui = device->GetUI();
        gCurrentUI = ui;//赋值ui界面
    
        ui->SetLocale(locale);//获取归属地信息
        ui->Init();//初始化,可以重载,在init中实现相应功能
        ui->SetStage(st_cur, st_max);
        ui->SetBackground(RecoveryUI::NONE);
    

    进行分区挂载操作
    ensure_path_mounted

    int ensure_path_mounted(const char* path) {
        ...
        Volume* v = volume_for_path(path);//根据路径名获取分区信息
        ...
        int result;
        result = scan_mounted_volumes();
    
        const MountedVolume* mv =
            find_mounted_volume_by_mount_point(v->mount_point);//根据挂载点,获取已挂载分区的信息,如果不为空,说明已经成功挂载
        if (mv) {
            // volume is already mounted
            return 0;
        }
    
        result = mkdir(v->mount_point, 0755);  // 创建对应目录,确保目录存在,也有可能目录已经存在
        if (result!=0)
        {
            printf("failed to create %s dir,err=%s!\n",v->mount_point,strerror(errno));
        }
    
        // 根据文件系统类型,执行mount操作
        if (strcmp(v->fs_type, "yaffs2") == 0) {
            // mount an MTD partition as a YAFFS2 filesystem.
            mtd_scan_partitions();
            const MtdPartition* partition;
            partition = mtd_find_partition_by_name(v->blk_device);
            if (partition == NULL) {
                LOGE("failed to find \"%s\" partition to mount at \"%s\"\n",
                     v->blk_device, v->mount_point);
                return -1;
            }
            return mtd_mount_partition(partition, v->mount_point, v->fs_type, 0);
        } else if (strcmp(v->fs_type, "ext4") == 0 ||
                   strcmp(v->fs_type, "ext3") == 0) {
            result = mount(v->blk_device, v->mount_point, v->fs_type,
                           MS_NOATIME | MS_NODEV | MS_NODIRATIME, "");
            if (result == 0) return 0;
    
            LOGE("failed to mount %s %s (%s)\n", v->mount_point, v->blk_device, strerror(errno));
            return -1;
        } else if (strcmp(v->fs_type, "vfat") == 0) {
            result = mount(v->blk_device, v->mount_point, v->fs_type,
                           MS_NOATIME | MS_NODEV | MS_NODIRATIME, "shortname=mixed,utf8");
            if (result == 0) return 0;
    
            LOGW("trying mount %s to ntfs\n", v->blk_device);
            result = mount(v->blk_device, v->mount_point, "ntfs",
                               MS_NOATIME | MS_NODEV | MS_NODIRATIME, "");
            if (result == 0) return 0;
    
            char *sec_dev = v->fs_options;
            if(sec_dev != NULL) {
                char *temp = strchr(sec_dev, ',');
                if(temp) {
                    temp[0] = '\0';
                }
    
                result = mount(sec_dev, v->mount_point, v->fs_type,
                                       MS_NOATIME | MS_NODEV | MS_NODIRATIME, "shortname=mixed,utf8");
                if (result == 0) return 0;
    
                LOGW("trying mount %s to ntfs\n", sec_dev);
                result = mount(sec_dev, v->mount_point, "ntfs",
                                   MS_NOATIME | MS_NODEV | MS_NODIRATIME, "");
                if (result == 0) return 0;
            }
    
            LOGE("failed to mount %s (%s)\n", v->mount_point, strerror(errno));
            return -1;
        }else if (strcmp(v->fs_type, "ntfs") == 0) {
            LOGW("trying mount %s to ntfs\n", v->blk_device);
            result = mount(v->blk_device, v->mount_point, "ntfs",
                               MS_NOATIME | MS_NODEV | MS_NODIRATIME, "");
            if (result == 0) return 0;
    
            LOGE("failed to mount %s (%s)\n", v->mount_point, strerror(errno));
            return -1;
        }
    
        LOGE("unknown fs_type \"%s\" for %s\n", v->fs_type, v->mount_point);
        return -1;
    }
    
    5.界面定制实现Recovery UI

    在自己的设备目录下:device/vendor/recovery/recovery_ui.cpp

    #include <linux/input.h>
    #include <sys/stat.h>
    #include <errno.h>
    #include <string.h>
    
    #include "common.h"
    #include "device.h"
    #include "screen_ui.h"
    
    6.实现头部显示和列表项
    const char* HEADERS[] = { 
        "Volume up/down to move highlight;",
        "power button to select.", 
        "", 
        NULL 
    }; 
    const char* ITEMS[] ={ 
        "reboot system now", 
      //"apply update from ADB",
        "apply update from external storage", 
        "update rkimage from external storage", 
        "apply update from cache", 
        "wipe data/factory reset", 
        "wipe cache partition", 
        "recovery system from backup", 
        NULL 
    };
    
    7.实现ScreenRecoveryUI
    class DeviceUI : public ScreenRecoveryUI {
      public:
        DeviceUI () :
            consecutive_power_keys(0) {
        }
    
        //实现自己的识别key类型的功能,可以为不同的输入设备适配recovery功能
        virtual KeyAction CheckKey(int key) {
            if (IsKeyPressed(KEY_POWER) && key == KEY_VOLUMEUP) {
                return TOGGLE;
            }
            if (key == KEY_POWER) {
                ++consecutive_power_keys;
                if (consecutive_power_keys >= 7) {
                    return REBOOT;
                }
            } else {
                consecutive_power_keys = 0;
            }
            return ENQUEUE;
        }
    
      private:
        int consecutive_power_keys;
    };
    
    8.实现设备类
    class MyDevice : public Device {
      public:
        RkDevice() :
            ui(new DeviceUI ) {
        }
    
        RecoveryUI* GetUI() { return ui; }
    
        int HandleMenuKey(int key_code, int visible) {
            if (visible) {
                switch (key_code) {
                  case KEY_DOWN:
                  case KEY_VOLUMEDOWN:
                    return kHighlightDown;
    
                  case KEY_UP:
                  case KEY_VOLUMEUP:
                    return kHighlightUp;
    
                  case KEY_ENTER:
                  case KEY_POWER:
                    return kInvokeItem;
                }
            }
    
            return kNoAction;
        }
    
        BuiltinAction InvokeMenuItem(int menu_position) {
            switch (menu_position) {
              case 0: return REBOOT;
              //case 1: return APPLY_ADB_SIDELOAD;
              case 1: return APPLY_EXT;
              case 2: return APPLY_INT_RKIMG;
              case 3: return APPLY_CACHE;
              case 4: return WIPE_DATA;
              case 5: return WIPE_CACHE;
              case 6: return RECOVER_SYSTEM;
              default: return NO_ACTION;
            }
        }
    
        const char* const* GetMenuHeaders() { return HEADERS; }
        const char* const* GetMenuItems() { return ITEMS; }
    
      private:
        RecoveryUI* ui;
    };
    
    //创建自己实现的设备
    Device* make_device() {
        return new MyDevice ;
    }
    
    9.添加编译实现

    主要是覆盖TARGET_RECOVERY_UI_LIB,输出到/out/…./recovery/root目录下:
    Android.mk

    LOCAL_PATH := $(call my-dir)
    include $(CLEAR_VARS)
    
    LOCAL_MODULE_TAGS := eng
    LOCAL_C_INCLUDES += bootable/recovery
    LOCAL_SRC_FILES := recovery_ui.cpp
    
    # should match TARGET_RECOVERY_UI_LIB set in BoardConfig.mk
    LOCAL_MODULE := librecovery_ui_$(TARGET_PRODUCT)
    
    include $(BUILD_STATIC_LIBRARY)
    

    相关文章

      网友评论

        本文标题:Android Recovery 源码解析和界面定制

        本文链接:https://www.haomeiwen.com/subject/wtckrxtx.html