Android11.0(R) MTK 预置可卸载app恢复出厂不恢复(仿RK方案)

简介: Android11.0(R) MTK 预置可卸载app恢复出厂不恢复(仿RK方案)

常规的预置可卸载 apk 并且恢复出厂不恢复,都是放到 data 目录下,也就是打包到 userdata.img 中。


这里列举几个缺点,


1、从 Q 开始谷歌默认不建议这样做了,所以在不修改源码情况下,你只要 data 中预置了东西,烧写后开机直接进入


recovery了提示必须清除数据才能正常开机。当然通过修改源码可以编译这个问题androidQ(10.0) 预装集成apk到data分区


即便是解决这个问题以后还有潜在的其它问题,比如第一次成功开机后,你并没有主动卸载apk,再一次重启后apk没了。


2、打包 ota 的时候,需要手动将上一次编译的 userdata.img 拷贝留存,不然再次编译会被覆盖,烧写后并没有预装 apk


3、预装 apk 大小过大时,也容易出现预置失败的情况。


参考 RK 解决方案,通过文件 last_deleteApkFile.dat 记录卸载过的 apk 包名,在 PMS 扫描安装时读取此文件进行过滤。


RK 这个解决方案还存在一个 bug,当你把 apk 卸载后包名被记录到 last_deleteApkFile.dat,你再次刷机后开机 apk 也不存在,


这显然是不符合正常需求的。


现在在 MTK 的平台上来实现并解决这个 bug,思路如下


1、MTK 本身存在配置可卸载白名单包名,文件路径 vendor\mediatek\proprietary\frameworks\base\data\etc\pms_sysapp_removable_system_list.txt


在里面添加预制可卸载 apk 包名,并将 apk 预装在 system 分区下,mk 中不用指定路径就行


2、MTK 自带节点 /mnt/vendor/protect_f/ 可存储恢复出厂+刷机不丢失数据,正好用来存放 last_deleteApkFile.dat


3、在 PMS 中增加读写 /mnt/vendor/protect_f/last_deleteApkFile.dat 逻辑,需要解决 selinux 权限(我这里偷懒了直接关闭源码里的 selinux)


4、区分是刷机后第一次启动还是恢复出厂后第一次启动,正规来讲需要通过 nvram 方式去写标志区分(我发现一种偷懒取巧方式,判断 /cache/recovery/last_install 文件是否存在)


刚刷机完启动系统不存在这个文件,当手动点击恢复出厂后会多出来这个文件。RK 平台烧写后也会有这个文件,因为烧写后会执行一次恢复出厂,RK 平台可以判断


/cache/recovery/last_kmsg.2 每执行一次恢复出厂操作,/cache/recovery/ 中就会多出文件


/cache/recovery # ls

last_install last_kmsg last_kmsg.1 last_locale last_log last_log.1


/cache/recovery # ls

last_install last_kmsg last_kmsg.1 last_kmsg.2 last_locale last_log last_log.1 last_log.2


/cache/recovery # ls

last_install last_kmsg.1 last_kmsg.3 last_log last_log.2

last_kmsg last_kmsg.2 last_locale last_log.1 last_log.3


接下来是实现代码


frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java


卸载 apk 时判断包名是否在 pms_sysapp_removable_system_list 中,区分于用户后来手动安装的。


在白名单中,先检查 last_deleteApkFile.dat 文件是否存在,不存在第一次卸载先创建文件并把包名写进去


文件存在,读取 dat 中是否已经写入过包名,未写入则 write

import java.util.function.Predicate;
+import java.io.BufferedWriter;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.FileReader;
@@ -605,6 +614,9 @@ public class PackageManagerService extends IPackageManager.Stub
     private static final String ODM_OVERLAY_DIR = "/odm/overlay";
     private static final String OEM_OVERLAY_DIR = "/oem/overlay";
+    //cczheng add 
+    private static final String SYSTEM_APP_DIR = "/system/app";
+    private static final String DELETE_APK_FILE = "/mnt/vendor/protect_f/last_deleteApkFile.dat";
@@ -19651,6 +19686,48 @@ public class PackageManagerService extends IPackageManager.Stub
             if (DEBUG_REMOVE) Slog.d(TAG, "Removing non-system package: " + ps.name);
             deleteInstalledPackageLIF(ps, deleteCodeAndResources, flags, allUserHandles,
                     outInfo, writeSettings, replacingPackage);
