From 99dd37b3c5262910150ef955d16a33d32da264dd Mon Sep 17 00:00:00 2001 From: Calin Juravle Date: Wed, 22 Feb 2017 19:05:06 -0800 Subject: [PATCH] Update package use info when the app data is updated - clear usesByOtherApps flag when the package is updated - delete secondary dex usage data when the app data is destroyed Test: runtest -x .../PackageDexUsageTests.java runtest -x .../DexManagerTests.java Bug: 32871170 Bug: 35381405 Change-Id: I3a249b9e8680e745fa678c7ce61b4ae764078fb9 --- .../android/server/pm/PackageManagerService.java | 6 ++ .../java/com/android/server/pm/dex/DexManager.java | 96 +++++++++++++---- .../com/android/server/pm/dex/PackageDexUsage.java | 33 ++++++ .../com/android/server/pm/dex/DexManagerTests.java | 118 ++++++++++++++++++++- .../server/pm/dex/PackageDexUsageTests.java | 50 +++++++++ 5 files changed, 283 insertions(+), 20 deletions(-) diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 371a062c0c43..d38a3dc99b24 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -8592,6 +8592,7 @@ public class PackageManagerService extends IPackageManager.Stub { } catch (InstallerException e) { Slog.w(TAG, String.valueOf(e)); } + mDexManager.notifyPackageDataDestroyed(pkg.packageName, userId); } } @@ -16109,6 +16110,8 @@ public class PackageManagerService extends IPackageManager.Stub { setInstantAppForUser(ps, user.getIdentifier(), instantApp, fullApp); prepareAppDataAfterInstallLIF(newPackage); addedPkg = true; + mDexManager.notifyPackageUpdated(newPackage.packageName, + newPackage.baseCodePath, newPackage.splitCodePaths); } catch (PackageManagerException e) { res.setError("Package couldn't be installed in " + pkg.codePath, e); } @@ -16258,6 +16261,9 @@ public class PackageManagerService extends IPackageManager.Stub { updateSettingsLI(newPackage, installerPackageName, allUsers, res, user, installReason); prepareAppDataAfterInstallLIF(newPackage); + + mDexManager.notifyPackageUpdated(newPackage.packageName, + newPackage.baseCodePath, newPackage.splitCodePaths); } } catch (PackageManagerException e) { res.setReturnCode(INSTALL_FAILED_INTERNAL_ERROR); 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 00f3711c7038..11f655ccceb9 100644 --- a/services/core/java/com/android/server/pm/dex/DexManager.java +++ b/services/core/java/com/android/server/pm/dex/DexManager.java @@ -22,6 +22,7 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageParser; import android.os.RemoteException; import android.os.storage.StorageManager; +import android.os.UserHandle; import android.util.Slog; @@ -179,17 +180,64 @@ public class DexManager { } } - public void notifyPackageInstalled(PackageInfo info, int userId) { - cachePackageCodeLocation(info, userId); + /** + * Notifies that a new package was installed for {@code userId}. + * {@code userId} must not be {@code UserHandle.USER_ALL}. + * + * @throws IllegalArgumentException if {@code userId} is {@code UserHandle.USER_ALL}. + */ + public void notifyPackageInstalled(PackageInfo pi, int userId) { + if (userId == UserHandle.USER_ALL) { + throw new IllegalArgumentException( + "notifyPackageInstalled called with USER_ALL"); + } + cachePackageCodeLocation(pi.packageName, pi.applicationInfo.sourceDir, + pi.applicationInfo.splitSourceDirs, pi.applicationInfo.dataDir, userId); + } + + /** + * Notifies that package {@code packageName} was updated. + * This will clear the UsedByOtherApps mark if it exists. + */ + public void notifyPackageUpdated(String packageName, String baseCodePath, + String[] splitCodePaths) { + cachePackageCodeLocation(packageName, baseCodePath, splitCodePaths, null, /*userId*/ -1); + // In case there was an update, write the package use info to disk async. + // Note that we do the writing here and not in PackageDexUsage in order to be + // consistent with other methods in DexManager (e.g. reconcileSecondaryDexFiles performs + // multiple updates in PackaeDexUsage before writing it). + if (mPackageDexUsage.clearUsedByOtherApps(packageName)) { + mPackageDexUsage.maybeWriteAsync(); + } + } + + /** + * Notifies that the user {@code userId} data for package {@code packageName} + * was destroyed. This will remove all usage info associated with the package + * for the given user. + * {@code userId} is allowed to be {@code UserHandle.USER_ALL} in which case + * all usage information for the package will be removed. + */ + public void notifyPackageDataDestroyed(String packageName, int userId) { + boolean updated = userId == UserHandle.USER_ALL + ? mPackageDexUsage.removePackage(packageName) + : mPackageDexUsage.removeUserPackage(packageName, userId); + // In case there was an update, write the package use info to disk async. + // Note that we do the writing here and not in PackageDexUsage in order to be + // consistent with other methods in DexManager (e.g. reconcileSecondaryDexFiles performs + // multiple updates in PackaeDexUsage before writing it). + if (updated) { + mPackageDexUsage.maybeWriteAsync(); + } } - private void cachePackageCodeLocation(PackageInfo info, int userId) { - PackageCodeLocations pcl = mPackageCodeLocationsCache.get(info.packageName); - if (pcl != null) { - pcl.mergeAppDataDirs(info.applicationInfo, userId); - } else { - mPackageCodeLocationsCache.put(info.packageName, - new PackageCodeLocations(info.applicationInfo, userId)); + public void cachePackageCodeLocation(String packageName, String baseCodePath, + String[] splitCodePaths, String dataDir, int userId) { + PackageCodeLocations pcl = putIfAbsent(mPackageCodeLocationsCache, packageName, + new PackageCodeLocations(packageName, baseCodePath, splitCodePaths)); + pcl.updateCodeLocation(baseCodePath, splitCodePaths); + if (dataDir != null) { + pcl.mergeAppDataDirs(dataDir, userId); } } @@ -202,7 +250,8 @@ public class DexManager { int userId = entry.getKey(); for (PackageInfo pi : packageInfoList) { // Cache the code locations. - cachePackageCodeLocation(pi, userId); + cachePackageCodeLocation(pi.packageName, pi.applicationInfo.sourceDir, + pi.applicationInfo.splitSourceDirs, pi.applicationInfo.dataDir, userId); // Cache a map from package name to the set of user ids who installed the package. // We will use it to sync the data and remove obsolete entries from @@ -408,27 +457,36 @@ public class DexManager { */ private static class PackageCodeLocations { private final String mPackageName; - private final String mBaseCodePath; + private String mBaseCodePath; private final Set mSplitCodePaths; // Maps user id to the application private directory. private final Map> mAppDataDirs; public PackageCodeLocations(ApplicationInfo ai, int userId) { - mPackageName = ai.packageName; - mBaseCodePath = ai.sourceDir; + this(ai.packageName, ai.sourceDir, ai.splitSourceDirs); + mergeAppDataDirs(ai.dataDir, userId); + } + public PackageCodeLocations(String packageName, String baseCodePath, + String[] splitCodePaths) { + mPackageName = packageName; mSplitCodePaths = new HashSet<>(); - if (ai.splitSourceDirs != null) { - for (String split : ai.splitSourceDirs) { + mAppDataDirs = new HashMap<>(); + updateCodeLocation(baseCodePath, splitCodePaths); + } + + public void updateCodeLocation(String baseCodePath, String[] splitCodePaths) { + mBaseCodePath = baseCodePath; + mSplitCodePaths.clear(); + if (splitCodePaths != null) { + for (String split : splitCodePaths) { mSplitCodePaths.add(split); } } - mAppDataDirs = new HashMap<>(); - mergeAppDataDirs(ai, userId); } - public void mergeAppDataDirs(ApplicationInfo ai, int userId) { + public void mergeAppDataDirs(String dataDir, int userId) { Set dataDirs = putIfAbsent(mAppDataDirs, userId, new HashSet<>()); - dataDirs.add(ai.dataDir); + dataDirs.add(dataDir); } public int searchDex(String dexPath, int userId) { 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 3693bce04eb1..8a66f12cb6d9 100644 --- a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java +++ b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java @@ -377,7 +377,34 @@ public class PackageDexUsage extends AbstractStatsBase { } /** + * Clears the {@code usesByOtherApps} marker for the package {@code packageName}. + * @return true if the package usage info was updated. + */ + public boolean clearUsedByOtherApps(String packageName) { + synchronized (mPackageUseInfoMap) { + PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(packageName); + if (packageUseInfo == null || !packageUseInfo.mIsUsedByOtherApps) { + return false; + } + packageUseInfo.mIsUsedByOtherApps = false; + return true; + } + } + + /** + * Remove the usage data associated with package {@code packageName}. + * @return true if the package usage was found and removed successfully. + */ + public boolean removePackage(String packageName) { + synchronized (mPackageUseInfoMap) { + return mPackageUseInfoMap.remove(packageName) != null; + } + } + + /** * Remove all the records about package {@code packageName} belonging to user {@code userId}. + * If the package is left with no records of secondary dex usage and is not used by other + * apps it will be removed as well. * @return true if the record was found and actually deleted, * false if the record doesn't exist */ @@ -397,6 +424,12 @@ public class PackageDexUsage extends AbstractStatsBase { updated = true; } } + // If no secondary dex info is left and the package is not used by other apps + // remove the data since it is now useless. + if (packageUseInfo.mDexUseInfoMap.isEmpty() && !packageUseInfo.mIsUsedByOtherApps) { + mPackageUseInfoMap.remove(packageName); + updated = true; + } return updated; } } 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 90a2ec0c7628..fa0bd392f75d 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 @@ -16,9 +16,10 @@ package com.android.server.pm.dex; -import android.os.Build; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; +import android.os.Build; +import android.os.UserHandle; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; @@ -57,6 +58,7 @@ public class DexManagerTests { private int mUser0; private int mUser1; + @Before public void setup() { @@ -243,6 +245,113 @@ public class DexManagerTests { assertSecondaryUse(newPackage, pui, newSecondaries, /*isUsedByOtherApps*/false, mUser0); } + @Test + public void testNotifyPackageUpdated() { + // Foo loads Bar main apks. + notifyDexLoad(mFooUser0, mBarUser0.getBaseAndSplitDexPaths(), mUser0); + + // Bar is used by others now and should be in our records. + PackageUseInfo pui = getPackageUseInfo(mBarUser0); + assertNotNull(pui); + assertTrue(pui.isUsedByOtherApps()); + assertTrue(pui.getDexUseInfoMap().isEmpty()); + + // Notify that bar is updated. + mDexManager.notifyPackageUpdated(mBarUser0.getPackageName(), + mBarUser0.mPackageInfo.applicationInfo.sourceDir, + mBarUser0.mPackageInfo.applicationInfo.splitSourceDirs); + + // The usedByOtherApps flag should be clear now. + pui = getPackageUseInfo(mBarUser0); + assertNotNull(pui); + assertFalse(pui.isUsedByOtherApps()); + } + + @Test + public void testNotifyPackageUpdatedCodeLocations() { + // Simulate a split update. + String newSplit = mBarUser0.replaceLastSplit(); + List newSplits = new ArrayList<>(); + newSplits.add(newSplit); + + // We shouldn't find yet the new split as we didn't notify the package update. + notifyDexLoad(mFooUser0, newSplits, mUser0); + PackageUseInfo pui = getPackageUseInfo(mBarUser0); + assertNull(pui); + + // Notify that bar is updated. splitSourceDirs will contain the updated path. + mDexManager.notifyPackageUpdated(mBarUser0.getPackageName(), + mBarUser0.mPackageInfo.applicationInfo.sourceDir, + mBarUser0.mPackageInfo.applicationInfo.splitSourceDirs); + + // Now, when the split is loaded we will find it and we should mark Bar as usedByOthers. + notifyDexLoad(mFooUser0, newSplits, mUser0); + pui = getPackageUseInfo(mBarUser0); + assertNotNull(pui); + assertTrue(pui.isUsedByOtherApps()); + } + + @Test + public void testNotifyPackageDataDestroyForOne() { + // Bar loads its own secondary files. + notifyDexLoad(mBarUser0, mBarUser0.getSecondaryDexPaths(), mUser0); + notifyDexLoad(mBarUser1, mBarUser1.getSecondaryDexPaths(), mUser1); + + mDexManager.notifyPackageDataDestroyed(mBarUser0.getPackageName(), mUser0); + + // Bar should not be around since it was removed for all users. + PackageUseInfo pui = getPackageUseInfo(mBarUser1); + assertNotNull(pui); + assertSecondaryUse(mBarUser1, pui, mBarUser1.getSecondaryDexPaths(), + /*isUsedByOtherApps*/false, mUser1); + } + + @Test + public void testNotifyPackageDataDestroyForeignUse() { + // Foo loads its own secondary files. + List fooSecondaries = mFooUser0.getSecondaryDexPaths(); + notifyDexLoad(mFooUser0, fooSecondaries, mUser0); + + // Bar loads Foo main apks. + notifyDexLoad(mBarUser0, mFooUser0.getBaseAndSplitDexPaths(), mUser0); + + mDexManager.notifyPackageDataDestroyed(mFooUser0.getPackageName(), mUser0); + + // Foo should still be around since it's used by other apps but with no + // secondary dex info. + PackageUseInfo pui = getPackageUseInfo(mFooUser0); + assertNotNull(pui); + assertTrue(pui.isUsedByOtherApps()); + assertTrue(pui.getDexUseInfoMap().isEmpty()); + } + + @Test + public void testNotifyPackageDataDestroyComplete() { + // Foo loads its own secondary files. + List fooSecondaries = mFooUser0.getSecondaryDexPaths(); + notifyDexLoad(mFooUser0, fooSecondaries, mUser0); + + mDexManager.notifyPackageDataDestroyed(mFooUser0.getPackageName(), mUser0); + + // Foo should not be around since all its secondary dex info were deleted + // and it is not used by other apps. + PackageUseInfo pui = getPackageUseInfo(mFooUser0); + assertNull(pui); + } + + @Test + public void testNotifyPackageDataDestroyForAll() { + // Foo loads its own secondary files. + notifyDexLoad(mBarUser0, mBarUser0.getSecondaryDexPaths(), mUser0); + notifyDexLoad(mBarUser1, mBarUser1.getSecondaryDexPaths(), mUser1); + + mDexManager.notifyPackageDataDestroyed(mBarUser0.getPackageName(), UserHandle.USER_ALL); + + // Bar should not be around since it was removed for all users. + PackageUseInfo pui = getPackageUseInfo(mBarUser0); + assertNull(pui); + } + private void assertSecondaryUse(TestData testData, PackageUseInfo pui, List secondaries, boolean isUsedByOtherApps, int ownerUserId) { for (String dex : secondaries) { @@ -317,5 +426,12 @@ public class DexManagerTests { } return paths; } + + String replaceLastSplit() { + int length = mPackageInfo.applicationInfo.splitSourceDirs.length; + // Add an extra bogus dex extension to simulate a new split name. + mPackageInfo.applicationInfo.splitSourceDirs[length - 1] += ".dex"; + return mPackageInfo.applicationInfo.splitSourceDirs[length - 1]; + } } } 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 19e0bcf6ba56..2e99433149ea 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 @@ -257,6 +257,30 @@ public class PackageDexUsageTests { } @Test + public void testRemovePackage() { + // Record Bar secondaries for two different users. + assertTrue(record(mBarSecondary1User0)); + assertTrue(record(mBarSecondary2User1)); + + // Remove the package. + assertTrue(mPackageDexUsage.removePackage(mBarSecondary1User0.mPackageName)); + // Assert that we can't find the package anymore. + assertNull(mPackageDexUsage.getPackageUseInfo(mBarSecondary1User0.mPackageName)); + } + + @Test + public void testRemoveNonexistentPackage() { + // Record Bar secondaries for two different users. + assertTrue(record(mBarSecondary1User0)); + + // Remove the package. + assertTrue(mPackageDexUsage.removePackage(mBarSecondary1User0.mPackageName)); + // Remove the package again. It should return false because the package no longer + // has a record in the use info. + assertFalse(mPackageDexUsage.removePackage(mBarSecondary1User0.mPackageName)); + } + + @Test public void testRemoveUserPackage() { // Record Bar secondaries for two different users. assertTrue(record(mBarSecondary1User0)); @@ -282,6 +306,32 @@ public class PackageDexUsageTests { assertPackageDexUsage(null, mBarSecondary2User1); } + @Test + public void testClearUsedByOtherApps() { + // Write a package which is used by other apps. + assertTrue(record(mFooSplit2UsedByOtherApps0)); + assertTrue(mPackageDexUsage.clearUsedByOtherApps(mFooSplit2UsedByOtherApps0.mPackageName)); + + // Check that the package is no longer used by other apps. + TestData noLongerUsedByOtherApps = new TestData( + mFooSplit2UsedByOtherApps0.mPackageName, + mFooSplit2UsedByOtherApps0.mDexFile, + mFooSplit2UsedByOtherApps0.mOwnerUserId, + mFooSplit2UsedByOtherApps0.mLoaderIsa, + /*mIsUsedByOtherApps*/false, + mFooSplit2UsedByOtherApps0.mPrimaryOrSplit); + assertPackageDexUsage(noLongerUsedByOtherApps); + } + + @Test + public void testClearUsedByOtherAppsNonexistent() { + // Write a package which is used by other apps. + assertTrue(record(mFooSplit2UsedByOtherApps0)); + assertTrue(mPackageDexUsage.clearUsedByOtherApps(mFooSplit2UsedByOtherApps0.mPackageName)); + // Clearing again should return false as there should be no update on the use info. + assertFalse(mPackageDexUsage.clearUsedByOtherApps(mFooSplit2UsedByOtherApps0.mPackageName)); + } + private void assertPackageDexUsage(TestData primary, TestData... secondaries) { String packageName = primary == null ? secondaries[0].mPackageName : primary.mPackageName; boolean primaryUsedByOtherApps = primary == null ? false : primary.mUsedByOtherApps; -- 2.11.0