一、Recovery系統簡介
??Recovery模式指的是一種可以對安卓機內部的資料或系統進行修改的模式(類似于windows PE或DOS),在這個模式下我們可以刷入新的Android系統,或者對已有的系統進行備份或升級,也可以在此模式下恢復出廠設定,系統進入recovery模式后會裝載recovery磁區,該磁區包含recovery.img(與boot.img類似,也包含了標準的內核和根檔案系統).進入該模式后主要就是運行了recovery服務(/sbin/recovery),
二、Recovery系統的啟動流程
2.1 進入Recovery系統的三種常見方法:
- 在關機情況下,同時按住電源(Power)+ 音量加(Vol +)鍵,直到出現Recovery界面為止,注:有的系統按鍵方式可能不同,
- 使用安卓輔助工具,如:刷機精靈、360手機助手等等,
- 使用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,這個檔案的主要作用就是:
- 設定環境變數,
- 建立etc連接,
- 新建目錄,備用,
- 掛載/tmp為記憶體檔案系統tmpfs
- 啟動recovery(/sbin/recovery)服務,
- 啟動adbd服務(用于除錯),
這里最重要的當然就是啟動recovery服務了,
重啟到recovery模式的流程圖如下:

三、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的作業需要整個軟體平臺的配合,從通信架構上來看,主要有三個部分,
- MainSystem:即上面提到的正常啟動模式(BCB中無命令情況),是引導boot.img啟動的系統,Android的正常作業模式,更新時,在這種模式下的操作就是往 /cache/recovery/command 檔案中寫入ota升級命令及包存放路徑,在重啟進入Recovery模式之前,會向BCB中寫入命令,以便在重啟后告訴bootloader進入Recovery模式,
- Recovery:系統進入Recovery模式后會裝載Recovery磁區,該磁區包含recovery.img,進入該模式后主要是運行Recovery服務(/sbin/recovery)來做相應的操作(重啟、升級update.zip、擦除cache磁區等),
- Bootloader:除了正常的加載啟動系統之外,還會通過讀取MISC磁區(BCB)獲得來至Main system和Recovery的訊息,
3.3 Recovery模式的兩個通信介面
?在Recovery服務中上述的三個物體之間的通信是必不可少的,他們相互之間又有以下兩個通信介面,
3.3.1 通過CACHE磁區中的三個檔案通信
Recovery通過/cache/recovery/目錄下的三個檔案與mian system通信,具體如下
-
/cache/recovery/command:這個檔案保存著Main system傳給Recovery的命令列,每一行就是一條命令,下表給出一些常用的命令及其含義:
命令 取值 含義 send_intent 字串 Recovery結束后將字串寫到這里,
然后寫入/cache/recovery/intent,比如升級結果update_package 路徑 安裝OTA升級包的路徑 wipe_data 無 擦除userdata以及cache,然后重啟 wipe_cache 無 擦除cache,然后重啟 set_encrypted_filesystem on off just_exit 無 退出和重啟 -
/cache/recovery/last_log:Recovery模式在作業中的log列印,在recovery服務運行程序中,stdout以及stderr會重定位到/tmp/recovery.log在recovery退出之前會將其轉存到/cache/recovery/last_log中,供查看,
-
/cache/recovery/intent:Recovery傳遞給Main system的資訊,列如反饋升級是否成功,
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];
};
- command成員:其可能的取值我們在上文已經分析過了,即當我們想要在重啟進入Recovery模式時,Main System會將boot-recovery命令寫入,另外在退出Recovery時,會清除這個成員的值,防止重啟時再次進入Recovery模式,
- status:在完成相應的更新后,Bootloader會將執行結果寫入到這個欄位,
- 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系統的三個部分和兩個通信介面的示意圖如下:

四、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方法執行的流程圖大概如下:

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界面的流程大概有下面幾步:
- 新建一個Device類的物件;
- 呼叫Device類的GetUI()回傳一個RecoveryUI物件,這里應該回傳的是ScreenRecoveryUI,ScreenRecoveryUI繼承于RecoveryUI;
- 呼叫Init()初始化UI;
- 呼叫RecoveryUi的init方法去設定國家語言,然后初始化輸入設備,并創建一個執行緒用于監聽輸入事件;
- 呼叫minui庫的gr_init方法初始化圖形顯示,主要是打開設備、分配記憶體、初始化一些引數;
- 通過LoadBitmap()加載png圖片生成surface物件
- 創建一個子執行緒更新progress進度條;
- 呼叫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包升級的時序圖如下:

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主要做了以下幾個操作:
- update_binary_command:決議升級包,將一些檔案描述資訊保存到args中,
- fork創建一個子行程 , 使用系統呼叫函式execv( ) 去執行/tmp/update-binary程式,
- update-binary: 這個是Recovery OTA升級的核心程式,是一個二進制檔案,實作代碼位于系統原始碼bootable/updater.cpp,其實質是相當于一個腳本解釋器,能夠識別updater-script中描述的操作并執行,
- 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界面總共只需要兩步:
- 旋轉gr_init中初始化的GRSurface的畫面長寬及data記憶體大小;
- 旋轉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的資料具體的操作流程如下圖:

?說明:第一個回圈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這個圖片的資源,這個圖片的內容大概如下:

?這個圖片資源很長,加載的時候會根據前面4.6章節設定的locale來加載相應的文字,然后生成surface資源,然后顯示;
?生成這個圖片資源的方法是:
- 修改/tools/recovery_l10n/res/values/string.xml 中的字串,如果有其他語言,比如中文,修改values-zh-rCN比如修改為Installing Lbb update…;
- 單獨編譯這個APK mmm ./bootable/recovery/tools/recovery_l10n/;
- 安裝apk后點擊go按鈕就會生成圖片到./data/data/com.android.recovery_l10n/files/目錄;
- 然后在linux下執行pngcrush -c 0 text-out.png output.png 獲得壓縮后的png;
- 然后放入/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方法就可以了,
七、小知識
- 如果recovery有問題啟動不了,那么會卡在開機log的第二幀上;
- recovery就是recovery.cpp編譯生成的可執行檔案(具體可以看recovery下的Android.mk);
- 平時使用的adb就是PC端adb通過socket連接sbin/adbd,執行的命令都是這個adbd執行的;
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/279616.html
標籤:其他
