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

Commit b2fe2bc1 authored by lpeter's avatar lpeter
Browse files

Use the right token to clear service info caches in the different instances.

Since one process can have multiple instances of RegisteredServicesCache,
and they all share the same background handler that is used to clear the
cache, there must be another identifier in the token of the handler job
to distinguish different instances of RegisteredServicesCache.

Flag: android.content.pm.optimize_parsing_in_registered_services_cache

Bug: 319137634
Test: atest RegisteredServicesCacheTest
Test: atest RegisteredServicesCacheUnitTest
Change-Id: I5297bb7e5455723474beb4c75fad33b282ece597
parent d54d5c46
Loading
Loading
Loading
Loading
+37 −1
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package android.content.pm;

import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -558,7 +559,8 @@ public abstract class RegisteredServicesCache<V> {
        if (Flags.optimizeParsingInRegisteredServicesCache()) {
            synchronized (mUserIdToServiceInfoCaches) {
                if (mUserIdToServiceInfoCaches.numElementsForKey(userId) > 0) {
                    final Integer token = Integer.valueOf(userId);
                    final ServiceInfoCachesToken<V> token = new ServiceInfoCachesToken<V>(
                            this /* RegisteredServicesCache<V> */, userId);
                    mBackgroundHandler.removeCallbacksAndEqualMessages(token);
                    mBackgroundHandler.postDelayed(
                            new ClearServiceInfoCachesTimeoutRunnable(userId), token,
@@ -962,4 +964,38 @@ public abstract class RegisteredServicesCache<V> {
            }
        }
    }

    /**
     * Use the token to make sure the service info caches to be cleared in the different instances.
     * @param <V> The type of the value.
     *
     * @hide
     */
    public static final class ServiceInfoCachesToken<V> {
        public final RegisteredServicesCache<V> mRegisteredServicesCache;
        public final int mUserId;

        public ServiceInfoCachesToken(RegisteredServicesCache<V> registeredServicesCache,
                int userId) {
            this.mRegisteredServicesCache = registeredServicesCache;
            this.mUserId = userId;
        }

        @Override
        public boolean equals(@Nullable Object o) {
            if (o instanceof ServiceInfoCachesToken) {
                final ServiceInfoCachesToken<V> other = (ServiceInfoCachesToken<V>) o;
                return mRegisteredServicesCache == other.mRegisteredServicesCache
                        && mUserId == other.mUserId;
            }
            return false;
        }

        @Override
        public int hashCode() {
            int result = mRegisteredServicesCache != null ? mRegisteredServicesCache.hashCode() : 0;
            result = 31 * result + mUserId;
            return result;
        }
    }
}
+88 −2
Original line number Diff line number Diff line
@@ -48,6 +48,7 @@ import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;

import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -344,9 +345,13 @@ public class RegisteredServicesCacheUnitTest {
        // of U1 user.
        doAnswer(invocation -> {
            Message message = invocation.getArgument(0);
            if (!message.obj.equals(Integer.valueOf(U0))) {
            if (message.obj instanceof RegisteredServicesCache.ServiceInfoCachesToken) {
                final RegisteredServicesCache.ServiceInfoCachesToken token =
                        (RegisteredServicesCache.ServiceInfoCachesToken) message.obj;
                if (token.mUserId == U1) {
                    message.getCallback().run();
                }
            }
            return true;
        }).when(mMockBackgroundHandler).sendMessageAtTime(any(Message.class), anyLong());

@@ -369,6 +374,87 @@ public class RegisteredServicesCacheUnitTest {
        verify(testServicesCache, times(1)).parseServiceInfo(eq(mResolveInfo2), eq(2000L));
    }

    @Test
    public void testClearServiceInfoCachesForTwoDifferentInstancesAfterTimeout() throws Exception {
        PackageInfo packageInfo1 = createPackageInfo(1000L /* lastUpdateTime */);
        when(mMockPackageManager.getPackageInfoAsUser(eq(mResolveInfo1.serviceInfo.packageName),
                anyInt(), eq(U0))).thenReturn(packageInfo1);

        TestRegisteredServicesCache testServicesCache1 = spy(
                new TestRegisteredServicesCache(mMockInjector, null /* serializerAndParser */));
        final RegisteredServicesCache.ServiceInfo<TestServiceType> serviceInfo1 = newServiceInfo(
                mTestServiceType1, UID1, mResolveInfo1.serviceInfo.getComponentName(),
                1000L /* lastUpdateTime */);
        testServicesCache1.addServiceForQuerying(U0, mResolveInfo1, serviceInfo1);

        TestRegisteredServicesCache testServicesCache2 = spy(
                new TestRegisteredServicesCache(mMockInjector, null /* serializerAndParser */));
        testServicesCache2.addServiceForQuerying(U0, mResolveInfo1, serviceInfo1);

        final ArrayList<RegisteredServicesCache<TestServiceType>> registeredServicesCacheArrayList =
                new ArrayList<>();

        doAnswer(invocation -> {
            Message message = invocation.getArgument(0);
            if (message.obj instanceof RegisteredServicesCache.ServiceInfoCachesToken) {
                final RegisteredServicesCache.ServiceInfoCachesToken token =
                        (RegisteredServicesCache.ServiceInfoCachesToken) message.obj;
                Log.d(TAG, "RegisteredServicesCache token: " + token.mRegisteredServicesCache);
                registeredServicesCacheArrayList.add(token.mRegisteredServicesCache);
            }
            return true;
        }).when(mMockBackgroundHandler).sendMessageAtTime(any(Message.class), anyLong());

        testServicesCache1.getAllServices(U0);
        verify(mMockBackgroundHandler, times(1)).sendMessageAtTime(any(Message.class), anyLong());

        testServicesCache2.getAllServices(U0);
        verify(mMockBackgroundHandler, times(2)).sendMessageAtTime(any(Message.class), anyLong());

        assertThat(registeredServicesCacheArrayList.size()).isEqualTo(2);
        assertThat(registeredServicesCacheArrayList.get(0)).isNotEqualTo(
                registeredServicesCacheArrayList.get(1));
    }

    @Test
    public void testClearServiceInfoCachesForTwoSameInstancesAfterTimeout() throws Exception {
        PackageInfo packageInfo1 = createPackageInfo(1000L /* lastUpdateTime */);
        when(mMockPackageManager.getPackageInfoAsUser(eq(mResolveInfo1.serviceInfo.packageName),
                anyInt(), eq(U0))).thenReturn(packageInfo1);

        TestRegisteredServicesCache testServicesCache1 = spy(
                new TestRegisteredServicesCache(mMockInjector, null /* serializerAndParser */));
        final RegisteredServicesCache.ServiceInfo<TestServiceType> serviceInfo1 = newServiceInfo(
                mTestServiceType1, UID1, mResolveInfo1.serviceInfo.getComponentName(),
                1000L /* lastUpdateTime */);
        testServicesCache1.addServiceForQuerying(U0, mResolveInfo1, serviceInfo1);

        final ArrayList<RegisteredServicesCache<TestServiceType>> registeredServicesCacheArrayList =
                new ArrayList<>();

        doAnswer(invocation -> {
            Message message = invocation.getArgument(0);
            if (message.obj instanceof RegisteredServicesCache.ServiceInfoCachesToken) {
                final RegisteredServicesCache.ServiceInfoCachesToken token =
                        (RegisteredServicesCache.ServiceInfoCachesToken) message.obj;
                Log.d(TAG, "RegisteredServicesCache token: " + token.mRegisteredServicesCache);
                registeredServicesCacheArrayList.add(token.mRegisteredServicesCache);
            }
            return true;
        }).when(mMockBackgroundHandler).sendMessageAtTime(any(Message.class), anyLong());

        testServicesCache1.getAllServices(U0);
        verify(mMockBackgroundHandler, times(1)).sendMessageAtTime(any(Message.class), anyLong());

        testServicesCache1.invalidateCache(U0);
        testServicesCache1.getAllServices(U0);
        verify(mMockBackgroundHandler, times(2)).sendMessageAtTime(any(Message.class), anyLong());

        assertThat(registeredServicesCacheArrayList.size()).isEqualTo(2);
        assertThat(registeredServicesCacheArrayList.get(0)).isEqualTo(
                registeredServicesCacheArrayList.get(1));
    }

    private static RegisteredServicesCache.ServiceInfo<TestServiceType> newServiceInfo(
            TestServiceType type, int uid, ComponentName componentName, long lastUpdateTime) {
        final ComponentInfo info = new ComponentInfo();