Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 24ff75fb authored by Calin Juravle's avatar Calin Juravle
Browse files

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

(cherry picked from commit 99dd37b3)

Merged-In: I3a249b9e8680e745fa678c7ce61b4ae764078fb9
Change-Id: Ia8416e7232cda3e42a8dccd51cb152a237e0f317
parent cbcb388d
Loading
Loading
Loading
Loading
+6 −0
Original line number Original line Diff line number Diff line
@@ -7738,6 +7738,7 @@ public class PackageManagerService extends IPackageManager.Stub {
            } catch (InstallerException e) {
            } catch (InstallerException e) {
                Slog.w(TAG, String.valueOf(e));
                Slog.w(TAG, String.valueOf(e));
            }
            }
            mDexManager.notifyPackageDataDestroyed(pkg.packageName, userId);
        }
        }
    }
    }
@@ -14449,6 +14450,8 @@ public class PackageManagerService extends IPackageManager.Stub {
                }
                }
                prepareAppDataAfterInstallLIF(newPackage);
                prepareAppDataAfterInstallLIF(newPackage);
                addedPkg = true;
                addedPkg = true;
                mDexManager.notifyPackageUpdated(newPackage.packageName,
                        newPackage.baseCodePath, newPackage.splitCodePaths);
            } catch (PackageManagerException e) {
            } catch (PackageManagerException e) {
                res.setError("Package couldn't be installed in " + pkg.codePath, e);
                res.setError("Package couldn't be installed in " + pkg.codePath, e);
            }
            }
