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

Commit 58836c91 authored by Bryce Lee's avatar Bryce Lee
Browse files

Allow overriding conditions for Conditions Monitor.

This changelist introduces overriding conditions to Conditions Monitor, which the state of one condition to take precedence over others that are present. To support this feature, the following changes have been made to the Monitor:
- Conditions can now be added and removed to the Monitor post construction.
- Changes to the callbacks and conditions are now handled on the main executor.
- The value for a condition is consulted on every update. Callbacks no longer include the updated value.

Bug: 218744120
Test: atest ConditionMonitorTest
Change-Id: If895732ae9fad120eccabb78207f4555c6236fde
parent db48e2b6
Loading
Loading
Loading
Loading
+23 −4
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ public abstract class Condition implements CallbackController<Condition.Callback
    private final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>();
    private boolean mIsConditionMet = false;
    private boolean mStarted = false;
    private boolean mOverriding = false;

    /**
     * Starts monitoring the condition.
@@ -47,6 +48,21 @@ public abstract class Condition implements CallbackController<Condition.Callback
     */
    protected abstract void stop();

    /**
     * Sets whether this condition's value overrides others in determining the overall state.
     */
    public void setOverriding(boolean overriding) {
        mOverriding = overriding;
        updateCondition(mIsConditionMet);
    }

    /**
     * Returns whether the current condition overrides
     */
    public boolean isOverridingCondition() {
        return mOverriding;
    }

    /**
     * Registers a callback to receive updates once started. This should be called before
     * {@link #start()}. Also triggers the callback immediately if already started.
@@ -57,7 +73,7 @@ public abstract class Condition implements CallbackController<Condition.Callback
        mCallbacks.add(new WeakReference<>(callback));

        if (mStarted) {
            callback.onConditionChanged(this, mIsConditionMet);
            callback.onConditionChanged(this);
            return;
        }

@@ -107,11 +123,15 @@ public abstract class Condition implements CallbackController<Condition.Callback
            if (cb == null) {
                iterator.remove();
            } else {
                cb.onConditionChanged(this, mIsConditionMet);
                cb.onConditionChanged(this);
            }
        }
    }

    public boolean isConditionMet() {
        return mIsConditionMet;
    }

    private boolean shouldLog() {
        return Log.isLoggable(mTag, Log.DEBUG);
    }
@@ -124,8 +144,7 @@ public abstract class Condition implements CallbackController<Condition.Callback
         * Called when the fulfillment of the condition changes.
         *
         * @param condition The condition in question.
         * @param isConditionMet True if the condition has been fulfilled. False otherwise.
         */
        void onConditionChanged(Condition condition, boolean isConditionMet);
        void onConditionChanged(Condition condition);
    }
}
+91 −46
Original line number Diff line number Diff line
@@ -18,15 +18,17 @@ package com.android.systemui.util.condition;

import android.util.Log;

import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.statusbar.policy.CallbackController;

import org.jetbrains.annotations.NotNull;

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

import javax.inject.Inject;

