问题背景:
因项目开发及测试需要,设备升级频率比较高,升级出现失败的情况肯定是有的,原因用多方面,如:故意使用非法的升级包,升级版本不匹配等等。
出现升级失败问题后,对于手机用户来说可以选择重启手机即可,而我们使用设备及环境不允许人为对其经常操作,所以如果升级失败,界面就停留在recovery界面,设备就无法正常工作。
解决方案:
设备需要实现一种自动恢复机制,自动重启设备,恢复到正常界面。
进入recovery升级界面,延迟20s后进行设备重启进入正常界面。
具体步骤:
android设备升级失败,往往会进入一个躺倒着的机器人界面:
进而进入到升级异常的界面,如下图:
对于我们项目的设备,该界面是无法进行选项操作的,一般到此界面,下发都有一些升级失败的提示,我们可以根据提示,在android源码里面找到该界面的显示绘制的地方。或者在利用界面关键词"Reboot system now"在android源码中搜索,这里我直接在androidxref上搜索,如下:
这样初步定位到该界面显示的代码在:bootable/recovery/下。
进一步分析源码,很容易得到界面选择的源码(http://androidxref.com/7.0.0_r1/xref/bootable/recovery/recovery.cpp#1046):
1025
1026// Return REBOOT, SHUTDOWN, or REBOOT_BOOTLOADER. Returning NO_ACTION
1027// means to take the default, which is to reboot or shutdown depending
1028// on if the --shutdown_after flag was passed to recovery.
1029static Device::BuiltinAction
1030prompt_and_wait(Device* device, int status) {
1031 for (;;) {
1032 finish_recovery(NULL);
1033 switch (status) {
1034 case INSTALL_SUCCESS:
1035 case INSTALL_NONE:
1036 ui->SetBackground(RecoveryUI::NO_COMMAND);
1037 break;
1038
1039 case INSTALL_ERROR:
1040 case INSTALL_CORRUPT:
1041 ui->SetBackground(RecoveryUI::ERROR);
1042 break;
1043 }
1044 ui->SetProgressType(RecoveryUI::EMPTY);
1045
1046 int chosen_item = get_menu_selection(nullptr, device->GetMenuItems(), 0, 0, device);
1047
1048 // device-specific code may take some action here. It may
1049 // return one of the core actions handled in the switch
1050 // statement below.
1051 Device::BuiltinAction chosen_action = device->InvokeMenuItem(chosen_item);
1052
1053 bool should_wipe_cache = false;
1054 switch (chosen_action) {
1055 case Device::NO_ACTION:
1056 break;
1057
1058 case Device::REBOOT:
1059 case Device::SHUTDOWN:
1060 case Device::REBOOT_BOOTLOADER:
1061 return chosen_action;
1062
1063 case Device::WIPE_DATA:
也可以看到源码(http://androidxref.com/7.0.0_r1/xref/bootable/recovery/recovery.cpp#155)
153 * 6. finish_recovery() erases BCB
154 * -- after this, rebooting will (try to) restart the main system --
155 * 7. ** if install failed **
156 * 7a. prompt_and_wait() shows an error icon and waits for the user
157 * 7b; the user reboots (pulling the battery, etc) into the main system
头文件注释说明,如果安装失败,recovery.cpp中prompt_and_wait会显示失败错误信息并等待人为操作。
而我们要做的就是将1046行get_menu_selection人为选择改成自动选择重启。
首先看下get_menu_selection的实现:
658static int
659get_menu_selection(const char* const * headers, const char* const * items,
660 int menu_only, int initial_selection, Device* device) {
661 // throw away keys pressed previously, so user doesn't
662 // accidentally trigger menu items.
663 ui->FlushKeys();
664
665 ui->StartMenu(headers, items, initial_selection);
666 int selected = initial_selection;
667 int chosen_item = -1;
668
669 while (chosen_item < 0) {
670 int key = ui->WaitKey();
671 int visible = ui->IsTextVisible();
672
673 if (key == -1) { // ui_wait_key() timed out
674 if (ui->WasTextEverVisible()) {
675 continue;
676 } else {
677 LOGI("timed out waiting for key input; rebooting.\n");
678 ui->EndMenu();
679 return 0; // XXX fixme
680 }
681 }
682
683 int action = device->HandleMenuKey(key, visible);
684
685 if (action < 0) {
686 switch (action) {
687 case Device::kHighlightUp:
688 selected = ui->SelectMenu(--selected);
689 break;
690 case Device::kHighlightDown:
691 selected = ui->SelectMenu(++selected);
692 break;
693 case Device::kInvokeItem:
694 chosen_item = selected;
695 break;
696 case Device::kNoAction:
697 break;
698 }
699 } else if (!menu_only) {
700 chosen_item = action;
701 }
702 }
703
704 ui->EndMenu();
705 return chosen_item;
706}
可以看到:选项超时返回的key值是-1
(if (key == -1) { // ui_wait_key() timed out).
我们可以继续跟踪代码(ui->WaitKey()),可以得知,在此界面等待用户选择的超时时间是120s,源码(http://androidxref.com/7.0.0_r1/xref/bootable/recovery/ui.cpp#41)。
#define UI_WAIT_KEY_TIMEOUT_SEC 120
故根据这个代码思路,我这边的修改方案:
1.将选择界面超时时间修改为20s.即:
#define UI_WAIT_KEY_TIMEOUT_SEC 20
2.将get_menu_selection代码中等待选项超时后,默认选择项改为0,代码如下:
可以看到代码677行,日志打印的意思:等待输入超时,重启,也是return 是0。可见,原生android在有菜单选项时android默认的continue等待用户继续输入,无菜单选项时默认返回的也是重启。
我们这里因为菜单可见,所以走到675行的continue,进而继续等待用户输入,所以我们这里修改方案改成等待用户选择超时的时候直接return 0给默认选择了重启从而达到目的。
文末思考:
为什么不将选择菜单界面设置未不可见让674行的条件不成立而是程序直接在else里面的" return 0;"?