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

Commit e6751e09 authored by Lee Shombert's avatar Lee Shombert
Browse files

PM caches now cache nulls

PackageManager caches for getPackagesForUid(), getApplicationInfo(),
and getPackageInfo() now cache null results.  That is, if PM returns
null for a query, that null result is cached against the query.

AppsFilterImpl invalidates the caches to cover the situation in which
an application gains visibility and is now able to query package info.
The corresponding AppsFilterImplTest is updated to disable the caches
for the duration of the tests.

This change also makes invalidateGetPackagesForUidCache() faster by
going directly to the static cache.  This avoids the search for the
nonce handler that corresponds to the property string.

The cache declarations are refactored slightly to use the new Args()
constructor.

The name of the cache for getPackagesForUid() has been shortened to
"get_packages_for_uid" instead of the full property name.

Manually verified via dumpsys cacheinfo that the cache keys have not
changed and that the respective invalidations did not change
unexpectedly.

Flag: EXEMPT bug-fix
Bug: 382719639
Test: atest
 * com.android.server.pm.PackageManagerTests
 * android.content.pm.cts.PackageManagerTest
 * CtsAppEnumerationTestCases
Change-Id: I444379873a17ac9e8cdf4ce5a6ea5b4e59274a65
parent 24b592ab
Loading
Loading
Loading
Loading
+13 −10
Original line number Diff line number Diff line
@@ -17,7 +17,6 @@
package android.app;

