Android7.x 通过Recovery保留特定文件实现恢复出厂设置后保留系统语言设置
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保留特定文件实现恢复出厂设置后保留系统语言设置相关推荐
- python技巧:将文件夹下的文件遍历,保留特定文件,删除其他文件
在一个文件夹中保留特定文件,删除其他文件的操作 import os path='C:\\Users\\123\\Desktop\\data' #上一级文件夹路径 a=os.path.join(path ...
- linux 文件 字符集设置,Linux字符集和系统语言设置-LANG,locale,LC_ALL,POSIX等命令及参数详解...
博文说明[前言]: 本文将通过个人口吻介绍Linux字符集和系统语言设置,包括LANG,locale,LC_ALL,POSIX等命令及参数详解的相关知识,在目前时间点[2017年6月21号]下,所掌握 ...
- Android 11.0 进入recovery模式(等待用户选择recovery模式界面)进入自动恢复出厂设置模式
1.概述 在定制11.0的产品的时候,由于没有音量键 所以用音量键和电源键来选择recovery模式就无法实现了 所以当进入recovery选择模式界面 就一直停在那里 根据需要 要修改成进入等待用户 ...
- Android 10.0 进入recovery模式(等待用户选择recovery模式界面)实现自动恢复出厂设置
1.概述 在10.0 的产品由于没有音量键 所以用音量键和电源键来选择recovery模式就无法实现了 所以当进入 recovery选择模式界面 就一直停在那里 根据需要 要修改成进入等待用户选择re ...
- Android 12.0 进入recovery模式(等待用户选择recovery模式界面)进入自动恢复出厂设置模式
目录 1.概述 2.进入recovery模式(等待用户选择recovery模式界面)进入自动恢复出厂设置模式的核心类
- 云 文件 服务器 文件怎么恢复出厂设置密码,云服务器数据怎么恢复出厂设置密码...
云服务器数据怎么恢复出厂设置密码 内容精选 换一换 如果Linux操作系统云服务器未安装密码重置插件,可以参见本节内容重新设置密码.本节操作重置的是root用户的密码,您可以重置完root密码后登录云 ...
- 计算机彻底删除的文件如何恢复出厂设置,电脑的恢复出厂设置在哪里 如果是出厂设置肯定是删除所有...
导语:讲到电脑,大家都了解,有人问电脑怎么进入恢复出厂设置,当然了,还有人问怎么让笔记本电脑恢复出厂设置,这到底怎么回事呢?实际上win7台式电脑如何连接手机热点呢,下面是小编推荐给大家的电脑的恢复出 ...
- 计算机彻底删除的文件如何恢复出厂设置,普通电脑怎么恢复出厂设置
恢复出厂设置的方法:按开始按键点击[控制面板],然后再点击[备份和还原],如果有很重要系统文件建议先Windows备份,如果没有直接点击下方的[恢复系统设置或计算机],然后点击[打开系统还原],按照提 ...
- ubuntu16.04 xfce4的鼠标主题设置为oxygen-red、修改文件夹背景颜色、两处系统字体设置、右键菜单添加压缩解压选项
安装 oxygen-cursor-theme oxygen-cursor-theme-extra 然后 设置=>鼠标和触摸板 如果是自定义鼠标主题,那么把主题文件解压后拷贝到: /usr/sha ...
最新文章
- flask部署机器学习_如何开发端到端机器学习项目并使用Flask将其部署到Heroku
- 人工智能正在向具有“高情商”发展
- 七夕节,阿里云AI发女朋友了
- 检验两组数据是否显著差异_SPSS非参数两个相关样本检验
- 参加JavaEE培训需要什么条件
- 安卓APP_ 四大基本组件(1)—— Activity
- 什么样的项目是成功的?
- 到底什么是“机器学习”?机器学习有哪些基本概念?(简单易懂)
- openGauss训练营第二期结营!一百个QA和PPT合辑大放送!随附105人结营学员名单...
- AI学习笔记(四)相机模型、图像聚类算法
- mysql---存储过程和函数
- 自定义OutputFormat案例实操
- 修改linux的文件权限命令 chmod
- android生成kml和kmz
- jackson java用法_Jackson 使用方法总结
- 一篇文章让你彻底了解算法的时间复杂度O(n)!!!
- c++ IO多路复用
- 【C语言】设计实现M*N矩阵和N*M矩阵相乘
- 强化学习--实验一倒立摆
- 关于命令: nohup java -jar xxx.jar log.log 21 的解释。
热门文章
- 刷题第45, 46天 | 70. 爬楼梯 (进阶)、322. 零钱兑换、279.完全平方数、139.单词拆分
- CAPI 初探及使用小结(3)
- HDU 5366-The mook jong(组合数学)
- h5 页面判段是否是微信内,是否是IOS,区分qq内置浏览器和QQ浏览器
- Mybatis generator的简单使用
- java怎样保存图片_使用Java保存风景图片
- C++与C语言、python的区别? 信奥赛的有没有用? 如何进行编程学习规划?
- 什么是RTP、RTCP和RSVP?
- 2021年中国医药行业发展现状及重点企业对比分析[图]
- java 两个list排序_java实现两个不同list对象合并后并排序