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

Commit 224f8c01 authored by Andrei Onea's avatar Andrei Onea
Browse files

Allow overriding compat config prior to install time.

If the user tries to apply an override for a package that is not yet
installed, the override is put into a 'deferred' bucket, to be validated
upon installation.
Overrides are rechecked every time an app is installed, updated or
deleted.
If a deferred override is valid at install or update time, it gets
promoted to a regular override.
If a deferred override cannot be applied upon app installation or
update, it remains deferred.
If an app is deleted, all its overrides become deferred.
When any of the reset commands are called for a given package, that
removes both regular and deferred overrides.

Bug: 158696818
Test: atest CompatConfigTest
Test: atest OverrideValidatorImplTest
Change-Id: I50eab62214d1325ec6185ddcfe9e8f425e11c98b
parent 771aa062
Loading
Loading
Loading
Loading
+7 −11
Original line number Diff line number Diff line
@@ -33,7 +33,7 @@ public final class OverrideAllowedState implements Parcelable {
            DISABLED_NOT_DEBUGGABLE,
            DISABLED_NON_TARGET_SDK,
            DISABLED_TARGET_SDK_TOO_HIGH,
            PACKAGE_DOES_NOT_EXIST,
            DEFERRED_VERIFICATION,
            LOGGING_ONLY_CHANGE
    })
    @Retention(RetentionPolicy.SOURCE)
