/* * 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): List { 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 } }