diff options
| author | 2023-05-02 01:15:32 +0000 | |
|---|---|---|
| committer | 2023-05-02 01:15:32 +0000 | |
| commit | 270653263206c6140351afc4bbf77632095b9f3f (patch) | |
| tree | 2042223cda8ac75c03a2584a26eec00af269f97b | |
| parent | 7c216e6d30730699d63e038a9c73ab30a2539bee (diff) | |
| parent | f51f8677e2860cf672cfe3d355f1edc085c2ff59 (diff) | |
Merge "Add support for lazy conditions" into udc-dev am: f51f8677e2
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/22730954
Change-Id: I843846a5b045bccd2ca4868a57c5eea7b0895965
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
15 files changed, 821 insertions, 236 deletions
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/CombinedCondition.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/CombinedCondition.kt index 2d83458ec2f7..a2b6e2cf9ae3 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/CombinedCondition.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/CombinedCondition.kt @@ -16,27 +16,179 @@ package com.android.systemui.shared.condition +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.Job +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.launch + /** * A higher order [Condition] which combines multiple conditions with a specified - * [Evaluator.ConditionOperand]. + * [Evaluator.ConditionOperand]. Conditions are executed lazily as-needed. + * + * @param scope The [CoroutineScope] to execute in. + * @param conditions The list of conditions to evaluate. Since conditions are executed lazily, the + * ordering is important here. + * @param operand The [Evaluator.ConditionOperand] to apply to the conditions. */ -internal class CombinedCondition +@OptIn(ExperimentalCoroutinesApi::class) +class CombinedCondition constructor( + private val scope: CoroutineScope, private val conditions: Collection<Condition>, @Evaluator.ConditionOperand private val operand: Int -) : Condition(null, false), Condition.Callback { +) : Condition(scope, null, false) { + + private var job: Job? = null + private val _startStrategy by lazy { calculateStartStrategy() } override fun start() { - onConditionChanged(this) - conditions.forEach { it.addCallback(this) } - } + job = + scope.launch { + val groupedConditions = conditions.groupBy { it.isOverridingCondition } - override fun onConditionChanged(condition: Condition) { - Evaluator.evaluate(conditions, operand)?.also { value -> updateCondition(value) } - ?: clearCondition() + lazilyEvaluate( + conditions = groupedConditions.getOrDefault(true, emptyList()), + filterUnknown = true + ) + .distinctUntilChanged() + .flatMapLatest { overriddenValue -> + // If there are overriding conditions with values set, they take precedence. + if (overriddenValue == null) { + lazilyEvaluate( + conditions = groupedConditions.getOrDefault(false, emptyList()), + filterUnknown = false + ) + } else { + flowOf(overriddenValue) + } + } + .collect { conditionMet -> + if (conditionMet == null) { + clearCondition() + } else { + updateCondition(conditionMet) + } + } + } } override fun stop() { - conditions.forEach { it.removeCallback(this) } + job?.cancel() + job = null + } + + /** + * Evaluates a list of conditions lazily with support for short-circuiting. Conditions are + * executed serially in the order provided. At any point if the result can be determined, we + * short-circuit and return the result without executing all conditions. + */ + private fun lazilyEvaluate( + conditions: Collection<Condition>, + filterUnknown: Boolean, + ): Flow<Boolean?> = callbackFlow { + val jobs = MutableList<Job?>(conditions.size) { null } + val values = MutableList<Boolean?>(conditions.size) { null } + val flows = conditions.map { it.toFlow() } + + fun cancelAllExcept(indexToSkip: Int) { + for (index in 0 until jobs.size) { + if (index == indexToSkip) { + continue + } + if ( + indexToSkip == -1 || + conditions.elementAt(index).startStrategy == START_WHEN_NEEDED + ) { + jobs[index]?.cancel() + jobs[index] = null + values[index] = null + } + } + } + + fun collectFlow(index: Int) { + // Base case which is triggered once we have collected all the flows. In this case, + // we never short-circuited and therefore should return the fully evaluated + // conditions. + if (flows.isEmpty() || index == -1) { + val filteredValues = + if (filterUnknown) { + values.filterNotNull() + } else { + values + } + trySend(Evaluator.evaluate(filteredValues, operand)) + return + } + jobs[index] = + scope.launch { + flows.elementAt(index).collect { value -> + values[index] = value + if (shouldEarlyReturn(value)) { + trySend(value) + // The overall result is contingent on this condition, so we don't need + // to monitor any other conditions. + cancelAllExcept(index) + } else { + collectFlow(jobs.indexOfFirst { it == null }) + } + } + } + } + + // Collect any eager conditions immediately. + var started = false + for ((index, condition) in conditions.withIndex()) { + if (condition.startStrategy == START_EAGERLY) { + collectFlow(index) + started = true + } + } + + // If no eager conditions started, start the first condition to kick off evaluation. + if (!started) { + collectFlow(0) + } + awaitClose { cancelAllExcept(-1) } + } + + private fun shouldEarlyReturn(conditionMet: Boolean?): Boolean { + return when (operand) { + Evaluator.OP_AND -> conditionMet == false + Evaluator.OP_OR -> conditionMet == true + else -> false + } + } + + /** + * Calculate the start strategy for this condition. This depends on the strategies of the child + * conditions. If there are any eager conditions, we must also start this condition eagerly. In + * the absence of eager conditions, we check for lazy conditions. In the absence of either, we + * make the condition only start when needed. + */ + private fun calculateStartStrategy(): Int { + var startStrategy = START_WHEN_NEEDED + for (condition in conditions) { + when (condition.startStrategy) { + START_EAGERLY -> return START_EAGERLY + START_LAZILY -> { + startStrategy = START_LAZILY + } + START_WHEN_NEEDED -> { + // this is the default, so do nothing + } + } + } + return startStrategy + } + + override fun getStartStrategy(): Int { + return _startStrategy } } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Condition.java b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Condition.java index cc48090e1217..6bf1ce57b01e 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Condition.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Condition.java @@ -18,11 +18,14 @@ package com.android.systemui.shared.condition; import android.util.Log; +import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleEventObserver; import androidx.lifecycle.LifecycleOwner; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; @@ -30,6 +33,8 @@ import java.util.Collection; import java.util.Iterator; import java.util.List; +import kotlinx.coroutines.CoroutineScope; + /** * Base class for a condition that needs to be fulfilled in order for {@link Monitor} to inform * its callbacks. @@ -39,24 +44,27 @@ public abstract class Condition { private final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>(); private final boolean mOverriding; + private final CoroutineScope mScope; private Boolean mIsConditionMet; private boolean mStarted = false; /** * By default, conditions have an initial value of false and are not overriding. */ - public Condition() { - this(false, false); + public Condition(CoroutineScope scope) { + this(scope, false, false); } /** * Constructor for specifying initial state and overriding condition attribute. + * * @param initialConditionMet Initial state of the condition. - * @param overriding Whether this condition overrides others. + * @param overriding Whether this condition overrides others. */ - protected Condition(Boolean initialConditionMet, boolean overriding) { + protected Condition(CoroutineScope scope, Boolean initialConditionMet, boolean overriding) { mIsConditionMet = initialConditionMet; mOverriding = overriding; + mScope = scope; } /** @@ -70,6 +78,29 @@ public abstract class Condition { protected abstract void stop(); /** + * Condition should be started as soon as there is an active subscription. + */ + public static final int START_EAGERLY = 0; + /** + * Condition should be started lazily only if needed. But once started, it will not be cancelled + * unless there are no more active subscriptions. + */ + public static final int START_LAZILY = 1; + /** + * Condition should be started lazily only if needed, and can be stopped when not needed. This + * should be used for conditions which are expensive to keep running. + */ + public static final int START_WHEN_NEEDED = 2; + + @Retention(RetentionPolicy.SOURCE) + @IntDef({START_EAGERLY, START_LAZILY, START_WHEN_NEEDED}) + @interface StartStrategy { + } + + @StartStrategy + protected abstract int getStartStrategy(); + + /** * Returns whether the current condition overrides */ public boolean isOverridingCondition() { @@ -183,6 +214,7 @@ public abstract class Condition { /** * Returns whether the condition is set. This method should be consulted to understand the * value of {@link #isConditionMet()}. + * * @return {@code true} if value is present, {@code false} otherwise. */ public boolean isConditionSet() { @@ -210,17 +242,18 @@ public abstract class Condition { * conditions are true. */ public Condition and(@NonNull Collection<Condition> others) { - final List<Condition> conditions = new ArrayList<>(others); + final List<Condition> conditions = new ArrayList<>(); conditions.add(this); - return new CombinedCondition(conditions, Evaluator.OP_AND); + conditions.addAll(others); + return new CombinedCondition(mScope, conditions, Evaluator.OP_AND); } /** * Creates a new condition which will only be true when both this condition and the provided * condition is true. */ - public Condition and(@NonNull Condition other) { - return new CombinedCondition(Arrays.asList(this, other), Evaluator.OP_AND); + public Condition and(@NonNull Condition... others) { + return and(Arrays.asList(others)); } /** @@ -228,17 +261,18 @@ public abstract class Condition { * provided conditions are true. */ public Condition or(@NonNull Collection<Condition> others) { - final List<Condition> conditions = new ArrayList<>(others); + final List<Condition> conditions = new ArrayList<>(); conditions.add(this); - return new CombinedCondition(conditions, Evaluator.OP_OR); + conditions.addAll(others); + return new CombinedCondition(mScope, conditions, Evaluator.OP_OR); } /** * Creates a new condition which will only be true when either this condition or the provided * condition is true. */ - public Condition or(@NonNull Condition other) { - return new CombinedCondition(Arrays.asList(this, other), Evaluator.OP_OR); + public Condition or(@NonNull Condition... others) { + return or(Arrays.asList(others)); } /** diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/ConditionExtensions.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/ConditionExtensions.kt index 8f8bff86f64d..84edc3577007 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/ConditionExtensions.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/ConditionExtensions.kt @@ -1,14 +1,22 @@ package com.android.systemui.shared.condition +import com.android.systemui.shared.condition.Condition.StartStrategy import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job +import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.launch /** Converts a boolean flow to a [Condition] object which can be used with a [Monitor] */ @JvmOverloads -fun Flow<Boolean>.toCondition(scope: CoroutineScope, initialValue: Boolean? = null): Condition { - return object : Condition(initialValue, false) { +fun Flow<Boolean>.toCondition( + scope: CoroutineScope, + @StartStrategy strategy: Int, + initialValue: Boolean? = null +): Condition { + return object : Condition(scope, initialValue, false) { var job: Job? = null override fun start() { @@ -19,5 +27,25 @@ fun Flow<Boolean>.toCondition(scope: CoroutineScope, initialValue: Boolean? = nu job?.cancel() job = null } + + override fun getStartStrategy() = strategy } } + +/** Converts a [Condition] to a boolean flow */ +fun Condition.toFlow(): Flow<Boolean?> { + return callbackFlow { + val callback = + Condition.Callback { condition -> + if (condition.isConditionSet) { + trySend(condition.isConditionMet) + } else { + trySend(null) + } + } + addCallback(callback) + callback.onConditionChanged(this@toFlow) + awaitClose { removeCallback(callback) } + } + .distinctUntilChanged() +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Evaluator.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Evaluator.kt index 454294f36d2a..584d9785c300 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Evaluator.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Evaluator.kt @@ -22,7 +22,7 @@ import android.annotation.IntDef * Helper for evaluating a collection of [Condition] objects with a given * [Evaluator.ConditionOperand] */ -internal object Evaluator { +object Evaluator { /** Operands for combining multiple conditions together */ @Retention(AnnotationRetention.SOURCE) @IntDef(value = [OP_AND, OP_OR]) @@ -70,15 +70,31 @@ internal object Evaluator { fun evaluate(conditions: Collection<Condition>, @ConditionOperand operand: Int): Boolean? { if (conditions.isEmpty()) return null // If there are overriding conditions with values set, they take precedence. - val targetConditions = + val values: Collection<Boolean?> = conditions .filter { it.isConditionSet && it.isOverridingCondition } .ifEmpty { conditions } + .map { condition -> + if (condition.isConditionSet) { + condition.isConditionMet + } else { + null + } + } + return evaluate(values = values, operand = operand) + } + + /** + * Evaluates a set of booleans with a given operand + * + * @param operand The operand to use when evaluating. + * @return Either true or false if the value is known, or null if value is unknown + */ + internal fun evaluate(values: Collection<Boolean?>, @ConditionOperand operand: Int): Boolean? { + if (values.isEmpty()) return null return when (operand) { - OP_AND -> - threeValuedAndOrOr(conditions = targetConditions, returnValueIfAnyMatches = false) - OP_OR -> - threeValuedAndOrOr(conditions = targetConditions, returnValueIfAnyMatches = true) + OP_AND -> threeValuedAndOrOr(values = values, returnValueIfAnyMatches = false) + OP_OR -> threeValuedAndOrOr(values = values, returnValueIfAnyMatches = true) else -> null } } @@ -90,16 +106,16 @@ internal object Evaluator { * any value is true. */ private fun threeValuedAndOrOr( - conditions: Collection<Condition>, + values: Collection<Boolean?>, returnValueIfAnyMatches: Boolean ): Boolean? { var hasUnknown = false - for (condition in conditions) { - if (!condition.isConditionSet) { + for (value in values) { + if (value == null) { hasUnknown = true continue } - if (condition.isConditionMet == returnValueIfAnyMatches) { + if (value == returnValueIfAnyMatches) { return returnValueIfAnyMatches } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java b/packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java index 250cfeca5aa4..c889ac214cda 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java @@ -18,11 +18,14 @@ package com.android.systemui.dreams.conditions; import com.android.internal.app.AssistUtils; import com.android.internal.app.IVisualQueryDetectionAttentionListener; +import com.android.systemui.dagger.qualifiers.Application; import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.shared.condition.Condition; import javax.inject.Inject; +import kotlinx.coroutines.CoroutineScope; + /** * {@link AssistantAttentionCondition} provides a signal when assistant has the user's attention. */ @@ -58,8 +61,10 @@ public class AssistantAttentionCondition extends Condition { @Inject public AssistantAttentionCondition( + @Application CoroutineScope scope, DreamOverlayStateController dreamOverlayStateController, AssistUtils assistUtils) { + super(scope); mDreamOverlayStateController = dreamOverlayStateController; mAssistUtils = assistUtils; } @@ -75,6 +80,11 @@ public class AssistantAttentionCondition extends Condition { mDreamOverlayStateController.removeCallback(mCallback); } + @Override + protected int getStartStrategy() { + return START_EAGERLY; + } + private void enableVisualQueryDetection() { if (mEnabled) { return; diff --git a/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java b/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java index 3ef19b760826..99688bee68e0 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java @@ -19,19 +19,20 @@ import android.app.DreamManager; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; +import com.android.systemui.dagger.qualifiers.Application; import com.android.systemui.shared.condition.Condition; import javax.inject.Inject; +import kotlinx.coroutines.CoroutineScope; + /** * {@link DreamCondition} provides a signal when a dream begins and ends. */ public class DreamCondition extends Condition { private final DreamManager mDreamManager; - private final KeyguardUpdateMonitor mUpdateMonitor; - private final KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() { @Override @@ -41,7 +42,11 @@ public class DreamCondition extends Condition { }; @Inject - public DreamCondition(DreamManager dreamManager, KeyguardUpdateMonitor monitor) { + public DreamCondition( + @Application CoroutineScope scope, + DreamManager dreamManager, + KeyguardUpdateMonitor monitor) { + super(scope); mDreamManager = dreamManager; mUpdateMonitor = monitor; } @@ -56,4 +61,9 @@ public class DreamCondition extends Condition { protected void stop() { mUpdateMonitor.removeCallback(mUpdateCallback); } + + @Override + protected int getStartStrategy() { + return START_EAGERLY; + } } diff --git a/packages/SystemUI/src/com/android/systemui/process/condition/SystemProcessCondition.java b/packages/SystemUI/src/com/android/systemui/process/condition/SystemProcessCondition.java index 80fbf9115065..b6a5ad6f2155 100644 --- a/packages/SystemUI/src/com/android/systemui/process/condition/SystemProcessCondition.java +++ b/packages/SystemUI/src/com/android/systemui/process/condition/SystemProcessCondition.java @@ -16,11 +16,14 @@ package com.android.systemui.process.condition; +import com.android.systemui.dagger.qualifiers.Application; import com.android.systemui.process.ProcessWrapper; import com.android.systemui.shared.condition.Condition; import javax.inject.Inject; +import kotlinx.coroutines.CoroutineScope; + /** * {@link SystemProcessCondition} checks to make sure the current process is being ran by the * System User. @@ -29,8 +32,9 @@ public class SystemProcessCondition extends Condition { private final ProcessWrapper mProcessWrapper; @Inject - public SystemProcessCondition(ProcessWrapper processWrapper) { - super(); + public SystemProcessCondition(@Application CoroutineScope scope, + ProcessWrapper processWrapper) { + super(scope); mProcessWrapper = processWrapper; } @@ -42,4 +46,9 @@ public class SystemProcessCondition extends Condition { @Override protected void stop() { } + + @Override + protected int getStartStrategy() { + return START_EAGERLY; + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java index ef1061faeed9..07cb5d88a515 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java @@ -42,6 +42,8 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import kotlinx.coroutines.CoroutineScope; + @SmallTest @RunWith(AndroidTestingRunner.class) public class AssistantAttentionConditionTest extends SysuiTestCase { @@ -51,6 +53,8 @@ public class AssistantAttentionConditionTest extends SysuiTestCase { AssistUtils mAssistUtils; @Mock DreamOverlayStateController mDreamOverlayStateController; + @Mock + CoroutineScope mScope; private AssistantAttentionCondition mAssistantAttentionCondition; @@ -59,7 +63,7 @@ public class AssistantAttentionConditionTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); mAssistantAttentionCondition = - new AssistantAttentionCondition(mDreamOverlayStateController, mAssistUtils); + new AssistantAttentionCondition(mScope, mDreamOverlayStateController, mAssistUtils); // Adding a callback also starts the condition. mAssistantAttentionCondition.addCallback(mCallback); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java index e1c54976d734..68c79652ac00 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java @@ -25,7 +25,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.DreamManager; -import android.content.Context; import android.testing.AndroidTestingRunner; import androidx.test.filters.SmallTest; @@ -42,13 +41,12 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import kotlinx.coroutines.CoroutineScope; + @SmallTest @RunWith(AndroidTestingRunner.class) public class DreamConditionTest extends SysuiTestCase { @Mock - Context mContext; - - @Mock Condition.Callback mCallback; @Mock @@ -57,6 +55,9 @@ public class DreamConditionTest extends SysuiTestCase { @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor; + @Mock + CoroutineScope mScope; + @Before public void setup() { MockitoAnnotations.initMocks(this); @@ -68,7 +69,8 @@ public class DreamConditionTest extends SysuiTestCase { @Test public void testInitialDreamingState() { when(mDreamManager.isDreaming()).thenReturn(true); - final DreamCondition condition = new DreamCondition(mDreamManager, mKeyguardUpdateMonitor); + final DreamCondition condition = new DreamCondition(mScope, mDreamManager, + mKeyguardUpdateMonitor); condition.addCallback(mCallback); verify(mCallback).onConditionChanged(eq(condition)); @@ -81,7 +83,8 @@ public class DreamConditionTest extends SysuiTestCase { @Test public void testInitialNonDreamingState() { when(mDreamManager.isDreaming()).thenReturn(false); - final DreamCondition condition = new DreamCondition(mDreamManager, mKeyguardUpdateMonitor); + final DreamCondition condition = new DreamCondition(mScope, mDreamManager, + mKeyguardUpdateMonitor); condition.addCallback(mCallback); verify(mCallback, never()).onConditionChanged(eq(condition)); @@ -96,7 +99,8 @@ public class DreamConditionTest extends SysuiTestCase { final ArgumentCaptor<KeyguardUpdateMonitorCallback> callbackCaptor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class); when(mDreamManager.isDreaming()).thenReturn(true); - final DreamCondition condition = new DreamCondition(mDreamManager, mKeyguardUpdateMonitor); + final DreamCondition condition = new DreamCondition(mScope, mDreamManager, + mKeyguardUpdateMonitor); condition.addCallback(mCallback); verify(mKeyguardUpdateMonitor).registerCallback(callbackCaptor.capture()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/process/condition/SystemProcessConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/process/condition/SystemProcessConditionTest.java index fb7197706ddc..ff60fcda53e0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/process/condition/SystemProcessConditionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/process/condition/SystemProcessConditionTest.java @@ -37,6 +37,8 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import kotlinx.coroutines.CoroutineScope; + @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @SmallTest @@ -47,6 +49,9 @@ public class SystemProcessConditionTest extends SysuiTestCase { @Mock Monitor.Callback mCallback; + @Mock + CoroutineScope mScope; + private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock()); @Before @@ -61,7 +66,7 @@ public class SystemProcessConditionTest extends SysuiTestCase { @Test public void testConditionFailsWithNonSystemProcess() { - final Condition condition = new SystemProcessCondition(mProcessWrapper); + final Condition condition = new SystemProcessCondition(mScope, mProcessWrapper); when(mProcessWrapper.isSystemUser()).thenReturn(false); final Monitor monitor = new Monitor(mExecutor); @@ -82,7 +87,7 @@ public class SystemProcessConditionTest extends SysuiTestCase { @Test public void testConditionSucceedsWithSystemProcess() { - final Condition condition = new SystemProcessCondition(mProcessWrapper); + final Condition condition = new SystemProcessCondition(mScope, mProcessWrapper); when(mProcessWrapper.isSystemUser()).thenReturn(true); final Monitor monitor = new Monitor(mExecutor); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/CombinedConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/CombinedConditionTest.kt new file mode 100644 index 000000000000..1a0e932d7e8a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/CombinedConditionTest.kt @@ -0,0 +1,450 @@ +/* + * Copyright (C) 2023 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.systemui.shared.condition + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.shared.condition.Condition.START_EAGERLY +import com.android.systemui.shared.condition.Condition.START_LAZILY +import com.android.systemui.shared.condition.Condition.START_WHEN_NEEDED +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.cancel +import kotlinx.coroutines.runBlocking +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class CombinedConditionTest : SysuiTestCase() { + + class FakeCondition + constructor( + scope: CoroutineScope, + initialValue: Boolean?, + overriding: Boolean = false, + @StartStrategy private val startStrategy: Int = START_WHEN_NEEDED, + ) : Condition(scope, initialValue, overriding) { + private var _started = false + val started: Boolean + get() = _started + + override fun start() { + _started = true + } + + override fun stop() { + _started = false + } + + override fun getStartStrategy(): Int { + return startStrategy + } + + fun setValue(value: Boolean?) { + value?.also { updateCondition(value) } ?: clearCondition() + } + } + + @Test + fun testOrOperatorWithMixedConditions() = runSelfCancelingTest { + val startWhenNeededCondition = + FakeCondition(scope = this, initialValue = false, startStrategy = START_WHEN_NEEDED) + val eagerCondition = + FakeCondition(scope = this, initialValue = false, startStrategy = START_EAGERLY) + val lazyCondition = + FakeCondition(scope = this, initialValue = false, startStrategy = START_LAZILY) + + val combinedCondition = + CombinedCondition( + scope = this, + conditions = + listOf( + eagerCondition, + lazyCondition, + startWhenNeededCondition, + ), + operand = Evaluator.OP_OR + ) + + val callback = Condition.Callback {} + combinedCondition.addCallback(callback) + + assertThat(combinedCondition.isConditionMet).isFalse() + assertThat(eagerCondition.started).isTrue() + assertThat(lazyCondition.started).isTrue() + assertThat(startWhenNeededCondition.started).isTrue() + + eagerCondition.setValue(true) + assertThat(combinedCondition.isConditionMet).isTrue() + assertThat(eagerCondition.started).isTrue() + assertThat(lazyCondition.started).isTrue() + assertThat(startWhenNeededCondition.started).isFalse() + + startWhenNeededCondition.setValue(true) + assertThat(combinedCondition.isConditionMet).isTrue() + assertThat(eagerCondition.started).isTrue() + assertThat(lazyCondition.started).isTrue() + assertThat(startWhenNeededCondition.started).isFalse() + + startWhenNeededCondition.setValue(false) + eagerCondition.setValue(false) + assertThat(combinedCondition.isConditionMet).isFalse() + assertThat(eagerCondition.started).isTrue() + assertThat(lazyCondition.started).isTrue() + assertThat(startWhenNeededCondition.started).isTrue() + } + + @Test + fun testAndOperatorWithMixedConditions() = runSelfCancelingTest { + val startWhenNeededCondition = + FakeCondition(scope = this, initialValue = false, startStrategy = START_WHEN_NEEDED) + val eagerCondition = + FakeCondition(scope = this, initialValue = false, startStrategy = START_EAGERLY) + val lazyCondition = + FakeCondition(scope = this, initialValue = false, startStrategy = START_LAZILY) + + val combinedCondition = + CombinedCondition( + scope = this, + conditions = + listOf( + startWhenNeededCondition, + lazyCondition, + eagerCondition, + ), + operand = Evaluator.OP_AND + ) + + val callback = Condition.Callback {} + combinedCondition.addCallback(callback) + + assertThat(combinedCondition.isConditionMet).isFalse() + assertThat(eagerCondition.started).isTrue() + assertThat(lazyCondition.started).isFalse() + assertThat(startWhenNeededCondition.started).isFalse() + + eagerCondition.setValue(true) + assertThat(combinedCondition.isConditionMet).isFalse() + assertThat(eagerCondition.started).isTrue() + assertThat(startWhenNeededCondition.started).isTrue() + assertThat(lazyCondition.started).isFalse() + + startWhenNeededCondition.setValue(true) + assertThat(combinedCondition.isConditionMet).isFalse() + assertThat(eagerCondition.started).isTrue() + assertThat(startWhenNeededCondition.started).isFalse() + assertThat(lazyCondition.started).isTrue() + + startWhenNeededCondition.setValue(false) + assertThat(combinedCondition.isConditionMet).isFalse() + assertThat(eagerCondition.started).isTrue() + assertThat(startWhenNeededCondition.started).isFalse() + assertThat(lazyCondition.started).isTrue() + + startWhenNeededCondition.setValue(true) + lazyCondition.setValue(true) + assertThat(combinedCondition.isConditionMet).isTrue() + assertThat(eagerCondition.started).isTrue() + assertThat(startWhenNeededCondition.started).isTrue() + assertThat(lazyCondition.started).isTrue() + } + + @Test + fun testAndOperatorWithStartWhenNeededConditions() = runSelfCancelingTest { + val conditions = + 0.rangeTo(2) + .map { + FakeCondition( + scope = this, + initialValue = false, + startStrategy = START_WHEN_NEEDED + ) + } + .toList() + + val combinedCondition = + CombinedCondition(scope = this, conditions = conditions, operand = Evaluator.OP_AND) + + val callback = Condition.Callback {} + combinedCondition.addCallback(callback) + assertThat(combinedCondition.isConditionMet).isFalse() + // Only the first condition should be started + assertThat(areStarted(conditions)).containsExactly(true, false, false).inOrder() + + conditions[0].setValue(true) + assertThat(combinedCondition.isConditionMet).isFalse() + assertThat(areStarted(conditions)).containsExactly(false, true, false).inOrder() + + conditions[1].setValue(true) + assertThat(combinedCondition.isConditionMet).isFalse() + assertThat(areStarted(conditions)).containsExactly(false, false, true).inOrder() + + conditions[2].setValue(true) + assertThat(combinedCondition.isConditionMet).isTrue() + assertThat(areStarted(conditions)).containsExactly(true, true, true).inOrder() + + conditions[0].setValue(false) + assertThat(combinedCondition.isConditionMet).isFalse() + assertThat(areStarted(conditions)).containsExactly(true, false, false).inOrder() + } + + @Test + fun testOrOperatorWithStartWhenNeededConditions() = runSelfCancelingTest { + val conditions = + 0.rangeTo(2) + .map { + FakeCondition( + scope = this, + initialValue = false, + startStrategy = START_WHEN_NEEDED + ) + } + .toList() + + val combinedCondition = + CombinedCondition(scope = this, conditions = conditions, operand = Evaluator.OP_OR) + + val callback = Condition.Callback {} + combinedCondition.addCallback(callback) + // Default is to monitor all conditions when false + assertThat(combinedCondition.isConditionMet).isFalse() + assertThat(areStarted(conditions)).containsExactly(true, true, true).inOrder() + + // Condition 2 is true, so we should only monitor condition 2 + conditions[1].setValue(true) + assertThat(combinedCondition.isConditionMet).isTrue() + assertThat(areStarted(conditions)).containsExactly(false, true, false).inOrder() + + // Condition 2 becomes false, so we go back to monitoring all conditions + conditions[1].setValue(false) + assertThat(combinedCondition.isConditionMet).isFalse() + assertThat(areStarted(conditions)).containsExactly(true, true, true).inOrder() + + // Condition 3 becomes true, so we only monitor condition 3 + conditions[2].setValue(true) + assertThat(combinedCondition.isConditionMet).isTrue() + assertThat(areStarted(conditions)).containsExactly(false, false, true).inOrder() + + // Condition 2 becomes true, but we are still only monitoring condition 3 + conditions[1].setValue(true) + assertThat(combinedCondition.isConditionMet).isTrue() + assertThat(areStarted(conditions)).containsExactly(false, false, true).inOrder() + + // Condition 3 becomes false, so we should now only be monitoring condition 2 + conditions[2].setValue(false) + assertThat(combinedCondition.isConditionMet).isTrue() + assertThat(areStarted(conditions)).containsExactly(false, true, false).inOrder() + } + + @Test + fun testRemovingCallbackWillStopMonitoringAllConditions() = runSelfCancelingTest { + val conditions = + 0.rangeTo(2) + .map { + FakeCondition( + scope = this, + initialValue = false, + startStrategy = START_WHEN_NEEDED + ) + } + .toList() + + val combinedCondition = + CombinedCondition(scope = this, conditions = conditions, operand = Evaluator.OP_OR) + + val callback = Condition.Callback {} + combinedCondition.addCallback(callback) + assertThat(areStarted(conditions)).containsExactly(true, true, true) + + combinedCondition.removeCallback(callback) + assertThat(areStarted(conditions)).containsExactly(false, false, false) + } + + @Test + fun testOverridingConditionSkipsOtherConditions() = runSelfCancelingTest { + val startWhenNeededCondition = + FakeCondition(scope = this, initialValue = false, startStrategy = START_WHEN_NEEDED) + val eagerCondition = + FakeCondition(scope = this, initialValue = false, startStrategy = START_EAGERLY) + val lazyCondition = + FakeCondition(scope = this, initialValue = false, startStrategy = START_LAZILY) + val overridingCondition1 = + FakeCondition(scope = this, initialValue = null, overriding = true) + val overridingCondition2 = + FakeCondition(scope = this, initialValue = null, overriding = true) + + val combinedCondition = + CombinedCondition( + scope = this, + conditions = + listOf( + eagerCondition, + overridingCondition1, + lazyCondition, + startWhenNeededCondition, + overridingCondition2 + ), + operand = Evaluator.OP_OR + ) + + val callback = Condition.Callback {} + combinedCondition.addCallback(callback) + assertThat(combinedCondition.isConditionMet).isFalse() + assertThat(eagerCondition.started).isTrue() + assertThat(lazyCondition.started).isTrue() + assertThat(startWhenNeededCondition.started).isTrue() + assertThat(overridingCondition1.started).isTrue() + + // Overriding condition is true, so we should stop monitoring all other conditions + overridingCondition1.setValue(true) + assertThat(combinedCondition.isConditionMet).isTrue() + assertThat(eagerCondition.started).isFalse() + assertThat(lazyCondition.started).isFalse() + assertThat(startWhenNeededCondition.started).isFalse() + assertThat(overridingCondition1.started).isTrue() + assertThat(overridingCondition2.started).isFalse() + + // Overriding condition is false, so we should only monitor other overriding conditions + overridingCondition1.setValue(false) + eagerCondition.setValue(true) + assertThat(combinedCondition.isConditionMet).isFalse() + assertThat(eagerCondition.started).isFalse() + assertThat(lazyCondition.started).isFalse() + assertThat(startWhenNeededCondition.started).isFalse() + assertThat(overridingCondition1.started).isTrue() + assertThat(overridingCondition2.started).isTrue() + + // Second overriding condition is true, condition 1 is still true + overridingCondition2.setValue(true) + assertThat(combinedCondition.isConditionMet).isTrue() + assertThat(eagerCondition.started).isFalse() + assertThat(lazyCondition.started).isFalse() + assertThat(startWhenNeededCondition.started).isFalse() + assertThat(overridingCondition1.started).isFalse() + assertThat(overridingCondition2.started).isTrue() + + // Overriding condition is cleared, condition 1 is still true + overridingCondition1.setValue(null) + overridingCondition2.setValue(null) + assertThat(combinedCondition.isConditionMet).isTrue() + assertThat(eagerCondition.started).isTrue() + assertThat(lazyCondition.started).isFalse() + assertThat(startWhenNeededCondition.started).isFalse() + assertThat(overridingCondition1.started).isTrue() + assertThat(overridingCondition2.started).isTrue() + } + + @Test + fun testAndOperatorCorrectlyHandlesUnknownValues() = runSelfCancelingTest { + val conditions = + 0.rangeTo(2) + .map { + FakeCondition(scope = this, initialValue = false, startStrategy = START_EAGERLY) + } + .toList() + + val combinedCondition = + CombinedCondition(scope = this, conditions = conditions, operand = Evaluator.OP_AND) + + val callback = Condition.Callback {} + combinedCondition.addCallback(callback) + + conditions[0].setValue(null) + conditions[1].setValue(true) + conditions[2].setValue(false) + assertThat(combinedCondition.isConditionMet).isFalse() + assertThat(combinedCondition.isConditionSet).isTrue() + + // The condition should not be set since the value is unknown + conditions[2].setValue(true) + assertThat(combinedCondition.isConditionMet).isFalse() + assertThat(combinedCondition.isConditionSet).isFalse() + + conditions[0].setValue(true) + assertThat(combinedCondition.isConditionMet).isTrue() + assertThat(combinedCondition.isConditionSet).isTrue() + } + + @Test + fun testOrOperatorCorrectlyHandlesUnknownValues() = runSelfCancelingTest { + val conditions = + 0.rangeTo(2) + .map { + FakeCondition(scope = this, initialValue = false, startStrategy = START_EAGERLY) + } + .toList() + + val combinedCondition = + CombinedCondition(scope = this, conditions = conditions, operand = Evaluator.OP_OR) + + val callback = Condition.Callback {} + combinedCondition.addCallback(callback) + + conditions[0].setValue(null) + conditions[1].setValue(true) + conditions[2].setValue(false) + assertThat(combinedCondition.isConditionMet).isTrue() + assertThat(combinedCondition.isConditionSet).isTrue() + + conditions[1].setValue(false) + assertThat(combinedCondition.isConditionMet).isFalse() + // The condition should not be set since the value is unknown + assertThat(combinedCondition.isConditionSet).isFalse() + } + + @Test + fun testEmptyConditions() = runSelfCancelingTest { + for (operand in intArrayOf(Evaluator.OP_OR, Evaluator.OP_AND)) { + val combinedCondition = + CombinedCondition( + scope = this, + conditions = emptyList(), + operand = operand, + ) + + val callback = Condition.Callback {} + combinedCondition.addCallback(callback) + assertThat(combinedCondition.isConditionMet).isFalse() + assertThat(combinedCondition.isConditionSet).isFalse() + } + } + + private fun areStarted(conditions: List<FakeCondition>): List<Boolean> { + return conditions.map { it.started } + } + + /** + * Executes the given block of execution within the scope of a dedicated [CoroutineScope] which + * is then automatically canceled and cleaned-up. + */ + private fun runSelfCancelingTest( + block: suspend CoroutineScope.() -> Unit, + ) = + runBlocking(IMMEDIATE) { + val scope = CoroutineScope(coroutineContext + Job()) + block(scope) + scope.cancel() + } + + companion object { + private val IMMEDIATE = Dispatchers.Main.immediate + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt index 2b4a7fb4803b..0a8210d6b319 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt @@ -32,7 +32,7 @@ class ConditionExtensionsTest : SysuiTestCase() { fun flowInitiallyTrue() = testScope.runTest { val flow = flowOf(true) - val condition = flow.toCondition(this) + val condition = flow.toCondition(scope = this, Condition.START_EAGERLY) runCurrent() assertThat(condition.isConditionSet).isFalse() @@ -47,7 +47,7 @@ class ConditionExtensionsTest : SysuiTestCase() { fun flowInitiallyFalse() = testScope.runTest { val flow = flowOf(false) - val condition = flow.toCondition(this) + val condition = flow.toCondition(scope = this, Condition.START_EAGERLY) runCurrent() assertThat(condition.isConditionSet).isFalse() @@ -62,7 +62,7 @@ class ConditionExtensionsTest : SysuiTestCase() { fun emptyFlowWithNoInitialValue() = testScope.runTest { val flow = emptyFlow<Boolean>() - val condition = flow.toCondition(this) + val condition = flow.toCondition(scope = this, Condition.START_EAGERLY) condition.start() runCurrent() @@ -74,7 +74,12 @@ class ConditionExtensionsTest : SysuiTestCase() { fun emptyFlowWithInitialValueOfTrue() = testScope.runTest { val flow = emptyFlow<Boolean>() - val condition = flow.toCondition(scope = this, initialValue = true) + val condition = + flow.toCondition( + scope = this, + strategy = Condition.START_EAGERLY, + initialValue = true + ) condition.start() runCurrent() @@ -86,7 +91,12 @@ class ConditionExtensionsTest : SysuiTestCase() { fun emptyFlowWithInitialValueOfFalse() = testScope.runTest { val flow = emptyFlow<Boolean>() - val condition = flow.toCondition(scope = this, initialValue = false) + val condition = + flow.toCondition( + scope = this, + strategy = Condition.START_EAGERLY, + initialValue = false + ) condition.start() runCurrent() @@ -98,7 +108,7 @@ class ConditionExtensionsTest : SysuiTestCase() { fun conditionUpdatesWhenFlowEmitsNewValue() = testScope.runTest { val flow = MutableStateFlow(false) - val condition = flow.toCondition(this) + val condition = flow.toCondition(scope = this, strategy = Condition.START_EAGERLY) condition.start() runCurrent() @@ -120,7 +130,7 @@ class ConditionExtensionsTest : SysuiTestCase() { fun stoppingConditionUnsubscribesFromFlow() = testScope.runTest { val flow = MutableSharedFlow<Boolean>() - val condition = flow.toCondition(this) + val condition = flow.toCondition(scope = this, strategy = Condition.START_EAGERLY) runCurrent() assertThat(flow.subscriptionCount.value).isEqualTo(0) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java index aa1636d8a030..de5824d1f463 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java @@ -39,12 +39,15 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.util.Arrays; import java.util.HashSet; +import kotlinx.coroutines.CoroutineScope; + @SmallTest @RunWith(AndroidTestingRunner.class) public class ConditionMonitorTest extends SysuiTestCase { @@ -54,15 +57,18 @@ public class ConditionMonitorTest extends SysuiTestCase { private HashSet<Condition> mConditions; private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock()); + @Mock + private CoroutineScope mScope; + private Monitor mConditionMonitor; @Before public void setup() { MockitoAnnotations.initMocks(this); - mCondition1 = spy(new FakeCondition()); - mCondition2 = spy(new FakeCondition()); - mCondition3 = spy(new FakeCondition()); + mCondition1 = spy(new FakeCondition(mScope)); + mCondition2 = spy(new FakeCondition(mScope)); + mCondition3 = spy(new FakeCondition(mScope)); mConditions = new HashSet<>(Arrays.asList(mCondition1, mCondition2, mCondition3)); mConditionMonitor = new Monitor(mExecutor); @@ -396,7 +402,7 @@ public class ConditionMonitorTest extends SysuiTestCase { @Test public void unsetCondition_shouldNotAffectValue() { - final FakeCondition settableCondition = new FakeCondition(null, false); + final FakeCondition settableCondition = new FakeCondition(mScope, null, false); mCondition1.fakeUpdateCondition(true); mCondition2.fakeUpdateCondition(true); mCondition3.fakeUpdateCondition(true); @@ -414,7 +420,7 @@ public class ConditionMonitorTest extends SysuiTestCase { @Test public void setUnsetCondition_shouldAffectValue() { - final FakeCondition settableCondition = new FakeCondition(null, false); + final FakeCondition settableCondition = new FakeCondition(mScope, null, false); mCondition1.fakeUpdateCondition(true); mCondition2.fakeUpdateCondition(true); mCondition3.fakeUpdateCondition(true); @@ -443,7 +449,7 @@ public class ConditionMonitorTest extends SysuiTestCase { @Test public void clearingOverridingCondition_shouldBeExcluded() { - final FakeCondition overridingCondition = new FakeCondition(true, true); + final FakeCondition overridingCondition = new FakeCondition(mScope, true, true); mCondition1.fakeUpdateCondition(false); mCondition2.fakeUpdateCondition(false); mCondition3.fakeUpdateCondition(false); @@ -466,7 +472,7 @@ public class ConditionMonitorTest extends SysuiTestCase { @Test public void settingUnsetOverridingCondition_shouldBeIncluded() { - final FakeCondition overridingCondition = new FakeCondition(null, true); + final FakeCondition overridingCondition = new FakeCondition(mScope, null, true); mCondition1.fakeUpdateCondition(false); mCondition2.fakeUpdateCondition(false); mCondition3.fakeUpdateCondition(false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionTest.java index 8443221e8b7a..6efade9c9361 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionTest.java @@ -34,15 +34,23 @@ import com.android.systemui.SysuiTestCase; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import kotlinx.coroutines.CoroutineScope; @SmallTest @RunWith(AndroidTestingRunner.class) public class ConditionTest extends SysuiTestCase { + @Mock + CoroutineScope mScope; + private FakeCondition mCondition; @Before public void setup() { - mCondition = spy(new FakeCondition()); + MockitoAnnotations.initMocks(this); + mCondition = spy(new FakeCondition(mScope)); } @Test @@ -152,168 +160,4 @@ public class ConditionTest extends SysuiTestCase { mCondition.clearCondition(); assertThat(mCondition.isConditionSet()).isFalse(); } - - @Test - public void combineConditionsWithOr_allFalse_reportsNotMet() { - mCondition.fakeUpdateCondition(false); - - final Condition combinedCondition = mCondition.or( - new FakeCondition(/* initialValue= */ false)); - - final Condition.Callback callback = mock( - Condition.Callback.class); - combinedCondition.addCallback(callback); - - assertThat(combinedCondition.isConditionSet()).isTrue(); - assertThat(combinedCondition.isConditionMet()).isFalse(); - verify(callback, times(1)).onConditionChanged(combinedCondition); - } - - @Test - public void combineConditionsWithOr_allTrue_reportsMet() { - mCondition.fakeUpdateCondition(true); - - final Condition combinedCondition = mCondition.or( - new FakeCondition(/* initialValue= */ true)); - - final Condition.Callback callback = mock( - Condition.Callback.class); - combinedCondition.addCallback(callback); - - assertThat(combinedCondition.isConditionSet()).isTrue(); - assertThat(combinedCondition.isConditionMet()).isTrue(); - verify(callback, times(1)).onConditionChanged(combinedCondition); - } - - @Test - public void combineConditionsWithOr_singleTrue_reportsMet() { - mCondition.fakeUpdateCondition(false); - - final Condition combinedCondition = mCondition.or( - new FakeCondition(/* initialValue= */ true)); - - final Condition.Callback callback = mock( - Condition.Callback.class); - combinedCondition.addCallback(callback); - - assertThat(combinedCondition.isConditionSet()).isTrue(); - assertThat(combinedCondition.isConditionMet()).isTrue(); - verify(callback, times(1)).onConditionChanged(combinedCondition); - } - - @Test - public void combineConditionsWithOr_unknownAndTrue_reportsMet() { - mCondition.fakeUpdateCondition(true); - - // Combine with an unset condition. - final Condition combinedCondition = mCondition.or( - new FakeCondition(/* initialValue= */ null)); - - final Condition.Callback callback = mock( - Condition.Callback.class); - combinedCondition.addCallback(callback); - - assertThat(combinedCondition.isConditionSet()).isTrue(); - assertThat(combinedCondition.isConditionMet()).isTrue(); - verify(callback, times(1)).onConditionChanged(combinedCondition); - } - - @Test - public void combineConditionsWithOr_unknownAndFalse_reportsNotMet() { - mCondition.fakeUpdateCondition(false); - - // Combine with an unset condition. - final Condition combinedCondition = mCondition.or( - new FakeCondition(/* initialValue= */ null)); - - final Condition.Callback callback = mock( - Condition.Callback.class); - combinedCondition.addCallback(callback); - - assertThat(combinedCondition.isConditionSet()).isFalse(); - assertThat(combinedCondition.isConditionMet()).isFalse(); - verify(callback, never()).onConditionChanged(combinedCondition); - } - - @Test - public void combineConditionsWithAnd_allFalse_reportsNotMet() { - mCondition.fakeUpdateCondition(false); - - final Condition combinedCondition = mCondition.and( - new FakeCondition(/* initialValue= */ false)); - - final Condition.Callback callback = mock( - Condition.Callback.class); - combinedCondition.addCallback(callback); - - assertThat(combinedCondition.isConditionSet()).isTrue(); - assertThat(combinedCondition.isConditionMet()).isFalse(); - verify(callback, times(1)).onConditionChanged(combinedCondition); - } - - @Test - public void combineConditionsWithAnd_allTrue_reportsMet() { - mCondition.fakeUpdateCondition(true); - - final Condition combinedCondition = mCondition.and( - new FakeCondition(/* initialValue= */ true)); - - final Condition.Callback callback = mock( - Condition.Callback.class); - combinedCondition.addCallback(callback); - - assertThat(combinedCondition.isConditionSet()).isTrue(); - assertThat(combinedCondition.isConditionMet()).isTrue(); - verify(callback, times(1)).onConditionChanged(combinedCondition); - } - - @Test - public void combineConditionsWithAnd_singleTrue_reportsNotMet() { - mCondition.fakeUpdateCondition(true); - - final Condition combinedCondition = mCondition.and( - new FakeCondition(/* initialValue= */ false)); - - final Condition.Callback callback = mock( - Condition.Callback.class); - combinedCondition.addCallback(callback); - - assertThat(combinedCondition.isConditionSet()).isTrue(); - assertThat(combinedCondition.isConditionMet()).isFalse(); - verify(callback, times(1)).onConditionChanged(combinedCondition); - } - - @Test - public void combineConditionsWithAnd_unknownAndTrue_reportsNotMet() { - mCondition.fakeUpdateCondition(true); - - // Combine with an unset condition. - final Condition combinedCondition = mCondition.and( - new FakeCondition(/* initialValue= */ null)); - - final Condition.Callback callback = mock( - Condition.Callback.class); - combinedCondition.addCallback(callback); - - assertThat(combinedCondition.isConditionSet()).isFalse(); - assertThat(combinedCondition.isConditionMet()).isFalse(); - verify(callback, never()).onConditionChanged(combinedCondition); - } - - @Test - public void combineConditionsWithAnd_unknownAndFalse_reportsMet() { - mCondition.fakeUpdateCondition(false); - - // Combine with an unset condition. - final Condition combinedCondition = mCondition.and( - new FakeCondition(/* initialValue= */ null)); - - final Condition.Callback callback = mock( - Condition.Callback.class); - combinedCondition.addCallback(callback); - - assertThat(combinedCondition.isConditionSet()).isTrue(); - assertThat(combinedCondition.isConditionMet()).isFalse(); - verify(callback, times(1)).onConditionChanged(combinedCondition); - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/FakeCondition.java b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/FakeCondition.java index 55a6d39d4644..a325cbf25ffe 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/FakeCondition.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/FakeCondition.java @@ -16,21 +16,19 @@ package com.android.systemui.shared.condition; +import kotlinx.coroutines.CoroutineScope; + /** * Fake implementation of {@link Condition}, and provides a way for tests to update * condition fulfillment. */ public class FakeCondition extends Condition { - FakeCondition() { - super(); - } - - FakeCondition(Boolean initialValue) { - super(initialValue, false); + FakeCondition(CoroutineScope scope) { + super(scope); } - FakeCondition(Boolean initialValue, boolean overriding) { - super(initialValue, overriding); + FakeCondition(CoroutineScope scope, Boolean initialValue, boolean overriding) { + super(scope, initialValue, overriding); } @Override @@ -41,6 +39,11 @@ public class FakeCondition extends Condition { public void stop() { } + @Override + protected int getStartStrategy() { + return START_EAGERLY; + } + public void fakeUpdateCondition(boolean isConditionMet) { updateCondition(isConditionMet); } |