客户需求,要预置APK文件到data/media/0/System/APK文件下,也就是说客户要求预置资源文件到sdcard,系统第一次启动的时候,加载完谷歌向导之后,会启动客户的另外一个应用来选择是否安装预置到SD卡的应用文件,此应用可在文件浏览器中找到并且可安装卸载,恢复出厂设置之后,预置的文件APK文件保留。
如果只是预置可卸载的文件,恢复出厂设置不可恢复,很简单,只需要在Android.mk文件中加上:LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)就可以了。
不过这次需求是预置APK文件(非安装好的),在第一次启动之后加载完谷歌向导后,会启动客户另外一个已经预安装好的应用来选择是否要安装此应用,并且此应用可卸载,恢复出厂设置之后还要在SD卡中,预置文件到SD卡上比较简单,主要问题是恢复出厂设置还要在预置的APK文件还要在SD卡中。
下面直接给出解决方案:
1.预置到sdcard: 在项目的配置文件中,利用 PRODUCT_COPY_FILES 分别拷贝文件到system目录下与/data/media/0/System/APK,如下:
PRODUCT_COPY_FILES += \
tyconfig/tyOverSeaCommon/packages/apps/3rd_Oversea/archos/preset_apps/100Games_Archos_50_Neon_100_AB46.apk:data/media/System/APK/100Games_Archos_50_Neon_100_AB46.apk \
tyconfig/tyOverSeaCommon/packages/apps/3rd_Oversea/archos/preset_apps/100Games_Archos_50_Neon_100_AB46.apk:system/pre-install/100Games_Archos_50_Neon_100_AB46.apk
这样保证系统第一次启动的时候可以满足需求,之所以会要在system/pre-install/目录下拷贝一份,是为了备份一下这个文件,在恢复出厂设置的时候要把/system/pre-install目录下的文件拷贝到/data/media/文件下。
2.恢复出厂设置,文件可以恢复。
在恢复出厂设置这一块代码下手,恢复出厂设置会把/data分区跟/cache全部清除,data分区清除意味着预置的APK文件也将会被清除掉。所以,在/data,以及/cache清除完毕后,可以做一个操作,复制/system/pre-install/目录下的文件到/data/media/0/System/APK目录,这样就能够保证在恢复出厂设置之后,预置的APK文件还在sdcard中。
bootable/recovery/recovery.cpp
//拷贝方法,需要把 build/libs/host/include/host/CopyFile.h 与 /build/libs/host/CopyFile.c文件拷贝至recovery.cpp同级目录
#include "CopyFile.h"
static int
apply_pre_installed_apps() {
if (ensure_path_mounted("/data")) {
ui_print("copy_app_data: mount /data failed!");
return -1;
}
if (ensure_path_mounted("/system/")) {
ui_print("copy_app_data: mount /system failed!");
return -1;
}
//保证对应目录存在
if(mkdir("/data/media/",00777) != 0)
{
LOGE("create /media failed\n");
}
if(mkdir("/data/media/System/",00777) != 0)
{
LOGE("create /System failed\n");
}
if(mkdir("/data/media/System/APK/",00777) != 0)
{
LOGE("create /APK failed\n");
}
//ui_print("copy property app \n");
//开始拷贝
copyFile("/system/pre-install/", "/data/media/System/APK/", COPY_RECURSIVE);
//copyFile("/system/etc/permissions", "/data/mojl/", COPY_RECURSIVE);
chmod("/data/",0777);
chmod("/data/media/",0777);
chmod("/data/media/System/",0777);
chmod("/data/media/System/APK/",0777);
if(ensure_path_unmounted("/data") != 0 ){
ui->Print("\n-- system unmounted fail.\n");
}
if(ensure_path_unmounted("/system/") != 0){
ui->Print("\n-- data unmounted fail.\n");
}
sync(); // For good measure.
return 0;
}
static void
wipe_data(int confirm, Device* device) {
if (confirm) {
static const char** title_headers = NULL;
if (title_headers == NULL) {
const char* headers[] = { "Confirm wipe of all user data?",
" THIS CAN NOT BE UNDONE.",
"",
NULL };
title_headers = prepend_title((const char**)headers);
}
const char* items[] = { " No",
" No",
" No",
" No",
" No",
" No",
" No",
" Yes -- delete all user data", // [7]
" No",
" No",
" No",
NULL };
int chosen_item = get_menu_selection(title_headers, items, 1, 0, device);
if (chosen_item != 7) {
return;
}
}
ui->Print("\n-- Wiping data...\n");
device->WipeData();
erase_volume("/data");
erase_volume("/cache");
/* 在清除完data与cache之后开始拷贝 */
apply_pre_installed_apps();
erase_persistent_partition();
ui->Print("Data wipe complete.\n");
}
int
main(int argc, char **argv) {
time_t start = time(NULL);
redirect_stdio(TEMPORARY_LOG_FILE);
……
int status = INSTALL_SUCCESS;
if (update_package != NULL) {
……
} else if (wipe_data) {
if (device->WipeData()) status = INSTALL_ERROR;
if (erase_volume("/data")) status = INSTALL_ERROR;
if (wipe_cache && erase_volume("/cache")) status = INSTALL_ERROR;
if (erase_persistent_partition() == -1 ) status = INSTALL_ERROR;
ui->Print("status == "+status+" . \n");
if (status != INSTALL_SUCCESS) ui->Print("Data wipe failed.\n");
/* 在清除失败后也要做拷贝动作 */
apply_pre_installed_apps();
} else if (wipe_cache) {
if (wipe_cache && erase_volume("/cache")) status = INSTALL_ERROR;
if (status != INSTALL_SUCCESS) ui->Print("Cache wipe failed.\n");
} else if (!just_exit) {
status = INSTALL_NONE; // No command specified
ui->SetBackground(RecoveryUI::NO_COMMAND);
}
……
还需要在recovery目录下对应的Android.mk文件中增加对CopyFile.c文件的引用,LOCAL_SRC_FILES += CopyFile.c
还有一个问题,通过adb shell进入到data/media/目录下,可以看到有一个0目录,这里面就是手机内存里面的文件,其实,系统第一次启动时installd进程会自动将/data/media/里面的东西移动到/data/media/0/目录,代码如下
/framework/native/cmds/installd/Installd.c
int initialize_directories() {
int res = -1;
// Read current filesystem layout version to handle upgrade paths
char version_path[PATH_MAX];
snprintf(version_path, PATH_MAX, "%s.layout_version", android_data_dir.path);
int oldVersion;
if (fs_read_atomic_int(version_path, &oldVersion) == -1) {
oldVersion = 0;
}
int version = oldVersion;
// /data/user
char *user_data_dir = build_string2(android_data_dir.path, SECONDARY_USER_PREFIX);
// /data/data
char *legacy_data_dir = build_string2(android_data_dir.path, PRIMARY_USER_PREFIX);
// /data/user/0
char *primary_data_dir = build_string3(android_data_dir.path, SECONDARY_USER_PREFIX, "0");
if (!user_data_dir || !legacy_data_dir || !primary_data_dir) {
goto fail;
}
// Make the /data/user directory if necessary
if (access(user_data_dir, R_OK) < 0) {
if (mkdir(user_data_dir, 0711) < 0) {
goto fail;
}
if (chown(user_data_dir, AID_SYSTEM, AID_SYSTEM) < 0) {
goto fail;
}
if (chmod(user_data_dir, 0711) < 0) {
goto fail;
}
}
// Make the /data/user/0 symlink to /data/data if necessary
if (access(primary_data_dir, R_OK) < 0) {
if (symlink(legacy_data_dir, primary_data_dir)) {
goto fail;
}
}
if (version == 0) {
// Introducing multi-user, so migrate /data/media contents into /data/media/0
ALOGD("Upgrading /data/media for multi-user");
// Ensure /data/media
if (fs_prepare_dir(android_media_dir.path, 0770, AID_MEDIA_RW, AID_MEDIA_RW) == -1) {
goto fail;
}
// /data/media.tmp
char media_tmp_dir[PATH_MAX];
snprintf(media_tmp_dir, PATH_MAX, "%smedia.tmp", android_data_dir.path);
// Only copy when upgrade not already in progress
if (access(media_tmp_dir, F_OK) == -1) {
if (rename(android_media_dir.path, media_tmp_dir) == -1) {//将/data/media/目录切换成/data/media.tmp
ALOGE("Failed to move legacy media path: %s", strerror(errno));
goto fail;
}
}
// Create /data/media again
if (fs_prepare_dir(android_media_dir.path, 0770, AID_MEDIA_RW, AID_MEDIA_RW) == -1) {//重新创建/data/meida目录
goto fail;
}
if (selinux_android_restorecon(android_media_dir.path, 0)) {
goto fail;
}
// /data/media/0
char owner_media_dir[PATH_MAX];
snprintf(owner_media_dir, PATH_MAX, "%s0", android_media_dir.path);
// Move any owner data into place
if (access(media_tmp_dir, F_OK) == 0) {
if (rename(media_tmp_dir, owner_media_dir) == -1) {//将/data/media.tmp/切换成/data/meida/0目录
ALOGE("Failed to move owner media path: %s", strerror(errno));
goto fail;
}
}
……
// Persist layout version if changed
if (version != oldVersion) {
if (fs_write_atomic_int(version_path, version) == -1) {//将version写入到.layout_version中,做为renam成功标识。
ALOGE("Failed to save version to %s: %s", version_path, strerror(errno));
goto fail;
}
}
// Success!
res = 0;
}
通过上面的方法,确实可以实现需求。但是试用过程中,会有几率出现问题的,预置的文件有可能会被/data/media/0/0目录中,相当于多了一级0目录,这样就会导致谷歌向导结束后,客户应用扫描不到对应的APK文件导致无法安装。翻阅大量资料,有人认为是因为上面代码的稳定性依赖于函数fs_write_atomic_int()的原子操作性,并给出解决方案要在此函数调用后加入sync(),经过大量试验,此方法并没有生效。
有一个方法就是去判断,如果目录中多了一级0目录,那就去把这个目录删除,代码如下
/framework/native/cmds/installd/Installd.c
int initialize_directories() {
int res = -1;
// Read current filesystem layout version to handle upgrade paths
char version_path[PATH_MAX];
snprintf(version_path, PATH_MAX, "%s.layout_version", android_data_dir.path);
……
// /data/media/0
char owner_media_dir[PATH_MAX];
snprintf(owner_media_dir, PATH_MAX, "%s0", android_media_dir.path);
// Move any owner data into place
if (access(media_tmp_dir, F_OK) == 0) {
if (rename(media_tmp_dir, owner_media_dir) == -1) {
ALOGE("Failed to move owner media path: %s", strerror(errno));
goto fail;
}
}
/* 判断如果/data/meida/0/0目录存在*/
if (access("/data/media/0/0", F_OK) == 0) {
ALOGE("/data/media/0/0 is access now :");
int result = rename("/data/media/0/0/System", "/data/media/0/System");//rename目录
ALOGE("result ==== %i ",result);
if (result == -1) {//rename失败
ALOGE("Failed to raname /data/media/0/0 : %s", strerror(errno));
}else{
ALOGE("success to raname /data/media/0/0");
if(rmdir("/data/media/0/0") == -1){//rename成功后需要删除/data/media/0/0目录
ALOGE("Failed to rmdir /data/media/0/0: %s", strerror(errno));
}
}
}else{
ALOGE("/data/media/0/0 is not access .");
}
/* yueshuai 20150916 modify for Pre-install end*/
// Ensure media directories for any existing users
DIR *dir;
struct dirent *dirent;
char user_media_dir[PATH_MAX];
……
经过上述操作,所有问题都已经解决。