场景描述
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;