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

Commit c1877300 authored by atrost's avatar atrost
Browse files

Cache binder calls in CompatChanges

Cached call values are kept in ChangeIdStateCache (per-process cache).
Use "cache_key.is_compat_change_enabled" system property to
invalidate the cache (i.e. any value different from the last observed
will trigger an invalidation).
Any process can read that property, but only system server can change it.

Rolling forward the original caching with invalidating the cache upon
package install/update.
Tested fix locally by using the API in SoftRestrictedPermissionPolicy.java,
and running android.permission2.cts.RestrictedStoragePermissionSharedUidTest
which installs some apps with the same uid, which modifies the result of
isChangeEnabled. With the additional cache invalidation the test passes.

ToDo: add CTS test for the caching.

Bug: 140441727
Test: atest PlatformCompatTest
Test: atest CompatConfigTest
Test: atest CompatChangesTest
Test: atest PlatformCompatGating
Change-Id: I0265df1c427b43592e4f4d32fe9cfeb8fbc1bfa9
parent 1fd15d20
Loading
Loading
Loading
Loading
+86 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.app.compat;

import android.app.PropertyInvalidatedCache;
import android.content.Context;
import android.os.Binder;
import android.os.RemoteException;
import android.os.ServiceManager;

import com.android.internal.compat.IPlatformCompat;

/**
 * Handles caching of calls to {@link com.android.internal.compat.IPlatformCompat}
 * @hide
 */
public final class ChangeIdStateCache
        extends PropertyInvalidatedCache<ChangeIdStateQuery, Boolean> {
    private static final String CACHE_KEY = "cache_key.is_compat_change_enabled";
    private static final int MAX_ENTRIES = 20;
    private static boolean sDisabled = false;

    /** @hide */
    public ChangeIdStateCache() {
        super(MAX_ENTRIES, CACHE_KEY);
    }

    /**
     * Disable cache.
     *
     * <p>Should only be used in unit tests.
     * @hide
     */
    public static void disable() {
        sDisabled = true;
    }

    /**
     * Invalidate the cache.
     *
     * <p>Can only be called by the system server process.
     * @hide
     */
    public static void invalidate() {
        if (!sDisabled) {
            PropertyInvalidatedCache.invalidateCache(CACHE_KEY);
        }
    }

    @Override
    protected Boolean recompute(ChangeIdStateQuery query) {
        IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
                ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
        final long token = Binder.clearCallingIdentity();
        try {
            if (query.type == ChangeIdStateQuery.QUERY_BY_PACKAGE_NAME) {
                return platformCompat.isChangeEnabledByPackageName(query.changeId,
                                                                   query.packageName,
                                                                   query.userId);
            } else if (query.type == ChangeIdStateQuery.QUERY_BY_UID) {
                return platformCompat.isChangeEnabledByUid(query.changeId, query.uid);
            } else {
                throw new IllegalArgumentException("Invalid query type: " + query.type);
            }
        } catch (RemoteException e) {
            e.rethrowFromSystemServer();
        } finally {
            Binder.restoreCallingIdentity(token);
        }
        throw new IllegalStateException("Could not recompute value!");
    }
}
+90 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.app.compat;

import android.annotation.IntDef;
import android.annotation.NonNull;

import com.android.internal.annotations.Immutable;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;


/**
 * A key type for caching calls to {@link com.android.internal.compat.IPlatformCompat}
 *
 * <p>For {@link com.android.internal.compat.IPlatformCompat#isChangeEnabledByPackageName}
 * and {@link com.android.internal.compat.IPlatformCompat#isChangeEnabledByUid}
 *
 * @hide
 */
@Immutable
final class ChangeIdStateQuery {

    static final int QUERY_BY_PACKAGE_NAME = 0;
    static final int QUERY_BY_UID = 1;
    @IntDef({QUERY_BY_PACKAGE_NAME, QUERY_BY_UID})
    @Retention(RetentionPolicy.SOURCE)
    @interface QueryType {}

    public @QueryType int type;
    public long changeId;
    public String packageName;
    public int uid;
    public int userId;

    private ChangeIdStateQuery(@QueryType int type, long changeId, String packageName,
                               int uid, int userId) {
        this.type = type;
        this.changeId = changeId;
        this.packageName = packageName;
        this.uid = uid;
        this.userId = userId;
    }

    static ChangeIdStateQuery byPackageName(long changeId, @NonNull String packageName,
                                            int userId) {
        return new ChangeIdStateQuery(QUERY_BY_PACKAGE_NAME, changeId, packageName, 0, userId);
    }

