Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9038b06d authored by Lucas Silva's avatar Lucas Silva Committed by Android (Google) Code Review
Browse files

Merge "Add ability to AND and OR conditions together" into tm-qpr-dev

parents 33b7f00a fb212397
Loading
Loading
Loading
Loading
+42 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.util.condition

/**
 * A higher order [Condition] which combines multiple conditions with a specified
 * [Evaluator.ConditionOperand].
 */
internal class CombinedCondition
constructor(
    private val conditions: Collection<Condition>,
    @Evaluator.ConditionOperand private val operand: Int
) : Condition(null, false), Condition.Callback {

    override fun start() {
        onConditionChanged(this)
        conditions.forEach { it.addCallback(this) }
    }

    override fun onConditionChanged(condition: Condition) {
        Evaluator.evaluate(conditions, operand)?.also { value -> updateCondition(value) }
            ?: clearCondition()
    }

    override fun stop() {
        conditions.forEach { it.removeCallback(this) }
    }
}
+39 −0
Original line number Diff line number Diff line
@@ -24,7 +24,10 @@ import org.jetbrains.annotations.NotNull;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

/**
 * Base class for a condition that needs to be fulfilled in order for {@link Monitor} to inform
@@ -180,6 +183,42 @@ public abstract class Condition implements CallbackController<Condition.Callback
        return mTag;
    }

    /**
     * Creates a new condition which will only be true when both this condition and all the provided
     * conditions are true.
     */
    public Condition and(Collection<Condition> others) {
        final List<Condition> conditions = new ArrayList<>(others);
        conditions.add(this);
        return new CombinedCondition(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(Condition other) {
        return new CombinedCondition(Arrays.asList(this, other), Evaluator.OP_AND);
    }

    /**
     * Creates a new condition which will only be true when either this condition or any of the
     * provided conditions are true.
     */
    public Condition or(Collection<Condition> others) {
        final List<Condition> conditions = new ArrayList<>(others);
        conditions.add(this);
        return new CombinedCondition(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(Condition other) {
        return new CombinedCondition(Arrays.asList(this, other), Evaluator.OP_OR);
    }

    /**
     * Callback that receives updates about whether the condition has been fulfilled.
     */
+92 −0
Original line number Diff line number Diff line
package com.android.systemui.util.condition

import android.annotation.IntDef

/**
 * Helper for evaluating a collection of [Condition] objects with a given
 * [Evaluator.ConditionOperand]
 */
internal object Evaluator {
    /** Operands for combining multiple conditions together */
    @Retention(AnnotationRetention.SOURCE)
    @IntDef(value = [OP_AND, OP_OR])
    annotation class ConditionOperand

    /**
     * 3-valued logical AND operand, with handling for unknown values (represented as null)
     *
     * ```
     * +-----+----+---+---+
     * | AND | T  | F | U |
     * +-----+----+---+---+
     * | T   | T  | F | U |
     * | F   | F  | F | F |
     * | U   | U  | F | U |
     * +-----+----+---+---+
     * ```
     */
    const val OP_AND = 0

    /**
     * 3-valued logical OR operand, with handling for unknown values (represented as null)
     *
     * ```
     * +-----+----+---+---+
     * | OR  | T  | F | U |
     * +-----+----+---+---+
     * | T   | T  | T | T |
     * | F   | T  | F | U |
     * | U   | T  | U | U |
     * +-----+----+---+---+
     * ```
     */
    const val OP_OR = 1

    /**
     * Evaluates a set of conditions with a given operand
     *
     * If overriding conditions are present, they take precedence over normal conditions if set.
     *
     * @param conditions The collection of conditions to evaluate. If empty, null is returned.
     * @param operand The operand to use when evaluating.
     * @return Either true or false if the value is known, or null if value is unknown
     */
    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 =
            conditions
                .filter { it.isConditionSet && it.isOverridingCondition }
                .ifEmpty { conditions }
        return when (operand) {
            OP_AND ->
                threeValuedAndOrOr(conditions = targetConditions, returnValueIfAnyMatches = false)
            OP_OR ->
                threeValuedAndOrOr(conditions = targetConditions, returnValueIfAnyMatches = true)
            else -> null
        }
    }

    /**
     * Helper for evaluating 3-valued logical AND/OR.
     *
     * @param returnValueIfAnyMatches AND returns false if any value is false. OR returns true if
     * any value is true.
     */
    private fun threeValuedAndOrOr(
        conditions: Collection<Condition>,
        returnValueIfAnyMatches: Boolean
    ): Boolean? {
        var hasUnknown = false
        for (condition in conditions) {
            if (!condition.isConditionSet) {
                hasUnknown = true
                continue
            }
            if (condition.isConditionMet == returnValueIfAnyMatches) {
                return returnValueIfAnyMatches
            }
        }
        return if (hasUnknown) null else !returnValueIfAnyMatches
    }
}
+12 −21
Original line number Diff line number Diff line
@@ -24,12 +24,10 @@ import com.android.systemui.dagger.qualifiers.Main;
import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.stream.Collectors;

import javax.inject.Inject;

@@ -57,21 +55,10 @@ public class Monitor {
        }

        public void update() {
            // Only consider set conditions.
            final Collection<Condition> setConditions = mSubscription.mConditions.stream()
                    .filter(Condition::isConditionSet).collect(Collectors.toSet());

            // Overriding conditions do not override each other
            final Collection<Condition> overridingConditions = setConditions.stream()
                    .filter(Condition::isOverridingCondition).collect(Collectors.toSet());

            final Collection<Condition> targetCollection = overridingConditions.isEmpty()
                    ? setConditions : overridingConditions;

            final boolean newAllConditionsMet = targetCollection.isEmpty() ? true : targetCollection
                    .stream()
                    .map(Condition::isConditionMet)
                    .allMatch(conditionMet -> conditionMet);
            final Boolean result = Evaluator.INSTANCE.evaluate(mSubscription.mConditions,
                    Evaluator.OP_AND);
            // Consider unknown (null) as true
            final boolean newAllConditionsMet = result == null || result;

            if (mAllConditionsMet != null && newAllConditionsMet == mAllConditionsMet) {
                return;
@@ -109,6 +96,7 @@ public class Monitor {

    /**
     * Registers a callback and the set of conditions to trigger it.
     *
     * @param subscription A {@link Subscription} detailing the desired conditions and callback.
     * @return A {@link Subscription.Token} that can be used to remove the subscription.
     */
@@ -139,6 +127,7 @@ public class Monitor {

    /**
     * Removes a subscription from participating in future callbacks.
     *
     * @param token The {@link Subscription.Token} returned when the {@link Subscription} was
     *              originally added.
     */
@@ -179,7 +168,9 @@ public class Monitor {
        private final Set<Condition> mConditions;
        private final Callback mCallback;

        /** */
        /**
         *
         */
        public Subscription(Set<Condition> conditions, Callback callback) {
            this.mConditions = Collections.unmodifiableSet(conditions);
            this.mCallback = callback;
@@ -209,7 +200,6 @@ public class Monitor {

            /**
             * Default constructor specifying the {@link Callback} for the {@link Subscription}.
             * @param callback
             */
            public Builder(Callback callback) {
                mCallback = callback;
@@ -218,7 +208,7 @@ public class Monitor {

            /**
             * Adds a {@link Condition} to be associated with the {@link Subscription}.
             * @param condition
             *
             * @return The updated {@link Builder}.
             */
            public Builder addCondition(Condition condition) {
@@ -228,7 +218,7 @@ public class Monitor {

            /**
             * Adds a set of {@link Condition} to be associated with the {@link Subscription}.
             * @param condition
             *
             * @return The updated {@link Builder}.
             */
            public Builder addConditions(Set<Condition> condition) {
@@ -238,6 +228,7 @@ public class Monitor {

            /**
             * Builds the {@link Subscription}.
             *
             * @return The resulting {@link Subscription}.
             */
            public Subscription build() {
+154 −0
Original line number Diff line number Diff line
@@ -141,4 +141,158 @@ 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);
    }
}
Loading