diff --git a/bootable/recovery/device.h b/bootable/recovery/device.h
index 19e4bec..a719aa3 100644
--- a/bootable/recovery/device.h
+++ b/bootable/recovery/device.h
@@ -81,7 +81,7 @@ class Device {
APPLY_CACHE, // APPLY_CACHE is deprecated; has
APPLY_ADB_SIDELOAD, WIPE_DATA, WIPE_CACHE,
USER_DATA_BACKUP, USER_DATA_RESTORE, CHECK_ROO
- REBOOT_BOOTLOADER, SHUTDOWN, READ_RECOVERY_LAS
+ REBOOT_BOOTLOADER, SHUTDOWN, READ_RECOVERY_LAS
#endif
// Return the list of menu items (an array of strings,
// NULL-terminated). The menu_position passed to InvokeMenuItem
diff --git a/bootable/recovery/device.cpp b/bootable/recovery/device.cpp
index b4eb7e9..d388ae2 100644
--- a/bootable/recovery/device.cpp
+++ b/bootable/recovery/device.cpp
@@ -21,6 +21,7 @@ static const char* MENU_ITEMS[] = {
"Reboot to bootloader",
"Apply update from ADB",
"Apply update from SD card",
+ "Apply update from internal storage",
#if defined(SUPPORT_SDCARD2) && !defined(MTK_SHARED_SDCARD) //wschen 20
"Apply update from sdcard2",
#endif //SUPPORT_SDCARD2
@@ -62,32 +63,33 @@ Device::BuiltinAction Device::InvokeMenuItem(int men
case 1: return REBOOT_BOOTLOADER;
case 2: return APPLY_ADB_SIDELOAD;
case 3: return APPLY_EXT;
- case 4: return APPLY_SDCARD2;
- case 5: return WIPE_DATA;
- case 6: return WIPE_CACHE;
+ case 4: return APPLY_INTERNAL_STORAGE;
+ case 5: return APPLY_SDCARD2;
+ case 6: return WIPE_DATA;
+ case 7: return WIPE_CACHE;
#ifdef SUPPORT_DATA_BACKUP_RESTORE
- case 7: return USER_DATA_BACKUP;
- case 8: return USER_DATA_RESTORE;
+ case 8: return USER_DATA_BACKUP;
+ case 9: return USER_DATA_RESTORE;
#ifdef ROOT_CHECK
- case 9: return CHECK_ROOT;
+ case 10: return CHECK_ROOT;
+ case 11: return MOUNT_SYSTEM;
+ case 12: return READ_RECOVERY_LASTLOG;
+ case 13: return SHUTDOWN;
+#else
case 10: return MOUNT_SYSTEM;
case 11: return READ_RECOVERY_LASTLOG;
case 12: return SHUTDOWN;
+#endif
#else
+#ifdef ROOT_CHECK
+ case 8: return CHECK_ROOT;
case 9: return MOUNT_SYSTEM;
case 10: return READ_RECOVERY_LASTLOG;
case 11: return SHUTDOWN;
-#endif
#else
-#ifdef ROOT_CHECK
- case 7: return CHECK_ROOT;
case 8: return MOUNT_SYSTEM;
case 9: return READ_RECOVERY_LASTLOG;
case 10: return SHUTDOWN;
-#else
- case 7: return MOUNT_SYSTEM;
- case 8: return READ_RECOVERY_LASTLOG;
- case 9: return SHUTDOWN;
#endif
#endif
default: return NO_ACTION;
@@ -96,31 +98,32 @@ Device::BuiltinAction Device::InvokeMenuItem(int men
case 1: return REBOOT_BOOTLOADER;
case 2: return APPLY_ADB_SIDELOAD;
case 3: return APPLY_EXT;
- case 4: return WIPE_DATA;
- case 5: return WIPE_CACHE;
+ case 4: return APPLY_INTERNAL_STORAGE;
+ case 5: return WIPE_DATA;
+ case 6: return WIPE_CACHE;
#ifdef SUPPORT_DATA_BACKUP_RESTORE
- case 6: return USER_DATA_BACKUP;
- case 7: return USER_DATA_RESTORE;
+ case 7: return USER_DATA_BACKUP;
+ case 8: return USER_DATA_RESTORE;
#ifdef ROOT_CHECK
- case 8: return CHECK_ROOT;
+ case 9: return CHECK_ROOT;
+ case 10: return MOUNT_SYSTEM;
+ case 11: return READ_RECOVERY_LASTLOG;
+ case 12: return SHUTDOWN;
+#else
case 9: return MOUNT_SYSTEM;
case 10: return READ_RECOVERY_LASTLOG;
case 11: return SHUTDOWN;
+#endif
#else
+#ifdef ROOT_CHECK
+ case 7: return CHECK_ROOT;
case 8: return MOUNT_SYSTEM;
case 9: return READ_RECOVERY_LASTLOG;
case 10: return SHUTDOWN;
-#endif
#else
-#ifdef ROOT_CHECK
- case 6: return CHECK_ROOT;
case 7: return MOUNT_SYSTEM;
case 8: return READ_RECOVERY_LASTLOG;
case 9: return SHUTDOWN;
-#else
- case 6: return MOUNT_SYSTEM;
- case 7: return READ_RECOVERY_LASTLOG;
- case 8: return SHUTDOWN;
#endif
#endif
default: return NO_ACTION;
diff --git a/bootable/recovery/mt_recovery.cpp b/bootable/recovery/mt_re
index ea60afb..03fbd96 100644
--- a/bootable/recovery/mt_recovery.cpp
+++ b/bootable/recovery/mt_recovery.cpp
@@ -32,6 +32,7 @@ static const char *SDCARD_ROOT = "/sdcard";
static const char *TEMPORARY_LOG_FILE = "/tmp/recovery.log";
static const char *TEMPORARY_INSTALL_FILE = "/tmp/last_install";
static const char *MT_UPDATE_STAGE_FILE = "/cache/recovery/last_mtupdat
+static const char *INTERNAL_STORAGE = "/data/media/0";
extern char* stage;
extern RecoveryUI* ui;
@@ -708,6 +709,11 @@ mt_prompt_and_wait(Device* device, int status) {
ui->Print("Mounted /system.\n");
}
break;
+ case Device::APPLY_INTERNAL_STORAGE:
+ modified_flash = true;
+ if (mt_prompt_and_wait_install(device, status, CACH
+ return ret;
+ break;
default:
break;
}
另外,如果没插sd卡而从sd卡升级,会有很长时间的等待,查看日志可知重新挂载了很多次,
调用过程是选择升级方式后会调用mt_recovery.cpp中mt_prompt_and_wait_install-->ensure_path_mounted-->
其中
mt_ensure_dev_ready:
while ((count++ < 5) && (access(v->blk_device, R_OK) != 0)) {
printf("no %s entry %s, count = %d\n", mount_point, v->blk_device, count);
sleep(1);
}
另外mt_ensure_path_mounted被调用了3次,browse_directory也调用了一次
在Recovery模式首页,Recovery系统通过调用GetMenuItems()函数向我们展示了一个选项列表,当有按键操作发生时,系统会通过HandleMenuKey()函数来处理按键操作。通常情况下我们可以通过操作VolumeUp和VolumeDown来切换选项。原生环境下,如果当前选项为首项或尾项,我们想要切换至尾项或首项时,不得不频繁操作VolumeUp和VolumeDown进行切换。影响用户体验。那么如果当前选项为首项或尾项时,我们如何通过一次操作进行首尾项的切换呢?
首先我们来看Recovery系统处理按键的函数HandleMenuKey()。
源码地址:https://android.googlesource.com/platform/bootable/recovery/+/android-5.1.1_r2/default_device.cpp
int HandleMenuKey(int key, int visible) {
if (visible) {
switch (key) {
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;
}
通过上面这段代码了解到,Recovery系统是通过操作VolumeUp来向上切换选项,通过VolumeDown向下切换选项,且通过Power按键进入选项。因此在Recovery.cpp的get_menu_selection()函数中,Recovery系统通过调用上面的HandleMenuKey()函数判断当前动作。然后再根据动作作出相应的处理。这里的处理也就是切换选项,主要是告诉系统用户选择的菜单项和对屏幕中的菜单项进行高亮显示。
源码地址:https://android.googlesource.com/platform/bootable/recovery/+/android-5.1.1_r2/recovery.cpp
static int
get_menu_selection(const char* const * headers, const char* const * items,
int menu_only, int initial_selection, Device* device) {
// throw away keys pressed previously, so user doesn't
// accidentally trigger menu items.
ui->FlushKeys();
ui->StartMenu(headers, items, initial_selection);
int selected = initial_selection;
int chosen_item = -1;
while (chosen_item < 0) {
int key = ui->WaitKey();
int visible = ui->IsTextVisible();
if (key == -1) { // ui_wait_key() timed out
if (ui->WasTextEverVisible()) {
continue;
} else {
LOGI("timed out waiting for key input; rebooting.\n");
ui->EndMenu();
return 0; // XXX fixme
}
}
int action = device->HandleMenuKey(key, visible);
if (action < 0) {
switch (action) {
case Device::kHighlightUp:
--selected;
selected = ui->SelectMenu(selected);
break;
case Device::kHighlightDown:
++selected;
selected = ui->SelectMenu(selected);
break;
case Device::kInvokeItem:
chosen_item = selected;
break;
case Device::kNoAction:
break;
}
} else if (!menu_only) {
chosen_item = action;
}
}
ui->EndMenu();
return chosen_item;
}
上面标注为红色的代码也就是Recovery系统相应按键作出的选项切换的动作。也就是说如果当前用户按下一次VolumeUp健,Recovery系统就会向上切换选项菜单。但是如果当前选项菜单处于首项或尾项时,Recovery系统就会调用screen_ui.cpp中的SelectMenu()函数进行进一步的处理。
源码地址:https://android.googlesource.com/platform/bootable/recovery/+/android-5.1.1_r2/screen_ui.cpp
int ScreenRecoveryUI::SelectMenu(int sel) {
int old_sel;
pthread_mutex_lock(&updateMutex);
if (show_menu > 0) {
old_sel = menu_sel;
menu_sel = sel;
if (menu_sel < 0) menu_sel = 0;//这里表示如果当前项为首项,按键操作为向上切换时,保持首项不变。
if (menu_sel >= menu_items) menu_sel = menu_items-1;//这里表示如果当前项为尾项,按键操作为向下切换时,保持尾项不变。
sel = menu_sel;
if (menu_sel != old_sel) update_screen_locked();//重绘screen刷新界面
}
pthread_mutex_unlock(&updateMutex);
return sel;
}
上面红色部分即原生对当前项为首项或尾项时,切换选项所作出的处理。对于处理的方法在上面的备注中已经有相应的注释。那么我们想要实现循环切换选项的突破口也就是这个函数了。下面是修改后的函数:
int ScreenRecoveryUI::SelectMenu(int sel) {
int old_sel;
pthread_mutex_lock(&updateMutex);
if (show_menu > 0) {
old_sel = menu_sel;
menu_sel = sel;
if (menu_sel < 0) {
menu_sel = menu_items-1;//这里表示如果当前项为首项,按键操作为向上切换时,切换至尾项
else if (menu_sel >= menu_items){
menu_sel = 0;//这里表示如果当前项为尾项,按键操作为向下切换时,切换至首项
}
sel = menu_sel;
if (menu_sel != old_sel) update_screen_locked();//重绘screen刷新界面
}
pthread_mutex_unlock(&updateMutex);
return sel;
}
深入了解recovery源码前,先浏览下recovery能够给我们提供哪些功能;
在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/recovery/root/目录;
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 - 退出并重启
从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;
}
在自己的设备目录下:device/vendor/recovery/recovery_ui.cpp
#include
#include
#include
#include
#include "common.h"
#include "device.h"
#include "screen_ui.h"
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 };
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;
};
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 ;
}
主要是覆盖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)
最近在做一个手机项目,每次使用ZIP包升级都需要先拷到SD卡上,然后再从SD卡上升级。SD卡不好找,每次都要从别的手机上卸下来,升级完再装回去,麻烦的很。既然支持内置存储,为什么不能从内置存储上升级呢?貌似华为等手机都支持这个功能的。
进入recovery,看到默认是不挂载内置SD的,仅支持外部SD。首先要做的是把内置SD挂载上。
正常启动,adb shell,df查看当前内置SD卡是怎么挂载的。
这里看到是一个类似 /dev/block/vold/179:1 之类的设备,显然是被vold管理着的。根据设备号,到 /dev/block下 ls -l 一下,找到设备真正的名字,是mmcblkXpY之类的,其中XY是数字。先记下来。
打开recovery.fstab,加上一行
[plain] view plain copy
xxx就是挂载点了,vfat是格式,后面是刚才记下来的设备名。
打开recovery.c,在main()中加入一行
[cpp] view plain copy
编译运行,在通过ADB查看,/xxx这个目录已经被挂载上了,ls看一下,内容与正常开机时显示一样。
第一个问题解决了,内置SD卡可以正常挂载。下来一个问题是,如何在recovery添加菜单项和对应的功能。
查看代码,main()最后是用prompt_and_wait()函数来等待操作,在这个函数里,又是通过get_menu_selection()来获得当前菜单选择。打开default_recovery_ui.c,找到MENU_ITEMS,在其中增加一项。recovery_ui.h中增加对应的宏。回到prompt_and_wait(),仿照ITEM_APPLY_SDCARD,增加自己的处理流程。基本上都是一样的,只是把路径替换成了/xxx。
再编译,运行。
这里遇到了一个问题,运行后卡死,通过DEBUG发现是将文件从内置SD读入内存的时候莫名其妙的崩溃了,还看不到错误到底是怎么回事。于是修改了一下升级用的ZIP包,精简掉一些APK,文件体积变小了。再使用这个较小的ZIP,成功升级。
通过以上方法修改,可以成功从内置SD升级。只是不支持大文件的问题有些蹊跷,有时间再分析吧。
这篇文章是我转载过来的。我的问题是,我从华为的一个项目copy过来的时候,华为用自己独立的OTA升级的APK,而我自己开发了一个OTA的升级APK,之后可以进行SD卡升级,但是怎么样都无法进行内置存储升级,到目前,这还是一个问题,华为使用了一个/HWUser 这个目录。我的APK 找不到这个目录,没有把zip升级包复制进去。