Loading packages/SystemUI/shared/src/com/android/systemui/shared/condition/CombinedCondition.kt +162 −10 Original line number Diff line number Diff line Loading @@ -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 } } packages/SystemUI/shared/src/com/android/systemui/shared/condition/Condition.java +46 −12 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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. Loading @@ -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. */ protected Condition(Boolean initialConditionMet, boolean overriding) { protected Condition(CoroutineScope scope, Boolean initialConditionMet, boolean overriding) { mIsConditionMet = initialConditionMet; mOverriding = overriding; mScope = scope; } /** Loading @@ -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 */ Loading Loading @@ -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() { Loading Loading @@ -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)); } /** Loading @@ -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)); } /** Loading packages/SystemUI/shared/src/com/android/systemui/shared/condition/ConditionExtensions.kt +30 −2 Original line number Diff line number Diff line 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() { Loading @@ -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() } packages/SystemUI/shared/src/com/android/systemui/shared/condition/Evaluator.kt +26 −10 Original line number Diff line number Diff line Loading @@ -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]) Loading Loading @@ -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 } } Loading @@ -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 } } Loading packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java +10 −0 Original line number Diff line number Diff line Loading @@ -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. */ Loading Loading @@ -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; } Loading @@ -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; Loading Loading
packages/SystemUI/shared/src/com/android/systemui/shared/condition/CombinedCondition.kt +162 −10 Original line number Diff line number Diff line Loading @@ -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 } }
packages/SystemUI/shared/src/com/android/systemui/shared/condition/Condition.java +46 −12 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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. Loading @@ -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. */ protected Condition(Boolean initialConditionMet, boolean overriding) { protected Condition(CoroutineScope scope, Boolean initialConditionMet, boolean overriding) { mIsConditionMet = initialConditionMet; mOverriding = overriding; mScope = scope; } /** Loading @@ -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 */ Loading Loading @@ -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() { Loading Loading @@ -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)); } /** Loading @@ -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)); } /** Loading
packages/SystemUI/shared/src/com/android/systemui/shared/condition/ConditionExtensions.kt +30 −2 Original line number Diff line number Diff line 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() { Loading @@ -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() }
packages/SystemUI/shared/src/com/android/systemui/shared/condition/Evaluator.kt +26 −10 Original line number Diff line number Diff line Loading @@ -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]) Loading Loading @@ -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 } } Loading @@ -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 } } Loading
packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java +10 −0 Original line number Diff line number Diff line Loading @@ -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. */ Loading Loading @@ -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; } Loading @@ -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; Loading