Recovery UI更新分析

1.Recovery流程

Recovery UI更新分析

2.Recovery UI初始化流程分析

之前讲过Recovery 的流程。那么在定制化的过程当中,可能涉及到Recovery UI的改动。下面就介绍一下Recovery UI的一些东西。
首先还是从recovery的main函数开始分析;

int main(int argc, char **argv){
//中间处理命令的部分省略
	1.这里就是开始创建UI的一个对象
    Device* device = make_device();
    ui = device->GetUI();
    gCurrentUI = ui;
	ui->SetLocale(locale);
	//实例完之后开始进行初始化

    ui->Init();//开启thread,监听按键
	...
	 if (update_package != NULL) {
	         status = install_package(update_package, &should_wipe_cache, TEMPORARY_INSTALL_FILE, mount_required);
			 ...
		if (status != INSTALL_SUCCESS) {
            	ui->Print("Installation aborted.\n");

            	// 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()) {//如果是debug版的话就显示升级的log
                	ui->ShowText(true);
            	}
        }

	 }
}

下面将上面分为两部分来分析:
1.UI部分分析

首先看一下make_device里面做了什么事情

Device* make_device() {
  return new Device(new ScreenRecoveryUI);
}

实现很简单,就是new了一个ScreenRecoveryUI的对象。
看一下ScreenRecoveryUI是如何实现的:

bootable/recovery/screen_ui.h
class ScreenRecoveryUI : public RecoveryUI {}

ScreenRecoveryUI继承自RecoveryUI。ScreenRecoveryUI中实现了画图所需要的各种接口.
下面看一下上面涉及到的接口

void ScreenRecoveryUI::Init() {
    gr_init();//初始化显示设备,加载字体

    gr_font_size(&char_width, &char_height);
    text_rows_ = gr_fb_height() / char_height;
    text_cols_ = gr_fb_width() / char_width;

    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;
	//加载图片资源
    backgroundIcon[NONE] = nullptr;
    LoadBitmapArray("icon_installing", &installing_frames, &installation);
    backgroundIcon[INSTALLING_UPDATE] = installing_frames ? installation[0] : nullptr;
    backgroundIcon[ERASING] = backgroundIcon[INSTALLING_UPDATE];
    LoadBitmap("icon_error", &backgroundIcon[ERROR]);
    backgroundIcon[NO_COMMAND] = backgroundIcon[ERROR];

    LoadBitmap("progress_empty", &progressBarEmpty);
    LoadBitmap("progress_fill", &progressBarFill);
    LoadBitmap("stage_empty", &stageMarkerEmpty);
    LoadBitmap("stage_fill", &stageMarkerFill);

    LoadLocalizedBitmap("installing_text", &backgroundText[INSTALLING_UPDATE]);
    LoadLocalizedBitmap("erasing_text", &backgroundText[ERASING]);
    LoadLocalizedBitmap("no_command_text", &backgroundText[NO_COMMAND]);
    LoadLocalizedBitmap("error_text", &backgroundText[ERROR]);

    pthread_create(&progress_thread_, nullptr, ProgressThreadStartRoutine, this);
 	//调用父类的初始化函数。
    RecoveryUI::Init();
}

3.字体加载函数gr_init

int gr_init(void)
{
    gr_init_font();//加载字体
	//打开显示模块
       if (!gr_draw) {
        gr_backend = open_fbdev();
        gr_draw = gr_backend->init(gr_backend);
        if (gr_draw == NULL) {
            return -1;
        }
    }
}

static void gr_init_font(void)
{
    gr_font = reinterpret_cast<GRFont*>(calloc(sizeof(*gr_font), 1));

    int res = res_create_alpha_surface("font", &(gr_font->texture));//读取font图片中的数据
   ···
}

4.下面看一下图片加载函数LoadBitmap

void ScreenRecoveryUI::LoadBitmap(const char* filename, GRSurface** surface) {
    int result = res_create_display_surface(filename, surface);
    if (result < 0) {
        LOGE("missing bitmap %s\n(Code %d)\n", filename, result);
    }
}

