Android 系统配置第三方应用不可以卸载

为解决system分区过大的问题,当客户需要在Android系统中预装多个应用并要求不可卸载时,通过在/data/local/config/下配置不可卸载应用列表。应用安装时,系统会将这些应用标记为系统应用,防止被卸载。卸载时,系统会检查配置列表以确定是否允许卸载。在adb、pm命令及packageInstaller等安装方式下,系统都会在安装过程中将配置的应用标记为系统应用,并在卸载时阻止卸载。重启时,系统也会保护这些应用的数据不被删除。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

场景描述

android系统system分区大小是固定,如果system分区大小超过云机system分区固定大小,system分区升级时会占用下个分区内存,导致基线分区地址错乱、系统升级失败、系统启动崩溃。

内置应用配置过多,会导致system.img镜像过大。但客户需求安装多个应用,且安装应用标记为系统应用,不可卸载处理。

方案设计

在系统/data/local/config/路径下配置不可卸载应用NoDeleteApp列表项,根据配置列表,应用安装解析应用包和云机重启扫描解析应用包过程中,标记改应用为系统应用,应用在卸载时,设置不可卸载。

可行性场景分析

android系统adb命令、pm命令、静默安装、packageInstaller等应用安装方式,最终Binder IPC方式调用PackageManagerService服务的verifyPendingInstall-->installPackageLI方法,同时PackageParser.java-->parseBaseApkCommon会对apk进行解析,填充Package信息,返回一个Package对象。parseBaseApkCommon解析过程中,获取NoDeleteApp配置项,设置Package的applicationinfo.flags参数为系统应用。

android系统adb命令、pm命令、packageInstaller等应用卸载方式,最终Binder IPC方式调用PackageManagerService服务的deletePackageX方法,改方法内根据NoDeleteApplist配置项,返回是否可卸载应用。

android系统重启,PackageManagerService具有scanPackageLI 过程,在扫描/data/app目录时,会判断非系统应用的Package中是否存在改目录中,如果不存在,会删除对应/data/app目录下数据。因此,removePackageLI内获取NoDeleteApplist配置项,设置不可删除/data/app目录下对应应用数据。

具体代码实现

diff --git a/frameworks/base/core/java/android/content/pm/PackageParser.java b/frameworks/base/core/java/android/content/pm/PackageParser.java
index 2da2cb4c9..842b92ae3 100755
--- a/frameworks/base/core/java/android/content/pm/PackageParser.java
+++ b/frameworks/base/core/java/android/content/pm/PackageParser.java
@@ -126,6 +126,8 @@ import java.util.List;
 import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.io.BufferedReader;
