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

Commit 6f03e628 authored by Bryce Lee's avatar Bryce Lee Committed by Android (Google) Code Review
Browse files

Merge changes I1efabf11,Ib4121d37,Ifbe498ba into tm-qpr-dev

* changes:
  UserProcessCondition Introduction.
  ConditionalCoreStartable Introduction.
  Add Preconditions and Nested Subscriptions.
parents 55f11aac 746c301e
Loading
Loading
Loading
Loading
+171 −13
Original line number Diff line number Diff line
@@ -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) {
        }
    }
}
+27 −0
Original line number Diff line number Diff line
/*
 * 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();
    }
}
+48 −0
Original line number Diff line number Diff line
/*
 * 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() {
    }
}
+84 −0
Original line number Diff line number Diff line
/*
 * 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() {
    }
}
+105 −0
Original line number Diff line number Diff line
/*
 * 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);
    }

}
Loading