Allow only selected priv apps to run OOB
- Add pm.dexopt.priv-apps-oob-list to allow selecting specific packages
to run OOB, in a comma-separated string of package names. When set to
"ALL" (default), all priv apps will run in OOB.
- Add a global config priv_app_oob_list to persist the state for
experiment.
- Also make background dexopt to respect the config.
Test: 0. Reset previous OOB settings.
1. settings put global priv_app_oob_list \
com.google.android.gms,com.android.vending
2. cmd package compile -m speed -f com.google.android.gms (then
com.android.vending, com.google.android.googlequicksearchbox)
3. dumpsys package dexopt
# .vending and .gms are "verify", .googlequicksearchbox is
# "speed".
Test: settings put global priv_app_oob_list 'ALL' # see the same result
Test: settings delete global priv_app_oob_list # see the same result
Test: atest SettingsBackupTest
Bug: 30972906
Bug: 63920015
Change-Id: Iba47b4763a026cdc94939db0a743822278917269
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 803abf3..d9ee41a 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8902,6 +8902,14 @@
public static final String PRIV_APP_OOB_ENABLED = "priv_app_oob_enabled";
/**
+ * Comma separated list of privileged package names, which will be running out-of-box APK.
+ * Default: "ALL"
+ *
+ * @hide
+ */
+ public static final String PRIV_APP_OOB_LIST = "priv_app_oob_list";
+
+ /**
* The interval in milliseconds at which location requests will be throttled when they are
* coming from the background.
*
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index db221cd..0d60f33 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -355,6 +355,7 @@
Settings.Global.POWER_MANAGER_CONSTANTS,
Settings.Global.PREFERRED_NETWORK_MODE,
Settings.Global.PRIV_APP_OOB_ENABLED,
+ Settings.Global.PRIV_APP_OOB_LIST,
Settings.Global.PROVISIONING_APN_ALARM_DELAY_IN_MS,
Settings.Global.RADIO_BLUETOOTH,
Settings.Global.RADIO_CELL,
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 9de876e..24f544e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -466,6 +466,7 @@
import com.android.server.job.JobSchedulerInternal;
import com.android.server.pm.Installer;
import com.android.server.pm.Installer.InstallerException;
+import com.android.server.pm.dex.DexManager;
import com.android.server.utils.PriorityDump;
import com.android.server.vr.VrManagerInternal;
import com.android.server.wm.PinnedStackWindowController;
@@ -4282,7 +4283,7 @@
}
if (app.info.isPrivilegedApp() &&
- SystemProperties.getBoolean("pm.dexopt.priv-apps-oob", false)) {
+ DexManager.isPackageSelectedToRunOob(app.pkgList.keySet())) {
runtimeFlags |= Zygote.ONLY_USE_SYSTEM_OAT_FILES;
}
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index ebab1a7..b0be4a9 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -34,6 +34,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.pm.Installer.InstallerException;
+import com.android.server.pm.dex.DexManager;
import com.android.server.pm.dex.DexoptOptions;
import com.android.server.pm.dex.DexoptUtils;
import com.android.server.pm.dex.PackageDexUsage;
@@ -495,10 +496,9 @@
boolean isUsedByOtherApps) {
int flags = info.flags;
boolean vmSafeMode = (flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0;
- // When pm.dexopt.priv-apps-oob is true, we only verify privileged apps.
- if (info.isPrivilegedApp() &&
- SystemProperties.getBoolean("pm.dexopt.priv-apps-oob", false)) {
- return "verify";
+ // When a priv app is configured to run out of box, only verify it.
+ if (info.isPrivilegedApp() && DexManager.isPackageSelectedToRunOob(info.packageName)) {
+ return "verify";
}
if (vmSafeMode) {
return getSafeModeCompilerFilter(targetCompilerFilter);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index c276938..327acf3 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -578,8 +578,6 @@
private static final String PRODUCT_OVERLAY_DIR = "/product/overlay";
- private static final String PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB = "pm.dexopt.priv-apps-oob";
-
/** Canonical intent used to identify what counts as a "web browser" app */
private static final Intent sBrowserIntent;
static {
@@ -2459,7 +2457,7 @@
"*dexopt*");
DexManager.Listener dexManagerListener = DexLogger.getListener(this,
installer, mInstallLock);
- mDexManager = new DexManager(this, mPackageDexOptimizer, installer, mInstallLock,
+ mDexManager = new DexManager(mContext, this, mPackageDexOptimizer, installer, mInstallLock,
dexManagerListener);
mArtManagerService = new ArtManagerService(mContext, this, installer, mInstallLock);
mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper());
@@ -10428,11 +10426,7 @@
Log.d(TAG, "Scanning package " + pkg.packageName);
}
- if (Build.IS_DEBUGGABLE &&
- pkg.isPrivileged() &&
- SystemProperties.getBoolean(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB, false)) {
- PackageManagerServiceUtils.logPackageHasUncompressedCode(pkg);
- }
+ DexManager.maybeLogUnexpectedPackageDetails(pkg);
// Initialize package source and resource directories
final File scanFile = new File(pkg.codePath);
@@ -21023,23 +21017,6 @@
.getUriFor(Secure.INSTANT_APPS_ENABLED), false, co, UserHandle.USER_SYSTEM);
co.onChange(true);
- // This observer provides an one directional mapping from Global.PRIV_APP_OOB_ENABLED to
- // pm.dexopt.priv-apps-oob property. This is only for experiment and should be removed once
- // it is done.
- ContentObserver privAppOobObserver = new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- int oobEnabled = Global.getInt(resolver, Global.PRIV_APP_OOB_ENABLED, 0);
- SystemProperties.set(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB,
- oobEnabled == 1 ? "true" : "false");
- }
- };
- mContext.getContentResolver().registerContentObserver(
- Global.getUriFor(Global.PRIV_APP_OOB_ENABLED), false, privAppOobObserver,
- UserHandle.USER_SYSTEM);
- // At boot, restore the value from the setting, which persists across reboot.
- privAppOobObserver.onChange(true);
-
// Disable any carrier apps. We do this very early in boot to prevent the apps from being
// disabled after already being started.
CarrierAppUtils.disableCarrierAppsUntilPrivileged(mContext.getOpPackageName(), this,
@@ -21128,6 +21105,7 @@
storage.registerListener(mStorageListener);
mInstallerService.systemReady();
+ mDexManager.systemReady();
mPackageDexOptimizer.systemReady();
StorageManagerInternal StorageManagerInternal = LocalServices.getService(
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 4b907f4..1aea8f0 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -26,7 +26,6 @@
import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
import com.android.internal.content.NativeLibraryHelper;
-import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastPrintWriter;
import com.android.server.EventLogTags;
import com.android.server.pm.dex.DexManager;
@@ -56,7 +55,6 @@
import android.util.Log;
import android.util.PackageUtils;
import android.util.Slog;
-import android.util.jar.StrictJarFile;
import android.util.proto.ProtoOutputStream;
import dalvik.system.VMRuntime;
@@ -85,12 +83,10 @@
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
-import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Predicate;
import java.util.zip.GZIPInputStream;
-import java.util.zip.ZipEntry;
/**
* Class containing helper methods for the PackageManagerService.
@@ -317,61 +313,6 @@
return maxModifiedTime;
}
- /**
- * Checks that the archive located at {@code fileName} has uncompressed dex file and so
- * files that can be direclty mapped.
- */
- public static void logApkHasUncompressedCode(String fileName) {
- StrictJarFile jarFile = null;
- try {
- jarFile = new StrictJarFile(fileName,
- false /*verify*/, false /*signatureSchemeRollbackProtectionsEnforced*/);
- Iterator<ZipEntry> it = jarFile.iterator();
- while (it.hasNext()) {
- ZipEntry entry = it.next();
- if (entry.getName().endsWith(".dex")) {
- if (entry.getMethod() != ZipEntry.STORED) {
- Slog.w(TAG, "APK " + fileName + " has compressed dex code " +
- entry.getName());
- } else if ((entry.getDataOffset() & 0x3) != 0) {
- Slog.w(TAG, "APK " + fileName + " has unaligned dex code " +
- entry.getName());
- }
- } else if (entry.getName().endsWith(".so")) {
- if (entry.getMethod() != ZipEntry.STORED) {
- Slog.w(TAG, "APK " + fileName + " has compressed native code " +
- entry.getName());
- } else if ((entry.getDataOffset() & (0x1000 - 1)) != 0) {
- Slog.w(TAG, "APK " + fileName + " has unaligned native code " +
- entry.getName());
- }
- }
- }
- } catch (IOException ignore) {
- Slog.wtf(TAG, "Error when parsing APK " + fileName);
- } finally {
- try {
- if (jarFile != null) {
- jarFile.close();
- }
- } catch (IOException ignore) {}
- }
- return;
- }
-
- /**
- * Checks that the APKs in the given package have uncompressed dex file and so
- * files that can be direclty mapped.
- */
- public static void logPackageHasUncompressedCode(PackageParser.Package pkg) {
- logApkHasUncompressedCode(pkg.baseCodePath);
- if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) {
- for (int i = 0; i < pkg.splitCodePaths.length; i++) {
- logApkHasUncompressedCode(pkg.splitCodePaths[i]);
- }
- }
- }
-
private static File getSettingsProblemFile() {
File dataDir = Environment.getDataDirectory();
File systemDir = new File(dataDir, "system");
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index 3e63fb4..392d4d8 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -16,17 +16,25 @@
package com.android.server.pm.dex;
+import android.content.ContentResolver;
+import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
+import android.content.pm.PackageParser;
+import android.database.ContentObserver;
+import android.os.Build;
import android.os.FileUtils;
import android.os.RemoteException;
import android.os.storage.StorageManager;
+import android.os.SystemProperties;
import android.os.UserHandle;
-
+import android.provider.Settings.Global;
import android.util.Slog;
+import android.util.jar.StrictJarFile;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
import com.android.server.pm.Installer;
import com.android.server.pm.Installer.InstallerException;
import com.android.server.pm.PackageDexOptimizer;
@@ -36,13 +44,16 @@
import java.io.File;
import java.io.IOException;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.Iterator;
import java.util.List;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
+import java.util.zip.ZipEntry;
import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo;
@@ -59,8 +70,14 @@
public class DexManager {
private static final String TAG = "DexManager";
+ private static final String PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB = "pm.dexopt.priv-apps-oob";
+ private static final String PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB_LIST =
+ "pm.dexopt.priv-apps-oob-list";
+
private static final boolean DEBUG = false;
+ private final Context mContext;
+
// Maps package name to code locations.
// It caches the code locations for the installed packages. This allows for
// faster lookups (no locks) when finding what package owns the dex file.
@@ -106,8 +123,9 @@
String dexPath, int storageFlags);
}
- public DexManager(IPackageManager pms, PackageDexOptimizer pdo,
+ public DexManager(Context context, IPackageManager pms, PackageDexOptimizer pdo,
Installer installer, Object installLock, Listener listener) {
+ mContext = context;
mPackageCodeLocationsCache = new HashMap<>();
mPackageDexUsage = new PackageDexUsage();
mPackageManager = pms;
@@ -117,6 +135,10 @@
mListener = listener;
}
+ public void systemReady() {
+ registerSettingObserver();
+ }
+
/**
* Notify about dex files loads.
* Note that this method is invoked when apps load dex files and it should
@@ -641,6 +663,141 @@
mPackageDexUsage.writeNow();
}
+ private void registerSettingObserver() {
+ final ContentResolver resolver = mContext.getContentResolver();
+
+ // This observer provides a one directional mapping from Global.PRIV_APP_OOB_ENABLED to
+ // pm.dexopt.priv-apps-oob property. This is only for experiment and should be removed once
+ // it is done.
+ ContentObserver privAppOobObserver = new ContentObserver(null) {
+ @Override
+ public void onChange(boolean selfChange) {
+ int oobEnabled = Global.getInt(resolver, Global.PRIV_APP_OOB_ENABLED, 0);
+ SystemProperties.set(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB,
+ oobEnabled == 1 ? "true" : "false");
+ }
+ };
+ resolver.registerContentObserver(
+ Global.getUriFor(Global.PRIV_APP_OOB_ENABLED), false, privAppOobObserver,
+ UserHandle.USER_SYSTEM);
+ // At boot, restore the value from the setting, which persists across reboot.
+ privAppOobObserver.onChange(true);
+
+ ContentObserver privAppOobListObserver = new ContentObserver(null) {
+ @Override
+ public void onChange(boolean selfChange) {
+ String oobList = Global.getString(resolver, Global.PRIV_APP_OOB_LIST);
+ if (oobList == null) {
+ oobList = "ALL";
+ }
+ SystemProperties.set(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB_LIST, oobList);
+ }
+ };
+ resolver.registerContentObserver(
+ Global.getUriFor(Global.PRIV_APP_OOB_LIST), false, privAppOobListObserver,
+ UserHandle.USER_SYSTEM);
+ // At boot, restore the value from the setting, which persists across reboot.
+ privAppOobListObserver.onChange(true);
+ }
+
+ /**
+ * Returns whether the given package is in the list of privilaged apps that should run out of
+ * box. This only makes sense if PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB is true. Note that when
+ * the the OOB list is empty, all priv apps will run in OOB mode.
+ */
+ public static boolean isPackageSelectedToRunOob(String packageName) {
+ return isPackageSelectedToRunOob(Arrays.asList(packageName));
+ }
+
+ /**
+ * Returns whether any of the given packages are in the list of privilaged apps that should run
+ * out of box. This only makes sense if PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB is true. Note that
+ * when the the OOB list is empty, all priv apps will run in OOB mode.
+ */
+ public static boolean isPackageSelectedToRunOob(Collection<String> packageNamesInSameProcess) {
+ if (!SystemProperties.getBoolean(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB, false)) {
+ return false;
+ }
+ String oobListProperty = SystemProperties.get(
+ PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB_LIST, "ALL");
+ if ("ALL".equals(oobListProperty)) {
+ return true;
+ }
+ for (String oobPkgName : oobListProperty.split(",")) {
+ if (packageNamesInSameProcess.contains(oobPkgName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Generates package related log if the package has code stored in unexpected way.
+ */
+ public static void maybeLogUnexpectedPackageDetails(PackageParser.Package pkg) {
+ if (!Build.IS_DEBUGGABLE) {
+ return;
+ }
+
+ if (pkg.isPrivileged() && isPackageSelectedToRunOob(pkg.packageName)) {
+ logIfPackageHasUncompressedCode(pkg);
+ }
+ }
+
+ /**
+ * Generates log if the APKs in the given package have uncompressed dex file and so
+ * files that can be direclty mapped.
+ */
+ private static void logIfPackageHasUncompressedCode(PackageParser.Package pkg) {
+ logIfApkHasUncompressedCode(pkg.baseCodePath);
+ if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) {
+ for (int i = 0; i < pkg.splitCodePaths.length; i++) {
+ logIfApkHasUncompressedCode(pkg.splitCodePaths[i]);
+ }
+ }
+ }
+
+ /**
+ * Generates log if the archive located at {@code fileName} has uncompressed dex file and so
+ * files that can be direclty mapped.
+ */
+ private static void logIfApkHasUncompressedCode(String fileName) {
+ StrictJarFile jarFile = null;
+ try {
+ jarFile = new StrictJarFile(fileName,
+ false /*verify*/, false /*signatureSchemeRollbackProtectionsEnforced*/);
+ Iterator<ZipEntry> it = jarFile.iterator();
+ while (it.hasNext()) {
+ ZipEntry entry = it.next();
+ if (entry.getName().endsWith(".dex")) {
+ if (entry.getMethod() != ZipEntry.STORED) {
+ Slog.w(TAG, "APK " + fileName + " has compressed dex code " +
+ entry.getName());
+ } else if ((entry.getDataOffset() & 0x3) != 0) {
+ Slog.w(TAG, "APK " + fileName + " has unaligned dex code " +
+ entry.getName());
+ }
+ } else if (entry.getName().endsWith(".so")) {
+ if (entry.getMethod() != ZipEntry.STORED) {
+ Slog.w(TAG, "APK " + fileName + " has compressed native code " +
+ entry.getName());
+ } else if ((entry.getDataOffset() & (0x1000 - 1)) != 0) {
+ Slog.w(TAG, "APK " + fileName + " has unaligned native code " +
+ entry.getName());
+ }
+ }
+ }
+ } catch (IOException ignore) {
+ Slog.wtf(TAG, "Error when parsing APK " + fileName);
+ } finally {
+ try {
+ if (jarFile != null) {
+ jarFile.close();
+ }
+ } catch (IOException ignore) {}
+ }
+ }
+
public static class RegisterDexModuleResult {
public RegisterDexModuleResult() {
this(false, null);
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
index 36d0c8b..147347d 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
@@ -111,7 +111,8 @@
DELEGATE_LAST_CLASS_LOADER_NAME);
mDexManager = new DexManager(
- mPM, /*PackageDexOptimizer*/ null, mInstaller, mInstallLock, mListener);
+ /*Context*/ null, mPM, /*PackageDexOptimizer*/ null, mInstaller, mInstallLock,
+ mListener);
// Foo and Bar are available to user0.
// Only Bar is available to user1;