常规的预置可卸载 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.Stubprivate 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.Stubif (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.StubLog.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 packagescontinue;}
+                //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)

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

  1. Android11.0(R) MTK平台添加新分区

    mtk 平台增加一个新分区test,修改文件列表如下 modified: device/mediatek/mt6765/init.mt6765.rcmodified: device/mediatek/ ...

  2. android11.0(R) data分区节点加密控制分析

    前情提要 androidQ(10.0) 预装集成apk到data分区 Android O.P.Q 版本如何预装 APK 遇到问题 当然是和之前一样啦,开机并不能正常启动,而是 自动进入了 recove ...

  3. Android11.0(R) framework 新增类 lint 编码检查问题

    从 10.0 移植了几个类过来,没想到一编译出来几十个 errors,这就很离谱,明明是现成的代码. 后来仔细看了错误 log 提示,Your API changes are triggering A ...

  4. Android11.0(R) 预留清空锁屏密码接口

    前言 出厂的设备有些客户喜欢设置锁屏密码,无奈记性不好,忘记密码后就只能恢复出厂或者重新刷机了,啊这客户肯定不接受的. 为了防止客户逼逼赖赖,我们就未雨绸缪,给它加个清除接口. 先说结论,系统锁屏密码 ...

  5. Android11.0(R) 预置 wifi 信息自动连接

    在系统中预置一个 wifi 的 ssid 和 pwd,这样在系统烧写启动完成后开机打开 wifi 就能 自动连接指定 ssid. 1.获取 WifiConfigStore.xml R 版本此文件路径位 ...

  6. android11.0 12.0Launcher3禁止拖拽app图标到第一屏

    1.概述 在11.0 12.0进行定制化开发Launcher3中,会对Launcher3 做些要求,比如现在的需求就是Launcher3第一屏的图标固定,不让其他屏的图标拖动到 第一屏所以说这个需求和 ...

  7. Android11.0(R) 华为充电动画

    根据系统原有的无线充电动画流程,新增有线充电气泡动画. 效果图 修改文件清单 vendor/mediatek/proprietary/packages/apps/SystemUI/src/com/an ...

  8. Android11.0(R) MTK6771 user版本关闭 SELinux

    开始我们先来跟一下 selinux 的初始化过程 system\core\init\main.cpp int main(int argc, char** argv) {#if __has_featur ...

  9. Android11.0(R) HAL 相机集成水印算法+多帧算法

    一.写在前面 上网

最新文章

  1. 大触教你如何调节python内置函数
  2. 用 C 语言开发一门编程语言 — 交互式解释器
  3. php无限分类原理,php 递归无限级分类原理和实现代码
  4. WPF: 使用CommandManager.InvalidateRequerySuggested手动更新Command状态
  5. 倪飞曝腾讯红魔6更多细节:搭载业内顶级散热技术
  6. 【Elasticsearch】在 Elasticsearch 中每秒存储 5000 万个事件:我们是如何做到的
  7. 学习云计算有哪些优势?云计算教程学习路线图
  8. 7年前的200电话卡帐号
  9. asp.net 无法访问已关闭的资源集
  10. [原创]如何免费使用宝塔专业版
  11. 文秘计算机考核,行政文秘绩效考核
  12. zcu111解决DP时钟报错问题
  13. 解决 have unmet dependencies: youdao-dict :
  14. win10蓝牙android上网,Win10系统如何共享安卓手机蓝牙上网(非热点)
  15. 物联网协议之一:MQTT协议和kafka
  16. Android 获取联系人姓名和电话号码信息
  17. 批量部署stg Pool到生产脚本
  18. AXI接口协议详解-AXI总线、接口、协议
  19. 如何让更多游客参与到景区夜游光影秀
  20. 3 数据库图形管理工具sqlite3

热门文章

  1. 51CTO学院优惠版
  2. 应力应变基础理论分析
  3. unity开发_Unity开发人员在Ludum Dare 30上大放异彩
  4. .rvm/gems/ruby-2.4.1@global/gems/cocoapods-1.5.0/lib/cocoapods/executable.rb:89: warning: Insecure
  5. JVM内存和垃圾回收-02.类加载子系统
  6. Win7如何查看自己得Win7版本号
  7. 【猿码】java swing实现喜羊羊与灰太狼推箱子游戏附带视频开发教程可做为Java毕设大作业
  8. 一剑开尘走龙蛇 XGBoost
  9. 讯飞语音——带你简单实现语音听写
  10. 安卓手机安装虚拟定位的方法Xposed安装器+模拟位置(Xposed模块)