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
This commit is contained in:
Calin Juravle
2017-02-22 19:05:06 -08:00
parent 8a51023605
commit 99dd37b3c5
5 changed files with 283 additions and 20 deletions

View File

@@ -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);

View File

@@ -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);
}
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));
/**
* 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();
}
}
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<String> mSplitCodePaths;
// Maps user id to the application private directory.
private final Map<Integer, Set<String>> 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<String> dataDirs = putIfAbsent(mAppDataDirs, userId, new HashSet<>());
dataDirs.add(ai.dataDir);
dataDirs.add(dataDir);
}
public int searchDex(String dexPath, int userId) {

View File

@@ -376,8 +376,35 @@ public class PackageDexUsage extends AbstractStatsBase<Void> {
}
}
/**
* 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<Void> {
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;
}
}

View File

@@ -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<String> 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<String> 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<String> 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<String> 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];
}
}
}

View File

@@ -256,6 +256,30 @@ public class PackageDexUsageTests {
assertNull(mPackageDexUsage.getPackageUseInfo(mFooBaseUser0.mPackageName));
}
@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.
@@ -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;