Android7.x 通过Recovery保留特定文件实现恢复出厂设置后保留系统语言设置

4 篇文章 0 订阅
3 篇文章 0 订阅
本文介绍了如何在Android 7.x的Recovery模式下,保留系统语言设置,即使执行出厂设置也不丢失。通过理解Recovery的文件保存机制,重点讲解了如何在缓存分区中保存`/cache/persist.prop`文件,以及如何在设置更新时将语言保存到此文件,最后说明了如何在系统启动时读取这些自定义设置。
摘要由CSDN通过智能技术生成

Android7.x 通过Recovery保留特定文件实现恢复出厂设置后保留系统语言设置

最近有一个需求,要求在恢复出厂设置之后不还原语言设置,由于我们知道语言设置可以在Properties System中保存,所以首先想到的就是能不能将某个property保存下来。

恢复出厂设置不影响的文件持久化保存方法

经过研究发现,有几种方法可以实现,可以做一个新的分区,并且在恢复出厂时不擦除。
但后来发现Recovery在恢复出厂的时候会保留/cache/recovery目录中的部分文件,因为我们的需求只是保留个别配置,感觉这个方式更合适。
因此参照这个过程照葫芦画瓢搞一个,我们先看关键代码:

// bootable\recovery.cpp
 
// 清理的入口函数为:
static bool wipe_data(int should_confirm, Device* device);
{
    //.....
    //往里追踪可以看到关键语句,实际上是跳进了erase_volume方法
    ui->Print("\n-- Wiping data...\n");
    bool success =
        device->PreWipeData() &&
        erase_volume("/data") &&
        (has_cache ? erase_volume("/cache") : true) &&
        device->PostWipeData();
    ui->Print("Data wipe %s.\n", success ? "complete" : "failed");
    return success;
}
 
