Loading services/core/java/com/android/server/wm/utils/OptPropFactory.java 0 → 100644 +233 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.server.wm.utils; import static java.lang.Boolean.FALSE; import static java.lang.Boolean.TRUE; import android.annotation.IntDef; import android.annotation.NonNull; import android.content.pm.PackageManager; import android.util.Slog; import java.util.function.BooleanSupplier; /** * Utility class which helps with handling with properties to opt-in or * opt-out a specific feature. */ public class OptPropFactory { @NonNull private final PackageManager mPackageManager; @NonNull private final String mPackageName; /** * Object responsible to handle optIn and optOut properties. * * @param packageManager The PackageManager reference * @param packageName The name of the package. */ public OptPropFactory(@NonNull PackageManager packageManager, @NonNull String packageName) { mPackageManager = packageManager; mPackageName = packageName; } /** * Creates an OptProp for the given property * * @param propertyName The name of the property. * @return The OptProp for the given property */ @NonNull public OptProp create(@NonNull String propertyName) { return OptProp.create( () -> mPackageManager.getProperty(propertyName, mPackageName).getBoolean(), propertyName); } /** * Creates an OptProp for the given property behind a gate condition. * * @param propertyName The name of the property. * @param gateCondition If this resolves to false, the property is unset. This is evaluated at * every interaction with the OptProp. * @return The OptProp for the given property */ @NonNull public OptProp create(@NonNull String propertyName, @NonNull BooleanSupplier gateCondition) { return OptProp.create( () -> mPackageManager.getProperty(propertyName, mPackageName).getBoolean(), propertyName, gateCondition); } @FunctionalInterface private interface ThrowableBooleanSupplier { boolean get() throws Exception; } public static class OptProp { private static final int VALUE_UNSET = -2; private static final int VALUE_UNDEFINED = -1; private static final int VALUE_FALSE = 0; private static final int VALUE_TRUE = 1; @IntDef(prefix = {"VALUE_"}, value = { VALUE_UNSET, VALUE_UNDEFINED, VALUE_FALSE, VALUE_TRUE, }) @interface OptionalValue {} private static final String TAG = "OptProp"; // The condition is evaluated every time the OptProp state is accessed. @NonNull private final BooleanSupplier mCondition; // This is evaluated only once in the lifetime of an OptProp. @NonNull private final ThrowableBooleanSupplier mValueSupplier; @NonNull private final String mPropertyName; @OptionalValue private int mValue = VALUE_UNDEFINED; private OptProp(@NonNull ThrowableBooleanSupplier valueSupplier, @NonNull String propertyName, @NonNull BooleanSupplier condition) { mValueSupplier = valueSupplier; mPropertyName = propertyName; mCondition = condition; } @NonNull private static OptProp create(@NonNull ThrowableBooleanSupplier valueSupplier, @NonNull String propertyName) { return new OptProp(valueSupplier, propertyName, () -> true); } @NonNull private static OptProp create(@NonNull ThrowableBooleanSupplier valueSupplier, @NonNull String propertyName, @NonNull BooleanSupplier condition) { return new OptProp(valueSupplier, propertyName, condition); } /** * @return {@code true} when the guarding condition is {@code true} and the property has * been explicitly set to {@code true}. {@code false} otherwise. The guarding condition is * evaluated every time this method is invoked. */ public boolean isTrue() { return mCondition.getAsBoolean() && getValue() == VALUE_TRUE; } /** * @return {@code true} when the guarding condition is {@code true} and the property has * been explicitly set to {@code false}. {@code false} otherwise. The guarding condition is * evaluated every time this method is invoked. */ public boolean isFalse() { return mCondition.getAsBoolean() && getValue() == VALUE_FALSE; } /** * Returns {@code true} when the following conditions are met: * <ul> * <li>{@code gatingCondition} doesn't evaluate to {@code false} * <li>App developers didn't opt out with a component {@code property} * <li>App developers opted in with a component {@code property} or an OEM opted in with * a per-app override * </ul> * * <p>This is used for the treatments that are enabled only on per-app basis. */ public boolean shouldEnableWithOverrideAndProperty(boolean overrideValue) { if (!mCondition.getAsBoolean()) { return false; } if (getValue() == VALUE_FALSE) { return false; } return getValue() == VALUE_TRUE || overrideValue; } /** * Returns {@code true} when the following conditions are met: * <ul> * <li>{@code gatingCondition} doesn't evaluate to {@code false} * <li>App developers didn't opt out with a component {@code property} * <li>OEM opted in with a per-app override * </ul> * * <p>This is used for the treatments that are enabled based with the heuristic but can be * disabled on per-app basis by OEMs or app developers. */ public boolean shouldEnableWithOptInOverrideAndOptOutProperty( boolean overrideValue) { if (!mCondition.getAsBoolean()) { return false; } return getValue() != VALUE_FALSE && overrideValue; } /** * Returns {@code true} when the following conditions are met: * <ul> * <li>{@code gatingCondition} doesn't resolve to {@code false} * <li>OEM didn't opt out with a per-app override * <li>App developers didn't opt out with a component {@code property} * </ul> * * <p>This is used for the treatments that are enabled based with the heuristic but can be * disabled on per-app basis by OEMs or app developers. */ public boolean shouldEnableWithOptOutOverrideAndProperty(boolean overrideValue) { if (!mCondition.getAsBoolean()) { return false; } return getValue() != VALUE_FALSE && !overrideValue; } @OptionalValue private int getValue() { if (mValue == VALUE_UNDEFINED) { try { final Boolean value = mValueSupplier.get(); if (TRUE.equals(value)) { mValue = VALUE_TRUE; } else if (FALSE.equals(value)) { mValue = VALUE_FALSE; } else { mValue = VALUE_UNSET; } } catch (Exception e) { Slog.w(TAG, "Cannot read opt property " + mPropertyName); mValue = VALUE_UNSET; } } return mValue; } } } services/tests/wmtests/src/com/android/server/wm/utils/OptPropFactoryTest.java 0 → 100644 +286 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.server.wm.utils; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.pm.PackageManager; import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; import com.android.server.wm.utils.OptPropFactory.OptProp; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; import java.util.function.BooleanSupplier; /** * Build/Install/Run: * atest WmTests:OptPropFactoryTest */ @SmallTest @Presubmit public class OptPropFactoryTest { private PackageManager mPackageManager; private OptPropFactory mOptPropFactory; @Before public void setUp() { mPackageManager = mock(PackageManager.class); mOptPropFactory = new OptPropFactory(mPackageManager, ""); } @Test public void optProp_laziness() throws PackageManager.NameNotFoundException { initPropAs(/* propertyValue */ true); // When OptPropBuilder is created the PackageManager is not used verify(mPackageManager, never()).getProperty(anyString(), anyString()); // Accessing the value multiple times only uses PackageManager once final OptProp optProp = createOptProp(); optProp.isTrue(); optProp.isFalse(); verify(mPackageManager).getProperty(anyString(), anyString()); } @Test public void optProp_withSetValueTrue() throws PackageManager.NameNotFoundException { initPropAs(/* propertyValue */ true); final OptProp optProp = createOptProp(); assertTrue(optProp.isTrue()); assertFalse(optProp.isFalse()); } @Test public void optProp_withSetValueFalse() throws PackageManager.NameNotFoundException { initPropAs(/* propertyValue */ false); final OptProp optProp = createOptProp(); assertFalse(optProp.isTrue()); assertTrue(optProp.isFalse()); } @Test public void optProp_withSetValueWithConditionFalse() throws PackageManager.NameNotFoundException { initPropAs(/* propertyValue */ true); final OptProp optProp = createOptProp(() -> false); assertFalse(optProp.isTrue()); assertFalse(optProp.isFalse()); } @Test public void optProp_withUnsetValue() { final OptProp optProp = createOptProp(); assertFalse(optProp.isTrue()); assertFalse(optProp.isFalse()); } @Test public void optProp_isUnsetWhenPropertyIsNotPresent() throws PackageManager.NameNotFoundException { initPropAsWithException(); // Property is unset final OptProp optUnset = createOptProp(); assertFalse(optUnset.isTrue()); assertFalse(optUnset.isFalse()); } @Test public void optProp_shouldEnableWithOverrideAndProperty() throws PackageManager.NameNotFoundException { // Property is unset final OptProp optUnset = createOptProp(() -> false); assertFalse(optUnset.shouldEnableWithOverrideAndProperty(/* override */ true)); // The value is the override one final OptProp optUnsetOn = createOptProp(); assertTrue(optUnsetOn.shouldEnableWithOverrideAndProperty(/* override */ true)); assertFalse(optUnsetOn.shouldEnableWithOverrideAndProperty(/* override */ false)); // Property is set to true initPropAs(true); final OptProp optTrue = createOptProp(() -> false); assertFalse(optTrue.shouldEnableWithOverrideAndProperty(/* override */ true)); final OptProp optTrueOn = createOptProp(() -> true); assertTrue(optTrueOn.shouldEnableWithOverrideAndProperty(/* override */ true)); assertTrue(optTrueOn.shouldEnableWithOverrideAndProperty(/* override */ false)); // Property is set to false initPropAs(false); final OptProp optFalse = createOptProp(() -> false); assertFalse(optFalse.shouldEnableWithOverrideAndProperty(/* override */ true)); final OptProp optFalseOn = createOptProp(); assertFalse(optFalseOn.shouldEnableWithOverrideAndProperty(/* override */ true)); assertFalse(optFalseOn.shouldEnableWithOverrideAndProperty(/* override */ false)); } @Test public void optProp_shouldEnableWithOptInOverrideAndOptOutProperty() throws PackageManager.NameNotFoundException { // Property is unset final OptProp optUnset = createOptProp(() -> false); assertFalse(optUnset.shouldEnableWithOptInOverrideAndOptOutProperty(/* override */ true)); final OptProp optUnsetOn = createOptProp(); assertTrue(optUnsetOn.shouldEnableWithOptInOverrideAndOptOutProperty(/* override */ true)); assertFalse( optUnsetOn.shouldEnableWithOptInOverrideAndOptOutProperty(/* override */ false)); // Property is set to true initPropAs(true); final OptProp optTrue = createOptProp(() -> false); assertFalse(optTrue.shouldEnableWithOptInOverrideAndOptOutProperty(/* override */ true)); // Is the value of the override final OptProp optTrueOn = createOptProp(() -> true); assertTrue(optTrueOn.shouldEnableWithOptInOverrideAndOptOutProperty(/* override */ true)); assertFalse(optTrueOn.shouldEnableWithOptInOverrideAndOptOutProperty(/* override */ false)); // Property is set to false initPropAs(false); final OptProp optFalse = createOptProp(() -> false); assertFalse(optFalse.shouldEnableWithOptInOverrideAndOptOutProperty(/* override */ true)); // Always false ahatever is the value of the override final OptProp optFalseOn = createOptProp(); assertFalse(optFalseOn.shouldEnableWithOptInOverrideAndOptOutProperty(/* override */ true)); assertFalse( optFalseOn.shouldEnableWithOptInOverrideAndOptOutProperty(/* override */ false)); } @Test public void optProp_shouldEnableWithOptOutOverrideAndProperty() throws PackageManager.NameNotFoundException { // Property is unset final OptProp optUnset = createOptProp(() -> false); assertFalse(optUnset.shouldEnableWithOptOutOverrideAndProperty(/* override */ true)); // Is the negate of the override value final OptProp optUnsetOn = createOptProp(); assertTrue(optUnsetOn.shouldEnableWithOptOutOverrideAndProperty(/* override */ false)); assertFalse(optUnsetOn.shouldEnableWithOptOutOverrideAndProperty(/* override */ true)); // Property is set to true initPropAs(true); final OptProp optTrue = createOptProp(() -> false); assertFalse(optTrue.shouldEnableWithOptOutOverrideAndProperty(/* override */ true)); // Is the negate of the override value final OptProp optTrueOn = createOptProp(() -> true); assertTrue(optTrueOn.shouldEnableWithOptOutOverrideAndProperty(/* override */ false)); assertFalse(optTrueOn.shouldEnableWithOptOutOverrideAndProperty(/* override */ true)); // Property is set to false initPropAs(false); final OptProp optFalse = createOptProp(() -> false); assertFalse(optFalse.shouldEnableWithOptOutOverrideAndProperty(/* override */ true)); // Always false ahatever is the value of the override final OptProp optFalseOn = createOptProp(); assertFalse(optFalseOn.shouldEnableWithOptOutOverrideAndProperty(/* override */ true)); assertFalse(optFalseOn.shouldEnableWithOptOutOverrideAndProperty(/* override */ false)); } @Test public void optProp_gateConditionIsInvokedOnlyOncePerInvocation() throws PackageManager.NameNotFoundException { final FakeGateCondition trueCondition = new FakeGateCondition(/* returnValue */ true); final OptProp optProp = createOptProp(trueCondition); optProp.shouldEnableWithOverrideAndProperty(/* override value */ true); assertEquals(1, trueCondition.getInvocationCount()); trueCondition.clearInvocationCount(); initPropAs(true); optProp.shouldEnableWithOptInOverrideAndOptOutProperty(/* override value */ true); assertEquals(1, trueCondition.getInvocationCount()); trueCondition.clearInvocationCount(); optProp.shouldEnableWithOptOutOverrideAndProperty(/* override value */ true); assertEquals(1, trueCondition.getInvocationCount()); trueCondition.clearInvocationCount(); } private void initPropAs(boolean propertyValue) throws PackageManager.NameNotFoundException { Mockito.clearInvocations(mPackageManager); final PackageManager.Property prop = new PackageManager.Property( "", /* value */ propertyValue, "", ""); when(mPackageManager.getProperty(anyString(), anyString())).thenReturn(prop); } private void initPropAsWithException() throws PackageManager.NameNotFoundException { Mockito.clearInvocations(mPackageManager); when(mPackageManager.getProperty("", "")).thenThrow( new PackageManager.NameNotFoundException()); } private OptProp createOptProp() { return mOptPropFactory.create(""); } private OptProp createOptProp(BooleanSupplier condition) { return mOptPropFactory.create("", condition); } private static class FakeGateCondition implements BooleanSupplier { private int mInvocationCount = 0; private final boolean mReturnValue; private FakeGateCondition(boolean returnValue) { mReturnValue = returnValue; } @Override public boolean getAsBoolean() { mInvocationCount++; return mReturnValue; } int getInvocationCount() { return mInvocationCount; } void clearInvocationCount() { mInvocationCount = 0; } } } Loading
services/core/java/com/android/server/wm/utils/OptPropFactory.java 0 → 100644 +233 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.server.wm.utils; import static java.lang.Boolean.FALSE; import static java.lang.Boolean.TRUE; import android.annotation.IntDef; import android.annotation.NonNull; import android.content.pm.PackageManager; import android.util.Slog; import java.util.function.BooleanSupplier; /** * Utility class which helps with handling with properties to opt-in or * opt-out a specific feature. */ public class OptPropFactory { @NonNull private final PackageManager mPackageManager; @NonNull private final String mPackageName; /** * Object responsible to handle optIn and optOut properties. * * @param packageManager The PackageManager reference * @param packageName The name of the package. */ public OptPropFactory(@NonNull PackageManager packageManager, @NonNull String packageName) { mPackageManager = packageManager; mPackageName = packageName; } /** * Creates an OptProp for the given property * * @param propertyName The name of the property. * @return The OptProp for the given property */ @NonNull public OptProp create(@NonNull String propertyName) { return OptProp.create( () -> mPackageManager.getProperty(propertyName, mPackageName).getBoolean(), propertyName); } /** * Creates an OptProp for the given property behind a gate condition. * * @param propertyName The name of the property. * @param gateCondition If this resolves to false, the property is unset. This is evaluated at * every interaction with the OptProp. * @return The OptProp for the given property */ @NonNull public OptProp create(@NonNull String propertyName, @NonNull BooleanSupplier gateCondition) { return OptProp.create( () -> mPackageManager.getProperty(propertyName, mPackageName).getBoolean(), propertyName, gateCondition); } @FunctionalInterface private interface ThrowableBooleanSupplier { boolean get() throws Exception; } public static class OptProp { private static final int VALUE_UNSET = -2; private static final int VALUE_UNDEFINED = -1; private static final int VALUE_FALSE = 0; private static final int VALUE_TRUE = 1; @IntDef(prefix = {"VALUE_"}, value = { VALUE_UNSET, VALUE_UNDEFINED, VALUE_FALSE, VALUE_TRUE, }) @interface OptionalValue {} private static final String TAG = "OptProp"; // The condition is evaluated every time the OptProp state is accessed. @NonNull private final BooleanSupplier mCondition; // This is evaluated only once in the lifetime of an OptProp. @NonNull private final ThrowableBooleanSupplier mValueSupplier; @NonNull private final String mPropertyName; @OptionalValue private int mValue = VALUE_UNDEFINED; private OptProp(@NonNull ThrowableBooleanSupplier valueSupplier, @NonNull String propertyName, @NonNull BooleanSupplier condition) { mValueSupplier = valueSupplier; mPropertyName = propertyName; mCondition = condition; } @NonNull private static OptProp create(@NonNull ThrowableBooleanSupplier valueSupplier, @NonNull String propertyName) { return new OptProp(valueSupplier, propertyName, () -> true); } @NonNull private static OptProp create(@NonNull ThrowableBooleanSupplier valueSupplier, @NonNull String propertyName, @NonNull BooleanSupplier condition) { return new OptProp(valueSupplier, propertyName, condition); } /** * @return {@code true} when the guarding condition is {@code true} and the property has * been explicitly set to {@code true}. {@code false} otherwise. The guarding condition is * evaluated every time this method is invoked. */ public boolean isTrue() { return mCondition.getAsBoolean() && getValue() == VALUE_TRUE; } /** * @return {@code true} when the guarding condition is {@code true} and the property has * been explicitly set to {@code false}. {@code false} otherwise. The guarding condition is * evaluated every time this method is invoked. */ public boolean isFalse() { return mCondition.getAsBoolean() && getValue() == VALUE_FALSE; } /** * Returns {@code true} when the following conditions are met: * <ul> * <li>{@code gatingCondition} doesn't evaluate to {@code false} * <li>App developers didn't opt out with a component {@code property} * <li>App developers opted in with a component {@code property} or an OEM opted in with * a per-app override * </ul> * * <p>This is used for the treatments that are enabled only on per-app basis. */ public boolean shouldEnableWithOverrideAndProperty(boolean overrideValue) { if (!mCondition.getAsBoolean()) { return false; } if (getValue() == VALUE_FALSE) { return false; } return getValue() == VALUE_TRUE || overrideValue; } /** * Returns {@code true} when the following conditions are met: * <ul> * <li>{@code gatingCondition} doesn't evaluate to {@code false} * <li>App developers didn't opt out with a component {@code property} * <li>OEM opted in with a per-app override * </ul> * * <p>This is used for the treatments that are enabled based with the heuristic but can be * disabled on per-app basis by OEMs or app developers. */ public boolean shouldEnableWithOptInOverrideAndOptOutProperty( boolean overrideValue) { if (!mCondition.getAsBoolean()) { return false; } return getValue() != VALUE_FALSE && overrideValue; } /** * Returns {@code true} when the following conditions are met: * <ul> * <li>{@code gatingCondition} doesn't resolve to {@code false} * <li>OEM didn't opt out with a per-app override * <li>App developers didn't opt out with a component {@code property} * </ul> * * <p>This is used for the treatments that are enabled based with the heuristic but can be * disabled on per-app basis by OEMs or app developers. */ public boolean shouldEnableWithOptOutOverrideAndProperty(boolean overrideValue) { if (!mCondition.getAsBoolean()) { return false; } return getValue() != VALUE_FALSE && !overrideValue; } @OptionalValue private int getValue() { if (mValue == VALUE_UNDEFINED) { try { final Boolean value = mValueSupplier.get(); if (TRUE.equals(value)) { mValue = VALUE_TRUE; } else if (FALSE.equals(value)) { mValue = VALUE_FALSE; } else { mValue = VALUE_UNSET; } } catch (Exception e) { Slog.w(TAG, "Cannot read opt property " + mPropertyName); mValue = VALUE_UNSET; } } return mValue; } } }
services/tests/wmtests/src/com/android/server/wm/utils/OptPropFactoryTest.java 0 → 100644 +286 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.server.wm.utils; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.pm.PackageManager; import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; import com.android.server.wm.utils.OptPropFactory.OptProp; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; import java.util.function.BooleanSupplier; /** * Build/Install/Run: * atest WmTests:OptPropFactoryTest */ @SmallTest @Presubmit public class OptPropFactoryTest { private PackageManager mPackageManager; private OptPropFactory mOptPropFactory; @Before public void setUp() { mPackageManager = mock(PackageManager.class); mOptPropFactory = new OptPropFactory(mPackageManager, ""); } @Test public void optProp_laziness() throws PackageManager.NameNotFoundException { initPropAs(/* propertyValue */ true); // When OptPropBuilder is created the PackageManager is not used verify(mPackageManager, never()).getProperty(anyString(), anyString()); // Accessing the value multiple times only uses PackageManager once final OptProp optProp = createOptProp(); optProp.isTrue(); optProp.isFalse(); verify(mPackageManager).getProperty(anyString(), anyString()); } @Test public void optProp_withSetValueTrue() throws PackageManager.NameNotFoundException { initPropAs(/* propertyValue */ true); final OptProp optProp = createOptProp(); assertTrue(optProp.isTrue()); assertFalse(optProp.isFalse()); } @Test public void optProp_withSetValueFalse() throws PackageManager.NameNotFoundException { initPropAs(/* propertyValue */ false); final OptProp optProp = createOptProp(); assertFalse(optProp.isTrue()); assertTrue(optProp.isFalse()); } @Test public void optProp_withSetValueWithConditionFalse() throws PackageManager.NameNotFoundException { initPropAs(/* propertyValue */ true); final OptProp optProp = createOptProp(() -> false); assertFalse(optProp.isTrue()); assertFalse(optProp.isFalse()); } @Test public void optProp_withUnsetValue() { final OptProp optProp = createOptProp(); assertFalse(optProp.isTrue()); assertFalse(optProp.isFalse()); } @Test public void optProp_isUnsetWhenPropertyIsNotPresent() throws PackageManager.NameNotFoundException { initPropAsWithException(); // Property is unset final OptProp optUnset = createOptProp(); assertFalse(optUnset.isTrue()); assertFalse(optUnset.isFalse()); } @Test public void optProp_shouldEnableWithOverrideAndProperty() throws PackageManager.NameNotFoundException { // Property is unset final OptProp optUnset = createOptProp(() -> false); assertFalse(optUnset.shouldEnableWithOverrideAndProperty(/* override */ true)); // The value is the override one final OptProp optUnsetOn = createOptProp(); assertTrue(optUnsetOn.shouldEnableWithOverrideAndProperty(/* override */ true)); assertFalse(optUnsetOn.shouldEnableWithOverrideAndProperty(/* override */ false)); // Property is set to true initPropAs(true); final OptProp optTrue = createOptProp(() -> false); assertFalse(optTrue.shouldEnableWithOverrideAndProperty(/* override */ true)); final OptProp optTrueOn = createOptProp(() -> true); assertTrue(optTrueOn.shouldEnableWithOverrideAndProperty(/* override */ true)); assertTrue(optTrueOn.shouldEnableWithOverrideAndProperty(/* override */ false)); // Property is set to false initPropAs(false); final OptProp optFalse = createOptProp(() -> false); assertFalse(optFalse.shouldEnableWithOverrideAndProperty(/* override */ true)); final OptProp optFalseOn = createOptProp(); assertFalse(optFalseOn.shouldEnableWithOverrideAndProperty(/* override */ true)); assertFalse(optFalseOn.shouldEnableWithOverrideAndProperty(/* override */ false)); } @Test public void optProp_shouldEnableWithOptInOverrideAndOptOutProperty() throws PackageManager.NameNotFoundException { // Property is unset final OptProp optUnset = createOptProp(() -> false); assertFalse(optUnset.shouldEnableWithOptInOverrideAndOptOutProperty(/* override */ true)); final OptProp optUnsetOn = createOptProp(); assertTrue(optUnsetOn.shouldEnableWithOptInOverrideAndOptOutProperty(/* override */ true)); assertFalse( optUnsetOn.shouldEnableWithOptInOverrideAndOptOutProperty(/* override */ false)); // Property is set to true initPropAs(true); final OptProp optTrue = createOptProp(() -> false); assertFalse(optTrue.shouldEnableWithOptInOverrideAndOptOutProperty(/* override */ true)); // Is the value of the override final OptProp optTrueOn = createOptProp(() -> true); assertTrue(optTrueOn.shouldEnableWithOptInOverrideAndOptOutProperty(/* override */ true)); assertFalse(optTrueOn.shouldEnableWithOptInOverrideAndOptOutProperty(/* override */ false)); // Property is set to false initPropAs(false); final OptProp optFalse = createOptProp(() -> false); assertFalse(optFalse.shouldEnableWithOptInOverrideAndOptOutProperty(/* override */ true)); // Always false ahatever is the value of the override final OptProp optFalseOn = createOptProp(); assertFalse(optFalseOn.shouldEnableWithOptInOverrideAndOptOutProperty(/* override */ true)); assertFalse( optFalseOn.shouldEnableWithOptInOverrideAndOptOutProperty(/* override */ false)); } @Test public void optProp_shouldEnableWithOptOutOverrideAndProperty() throws PackageManager.NameNotFoundException { // Property is unset final OptProp optUnset = createOptProp(() -> false); assertFalse(optUnset.shouldEnableWithOptOutOverrideAndProperty(/* override */ true)); // Is the negate of the override value final OptProp optUnsetOn = createOptProp(); assertTrue(optUnsetOn.shouldEnableWithOptOutOverrideAndProperty(/* override */ false)); assertFalse(optUnsetOn.shouldEnableWithOptOutOverrideAndProperty(/* override */ true)); // Property is set to true initPropAs(true); final OptProp optTrue = createOptProp(() -> false); assertFalse(optTrue.shouldEnableWithOptOutOverrideAndProperty(/* override */ true)); // Is the negate of the override value final OptProp optTrueOn = createOptProp(() -> true); assertTrue(optTrueOn.shouldEnableWithOptOutOverrideAndProperty(/* override */ false)); assertFalse(optTrueOn.shouldEnableWithOptOutOverrideAndProperty(/* override */ true)); // Property is set to false initPropAs(false); final OptProp optFalse = createOptProp(() -> false); assertFalse(optFalse.shouldEnableWithOptOutOverrideAndProperty(/* override */ true)); // Always false ahatever is the value of the override final OptProp optFalseOn = createOptProp(); assertFalse(optFalseOn.shouldEnableWithOptOutOverrideAndProperty(/* override */ true)); assertFalse(optFalseOn.shouldEnableWithOptOutOverrideAndProperty(/* override */ false)); } @Test public void optProp_gateConditionIsInvokedOnlyOncePerInvocation() throws PackageManager.NameNotFoundException { final FakeGateCondition trueCondition = new FakeGateCondition(/* returnValue */ true); final OptProp optProp = createOptProp(trueCondition); optProp.shouldEnableWithOverrideAndProperty(/* override value */ true); assertEquals(1, trueCondition.getInvocationCount()); trueCondition.clearInvocationCount(); initPropAs(true); optProp.shouldEnableWithOptInOverrideAndOptOutProperty(/* override value */ true); assertEquals(1, trueCondition.getInvocationCount()); trueCondition.clearInvocationCount(); optProp.shouldEnableWithOptOutOverrideAndProperty(/* override value */ true); assertEquals(1, trueCondition.getInvocationCount()); trueCondition.clearInvocationCount(); } private void initPropAs(boolean propertyValue) throws PackageManager.NameNotFoundException { Mockito.clearInvocations(mPackageManager); final PackageManager.Property prop = new PackageManager.Property( "", /* value */ propertyValue, "", ""); when(mPackageManager.getProperty(anyString(), anyString())).thenReturn(prop); } private void initPropAsWithException() throws PackageManager.NameNotFoundException { Mockito.clearInvocations(mPackageManager); when(mPackageManager.getProperty("", "")).thenThrow( new PackageManager.NameNotFoundException()); } private OptProp createOptProp() { return mOptPropFactory.create(""); } private OptProp createOptProp(BooleanSupplier condition) { return mOptPropFactory.create("", condition); } private static class FakeGateCondition implements BooleanSupplier { private int mInvocationCount = 0; private final boolean mReturnValue; private FakeGateCondition(boolean returnValue) { mReturnValue = returnValue; } @Override public boolean getAsBoolean() { mInvocationCount++; return mReturnValue; } int getInvocationCount() { return mInvocationCount; } void clearInvocationCount() { mInvocationCount = 0; } } }