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 {
|
public class Monitor {
|
||||||
private final String mTag = getClass().getSimpleName();
|
private final String mTag = getClass().getSimpleName();
|
||||||
private final Executor mExecutor;
|
private final Executor mExecutor;
|
||||||
|
private final Set<Condition> mPreconditions;
|
||||||
|
|
||||||
private final HashMap<Condition, ArraySet<Subscription.Token>> mConditions = new HashMap<>();
|
private final HashMap<Condition, ArraySet<Subscription.Token>> mConditions = new HashMap<>();
|
||||||
private final HashMap<Subscription.Token, SubscriptionState> mSubscriptions = new HashMap<>();
|
private final HashMap<Subscription.Token, SubscriptionState> mSubscriptions = new HashMap<>();
|
||||||
|
|
||||||
private static class SubscriptionState {
|
private static class SubscriptionState {
|
||||||
private final Subscription mSubscription;
|
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 mAllConditionsMet;
|
||||||
|
private boolean mActive;
|
||||||
|
|
||||||
SubscriptionState(Subscription subscription) {
|
SubscriptionState(Subscription subscription) {
|
||||||
mSubscription = subscription;
|
mSubscription = subscription;
|
||||||
@@ -54,7 +60,27 @@ public class Monitor {
|
|||||||
return mSubscription.mConditions;
|
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,
|
final Boolean result = Evaluator.INSTANCE.evaluate(mSubscription.mConditions,
|
||||||
Evaluator.OP_AND);
|
Evaluator.OP_AND);
|
||||||
// Consider unknown (null) as true
|
// Consider unknown (null) as true
|
||||||
@@ -65,7 +91,50 @@ public class Monitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mAllConditionsMet = newAllConditionsMet;
|
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
|
@Inject
|
||||||
public Monitor(@Main Executor executor) {
|
public Monitor(@Main Executor executor) {
|
||||||
|
this(executor, Collections.emptySet());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main constructor, allowing specifying preconditions.
|
||||||
|
*/
|
||||||
|
public Monitor(Executor executor, Set<Condition> preconditions) {
|
||||||
mExecutor = executor;
|
mExecutor = executor;
|
||||||
|
mPreconditions = preconditions;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateConditionMetState(Condition condition) {
|
private void updateConditionMetState(Condition condition) {
|
||||||
@@ -91,7 +171,7 @@ public class Monitor {
|
|||||||
return;
|
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.
|
* @return A {@link Subscription.Token} that can be used to remove the subscription.
|
||||||
*/
|
*/
|
||||||
public Subscription.Token addSubscription(@NonNull Subscription 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 Subscription.Token token = new Subscription.Token();
|
||||||
final SubscriptionState state = new SubscriptionState(subscription);
|
final SubscriptionState state = new SubscriptionState(normalizedCondition);
|
||||||
|
|
||||||
mExecutor.execute(() -> {
|
mExecutor.execute(() -> {
|
||||||
if (shouldLog()) Log.d(mTag, "adding subscription");
|
if (shouldLog()) Log.d(mTag, "adding subscription");
|
||||||
mSubscriptions.put(token, state);
|
mSubscriptions.put(token, state);
|
||||||
|
|
||||||
// Add and associate conditions.
|
// Add and associate conditions.
|
||||||
subscription.getConditions().stream().forEach(condition -> {
|
normalizedCondition.getConditions().stream().forEach(condition -> {
|
||||||
if (!mConditions.containsKey(condition)) {
|
if (!mConditions.containsKey(condition)) {
|
||||||
mConditions.put(condition, new ArraySet<>());
|
mConditions.put(condition, new ArraySet<>());
|
||||||
condition.addCallback(mConditionCallback);
|
condition.addCallback(mConditionCallback);
|
||||||
@@ -118,8 +208,10 @@ public class Monitor {
|
|||||||
mConditions.get(condition).add(token);
|
mConditions.get(condition).add(token);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
state.onAdded();
|
||||||
|
|
||||||
// Update subscription state.
|
// Update subscription state.
|
||||||
state.update();
|
state.update(this);
|
||||||
|
|
||||||
});
|
});
|
||||||
return token;
|
return token;
|
||||||
@@ -139,7 +231,9 @@ public class Monitor {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
mSubscriptions.remove(token).getConditions().forEach(condition -> {
|
final SubscriptionState removedSubscription = mSubscriptions.remove(token);
|
||||||
|
|
||||||
|
removedSubscription.getConditions().forEach(condition -> {
|
||||||
if (!mConditions.containsKey(condition)) {
|
if (!mConditions.containsKey(condition)) {
|
||||||
Log.e(mTag, "condition not present:" + condition);
|
Log.e(mTag, "condition not present:" + condition);
|
||||||
return;
|
return;
|
||||||
@@ -153,6 +247,8 @@ public class Monitor {
|
|||||||
mConditions.remove(condition);
|
mConditions.remove(condition);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
removedSubscription.onRemoved(this);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,12 +264,19 @@ public class Monitor {
|
|||||||
private final Set<Condition> mConditions;
|
private final Set<Condition> mConditions;
|
||||||
private final Callback mCallback;
|
private final Callback mCallback;
|
||||||
|
|
||||||
/**
|
// 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
|
||||||
public Subscription(Set<Condition> conditions, Callback callback) {
|
// 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.mConditions = Collections.unmodifiableSet(conditions);
|
||||||
this.mCallback = callback;
|
this.mCallback = callback;
|
||||||
|
this.mNestedSubscription = nestedSubscription;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<Condition> getConditions() {
|
public Set<Condition> getConditions() {
|
||||||
@@ -184,6 +287,10 @@ public class Monitor {
|
|||||||
return mCallback;
|
return mCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Subscription getNestedSubscription() {
|
||||||
|
return mNestedSubscription;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link Token} is an identifier that is associated with a {@link Subscription} which is
|
* A {@link Token} is an identifier that is associated with a {@link Subscription} which is
|
||||||
* registered with a {@link Monitor}.
|
* registered with a {@link Monitor}.
|
||||||
@@ -196,14 +303,26 @@ public class Monitor {
|
|||||||
*/
|
*/
|
||||||
public static class Builder {
|
public static class Builder {
|
||||||
private final Callback mCallback;
|
private final Callback mCallback;
|
||||||
|
private final Subscription mNestedSubscription;
|
||||||
private final ArraySet<Condition> mConditions;
|
private final ArraySet<Condition> mConditions;
|
||||||
|
private final ArraySet<Condition> mPreconditions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default constructor specifying the {@link Callback} for the {@link Subscription}.
|
* Default constructor specifying the {@link Callback} for the {@link Subscription}.
|
||||||
*/
|
*/
|
||||||
public Builder(Callback callback) {
|
public Builder(Callback callback) {
|
||||||
|
this(null, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder(Subscription nestedSubscription) {
|
||||||
|
this(nestedSubscription, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Builder(Subscription nestedSubscription, Callback callback) {
|
||||||
|
mNestedSubscription = nestedSubscription;
|
||||||
mCallback = callback;
|
mCallback = callback;
|
||||||
mConditions = new ArraySet<>();
|
mConditions = new ArraySet();
|
||||||
|
mPreconditions = new ArraySet();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -216,12 +335,39 @@ public class Monitor {
|
|||||||
return this;
|
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}.
|
* Adds a set of {@link Condition} to be associated with the {@link Subscription}.
|
||||||
*
|
*
|
||||||
* @return The updated {@link Builder}.
|
* @return The updated {@link Builder}.
|
||||||
*/
|
*/
|
||||||
public Builder addConditions(Set<Condition> condition) {
|
public Builder addConditions(Set<Condition> condition) {
|
||||||
|
if (condition == null) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
mConditions.addAll(condition);
|
mConditions.addAll(condition);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@@ -232,7 +378,11 @@ public class Monitor {
|
|||||||
* @return The resulting {@link Subscription}.
|
* @return The resulting {@link Subscription}.
|
||||||
*/
|
*/
|
||||||
public Subscription build() {
|
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.
|
* only partial conditions have been fulfilled.
|
||||||
*/
|
*/
|
||||||
void onConditionsChanged(boolean allConditionsMet);
|
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();
|
mExecutor.runAllReady();
|
||||||
verify(callback).onConditionsChanged(true);
|
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