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

Commit 3c954e9e authored by Calin Juravle's avatar Calin Juravle Committed by Android (Google) Code Review
Browse files

Merge "Update package use info when the app data is updated"

parents 8fb42cc3 99dd37b3
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -8719,6 +8719,7 @@ public class PackageManagerService extends IPackageManager.Stub {
            } catch (InstallerException e) {
                Slog.w(TAG, String.valueOf(e));
            }
            mDexManager.notifyPackageDataDestroyed(pkg.packageName, userId);
        }
    }
@@ -16134,6 +16135,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);
            }
@@ -16283,6 +16286,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);
+77 −19
Original line number Diff line number Diff line
@@ -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
@@ -425,27 +474,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) {
+33 −0
Original line number 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}.
     * 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;
        }
    }
+117 −1
Original line number Diff line number Diff line
@@ -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];
        }
    }
}
+50 −0
Original line number Diff line number Diff line
@@ -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;