int res_create_display_surface(const char* name, GRSurface** pSurface) {
    GRSurface* surface = NULL;
    //打开图片,获取图片的详细参数
    result = open_png(name, &png_ptr, &info_ptr, &width, &height, &channels);
    if (result < 0) return result;
    //根据图片得参数初始化一个GRSurface,用来保存图片数据
    surface = init_display_surface(width, height);
    if (surface == NULL) {
        result = -8;
        goto exit;
    }
	//将图片中的数据按行的读取方式读取出来,保存到surface中。
    for (y = 0; y < height; ++y) {
        png_read_row(png_ptr, p_row, NULL);
        transform_rgb_to_draw(p_row, surface->data + y * surface->row_bytes, channels, width);
    }
    free(p_row);

    *pSurface = surface;

  exit:
    png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
    if (result < 0 && surface != NULL) free(surface);
    return result;
}

以上是如何读取图片并且保存的。在recovery里面的文字也是采用的这种方式读取的,这里就不再写出来了。这里需要说明的是,recovery中的文字也是从图片中截取出来,然后在显示出来的。

5.Recovery按键事件的监听

void RecoveryUI::Init() {
    ev_init(InputCallback, this);//打开/dev/input,从里面读取event,并且注册按键处理函数InputCallback

    pthread_create(&input_thread_, nullptr, InputThreadLoop, nullptr);//开启一个thread去监听key event
	
}

static void* InputThreadLoop(void*) {
    while (true) {
        if (!ev_wait(-1)) {
            ev_dispatch();//分发事件调用InputCallback
        }
    }
    return nullptr;
}

6.Recovery 中图片的显示

上面介绍了图片的读取,下面通过分期进度条的显示来看recovery的显示

void ScreenRecoveryUI::draw_progress_locked() {
	... 
    if (progressBarType != EMPTY) {
		//读取图片的宽度和高度
        int em_width = gr_get_width(progressBarEmpty);
        int em_height = gr_get_height(progressBarEmpty);
        int fl_width = gr_get_width(progressBarFill);
        int fl_height = gr_get_height(progressBarFill);
        //获取到lcd的宽度
		int dx = gr_fb_width();
        int dy = (3*gr_fb_height() - 2*em_height)/4;
        
         if (progressBarType == DETERMINATE) {

            float p = progressScopeStart + progress * progressScopeSize;
            int pos = (int) (p * em_height);//计算进度条的显示位置
            if (rtl_locale){//right to left
                if (pos >= 0) {//画有颜色进度条
                    gr_blit(progressBarFill, 0, fl_height, fl_width, dy/2 + pos, dx/2, dy/2);
                }
                
                if (pos < em_height-1){//画空进度条
                    gr_blit(progressBarEmpty, 0, 0, em_width, dy/2 + em_height - pos, dx/2, dy/2 + fl_height-pos);
                }
            }else {//left to right
                if (pos >= 0) {
                    gr_blit(progressBarFill, 0, 0, fl_width, pos, dx/2, dy/2);
                }
                // this maybe incorrect. but if use em_height, maybe not fullfill
                if (pos < fl_height-1){
                    gr_blit(progressBarEmpty, 0, pos, em_width, fl_height - pos, dx/2, dy/2 + pos);
                }
            }
         }
    }
}

这里面主要的就是gr_blit,下面看一下这个函数

/*
*source:被画的图片数据
*sx/sy:这是一个坐标对,表示从哪个点开始画,这是一个相对dx、dy的数据
*w/h:source的长和宽
*dx/dy:开始从哪个点开始画
*/
void gr_blit(GRSurface* source, int sx, int sy, int w, int h, int dx, int dy) {
    if (source == NULL) return;

    if (gr_draw->pixel_bytes != source->pixel_bytes) {
        printf("gr_blit: source has wrong format\n");
        return;
    }

    dx += overscan_offset_x;
    dy += overscan_offset_y;

    if (outside(dx, dy) || outside(dx+w-1, dy+h-1)) return;

    unsigned char* src_p = source->data + sy*source->row_bytes + sx*source->pixel_bytes;//指向将要画出的数据
    unsigned char* dst_p = gr_draw->data + dy*gr_draw->row_bytes + dx*gr_draw->pixel_bytes;//指向将要画到哪里

    int i;
	//将数据复制过去
    for (i = 0; i < h; ++i) {
        memcpy(dst_p, src_p, w * source->pixel_bytes);
        src_p += source->row_bytes;
        dst_p += gr_draw->row_bytes;
    }
}

到目前为止,只是将数据放入到显示buffer中,并不能显示。要显示的话需要调用函数gr_flip()。这个函数就是将buffer中的数据显示到屏幕上。

7.Recovery UI 更新