import static android.app.PropertyInvalidatedCache.MODULE_SYSTEM;
import static android.app.PropertyInvalidatedCache.createSystemCacheKey;
import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_COLORED;
import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_NOT_COLORED;
import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
@@ -1146,12 +1145,16 @@ public class ApplicationPackageManager extends PackageManager {
        }
    }

    private static final String CACHE_KEY_PACKAGES_FOR_UID_PROPERTY =
            createSystemCacheKey("get_packages_for_uid");
    private static final PropertyInvalidatedCache<Integer, GetPackagesForUidResult>
            mGetPackagesForUidCache =
            new PropertyInvalidatedCache<Integer, GetPackagesForUidResult>(
                1024, CACHE_KEY_PACKAGES_FOR_UID_PROPERTY) {
    private static final String CACHE_KEY_PACKAGES_FOR_UID_API = "get_packages_for_uid";

    /** @hide */
    @VisibleForTesting
    public static final PropertyInvalidatedCache<Integer, GetPackagesForUidResult>
            sGetPackagesForUidCache = new PropertyInvalidatedCache<>(
                new PropertyInvalidatedCache.Args(MODULE_SYSTEM)
                .maxEntries(1024).api(CACHE_KEY_PACKAGES_FOR_UID_API).cacheNulls(true),
                CACHE_KEY_PACKAGES_FOR_UID_API, null) {

                @Override
                public GetPackagesForUidResult recompute(Integer uid) {
                    try {
@@ -1170,17 +1173,17 @@ public class ApplicationPackageManager extends PackageManager {

    @Override
    public String[] getPackagesForUid(int uid) {
        return mGetPackagesForUidCache.query(uid).value();
        return sGetPackagesForUidCache.query(uid).value();
    }

    /** @hide */
    public static void disableGetPackagesForUidCache() {
        mGetPackagesForUidCache.disableLocal();
        sGetPackagesForUidCache.disableLocal();
    }

    /** @hide */
    public static void invalidateGetPackagesForUidCache() {
        PropertyInvalidatedCache.invalidateCache(CACHE_KEY_PACKAGES_FOR_UID_PROPERTY);
        sGetPackagesForUidCache.invalidateCache();
    }

    @Override
+26 −6
Original line number Diff line number Diff line
@@ -1165,6 +1165,17 @@ public class PropertyInvalidatedCache<Query, Result> {
        return new NonceWatcher(getNonceHandler(propertyName));
    }

    /**
     * Return the current cache nonce.
     * @hide
     */
    @VisibleForTesting
    public long getNonce() {
        synchronized (mLock) {
            return mNonce.getNonce();
        }
    }

    /**
     * Complete key prefixes.
     */
@@ -1317,7 +1328,7 @@ public class PropertyInvalidatedCache<Query, Result> {

    /**
     * Burst a property name into module and api.  Throw if the key is invalid.  This method is
     * used in to transition legacy cache constructors to the args constructor.
     * used to transition legacy cache constructors to the Args constructor.
     */
    private static Args argsFromProperty(@NonNull String name) {
        throwIfInvalidCacheKey(name);
@@ -1329,6 +1340,15 @@ public class PropertyInvalidatedCache<Query, Result> {
        return new Args(module).api(api);
    }

    /**
     * Return the API porting of a legacy property.  This method is used to transition caches to
     * the Args constructor.
     * @hide
     */
    public static String apiFromProperty(@NonNull String name) {
        return argsFromProperty(name).mApi;
    }

    /**
     * Make a new property invalidated cache.  This constructor names the cache after the
     * property name.  New clients should prefer the constructor that takes an explicit
@@ -2039,11 +2059,11 @@ public class PropertyInvalidatedCache<Query, Result> {
    }

    /**
     * Disable all caches in the local process.  This is primarily useful for testing when
     * the test needs to bypass the cache or when the test is for a server, and the test
     * process does not have privileges to write SystemProperties. Once disabled it is not
     * possible to re-enable caching in the current process.  If a client wants to
     * temporarily disable caching, use the corking mechanism.
     * Disable all caches in the local process.  This is primarily useful for testing when the
     * test needs to bypass the cache or when the test is for a server, and the test process does
     * not have privileges to write the nonce. Once disabled it is not possible to re-enable
     * caching in the current process.  See {@link #testPropertyName} for a more focused way to
     * bypass caches when the test is for a server.
     * @hide
     */
    public static void disableForTestMode() {
+22 −9
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package android.content.pm;

import static android.app.PropertyInvalidatedCache.MODULE_SYSTEM;
import static android.content.pm.SigningInfo.AppSigningSchemeVersion;
import static android.media.audio.Flags.FLAG_FEATURE_SPATIAL_AUDIO_HEADTRACKING_LOW_LATENCY;

@@ -11659,11 +11660,22 @@ public abstract class PackageManager {
        }
    }

    private static final PropertyInvalidatedCache<ApplicationInfoQuery, ApplicationInfo>
            sApplicationInfoCache =
            new PropertyInvalidatedCache<ApplicationInfoQuery, ApplicationInfo>(
                    2048, PermissionManager.CACHE_KEY_PACKAGE_INFO_CACHE,
                    "getApplicationInfo") {
    private static String packageInfoApi() {
        return PropertyInvalidatedCache.apiFromProperty(
            PermissionManager.CACHE_KEY_PACKAGE_INFO_CACHE);
    }

    // The maximum number of entries to keep in the packageInfo and applicationInfo caches.
    private final static int MAX_INFO_CACHE_ENTRIES = 2048;

    /** @hide */
    @VisibleForTesting
    public static final PropertyInvalidatedCache<ApplicationInfoQuery, ApplicationInfo>
            sApplicationInfoCache = new PropertyInvalidatedCache<>(
                new PropertyInvalidatedCache.Args(MODULE_SYSTEM)
                .maxEntries(MAX_INFO_CACHE_ENTRIES).api(packageInfoApi()).cacheNulls(true),
                "getApplicationInfo", null) {

                @Override
                public ApplicationInfo recompute(ApplicationInfoQuery query) {
                    return getApplicationInfoAsUserUncached(
@@ -11749,10 +11761,11 @@ public abstract class PackageManager {
    }

    private static final PropertyInvalidatedCache<PackageInfoQuery, PackageInfo>
            sPackageInfoCache =
            new PropertyInvalidatedCache<PackageInfoQuery, PackageInfo>(
                    2048, PermissionManager.CACHE_KEY_PACKAGE_INFO_CACHE,
                    "getPackageInfo") {
            sPackageInfoCache = new PropertyInvalidatedCache<>(
                new PropertyInvalidatedCache.Args(MODULE_SYSTEM)
                .maxEntries(MAX_INFO_CACHE_ENTRIES).api(packageInfoApi()).cacheNulls(true),
                "getPackageInfo", null) {

                @Override
                public PackageInfo recompute(PackageInfoQuery query) {
                    return getPackageInfoAsUserUncached(
+5 −0
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ import static com.android.server.pm.AppsFilterUtils.canQueryViaUsesLibrary;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ApplicationPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.SigningDetails;
@@ -173,6 +174,10 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable,
     * Report a change to observers.
     */
    private void onChanged() {
        // App visibility may have changed, which means that earlier fetches from these caches may
        // be invalid.
        PackageManager.invalidatePackageInfoCache();
        ApplicationPackageManager.invalidateGetPackagesForUidCache();
        dispatchChange(this);
    }

+55 −1
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ import android.content.pm.PackageManagerInternal;
import android.content.pm.Signature;
import android.content.pm.SigningDetails;
import android.content.pm.UserInfo;
import android.app.PropertyInvalidatedCache;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
@@ -50,6 +51,8 @@ import android.util.SparseArray;

import androidx.annotation.NonNull;

import android.app.ApplicationPackageManager;
import android.content.pm.PackageManager;
import com.android.internal.pm.parsing.pkg.PackageImpl;
import com.android.internal.pm.parsing.pkg.ParsedPackage;
import com.android.internal.pm.pkg.component.ParsedActivity;
@@ -64,8 +67,10 @@ import com.android.internal.pm.pkg.component.ParsedUsesPermissionImpl;
import com.android.internal.pm.pkg.parsing.ParsingPackage;
import com.android.server.om.OverlayReferenceMapper;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.utils.Watchable;
import com.android.server.utils.WatchableTester;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -244,6 +249,55 @@ public class AppsFilterImplTest {
                (Answer<Boolean>) invocation ->
                        ((AndroidPackage) invocation.getArgument(SYSTEM_USER)).getTargetSdkVersion()
                                >= Build.VERSION_CODES.R);
        PropertyInvalidatedCache.setTestMode(true);
        PackageManager.sApplicationInfoCache.testPropertyName();
        ApplicationPackageManager.sGetPackagesForUidCache.testPropertyName();
    }

    @After
    public void tearDown() {
        PropertyInvalidatedCache.setTestMode(false);
    }

    /**
     * A class to make it easier to verify that PM caches are properly invalidated by
     * AppsFilterImpl operations.  This extends WatchableTester to test the cache nonces along
     * with change reporting.
     */
    private static class NonceTester extends WatchableTester {
        // The nonces from caches under consideration.  The no-parameter constructor fetches the
        // values from the cacches.
        private static record Nonces(long applicationInfo, long packageInfo) {
            Nonces() {
                this(ApplicationPackageManager.sGetPackagesForUidCache.getNonce(),
                        PackageManager.sApplicationInfoCache.getNonce());
            }
        }

        // Track the latest cache nonces.
        private Nonces mNonces;

        NonceTester(Watchable w, String k) {
            super(w, k);
            mNonces = new Nonces();
        }

        @Override
        public void verifyChangeReported(String msg) {
            super.verifyChangeReported(msg);
            Nonces update = new Nonces();
            assertTrue(msg, update.applicationInfo != mNonces.applicationInfo);
            assertTrue(msg, update.packageInfo != mNonces.packageInfo);
            mNonces = update;
        }

        @Override
        public void verifyNoChangeReported(String msg) {
            super.verifyNoChangeReported(msg);
            Nonces update = new Nonces();
            assertTrue(msg, update.applicationInfo == mNonces.applicationInfo);
            assertTrue(msg, update.packageInfo == mNonces.packageInfo);
        }
    }

    @Test
@@ -1167,7 +1221,7 @@ public class AppsFilterImplTest {
        final AppsFilterImpl appsFilter =
                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */
                        false, /* overlayProvider */ null, mMockHandler);
        final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
        final WatchableTester watcher = new NonceTester(appsFilter, "onChange");
        watcher.register();
        simulateAddBasicAndroid(appsFilter);
        watcher.verifyChangeReported("addBasicAndroid");