Merge "Add support for lazy conditions" into udc-dev
This commit is contained in:
@@ -16,27 +16,179 @@
|
|||||||
|
|
||||||
package com.android.systemui.shared.condition
|
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
|
* 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(
|
constructor(
|
||||||
|
private val scope: CoroutineScope,
|
||||||
private val conditions: Collection<Condition>,
|
private val conditions: Collection<Condition>,
|
||||||
@Evaluator.ConditionOperand private val operand: Int
|
@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() {
|
override fun start() {
|
||||||
onConditionChanged(this)
|
job =
|
||||||
conditions.forEach { it.addCallback(this) }
|
scope.launch {
|
||||||
}
|
val groupedConditions = conditions.groupBy { it.isOverridingCondition }
|
||||||
|
|
||||||
override fun onConditionChanged(condition: Condition) {
|
lazilyEvaluate(
|
||||||
Evaluator.evaluate(conditions, operand)?.also { value -> updateCondition(value) }
|
conditions = groupedConditions.getOrDefault(true, emptyList()),
|
||||||
?: clearCondition()
|
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() {
|
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 android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.IntDef;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.lifecycle.Lifecycle;
|
import androidx.lifecycle.Lifecycle;
|
||||||
import androidx.lifecycle.LifecycleEventObserver;
|
import androidx.lifecycle.LifecycleEventObserver;
|
||||||
import androidx.lifecycle.LifecycleOwner;
|
import androidx.lifecycle.LifecycleOwner;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@@ -30,6 +33,8 @@ import java.util.Collection;
|
|||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
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
|
* Base class for a condition that needs to be fulfilled in order for {@link Monitor} to inform
|
||||||
* its callbacks.
|
* its callbacks.
|
||||||
@@ -39,24 +44,27 @@ public abstract class Condition {
|
|||||||
|
|
||||||
private final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>();
|
private final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>();
|
||||||
private final boolean mOverriding;
|
private final boolean mOverriding;
|
||||||
|
private final CoroutineScope mScope;
|
||||||
private Boolean mIsConditionMet;
|
private Boolean mIsConditionMet;
|
||||||
private boolean mStarted = false;
|
private boolean mStarted = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* By default, conditions have an initial value of false and are not overriding.
|
* By default, conditions have an initial value of false and are not overriding.
|
||||||
*/
|
*/
|
||||||
public Condition() {
|
public Condition(CoroutineScope scope) {
|
||||||
this(false, false);
|
this(scope, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor for specifying initial state and overriding condition attribute.
|
* Constructor for specifying initial state and overriding condition attribute.
|
||||||
|
*
|
||||||
* @param initialConditionMet Initial state of the condition.
|
* @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;
|
mIsConditionMet = initialConditionMet;
|
||||||
mOverriding = overriding;
|
mOverriding = overriding;
|
||||||
|
mScope = scope;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -69,6 +77,29 @@ public abstract class Condition {
|
|||||||
*/
|
*/
|
||||||
protected abstract void stop();
|
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
|
* 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
|
* Returns whether the condition is set. This method should be consulted to understand the
|
||||||
* value of {@link #isConditionMet()}.
|
* value of {@link #isConditionMet()}.
|
||||||
|
*
|
||||||
* @return {@code true} if value is present, {@code false} otherwise.
|
* @return {@code true} if value is present, {@code false} otherwise.
|
||||||
*/
|
*/
|
||||||
public boolean isConditionSet() {
|
public boolean isConditionSet() {
|
||||||
@@ -210,17 +242,18 @@ public abstract class Condition {
|
|||||||
* conditions are true.
|
* conditions are true.
|
||||||
*/
|
*/
|
||||||
public Condition and(@NonNull Collection<Condition> others) {
|
public Condition and(@NonNull Collection<Condition> others) {
|
||||||
final List<Condition> conditions = new ArrayList<>(others);
|
final List<Condition> conditions = new ArrayList<>();
|
||||||
conditions.add(this);
|
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
|
* Creates a new condition which will only be true when both this condition and the provided
|
||||||
* condition is true.
|
* condition is true.
|
||||||
*/
|
*/
|
||||||
public Condition and(@NonNull Condition other) {
|
public Condition and(@NonNull Condition... others) {
|
||||||
return new CombinedCondition(Arrays.asList(this, other), Evaluator.OP_AND);
|
return and(Arrays.asList(others));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -228,17 +261,18 @@ public abstract class Condition {
|
|||||||
* provided conditions are true.
|
* provided conditions are true.
|
||||||
*/
|
*/
|
||||||
public Condition or(@NonNull Collection<Condition> others) {
|
public Condition or(@NonNull Collection<Condition> others) {
|
||||||
final List<Condition> conditions = new ArrayList<>(others);
|
final List<Condition> conditions = new ArrayList<>();
|
||||||
conditions.add(this);
|
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
|
* Creates a new condition which will only be true when either this condition or the provided
|
||||||
* condition is true.
|
* condition is true.
|
||||||
*/
|
*/
|
||||||
public Condition or(@NonNull Condition other) {
|
public Condition or(@NonNull Condition... others) {
|
||||||
return new CombinedCondition(Arrays.asList(this, other), Evaluator.OP_OR);
|
return or(Arrays.asList(others));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,14 +1,22 @@
|
|||||||
package com.android.systemui.shared.condition
|
package com.android.systemui.shared.condition
|
||||||
|
|
||||||
|
import com.android.systemui.shared.condition.Condition.StartStrategy
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.callbackFlow
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
/** Converts a boolean flow to a [Condition] object which can be used with a [Monitor] */
|
/** Converts a boolean flow to a [Condition] object which can be used with a [Monitor] */
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun Flow<Boolean>.toCondition(scope: CoroutineScope, initialValue: Boolean? = null): Condition {
|
fun Flow<Boolean>.toCondition(
|
||||||
return object : Condition(initialValue, false) {
|
scope: CoroutineScope,
|
||||||
|
@StartStrategy strategy: Int,
|
||||||
|
initialValue: Boolean? = null
|
||||||
|
): Condition {
|
||||||
|
return object : Condition(scope, initialValue, false) {
|
||||||
var job: Job? = null
|
var job: Job? = null
|
||||||
|
|
||||||
override fun start() {
|
override fun start() {
|
||||||
@@ -19,5 +27,25 @@ fun Flow<Boolean>.toCondition(scope: CoroutineScope, initialValue: Boolean? = nu
|
|||||||
job?.cancel()
|
job?.cancel()
|
||||||
job = null
|
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
|
* Helper for evaluating a collection of [Condition] objects with a given
|
||||||
* [Evaluator.ConditionOperand]
|
* [Evaluator.ConditionOperand]
|
||||||
*/
|
*/
|
||||||
internal object Evaluator {
|
object Evaluator {
|
||||||
/** Operands for combining multiple conditions together */
|
/** Operands for combining multiple conditions together */
|
||||||
@Retention(AnnotationRetention.SOURCE)
|
@Retention(AnnotationRetention.SOURCE)
|
||||||
@IntDef(value = [OP_AND, OP_OR])
|
@IntDef(value = [OP_AND, OP_OR])
|
||||||
@@ -70,15 +70,31 @@ internal object Evaluator {
|
|||||||
fun evaluate(conditions: Collection<Condition>, @ConditionOperand operand: Int): Boolean? {
|
fun evaluate(conditions: Collection<Condition>, @ConditionOperand operand: Int): Boolean? {
|
||||||
if (conditions.isEmpty()) return null
|
if (conditions.isEmpty()) return null
|
||||||
// If there are overriding conditions with values set, they take precedence.
|
// If there are overriding conditions with values set, they take precedence.
|
||||||
val targetConditions =
|
val values: Collection<Boolean?> =
|
||||||
conditions
|
conditions
|
||||||
.filter { it.isConditionSet && it.isOverridingCondition }
|
.filter { it.isConditionSet && it.isOverridingCondition }
|
||||||
.ifEmpty { conditions }
|
.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) {
|
return when (operand) {
|
||||||
OP_AND ->
|
OP_AND -> threeValuedAndOrOr(values = values, returnValueIfAnyMatches = false)
|
||||||
threeValuedAndOrOr(conditions = targetConditions, returnValueIfAnyMatches = false)
|
OP_OR -> threeValuedAndOrOr(values = values, returnValueIfAnyMatches = true)
|
||||||
OP_OR ->
|
|
||||||
threeValuedAndOrOr(conditions = targetConditions, returnValueIfAnyMatches = true)
|
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,16 +106,16 @@ internal object Evaluator {
|
|||||||
* any value is true.
|
* any value is true.
|
||||||
*/
|
*/
|
||||||
private fun threeValuedAndOrOr(
|
private fun threeValuedAndOrOr(
|
||||||
conditions: Collection<Condition>,
|
values: Collection<Boolean?>,
|
||||||
returnValueIfAnyMatches: Boolean
|
returnValueIfAnyMatches: Boolean
|
||||||
): Boolean? {
|
): Boolean? {
|
||||||
var hasUnknown = false
|
var hasUnknown = false
|
||||||
for (condition in conditions) {
|
for (value in values) {
|
||||||
if (!condition.isConditionSet) {
|
if (value == null) {
|
||||||
hasUnknown = true
|
hasUnknown = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (condition.isConditionMet == returnValueIfAnyMatches) {
|
if (value == returnValueIfAnyMatches) {
|
||||||
return returnValueIfAnyMatches
|
return returnValueIfAnyMatches
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,11 +18,14 @@ package com.android.systemui.dreams.conditions;
|
|||||||
|
|
||||||
import com.android.internal.app.AssistUtils;
|
import com.android.internal.app.AssistUtils;
|
||||||
import com.android.internal.app.IVisualQueryDetectionAttentionListener;
|
import com.android.internal.app.IVisualQueryDetectionAttentionListener;
|
||||||
|
import com.android.systemui.dagger.qualifiers.Application;
|
||||||
import com.android.systemui.dreams.DreamOverlayStateController;
|
import com.android.systemui.dreams.DreamOverlayStateController;
|
||||||
import com.android.systemui.shared.condition.Condition;
|
import com.android.systemui.shared.condition.Condition;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineScope;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link AssistantAttentionCondition} provides a signal when assistant has the user's attention.
|
* {@link AssistantAttentionCondition} provides a signal when assistant has the user's attention.
|
||||||
*/
|
*/
|
||||||
@@ -58,8 +61,10 @@ public class AssistantAttentionCondition extends Condition {
|
|||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public AssistantAttentionCondition(
|
public AssistantAttentionCondition(
|
||||||
|
@Application CoroutineScope scope,
|
||||||
DreamOverlayStateController dreamOverlayStateController,
|
DreamOverlayStateController dreamOverlayStateController,
|
||||||
AssistUtils assistUtils) {
|
AssistUtils assistUtils) {
|
||||||
|
super(scope);
|
||||||
mDreamOverlayStateController = dreamOverlayStateController;
|
mDreamOverlayStateController = dreamOverlayStateController;
|
||||||
mAssistUtils = assistUtils;
|
mAssistUtils = assistUtils;
|
||||||
}
|
}
|
||||||
@@ -75,6 +80,11 @@ public class AssistantAttentionCondition extends Condition {
|
|||||||
mDreamOverlayStateController.removeCallback(mCallback);
|
mDreamOverlayStateController.removeCallback(mCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getStartStrategy() {
|
||||||
|
return START_EAGERLY;
|
||||||
|
}
|
||||||
|
|
||||||
private void enableVisualQueryDetection() {
|
private void enableVisualQueryDetection() {
|
||||||
if (mEnabled) {
|
if (mEnabled) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -19,19 +19,20 @@ import android.app.DreamManager;
|
|||||||
|
|
||||||
import com.android.keyguard.KeyguardUpdateMonitor;
|
import com.android.keyguard.KeyguardUpdateMonitor;
|
||||||
import com.android.keyguard.KeyguardUpdateMonitorCallback;
|
import com.android.keyguard.KeyguardUpdateMonitorCallback;
|
||||||
|
import com.android.systemui.dagger.qualifiers.Application;
|
||||||
import com.android.systemui.shared.condition.Condition;
|
import com.android.systemui.shared.condition.Condition;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineScope;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link DreamCondition} provides a signal when a dream begins and ends.
|
* {@link DreamCondition} provides a signal when a dream begins and ends.
|
||||||
*/
|
*/
|
||||||
public class DreamCondition extends Condition {
|
public class DreamCondition extends Condition {
|
||||||
private final DreamManager mDreamManager;
|
private final DreamManager mDreamManager;
|
||||||
|
|
||||||
private final KeyguardUpdateMonitor mUpdateMonitor;
|
private final KeyguardUpdateMonitor mUpdateMonitor;
|
||||||
|
|
||||||
|
|
||||||
private final KeyguardUpdateMonitorCallback mUpdateCallback =
|
private final KeyguardUpdateMonitorCallback mUpdateCallback =
|
||||||
new KeyguardUpdateMonitorCallback() {
|
new KeyguardUpdateMonitorCallback() {
|
||||||
@Override
|
@Override
|
||||||
@@ -41,7 +42,11 @@ public class DreamCondition extends Condition {
|
|||||||
};
|
};
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public DreamCondition(DreamManager dreamManager, KeyguardUpdateMonitor monitor) {
|
public DreamCondition(
|
||||||
|
@Application CoroutineScope scope,
|
||||||
|
DreamManager dreamManager,
|
||||||
|
KeyguardUpdateMonitor monitor) {
|
||||||
|
super(scope);
|
||||||
mDreamManager = dreamManager;
|
mDreamManager = dreamManager;
|
||||||
mUpdateMonitor = monitor;
|
mUpdateMonitor = monitor;
|
||||||
}
|
}
|
||||||
@@ -56,4 +61,9 @@ public class DreamCondition extends Condition {
|
|||||||
protected void stop() {
|
protected void stop() {
|
||||||
mUpdateMonitor.removeCallback(mUpdateCallback);
|
mUpdateMonitor.removeCallback(mUpdateCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getStartStrategy() {
|
||||||
|
return START_EAGERLY;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,11 +16,14 @@
|
|||||||
|
|
||||||
package com.android.systemui.process.condition;
|
package com.android.systemui.process.condition;
|
||||||
|
|
||||||
|
import com.android.systemui.dagger.qualifiers.Application;
|
||||||
import com.android.systemui.process.ProcessWrapper;
|
import com.android.systemui.process.ProcessWrapper;
|
||||||
import com.android.systemui.shared.condition.Condition;
|
import com.android.systemui.shared.condition.Condition;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineScope;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link SystemProcessCondition} checks to make sure the current process is being ran by the
|
* {@link SystemProcessCondition} checks to make sure the current process is being ran by the
|
||||||
* System User.
|
* System User.
|
||||||
@@ -29,8 +32,9 @@ public class SystemProcessCondition extends Condition {
|
|||||||
private final ProcessWrapper mProcessWrapper;
|
private final ProcessWrapper mProcessWrapper;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public SystemProcessCondition(ProcessWrapper processWrapper) {
|
public SystemProcessCondition(@Application CoroutineScope scope,
|
||||||
super();
|
ProcessWrapper processWrapper) {
|
||||||
|
super(scope);
|
||||||
mProcessWrapper = processWrapper;
|
mProcessWrapper = processWrapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,4 +46,9 @@ public class SystemProcessCondition extends Condition {
|
|||||||
@Override
|
@Override
|
||||||
protected void stop() {
|
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.Mock;
|
||||||
import org.mockito.MockitoAnnotations;
|
import org.mockito.MockitoAnnotations;
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineScope;
|
||||||
|
|
||||||
@SmallTest
|
@SmallTest
|
||||||
@RunWith(AndroidTestingRunner.class)
|
@RunWith(AndroidTestingRunner.class)
|
||||||
public class AssistantAttentionConditionTest extends SysuiTestCase {
|
public class AssistantAttentionConditionTest extends SysuiTestCase {
|
||||||
@@ -51,6 +53,8 @@ public class AssistantAttentionConditionTest extends SysuiTestCase {
|
|||||||
AssistUtils mAssistUtils;
|
AssistUtils mAssistUtils;
|
||||||
@Mock
|
@Mock
|
||||||
DreamOverlayStateController mDreamOverlayStateController;
|
DreamOverlayStateController mDreamOverlayStateController;
|
||||||
|
@Mock
|
||||||
|
CoroutineScope mScope;
|
||||||
|
|
||||||
private AssistantAttentionCondition mAssistantAttentionCondition;
|
private AssistantAttentionCondition mAssistantAttentionCondition;
|
||||||
|
|
||||||
@@ -59,7 +63,7 @@ public class AssistantAttentionConditionTest extends SysuiTestCase {
|
|||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
|
|
||||||
mAssistantAttentionCondition =
|
mAssistantAttentionCondition =
|
||||||
new AssistantAttentionCondition(mDreamOverlayStateController, mAssistUtils);
|
new AssistantAttentionCondition(mScope, mDreamOverlayStateController, mAssistUtils);
|
||||||
// Adding a callback also starts the condition.
|
// Adding a callback also starts the condition.
|
||||||
mAssistantAttentionCondition.addCallback(mCallback);
|
mAssistantAttentionCondition.addCallback(mCallback);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import static org.mockito.Mockito.verify;
|
|||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import android.app.DreamManager;
|
import android.app.DreamManager;
|
||||||
import android.content.Context;
|
|
||||||
import android.testing.AndroidTestingRunner;
|
import android.testing.AndroidTestingRunner;
|
||||||
|
|
||||||
import androidx.test.filters.SmallTest;
|
import androidx.test.filters.SmallTest;
|
||||||
@@ -42,12 +41,11 @@ import org.mockito.ArgumentCaptor;
|
|||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.MockitoAnnotations;
|
import org.mockito.MockitoAnnotations;
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineScope;
|
||||||
|
|
||||||
@SmallTest
|
@SmallTest
|
||||||
@RunWith(AndroidTestingRunner.class)
|
@RunWith(AndroidTestingRunner.class)
|
||||||
public class DreamConditionTest extends SysuiTestCase {
|
public class DreamConditionTest extends SysuiTestCase {
|
||||||
@Mock
|
|
||||||
Context mContext;
|
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
Condition.Callback mCallback;
|
Condition.Callback mCallback;
|
||||||
|
|
||||||
@@ -57,6 +55,9 @@ public class DreamConditionTest extends SysuiTestCase {
|
|||||||
@Mock
|
@Mock
|
||||||
KeyguardUpdateMonitor mKeyguardUpdateMonitor;
|
KeyguardUpdateMonitor mKeyguardUpdateMonitor;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
CoroutineScope mScope;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setup() {
|
public void setup() {
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
@@ -68,7 +69,8 @@ public class DreamConditionTest extends SysuiTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testInitialDreamingState() {
|
public void testInitialDreamingState() {
|
||||||
when(mDreamManager.isDreaming()).thenReturn(true);
|
when(mDreamManager.isDreaming()).thenReturn(true);
|
||||||
final DreamCondition condition = new DreamCondition(mDreamManager, mKeyguardUpdateMonitor);
|
final DreamCondition condition = new DreamCondition(mScope, mDreamManager,
|
||||||
|
mKeyguardUpdateMonitor);
|
||||||
condition.addCallback(mCallback);
|
condition.addCallback(mCallback);
|
||||||
|
|
||||||
verify(mCallback).onConditionChanged(eq(condition));
|
verify(mCallback).onConditionChanged(eq(condition));
|
||||||
@@ -81,7 +83,8 @@ public class DreamConditionTest extends SysuiTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testInitialNonDreamingState() {
|
public void testInitialNonDreamingState() {
|
||||||
when(mDreamManager.isDreaming()).thenReturn(false);
|
when(mDreamManager.isDreaming()).thenReturn(false);
|
||||||
final DreamCondition condition = new DreamCondition(mDreamManager, mKeyguardUpdateMonitor);
|
final DreamCondition condition = new DreamCondition(mScope, mDreamManager,
|
||||||
|
mKeyguardUpdateMonitor);
|
||||||
condition.addCallback(mCallback);
|
condition.addCallback(mCallback);
|
||||||
|
|
||||||
verify(mCallback, never()).onConditionChanged(eq(condition));
|
verify(mCallback, never()).onConditionChanged(eq(condition));
|
||||||
@@ -96,7 +99,8 @@ public class DreamConditionTest extends SysuiTestCase {
|
|||||||
final ArgumentCaptor<KeyguardUpdateMonitorCallback> callbackCaptor =
|
final ArgumentCaptor<KeyguardUpdateMonitorCallback> callbackCaptor =
|
||||||
ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class);
|
ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class);
|
||||||
when(mDreamManager.isDreaming()).thenReturn(true);
|
when(mDreamManager.isDreaming()).thenReturn(true);
|
||||||
final DreamCondition condition = new DreamCondition(mDreamManager, mKeyguardUpdateMonitor);
|
final DreamCondition condition = new DreamCondition(mScope, mDreamManager,
|
||||||
|
mKeyguardUpdateMonitor);
|
||||||
condition.addCallback(mCallback);
|
condition.addCallback(mCallback);
|
||||||
verify(mKeyguardUpdateMonitor).registerCallback(callbackCaptor.capture());
|
verify(mKeyguardUpdateMonitor).registerCallback(callbackCaptor.capture());
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ import org.junit.runner.RunWith;
|
|||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.MockitoAnnotations;
|
import org.mockito.MockitoAnnotations;
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineScope;
|
||||||
|
|
||||||
@RunWith(AndroidTestingRunner.class)
|
@RunWith(AndroidTestingRunner.class)
|
||||||
@TestableLooper.RunWithLooper
|
@TestableLooper.RunWithLooper
|
||||||
@SmallTest
|
@SmallTest
|
||||||
@@ -47,6 +49,9 @@ public class SystemProcessConditionTest extends SysuiTestCase {
|
|||||||
@Mock
|
@Mock
|
||||||
Monitor.Callback mCallback;
|
Monitor.Callback mCallback;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
CoroutineScope mScope;
|
||||||
|
|
||||||
private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
|
private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
@@ -61,7 +66,7 @@ public class SystemProcessConditionTest extends SysuiTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testConditionFailsWithNonSystemProcess() {
|
public void testConditionFailsWithNonSystemProcess() {
|
||||||
|
|
||||||
final Condition condition = new SystemProcessCondition(mProcessWrapper);
|
final Condition condition = new SystemProcessCondition(mScope, mProcessWrapper);
|
||||||
when(mProcessWrapper.isSystemUser()).thenReturn(false);
|
when(mProcessWrapper.isSystemUser()).thenReturn(false);
|
||||||
|
|
||||||
final Monitor monitor = new Monitor(mExecutor);
|
final Monitor monitor = new Monitor(mExecutor);
|
||||||
@@ -82,7 +87,7 @@ public class SystemProcessConditionTest extends SysuiTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testConditionSucceedsWithSystemProcess() {
|
public void testConditionSucceedsWithSystemProcess() {
|
||||||
|
|
||||||
final Condition condition = new SystemProcessCondition(mProcessWrapper);
|
final Condition condition = new SystemProcessCondition(mScope, mProcessWrapper);
|
||||||
when(mProcessWrapper.isSystemUser()).thenReturn(true);
|
when(mProcessWrapper.isSystemUser()).thenReturn(true);
|
||||||
|
|
||||||
final Monitor monitor = new Monitor(mExecutor);
|
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() =
|
fun flowInitiallyTrue() =
|
||||||
testScope.runTest {
|
testScope.runTest {
|
||||||
val flow = flowOf(true)
|
val flow = flowOf(true)
|
||||||
val condition = flow.toCondition(this)
|
val condition = flow.toCondition(scope = this, Condition.START_EAGERLY)
|
||||||
|
|
||||||
runCurrent()
|
runCurrent()
|
||||||
assertThat(condition.isConditionSet).isFalse()
|
assertThat(condition.isConditionSet).isFalse()
|
||||||
@@ -47,7 +47,7 @@ class ConditionExtensionsTest : SysuiTestCase() {
|
|||||||
fun flowInitiallyFalse() =
|
fun flowInitiallyFalse() =
|
||||||
testScope.runTest {
|
testScope.runTest {
|
||||||
val flow = flowOf(false)
|
val flow = flowOf(false)
|
||||||
val condition = flow.toCondition(this)
|
val condition = flow.toCondition(scope = this, Condition.START_EAGERLY)
|
||||||
|
|
||||||
runCurrent()
|
runCurrent()
|
||||||
assertThat(condition.isConditionSet).isFalse()
|
assertThat(condition.isConditionSet).isFalse()
|
||||||
@@ -62,7 +62,7 @@ class ConditionExtensionsTest : SysuiTestCase() {
|
|||||||
fun emptyFlowWithNoInitialValue() =
|
fun emptyFlowWithNoInitialValue() =
|
||||||
testScope.runTest {
|
testScope.runTest {
|
||||||
val flow = emptyFlow<Boolean>()
|
val flow = emptyFlow<Boolean>()
|
||||||
val condition = flow.toCondition(this)
|
val condition = flow.toCondition(scope = this, Condition.START_EAGERLY)
|
||||||
condition.start()
|
condition.start()
|
||||||
|
|
||||||
runCurrent()
|
runCurrent()
|
||||||
@@ -74,7 +74,12 @@ class ConditionExtensionsTest : SysuiTestCase() {
|
|||||||
fun emptyFlowWithInitialValueOfTrue() =
|
fun emptyFlowWithInitialValueOfTrue() =
|
||||||
testScope.runTest {
|
testScope.runTest {
|
||||||
val flow = emptyFlow<Boolean>()
|
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()
|
condition.start()
|
||||||
|
|
||||||
runCurrent()
|
runCurrent()
|
||||||
@@ -86,7 +91,12 @@ class ConditionExtensionsTest : SysuiTestCase() {
|
|||||||
fun emptyFlowWithInitialValueOfFalse() =
|
fun emptyFlowWithInitialValueOfFalse() =
|
||||||
testScope.runTest {
|
testScope.runTest {
|
||||||
val flow = emptyFlow<Boolean>()
|
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()
|
condition.start()
|
||||||
|
|
||||||
runCurrent()
|
runCurrent()
|
||||||
@@ -98,7 +108,7 @@ class ConditionExtensionsTest : SysuiTestCase() {
|
|||||||
fun conditionUpdatesWhenFlowEmitsNewValue() =
|
fun conditionUpdatesWhenFlowEmitsNewValue() =
|
||||||
testScope.runTest {
|
testScope.runTest {
|
||||||
val flow = MutableStateFlow(false)
|
val flow = MutableStateFlow(false)
|
||||||
val condition = flow.toCondition(this)
|
val condition = flow.toCondition(scope = this, strategy = Condition.START_EAGERLY)
|
||||||
condition.start()
|
condition.start()
|
||||||
|
|
||||||
runCurrent()
|
runCurrent()
|
||||||
@@ -120,7 +130,7 @@ class ConditionExtensionsTest : SysuiTestCase() {
|
|||||||
fun stoppingConditionUnsubscribesFromFlow() =
|
fun stoppingConditionUnsubscribesFromFlow() =
|
||||||
testScope.runTest {
|
testScope.runTest {
|
||||||
val flow = MutableSharedFlow<Boolean>()
|
val flow = MutableSharedFlow<Boolean>()
|
||||||
val condition = flow.toCondition(this)
|
val condition = flow.toCondition(scope = this, strategy = Condition.START_EAGERLY)
|
||||||
runCurrent()
|
runCurrent()
|
||||||
assertThat(flow.subscriptionCount.value).isEqualTo(0)
|
assertThat(flow.subscriptionCount.value).isEqualTo(0)
|
||||||
|
|
||||||
|
|||||||
@@ -39,12 +39,15 @@ import org.junit.Before;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.Mock;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
import org.mockito.MockitoAnnotations;
|
import org.mockito.MockitoAnnotations;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineScope;
|
||||||
|
|
||||||
@SmallTest
|
@SmallTest
|
||||||
@RunWith(AndroidTestingRunner.class)
|
@RunWith(AndroidTestingRunner.class)
|
||||||
public class ConditionMonitorTest extends SysuiTestCase {
|
public class ConditionMonitorTest extends SysuiTestCase {
|
||||||
@@ -54,15 +57,18 @@ public class ConditionMonitorTest extends SysuiTestCase {
|
|||||||
private HashSet<Condition> mConditions;
|
private HashSet<Condition> mConditions;
|
||||||
private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
|
private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private CoroutineScope mScope;
|
||||||
|
|
||||||
private Monitor mConditionMonitor;
|
private Monitor mConditionMonitor;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setup() {
|
public void setup() {
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
|
|
||||||
mCondition1 = spy(new FakeCondition());
|
mCondition1 = spy(new FakeCondition(mScope));
|
||||||
mCondition2 = spy(new FakeCondition());
|
mCondition2 = spy(new FakeCondition(mScope));
|
||||||
mCondition3 = spy(new FakeCondition());
|
mCondition3 = spy(new FakeCondition(mScope));
|
||||||
mConditions = new HashSet<>(Arrays.asList(mCondition1, mCondition2, mCondition3));
|
mConditions = new HashSet<>(Arrays.asList(mCondition1, mCondition2, mCondition3));
|
||||||
|
|
||||||
mConditionMonitor = new Monitor(mExecutor);
|
mConditionMonitor = new Monitor(mExecutor);
|
||||||
@@ -396,7 +402,7 @@ public class ConditionMonitorTest extends SysuiTestCase {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void unsetCondition_shouldNotAffectValue() {
|
public void unsetCondition_shouldNotAffectValue() {
|
||||||
final FakeCondition settableCondition = new FakeCondition(null, false);
|
final FakeCondition settableCondition = new FakeCondition(mScope, null, false);
|
||||||
mCondition1.fakeUpdateCondition(true);
|
mCondition1.fakeUpdateCondition(true);
|
||||||
mCondition2.fakeUpdateCondition(true);
|
mCondition2.fakeUpdateCondition(true);
|
||||||
mCondition3.fakeUpdateCondition(true);
|
mCondition3.fakeUpdateCondition(true);
|
||||||
@@ -414,7 +420,7 @@ public class ConditionMonitorTest extends SysuiTestCase {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void setUnsetCondition_shouldAffectValue() {
|
public void setUnsetCondition_shouldAffectValue() {
|
||||||
final FakeCondition settableCondition = new FakeCondition(null, false);
|
final FakeCondition settableCondition = new FakeCondition(mScope, null, false);
|
||||||
mCondition1.fakeUpdateCondition(true);
|
mCondition1.fakeUpdateCondition(true);
|
||||||
mCondition2.fakeUpdateCondition(true);
|
mCondition2.fakeUpdateCondition(true);
|
||||||
mCondition3.fakeUpdateCondition(true);
|
mCondition3.fakeUpdateCondition(true);
|
||||||
@@ -443,7 +449,7 @@ public class ConditionMonitorTest extends SysuiTestCase {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void clearingOverridingCondition_shouldBeExcluded() {
|
public void clearingOverridingCondition_shouldBeExcluded() {
|
||||||
final FakeCondition overridingCondition = new FakeCondition(true, true);
|
final FakeCondition overridingCondition = new FakeCondition(mScope, true, true);
|
||||||
mCondition1.fakeUpdateCondition(false);
|
mCondition1.fakeUpdateCondition(false);
|
||||||
mCondition2.fakeUpdateCondition(false);
|
mCondition2.fakeUpdateCondition(false);
|
||||||
mCondition3.fakeUpdateCondition(false);
|
mCondition3.fakeUpdateCondition(false);
|
||||||
@@ -466,7 +472,7 @@ public class ConditionMonitorTest extends SysuiTestCase {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void settingUnsetOverridingCondition_shouldBeIncluded() {
|
public void settingUnsetOverridingCondition_shouldBeIncluded() {
|
||||||
final FakeCondition overridingCondition = new FakeCondition(null, true);
|
final FakeCondition overridingCondition = new FakeCondition(mScope, null, true);
|
||||||
mCondition1.fakeUpdateCondition(false);
|
mCondition1.fakeUpdateCondition(false);
|
||||||
mCondition2.fakeUpdateCondition(false);
|
mCondition2.fakeUpdateCondition(false);
|
||||||
mCondition3.fakeUpdateCondition(false);
|
mCondition3.fakeUpdateCondition(false);
|
||||||
|
|||||||
@@ -34,15 +34,23 @@ import com.android.systemui.SysuiTestCase;
|
|||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.MockitoAnnotations;
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineScope;
|
||||||
|
|
||||||
@SmallTest
|
@SmallTest
|
||||||
@RunWith(AndroidTestingRunner.class)
|
@RunWith(AndroidTestingRunner.class)
|
||||||
public class ConditionTest extends SysuiTestCase {
|
public class ConditionTest extends SysuiTestCase {
|
||||||
|
@Mock
|
||||||
|
CoroutineScope mScope;
|
||||||
|
|
||||||
private FakeCondition mCondition;
|
private FakeCondition mCondition;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setup() {
|
public void setup() {
|
||||||
mCondition = spy(new FakeCondition());
|
MockitoAnnotations.initMocks(this);
|
||||||
|
mCondition = spy(new FakeCondition(mScope));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -152,168 +160,4 @@ public class ConditionTest extends SysuiTestCase {
|
|||||||
mCondition.clearCondition();
|
mCondition.clearCondition();
|
||||||
assertThat(mCondition.isConditionSet()).isFalse();
|
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;
|
package com.android.systemui.shared.condition;
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineScope;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fake implementation of {@link Condition}, and provides a way for tests to update
|
* Fake implementation of {@link Condition}, and provides a way for tests to update
|
||||||
* condition fulfillment.
|
* condition fulfillment.
|
||||||
*/
|
*/
|
||||||
public class FakeCondition extends Condition {
|
public class FakeCondition extends Condition {
|
||||||
FakeCondition() {
|
FakeCondition(CoroutineScope scope) {
|
||||||
super();
|
super(scope);
|
||||||
}
|
}
|
||||||
|
|
||||||
FakeCondition(Boolean initialValue) {
|
FakeCondition(CoroutineScope scope, Boolean initialValue, boolean overriding) {
|
||||||
super(initialValue, false);
|
super(scope, initialValue, overriding);
|
||||||
}
|
|
||||||
|
|
||||||
FakeCondition(Boolean initialValue, boolean overriding) {
|
|
||||||
super(initialValue, overriding);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -41,6 +39,11 @@ public class FakeCondition extends Condition {
|
|||||||
public void stop() {
|
public void stop() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getStartStrategy() {
|
||||||
|
return START_EAGERLY;
|
||||||
|
}
|
||||||
|
|
||||||
public void fakeUpdateCondition(boolean isConditionMet) {
|
public void fakeUpdateCondition(boolean isConditionMet) {
|
||||||
updateCondition(isConditionMet);
|
updateCondition(isConditionMet);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user