//再来看这个操作
bool erase_volume(const char* volume) {
    bool is_cache = (strcmp(volume, CACHE_ROOT) == 0);
    bool is_data = (strcmp(volume, DATA_ROOT) == 0);
    ui->SetBackground(RecoveryUI::ERASING);
    ui->SetProgressType(RecoveryUI::INDETERMINATE);
 
    saved_log_file* head = NULL;
 
    if (is_cache) {
        // If we're reformatting /cache, we load any past logs
        // (i.e. "/cache/recovery/last_*") and the current log
        // ("/cache/recovery/log") into memory, so we can restore them after
        // the reformat.
        // 可以看到这里其实已经写明了,会将既往logs先存到内存里,然后再格式化完成后在进行
        //  为此,要先确保待擦除的该cache分区已经挂载了
        ensure_path_mounted(volume);
        
        DIR* d;
        struct dirent* de;
        d = opendir(CACHE_LOG_DIR);
        if (d) {
            char path[PATH_MAX];
            strcpy(path, CACHE_LOG_DIR);
            strcat(path, "/");
            int path_len = strlen(path);
            while ((de = readdir(d)) != NULL) {
                //然后扫描所有符合条件的文件名,并分配结构体所需的内存。
                if (strncmp(de->d_name, "last_", 5) == 0 || strcmp(de->d_name, "log") == 0 || strncmp(de->d_name, "Recovery_", 9) == 0) {
                    saved_log_file* p = (saved_log_file*) malloc(sizeof(saved_log_file));
                    strcpy(path+path_len, de->d_name);
                    p->name = strdup(path);
                    if (stat(path, &(p->st)) == 0) {
                        // truncate files to 512kb
                        if (p->st.st_size > (1 << 19)) {
                            p->st.st_size = 1 << 19;
                        }
                        //分配文件本身内容所需的内存,并将其读出,至此完成将内容保存至内存中
                        p->data = (unsigned char*) malloc(p->st.st_size);
                        FILE* f = fopen(path, "rb");
                        fread(p->data, 1, p->st.st_size, f);
                        fclose(f);
                        //通过一个链表保存所有的文件
                        p->next = head;
                        head = p;
                    } else {
                        free(p);
                    }
                }
            }
            closedir(d);
        } else {
            if (errno != ENOENT) {
                printf("opendir failed: %s\n", strerror(errno));
            }
        }
    }
    ui->Print("Formatting %s...\n", volume);
    ensure_path_unmounted(volume);
    //....还有很多代码省略了,后面的流程就是接触挂载,调用系统api format_volume格式化分区,然后重新挂载并将内存中的文件写入到分区里。
    
    if (is_cache) {
        while (head) {
            FILE* f = fopen_path(head->name, "wb");
            if (f) {
                fwrite(head->data, 1, head->st.st_size, f);
                fclose(f);
                chmod(head->name, head->st.st_mode);
                chown(head->name, head->st.st_uid, head->st.st_gid);
            }
            free(head->name);
            free(head->data);
            saved_log_file* temp = head->next;
            free(head);
            head = temp;
            //上面这块是回写到文件的步骤,同时也包括了设置文件权限的操作。
        }
}

上面就是recovery里保存其自身log文件的方法,既然知道原理,而我们目的只是保存一个文件,那么就不需要搞的那么复杂,实现一个自己的操作:

//定义文件最大大小
#define LOCAL_FILE_LEN 1024

//指针,用来开辟一块内存,存放文件内容
static char *p_locale_buffer = NULL;
//记录从文件中读出的字节数
static int locale_file_copy_cnt = 0;
//路径
static const char *LOCALE_FILE_PATH = "/cache/persist.prop";

//将文件读出到内存里,可以同样放在erase_volume中,也可以放到wipe_data里,我这里是放在wipe_data的erase_volumn调用之前
    {
        ui->Print("\nStorage persist prop...\n");
        p_locale_buffer = (char*)malloc(LOCAL_FILE_LEN);
        int res;
        if(NULL == p_locale_buffer){
            printf("malloc locale failed!\n");
        }else{
            memset(p_locale_buffer, 0, LOCAL_FILE_LEN);
            if( copyFileToMem(LOCALE_FILE_PATH, p_locale_buffer, &locale_file_copy_cnt, LOCAL_FILE_LEN)  < 0 ){
                ui->Print("copyFileToMem failed: %s\n", LOCALE_FILE_PATH);
            }else{
                ui->Print("Persist prop file copied, total: %d byte.\n", locale_file_copy_cnt);
            }
        }
    }
    
//待erase_volumn返回后,再重新回写
    {
        if(p_locale_buffer != NULL){
            if(copyFileFromMem(LOCALE_FILE_PATH, p_locale_buffer, locale_file_copy_cnt) < 0){
                ui->Print("copyFileFromMem failed: %s\n", LOCALE_FILE_PATH);
            }else{
                ui->Print("Persist prop file restored.\n");
            }
            free(p_locale_buffer);
            p_locale_buffer = NULL;
        }
    }

//上面所使用的copyFileToMem/FromMem如下,基本文件IO,没什么好说的:
static int copyFileToMem(const char *path,char *p,int *count,int size) {
    char *tmp = p;
    chmod(path,0770);
    chown(path,1000,1000);
    FILE* fd = fopen_path(path,"r");

    if(NULL == fd) {
        printf("open %s failed %d(%s)\n",path,errno,strerror(errno));
        return -1;
    }

    int res = 0;
    char buf[50] = {0};
    while((res = fread(buf,1,sizeof(buf),fd)) > 0) {
        *count += res;
        if (*count <= size) {
            memcpy(tmp,buf,res);
            tmp += res;
        } else {
            *count -= res;
            printf("size overflow");
            break;
        }
    }
    fclose(fd);
    return 0;
}


static int copyFileFromMem(const char *path,char *p,int count) {
    FILE* fd = fopen_path(path,"w+");
    //设置下文件权限,这里的644是必要的,否则就需要改init中的util.cpp,不然init会因为权限问题认为该文件是insecure的,拒绝读取
    chmod(path,0644);
    chown(path,1000,1000);
    if(NULL == fd) {
        printf("open %s failed %d(%s)\n",path,errno,strerror(errno));
        return -1;
    }
    int res = 0;
    if ((res = fwrite(p,1,count,fd)) > 0) {
        printf("write done\n");
    }
    fclose(fd);
    return 0;
}

这样一来就实现了文件的持久化保存,但这只是第一步,下面要将其作用在保存系统语言。

将配置写入持久化保存的文件

因为android的系统语言都是可以在property中定义的,所以首先想到的就是在底层的property_set的入口里加一个处理函数,根据property key来决定是不是写入到持久化的文件里,后来查找一下发现这个函数比较深,而且目前的需求只是写一个参数,所以想最小化它对系统代码的影响,所以直接移到设置入口里写。

Android 7.x中,语言设置在Settings的入口是这样的,实现了一个List的Drag&Drop交互,因为允许多个语言按优先级来显示,所以看起来有点复杂:
但是通过一顿搜索,发现最终实际都通过系统内部的一个接口类LocalePicker.updateLocales方法来更新系统语言,直接一顿查找,发现下面这个入口

//com.android.settings.localepicker 是的,同名
//import com.android.internal.app.LocalePicker; 上面说的内部接口类是这个
    public void updateLocalesWhenAnimationStops(final LocaleList localeList) {
        if (localeList.equals(mLocalesToSetNext)) {
            return;
        }
        // This will only update the Settings application to make things feel more responsive,
        // the system will be updated later, when animation stopped.
        LocaleList.setDefault(localeList);
        mLocalesToSetNext = localeList;
        final RecyclerView.ItemAnimator itemAnimator = mParentView.getItemAnimator();
        itemAnimator.isRunning(new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() {
            @Override
            public void onAnimationsFinished() {
                if (mLocalesToSetNext == null || mLocalesToSetNext.equals(mLocalesSetLast)) {
                    // All animations finished, but the locale list did not change
                    return;
                }
                //调用内部接口,更新系统语言,这里的mLocaleToSetNext是一个LocaleList,也是由内部提供的,由于系统支持按照语言优先级显示,实际上是对Locale的一个便利化封装,并且实现了序列化接口
                LocalePicker.updateLocales(mLocalesToSetNext);
                mLocalesSetLast = mLocalesToSetNext;
                mLocalesToSetNext = null;
                mNumberFormatter = NumberFormat.getNumberInstance(Locale.getDefault());
            }
        });
    }

发现入口之后直接在这里做手脚, 写一个工具方法将某个系统语言保存到持久化文件中,调用的方法直接插在LocalePicker.updateLocales调用的后面,只需要获取mLocalesToSetNext的第一位:

     public void saveLocaleToPersistProp(Locale locale){
        try {
            File output = new File("/cache/persist.prop");
            if (!output.exists()) output.createNewFile();
            FileOutputStream fos = new FileOutputStream(output);
            //因为最终是按照prop来读的,这里直接写进去文件里
            fos.write(("persist.sys.locale="+locale.toLanguageTag()).getBytes());
            Log.e("GCAT DBG", "Write to persist prop.");
            fos.flush();
            fos.close();
        } catch (Exception ex) {
            Log.e("GCAT DBG", "Error when writing file.");
            ex.printStackTrace();
        }
     }

顺带一提,实际负责property写入的其实是在ActivityManager里

//frameworks\base\services\core\java\com\android\server\am\ActivityManagerService.java
private boolean updateConfigurationLocked(Configuration values, ActivityRecord starting,
            boolean initLocale, boolean persistent, int userId, boolean deferResume) {
        int changes = 0;

        if (mWindowManager != null) {
            mWindowManager.deferSurfaceLayout();
        }
        if (values != null) {
            Configuration newConfig = new Configuration(mConfiguration);
            changes = newConfig.updateFrom(values);
            if (changes != 0) {
                if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.i(TAG_CONFIGURATION,
                        "Updating configuration to: " + values);

                EventLog.writeEvent(EventLogTags.CONFIGURATION_CHANGED, changes);

                if (!initLocale && !values.getLocales().isEmpty() && values.userSetLocale) {
                    final LocaleList locales = values.getLocales();
                    int bestLocaleIndex = 0;
                    if (locales.size() > 1) {
                        if (mSupportedSystemLocales == null) {
                            mSupportedSystemLocales =
                                    Resources.getSystem().getAssets().getLocales();
                        }
                        bestLocaleIndex = Math.max(0,
                                locales.getFirstMatchIndex(mSupportedSystemLocales));
                    }
                    SystemProperties.set("persist.sys.locale",
                            locales.get(bestLocaleIndex).toLanguageTag());
                    LocaleList.setDefault(locales, bestLocaleIndex);
                    mHandler.sendMessage(mHandler.obtainMessage(SEND_LOCALE_TO_MOUNT_DAEMON_MSG,
                            locales.get(bestLocaleIndex)));
                }

                mConfigurationSeq++;
                if (mConfigurationSeq <= 0) {
                    mConfigurationSeq = 1;
                }
                newConfig.seq = mConfigurationSeq;
                mConfiguration = newConfig;
                Slog.i(TAG, "Config changes=" + Integer.toHexString(changes) + " " + newConfig);
                mUsageStatsService.reportConfigurationChange(newConfig,
                        mUserController.getCurrentUserIdLocked());
                //mUsageStatsService.noteStartConfig(newConfig);

                final Configuration configCopy = new Configuration(mConfiguration);

                // TODO: If our config changes, should we auto dismiss any currently
                // showing dialogs?
                mShowDialogs = shouldShowDialogs(newConfig, mInVrMode);

                AttributeCache ac = AttributeCache.instance();
                if (ac != null) {
                    ac.updateConfiguration(configCopy);
                }

                // Make sure all resources in our process are updated
                // right now, so that anyone who is going to retrieve
                // resource values after we return will be sure to get
                // the new ones.  This is especially important during
                // boot, where the first config change needs to guarantee
                // all resources have that config before following boot
                // code is executed.
                mSystemThread.applyConfigurationToResources(configCopy);

                if (persistent && Settings.System.hasInterestingConfigurationChanges(changes)) {
                    Message msg = mHandler.obtainMessage(UPDATE_CONFIGURATION_MSG);
                    msg.obj = new Configuration(configCopy);
                    msg.arg1 = userId;
                    mHandler.sendMessage(msg);
                }

                final boolean isDensityChange = (changes & ActivityInfo.CONFIG_DENSITY) != 0;
                if (isDensityChange) {
                    // Reset the unsupported display size dialog.
                    mUiHandler.sendEmptyMessage(SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG);

                    killAllBackgroundProcessesExcept(Build.VERSION_CODES.N,
                            ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
                }

                for (int i=mLruProcesses.size()-1; i>=0; i--) {
                    ProcessRecord app = mLruProcesses.get(i);
                    try {
                        if (app.thread != null) {
                            if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Sending to proc "
                                    + app.processName + " new config " + mConfiguration);
                            app.thread.scheduleConfigurationChanged(configCopy);
                        }
                    } catch (Exception e) {
                    }
                }
                Intent intent = new Intent(Intent.ACTION_CONFIGURATION_CHANGED);
                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                        | Intent.FLAG_RECEIVER_REPLACE_PENDING
                        | Intent.FLAG_RECEIVER_FOREGROUND);
                broadcastIntentLocked(null, null, intent, null, null, 0, null, null,
                        null, AppOpsManager.OP_NONE, null, false, false,
                        MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
                if ((changes&ActivityInfo.CONFIG_LOCALE) != 0) {
                    intent = new Intent(Intent.ACTION_LOCALE_CHANGED);
                    intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
	            if (initLocale || !mProcessesReady) {
                        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
                    }
                    broadcastIntentLocked(null, null, intent,
                            null, null, 0, null, null, null, AppOpsManager.OP_NONE,
                            null, false, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
                }
            }
            // Update the configuration with WM first and check if any of the stacks need to be
            // resized due to the configuration change. If so, resize the stacks now and do any
            // relaunches if necessary. This way we don't need to relaunch again below in
            // ensureActivityConfigurationLocked().
            if (mWindowManager != null) {
                final int[] resizedStacks = mWindowManager.setNewConfiguration(mConfiguration);
                if (resizedStacks != null) {
                    for (int stackId : resizedStacks) {
                        final Rect newBounds = mWindowManager.getBoundsForNewConfiguration(stackId);
                        mStackSupervisor.resizeStackLocked(
                                stackId, newBounds, null, null, false, false, deferResume);
                    }
                }
            }
        }

让系统启动时读取我们自己持久化的property

上面完成了文件持久化和设置语言时的保存,最后一步就是使系统加载时读取:
这点可以直接修改init对应的property_service来实现

//system\core\init\property_service.cpp
//这个文件实际负责系统启动时的property初始化,其入口
void load_system_props(){
    load_properties_from_file(PROP_PATH_SYSTEM_BUILD, NULL);
    load_properties_from_file(PROP_PATH_VENDOR_BUILD, NULL);
    
    //加入自定义配置的读取,这里已经有现成的方法直接从文件中读取并解析到property system里了
    ERROR("Loading CACHE Prop....\n");
    load_properties_from_file("/cache/persist.prop", NULL);

    load_properties_from_file(PROP_PATH_FACTORY, "ro.*");
    load_recovery_id_prop();
}


//这个方法会被->init\builtins.cpp注册到一个map中,并且由builtins.h提供一个类。
//最终在主入口init.cpp的main函数里Action::set_function_map(&function_map);中注册
//然后通过响应请求load_system_props来调用
  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值