Merge "Add an API method for clearing compat overrides on release builds"
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 039c6b1..ea626b3 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1124,10 +1124,11 @@
 package android.app.compat {
 
   public final class CompatChanges {
+    method @RequiresPermission(android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD) public static void addPackageOverrides(@NonNull String, @NonNull java.util.Map<java.lang.Long,android.app.compat.PackageOverride>);
     method public static boolean isChangeEnabled(long);
     method @RequiresPermission(allOf={"android.permission.READ_COMPAT_CHANGE_CONFIG", "android.permission.LOG_COMPAT_CHANGE"}) public static boolean isChangeEnabled(long, @NonNull String, @NonNull android.os.UserHandle);
     method @RequiresPermission(allOf={"android.permission.READ_COMPAT_CHANGE_CONFIG", "android.permission.LOG_COMPAT_CHANGE"}) public static boolean isChangeEnabled(long, int);
-    method @RequiresPermission(android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD) public static void setPackageOverride(@NonNull String, @NonNull java.util.Map<java.lang.Long,android.app.compat.PackageOverride>);
+    method @RequiresPermission(android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD) public static void removePackageOverrides(@NonNull String, @NonNull java.util.Set<java.lang.Long>);
   }
 
   public final class PackageOverride {
diff --git a/core/java/android/app/compat/CompatChanges.java b/core/java/android/app/compat/CompatChanges.java
index 74e1ece..8ca43c4 100644
--- a/core/java/android/app/compat/CompatChanges.java
+++ b/core/java/android/app/compat/CompatChanges.java
@@ -26,9 +26,11 @@
 import android.os.UserHandle;
 
 import com.android.internal.compat.CompatibilityOverrideConfig;
+import com.android.internal.compat.CompatibilityOverridesToRemoveConfig;
 import com.android.internal.compat.IPlatformCompat;
 
 import java.util.Map;
+import java.util.Set;
 
 /**
  * CompatChanges APIs - to be used by platform code only (including mainline
@@ -98,15 +100,19 @@
     }
 
     /**
-     * Set an app compat override for a given package. This will check whether the caller is allowed
+     * Adds app compat overrides for a given package. This will check whether the caller is allowed
      * to perform this operation on the given apk and build. Only the installer package is allowed
      * to set overrides on a non-debuggable final build and a non-test apk.
      *
+     * <p>Note that calling this method doesn't remove previously added overrides for the given
+     * package if their change ID isn't in the given map, only replaces those that have the same
+     * change ID.
+     *
      * @param packageName The package name of the app in question.
-     * @param overrides A map from changeId to the override applied for this change id.
+     * @param overrides A map from change ID to the override applied for this change ID.
      */
     @RequiresPermission(android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD)
-    public static void setPackageOverride(@NonNull String packageName,
+    public static void addPackageOverrides(@NonNull String packageName,
             @NonNull Map<Long, PackageOverride> overrides) {
         IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
                 ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
@@ -117,4 +123,29 @@
             e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Removes app compat overrides for a given package. This will check whether the caller is
+     * allowed to perform this operation on the given apk and build. Only the installer package is
+     * allowed to clear overrides on a non-debuggable final build and a non-test apk.
+     *
+     * <p>Note that calling this method with an empty set is a no-op and no overrides will be
+     * removed for the given package.
+     *
+     * @param packageName The package name of the app in question.
+     * @param overridesToRemove A set of change IDs for which to remove overrides.
+     */
+    @RequiresPermission(android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD)
+    public static void removePackageOverrides(@NonNull String packageName,
+            @NonNull Set<Long> overridesToRemove) {
+        IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
+                ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+        CompatibilityOverridesToRemoveConfig config = new CompatibilityOverridesToRemoveConfig(
+                overridesToRemove);
+        try {
+            platformCompat.removeOverridesOnReleaseBuilds(config, packageName);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/com/android/internal/compat/CompatibilityOverridesToRemoveConfig.aidl b/core/java/com/android/internal/compat/CompatibilityOverridesToRemoveConfig.aidl
new file mode 100644
index 0000000..441e553
--- /dev/null
+++ b/core/java/com/android/internal/compat/CompatibilityOverridesToRemoveConfig.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2021 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 com.android.internal.compat;
+
+parcelable CompatibilityOverridesToRemoveConfig;
diff --git a/core/java/com/android/internal/compat/CompatibilityOverridesToRemoveConfig.java b/core/java/com/android/internal/compat/CompatibilityOverridesToRemoveConfig.java
new file mode 100644
index 0000000..642f79c
--- /dev/null
+++ b/core/java/com/android/internal/compat/CompatibilityOverridesToRemoveConfig.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2021 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 com.android.internal.compat;
+
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Parcelable containing compat config change IDs for which to remove overrides for a given
+ * application.
+ * @hide
+ */
+public final class CompatibilityOverridesToRemoveConfig implements Parcelable {
+    public final Set<Long> changeIds;
+
+    public CompatibilityOverridesToRemoveConfig(Set<Long> changeIds) {
+        this.changeIds = changeIds;
+    }
+
+    private CompatibilityOverridesToRemoveConfig(Parcel in) {
+        int keyCount = in.readInt();
+        changeIds = new HashSet<>();
+        for (int i = 0; i < keyCount; i++) {
+            changeIds.add(in.readLong());
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(changeIds.size());
+        for (Long changeId : changeIds) {
+            dest.writeLong(changeId);
+        }
+    }
+
+    public static final Creator<CompatibilityOverridesToRemoveConfig> CREATOR =
+            new Creator<CompatibilityOverridesToRemoveConfig>() {
+
+                @Override
+                public CompatibilityOverridesToRemoveConfig createFromParcel(Parcel in) {
+                    return new CompatibilityOverridesToRemoveConfig(in);
+                }
+
+                @Override
+                public CompatibilityOverridesToRemoveConfig[] newArray(int size) {
+                    return new CompatibilityOverridesToRemoveConfig[size];
+                }
+            };
+}
diff --git a/core/java/com/android/internal/compat/IPlatformCompat.aidl b/core/java/com/android/internal/compat/IPlatformCompat.aidl
index 78d1d22..c9f1704 100644
--- a/core/java/com/android/internal/compat/IPlatformCompat.aidl
+++ b/core/java/com/android/internal/compat/IPlatformCompat.aidl
@@ -22,6 +22,7 @@
 
 parcelable CompatibilityChangeConfig;
 parcelable CompatibilityOverrideConfig;
+parcelable CompatibilityOverridesToRemoveConfig;
 parcelable CompatibilityChangeInfo;
 /**
  * Platform private API for talking with the PlatformCompat service.
@@ -204,6 +205,27 @@
     void clearOverrideForTest(long changeId, String packageName);
 
     /**
+     * Restores the default behaviour for compatibility changes on release builds.
+     *
+     * <p>The caller to this API needs to hold
+     * {@code android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD} and all change ids
+     * in {@code overridesToRemove} need to annotated with
+     * {@link android.compat.annotation.Overridable}.
+     *
+     * A release build in this definition means that {@link android.os.Build#IS_DEBUGGABLE} needs to
+     * be {@code false}.
+     *
+     * <p>Note that this does not kill the app, and therefore overrides read from the app process
+     * will not be updated. Overrides read from the system process do take effect.
+     *
+     * @param overridesToRemove   parcelable containing the compat change overrides to be removed
+     * @param packageName         the package name of the app whose changes will be restored to the
+     *                            default behaviour
+     * @throws SecurityException if overriding changes is not permitted
+     */
+    void removeOverridesOnReleaseBuilds(in CompatibilityOverridesToRemoveConfig overridesToRemove, in String packageName);
+
+    /**
      * Enables all compatibility changes that have enabledSinceTargetSdk ==
      * {@param targetSdkVersion} for an app, subject to the policy.
      *
diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java
index 55e2696..9247568 100644
--- a/services/core/java/com/android/server/compat/CompatConfig.java
+++ b/services/core/java/com/android/server/compat/CompatConfig.java
@@ -33,6 +33,7 @@
 import com.android.internal.compat.CompatibilityChangeConfig;
 import com.android.internal.compat.CompatibilityChangeInfo;
 import com.android.internal.compat.CompatibilityOverrideConfig;
+import com.android.internal.compat.CompatibilityOverridesToRemoveConfig;
 import com.android.internal.compat.IOverrideValidator;
 import com.android.internal.compat.OverrideAllowedState;
 import com.android.server.compat.config.Change;
@@ -370,6 +371,27 @@
         }
     }
 
+    /**
+     * Removes overrides whose change ID is specified in {@code overridesToRemove} that were
+     * previously added via {@link #addOverride(long, String, boolean)} or
+     * {@link #addOverrides(CompatibilityOverrideConfig, String)} for a certain package.
+     *
+     * <p>This restores the default behaviour for the given change IDs and app.
+     *
+     * @param overridesToRemove list of change IDs for which to restore the default behaviour.
+     * @param packageName       the package for which the overrides should be purged
+     */
+    void removePackageOverrides(CompatibilityOverridesToRemoveConfig overridesToRemove,
+            String packageName) {
+        synchronized (mChanges) {
+            for (Long changeId : overridesToRemove.changeIds) {
+                removeOverrideUnsafe(changeId, packageName);
+            }
+            saveOverrides();
+            invalidateCache();
+        }
+    }
+
     private long[] getAllowedChangesSinceTargetSdkForPackage(String packageName,
             int targetSdkVersion) {
         LongArray allowed = new LongArray();
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index 20469a2..2591f38 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -46,6 +46,7 @@
 import com.android.internal.compat.CompatibilityChangeConfig;
 import com.android.internal.compat.CompatibilityChangeInfo;
 import com.android.internal.compat.CompatibilityOverrideConfig;
+import com.android.internal.compat.CompatibilityOverridesToRemoveConfig;
 import com.android.internal.compat.IOverrideValidator;
 import com.android.internal.compat.IPlatformCompat;
 import com.android.internal.util.DumpUtils;
@@ -54,6 +55,7 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -187,7 +189,7 @@
             String packageName) {
         // TODO(b/183630314): Unify the permission enforcement with the other setOverrides* methods.
         checkCompatChangeOverrideOverridablePermission();
-        checkAllCompatOverridesAreOverridable(overrides);
+        checkAllCompatOverridesAreOverridable(overrides.overrides.keySet());
         mCompatConfig.addOverrides(overrides, packageName);
     }
 
@@ -251,6 +253,16 @@
     }
 
     @Override
+    public void removeOverridesOnReleaseBuilds(
+            CompatibilityOverridesToRemoveConfig overridesToRemove,
+            String packageName) {
+        // TODO(b/183630314): Unify the permission enforcement with the other setOverrides* methods.
+        checkCompatChangeOverrideOverridablePermission();
+        checkAllCompatOverridesAreOverridable(overridesToRemove.changeIds);
+        mCompatConfig.removePackageOverrides(overridesToRemove, packageName);
+    }
+
+    @Override
     public CompatibilityChangeConfig getAppConfig(ApplicationInfo appInfo) {
         checkCompatChangeReadAndLogPermission();
         return mCompatConfig.getAppConfig(appInfo);
@@ -396,8 +408,8 @@
         }
     }
 
-    private void checkAllCompatOverridesAreOverridable(CompatibilityOverrideConfig overrides) {
-        for (Long changeId : overrides.overrides.keySet()) {
+    private void checkAllCompatOverridesAreOverridable(Collection<Long> changeIds) {
+        for (Long changeId : changeIds) {
             if (!mCompatConfig.isOverridable(changeId)) {
                 throw new SecurityException("Only change ids marked as Overridable can be "
                         + "overridden.");
diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigBuilder.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigBuilder.java
index 7bdc87e..6b9aa18 100644
--- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigBuilder.java
+++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigBuilder.java
@@ -115,7 +115,12 @@
         return this;
     }
 
-    CompatConfigBuilder addOverridableChangeWithId(long id) {
+    CompatConfigBuilder addEnabledOverridableChangeWithId(long id) {
+        mChanges.add(new CompatChange(id, "", -1, -1, false, false, "", true));
+        return this;
+    }
+
+    CompatConfigBuilder addDisabledOverridableChangeWithId(long id) {
         mChanges.add(new CompatChange(id, "", -1, -1, true, false, "", true));
         return this;
     }
diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
index a866363..0248b9b 100644
--- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
@@ -36,6 +36,7 @@
 
 import com.android.internal.compat.AndroidBuildClassifier;
 import com.android.internal.compat.CompatibilityOverrideConfig;
+import com.android.internal.compat.CompatibilityOverridesToRemoveConfig;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -50,6 +51,10 @@
 import java.nio.file.Files;
 import java.nio.file.Paths;
 import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
 import java.util.UUID;
 
 @RunWith(AndroidJUnit4.class)
@@ -249,7 +254,7 @@
         when(packageManager.getApplicationInfo(eq("com.some.package"), anyInt()))
             .thenReturn(applicationInfo);
 
-        // Force the validator to prevent overriding the change by using a user build.
+        // Force the validator to prevent overriding non-overridable changes by using a user build.
         when(mBuildClassifier.isDebuggableBuild()).thenReturn(false);
         when(mBuildClassifier.isFinalBuild()).thenReturn(true);
 
@@ -261,10 +266,12 @@
 
     @Test
     public void testInstallerCanSetOverrides() throws Exception {
-        final long changeId = 1234L;
-        final int installerUid = 23;
+        final long disabledChangeId1 = 1234L;
+        final long disabledChangeId2 = 1235L;
+        // We make disabledChangeId2 non-overridable to make sure it is ignored.
         CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
-                .addOverridableChangeWithId(1234L)
+                .addDisabledOverridableChangeWithId(disabledChangeId1)
+                .addDisabledChangeWithId(disabledChangeId2)
                 .build();
         ApplicationInfo applicationInfo = ApplicationInfoBuilder.create()
                 .withPackageName("com.some.package")
@@ -274,19 +281,56 @@
         when(packageManager.getApplicationInfo(eq("com.some.package"), anyInt()))
                 .thenReturn(applicationInfo);
 
-        // Force the validator to prevent overriding the change by using a user build.
+        // Force the validator to prevent overriding non-overridable changes by using a user build.
         when(mBuildClassifier.isDebuggableBuild()).thenReturn(false);
         when(mBuildClassifier.isFinalBuild()).thenReturn(true);
 
         CompatibilityOverrideConfig config = new CompatibilityOverrideConfig(
-                Collections.singletonMap(1234L,
+                Collections.singletonMap(disabledChangeId1,
                         new PackageOverride.Builder()
                                 .setMaxVersionCode(99L)
                                 .setEnabled(true)
                                 .build()));
 
         compatConfig.addOverrides(config, "com.some.package");
-        assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isTrue();
+        assertThat(compatConfig.isChangeEnabled(disabledChangeId1, applicationInfo)).isTrue();
+        assertThat(compatConfig.isChangeEnabled(disabledChangeId2, applicationInfo)).isFalse();
+    }
+
+    @Test
+    public void testPreventInstallerSetNonOverridable() throws Exception {
+        final long disabledChangeId1 = 1234L;
+        final long disabledChangeId2 = 1235L;
+        final long disabledChangeId3 = 1236L;
+        CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                .addDisabledOverridableChangeWithId(disabledChangeId1)
+                .addDisabledChangeWithId(disabledChangeId2)
+                .addDisabledOverridableChangeWithId(disabledChangeId3)
+                .build();
+        ApplicationInfo applicationInfo = ApplicationInfoBuilder.create()
+                .withPackageName("com.some.package")
+                .build();
+        PackageManager packageManager = mock(PackageManager.class);
+        when(mContext.getPackageManager()).thenReturn(packageManager);
+        when(packageManager.getApplicationInfo(eq("com.some.package"), anyInt()))
+                .thenReturn(applicationInfo);
+
+        // Force the validator to prevent overriding non-overridable changes by using a user build.
+        when(mBuildClassifier.isDebuggableBuild()).thenReturn(false);
+        when(mBuildClassifier.isFinalBuild()).thenReturn(true);
+
+        Map<Long, PackageOverride> overrides = new HashMap<>();
+        overrides.put(disabledChangeId1, new PackageOverride.Builder().setEnabled(true).build());
+        overrides.put(disabledChangeId2, new PackageOverride.Builder().setEnabled(true).build());
+        overrides.put(disabledChangeId3, new PackageOverride.Builder().setEnabled(true).build());
+        CompatibilityOverrideConfig config = new CompatibilityOverrideConfig(overrides);
+
+        assertThrows(SecurityException.class,
+                () -> compatConfig.addOverrides(config, "com.some.package")
+        );
+        assertThat(compatConfig.isChangeEnabled(disabledChangeId1, applicationInfo)).isTrue();
+        assertThat(compatConfig.isChangeEnabled(disabledChangeId2, applicationInfo)).isFalse();
+        assertThat(compatConfig.isChangeEnabled(disabledChangeId3, applicationInfo)).isFalse();
     }
 
     @Test
@@ -459,7 +503,7 @@
         assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isTrue();
 
         // Reject all override attempts.
-        // Force the validator to prevent overriding the change by using a user build.
+        // Force the validator to prevent overriding non-overridable changes by using a user build.
         when(mBuildClassifier.isDebuggableBuild()).thenReturn(false);
         when(mBuildClassifier.isFinalBuild()).thenReturn(false);
         // Try to turn off change, but validator prevents it.
@@ -481,7 +525,7 @@
                 .thenReturn(applicationInfo);
 
         // Reject all override attempts.
-        // Force the validator to prevent overriding the change by using a user build.
+        // Force the validator to prevent overriding non-overridable changes by using a user build.
         when(mBuildClassifier.isDebuggableBuild()).thenReturn(false);
         when(mBuildClassifier.isFinalBuild()).thenReturn(true);
         // Try to remove a non existing override, and it doesn't fail.
@@ -509,6 +553,90 @@
     }
 
     @Test
+    public void testInstallerCanRemoveOverrides() throws Exception {
+        final long disabledChangeId1 = 1234L;
+        final long disabledChangeId2 = 1235L;
+        final long enabledChangeId = 1236L;
+        // We make disabledChangeId2 non-overridable to make sure it is ignored.
+        CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                .addDisabledOverridableChangeWithId(disabledChangeId1)
+                .addDisabledChangeWithId(disabledChangeId2)
+                .addEnabledOverridableChangeWithId(enabledChangeId)
+                .build();
+        ApplicationInfo applicationInfo = ApplicationInfoBuilder.create()
+                .withPackageName("com.some.package")
+                .build();
+        when(mPackageManager.getApplicationInfo(eq("com.some.package"), anyInt()))
+                .thenReturn(applicationInfo);
+
+        assertThat(compatConfig.addOverride(disabledChangeId1, "com.some.package", true)).isTrue();
+        assertThat(compatConfig.addOverride(disabledChangeId2, "com.some.package", true)).isTrue();
+        assertThat(compatConfig.addOverride(enabledChangeId, "com.some.package", false)).isTrue();
+        assertThat(compatConfig.isChangeEnabled(disabledChangeId1, applicationInfo)).isTrue();
+        assertThat(compatConfig.isChangeEnabled(disabledChangeId2, applicationInfo)).isTrue();
+        assertThat(compatConfig.isChangeEnabled(enabledChangeId, applicationInfo)).isFalse();
+
+        // Force the validator to prevent overriding non-overridable changes by using a user build.
+        when(mBuildClassifier.isDebuggableBuild()).thenReturn(false);
+        when(mBuildClassifier.isFinalBuild()).thenReturn(true);
+
+        Set<Long> overridesToRemove = new HashSet<>();
+        overridesToRemove.add(disabledChangeId1);
+        overridesToRemove.add(enabledChangeId);
+        CompatibilityOverridesToRemoveConfig config = new CompatibilityOverridesToRemoveConfig(
+                overridesToRemove);
+
+        compatConfig.removePackageOverrides(config, "com.some.package");
+        assertThat(compatConfig.isChangeEnabled(disabledChangeId1, applicationInfo)).isFalse();
+        assertThat(compatConfig.isChangeEnabled(disabledChangeId2, applicationInfo)).isTrue();
+        assertThat(compatConfig.isChangeEnabled(enabledChangeId, applicationInfo)).isTrue();
+    }
+
+    @Test
+    public void testPreventInstallerRemoveNonOverridable() throws Exception {
+        final long disabledChangeId1 = 1234L;
+        final long disabledChangeId2 = 1235L;
+        final long disabledChangeId3 = 1236L;
+        CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                .addDisabledOverridableChangeWithId(disabledChangeId1)
+                .addDisabledChangeWithId(disabledChangeId2)
+                .addDisabledOverridableChangeWithId(disabledChangeId3)
+                .build();
+        ApplicationInfo applicationInfo = ApplicationInfoBuilder.create()
+                .withPackageName("com.some.package")
+                .build();
+        PackageManager packageManager = mock(PackageManager.class);
+        when(mContext.getPackageManager()).thenReturn(packageManager);
+        when(packageManager.getApplicationInfo(eq("com.some.package"), anyInt()))
+                .thenReturn(applicationInfo);
+
+        assertThat(compatConfig.addOverride(disabledChangeId1, "com.some.package", true)).isTrue();
+        assertThat(compatConfig.addOverride(disabledChangeId2, "com.some.package", true)).isTrue();
+        assertThat(compatConfig.addOverride(disabledChangeId3, "com.some.package", true)).isTrue();
+        assertThat(compatConfig.isChangeEnabled(disabledChangeId1, applicationInfo)).isTrue();
+        assertThat(compatConfig.isChangeEnabled(disabledChangeId2, applicationInfo)).isTrue();
+        assertThat(compatConfig.isChangeEnabled(disabledChangeId3, applicationInfo)).isTrue();
+
+        // Force the validator to prevent overriding non-overridable changes by using a user build.
+        when(mBuildClassifier.isDebuggableBuild()).thenReturn(false);
+        when(mBuildClassifier.isFinalBuild()).thenReturn(true);
+
+        Set<Long> overridesToRemove = new HashSet<>();
+        overridesToRemove.add(disabledChangeId1);
+        overridesToRemove.add(disabledChangeId2);
+        overridesToRemove.add(disabledChangeId3);
+        CompatibilityOverridesToRemoveConfig config = new CompatibilityOverridesToRemoveConfig(
+                overridesToRemove);
+
+        assertThrows(SecurityException.class,
+                () -> compatConfig.removePackageOverrides(config, "com.some.package")
+        );
+        assertThat(compatConfig.isChangeEnabled(disabledChangeId1, applicationInfo)).isFalse();
+        assertThat(compatConfig.isChangeEnabled(disabledChangeId2, applicationInfo)).isTrue();
+        assertThat(compatConfig.isChangeEnabled(disabledChangeId3, applicationInfo)).isTrue();
+    }
+
+    @Test
     public void testEnableTargetSdkChangesForPackage() throws Exception {
         CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
                 .addEnabledChangeWithId(1L)
diff --git a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
index a2664e5..9accd49 100644
--- a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
@@ -98,7 +98,7 @@
                 .addEnableAfterSdkChangeWithId(Build.VERSION_CODES.Q, 5L)
                 .addEnableAfterSdkChangeWithId(Build.VERSION_CODES.R, 6L)
                 .addLoggingOnlyChangeWithId(7L)
-                .addOverridableChangeWithId(8L)
+                .addDisabledOverridableChangeWithId(8L)
                 .build();
         mPlatformCompat = new PlatformCompat(mContext, mCompatConfig, mBuildClassifier);
         assertThat(mPlatformCompat.listAllChanges()).asList().containsExactly(