下面讲一下,Recovery UI是如何得到更新的消息的。之前已经将Recovery 升级的过程讲过了,这里不再赘述。直接将关键的部分。

static int
try_update_binary(const char* path, ZipArchive* zip, bool* wipe_cache) {
	···
	//从升级包中获取升级的bin文件,即META-INF/com/google/android/update-binary
	const ZipEntry* binary_entry =
            mzFindZipEntry(zip, ASSUMED_UPDATE_BINARY_NAME);
			
	const char* binary = "/tmp/update_binary";
    unlink(binary);
    int fd = creat(binary, 0755);
			
	bool ok = mzExtractZipEntryToFile(zip, binary_entry, fd);读取到句柄中
	
    int pipefd[2];
    pipe(pipefd);
    const char** args = (const char**)malloc(sizeof(char*) * 5);
    args[0] = binary;
    args[1] = EXPAND(RECOVERY_API_VERSION);   // defined in Android.mk
    char* temp = (char*)malloc(10);
    sprintf(temp, "%d", pipefd[1]);
    args[2] = temp;
    args[3] = (char*)path;
    args[4] = NULL;
	//开体格新的thread
    pid_t pid = fork();
    if (pid == 0) {
        umask(022);
        close(pipefd[0]);
        execv(binary, (char* const*)args);//在子进程中运行update-binary
        fprintf(stdout, "E:Can't run %s (%s)\n", binary, strerror(errno));
        _exit(-1);
    }
    close(pipefd[1]);

    *wipe_cache = false;

    char buffer[1024];
    FILE* from_child = fdopen(pipefd[0], "r");//在父进程中读取来自子进程中的消息
    while (fgets(buffer, sizeof(buffer), from_child) != NULL) {
        char* command = strtok(buffer, " \n");
        if (command == NULL) {
            continue;
        } else if (strcmp(command, "progress") == 0) {
            char* fraction_s = strtok(NULL, " \n");
            char* seconds_s = strtok(NULL, " \n");

            float fraction = strtof(fraction_s, NULL);
            int seconds = strtol(seconds_s, NULL, 10);

            ui->ShowProgress(fraction * (1-VERIFICATION_PROGRESS_FRACTION), seconds);
        } else if (strcmp(command, "set_progress") == 0) {
            char* fraction_s = strtok(NULL, " \n");
            float fraction = strtof(fraction_s, NULL);
            ui->SetProgress(fraction);
        } else if (strcmp(command, "ui_print") == 0) {
            char* str = strtok(NULL, "\n");
            if (str) {
                ui->Print("%s", str);
            } else {
                ui->Print("\n");
            }
            fflush(stdout);
        } else if (strcmp(command, "wipe_cache") == 0) {
            *wipe_cache = true;
        } else if (strcmp(command, "clear_display") == 0) {
            ui->SetBackground(RecoveryUI::NONE);
        } else if (strcmp(command, "enable_reboot") == 0) {
            // 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 {
            LOGE("unknown command [%s]\n", command);
        }
    }
    fclose(from_child);

    int status;
    waitpid(pid, &status, 0);
    if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
        LOGE("Error in %s\n(Status %d)\n", path, WEXITSTATUS(status));
        return INSTALL_ERROR;
    }
    return INSTALL_SUCCESS;
}

下面将ota升级包中的脚本简单贴一部分

ui_print("Target: ASKEY/CDR6013/msm8952_64:6.0.1/CDR6013-DS-180307-1-40-4,release-keys");
show_progress(0.750000, 0);

ui_print这个函数是怎么实现的看一下

RegisterFunction("ui_print", UIPrintFn);

Value* UIPrintFn(const char* name, State* state, int argc, Expr* argv[]) {
    char** args = ReadVarArgs(state, argc, argv);
	//中间
    uiPrint(state, buffer);
    return StringValue(buffer);
}

void uiPrint(State* state, char* buffer) {
    char* line = strtok(buffer, "\n");
    UpdaterInfo* ui = (UpdaterInfo*)(state->cookie);
    while (line) {
        fprintf(ui->cmd_pipe, "ui_print %s\n", line);
        line = strtok(NULL, "\n");
    }
    fprintf(ui->cmd_pipe, "ui_print\n");
}

这样父进程接收到ui_print命令之后就会处理UI上的显示了
到这里Recovery的UI就结束了。
字体图片:
Recovery UI更新分析