Merge "Add support for lazy conditions" into udc-dev

This commit is contained in:
Lucas Silva
2023-05-02 00:47:50 +00:00
committed by Android (Google) Code Review
15 changed files with 821 additions and 236 deletions

View File

@@ -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
}
}

View File

@@ -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));
}
/**

View File

@@ -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()
}

View File

@@ -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
}
}

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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());

View File

@@ -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);

View File

@@ -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
}
}

View File

@@ -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)

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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);
}