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

Commit 7ed81d17 authored by Zhi An Ng's avatar Zhi An Ng Committed by Android (Google) Code Review
Browse files

Merge "Do not load xml metadata for unchanged packages in RegisteredServicesCache" into pi-dev

parents e4a6fc37 80948800
Loading
Loading
Loading
Loading
+39 −20
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package android.content.pm;

import android.Manifest;
import android.annotation.Nullable;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -174,7 +175,8 @@ public abstract class RegisteredServicesCache<V> {
        mContext.registerReceiver(mUserRemovedReceiver, userFilter);
    }

    private final void handlePackageEvent(Intent intent, int userId) {
    @VisibleForTesting
    protected void handlePackageEvent(Intent intent, int userId) {
        // Don't regenerate the services map when the package is removed or its
        // ASEC container unmounted as a step in replacement.  The subsequent
        // _ADDED / _AVAILABLE call will regenerate the map in the final state.
@@ -236,6 +238,9 @@ public abstract class RegisteredServicesCache<V> {

    public void invalidateCache(int userId) {
        synchronized (mServicesLock) {
            if (DEBUG) {
                Slog.d(TAG, "invalidating cache for " + userId + " " + mInterfaceName);
            }
            final UserServices<V> user = findOrCreateUserLocked(userId);
            user.services = null;
            onServicesChangedLocked(userId);
@@ -460,16 +465,37 @@ public abstract class RegisteredServicesCache<V> {
     *                    or null to assume that everything is affected.
     * @param userId the user for whom to update the services map.
     */
    private void generateServicesMap(int[] changedUids, int userId) {
    private void generateServicesMap(@Nullable int[] changedUids, int userId) {
        if (DEBUG) {
            Slog.d(TAG, "generateServicesMap() for " + userId + ", changed UIDs = "
                    + Arrays.toString(changedUids));
        }

        synchronized (mServicesLock) {
            final UserServices<V> user = findOrCreateUserLocked(userId);
            final boolean cacheInvalid = user.services == null;
            if (cacheInvalid) {
                user.services = Maps.newHashMap();
            }

            final ArrayList<ServiceInfo<V>> serviceInfos = new ArrayList<>();
            final List<ResolveInfo> resolveInfos = queryIntentServices(userId);

            for (ResolveInfo resolveInfo : resolveInfos) {
                try {
                    // when changedUids == null, we want to do a rescan of everything, this means
                    // it's the initial scan, and containsUid will trivially return true
                    // when changedUids != null, we got here because a package changed, but
                    // invalidateCache could have been called (thus user.services == null), and we
                    // should query from PackageManager again
                    if (!cacheInvalid
                            && !containsUid(
                                    changedUids, resolveInfo.serviceInfo.applicationInfo.uid)) {
                        if (DEBUG) {
                            Slog.d(TAG, "Skipping parseServiceInfo for " + resolveInfo);
                        }
                        continue;
                    }
                    ServiceInfo<V> info = parseServiceInfo(resolveInfo);
                    if (info == null) {
                        Log.w(TAG, "Unable to load service info " + resolveInfo.toString());
@@ -481,13 +507,6 @@ public abstract class RegisteredServicesCache<V> {
                }
            }

        synchronized (mServicesLock) {
            final UserServices<V> user = findOrCreateUserLocked(userId);
            final boolean firstScan = user.services == null;
            if (firstScan) {
                user.services = Maps.newHashMap();
            }

            StringBuilder changes = new StringBuilder();
            boolean changed = false;
            for (ServiceInfo<V> info : serviceInfos) {
@@ -508,7 +527,7 @@ public abstract class RegisteredServicesCache<V> {
                    changed = true;
                    user.services.put(info.type, info);
                    user.persistentServices.put(info.type, info.uid);
                    if (!(user.mPersistentServicesFileDidNotExist && firstScan)) {
                    if (!(user.mPersistentServicesFileDidNotExist && cacheInvalid)) {
                        notifyListener(info.type, userId, false /* removed */);
                    }
                } else if (previousUid == info.uid) {
+41 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package android.content.pm;

import android.content.Intent;
import android.content.res.Resources;
import android.os.FileUtils;
import android.os.Parcel;
@@ -188,6 +189,36 @@ public class RegisteredServicesCacheTest extends AndroidTestCase {
        assertEquals(0, cache.getPersistentServicesSize(u1));
    }

    /**
     * Check that an optimization to skip a call to PackageManager handles an invalidated cache.
     *
     * We added an optimization in generateServicesMap to only query PackageManager for packages
     * that have been changed, because if a package is unchanged, we have already cached the
     * services info for it, so we can save a query to PackageManager (and save some memory).
     * However, if invalidateCache was called, we cannot optimize, and must do a full query.
     * The initial optimization was buggy because it failed to check for an invalidated cache, and
     * only scanned the changed packages, given in the ACTION_PACKAGE_CHANGED intent (b/122912184).
     */
    public void testParseServiceInfoOptimizationHandlesInvalidatedCache() {
        TestServicesCache cache = new TestServicesCache();
        cache.addServiceForQuerying(U0, r1, newServiceInfo(t1, UID1));
        cache.addServiceForQuerying(U0, r2, newServiceInfo(t2, UID2));
        assertEquals(2, cache.getAllServicesSize(U0));

        // simulate the client of the cache invalidating it
        cache.invalidateCache(U0);

        // there should be 0 services (userServices.services == null ) at this point, but we don't
        // call getAllServicesSize since that would force a full scan of packages,
        // instead we trigger a package change in a package that is in the list of services
        Intent intent = new Intent(Intent.ACTION_PACKAGE_CHANGED);
        intent.putExtra(Intent.EXTRA_UID, UID1);
        cache.handlePackageEvent(intent, U0);

        // check that the optimization does a full query and caches both services
        assertEquals(2, cache.getAllServicesSize(U0));
    }

    private static RegisteredServicesCache.ServiceInfo<TestServiceType> newServiceInfo(
            TestServiceType type, int uid) {
        final ComponentInfo info = new ComponentInfo();
@@ -265,6 +296,11 @@ public class RegisteredServicesCacheTest extends AndroidTestCase {
                map = new HashMap<>();
                mServices.put(userId, map);
            }
            // in actual cases, resolveInfo should always have a serviceInfo, since we specifically
            // query for intent services
            resolveInfo.serviceInfo = new android.content.pm.ServiceInfo();
            resolveInfo.serviceInfo.applicationInfo =
                new ApplicationInfo(serviceInfo.componentInfo.applicationInfo);
            map.put(resolveInfo, serviceInfo);
        }

@@ -303,6 +339,11 @@ public class RegisteredServicesCacheTest extends AndroidTestCase {
        public void onUserRemoved(int userId) {
            super.onUserRemoved(userId);
        }

        @Override
        public void handlePackageEvent(Intent intent, int userId) {
            super.handlePackageEvent(intent, userId);
        }
    }

    static class TestSerializer implements XmlSerializerAndParser<TestServiceType> {