Merge "Add support for lazy conditions" into udc-dev
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,6 +77,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
|
||||
*/
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,12 +41,11 @@ 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;
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(CoroutineScope scope) {
|
||||
super(scope);
|
||||
}
|
||||
|
||||
FakeCondition(Boolean initialValue) {
|
||||
super(initialValue, false);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user