Merge changes I1efabf11,Ib4121d37,Ifbe498ba into tm-qpr-dev
* changes: UserProcessCondition Introduction. ConditionalCoreStartable Introduction. Add Preconditions and Nested Subscriptions.
This commit is contained in:
@@ -38,13 +38,19 @@ import javax.inject.Inject;
|
||||
public class Monitor {
|
||||
private final String mTag = getClass().getSimpleName();
|
||||
private final Executor mExecutor;
|
||||
private final Set<Condition> mPreconditions;
|
||||
|
||||
private final HashMap<Condition, ArraySet<Subscription.Token>> mConditions = new HashMap<>();
|
||||
private final HashMap<Subscription.Token, SubscriptionState> mSubscriptions = new HashMap<>();
|
||||
|
||||
private static class SubscriptionState {
|
||||
private final Subscription mSubscription;
|
||||
|
||||
// A subscription must maintain a reference to any active nested subscription so that it may
|
||||
// be later removed when the current subscription becomes invalid.
|
||||
private Subscription.Token mNestedSubscriptionToken;
|
||||
private Boolean mAllConditionsMet;
|
||||
private boolean mActive;
|
||||
|
||||
SubscriptionState(Subscription subscription) {
|
||||
mSubscription = subscription;
|
||||
@@ -54,7 +60,27 @@ public class Monitor {
|
||||
return mSubscription.mConditions;
|
||||
}
|
||||
|
||||
public void update() {
|
||||
/**
|
||||
* Signals that the {@link Subscription} is now being monitored and will receive updates
|
||||
* based on its conditions.
|
||||
*/
|
||||
private void setActive(boolean active) {
|
||||
if (mActive == active) {
|
||||
return;
|
||||
}
|
||||
|
||||
mActive = active;
|
||||
|
||||
final Callback callback = mSubscription.getCallback();
|
||||
|
||||
if (callback == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
callback.onActiveChanged(active);
|
||||
}
|
||||
|
||||
public void update(Monitor monitor) {
|
||||
final Boolean result = Evaluator.INSTANCE.evaluate(mSubscription.mConditions,
|
||||
Evaluator.OP_AND);
|
||||
// Consider unknown (null) as true
|
||||
@@ -65,7 +91,50 @@ public class Monitor {
|
||||
}
|
||||
|
||||
mAllConditionsMet = newAllConditionsMet;
|
||||
mSubscription.mCallback.onConditionsChanged(mAllConditionsMet);
|
||||
|
||||
final Subscription nestedSubscription = mSubscription.getNestedSubscription();
|
||||
|
||||
if (nestedSubscription != null) {
|
||||
if (mAllConditionsMet && mNestedSubscriptionToken == null) {
|
||||
// When all conditions are met for a subscription with a nested subscription
|
||||
// that is not currently being monitored, add the nested subscription for
|
||||
// monitor.
|
||||
mNestedSubscriptionToken =
|
||||
monitor.addSubscription(nestedSubscription, null);
|
||||
} else if (!mAllConditionsMet && mNestedSubscriptionToken != null) {
|
||||
// When conditions are not met and there is an active nested condition, remove
|
||||
// the nested condition from monitoring.
|
||||
removeNestedSubscription(monitor);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
mSubscription.getCallback().onConditionsChanged(mAllConditionsMet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when the {@link Subscription} has been added to the {@link Monitor}.
|
||||
*/
|
||||
public void onAdded() {
|
||||
setActive(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when the {@link Subscription} has been removed from the {@link Monitor},
|
||||
* allowing cleanup code to run.
|
||||
*/
|
||||
public void onRemoved(Monitor monitor) {
|
||||
setActive(false);
|
||||
removeNestedSubscription(monitor);
|
||||
}
|
||||
|
||||
private void removeNestedSubscription(Monitor monitor) {
|
||||
if (mNestedSubscriptionToken == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
monitor.removeSubscription(mNestedSubscriptionToken);
|
||||
mNestedSubscriptionToken = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,9 +146,20 @@ public class Monitor {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor for injected use-cases. By default, no preconditions are present.
|
||||
*/
|
||||
@Inject
|
||||
public Monitor(@Main Executor executor) {
|
||||
this(executor, Collections.emptySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Main constructor, allowing specifying preconditions.
|
||||
*/
|
||||
public Monitor(Executor executor, Set<Condition> preconditions) {
|
||||
mExecutor = executor;
|
||||
mPreconditions = preconditions;
|
||||
}
|
||||
|
||||
private void updateConditionMetState(Condition condition) {
|
||||
@@ -91,7 +171,7 @@ public class Monitor {
|
||||
return;
|
||||
}
|
||||
|
||||
subscriptions.stream().forEach(token -> mSubscriptions.get(token).update());
|
||||
subscriptions.stream().forEach(token -> mSubscriptions.get(token).update(this));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,15 +181,25 @@ public class Monitor {
|
||||
* @return A {@link Subscription.Token} that can be used to remove the subscription.
|
||||
*/
|
||||
public Subscription.Token addSubscription(@NonNull Subscription subscription) {
|
||||
return addSubscription(subscription, mPreconditions);
|
||||
}
|
||||
|
||||
private Subscription.Token addSubscription(@NonNull Subscription subscription,
|
||||
Set<Condition> preconditions) {
|
||||
// If preconditions are set on the monitor, set up as a nested condition.
|
||||
final Subscription normalizedCondition = preconditions != null
|
||||
? new Subscription.Builder(subscription).addConditions(preconditions).build()
|
||||
: subscription;
|
||||
|
||||
final Subscription.Token token = new Subscription.Token();
|
||||
final SubscriptionState state = new SubscriptionState(subscription);
|
||||
final SubscriptionState state = new SubscriptionState(normalizedCondition);
|
||||
|
||||
mExecutor.execute(() -> {
|
||||
if (shouldLog()) Log.d(mTag, "adding subscription");
|
||||
mSubscriptions.put(token, state);
|
||||
|
||||
// Add and associate conditions.
|
||||
subscription.getConditions().stream().forEach(condition -> {
|
||||
normalizedCondition.getConditions().stream().forEach(condition -> {
|
||||
if (!mConditions.containsKey(condition)) {
|
||||
mConditions.put(condition, new ArraySet<>());
|
||||
condition.addCallback(mConditionCallback);
|
||||
@@ -118,8 +208,10 @@ public class Monitor {
|
||||
mConditions.get(condition).add(token);
|
||||
});
|
||||
|
||||
state.onAdded();
|
||||
|
||||
// Update subscription state.
|
||||
state.update();
|
||||
state.update(this);
|
||||
|
||||
});
|
||||
return token;
|
||||
@@ -139,7 +231,9 @@ public class Monitor {
|
||||
return;
|
||||
}
|
||||
|
||||
mSubscriptions.remove(token).getConditions().forEach(condition -> {
|
||||
final SubscriptionState removedSubscription = mSubscriptions.remove(token);
|
||||
|
||||
removedSubscription.getConditions().forEach(condition -> {
|
||||
if (!mConditions.containsKey(condition)) {
|
||||
Log.e(mTag, "condition not present:" + condition);
|
||||
return;
|
||||
@@ -153,6 +247,8 @@ public class Monitor {
|
||||
mConditions.remove(condition);
|
||||
}
|
||||
});
|
||||
|
||||
removedSubscription.onRemoved(this);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -168,12 +264,19 @@ public class Monitor {
|
||||
private final Set<Condition> mConditions;
|
||||
private final Callback mCallback;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public Subscription(Set<Condition> conditions, Callback callback) {
|
||||
// A nested {@link Subscription} is a special callback where the specified condition's
|
||||
// active state is dependent on the conditions of the parent {@link Subscription} being met.
|
||||
// Once active, the nested subscription's conditions are registered as normal with the
|
||||
// monitor and its callback (which could also be a nested condition) is triggered based on
|
||||
// those conditions. The nested condition will be removed from monitor if the outer
|
||||
// subscription's conditions ever become invalid.
|
||||
private final Subscription mNestedSubscription;
|
||||
|
||||
private Subscription(Set<Condition> conditions, Callback callback,
|
||||
Subscription nestedSubscription) {
|
||||
this.mConditions = Collections.unmodifiableSet(conditions);
|
||||
this.mCallback = callback;
|
||||
this.mNestedSubscription = nestedSubscription;
|
||||
}
|
||||
|
||||
public Set<Condition> getConditions() {
|
||||
@@ -184,6 +287,10 @@ public class Monitor {
|
||||
return mCallback;
|
||||
}
|
||||
|
||||
public Subscription getNestedSubscription() {
|
||||
return mNestedSubscription;
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link Token} is an identifier that is associated with a {@link Subscription} which is
|
||||
* registered with a {@link Monitor}.
|
||||
@@ -196,14 +303,26 @@ public class Monitor {
|
||||
*/
|
||||
public static class Builder {
|
||||
private final Callback mCallback;
|
||||
private final Subscription mNestedSubscription;
|
||||
private final ArraySet<Condition> mConditions;
|
||||
private final ArraySet<Condition> mPreconditions;
|
||||
|
||||
/**
|
||||
* Default constructor specifying the {@link Callback} for the {@link Subscription}.
|
||||
*/
|
||||
public Builder(Callback callback) {
|
||||
this(null, callback);
|
||||
}
|
||||
|
||||
public Builder(Subscription nestedSubscription) {
|
||||
this(nestedSubscription, null);
|
||||
}
|
||||
|
||||
private Builder(Subscription nestedSubscription, Callback callback) {
|
||||
mNestedSubscription = nestedSubscription;
|
||||
mCallback = callback;
|
||||
mConditions = new ArraySet<>();
|
||||
mConditions = new ArraySet();
|
||||
mPreconditions = new ArraySet();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -216,12 +335,39 @@ public class Monitor {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a set of {@link Condition} to be a precondition for {@link Subscription}.
|
||||
*
|
||||
* @return The updated {@link Builder}.
|
||||
*/
|
||||
public Builder addPreconditions(Set<Condition> condition) {
|
||||
if (condition == null) {
|
||||
return this;
|
||||
}
|
||||
mPreconditions.addAll(condition);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a {@link Condition} to be a precondition for {@link Subscription}.
|
||||
*
|
||||
* @return The updated {@link Builder}.
|
||||
*/
|
||||
public Builder addPrecondition(Condition condition) {
|
||||
mPreconditions.add(condition);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a set of {@link Condition} to be associated with the {@link Subscription}.
|
||||
*
|
||||
* @return The updated {@link Builder}.
|
||||
*/
|
||||
public Builder addConditions(Set<Condition> condition) {
|
||||
if (condition == null) {
|
||||
return this;
|
||||
}
|
||||
|
||||
mConditions.addAll(condition);
|
||||
return this;
|
||||
}
|
||||
@@ -232,7 +378,11 @@ public class Monitor {
|
||||
* @return The resulting {@link Subscription}.
|
||||
*/
|
||||
public Subscription build() {
|
||||
return new Subscription(mConditions, mCallback);
|
||||
final Subscription subscription =
|
||||
new Subscription(mConditions, mCallback, mNestedSubscription);
|
||||
return !mPreconditions.isEmpty()
|
||||
? new Subscription(mPreconditions, null, subscription)
|
||||
: subscription;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -255,5 +405,13 @@ public class Monitor {
|
||||
* only partial conditions have been fulfilled.
|
||||
*/
|
||||
void onConditionsChanged(boolean allConditionsMet);
|
||||
|
||||
/**
|
||||
* Called when the active state of the {@link Subscription} changes.
|
||||
* @param active {@code true} when changes to the conditions will affect the
|
||||
* {@link Subscription}, {@code false} otherwise.
|
||||
*/
|
||||
default void onActiveChanged(boolean active) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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.process;
|
||||
|
||||
/**
|
||||
* A simple wrapper that provides access to process-related details. This facilitates testing by
|
||||
* providing a mockable target around these details.
|
||||
*/
|
||||
public class ProcessWrapper {
|
||||
public int getUserHandleIdentifier() {
|
||||
return android.os.Process.myUserHandle().getIdentifier();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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.process.condition;
|
||||
|
||||
import com.android.systemui.process.ProcessWrapper;
|
||||
import com.android.systemui.settings.UserTracker;
|
||||
import com.android.systemui.shared.condition.Condition;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* {@link UserProcessCondition} provides a signal when the process handle belongs to the current
|
||||
* user.
|
||||
*/
|
||||
public class UserProcessCondition extends Condition {
|
||||
private final ProcessWrapper mProcessWrapper;
|
||||
private final UserTracker mUserTracker;
|
||||
|
||||
@Inject
|
||||
public UserProcessCondition(ProcessWrapper processWrapper, UserTracker userTracker) {
|
||||
mProcessWrapper = processWrapper;
|
||||
mUserTracker = userTracker;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void start() {
|
||||
updateCondition(mUserTracker.getUserId()
|
||||
== mProcessWrapper.getUserHandleIdentifier());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void stop() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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.util.condition;
|
||||
|
||||
import com.android.systemui.CoreStartable;
|
||||
import com.android.systemui.shared.condition.Condition;
|
||||
import com.android.systemui.shared.condition.Monitor;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* {@link ConditionalCoreStartable} is a {@link com.android.systemui.CoreStartable} abstract
|
||||
* implementation where conditions must be met before routines are executed.
|
||||
*/
|
||||
public abstract class ConditionalCoreStartable implements CoreStartable {
|
||||
private final Monitor mMonitor;
|
||||
private final Set<Condition> mConditionSet;
|
||||
private Monitor.Subscription.Token mStartToken;
|
||||
private Monitor.Subscription.Token mBootCompletedToken;
|
||||
|
||||
public ConditionalCoreStartable(Monitor monitor) {
|
||||
this(monitor, null);
|
||||
}
|
||||
|
||||
public ConditionalCoreStartable(Monitor monitor, Set<Condition> conditionSet) {
|
||||
mMonitor = monitor;
|
||||
mConditionSet = conditionSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void start() {
|
||||
if (mConditionSet == null || mConditionSet.isEmpty()) {
|
||||
onStart();
|
||||
return;
|
||||
}
|
||||
|
||||
mStartToken = mMonitor.addSubscription(
|
||||
new Monitor.Subscription.Builder(allConditionsMet -> {
|
||||
if (allConditionsMet) {
|
||||
mMonitor.removeSubscription(mStartToken);
|
||||
mStartToken = null;
|
||||
onStart();
|
||||
}
|
||||
}).addConditions(mConditionSet)
|
||||
.build());
|
||||
}
|
||||
|
||||
protected abstract void onStart();
|
||||
|
||||
@Override
|
||||
public final void onBootCompleted() {
|
||||
if (mConditionSet == null || mConditionSet.isEmpty()) {
|
||||
bootCompleted();
|
||||
return;
|
||||
}
|
||||
|
||||
mBootCompletedToken = mMonitor.addSubscription(
|
||||
new Monitor.Subscription.Builder(allConditionsMet -> {
|
||||
if (allConditionsMet) {
|
||||
mMonitor.removeSubscription(mBootCompletedToken);
|
||||
mBootCompletedToken = null;
|
||||
bootCompleted();
|
||||
}
|
||||
}).addConditions(mConditionSet)
|
||||
.build());
|
||||
}
|
||||
|
||||
protected void bootCompleted() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* 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.process.condition;
|
||||
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.process.ProcessWrapper;
|
||||
import com.android.systemui.settings.UserTracker;
|
||||
import com.android.systemui.shared.condition.Condition;
|
||||
import com.android.systemui.shared.condition.Monitor;
|
||||
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.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
@TestableLooper.RunWithLooper
|
||||
@SmallTest
|
||||
public class UserProcessConditionTest extends SysuiTestCase {
|
||||
@Mock
|
||||
UserTracker mUserTracker;
|
||||
|
||||
@Mock
|
||||
ProcessWrapper mProcessWrapper;
|
||||
|
||||
@Mock
|
||||
Monitor.Callback mCallback;
|
||||
|
||||
private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies condition reports false when tracker reports a different user id than the
|
||||
* identifier from the process handle.
|
||||
*/
|
||||
@Test
|
||||
public void testConditionFailsWithDifferentIds() {
|
||||
|
||||
final Condition condition = new UserProcessCondition(mProcessWrapper, mUserTracker);
|
||||
when(mProcessWrapper.getUserHandleIdentifier()).thenReturn(0);
|
||||
when(mUserTracker.getUserId()).thenReturn(1);
|
||||
|
||||
final Monitor monitor = new Monitor(mExecutor);
|
||||
|
||||
monitor.addSubscription(new Monitor.Subscription.Builder(mCallback)
|
||||
.addCondition(condition)
|
||||
.build());
|
||||
|
||||
mExecutor.runAllReady();
|
||||
|
||||
verify(mCallback).onConditionsChanged(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies condition reports false when tracker reports a different user id than the
|
||||
* identifier from the process handle.
|
||||
*/
|
||||
@Test
|
||||
public void testConditionSucceedsWithSameIds() {
|
||||
|
||||
final Condition condition = new UserProcessCondition(mProcessWrapper, mUserTracker);
|
||||
when(mProcessWrapper.getUserHandleIdentifier()).thenReturn(0);
|
||||
when(mUserTracker.getUserId()).thenReturn(0);
|
||||
|
||||
final Monitor monitor = new Monitor(mExecutor);
|
||||
|
||||
monitor.addSubscription(new Monitor.Subscription.Builder(mCallback)
|
||||
.addCondition(condition)
|
||||
.build());
|
||||
|
||||
mExecutor.runAllReady();
|
||||
|
||||
verify(mCallback).onConditionsChanged(true);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -471,4 +471,142 @@ public class ConditionMonitorTest extends SysuiTestCase {
|
||||
mExecutor.runAllReady();
|
||||
verify(callback).onConditionsChanged(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that the result of a condition being true leads to its nested condition being
|
||||
* activated.
|
||||
*/
|
||||
@Test
|
||||
public void testNestedCondition() {
|
||||
mCondition1.fakeUpdateCondition(false);
|
||||
final Monitor.Callback callback =
|
||||
mock(Monitor.Callback.class);
|
||||
|
||||
mCondition2.fakeUpdateCondition(false);
|
||||
|
||||
// Create a nested condition
|
||||
mConditionMonitor.addSubscription(new Monitor.Subscription.Builder(
|
||||
new Monitor.Subscription.Builder(callback)
|
||||
.addCondition(mCondition2)
|
||||
.build())
|
||||
.addCondition(mCondition1)
|
||||
.build());
|
||||
|
||||
mExecutor.runAllReady();
|
||||
|
||||
// Ensure the nested condition callback is not called at all.
|
||||
verify(callback, never()).onActiveChanged(anyBoolean());
|
||||
verify(callback, never()).onConditionsChanged(anyBoolean());
|
||||
|
||||
// Update the inner condition to true and ensure that the nested condition is not triggered.
|
||||
mCondition2.fakeUpdateCondition(true);
|
||||
verify(callback, never()).onConditionsChanged(anyBoolean());
|
||||
mCondition2.fakeUpdateCondition(false);
|
||||
|
||||
// Set outer condition and make sure the inner condition becomes active and reports that
|
||||
// conditions aren't met
|
||||
mCondition1.fakeUpdateCondition(true);
|
||||
mExecutor.runAllReady();
|
||||
|
||||
verify(callback).onActiveChanged(eq(true));
|
||||
verify(callback).onConditionsChanged(eq(false));
|
||||
|
||||
Mockito.clearInvocations(callback);
|
||||
|
||||
// Update the inner condition and make sure the callback is updated.
|
||||
mCondition2.fakeUpdateCondition(true);
|
||||
mExecutor.runAllReady();
|
||||
|
||||
verify(callback).onConditionsChanged(true);
|
||||
|
||||
Mockito.clearInvocations(callback);
|
||||
// Invalidate outer condition and make sure callback is informed, but the last state is
|
||||
// not affected.
|
||||
mCondition1.fakeUpdateCondition(false);
|
||||
mExecutor.runAllReady();
|
||||
|
||||
verify(callback).onActiveChanged(eq(false));
|
||||
verify(callback, never()).onConditionsChanged(anyBoolean());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures a subscription is predicated on its precondition.
|
||||
*/
|
||||
@Test
|
||||
public void testPrecondition() {
|
||||
mCondition1.fakeUpdateCondition(false);
|
||||
final Monitor.Callback callback =
|
||||
mock(Monitor.Callback.class);
|
||||
|
||||
mCondition2.fakeUpdateCondition(false);
|
||||
|
||||
// Create a nested condition
|
||||
mConditionMonitor.addSubscription(new Monitor.Subscription.Builder(callback)
|
||||
.addPrecondition(mCondition1)
|
||||
.addCondition(mCondition2)
|
||||
.build());
|
||||
|
||||
mExecutor.runAllReady();
|
||||
|
||||
// Ensure the nested condition callback is not called at all.
|
||||
verify(callback, never()).onActiveChanged(anyBoolean());
|
||||
verify(callback, never()).onConditionsChanged(anyBoolean());
|
||||
|
||||
// Update the condition to true and ensure that the nested condition is not triggered.
|
||||
mCondition2.fakeUpdateCondition(true);
|
||||
verify(callback, never()).onConditionsChanged(anyBoolean());
|
||||
mCondition2.fakeUpdateCondition(false);
|
||||
|
||||
// Set precondition and make sure the inner condition becomes active and reports that
|
||||
// conditions aren't met
|
||||
mCondition1.fakeUpdateCondition(true);
|
||||
mExecutor.runAllReady();
|
||||
|
||||
verify(callback).onActiveChanged(eq(true));
|
||||
verify(callback).onConditionsChanged(eq(false));
|
||||
|
||||
Mockito.clearInvocations(callback);
|
||||
|
||||
// Update the condition and make sure the callback is updated.
|
||||
mCondition2.fakeUpdateCondition(true);
|
||||
mExecutor.runAllReady();
|
||||
|
||||
verify(callback).onConditionsChanged(true);
|
||||
|
||||
Mockito.clearInvocations(callback);
|
||||
// Invalidate precondition and make sure callback is informed, but the last state is
|
||||
// not affected.
|
||||
mCondition1.fakeUpdateCondition(false);
|
||||
mExecutor.runAllReady();
|
||||
|
||||
verify(callback).onActiveChanged(eq(false));
|
||||
verify(callback, never()).onConditionsChanged(anyBoolean());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure preconditions are applied to every subscription added to a monitor.
|
||||
*/
|
||||
@Test
|
||||
public void testPreconditionMonitor() {
|
||||
final Monitor.Callback callback =
|
||||
mock(Monitor.Callback.class);
|
||||
|
||||
mCondition2.fakeUpdateCondition(true);
|
||||
final Monitor monitor = new Monitor(mExecutor, new HashSet<>(Arrays.asList(mCondition1)));
|
||||
|
||||
monitor.addSubscription(new Monitor.Subscription.Builder(callback)
|
||||
.addCondition(mCondition2)
|
||||
.build());
|
||||
|
||||
mExecutor.runAllReady();
|
||||
|
||||
verify(callback, never()).onActiveChanged(anyBoolean());
|
||||
verify(callback, never()).onConditionsChanged(anyBoolean());
|
||||
|
||||
mCondition1.fakeUpdateCondition(true);
|
||||
mExecutor.runAllReady();
|
||||
|
||||
verify(callback).onActiveChanged(eq(true));
|
||||
verify(callback).onConditionsChanged(eq(true));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* 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.util.condition;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.never;
|
||||
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.CoreStartable;
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.shared.condition.Condition;
|
||||
import com.android.systemui.shared.condition.Monitor;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
public class ConditionalCoreStartableTest extends SysuiTestCase {
|
||||
public static class FakeConditionalCoreStartable extends ConditionalCoreStartable {
|
||||
interface Callback {
|
||||
void onStart();
|
||||
void bootCompleted();
|
||||
}
|
||||
|
||||
private final Callback mCallback;
|
||||
|
||||
public FakeConditionalCoreStartable(Monitor monitor, Set<Condition> conditions,
|
||||
Callback callback) {
|
||||
super(monitor, conditions);
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
mCallback.onStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void bootCompleted() {
|
||||
mCallback.bootCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
final Set<Condition> mConditions = new HashSet<>();
|
||||
|
||||
@Mock
|
||||
Condition mCondition;
|
||||
|
||||
@Mock
|
||||
Monitor mMonitor;
|
||||
|
||||
@Mock
|
||||
FakeConditionalCoreStartable.Callback mCallback;
|
||||
|
||||
@Mock
|
||||
Monitor.Subscription.Token mSubscriptionToken;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mConditions.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that {@link ConditionalCoreStartable#onStart()} is predicated on conditions being
|
||||
* met.
|
||||
*/
|
||||
@Test
|
||||
public void testOnStartCallback() {
|
||||
final CoreStartable coreStartable =
|
||||
new FakeConditionalCoreStartable(mMonitor,
|
||||
new HashSet<>(Arrays.asList(mCondition)),
|
||||
mCallback);
|
||||
|
||||
when(mMonitor.addSubscription(any())).thenReturn(mSubscriptionToken);
|
||||
coreStartable.start();
|
||||
|
||||
final ArgumentCaptor<Monitor.Subscription> subscriptionCaptor = ArgumentCaptor.forClass(
|
||||
Monitor.Subscription.class);
|
||||
verify(mMonitor).addSubscription(subscriptionCaptor.capture());
|
||||
|
||||
final Monitor.Subscription subscription = subscriptionCaptor.getValue();
|
||||
|
||||
assertThat(subscription.getConditions()).containsExactly(mCondition);
|
||||
|
||||
verify(mCallback, never()).onStart();
|
||||
|
||||
subscription.getCallback().onConditionsChanged(true);
|
||||
|
||||
verify(mCallback).onStart();
|
||||
verify(mMonitor).removeSubscription(mSubscriptionToken);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verifies that {@link ConditionalCoreStartable#bootCompleted()} ()} is predicated on
|
||||
* conditions being met.
|
||||
*/
|
||||
@Test
|
||||
public void testBootCompleted() {
|
||||
final CoreStartable coreStartable =
|
||||
new FakeConditionalCoreStartable(mMonitor,
|
||||
new HashSet<>(Arrays.asList(mCondition)),
|
||||
mCallback);
|
||||
|
||||
when(mMonitor.addSubscription(any())).thenReturn(mSubscriptionToken);
|
||||
coreStartable.onBootCompleted();
|
||||
|
||||
final ArgumentCaptor<Monitor.Subscription> subscriptionCaptor = ArgumentCaptor.forClass(
|
||||
Monitor.Subscription.class);
|
||||
verify(mMonitor).addSubscription(subscriptionCaptor.capture());
|
||||
|
||||
final Monitor.Subscription subscription = subscriptionCaptor.getValue();
|
||||
|
||||
assertThat(subscription.getConditions()).containsExactly(mCondition);
|
||||
|
||||
verify(mCallback, never()).bootCompleted();
|
||||
|
||||
subscription.getCallback().onConditionsChanged(true);
|
||||
|
||||
verify(mCallback).bootCompleted();
|
||||
verify(mMonitor).removeSubscription(mSubscriptionToken);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user