+import java.io.FileReader;
 
 /**
  * Parser for package files (APKs) on disk. This supports apps packaged either
@@ -199,6 +201,17 @@ public class PackageParser {
 
     private static final String METADATA_MAX_ASPECT_RATIO = "android.max_aspect";
 
+    /**
+     * Profile file path
+     */
+    private static final String PROFILE_FILE_PATH = "/data/local/config/";
+
+    /**
+     * Profile file name
+     */
+    private static final String PROFILE_FILE_PATH_NAME = "NoDeleteApp";
+
+
     /**
      * Bit mask of all the valid bits that can be set in recreateOnConfigChanges.
      * @hide
@@ -2489,9 +2502,51 @@ public class PackageParser {
         if (pkg.applicationInfo.usesCompatibilityMode()) {
             adjustPackageToBeUnresizeableAndUnpipable(pkg);
         }
+
+        if (isProfileNoDeleteApp(pkg.packageName)) {
+            Slog.w(TAG, "parser not removing packages " );
+            pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+        }
         return pkg;
     }
 
+    /**
+     * profile no delete app file
+     * @param pkgName
+     * @return
+     */
+    private boolean isProfileNoDeleteApp(String pkgName) {
+        final File systemDir;
+        final File blackListFile;
+        final ArrayList<String> blackListApps = new ArrayList<String>();
+        systemDir = new File(PROFILE_FILE_PATH);
+        blackListFile = new File(systemDir, PROFILE_FILE_PATH_NAME);
+        if (!blackListFile.exists()) {
+            return false;
+        }
+        try {
+            blackListApps.clear();
+            BufferedReader br = new BufferedReader(new FileReader(blackListFile));
+            String line = br.readLine();
+            while (line != null) {
+                blackListApps.add(line);
+                line = br.readLine();
+            }
+            br.close();
+        } catch (IOException e) {
+            e.printStackTrace();
+            return false;
+        }
+        Iterator<String> it = blackListApps.iterator();
+        while (it.hasNext()) {
+            String blacklistItem = it.next();
+            if (pkgName.equals(blacklistItem)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private boolean checkOverlayRequiredSystemProperty(String propName, String propValue) {
 
         if (TextUtils.isEmpty(propName) || TextUtils.isEmpty(propValue)) {
diff --git a/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java b/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
index 65e188f6e..491dd72e7 100755
--- a/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -372,6 +372,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Predicate;
 
+import java.io.BufferedReader;
+import java.io.FileReader;
+
 /**
  * Keep track of all those APKs everywhere.
  * <p>
@@ -735,6 +738,16 @@ public class PackageManagerService extends IPackageManager.Stub
      */
     boolean mPromoteSystemApps;
 
+    /**
+     * Profile file path
+     */
+    private static final String PROFILE_FILE_PATH = "/data/local/config/";
+
+    /**
+     * Profile file name
+     */
+    private static final String PROFILE_FILE_PATH_NAME = "NoDeleteApplist";
+
     @GuardedBy("mPackages")
     final Settings mSettings;
 
@@ -2821,7 +2834,11 @@ public class PackageManagerService extends IPackageManager.Stub
                      * If this is not a system app, it can't be a
                      * disable system app.
                      */
-                    if ((ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) == 0) {
+                    if ((ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) == 0) {
+                        continue;
+                    }
+
+                    if (isProfileNoDeleteApp(ps.name)) {
                         continue;
                     }
 
@@ -8489,6 +8506,10 @@ public class PackageManagerService extends IPackageManager.Stub
                     // Ignore entries which are not packages
                     continue;
                 }
+
+                if (!isPackage && !isProfileNoDeleteApp(file.getAbsolutePath())) {
+                    continue;
+                }
                 parallelPackageParser.submit(file, parseFlags);
                 fileCount++;
             }
@@ -9633,6 +9654,10 @@ public class PackageManagerService extends IPackageManager.Stub
     }
 
     void removeCodePathLI(File codePath) {
+        if (isProfileNoDeleteApp(codePath.getAbsolutePath())) {
+            Slog.w(TAG, "profile app file removeCodePathLI error");
+            return;
+        }
         if (codePath.isDirectory()) {
             try {
                 mInstaller.rmPackageDir(codePath.getAbsolutePath());
@@ -12271,6 +12296,9 @@ public class PackageManagerService extends IPackageManager.Stub
     }
 
     private void removePackageLI(PackageParser.Package pkg, boolean chatty) {
+        if (isProfileNoDeleteApp(pkg.packageName)) {
+            return;
+        }
         // Remove the parent package setting
         PackageSetting ps = (PackageSetting) pkg.mExtras;
         if (ps != null) {
@@ -18294,6 +18322,43 @@ public class PackageManagerService extends IPackageManager.Stub
         return mKeepUninstalledPackages != null && mKeepUninstalledPackages.contains(packageName);
     }
 
+    /**
+     * profile no delete app file
+     * @param pkgName
+     * @return
+     */
+    private boolean isProfileNoDeleteApp(String pkgName) {
+        final File systemDir;
+        final File blackListFile;
+        final ArrayList<String> blackListApps = new ArrayList<String>();
+        systemDir = new File(PROFILE_FILE_PATH);
+        blackListFile = new File(systemDir, PROFILE_FILE_PATH_NAME);
+        if (!blackListFile.exists()) {
+            return false;
+        }
+        try {
+            blackListApps.clear();
+            BufferedReader br = new BufferedReader(new FileReader(blackListFile));
+            String line = br.readLine();
+            while (line != null) {
+                blackListApps.add(line);
+                line = br.readLine();
+            }
+            br.close();
+        } catch (IOException e) {
+            e.printStackTrace();
+            return false;
+        }
+        Iterator<String> it = blackListApps.iterator();
+        while (it.hasNext()) {
+            String blacklistItem = it.next();
+            if (pkgName.contains(blacklistItem)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      *  This method is an internal method that could be get invoked either
      *  to delete an installed package or to clean up a failed installation.
@@ -18320,6 +18385,11 @@ public class PackageManagerService extends IPackageManager.Stub
             return PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER;
         }
 
+        if (isProfileNoDeleteApp(packageName)) {
+            Slog.w(TAG, "Not removing package " + packageName + ": has active device admin");
+            return PackageManager.DELETE_FAILED_INTERNAL_ERROR;
+        }
+
         PackageSetting uninstalledPs = null;
         PackageParser.Package pkg = null;
 
@@ -18564,6 +18634,9 @@ public class PackageManagerService extends IPackageManager.Stub
     private void removePackageDataLIF(PackageSetting ps, int[] allUserHandles,
             PackageRemovedInfo outInfo, int flags, boolean writeSettings) {
         String packageName = ps.name;
+        if (isProfileNoDeleteApp(packageName)) {
+            return;
+        }
         if (DEBUG_REMOVE) Slog.d(TAG, "removePackageDataLI: " + ps);
         // Retrieve object to delete permissions for shared user later on
         final PackageParser.Package deletedPkg;
@@ -18735,6 +18808,11 @@ public class PackageManagerService extends IPackageManager.Stub
             return false;
         }
 
+        if (isProfileNoDeleteApp(deletedPkg.packageName)) {
+            Slog.w(TAG, "deleteSystemPackageLIF packageName =  " + deletedPkg.packageName);
+            return false;
+        }
+
         final boolean applyUserRestrictions
                 = (allUserHandles != null) && outInfo != null && (outInfo.origUsers != null);
         final PackageSetting disabledPs;

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值