    static ChangeIdStateQuery byUid(long changeId, int uid) {
        return new ChangeIdStateQuery(QUERY_BY_UID, changeId, null, uid, 0);
    }

    @Override
    public boolean equals(Object other) {
        if (this == other) {
            return true;
        }
        if ((other == null) || !(other instanceof ChangeIdStateQuery)) {
            return false;
        }
        final ChangeIdStateQuery that = (ChangeIdStateQuery) other;
        return this.type == that.type
            && this.changeId == that.changeId
            && Objects.equals(this.packageName, that.packageName)
            && this.uid == that.uid
            && this.userId == that.userId;
    }

    @Override
    public int hashCode() {
        return Objects.hash(type, changeId, packageName, uid, userId);
    }
}
+5 −27
Original line number Diff line number Diff line
@@ -19,14 +19,8 @@ package android.app.compat;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.compat.Compatibility;
import android.content.Context;
import android.os.Binder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;

import com.android.internal.compat.IPlatformCompat;

/**
 * CompatChanges APIs - to be used by platform code only (including mainline
 * modules).
@@ -35,6 +29,7 @@ import com.android.internal.compat.IPlatformCompat;
 */
@SystemApi
public final class CompatChanges {
    private static final ChangeIdStateCache QUERY_CACHE = new ChangeIdStateCache();
    private CompatChanges() {}

    /**
@@ -69,17 +64,8 @@ public final class CompatChanges {
     */
    public static boolean isChangeEnabled(long changeId, @NonNull String packageName,
            @NonNull UserHandle user) {
        IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
                ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
        final long token = Binder.clearCallingIdentity();
        try {
            return platformCompat.isChangeEnabledByPackageName(changeId, packageName,
                    user.getIdentifier());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        } finally {
            Binder.restoreCallingIdentity(token);
        }
        return QUERY_CACHE.query(ChangeIdStateQuery.byPackageName(changeId, packageName,
                                                           user.getIdentifier()));
    }

    /**
@@ -101,15 +87,7 @@ public final class CompatChanges {
     * @return {@code true} if the change is enabled for the current app.
     */
    public static boolean isChangeEnabled(long changeId, int uid) {
        IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
                ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
        final long token = Binder.clearCallingIdentity();
        try {
            return platformCompat.isChangeEnabledByUid(changeId, uid);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        } finally {
            Binder.restoreCallingIdentity(token);
        }
        return QUERY_CACHE.query(ChangeIdStateQuery.byUid(changeId, uid));
    }

}
+11 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.server.compat;

import android.app.compat.ChangeIdStateCache;
import android.compat.Compatibility.ChangeConfig;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -80,6 +81,7 @@ final class CompatConfig {
    void addChange(CompatChange change) {
        synchronized (mChanges) {
            mChanges.put(change.getId(), change);
            invalidateCache();
        }
    }

@@ -172,6 +174,7 @@ final class CompatConfig {
                addChange(c);
            }
            c.addPackageOverride(packageName, enabled);
            invalidateCache();
        }
        return alreadyKnown;
    }
@@ -228,6 +231,7 @@ final class CompatConfig {
                // Should never occur, since validator is in the same process.
                throw new RuntimeException("Unable to call override validator!", e);
            }
            invalidateCache();
        }
        return overrideExists;
    }
@@ -250,6 +254,7 @@ final class CompatConfig {
                addOverride(changeId, packageName, false);

            }
            invalidateCache();
        }
    }

@@ -279,6 +284,7 @@ final class CompatConfig {
                    throw new RuntimeException("Unable to call override validator!", e);
                }
            }
            invalidateCache();
        }
    }

@@ -377,6 +383,7 @@ final class CompatConfig {
            config.initConfigFromLib(Environment.buildPath(
                    apex.apexDirectory, "etc", "compatconfig"));
        }
        config.invalidateCache();
        return config;
    }

@@ -406,4 +413,8 @@ final class CompatConfig {
    IOverrideValidator getOverrideValidator() {
        return mOverrideValidator;
    }

    private void invalidateCache() {
        ChangeIdStateCache.invalidate();
    }
}
+2 −0
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.compat.ChangeIdStateCache;
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
@@ -441,6 +442,7 @@ public final class Settings {

    private static void invalidatePackageCache() {
        PackageManager.invalidatePackageInfoCache();
        ChangeIdStateCache.invalidate();
    }

    PackageSetting getPackageLPr(String pkgName) {
Loading