Recovery UI Update Analysis

Keywords: Android Google

Article directory

1.Recovery process

2.Recovery UI initialization process analysis

I've talked about Recovery's process before. In the customization process, then, changes to Recovery UI may be involved. Here are some of the things about Recovery UI.
Firstly, the main function of recovery is analyzed.

int main(int argc, char **argv){
//Partial Ellipsis of Intermediate Processing Commands
	1.Here's how to start creating UI An object
    Device* device = make_device();
    ui = device->GetUI();
    gCurrentUI = ui;
	ui->SetLocale(locale);
	//Initialization begins after the instance is complete

    ui->Init();//Open thread and listen for buttons
	...
	 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()) {//If it's debug, the upgraded log will be displayed
                	ui->ShowText(true);
            	}
        }

	 }
}

Below will be divided into two parts to analyze:
1.UI Part Analysis

Let's first look at what's going on in make_device.

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

The implementation is simple. It's a new Screen Recovery UI object.
Take a look at how Screen Recovery UI is implemented:

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

Screen Recovery UI inherits from Recovery UI. Screen Recovery UI implements various interfaces needed for drawing.
Let's take a look at the interfaces mentioned above.

void ScreenRecoveryUI::Init() {
    gr_init();//Initialize display device, load font

    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;
	//Load Image Resources
    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);
 	//Call the initialization function of the parent class.
    RecoveryUI::Init();
}

3. Font loading function gr_init

int gr_init(void)
{
    gr_init_font();//Load font
	//Open the display module
       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));//Read data from font pictures
   ···
}

4. Let's take a look at the image loading function 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;
    //Open the picture and get the detailed parameters of the picture
    result = open_png(name, &png_ptr, &info_ptr, &width, &height, &channels);
    if (result < 0) return result;
    //Initialize a GRSurface based on the parameters of the image to save the image data
    surface = init_display_surface(width, height);
    if (surface == NULL) {
        result = -8;
        goto exit;
    }
	//The data in the picture is read out by line and saved in 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;
}

The above is how to read the picture and save it. The text in recovery is also read in this way, and it is no longer written here. It should be noted that the text in recovery is also intercepted from the picture and then displayed.

5.Recovery keyboard event monitoring

void RecoveryUI::Init() {
    ev_init(InputCallback, this);//Open / dev/input, read event s from it, and register the key handler InputCallback

    pthread_create(&input_thread_, nullptr, InputThreadLoop, nullptr);//Open a thread to listen for key event s
	
}

static void* InputThreadLoop(void*) {
    while (true) {
        if (!ev_wait(-1)) {
            ev_dispatch();//Distribution event calls InputCallback
        }
    }
    return nullptr;
}

6. Display of pictures in Recovery

The above is about reading pictures, and the following is about recovery by displaying the progress bar in stages.

void ScreenRecoveryUI::draw_progress_locked() {
	... 
    if (progressBarType != EMPTY) {
		//Width and Height of Reading Pictures
        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);
        //Get the width of the 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);//Calculate the display position of the progress bar
            if (rtl_locale){//right to left
                if (pos >= 0) {//Draw a color progress bar
                    gr_blit(progressBarFill, 0, fl_height, fl_width, dy/2 + pos, dx/2, dy/2);
                }
                
                if (pos < em_height-1){//Draw empty progress bar
                    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);
                }
            }
         }
    }
}

The main thing here is gr_blit. Let's take a look at this function.

/*
*source:Picture data drawn
*sx/sy:This is a pair of coordinates, indicating from which point to start drawing, this is a relative dx, dy data.
*w/h: source Length and width
*dx/dy: From which point do you begin to draw?
*/
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;//Point to the data to be drawn
    unsigned char* dst_p = gr_draw->data + dy*gr_draw->row_bytes + dx*gr_draw->pixel_bytes;//Point to where you're going to draw.

    int i;
	//Copy the data over
    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;
    }
}

So far, data has only been put into the display buffer, and can not be displayed. To display, you need to call the function gr_flip(). This function is to display the data in the buffer to the screen.

7.Recovery UI updates

Now let's talk about how Recovery UI gets updated messages. The process of Recovery upgrade has been described before, and I won't go into details here. Direct will be the key part.

static int
try_update_binary(const char* path, ZipArchive* zip, bool* wipe_cache) {
	···
	//Get the upgraded bin file from the upgrade package, 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);Read into the handle
	
    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;
	//New thread with open body
    pid_t pid = fork();
    if (pid == 0) {
        umask(022);
        close(pipefd[0]);
        execv(binary, (char* const*)args);//Running update-binary in a subprocess
        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");//Read messages from child processes in parent processes
    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;
}

Here's a brief posting of the scripts in the ota upgrade package

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

Let's see how the ui_print function is implemented.

RegisterFunction("ui_print", UIPrintFn);

Value* UIPrintFn(const char* name, State* state, int argc, Expr* argv[]) {
    char** args = ReadVarArgs(state, argc, argv);
	//Middle
    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");
}

This way, the parent process receives the ui_print command and processes the display on the UI.
Recovery's UI is over here.
Font pictures:

Posted by jstone3503 on Thu, 09 May 2019 13:12:39 -0700