@@ -14596,6 +14599,9 @@ public class PackageManagerService extends IPackageManager.Stub {
                updateSettingsLI(newPackage, installerPackageName, allUsers, res, user);
                updateSettingsLI(newPackage, installerPackageName, allUsers, res, user);
                prepareAppDataAfterInstallLIF(newPackage);
                prepareAppDataAfterInstallLIF(newPackage);
                mDexManager.notifyPackageUpdated(newPackage.packageName,
                            newPackage.baseCodePath, newPackage.splitCodePaths);
            }
            }
        } catch (PackageManagerException e) {
        } catch (PackageManagerException e) {
            res.setReturnCode(INSTALL_FAILED_INTERNAL_ERROR);
            res.setReturnCode(INSTALL_FAILED_INTERNAL_ERROR);
+77 −19
Original line number Original line Diff line number Diff line
@@ -22,6 +22,7 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageParser;
import android.content.pm.PackageParser;
import android.os.RemoteException;
import android.os.RemoteException;
import android.os.storage.StorageManager;
import android.os.storage.StorageManager;
import android.os.UserHandle;


import android.util.Slog;
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);
     * Notifies that package {@code packageName} was updated.
        if (pcl != null) {
     * This will clear the UsedByOtherApps mark if it exists.
            pcl.mergeAppDataDirs(info.applicationInfo, userId);
     */
        } else {
    public void notifyPackageUpdated(String packageName, String baseCodePath,
            mPackageCodeLocationsCache.put(info.packageName,
            String[] splitCodePaths) {
                new PackageCodeLocations(info.applicationInfo, userId));
        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();
            int userId = entry.getKey();
            for (PackageInfo pi : packageInfoList) {
            for (PackageInfo pi : packageInfoList) {
                // Cache the code locations.
                // 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.
                // 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
                // We will use it to sync the data and remove obsolete entries from
@@ -425,27 +474,36 @@ public class DexManager {
     */
     */
    private static class PackageCodeLocations {
    private static class PackageCodeLocations {
        private final String mPackageName;
        private final String mPackageName;
        private final String mBaseCodePath;
        private String mBaseCodePath;
        private final Set<String> mSplitCodePaths;
        private final Set<String> mSplitCodePaths;
        // Maps user id to the application private directory.
        // Maps user id to the application private directory.
        private final Map<Integer, Set<String>> mAppDataDirs;
        private final Map<Integer, Set<String>> mAppDataDirs;


        public PackageCodeLocations(ApplicationInfo ai, int userId) {
        public PackageCodeLocations(ApplicationInfo ai, int userId) {
            mPackageName = ai.packageName;
            this(ai.packageName, ai.sourceDir, ai.splitSourceDirs);
            mBaseCodePath = ai.sourceDir;
            mergeAppDataDirs(ai.dataDir, userId);
        }
        public PackageCodeLocations(String packageName, String baseCodePath,
                String[] splitCodePaths) {
            mPackageName = packageName;
            mSplitCodePaths = new HashSet<>();
            mSplitCodePaths = new HashSet<>();
            if (ai.splitSourceDirs != null) {
            mAppDataDirs = new HashMap<>();
                for (String split : ai.splitSourceDirs) {
            updateCodeLocation(baseCodePath, splitCodePaths);
        }

        public void updateCodeLocation(String baseCodePath, String[] splitCodePaths) {
            mBaseCodePath = baseCodePath;
            mSplitCodePaths.clear();
            if (splitCodePaths != null) {
                for (String split : splitCodePaths) {
                    mSplitCodePaths.add(split);
                    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<>());
            Set<String> dataDirs = putIfAbsent(mAppDataDirs, userId, new HashSet<>());
            dataDirs.add(ai.dataDir);
            dataDirs.add(dataDir);
        }
        }


        public int searchDex(String dexPath, int userId) {
        public int searchDex(String dexPath, int userId) {
+33 −0
Original line number Original line Diff line number Diff line
@@ -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}.
     * 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,
     * @return true if the record was found and actually deleted,
     *         false if the record doesn't exist
     *         false if the record doesn't exist
     */
     */
@@ -397,6 +424,12 @@ public class PackageDexUsage extends AbstractStatsBase<Void> {
                    updated = true;
                    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;
            return updated;
        }
        }
    }
    }
+117 −1
Original line number Original line Diff line number Diff line
@@ -16,9 +16,10 @@


package com.android.server.pm.dex;
package com.android.server.pm.dex;


import android.os.Build;
import android.content.pm.ApplicationInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInfo;
import android.os.Build;
import android.os.UserHandle;
import android.support.test.filters.SmallTest;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.support.test.runner.AndroidJUnit4;


@@ -57,6 +58,7 @@ public class DexManagerTests {


    private int mUser0;
    private int mUser0;
    private int mUser1;
    private int mUser1;

    @Before
    @Before
    public void setup() {
    public void setup() {


@@ -243,6 +245,113 @@ public class DexManagerTests {
        assertSecondaryUse(newPackage, pui, newSecondaries, /*isUsedByOtherApps*/false, mUser0);
        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,
    private void assertSecondaryUse(TestData testData, PackageUseInfo pui,
            List<String> secondaries, boolean isUsedByOtherApps, int ownerUserId) {
            List<String> secondaries, boolean isUsedByOtherApps, int ownerUserId) {
        for (String dex : secondaries) {
        for (String dex : secondaries) {
@@ -317,5 +426,12 @@ public class DexManagerTests {
            }
            }
            return paths;
            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];
        }
    }
    }
}
}
+50 −0
Original line number Original line Diff line number Diff line
@@ -256,6 +256,30 @@ public class PackageDexUsageTests {
        assertNull(mPackageDexUsage.getPackageUseInfo(mFooBaseUser0.mPackageName));
        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
    @Test
    public void testRemoveUserPackage() {
    public void testRemoveUserPackage() {
        // Record Bar secondaries for two different users.
        // Record Bar secondaries for two different users.
@@ -282,6 +306,32 @@ public class PackageDexUsageTests {
        assertPackageDexUsage(null, mBarSecondary2User1);
        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) {
    private void assertPackageDexUsage(TestData primary, TestData... secondaries) {
        String packageName = primary == null ? secondaries[0].mPackageName : primary.mPackageName;
        String packageName = primary == null ? secondaries[0].mPackageName : primary.mPackageName;
        boolean primaryUsedByOtherApps = primary == null ? false : primary.mUsedByOtherApps;
        boolean primaryUsedByOtherApps = primary == null ? false : primary.mUsedByOtherApps;