From 224f8c01d7096a57de8d8da6f51a79134553ee74 Mon Sep 17 00:00:00 2001 From: Andrei Onea Date: Tue, 24 Nov 2020 23:54:04 +0000 Subject: [PATCH] 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 --- .../internal/compat/OverrideAllowedState.java | 18 ++-- .../android/server/compat/CompatChange.java | 66 +++++++++++++++ .../android/server/compat/CompatConfig.java | 83 ++++++++++++------- .../server/compat/OverrideValidatorImpl.java | 4 +- .../android/server/compat/PlatformCompat.java | 35 ++++++++ .../java/com/android/server/SystemServer.java | 3 + .../server/compat/CompatConfigTest.java | 80 +++++++++++++++++- .../compat/OverrideValidatorImplTest.java | 43 ++++++++++ 8 files changed, 286 insertions(+), 46 deletions(-) diff --git a/core/java/com/android/internal/compat/OverrideAllowedState.java b/core/java/com/android/internal/compat/OverrideAllowedState.java index 9a78ad2011cf4..c0bbe5082131b 100644 --- a/core/java/com/android/internal/compat/OverrideAllowedState.java +++ b/core/java/com/android/internal/compat/OverrideAllowedState.java @@ -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) @@ -57,10 +57,10 @@ public final class OverrideAllowedState implements Parcelable { * Change cannot be overridden, due to the app's targetSdk being above the change's targetSdk. */ 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"; } diff --git a/services/core/java/com/android/server/compat/CompatChange.java b/services/core/java/com/android/server/compat/CompatChange.java index a8aa9aada607a..c4ff99bae6947 100644 --- a/services/core/java/com/android/server/compat/CompatChange.java +++ b/services/core/java/com/android/server/compat/CompatChange.java @@ -61,6 +61,7 @@ public final class CompatChange extends CompatibilityChangeInfo { ChangeListener mListener = null; private Map mPackageOverrides; + private Map 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. + * + *

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. + * + *

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.

+ * + * @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(); } diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java index 8511118cc840a..1cbaa1e3451ea 100644 --- a/services/core/java/com/android/server/compat/CompatConfig.java +++ b/services/core/java/com/android/server/compat/CompatConfig.java @@ -173,7 +173,17 @@ final class CompatConfig { c = new CompatChange(changeId); addChange(c); } - c.addPackageOverride(packageName, enabled); + 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) { - OverrideAllowedState allowedState = - mOverrideValidator.getOverrideAllowedState(changeId, packageName); - allowedState.enforce(changeId, packageName); - c.removePackageOverride(packageName); - } + if (c != null) { + // 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(); } + 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. * - *

This restores the default behaviour for the given change and app, once any app - * processes have been restarted. + *

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); - } + CompatChange change = mChanges.valueAt(i); + 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(); + } + } + } } diff --git a/services/core/java/com/android/server/compat/OverrideValidatorImpl.java b/services/core/java/com/android/server/compat/OverrideValidatorImpl.java index 79a13ca242c17..58250407b8643 100644 --- a/services/core/java/com/android/server/compat/OverrideValidatorImpl.java +++ b/services/core/java/com/android/server/compat/OverrideValidatorImpl.java @@ -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. diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java index aa85f7f0f55f9..fada37bce82c1 100644 --- a/services/core/java/com/android/server/compat/PlatformCompat.java +++ b/services/core/java/com/android/server/compat/PlatformCompat.java @@ -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); + } } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index b5c5bb52d0501..59b24f887f2c2 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -910,6 +910,9 @@ public final class SystemServer { mActivityManagerService.setSystemProcess(); t.traceEnd(); + // The package receiver depends on the activity service in order to get registered. + platformCompat.registerPackageReceiver(mSystemContext); + // Complete the watchdog setup with an ActivityManager instance and listen for reboots // Do this only after the ActivityManagerService is properly started as a system process t.traceBegin("InitWatchdog"); 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 8c63bfcf1407d..6cb2dbc7cbff8 100644 --- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java @@ -28,6 +28,7 @@ import android.app.compat.ChangeIdStateCache; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import androidx.test.runner.AndroidJUnit4; @@ -229,6 +230,83 @@ public class CompatConfigTest { assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isFalse(); } + @Test + public void testApplyDeferredOverridesAfterInstallingApp() throws Exception { + ApplicationInfo applicationInfo = ApplicationInfoBuilder.create() + .withPackageName("com.notinstalled.foo") + .debuggable().build(); + when(mPackageManager.getApplicationInfo(eq("com.notinstalled.foo"), anyInt())) + .thenThrow(new NameNotFoundException()); + CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) + .addDisabledChangeWithId(1234L).build(); + when(mBuildClassifier.isDebuggableBuild()).thenReturn(false); + when(mBuildClassifier.isFinalBuild()).thenReturn(true); + + // Add override before the app is available. + compatConfig.addOverride(1234L, "com.notinstalled.foo", true); + assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isFalse(); + + // Pretend the app is now installed. + when(mPackageManager.getApplicationInfo(eq("com.notinstalled.foo"), anyInt())) + .thenReturn(applicationInfo); + + compatConfig.recheckOverrides("com.notinstalled.foo"); + assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isTrue(); + } + + @Test + public void testApplyDeferredOverrideClearsOverrideAfterUninstall() throws Exception { + ApplicationInfo applicationInfo = ApplicationInfoBuilder.create() + .withPackageName("com.installedapp.foo") + .debuggable().build(); + when(mPackageManager.getApplicationInfo(eq("com.installedapp.foo"), anyInt())) + .thenReturn(applicationInfo); + + CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) + .addDisabledChangeWithId(1234L).build(); + when(mBuildClassifier.isDebuggableBuild()).thenReturn(false); + when(mBuildClassifier.isFinalBuild()).thenReturn(true); + + // Add override when app is installed. + compatConfig.addOverride(1234L, "com.installedapp.foo", true); + assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isTrue(); + + // Pretend the app is now uninstalled. + when(mPackageManager.getApplicationInfo(eq("com.installedapp.foo"), anyInt())) + .thenThrow(new NameNotFoundException()); + + compatConfig.recheckOverrides("com.installedapp.foo"); + assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isFalse(); + } + + @Test + public void testApplyDeferredOverrideClearsOverrideAfterChange() throws Exception { + ApplicationInfo debuggableApp = ApplicationInfoBuilder.create() + .withPackageName("com.installedapp.foo") + .debuggable().build(); + ApplicationInfo releaseApp = ApplicationInfoBuilder.create() + .withPackageName("com.installedapp.foo") + .build(); + when(mPackageManager.getApplicationInfo(eq("com.installedapp.foo"), anyInt())) + .thenReturn(debuggableApp); + + CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) + .addDisabledChangeWithId(1234L).build(); + when(mBuildClassifier.isDebuggableBuild()).thenReturn(false); + when(mBuildClassifier.isFinalBuild()).thenReturn(true); + + // Add override for debuggable app. + compatConfig.addOverride(1234L, "com.installedapp.foo", true); + assertThat(compatConfig.isChangeEnabled(1234L, debuggableApp)).isTrue(); + + // Pretend the app now is no longer debuggable, but has the same package. + when(mPackageManager.getApplicationInfo(eq("com.installedapp.foo"), anyInt())) + .thenReturn(releaseApp); + + compatConfig.recheckOverrides("com.installedapp.foo"); + assertThat(compatConfig.isChangeEnabled(1234L, releaseApp)).isFalse(); + } + @Test public void testLoggingOnlyChangePreventAddOverride() throws Exception { CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) @@ -259,7 +337,7 @@ public class CompatConfigTest { // Reject all override attempts. // Force the validator to prevent overriding the change by using a user build. when(mBuildClassifier.isDebuggableBuild()).thenReturn(false); - when(mBuildClassifier.isFinalBuild()).thenReturn(true); + when(mBuildClassifier.isFinalBuild()).thenReturn(false); // Try to turn off change, but validator prevents it. assertThrows(SecurityException.class, () -> compatConfig.removeOverride(1234L, "com.some.package")); diff --git a/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java b/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java index c53b29a08a4a8..019c615fed381 100644 --- a/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java +++ b/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java @@ -17,6 +17,7 @@ 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; @@ -31,6 +32,8 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; + import androidx.test.runner.AndroidJUnit4; @@ -409,4 +412,44 @@ public class OverrideValidatorImplTest { assertThat(stateDLoggingOnlyChange) .isEqualTo(new OverrideAllowedState(LOGGING_ONLY_CHANGE, -1, -1)); } + @Test + public void getOverrideAllowedState_finalBuildAnyChangeNotInstalledApp_deferOverride() + throws Exception { + CompatConfig config = CompatConfigBuilder.create(finalBuild(), mContext) + .addEnableAfterSdkChangeWithId(TARGET_SDK_BEFORE, 1) + .addEnableAfterSdkChangeWithId(TARGET_SDK, 2) + .addEnableAfterSdkChangeWithId(TARGET_SDK_AFTER, 3) + .addEnabledChangeWithId(4) + .addDisabledChangeWithId(5) + .addLoggingOnlyChangeWithId(6).build(); + IOverrideValidator overrideValidator = config.getOverrideValidator(); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenThrow(new NameNotFoundException()); + + OverrideAllowedState stateTargetSdkLessChange = + overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME); + OverrideAllowedState stateTargetSdkEqualChange = + overrideValidator.getOverrideAllowedState(2, PACKAGE_NAME); + OverrideAllowedState stateTargetSdkAfterChange = + overrideValidator.getOverrideAllowedState(3, PACKAGE_NAME); + OverrideAllowedState stateEnabledChange = + overrideValidator.getOverrideAllowedState(4, PACKAGE_NAME); + OverrideAllowedState stateDisabledChange = + overrideValidator.getOverrideAllowedState(5, PACKAGE_NAME); + OverrideAllowedState stateDLoggingOnlyChange = + overrideValidator.getOverrideAllowedState(6, PACKAGE_NAME); + + assertThat(stateTargetSdkLessChange) + .isEqualTo(new OverrideAllowedState(DEFERRED_VERIFICATION, -1, -1)); + assertThat(stateTargetSdkEqualChange) + .isEqualTo(new OverrideAllowedState(DEFERRED_VERIFICATION, -1, -1)); + assertThat(stateTargetSdkAfterChange) + .isEqualTo(new OverrideAllowedState(DEFERRED_VERIFICATION, -1, -1)); + assertThat(stateEnabledChange) + .isEqualTo(new OverrideAllowedState(DEFERRED_VERIFICATION, -1, -1)); + assertThat(stateDisabledChange) + .isEqualTo(new OverrideAllowedState(DEFERRED_VERIFICATION, -1, -1)); + assertThat(stateDLoggingOnlyChange) + .isEqualTo(new OverrideAllowedState(LOGGING_ONLY_CHANGE, -1, -1)); + } }