diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/ConditionalCoreStartable.java b/packages/SystemUI/src/com/android/systemui/util/condition/ConditionalCoreStartable.java new file mode 100644 index 0000000000000..b41bca0d77e1e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/condition/ConditionalCoreStartable.java @@ -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 mConditionSet; + private Monitor.Subscription.Token mStartToken; + private Monitor.Subscription.Token mBootCompletedToken; + + public ConditionalCoreStartable(Monitor monitor) { + this(monitor, null); + } + + public ConditionalCoreStartable(Monitor monitor, Set 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() { + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionalCoreStartableTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionalCoreStartableTest.java new file mode 100644 index 0000000000000..5ef62c1e7e8dd --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionalCoreStartableTest.java @@ -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 conditions, + Callback callback) { + super(monitor, conditions); + mCallback = callback; + } + + @Override + protected void onStart() { + mCallback.onStart(); + } + + @Override + protected void bootCompleted() { + mCallback.bootCompleted(); + } + } + + + final Set 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 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 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); + } +}