summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Lucas Silva <lusilva@google.com> 2023-05-02 01:15:32 +0000
committer Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> 2023-05-02 01:15:32 +0000
commit270653263206c6140351afc4bbf77632095b9f3f (patch)
tree2042223cda8ac75c03a2584a26eec00af269f97b
parent7c216e6d30730699d63e038a9c73ab30a2539bee (diff)
parentf51f8677e2860cf672cfe3d355f1edc085c2ff59 (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>
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/condition/CombinedCondition.kt172
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/condition/Condition.java58
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/condition/ConditionExtensions.kt32
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/condition/Evaluator.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/process/condition/SystemProcessCondition.java13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java18
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/process/condition/SystemProcessConditionTest.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/condition/CombinedConditionTest.kt450
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt24
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java20
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionTest.java174
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/condition/FakeCondition.java19
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);
}