+            Log.d(TAG, "Removing non-system package: " + ps.name);
+            //cczheng add
+            if (checkIsCanRemoveSystemApp(ps.name)) {
+               File deleteApkFile = new File(DELETE_APK_FILE);
+                if(!deleteApkFile.exists()) {
+                    try {
+                        deleteApkFile.createNewFile();
+                    } catch (Exception e) {
+                        e.printStackTrace();
+                        Log.e(TAG,"create file failed: " + DELETE_APK_FILE);
+                        return;
+                    }
+                }
+                ArrayList<String> list = new ArrayList<String>();
+                readDeleteFile(list);
+                if (list.contains(ps.name)) {
+                       Log.d(TAG, ps.name +" already writed");
+                       return;
+                }
+
+                BufferedWriter fileWriter  = null;
+                try {
+                    fileWriter = new BufferedWriter(new FileWriter(deleteApkFile,true));
+                    fileWriter.append(ps.name);
+                    fileWriter.newLine();
+                    fileWriter.flush();
+                } catch (Exception e) {
+                    e.printStackTrace();
+                    Log.e(TAG,"write file failed: " + DELETE_APK_FILE);
+                } finally {
+                    if (fileWriter != null) {
+                        try {
+                            fileWriter.close();
+                        } catch (Exception e) {
+                            e.printStackTrace();
+                            return;
+                        }
+                        fileWriter = null;
+                    }
+                }
+            }
+            //cczheng add 
         }


scanDirLI 中扫描时进行过滤,先区分是否刷机第一次启动还是恢复出厂第一次启动,如果是恢复出厂第一次启动,

读取 last_deleteApkFile.dat 中包名集合,扫描 system/app 文件夹下 apk 时,包名在集合中则跳过安装

     private static final Intent sBrowserIntent;
@@ -9114,6 +9126,17 @@ public class PackageManagerService extends IPackageManager.Stub
             Log.d(TAG, "Scanning app dir " + scanDir + " scanFlags=" + scanFlags
                     + " flags=0x" + Integer.toHexString(parseFlags));
         }
+        //cczheng add
+        ArrayList<String> list = new ArrayList<String>();
+        if (isRecoveryFirstBoot()) {
+               if (scanDir.getAbsolutePath().contains(SYSTEM_APP_DIR)) {
+                   if (!readDeleteFile(list)) {
+                       Log.e(TAG, "read readDeleteFile data failed");
+                       return;
+                   }
+               }
+        }//cczheng add
+
         try (ParallelPackageParser parallelPackageParser = new ParallelPackageParser(
                 mSeparateProcesses, mOnlyCore, mMetrics, mCacheDir,
                 mParallelPackageParserCallback)) {
@@ -9126,6 +9149,18 @@ public class PackageManagerService extends IPackageManager.Stub
                     // Ignore entries which are not packages
                     continue;
                 }
+                //cczheng add
+                if (file.getAbsolutePath().contains(SYSTEM_APP_DIR)) {
+                       if (list != null && list.size() > 0) {
+                           final boolean isdeleteApk = isDeleteApk(file, parseFlags, list);
+                           if (isdeleteApk) {
+                               // Ignore deleted bundled apps
+                               Log.d(TAG, "skip install "+file.getAbsolutePath());
+                               continue;
+                           }
+                      }
+                   }//cczheng add
+
                 parallelPackageParser.submit(file, parseFlags);
                 fileCount++;
             }