@@ -58,9 +58,9 @@ public final class OverrideAllowedState implements Parcelable {
     */
    public static final int DISABLED_TARGET_SDK_TOO_HIGH = 3;
     /**
     * Package does not exist.
     * Change override decision is currently being deferred, due to the app not being installed yet.
     */
    public static final int PACKAGE_DOES_NOT_EXIST = 4;
    public static final int DEFERRED_VERIFICATION = 4;
    /**
     * Change is marked as logging only, and cannot be toggled.
     */
@@ -106,6 +106,7 @@ public final class OverrideAllowedState implements Parcelable {
            throws SecurityException {
        switch (state) {
            case ALLOWED:
            case DEFERRED_VERIFICATION:
                return;
            case DISABLED_NOT_DEBUGGABLE:
                throw new SecurityException(
@@ -118,11 +119,6 @@ public final class OverrideAllowedState implements Parcelable {
                        "Cannot override %1$d for %2$s because the app's targetSdk (%3$d) is "
                                + "above the change's targetSdk threshold (%4$d)",
                        changeId, packageName, appTargetSdk, changeIdTargetSdk));
            case PACKAGE_DOES_NOT_EXIST:
                throw new SecurityException(String.format(
                        "Cannot override %1$d for %2$s because the package does not exist, and "
                                + "the change is targetSdk gated.",
                        changeId, packageName));
            case LOGGING_ONLY_CHANGE:
                throw new SecurityException(String.format(
                        "Cannot override %1$d because it is marked as a logging-only change.",
@@ -170,8 +166,8 @@ public final class OverrideAllowedState implements Parcelable {
                return "DISABLED_NON_TARGET_SDK";
            case DISABLED_TARGET_SDK_TOO_HIGH:
                return "DISABLED_TARGET_SDK_TOO_HIGH";
            case PACKAGE_DOES_NOT_EXIST:
                return "PACKAGE_DOES_NOT_EXIST";
            case DEFERRED_VERIFICATION:
                return "DEFERRED_VERIFICATION";
            case LOGGING_ONLY_CHANGE:
                return "LOGGING_ONLY_CHANGE";
        }
+66 −0
Original line number Diff line number Diff line
@@ -61,6 +61,7 @@ public final class CompatChange extends CompatibilityChangeInfo {
    ChangeListener mListener = null;

    private Map<String, Boolean> mPackageOverrides;
    private Map<String, Boolean> mDeferredOverrides;

    public CompatChange(long changeId) {
        this(changeId, null, -1, -1, false, false, null);
@@ -120,6 +121,56 @@ public final class CompatChange extends CompatibilityChangeInfo {
        notifyListener(pname);
    }

    /**
     * Tentatively set the state of this change for a given package name.
     * The override will only take effect after that package is installed, if applicable.
     *
     * <p>Note, this method is not thread safe so callers must ensure thread safety.
     *
     * @param packageName Package name to tentatively enable the change for.
     * @param enabled Whether or not to enable the change.
     */
    void addPackageDeferredOverride(String packageName, boolean enabled) {
        if (getLoggingOnly()) {
            throw new IllegalArgumentException(
                    "Can't add overrides for a logging only change " + toString());
        }
        if (mDeferredOverrides == null) {
            mDeferredOverrides = new HashMap<>();
        }
        mDeferredOverrides.put(packageName, enabled);
    }

    /**
     * Rechecks an existing (and possibly deferred) override.
     *
     * <p>For deferred overrides, check if they can be promoted to a regular override. For regular
     * overrides, check if they need to be demoted to deferred.</p>
     *
     * @param packageName Package name to apply deferred overrides for.
     * @param allowed Whether the override is allowed.
     *
     * @return {@code true} if the recheck yielded a result that requires invalidating caches
     *         (a deferred override was consolidated or a regular override was removed).
     */
    boolean recheckOverride(String packageName, boolean allowed) {
        // A deferred override now is allowed by the policy, so promote it to a regular override.
        if (hasDeferredOverride(packageName) && allowed) {
            boolean overrideValue = mDeferredOverrides.remove(packageName);
            addPackageOverride(packageName, overrideValue);
            return true;
        }
        // A previously set override is no longer allowed by the policy, so make it deferred.
        if (hasOverride(packageName) && !allowed) {
            boolean overrideValue = mPackageOverrides.remove(packageName);
            addPackageDeferredOverride(packageName, overrideValue);
            // Notify because the override was removed.
            notifyListener(packageName);
            return true;
        }
        return false;
    }

    /**
     * Remove any package override for the given package name, restoring the default behaviour.
     *
@@ -133,6 +184,9 @@ public final class CompatChange extends CompatibilityChangeInfo {
                notifyListener(pname);
            }
        }
        if (mDeferredOverrides != null) {
            mDeferredOverrides.remove(pname);
        }
    }

    /**
@@ -176,6 +230,15 @@ public final class CompatChange extends CompatibilityChangeInfo {
        return mPackageOverrides != null && mPackageOverrides.containsKey(packageName);
    }

    /**
     * Checks whether a change has a deferred override for a package.
     * @param packageName name of the package
     * @return true if there is such a deferred override
     */
    boolean hasDeferredOverride(String packageName) {
        return mDeferredOverrides != null && mDeferredOverrides.containsKey(packageName);
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder("ChangeId(")
@@ -195,6 +258,9 @@ public final class CompatChange extends CompatibilityChangeInfo {
        if (mPackageOverrides != null && mPackageOverrides.size() > 0) {
            sb.append("; packageOverrides=").append(mPackageOverrides);
        }
        if (mDeferredOverrides != null && mDeferredOverrides.size() > 0) {
            sb.append("; deferredOverrides=").append(mDeferredOverrides);
        }
        return sb.append(")").toString();
    }

+51 −32
Original line number Diff line number Diff line
@@ -173,7 +173,17 @@ final class CompatConfig {
                c = new CompatChange(changeId);
                addChange(c);
            }
            switch (allowedState.state) {
                case OverrideAllowedState.ALLOWED:
                    c.addPackageOverride(packageName, enabled);
                    break;
                case OverrideAllowedState.DEFERRED_VERIFICATION:
                    c.addPackageDeferredOverride(packageName, enabled);
                    break;
                default:
                    throw new IllegalStateException("Should only be able to override changes that "
                                                    + "are allowed or can be deferred.");
            }
            invalidateCache();
        }
        return alreadyKnown;
@@ -248,22 +258,22 @@ final class CompatConfig {
        boolean overrideExists = false;
        synchronized (mChanges) {
            CompatChange c = mChanges.get(changeId);
            try {
            if (c != null) {
                    overrideExists = c.hasOverride(packageName);
                    if (overrideExists) {
                // Always allow removing a deferred override.
                if (c.hasDeferredOverride(packageName)) {
                    c.removePackageOverride(packageName);
                    overrideExists = true;
                } else if (c.hasOverride(packageName)) {
                    // Regular overrides need to pass the policy.
                    overrideExists = true;
                    OverrideAllowedState allowedState =
                            mOverrideValidator.getOverrideAllowedState(changeId, packageName);
                    allowedState.enforce(changeId, packageName);
                    c.removePackageOverride(packageName);
                }
            }
            } catch (RemoteException e) {
                // Should never occur, since validator is in the same process.
                throw new RuntimeException("Unable to call override validator!", e);
        }
        invalidateCache();
        }
        return overrideExists;
    }

@@ -293,29 +303,15 @@ final class CompatConfig {
     * Removes all overrides previously added via {@link #addOverride(long, String, boolean)} or
     * {@link #addOverrides(CompatibilityChangeConfig, String)} for a certain package.
     *
     * <p>This restores the default behaviour for the given change and app, once any app
     * processes have been restarted.
     * <p>This restores the default behaviour for the given app.
     *
     * @param packageName The package for which the overrides should be purged.
     */
    void removePackageOverrides(String packageName) throws RemoteException, SecurityException {
        synchronized (mChanges) {
            for (int i = 0; i < mChanges.size(); ++i) {
                try {
                CompatChange change = mChanges.valueAt(i);
                    if (change.hasOverride(packageName)) {
                        OverrideAllowedState allowedState =
                                mOverrideValidator.getOverrideAllowedState(change.getId(),
                                        packageName);
                        allowedState.enforce(change.getId(), packageName);
                        if (change != null) {
                            mChanges.valueAt(i).removePackageOverride(packageName);
                        }
                    }
                } catch (RemoteException e) {
                    // Should never occur, since validator is in the same process.
                    throw new RuntimeException("Unable to call override validator!", e);
                }
                removeOverride(change.getId(), packageName);
            }
            invalidateCache();
        }
@@ -340,7 +336,7 @@ final class CompatConfig {
                    }
                } catch (RemoteException e) {
                    // Should never occur, since validator is in the same process.
                    throw new RuntimeException("Unable to call override validator!", e);
                    throw new AssertionError("Unable to call override validator!", e);
                }
            }
        }
@@ -511,4 +507,27 @@ final class CompatConfig {
    private void invalidateCache() {
        ChangeIdStateCache.invalidate();
    }
    /**
     * Rechecks all the existing overrides for a package.
     */
    void recheckOverrides(String packageName) {
        synchronized (mChanges) {
            boolean shouldInvalidateCache = false;
            for (int idx = 0; idx < mChanges.size(); ++idx) {
                CompatChange c = mChanges.valueAt(idx);
                try {
                    OverrideAllowedState allowedState =
                            mOverrideValidator.getOverrideAllowedState(c.getId(), packageName);
                    boolean allowedOverride = (allowedState.state == OverrideAllowedState.ALLOWED);
                    shouldInvalidateCache |= c.recheckOverride(packageName, allowedOverride);
                } catch (RemoteException e) {
                    // Should never occur, since validator is in the same process.
                    throw new AssertionError("Unable to call override validator!", e);
                }
            }
            if (shouldInvalidateCache) {
                invalidateCache();
            }
        }
    }
}
+2 −2
Original line number Diff line number Diff line
@@ -17,11 +17,11 @@
package com.android.server.compat;

import static com.android.internal.compat.OverrideAllowedState.ALLOWED;
import static com.android.internal.compat.OverrideAllowedState.DEFERRED_VERIFICATION;
import static com.android.internal.compat.OverrideAllowedState.DISABLED_NON_TARGET_SDK;
import static com.android.internal.compat.OverrideAllowedState.DISABLED_NOT_DEBUGGABLE;
import static com.android.internal.compat.OverrideAllowedState.DISABLED_TARGET_SDK_TOO_HIGH;
import static com.android.internal.compat.OverrideAllowedState.LOGGING_ONLY_CHANGE;
import static com.android.internal.compat.OverrideAllowedState.PACKAGE_DOES_NOT_EXIST;

import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -73,7 +73,7 @@ public class OverrideValidatorImpl extends IOverrideValidator.Stub {
        try {
            applicationInfo = packageManager.getApplicationInfo(packageName, 0);
        } catch (NameNotFoundException e) {
            return new OverrideAllowedState(PACKAGE_DOES_NOT_EXIST, -1, -1);
            return new OverrideAllowedState(DEFERRED_VERIFICATION, -1, -1);
        }
        int appTargetSdk = applicationInfo.targetSdkVersion;
        // Only allow overriding debuggable apps.
+35 −0
Original line number Diff line number Diff line
@@ -25,9 +25,13 @@ import static android.os.Process.SYSTEM_UID;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.IActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManagerInternal;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.RemoteException;
@@ -74,6 +78,7 @@ public class PlatformCompat extends IPlatformCompat.Stub {
        mChangeReporter = new ChangeReporter(
                ChangeReporter.SOURCE_SYSTEM_SERVER);
        mCompatConfig = compatConfig;
        registerPackageReceiver(context);
    }

    @Override
@@ -389,4 +394,34 @@ public class PlatformCompat extends IPlatformCompat.Stub {
        }
        return true;
    }

    /**
     * Registers a broadcast receiver that listens for package install, replace or remove.
     * @param context the context where the receiver should be registered.
     */
    public void registerPackageReceiver(Context context) {
        final BroadcastReceiver receiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                if (intent == null) {
                    return;
                }
                final Uri packageData = intent.getData();
                if (packageData == null) {
                    return;
                }
                final String packageName = packageData.getSchemeSpecificPart();
                if (packageName == null) {
                    return;
                }
                mCompatConfig.recheckOverrides(packageName);
            }
        };
        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_PACKAGE_ADDED);
        filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
        filter.addDataScheme("package");
        context.registerReceiver(receiver, filter);
    }
}
Loading