diff options
8 files changed, 205 insertions, 5 deletions
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 4ed84ab34561..d27b0df08b18 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -510,6 +510,13 @@ interface IPackageManager { void forceDexOpt(String packageName); /** + * Reconcile the information we have about the secondary dex files belonging to + * {@code packagName} and the actual dex files. For all dex files that were + * deleted, update the internal records and delete the generated oat files. + */ + void reconcileSecondaryDexFiles(String packageName); + + /** * Update status of external media on the package manager to scan and * install packages installed on the external media. Like say the * MountService uses this to call into the package manager to update diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index bc80374435ea..42a0bad302bf 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -433,6 +433,20 @@ public class Installer extends SystemService { } } + public boolean reconcileSecondaryDexFile(String apkPath, String packageName, int uid, + String[] isas, @Nullable String volumeUuid, int flags) throws InstallerException { + for (int i = 0; i < isas.length; i++) { + assertValidInstructionSet(isas[i]); + } + if (!checkBeforeRemote()) return false; + try { + return mInstalld.reconcileSecondaryDexFile(apkPath, packageName, uid, isas, + volumeUuid, flags); + } catch (Exception e) { + throw InstallerException.from(e); + } + } + private static void assertValidInstructionSet(String instructionSet) throws InstallerException { for (String abi : Build.SUPPORTED_ABIS) { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index b99de780edcc..328109ebd1a1 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -2133,7 +2133,7 @@ public class PackageManagerService extends IPackageManager.Stub { mInstaller = installer; mPackageDexOptimizer = new PackageDexOptimizer(installer, mInstallLock, context, "*dexopt*"); - mDexManager = new DexManager(this, mPackageDexOptimizer); + mDexManager = new DexManager(this, mPackageDexOptimizer, installer, mInstallLock); mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper()); mOnPermissionChangeListeners = new OnPermissionChangeListeners( @@ -7517,6 +7517,16 @@ public class PackageManagerService extends IPackageManager.Stub { return mDexManager.dexoptSecondaryDex(packageName, compilerFilter, force); } + /** + * Reconcile the information we have about the secondary dex files belonging to + * {@code packagName} and the actual dex files. For all dex files that were + * deleted, update the internal records and delete the generated oat files. + */ + @Override + public void reconcileSecondaryDexFiles(String packageName) { + mDexManager.reconcileSecondaryDexFiles(packageName); + } + Collection<PackageParser.Package> findSharedNonSystemLibraries(PackageParser.Package p) { if (p.usesLibraries != null || p.usesOptionalLibraries != null) { ArrayList<PackageParser.Package> retValue = new ArrayList<>(); diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 181069672bc2..5b2bf8062fa6 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -110,6 +110,8 @@ class PackageManagerShellCommand extends ShellCommand { return runInstallWrite(); case "compile": return runCompile(); + case "reconcile-secondary-dex-files": + return runreconcileSecondaryDexFiles(); case "dump-profiles": return runDumpProfiles(); case "list": @@ -416,6 +418,12 @@ class PackageManagerShellCommand extends ShellCommand { } } + private int runreconcileSecondaryDexFiles() throws RemoteException { + String packageName = getNextArg(); + mInterface.reconcileSecondaryDexFiles(packageName); + return 0; + } + private int runDumpProfiles() throws RemoteException { String packageName = getNextArg(); mInterface.dumpProfiles(packageName); @@ -1468,6 +1476,8 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" -3: filter to only show third party packages"); pw.println(" -i: see the installer for the packages"); pw.println(" -u: also include uninstalled packages"); + pw.println(" reconcile-secondary-dex-files TARGET-PACKAGE"); + pw.println(" Reconciles the package secondary dex files with the generated oat files."); pw.println(" list permission-groups"); pw.println(" Prints all known permission groups."); pw.println(" list permissions [-g] [-f] [-d] [-u] [GROUP]"); 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 c5f1646d3c81..28bf58fdfbef 100644 --- a/services/core/java/com/android/server/pm/dex/DexManager.java +++ b/services/core/java/com/android/server/pm/dex/DexManager.java @@ -21,8 +21,13 @@ import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.content.pm.PackageParser; import android.os.RemoteException; +import android.os.storage.StorageManager; + import android.util.Slog; +import com.android.internal.annotations.GuardedBy; +import com.android.server.pm.Installer; +import com.android.server.pm.Installer.InstallerException; import com.android.server.pm.PackageDexOptimizer; import com.android.server.pm.PackageManagerServiceUtils; import com.android.server.pm.PackageManagerServiceCompilerMapping; @@ -62,6 +67,9 @@ public class DexManager { private final IPackageManager mPackageManager; private final PackageDexOptimizer mPackageDexOptimizer; + private final Object mInstallLock; + @GuardedBy("mInstallLock") + private final Installer mInstaller; // Possible outcomes of a dex search. private static int DEX_SEARCH_NOT_FOUND = 0; // dex file not found @@ -69,11 +77,14 @@ public class DexManager { private static int DEX_SEARCH_FOUND_SPLIT = 2; // dex file is a split apk private static int DEX_SEARCH_FOUND_SECONDARY = 3; // dex file is a secondary dex - public DexManager(IPackageManager pms, PackageDexOptimizer pdo) { + public DexManager(IPackageManager pms, PackageDexOptimizer pdo, + Installer installer, Object installLock) { mPackageCodeLocationsCache = new HashMap<>(); mPackageDexUsage = new PackageDexUsage(); mPackageManager = pms; mPackageDexOptimizer = pdo; + mInstaller = installer; + mInstallLock = installLock; } /** @@ -255,7 +266,7 @@ public class DexManager { if (pkg == null) { Slog.d(TAG, "Could not find package when compiling secondary dex " + packageName + " for user " + dexUseInfo.getOwnerUserId()); - // Skip over it, another user might still have the package. + mPackageDexUsage.removeUserPackage(packageName, dexUseInfo.getOwnerUserId()); continue; } int result = pdo.dexOptSecondaryDexPath(pkg.applicationInfo, dexPath, @@ -266,6 +277,73 @@ public class DexManager { } /** + * Reconcile the information we have about the secondary dex files belonging to + * {@code packagName} and the actual dex files. For all dex files that were + * deleted, update the internal records and delete any generated oat files. + */ + public void reconcileSecondaryDexFiles(String packageName) { + PackageUseInfo useInfo = getPackageUseInfo(packageName); + if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) { + if (DEBUG) { + Slog.d(TAG, "No secondary dex use for package:" + packageName); + } + // Nothing to reconcile. + return; + } + Set<String> dexFilesToRemove = new HashSet<>(); + for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) { + String dexPath = entry.getKey(); + DexUseInfo dexUseInfo = entry.getValue(); + PackageInfo pkg = null; + try { + // Note that we look for the package in the PackageManager just to be able + // to get back the real app uid and its storage kind. These are only used + // to perform extra validation in installd. + // TODO(calin): maybe a bit overkill. + pkg = mPackageManager.getPackageInfo(packageName, /*flags*/0, + dexUseInfo.getOwnerUserId()); + } catch (RemoteException ignore) { + // Can't happen, DexManager is local. + } + if (pkg == null) { + // It may be that the package was uninstalled while we process the secondary + // dex files. + Slog.d(TAG, "Could not find package when compiling secondary dex " + packageName + + " for user " + dexUseInfo.getOwnerUserId()); + // Update the usage and continue, another user might still have the package. + mPackageDexUsage.removeUserPackage(packageName, dexUseInfo.getOwnerUserId()); + continue; + } + ApplicationInfo info = pkg.applicationInfo; + int flags = 0; + if (info.dataDir.equals(info.deviceProtectedDataDir)) { + flags |= StorageManager.FLAG_STORAGE_DE; + } else if (info.dataDir.equals(info.credentialProtectedDataDir)) { + flags |= StorageManager.FLAG_STORAGE_CE; + } else { + Slog.e(TAG, "Could not infer CE/DE storage for package " + info.packageName); + mPackageDexUsage.removeUserPackage(packageName, dexUseInfo.getOwnerUserId()); + continue; + } + + boolean dexStillExists = true; + synchronized(mInstallLock) { + try { + String[] isas = dexUseInfo.getLoaderIsas().toArray(new String[0]); + dexStillExists = mInstaller.reconcileSecondaryDexFile(dexPath, packageName, + pkg.applicationInfo.uid, isas, pkg.applicationInfo.volumeUuid, flags); + } catch (InstallerException e) { + Slog.e(TAG, "Got InstallerException when reconciling dex " + dexPath + + " : " + e.getMessage()); + } + } + if (!dexStillExists) { + mPackageDexUsage.removeDexFile(packageName, dexPath, dexUseInfo.getOwnerUserId()); + } + } + } + + /** * Retrieves the package which owns the given dexPath. */ private DexSearchResult getDexPackage( diff --git a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java index 10384a2749e8..253d0e97f51e 100644 --- a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java +++ b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java @@ -376,9 +376,64 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { } } + /** + * Remove all the records about package {@code packageName} belonging to user {@code userId}. + */ + public boolean removeUserPackage(String packageName, int userId) { + synchronized (mPackageUseInfoMap) { + PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(packageName); + if (packageUseInfo == null) { + return false; + } + boolean updated = false; + Iterator<Map.Entry<String, DexUseInfo>> dIt = + packageUseInfo.mDexUseInfoMap.entrySet().iterator(); + while (dIt.hasNext()) { + DexUseInfo dexUseInfo = dIt.next().getValue(); + if (dexUseInfo.mOwnerUserId == userId) { + dIt.remove(); + updated = true; + } + } + return updated; + } + } + + /** + * Remove the secondary dex file record belonging to the package {@code packageName} + * and user {@code userId}. + */ + public boolean removeDexFile(String packageName, String dexFile, int userId) { + synchronized (mPackageUseInfoMap) { + PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(packageName); + if (packageUseInfo == null) { + return false; + } + return removeDexFile(packageUseInfo, dexFile, userId); + } + } + + private boolean removeDexFile(PackageUseInfo packageUseInfo, String dexFile, int userId) { + DexUseInfo dexUseInfo = packageUseInfo.mDexUseInfoMap.get(dexFile); + if (dexUseInfo == null) { + return false; + } + if (dexUseInfo.mOwnerUserId == userId) { + packageUseInfo.mDexUseInfoMap.remove(dexFile); + return true; + } + return false; + } + public PackageUseInfo getPackageUseInfo(String packageName) { synchronized (mPackageUseInfoMap) { - return mPackageUseInfoMap.get(packageName); + PackageUseInfo useInfo = mPackageUseInfoMap.get(packageName); + // The useInfo contains a map for secondary dex files which could be modified + // concurrently after this method returns and thus outside the locking we do here. + // (i.e. the map is updated when new class loaders are created, which can happen anytime + // after this method returns) + // Make a defensive copy to be sure we don't get concurrent modifications. + return useInfo == null ? null : new PackageUseInfo(useInfo); } } 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 4d76312a2b1c..90a2ec0c7628 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 @@ -73,7 +73,7 @@ public class DexManagerTests { mInvalidIsa = new TestData("INVALID", "INVALID_ISA", mUser0); mDoesNotExist = new TestData("DOES.NOT.EXIST", isa, mUser1); - mDexManager = new DexManager(null, null); + mDexManager = new DexManager(null, null, null, null); // Foo and Bar are available to user0. // Only Bar is available to user1; diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java index 5a428414a970..19e0bcf6ba56 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java @@ -256,6 +256,32 @@ public class PackageDexUsageTests { assertNull(mPackageDexUsage.getPackageUseInfo(mFooBaseUser0.mPackageName)); } + @Test + public void testRemoveUserPackage() { + // Record Bar secondaries for two different users. + assertTrue(record(mBarSecondary1User0)); + assertTrue(record(mBarSecondary2User1)); + + // Remove user 0 files. + assertTrue(mPackageDexUsage.removeUserPackage(mBarSecondary1User0.mPackageName, + mBarSecondary1User0.mOwnerUserId)); + // Assert that only user 1 files are there. + assertPackageDexUsage(null, mBarSecondary2User1); + } + + @Test + public void testRemoveDexFile() { + // Record Bar secondaries for two different users. + assertTrue(record(mBarSecondary1User0)); + assertTrue(record(mBarSecondary2User1)); + + // Remove mBarSecondary1User0 file. + assertTrue(mPackageDexUsage.removeDexFile(mBarSecondary1User0.mPackageName, + mBarSecondary1User0.mDexFile, mBarSecondary1User0.mOwnerUserId)); + // Assert that only user 1 files are there. + assertPackageDexUsage(null, mBarSecondary2User1); + } + private void assertPackageDexUsage(TestData primary, TestData... secondaries) { String packageName = primary == null ? secondaries[0].mPackageName : primary.mPackageName; boolean primaryUsedByOtherApps = primary == null ? false : primary.mUsedByOtherApps; |