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:
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user