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 512kbif (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() {@Overridepublic void onAnimationsFinished() {if (mLocalesToSetNext == null || mLocalesToSetNext.equals(mLocalesSetLast)) {// All animations finished, but the locale list did not changereturn;}//调用内部接口,更新系统语言,这里的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来调用

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

  1. python技巧:将文件夹下的文件遍历,保留特定文件,删除其他文件

    在一个文件夹中保留特定文件,删除其他文件的操作 import os path='C:\\Users\\123\\Desktop\\data' #上一级文件夹路径 a=os.path.join(path ...

  2. linux 文件 字符集设置,Linux字符集和系统语言设置-LANG,locale,LC_ALL,POSIX等命令及参数详解...

    博文说明[前言]: 本文将通过个人口吻介绍Linux字符集和系统语言设置,包括LANG,locale,LC_ALL,POSIX等命令及参数详解的相关知识,在目前时间点[2017年6月21号]下,所掌握 ...

  3. Android 11.0 进入recovery模式(等待用户选择recovery模式界面)进入自动恢复出厂设置模式

    1.概述 在定制11.0的产品的时候,由于没有音量键 所以用音量键和电源键来选择recovery模式就无法实现了 所以当进入recovery选择模式界面 就一直停在那里 根据需要 要修改成进入等待用户 ...

  4. Android 10.0 进入recovery模式(等待用户选择recovery模式界面)实现自动恢复出厂设置

    1.概述 在10.0 的产品由于没有音量键 所以用音量键和电源键来选择recovery模式就无法实现了 所以当进入 recovery选择模式界面 就一直停在那里 根据需要 要修改成进入等待用户选择re ...

  5. Android 12.0 进入recovery模式(等待用户选择recovery模式界面)进入自动恢复出厂设置模式

    目录 1.概述 2.进入recovery模式(等待用户选择recovery模式界面)进入自动恢复出厂设置模式的核心类

  6. 云 文件 服务器 文件怎么恢复出厂设置密码,云服务器数据怎么恢复出厂设置密码...

    云服务器数据怎么恢复出厂设置密码 内容精选 换一换 如果Linux操作系统云服务器未安装密码重置插件,可以参见本节内容重新设置密码.本节操作重置的是root用户的密码,您可以重置完root密码后登录云 ...

  7. 计算机彻底删除的文件如何恢复出厂设置,电脑的恢复出厂设置在哪里 如果是出厂设置肯定是删除所有...

    导语:讲到电脑,大家都了解,有人问电脑怎么进入恢复出厂设置,当然了,还有人问怎么让笔记本电脑恢复出厂设置,这到底怎么回事呢?实际上win7台式电脑如何连接手机热点呢,下面是小编推荐给大家的电脑的恢复出 ...

  8. 计算机彻底删除的文件如何恢复出厂设置,普通电脑怎么恢复出厂设置

    恢复出厂设置的方法:按开始按键点击[控制面板],然后再点击[备份和还原],如果有很重要系统文件建议先Windows备份,如果没有直接点击下方的[恢复系统设置或计算机],然后点击[打开系统还原],按照提 ...

  9. ubuntu16.04 xfce4的鼠标主题设置为oxygen-red、修改文件夹背景颜色、两处系统字体设置、右键菜单添加压缩解压选项

    安装 oxygen-cursor-theme oxygen-cursor-theme-extra 然后 设置=>鼠标和触摸板 如果是自定义鼠标主题,那么把主题文件解压后拷贝到: /usr/sha ...

最新文章

  1. flask部署机器学习_如何开发端到端机器学习项目并使用Flask将其部署到Heroku
  2. 人工智能正在向具有“高情商”发展
  3. 七夕节,阿里云AI发女朋友了
  4. 检验两组数据是否显著差异_SPSS非参数两个相关样本检验
  5. 参加JavaEE培训需要什么条件
  6. 安卓APP_ 四大基本组件(1)—— Activity
  7. 什么样的项目是成功的?
  8. 到底什么是“机器学习”?机器学习有哪些基本概念?(简单易懂)
  9. openGauss训练营第二期结营!一百个QA和PPT合辑大放送!随附105人结营学员名单...
  10. AI学习笔记(四)相机模型、图像聚类算法
  11. mysql---存储过程和函数
  12. 自定义OutputFormat案例实操
  13. 修改linux的文件权限命令 chmod
  14. android生成kml和kmz
  15. jackson java用法_Jackson 使用方法总结
  16. 一篇文章让你彻底了解算法的时间复杂度O(n)!!!
  17. c++ IO多路复用
  18. 【C语言】设计实现M*N矩阵和N*M矩阵相乘
  19. 强化学习--实验一倒立摆
  20. 关于命令: nohup java -jar xxx.jar log.log 21 的解释。

热门文章

  1. 刷题第45, 46天 | 70. 爬楼梯 (进阶)、322. 零钱兑换、279.完全平方数、139.单词拆分
  2. CAPI 初探及使用小结(3)
  3. HDU 5366-The mook jong(组合数学)
  4. h5 页面判段是否是微信内,是否是IOS,区分qq内置浏览器和QQ浏览器
  5. Mybatis generator的简单使用
  6. java怎样保存图片_使用Java保存风景图片
  7. C++与C语言、python的区别? 信奥赛的有没有用? 如何进行编程学习规划?
  8. 什么是RTP、RTCP和RSVP?
  9. 2021年中国医药行业发展现状及重点企业对比分析[图]
  10. java 两个list排序_java实现两个不同list对象合并后并排序