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
}
}
+ 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) {
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(
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<>();
return runInstallWrite();
case "compile":
return runCompile();
+ case "reconcile-secondary-dex-files":
+ return runreconcileSecondaryDexFiles();
case "dump-profiles":
return runDumpProfiles();
case "list":
}
}
+ private int runreconcileSecondaryDexFiles() throws RemoteException {
+ String packageName = getNextArg();
+ mInterface.reconcileSecondaryDexFiles(packageName);
+ return 0;
+ }
+
private int runDumpProfiles() throws RemoteException {
String packageName = getNextArg();
mInterface.dumpProfiles(packageName);
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]");
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;
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
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;
}
/**
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,
}
/**
+ * 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(
}
}
+ /**
+ * 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);
}
}
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;
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;