@@ -41,9 +43,7 @@ public class Monitor implements CallbackController<Monitor.Callback> {

    // Set of all conditions that need to be monitored.
    private final Set<Condition> mConditions;

    // Map of values of each condition.
    private final HashMap<Condition, Boolean> mConditionsMap = new HashMap<>();
    private final Executor mExecutor;

    // Whether all conditions have been met.
    private boolean mAllConditionsMet = false;
@@ -52,10 +52,43 @@ public class Monitor implements CallbackController<Monitor.Callback> {
    private boolean mHaveConditionsStarted = false;

    // Callback for when each condition has been updated.
    private final Condition.Callback mConditionCallback = (condition, isConditionMet) -> {
        mConditionsMap.put(condition, isConditionMet);
    private final Condition.Callback mConditionCallback = new Condition.Callback() {
        @Override
        public void onConditionChanged(Condition condition) {
            mExecutor.execute(() -> updateConditionMetState());
        }
    };

    @Inject
    public Monitor(Executor executor, Set<Condition> conditions, Set<Callback> callbacks) {
        mConditions = new HashSet<>();
        mExecutor = executor;

        if (conditions != null) {
            mConditions.addAll(conditions);
        }

        if (callbacks == null) {
            return;
        }

        for (Callback callback : callbacks) {
            addCallbackLocked(callback);
        }
    }

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

        final boolean newAllConditionsMet = !mConditionsMap.containsValue(false);
        final Collection<Condition> targetCollection = overridingConditions.isEmpty()
                ? mConditions : overridingConditions;

        final boolean newAllConditionsMet = targetCollection.isEmpty() ? true : targetCollection
                .stream()
                .map(Condition::isConditionMet)
                .allMatch(conditionMet -> conditionMet);

        if (newAllConditionsMet == mAllConditionsMet) {
            return;
@@ -74,32 +107,44 @@ public class Monitor implements CallbackController<Monitor.Callback> {
                callback.onConditionsChanged(mAllConditionsMet);
            }
        }
    };
    }

    @Inject
    public Monitor(Set<Condition> conditions, Set<Callback> callbacks) {
        mConditions = conditions;
    private void addConditionLocked(@NotNull Condition condition) {
        mConditions.add(condition);

        // If there is no condition, give green pass.
        if (mConditions.isEmpty()) {
            mAllConditionsMet = true;
        if (!mHaveConditionsStarted) {
            return;
        }

        // Initializes the conditions map and registers a callback for each condition.
        mConditions.forEach((condition -> mConditionsMap.put(condition, false)));
        condition.addCallback(mConditionCallback);
        updateConditionMetState();
    }

        if (callbacks == null) {
            return;
    /**
     * Adds a condition for the monitor to listen to and consider when determining whether the
     * overall condition state is met.
     */
    public void addCondition(@NotNull Condition condition) {
        mExecutor.execute(() -> addConditionLocked(condition));
    }

        for (Callback callback : callbacks) {
            addCallback(callback);
    /**
     * Removes a condition from further consideration.
     */
    public void removeCondition(@NotNull Condition condition) {
        mExecutor.execute(() -> {
            mConditions.remove(condition);

            if (!mHaveConditionsStarted) {
                return;
            }

            condition.removeCallback(mConditionCallback);
            updateConditionMetState();
        });
    }

    @Override
    public void addCallback(@NotNull Callback callback) {
    private void addCallbackLocked(@NotNull Callback callback) {
        if (shouldLog()) Log.d(mTag, "adding callback");
        mCallbacks.add(callback);

@@ -109,12 +154,19 @@ public class Monitor implements CallbackController<Monitor.Callback> {
        if (!mHaveConditionsStarted) {
            if (shouldLog()) Log.d(mTag, "starting all conditions");
            mConditions.forEach(condition -> condition.addCallback(mConditionCallback));
            updateConditionMetState();
            mHaveConditionsStarted = true;
        }
    }

    @Override
    public void addCallback(@NotNull Callback callback) {
        mExecutor.execute(() -> addCallbackLocked(callback));
    }

    @Override
    public void removeCallback(@NotNull Callback callback) {
        mExecutor.execute(() -> {
            if (shouldLog()) Log.d(mTag, "removing callback");
            final Iterator<Callback> iterator = mCallbacks.iterator();
            while (iterator.hasNext()) {
@@ -131,14 +183,7 @@ public class Monitor implements CallbackController<Monitor.Callback> {
                mAllConditionsMet = false;
                mHaveConditionsStarted = false;
            }
    }

    /**
     * Force updates each condition to the value provided.
     */
    @VisibleForTesting
    public void overrideAllConditionsMet(boolean value) {
        mConditions.forEach(condition -> condition.updateCondition(value));
        });
    }

    private boolean shouldLog() {
+11 −6
Original line number Diff line number Diff line
@@ -16,7 +16,8 @@

package com.android.systemui.communal;

import static org.mockito.ArgumentMatchers.anyBoolean;
import static com.google.common.truth.Truth.assertThat;

import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
@@ -59,7 +60,8 @@ public class CommunalSettingConditionTest extends SysuiTestCase {

        final Condition.Callback callback = mock(Condition.Callback.class);
        mCondition.addCallback(callback);
        verify(callback).onConditionChanged(mCondition, true);
        verify(callback).onConditionChanged(mCondition);
        assertThat(mCondition.isConditionMet()).isTrue();
    }

    @Test
@@ -68,7 +70,7 @@ public class CommunalSettingConditionTest extends SysuiTestCase {

        final Condition.Callback callback = mock(Condition.Callback.class);
        mCondition.addCallback(callback);
        verify(callback, never()).onConditionChanged(eq(mCondition), anyBoolean());
        verify(callback, never()).onConditionChanged(eq(mCondition));
    }

    @Test
@@ -80,7 +82,8 @@ public class CommunalSettingConditionTest extends SysuiTestCase {
        clearInvocations(callback);

        updateCommunalSetting(true);
        verify(callback).onConditionChanged(mCondition, true);
        verify(callback).onConditionChanged(mCondition);
        assertThat(mCondition.isConditionMet()).isTrue();
    }

    @Test
@@ -92,7 +95,8 @@ public class CommunalSettingConditionTest extends SysuiTestCase {
        clearInvocations(callback);

        updateCommunalSetting(false);
        verify(callback).onConditionChanged(mCondition, false);
        verify(callback).onConditionChanged(mCondition);
        assertThat(mCondition.isConditionMet()).isFalse();
    }

    @Test
@@ -104,7 +108,8 @@ public class CommunalSettingConditionTest extends SysuiTestCase {
        clearInvocations(callback);

        updateCommunalSetting(true);
        verify(callback, never()).onConditionChanged(mCondition, true);
        verify(callback, never()).onConditionChanged(mCondition);
        assertThat(mCondition.isConditionMet()).isTrue();
    }

    private void updateCommunalSetting(boolean value) {
+11 −5
Original line number Diff line number Diff line
@@ -16,7 +16,8 @@

package com.android.systemui.communal;

import static org.mockito.ArgumentMatchers.anyBoolean;
import static com.google.common.truth.Truth.assertThat;

import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.clearInvocations;
@@ -49,6 +50,7 @@ import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;

@SmallTest
@@ -89,7 +91,8 @@ public class CommunalTrustedNetworkConditionTest extends SysuiTestCase {
        networkCallback.onCapabilitiesChanged(network, fakeNetworkCapabilities(mTrustedWifi1));

        // Verifies that the callback is triggered.
        verify(callback).onConditionChanged(mCondition, true);
        verify(callback).onConditionChanged(mCondition);
        assertThat(mCondition.isConditionMet()).isTrue();
    }

    @Test
@@ -110,7 +113,7 @@ public class CommunalTrustedNetworkConditionTest extends SysuiTestCase {
        networkCallback.onCapabilitiesChanged(network, fakeNetworkCapabilities(mTrustedWifi2));

        // Verifies that the callback is not triggered.
        verify(callback, never()).onConditionChanged(eq(mCondition), anyBoolean());
        verify(callback, never()).onConditionChanged(eq(mCondition));
    }

    @Test
@@ -126,11 +129,13 @@ public class CommunalTrustedNetworkConditionTest extends SysuiTestCase {
        networkCallback.onAvailable(network);
        networkCallback.onCapabilitiesChanged(network, fakeNetworkCapabilities(mTrustedWifi1));

        Mockito.clearInvocations(callback);
        // Connected to non-trusted Wi-Fi network.
        networkCallback.onCapabilitiesChanged(network, fakeNetworkCapabilities("random-wifi"));

        // Verifies that the callback is triggered.
        verify(callback).onConditionChanged(mCondition, false);
        verify(callback).onConditionChanged(mCondition);
        assertThat(mCondition.isConditionMet()).isFalse();
    }

    @Test
@@ -151,7 +156,8 @@ public class CommunalTrustedNetworkConditionTest extends SysuiTestCase {
        networkCallback.onLost(network);

        // Verifies that the callback is triggered.
        verify(callback).onConditionChanged(mCondition, false);
        verify(callback).onConditionChanged(mCondition);
        assertThat(mCondition.isConditionMet()).isFalse();
    }

    // Captures and returns the network callback, assuming it is registered with the connectivity
+121 −12
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.systemui.util.condition;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
@@ -24,16 +25,21 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.testing.AndroidTestingRunner;

import androidx.test.filters.SmallTest;

import com.android.systemui.SysuiTestCase;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;

import java.util.Arrays;
@@ -46,6 +52,7 @@ public class ConditionMonitorTest extends SysuiTestCase {
    private FakeCondition mCondition2;
    private FakeCondition mCondition3;
    private HashSet<Condition> mConditions;
    private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());

    private Monitor mConditionMonitor;

@@ -58,7 +65,83 @@ public class ConditionMonitorTest extends SysuiTestCase {
        mCondition3 = spy(new FakeCondition());
        mConditions = new HashSet<>(Arrays.asList(mCondition1, mCondition2, mCondition3));

        mConditionMonitor = new Monitor(mConditions, null /*callbacks*/);
        mConditionMonitor = new Monitor(mExecutor, mConditions, null /*callbacks*/);
    }

    @Test
    public void testOverridingCondition() {
        final Condition overridingCondition = Mockito.mock(Condition.class);
        final Condition regularCondition = Mockito.mock(Condition.class);
        final Monitor.Callback callback = Mockito.mock(Monitor.Callback.class);

        final Monitor monitor = new Monitor(
                mExecutor,
                new HashSet<>(Arrays.asList(overridingCondition, regularCondition)),
                new HashSet<>(Arrays.asList(callback)));

        when(overridingCondition.isOverridingCondition()).thenReturn(true);
        when(overridingCondition.isConditionMet()).thenReturn(true);
        when(regularCondition.isConditionMet()).thenReturn(false);

        final ArgumentCaptor<Condition.Callback> mCallbackCaptor =
                ArgumentCaptor.forClass(Condition.Callback.class);

        verify(overridingCondition).addCallback(mCallbackCaptor.capture());

        mCallbackCaptor.getValue().onConditionChanged(overridingCondition);
        mExecutor.runAllReady();

        verify(callback).onConditionsChanged(eq(true));
        Mockito.clearInvocations(callback);

        when(regularCondition.isConditionMet()).thenReturn(true);
        when(overridingCondition.isConditionMet()).thenReturn(false);

        mCallbackCaptor.getValue().onConditionChanged(overridingCondition);
        mExecutor.runAllReady();

        verify(callback).onConditionsChanged(eq(false));

        clearInvocations(callback);
        monitor.removeCondition(overridingCondition);
        mExecutor.runAllReady();

        verify(callback).onConditionsChanged(eq(true));
    }

    /**
     * Ensures that when multiple overriding conditions are present, it is the aggregate of those
     * conditions that are considered.
     */
    @Test
    public void testMultipleOverridingConditions() {
        final Condition overridingCondition = Mockito.mock(Condition.class);
        final Condition overridingCondition2 = Mockito.mock(Condition.class);
        final Condition regularCondition = Mockito.mock(Condition.class);
        final Monitor.Callback callback = Mockito.mock(Monitor.Callback.class);

        final Monitor monitor = new Monitor(
                mExecutor,
                new HashSet<>(Arrays.asList(overridingCondition, overridingCondition2,
                        regularCondition)),
                new HashSet<>(Arrays.asList(callback)));

        when(overridingCondition.isOverridingCondition()).thenReturn(true);
        when(overridingCondition.isConditionMet()).thenReturn(true);
        when(overridingCondition2.isOverridingCondition()).thenReturn(true);
        when(overridingCondition.isConditionMet()).thenReturn(false);
        when(regularCondition.isConditionMet()).thenReturn(true);

        final ArgumentCaptor<Condition.Callback> mCallbackCaptor =
                ArgumentCaptor.forClass(Condition.Callback.class);

        verify(overridingCondition).addCallback(mCallbackCaptor.capture());

        mCallbackCaptor.getValue().onConditionChanged(overridingCondition);
        mExecutor.runAllReady();

        verify(callback).onConditionsChanged(eq(false));
        Mockito.clearInvocations(callback);
    }

    @Test
@@ -66,11 +149,13 @@ public class ConditionMonitorTest extends SysuiTestCase {
        final Monitor.Callback callback1 =
                mock(Monitor.Callback.class);
        mConditionMonitor.addCallback(callback1);
        mExecutor.runAllReady();
        mConditions.forEach(condition -> verify(condition).addCallback(any()));

        final Monitor.Callback callback2 =
                mock(Monitor.Callback.class);
        mConditionMonitor.addCallback(callback2);
        mExecutor.runAllReady();
        mConditions.forEach(condition -> verify(condition, times(1)).addCallback(any()));
    }

@@ -79,6 +164,7 @@ public class ConditionMonitorTest extends SysuiTestCase {
        final Monitor.Callback callback =
                mock(Monitor.Callback.class);
        mConditionMonitor.addCallback(callback);
        mExecutor.runAllReady();
        verify(callback).onConditionsChanged(false);
    }

@@ -86,38 +172,53 @@ public class ConditionMonitorTest extends SysuiTestCase {
    public void addCallback_addSecondCallback_reportWithExistingValue() {
        final Monitor.Callback callback1 =
                mock(Monitor.Callback.class);
        mConditionMonitor.addCallback(callback1);

        mConditionMonitor.overrideAllConditionsMet(true);
        final Condition condition = mock(Condition.class);
        when(condition.isConditionMet()).thenReturn(true);
        final Monitor monitor = new Monitor(mExecutor, new HashSet<>(Arrays.asList(condition)),
                new HashSet<>(Arrays.asList(callback1)));

        final Monitor.Callback callback2 =
                mock(Monitor.Callback.class);
        mConditionMonitor.addCallback(callback2);
        verify(callback2).onConditionsChanged(true);
        monitor.addCallback(callback2);
        mExecutor.runAllReady();
        verify(callback2).onConditionsChanged(eq(true));
    }

    @Test
    public void addCallback_noConditions_reportAllConditionsMet() {
        final Monitor monitor = new Monitor(new HashSet<>(), null /*callbacks*/);
        final Monitor monitor = new Monitor(mExecutor, new HashSet<>(), null /*callbacks*/);
        final Monitor.Callback callback = mock(Monitor.Callback.class);

        monitor.addCallback(callback);

        mExecutor.runAllReady();
        verify(callback).onConditionsChanged(true);
    }

    @Test
    public void removeCallback_shouldNoLongerReceiveUpdate() {
        final Condition condition = mock(Condition.class);
        final Monitor monitor = new Monitor(mExecutor, new HashSet<>(Arrays.asList(condition)),
                null);
        final Monitor.Callback callback =
                mock(Monitor.Callback.class);
        mConditionMonitor.addCallback(callback);
        monitor.addCallback(callback);
        monitor.removeCallback(callback);
        mExecutor.runAllReady();
        clearInvocations(callback);
        mConditionMonitor.removeCallback(callback);

        mConditionMonitor.overrideAllConditionsMet(true);
        final ArgumentCaptor<Condition.Callback> conditionCallbackCaptor =
                ArgumentCaptor.forClass(Condition.Callback.class);
        verify(condition).addCallback(conditionCallbackCaptor.capture());
        final Condition.Callback conditionCallback = conditionCallbackCaptor.getValue();

        when(condition.isConditionMet()).thenReturn(true);
        conditionCallback.onConditionChanged(condition);
        mExecutor.runAllReady();
        verify(callback, never()).onConditionsChanged(true);

        mConditionMonitor.overrideAllConditionsMet(false);
        when(condition.isConditionMet()).thenReturn(false);
        conditionCallback.onConditionChanged(condition);
        mExecutor.runAllReady();
        verify(callback, never()).onConditionsChanged(false);
    }

@@ -131,9 +232,11 @@ public class ConditionMonitorTest extends SysuiTestCase {
        mConditionMonitor.addCallback(callback2);

        mConditionMonitor.removeCallback(callback1);
        mExecutor.runAllReady();
        mConditions.forEach(condition -> verify(condition, never()).removeCallback(any()));

        mConditionMonitor.removeCallback(callback2);
        mExecutor.runAllReady();
        mConditions.forEach(condition -> verify(condition).removeCallback(any()));
    }

@@ -147,6 +250,7 @@ public class ConditionMonitorTest extends SysuiTestCase {
        mCondition1.fakeUpdateCondition(true);
        mCondition2.fakeUpdateCondition(true);
        mCondition3.fakeUpdateCondition(true);
        mExecutor.runAllReady();

        verify(callback).onConditionsChanged(true);
    }
@@ -163,6 +267,7 @@ public class ConditionMonitorTest extends SysuiTestCase {
        clearInvocations(callback);

        mCondition1.fakeUpdateCondition(false);
        mExecutor.runAllReady();
        verify(callback).onConditionsChanged(false);
    }

@@ -171,16 +276,20 @@ public class ConditionMonitorTest extends SysuiTestCase {
        final Monitor.Callback callback =
                mock(Monitor.Callback.class);
        mConditionMonitor.addCallback(callback);
        mExecutor.runAllReady();
        verify(callback).onConditionsChanged(false);
        clearInvocations(callback);

        mCondition1.fakeUpdateCondition(true);
        mExecutor.runAllReady();
        verify(callback, never()).onConditionsChanged(anyBoolean());

        mCondition2.fakeUpdateCondition(true);
        mExecutor.runAllReady();
        verify(callback, never()).onConditionsChanged(anyBoolean());

        mCondition3.fakeUpdateCondition(true);
        mExecutor.runAllReady();
        verify(callback).onConditionsChanged(true);
    }
}
Loading