+    //cczheng add
+    private boolean checkIsCanRemoveSystemApp(String pkgName){
+       Log.d(TAG, "checkIsCanRemoveSystemApp pkgName=" + pkgName);
+        FileReader fr = null;
+        BufferedReader br = null;
+        HashSet<String> resultSet = new HashSet<String>();
+        resultSet.clear();
+        File file = Environment.buildPath(Environment.getRootDirectory(),
+                               "etc", "permissions","pms_sysapp_removable_system_list.txt");
+        try {
+            if (file.exists()) {
+                fr = new FileReader(file);
+            } else {
+                Log.d(TAG, "file in " + file + " does not exist!");
+                return false;
+            }
+            br = new BufferedReader(fr);
+            String line;
+            while ((line = br.readLine()) != null) {
+                line = line.trim();
+                if (!TextUtils.isEmpty(line)) {
+                    resultSet.add(line);
+                }
+            }
+            Log.e(TAG,"GRANT_SYS_APP_LIST_SYSTEM size="+resultSet.size());
+        } catch (Exception io) {
+            Log.d(TAG, io.getMessage());
+        } finally {
+            try {
+                if (br != null) {
+                    br.close();
+                }
+                if (fr != null) {
+                    fr.close();
+                }
+            } catch (Exception io) {
+                Log.d(TAG, io.getMessage());
+            }
+        }
+        return resultSet.contains(pkgName);
+    }
+
+    private  boolean readDeleteFile(ArrayList<String> list) {
+        File deleteApkFile = new File(DELETE_APK_FILE);
+        if (!deleteApkFile.exists()) {
+            Log.e(TAG,"deliteApkFile not exist");
+            return true;
+        }
+        BufferedReader br = null;
+        try {
+            br = new BufferedReader(new FileReader(deleteApkFile));
+            String name = null;
+            while(null != (name = br.readLine())) {
+                list.add(name);
+            }
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        } finally {
+            if (br != null) {
+                try {
+                    br.close();
+                } catch (Exception e) {
+                    e.printStackTrace();
+                    return false;
+                }
+                br = null;
+            }
+        }
+    }
+
+    private  boolean isDeleteApk(File scanFile, int parseFlags, ArrayList<String> list) {
+        PackageParser pp = new PackageParser();
+        final PackageParser.Package pkg;
+        try {
+            pkg = pp.parsePackage(scanFile, parseFlags);
+        } catch (PackageParserException e) {
+            e.printStackTrace();
+            return false;
+        }
+        if (list.contains(pkg.packageName)) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean isRecoveryFirstBoot() {
+       boolean result = false;
+       try {
+         File recoveryFile = new File("/cache/recovery/last_install");
+          Log.d("ccz","recoveryFile  "+recoveryFile.exists());
+          if (isFirstBoot()) {//only first boot need check
+               result = recoveryFile.exists();
+          }
+       }catch(Exception e){
+          e.printStackTrace();
+       }
+       Log.i("ccz","isRecoveryFirstBoot result="+result);
+        return result;
+    }
+    //cczheng add
+
     @GuardedBy("mPackages")
     private void markPackageUninstalledForUserLPw(PackageSetting ps, UserHandle user) {
         final int[] userIds = (user == null || user.getIdentifier() == UserHandle.USER_ALL)


目录
相关文章
|
8天前
|
ARouter IDE 开发工具
Android面试题之App的启动流程和启动速度优化
App启动流程概括: 当用户点击App图标,Launcher通过Binder IPC请求system_server启动Activity。system_server指示Zygote fork新进程,接着App进程向system_server申请启动Activity。经过Binder通信,Activity创建并回调生命周期方法。启动状态分为冷启动、温启动和热启动,其中冷启动耗时最长。优化技巧包括异步初始化、避免主线程I/O、类加载优化和简化布局。
27 3
Android面试题之App的启动流程和启动速度优化
|
6天前
|
缓存 JSON 网络协议
Android面试题:App性能优化之电量优化和网络优化
这篇文章讨论了Android应用的电量和网络优化。电量优化涉及Doze和Standby模式,其中应用可能需要通过用户白名单或电池广播来适应限制。Battery Historian和Android Studio的Energy Profile是电量分析工具。建议减少不必要的操作,延迟非关键任务,合并网络请求。网络优化包括HTTPDNS减少DNS解析延迟,Keep-Alive复用连接,HTTP/2实现多路复用,以及使用protobuf和gzip压缩数据。其他策略如使用WebP图像格式,按网络质量提供不同分辨率的图片,以及启用HTTP缓存也是有效手段。
28 9
|
7天前
|
XML 监控 安全
Android App性能优化之卡顿监控和卡顿优化
本文探讨了Android应用的卡顿优化,重点在于布局优化。建议包括将耗时操作移到后台、使用ViewPager2实现懒加载、减少布局嵌套并利用merge标签、使用ViewStub减少资源消耗,以及通过Layout Inspector和GPU过度绘制检测来优化。推荐使用AsyncLayoutInflater异步加载布局,但需注意线程安全和不支持特性。卡顿监控方面,提到了通过Looper、ChoreographerHelper、adb命令及第三方工具如systrace和BlockCanary。总结了Choreographer基于掉帧计算和BlockCanary基于Looper监控的原理。
18 3
|
10天前
|
安全 JavaScript 前端开发
kotlin开发安卓app,JetPack Compose框架,给webview新增一个按钮,点击刷新网页
在Kotlin中开发Android应用,使用Jetpack Compose框架时,可以通过添加一个按钮到TopAppBar来实现WebView页面的刷新功能。按钮位于右上角,点击后调用`webViewState?.reload()`来刷新网页内容。以下是代码摘要:
|
5天前
|
Java Android开发 Kotlin
Android面试题:App性能优化之Java和Kotlin常见的数据结构
Java数据结构摘要:ArrayList基于数组,适合查找和修改;LinkedList适合插入删除;HashMap1.8后用数组+链表/红黑树,初始化时预估容量可避免扩容。SparseArray优化查找,ArrayMap减少冲突。 Kotlin优化摘要:Kotlin的List用`listOf/mutableListOf`,Map用`mapOf/mutableMapOf`,支持操作符重载和扩展函数。序列提供懒加载,解构用于遍历Map,扩展函数默认参数增强灵活性。
14 0
|
13天前
|
前端开发 JavaScript Android开发
手机APP开发|基于安卓APP实现掌上党支部——党员app
手机APP开发|基于安卓APP实现掌上党支部——党员app
|
13天前
|
Java API Android开发
安卓开发app 调用usb 摄像头 需要用到哪个库
在安卓开发中,调用USB摄像头常常使用libuvc库,这是一个跨平台处理USB视频设备的库。有多个基于libuvc的开源项目简化了在安卓上的使用,如UVCCamera和Android EasyCap UVC。例如,UVCCamera提供了一个更简单的接口来访问USB摄像头,并且可以在Jetpack Compose中显示预览。开发者可以参考官方文档、开源项目以及相关教程和资源来学习和实现这一功能。
|
4天前
|
编解码 Java Android开发
FFmpeg开发笔记(三十一)使用RTMP Streamer开启APP直播推流
RTMP Streamer是一款开源的安卓直播推流框架,支持RTMP、RTSP和SRT协议,适用于各种直播场景。它支持H264、H265、AV1视频编码和AAC、G711、OPUS音频编码。本文档介绍了如何使用Java版的RTMP Streamer,建议使用小海豚版本的Android Studio (Dolphin)。加载项目时,可添加国内仓库加速依赖下载。RTMP Streamer包含五个模块:app、encoder、rtmp、rtplibrary和rtsp。完成加载后,可以在手机上安装并运行APP,提供多种直播方式。开发者可以从《FFmpeg开发实战:从零基础到短视频上线》获取更多信息。
30 7
FFmpeg开发笔记(三十一)使用RTMP Streamer开启APP直播推流
|
1天前
|
数据可视化 数据处理 Swift
Swift开发——简单App设计
SwiftUI教程概述:简化App设计,通过代码展示了如何创建一个计算两个数之和的界面。工程`MyCh0902`包含`ContentView.swift`,其中定义了`ContentView`和`MyView`结构体。`MyView`负责界面布局,使用`VStack`和`HStack`组织元素,如`TextField`和`Button`。点击`Button`调用`calc`方法处理输入并更新结果。界面设计可在Xcode的Inspector窗口中可视化配置。推荐将界面逻辑移到单独的`MyView.swift`文件中以清晰分离视图设计。
14 1
Swift开发——简单App设计
|
17天前
|
移动开发 小程序 视频直播
FFmpeg开发笔记(二十七)解决APP无法访问ZLMediaKit的直播链接问题
本文讲述了在使用ZLMediaKit进行视频直播时,遇到移动端通过ExoPlayer和微信小程序播放HLS直播地址失败的问题。错误源于ZLMediaKit对HTTP地址的Cookie校验导致401无权限响应。通过修改ZLMediaKit源码,注释掉相关鉴权代码并重新编译安装,解决了此问题,使得ExoPlayer和小程序能成功播放HLS视频。详细解决方案及FFmpeg集成可参考《FFmpeg开发实战:从零基础到短视频上线》一书。
34 3
FFmpeg开发笔记(二十七)解决APP无法访问ZLMediaKit的直播链接问题