summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Bryce Lee <brycelee@google.com> 2025-02-26 16:51:02 -0800
committer Bryce Lee <brycelee@google.com> 2025-03-10 22:34:56 -0700
commit6d85344a249cc1017cfda6227bded6aa11cb6933 (patch)
treea3169823e2279ce25ebee8f72959b696625c455b
parent819136b66c76334bf1950f0fd9c5ef35b462b2b8 (diff)
Make Condition#start a suspend function.
This change makes Condition#start a suspend function so that suspendable calls can be made within the function body. It also converts existing conditions to Kotlin to more easily extend start. Test: atest ConditionTest Bug: 394520234 Flag: EXEMPT refactor Change-Id: I877ec08f454d18b5b55c4005039b46ba52011429
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/DeviceInactiveConditionTest.kt5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java92
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.kt85
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java118
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/DreamConditionTest.kt106
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/process/condition/SystemProcessConditionTest.java103
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/process/condition/SystemProcessConditionTest.kt101
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt77
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java621
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionMonitorTest.kt605
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionTest.java163
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionTest.kt154
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/FakeCondition.java54
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/FakeCondition.kt46
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/condition/CombinedCondition.kt58
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/condition/Condition.kt28
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/condition/ConditionExtensions.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/DeviceInactiveCondition.java116
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/DeviceInactiveCondition.kt99
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java69
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.kt51
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java69
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.kt52
-rw-r--r--packages/SystemUI/src/com/android/systemui/lowlightclock/AmbientLightModeMonitor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/lowlightclock/DirectBootCondition.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/lowlightclock/ForceLowLightCondition.java137
-rw-r--r--packages/SystemUI/src/com/android/systemui/lowlightclock/ForceLowLightCondition.kt115
-rw-r--r--packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightCondition.java77
-rw-r--r--packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightCondition.kt65
-rw-r--r--packages/SystemUI/src/com/android/systemui/lowlightclock/ScreenSaverEnabledCondition.java87
-rw-r--r--packages/SystemUI/src/com/android/systemui/lowlightclock/ScreenSaverEnabledCondition.kt81
-rw-r--r--packages/SystemUI/src/com/android/systemui/process/condition/SystemProcessCondition.java54
-rw-r--r--packages/SystemUI/src/com/android/systemui/process/condition/SystemProcessCondition.kt39
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/lowlightclock/ForceLowLightConditionTest.java110
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/lowlightclock/ForceLowLightConditionTest.kt98
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/lowlightclock/LowLightConditionTest.java143
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/lowlightclock/LowLightConditionTest.kt157
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/lowlightclock/ScreenSaverEnabledConditionTest.java108
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/lowlightclock/ScreenSaverEnabledConditionTest.kt115
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/condition/CombinedConditionTest.kt5
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/condition/KosmosConditionTestExtensions.kt36
41 files changed, 2087 insertions, 2218 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/DeviceInactiveConditionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/DeviceInactiveConditionTest.kt
index 0c97750ba281..c98d6c531d54 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/DeviceInactiveConditionTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/DeviceInactiveConditionTest.kt
@@ -20,6 +20,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.keyguard.keyguardUpdateMonitor
import com.android.systemui.SysuiTestCase
+import com.android.systemui.condition.testStart
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP
import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE
@@ -67,7 +68,7 @@ class DeviceInactiveConditionTest : SysuiTestCase() {
fun asleep_conditionTrue() =
kosmos.runTest {
// Condition is false to start.
- underTest.start()
+ testStart(underTest)
assertThat(underTest.isConditionMet).isFalse()
// Condition is true when device goes to sleep.
@@ -79,7 +80,7 @@ class DeviceInactiveConditionTest : SysuiTestCase() {
fun dozingAndAsleep_conditionFalse() =
kosmos.runTest {
// Condition is true when device is asleep.
- underTest.start()
+ testStart(underTest)
sleep()
assertThat(underTest.isConditionMet).isTrue()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java
deleted file mode 100644
index ccadd143b582..000000000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * 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.dreams.conditions;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.assist.AssistManager;
-import com.android.systemui.assist.AssistManager.VisualQueryAttentionListener;
-import com.android.systemui.shared.condition.Condition;
-
-import kotlinx.coroutines.CoroutineScope;
-
-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;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-@android.platform.test.annotations.EnabledOnRavenwood
-public class AssistantAttentionConditionTest extends SysuiTestCase {
- @Mock
- Condition.Callback mCallback;
- @Mock
- AssistManager mAssistManager;
- @Mock
- CoroutineScope mScope;
-
- private AssistantAttentionCondition mAssistantAttentionCondition;
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
-
- mAssistantAttentionCondition = new AssistantAttentionCondition(mScope, mAssistManager);
- // Adding a callback also starts the condition.
- mAssistantAttentionCondition.addCallback(mCallback);
- }
-
- @Test
- public void testEnableVisualQueryDetection() {
- verify(mAssistManager).addVisualQueryAttentionListener(
- any(VisualQueryAttentionListener.class));
- }
-
- @Test
- public void testDisableVisualQueryDetection() {
- mAssistantAttentionCondition.stop();
- verify(mAssistManager).removeVisualQueryAttentionListener(
- any(VisualQueryAttentionListener.class));
- }
-
- @Test
- public void testAttentionChangedTriggersCondition() {
- final ArgumentCaptor<VisualQueryAttentionListener> argumentCaptor =
- ArgumentCaptor.forClass(VisualQueryAttentionListener.class);
- verify(mAssistManager).addVisualQueryAttentionListener(argumentCaptor.capture());
-
- argumentCaptor.getValue().onAttentionGained();
- assertThat(mAssistantAttentionCondition.isConditionMet()).isTrue();
-
- argumentCaptor.getValue().onAttentionLost();
- assertThat(mAssistantAttentionCondition.isConditionMet()).isFalse();
-
- verify(mCallback, times(2)).onConditionChanged(eq(mAssistantAttentionCondition));
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.kt
new file mode 100644
index 000000000000..eefc61a035c3
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2025 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.dreams.conditions
+
+import android.platform.test.annotations.EnabledOnRavenwood
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.assist.AssistManager
+import com.android.systemui.assist.AssistManager.VisualQueryAttentionListener
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.shared.condition.Condition
+import com.google.common.truth.Truth
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnabledOnRavenwood
+class AssistantAttentionConditionTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+
+ @Mock private lateinit var callback: Condition.Callback
+
+ @Mock private lateinit var assistManager: AssistManager
+
+ private lateinit var underTest: AssistantAttentionCondition
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ underTest = AssistantAttentionCondition(kosmos.testScope, assistManager)
+ // Adding a callback also starts the condition.
+ underTest.addCallback(callback)
+ }
+
+ @Test
+ fun testEnableVisualQueryDetection() =
+ kosmos.runTest { Mockito.verify(assistManager).addVisualQueryAttentionListener(any()) }
+
+ @Test
+ fun testDisableVisualQueryDetection() =
+ kosmos.runTest {
+ underTest.stop()
+ Mockito.verify(assistManager).removeVisualQueryAttentionListener(any())
+ }
+
+ @Test
+ fun testAttentionChangedTriggersCondition() =
+ kosmos.runTest {
+ val argumentCaptor = argumentCaptor<VisualQueryAttentionListener>()
+ Mockito.verify(assistManager).addVisualQueryAttentionListener(argumentCaptor.capture())
+
+ argumentCaptor.lastValue.onAttentionGained()
+ Truth.assertThat(underTest.isConditionMet).isTrue()
+
+ argumentCaptor.lastValue.onAttentionLost()
+ Truth.assertThat(underTest.isConditionMet).isFalse()
+
+ Mockito.verify(callback, Mockito.times(2)).onConditionChanged(eq(underTest))
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java
deleted file mode 100644
index 58c17e2a7286..000000000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * 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.dreams.conditions;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.DreamManager;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.shared.condition.Condition;
-
-import kotlinx.coroutines.CoroutineScope;
-
-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;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-@android.platform.test.annotations.EnabledOnRavenwood
-public class DreamConditionTest extends SysuiTestCase {
- @Mock
- Condition.Callback mCallback;
-
- @Mock
- DreamManager mDreamManager;
-
- @Mock
- KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-
- @Mock
- CoroutineScope mScope;
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- }
-
- /**
- * Ensure a dreaming state immediately triggers the condition.
- */
- @Test
- public void testInitialDreamingState() {
- when(mDreamManager.isDreaming()).thenReturn(true);
- final DreamCondition condition = new DreamCondition(mScope, mDreamManager,
- mKeyguardUpdateMonitor);
- condition.addCallback(mCallback);
-
- verify(mCallback).onConditionChanged(eq(condition));
- assertThat(condition.isConditionMet()).isTrue();
- }
-
- /**
- * Ensure a non-dreaming state does not trigger the condition.
- */
- @Test
- public void testInitialNonDreamingState() {
- when(mDreamManager.isDreaming()).thenReturn(false);
- final DreamCondition condition = new DreamCondition(mScope, mDreamManager,
- mKeyguardUpdateMonitor);
- condition.addCallback(mCallback);
-
- verify(mCallback, never()).onConditionChanged(eq(condition));
- assertThat(condition.isConditionMet()).isFalse();
- }
-
- /**
- * Ensure that changing dream state triggers condition.
- */
- @Test
- public void testChange() {
- final ArgumentCaptor<KeyguardUpdateMonitorCallback> callbackCaptor =
- ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class);
- when(mDreamManager.isDreaming()).thenReturn(true);
- final DreamCondition condition = new DreamCondition(mScope, mDreamManager,
- mKeyguardUpdateMonitor);
- condition.addCallback(mCallback);
- verify(mKeyguardUpdateMonitor).registerCallback(callbackCaptor.capture());
-
- clearInvocations(mCallback);
- callbackCaptor.getValue().onDreamingStateChanged(false);
- verify(mCallback).onConditionChanged(eq(condition));
- assertThat(condition.isConditionMet()).isFalse();
-
- clearInvocations(mCallback);
- callbackCaptor.getValue().onDreamingStateChanged(true);
- verify(mCallback).onConditionChanged(eq(condition));
- assertThat(condition.isConditionMet()).isTrue();
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/DreamConditionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/DreamConditionTest.kt
new file mode 100644
index 000000000000..302a6e1f432f
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/DreamConditionTest.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2025 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.dreams.conditions
+
+import android.app.DreamManager
+import android.platform.test.annotations.EnabledOnRavenwood
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.runCurrent
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.shared.condition.Condition
+import com.google.common.truth.Truth
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnabledOnRavenwood
+class DreamConditionTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+
+ @Mock private lateinit var callback: Condition.Callback
+
+ @Mock private lateinit var dreamManager: DreamManager
+
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ /** Ensure a dreaming state immediately triggers the condition. */
+ @Test
+ fun testInitialDreamingState() =
+ kosmos.runTest {
+ whenever(dreamManager.isDreaming).thenReturn(true)
+ val condition = DreamCondition(testScope, dreamManager, keyguardUpdateMonitor)
+ condition.addCallback(callback)
+ runCurrent()
+
+ Mockito.verify(callback).onConditionChanged(eq(condition))
+ Truth.assertThat(condition.isConditionMet).isTrue()
+ }
+
+ /** Ensure a non-dreaming state does not trigger the condition. */
+ @Test
+ fun testInitialNonDreamingState() =
+ kosmos.runTest {
+ whenever(dreamManager.isDreaming).thenReturn(false)
+ val condition = DreamCondition(testScope, dreamManager, keyguardUpdateMonitor)
+ condition.addCallback(callback)
+
+ Mockito.verify(callback, Mockito.never()).onConditionChanged(eq(condition))
+ Truth.assertThat(condition.isConditionMet).isFalse()
+ }
+
+ /** Ensure that changing dream state triggers condition. */
+ @Test
+ fun testChange() =
+ kosmos.runTest {
+ val callbackCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
+ whenever(dreamManager.isDreaming).thenReturn(true)
+ val condition = DreamCondition(testScope, dreamManager, keyguardUpdateMonitor)
+ condition.addCallback(callback)
+ runCurrent()
+ Mockito.verify(keyguardUpdateMonitor).registerCallback(callbackCaptor.capture())
+
+ Mockito.clearInvocations(callback)
+ callbackCaptor.lastValue.onDreamingStateChanged(false)
+ runCurrent()
+ Mockito.verify(callback).onConditionChanged(eq(condition))
+ Truth.assertThat(condition.isConditionMet).isFalse()
+
+ Mockito.clearInvocations(callback)
+ callbackCaptor.lastValue.onDreamingStateChanged(true)
+ runCurrent()
+ Mockito.verify(callback).onConditionChanged(eq(condition))
+ Truth.assertThat(condition.isConditionMet).isTrue()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/process/condition/SystemProcessConditionTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/process/condition/SystemProcessConditionTest.java
deleted file mode 100644
index 5250d56edc0b..000000000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/process/condition/SystemProcessConditionTest.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * 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.TestableLooper;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.process.ProcessWrapper;
-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 kotlinx.coroutines.CoroutineScope;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@RunWith(AndroidJUnit4.class)
-@TestableLooper.RunWithLooper
-@SmallTest
-public class SystemProcessConditionTest extends SysuiTestCase {
- @Mock
- ProcessWrapper mProcessWrapper;
-
- @Mock
- Monitor.Callback mCallback;
-
- @Mock
- CoroutineScope mScope;
-
- private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- }
-
- /**
- * Verifies condition reports false when tracker reports the process is being ran by the
- * system user.
- */
- @Test
- public void testConditionFailsWithNonSystemProcess() {
-
- final Condition condition = new SystemProcessCondition(mScope, mProcessWrapper);
- when(mProcessWrapper.isSystemUser()).thenReturn(false);
-
- 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 true when tracker reports the process is being ran by the
- * system user.
- */
- @Test
- public void testConditionSucceedsWithSystemProcess() {
-
- final Condition condition = new SystemProcessCondition(mScope, mProcessWrapper);
- when(mProcessWrapper.isSystemUser()).thenReturn(true);
-
- final Monitor monitor = new Monitor(mExecutor);
-
- monitor.addSubscription(new Monitor.Subscription.Builder(mCallback)
- .addCondition(condition)
- .build());
-
- mExecutor.runAllReady();
-
- verify(mCallback).onConditionsChanged(true);
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/process/condition/SystemProcessConditionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/process/condition/SystemProcessConditionTest.kt
new file mode 100644
index 000000000000..21524d97d7af
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/process/condition/SystemProcessConditionTest.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2025 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 android.testing.TestableLooper.RunWithLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.runCurrent
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.process.ProcessWrapper
+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.Mockito
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.whenever
+
+@RunWith(AndroidJUnit4::class)
+@RunWithLooper
+@SmallTest
+class SystemProcessConditionTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+
+ @Mock private lateinit var processWrapper: ProcessWrapper
+
+ @Mock private lateinit var callback: Monitor.Callback
+
+ private val executor = FakeExecutor(FakeSystemClock())
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ /**
+ * Verifies condition reports false when tracker reports the process is being ran by the system
+ * user.
+ */
+ @Test
+ fun testConditionFailsWithNonSystemProcess() =
+ kosmos.runTest {
+ val condition: Condition = SystemProcessCondition(kosmos.testScope, processWrapper)
+ whenever(processWrapper.isSystemUser).thenReturn(false)
+
+ val monitor = Monitor(executor)
+
+ monitor.addSubscription(
+ Monitor.Subscription.Builder(callback).addCondition(condition).build()
+ )
+
+ executor.runAllReady()
+ runCurrent()
+ executor.runAllReady()
+
+ Mockito.verify(callback).onConditionsChanged(false)
+ }
+
+ /**
+ * Verifies condition reports true when tracker reports the process is being ran by the system
+ * user.
+ */
+ @Test
+ fun testConditionSucceedsWithSystemProcess() =
+ kosmos.runTest {
+ val condition: Condition = SystemProcessCondition(testScope, processWrapper)
+ whenever(processWrapper.isSystemUser).thenReturn(true)
+
+ val monitor = Monitor(executor)
+
+ monitor.addSubscription(
+ Monitor.Subscription.Builder(callback).addCondition(condition).build()
+ )
+
+ executor.runAllReady()
+ runCurrent()
+ executor.runAllReady()
+
+ Mockito.verify(callback).onConditionsChanged(true)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt
index 17509dc6a80f..8e2d1b5a88f6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt
@@ -3,64 +3,57 @@ package com.android.systemui.shared.condition
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.condition.testStart
+import com.android.systemui.condition.testStop
+import com.android.systemui.kosmos.runCurrent
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
class ConditionExtensionsTest : SysuiTestCase() {
- private lateinit var testScope: TestScope
- private val testCallback =
- Condition.Callback {
- // This is a no-op
- }
-
- @Before
- fun setUp() {
- testScope = TestScope(UnconfinedTestDispatcher())
- }
+ private val kosmos = testKosmos()
@Test
fun flowInitiallyTrue() =
- testScope.runTest {
+ kosmos.runTest {
val flow = flowOf(true)
- val condition = flow.toCondition(scope = this, Condition.START_EAGERLY)
+ val condition = flow.toCondition(scope = testScope, Condition.START_EAGERLY)
assertThat(condition.isConditionSet).isFalse()
- condition.testStart()
+ testStart(condition)
assertThat(condition.isConditionSet).isTrue()
assertThat(condition.isConditionMet).isTrue()
}
@Test
fun flowInitiallyFalse() =
- testScope.runTest {
+ kosmos.runTest {
val flow = flowOf(false)
- val condition = flow.toCondition(scope = this, Condition.START_EAGERLY)
+ val condition = flow.toCondition(scope = testScope, Condition.START_EAGERLY)
assertThat(condition.isConditionSet).isFalse()
- condition.testStart()
+ testStart(condition)
assertThat(condition.isConditionSet).isTrue()
assertThat(condition.isConditionMet).isFalse()
}
@Test
fun emptyFlowWithNoInitialValue() =
- testScope.runTest {
+ kosmos.runTest {
val flow = emptyFlow<Boolean>()
- val condition = flow.toCondition(scope = this, Condition.START_EAGERLY)
- condition.testStop()
+ val condition = flow.toCondition(scope = testScope, Condition.START_EAGERLY)
+ testStop(condition)
assertThat(condition.isConditionSet).isFalse()
assertThat(condition.isConditionMet).isFalse()
@@ -68,15 +61,15 @@ class ConditionExtensionsTest : SysuiTestCase() {
@Test
fun emptyFlowWithInitialValueOfTrue() =
- testScope.runTest {
+ kosmos.runTest {
val flow = emptyFlow<Boolean>()
val condition =
flow.toCondition(
- scope = this,
+ scope = testScope,
strategy = Condition.START_EAGERLY,
initialValue = true,
)
- condition.testStart()
+ testStart(condition)
assertThat(condition.isConditionSet).isTrue()
assertThat(condition.isConditionMet).isTrue()
@@ -84,15 +77,15 @@ class ConditionExtensionsTest : SysuiTestCase() {
@Test
fun emptyFlowWithInitialValueOfFalse() =
- testScope.runTest {
+ kosmos.runTest {
val flow = emptyFlow<Boolean>()
val condition =
flow.toCondition(
- scope = this,
+ scope = testScope,
strategy = Condition.START_EAGERLY,
initialValue = false,
)
- condition.testStart()
+ testStart(condition)
assertThat(condition.isConditionSet).isTrue()
assertThat(condition.isConditionMet).isFalse()
@@ -100,42 +93,36 @@ class ConditionExtensionsTest : SysuiTestCase() {
@Test
fun conditionUpdatesWhenFlowEmitsNewValue() =
- testScope.runTest {
+ kosmos.runTest {
val flow = MutableStateFlow(false)
- val condition = flow.toCondition(scope = this, strategy = Condition.START_EAGERLY)
- condition.testStart()
+ val condition = flow.toCondition(scope = testScope, strategy = Condition.START_EAGERLY)
+ testStart(condition)
assertThat(condition.isConditionSet).isTrue()
assertThat(condition.isConditionMet).isFalse()
flow.value = true
+ runCurrent()
assertThat(condition.isConditionMet).isTrue()
flow.value = false
+ runCurrent()
assertThat(condition.isConditionMet).isFalse()
- condition.testStop()
+ testStop(condition)
}
@Test
fun stoppingConditionUnsubscribesFromFlow() =
- testScope.runTest {
+ kosmos.runTest {
val flow = MutableSharedFlow<Boolean>()
- val condition = flow.toCondition(scope = this, strategy = Condition.START_EAGERLY)
+ val condition = flow.toCondition(scope = testScope, strategy = Condition.START_EAGERLY)
assertThat(flow.subscriptionCount.value).isEqualTo(0)
- condition.testStart()
+ testStart(condition)
assertThat(flow.subscriptionCount.value).isEqualTo(1)
- condition.testStop()
+ testStop(condition)
assertThat(flow.subscriptionCount.value).isEqualTo(0)
}
-
- fun Condition.testStart() {
- addCallback(testCallback)
- }
-
- fun Condition.testStop() {
- removeCallback(testCallback)
- }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java
deleted file mode 100644
index 267f22bb2569..000000000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java
+++ /dev/null
@@ -1,621 +0,0 @@
-/*
- * Copyright (C) 2022 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.shared.condition;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.anyBoolean;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.plugins.log.TableLogBufferBase;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
-
-import kotlinx.coroutines.CoroutineScope;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class ConditionMonitorTest extends SysuiTestCase {
- private FakeCondition mCondition1;
- private FakeCondition mCondition2;
- private FakeCondition mCondition3;
- private HashSet<Condition> mConditions;
- private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
-
- @Mock
- private CoroutineScope mScope;
- @Mock
- private TableLogBufferBase mLogBuffer;
-
- private Monitor mConditionMonitor;
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
-
- mCondition1 = spy(new FakeCondition(mScope));
- mCondition2 = spy(new FakeCondition(mScope));
- mCondition3 = spy(new FakeCondition(mScope));
- mConditions = new HashSet<>(Arrays.asList(mCondition1, mCondition2, mCondition3));
-
- mConditionMonitor = new Monitor(mExecutor);
- }
-
- public Monitor.Subscription.Builder getDefaultBuilder(
- Monitor.Callback callback) {
- return new Monitor.Subscription.Builder(callback)
- .addConditions(mConditions);
- }
-
- private Condition createMockCondition() {
- final Condition condition = Mockito.mock(
- Condition.class);
- when(condition.isConditionSet()).thenReturn(true);
- return condition;
- }
-
- @Test
- public void testOverridingCondition() {
- final Condition overridingCondition = createMockCondition();
- final Condition regularCondition = createMockCondition();
- final Monitor.Callback callback = Mockito.mock(
- Monitor.Callback.class);
-
- final Monitor.Callback referenceCallback = Mockito.mock(
- Monitor.Callback.class);
-
- final Monitor
- monitor = new Monitor(mExecutor);
-
- monitor.addSubscription(getDefaultBuilder(callback)
- .addCondition(overridingCondition)
- .addCondition(regularCondition)
- .build());
-
- monitor.addSubscription(getDefaultBuilder(referenceCallback)
- .addCondition(regularCondition)
- .build());
-
- mExecutor.runAllReady();
-
- when(overridingCondition.isOverridingCondition()).thenReturn(true);
- when(overridingCondition.isConditionMet()).thenReturn(true);
- when(regularCondition.isConditionMet()).thenReturn(false);
-
- final ArgumentCaptor<Condition.Callback> mCallbackCaptor =
- ArgumentCaptor.forClass(Condition.Callback.class);
-
- verify(overridingCondition).addCallback(mCallbackCaptor.capture());
-
- mCallbackCaptor.getValue().onConditionChanged(overridingCondition);
- mExecutor.runAllReady();
-
- verify(callback).onConditionsChanged(eq(true));
- verify(referenceCallback).onConditionsChanged(eq(false));
- Mockito.clearInvocations(callback);
- Mockito.clearInvocations(referenceCallback);
-
- when(regularCondition.isConditionMet()).thenReturn(true);
- when(overridingCondition.isConditionMet()).thenReturn(false);
-
- mCallbackCaptor.getValue().onConditionChanged(overridingCondition);
- mExecutor.runAllReady();
-
- verify(callback).onConditionsChanged(eq(false));
- verify(referenceCallback, never()).onConditionsChanged(anyBoolean());
- }
-
- /**
- * Ensures that when multiple overriding conditions are present, it is the aggregate of those
- * conditions that are considered.
- */
- @Test
- public void testMultipleOverridingConditions() {
- final Condition overridingCondition = createMockCondition();
- final Condition overridingCondition2 = createMockCondition();
- final Condition regularCondition = createMockCondition();
- final Monitor.Callback callback = Mockito.mock(
- Monitor.Callback.class);
-
- final Monitor
- monitor = new Monitor(mExecutor);
-
- monitor.addSubscription(getDefaultBuilder(callback)
- .addCondition(overridingCondition)
- .addCondition(overridingCondition2)
- .build());
-
- mExecutor.runAllReady();
-
- when(overridingCondition.isOverridingCondition()).thenReturn(true);
- when(overridingCondition.isConditionMet()).thenReturn(true);
- when(overridingCondition2.isOverridingCondition()).thenReturn(true);
- when(overridingCondition.isConditionMet()).thenReturn(false);
- when(regularCondition.isConditionMet()).thenReturn(true);
-
- final ArgumentCaptor<Condition.Callback> mCallbackCaptor =
- ArgumentCaptor.forClass(Condition.Callback.class);
-
- verify(overridingCondition).addCallback(mCallbackCaptor.capture());
-
- mCallbackCaptor.getValue().onConditionChanged(overridingCondition);
- mExecutor.runAllReady();
-
- verify(callback).onConditionsChanged(eq(false));
- Mockito.clearInvocations(callback);
- }
-
- // Ensure that updating a callback that is removed doesn't result in an exception due to the
- // absence of the condition.
- @Test
- public void testUpdateRemovedCallback() {
- final Monitor.Callback callback1 =
- mock(Monitor.Callback.class);
- final Monitor.Subscription.Token subscription1 =
- mConditionMonitor.addSubscription(getDefaultBuilder(callback1).build());
- ArgumentCaptor<Condition.Callback> monitorCallback =
- ArgumentCaptor.forClass(Condition.Callback.class);
- mExecutor.runAllReady();
- verify(mCondition1).addCallback(monitorCallback.capture());
- // This will execute first before the handler for onConditionChanged.
- mConditionMonitor.removeSubscription(subscription1);
- monitorCallback.getValue().onConditionChanged(mCondition1);
- mExecutor.runAllReady();
- }
-
- @Test
- public void addCallback_addFirstCallback_addCallbackToAllConditions() {
- final Monitor.Callback callback1 =
- mock(Monitor.Callback.class);
- mConditionMonitor.addSubscription(getDefaultBuilder(callback1).build());
- mExecutor.runAllReady();
- mConditions.forEach(condition -> verify(condition).addCallback(any()));
-
- final Monitor.Callback callback2 =
- mock(Monitor.Callback.class);
- mConditionMonitor.addSubscription(getDefaultBuilder(callback2).build());
- mExecutor.runAllReady();
- mConditions.forEach(condition -> verify(condition, times(1)).addCallback(any()));
- }
-
- @Test
- public void addCallback_addFirstCallback_reportWithDefaultValue() {
- final Monitor.Callback callback =
- mock(Monitor.Callback.class);
- mConditionMonitor.addSubscription(getDefaultBuilder(callback).build());
- mExecutor.runAllReady();
- verify(callback).onConditionsChanged(false);
- }
-
- @Test
- public void addCallback_addSecondCallback_reportWithExistingValue() {
- final Monitor.Callback callback1 =
- mock(Monitor.Callback.class);
- final Condition condition = mock(
- Condition.class);
- when(condition.isConditionMet()).thenReturn(true);
- final Monitor
- monitor = new Monitor(mExecutor);
- monitor.addSubscription(new Monitor.Subscription.Builder(callback1)
- .addCondition(condition)
- .build());
-
- final Monitor.Callback callback2 =
- mock(Monitor.Callback.class);
- monitor.addSubscription(new Monitor.Subscription.Builder(callback2)
- .addCondition(condition)
- .build());
- mExecutor.runAllReady();
- verify(callback2).onConditionsChanged(eq(true));
- }
-
- @Test
- public void addCallback_noConditions_reportAllConditionsMet() {
- final Monitor
- monitor = new Monitor(mExecutor);
- final Monitor.Callback callback = mock(
- Monitor.Callback.class);
-
- monitor.addSubscription(new Monitor.Subscription.Builder(callback).build());
- mExecutor.runAllReady();
- verify(callback).onConditionsChanged(true);
- }
-
- @Test
- public void addCallback_preCondition_noConditions_reportAllConditionsMet() {
- final Monitor
- monitor = new Monitor(mExecutor, new HashSet<>(Arrays.asList(mCondition1)));
- final Monitor.Callback callback = mock(
- Monitor.Callback.class);
-
- monitor.addSubscription(new Monitor.Subscription.Builder(callback).build());
- mExecutor.runAllReady();
- verify(callback, never()).onConditionsChanged(true);
- mCondition1.fakeUpdateCondition(true);
- mExecutor.runAllReady();
- verify(callback).onConditionsChanged(true);
- }
-
- @Test
- public void removeCallback_noFailureOnDoubleRemove() {
- final Condition condition = mock(
- Condition.class);
- final Monitor
- monitor = new Monitor(mExecutor);
- final Monitor.Callback callback =
- mock(Monitor.Callback.class);
- final Monitor.Subscription.Token token = monitor.addSubscription(
- new Monitor.Subscription.Builder(callback).addCondition(condition).build()
- );
- monitor.removeSubscription(token);
- mExecutor.runAllReady();
- // Ensure second removal doesn't cause an exception.
- monitor.removeSubscription(token);
- mExecutor.runAllReady();
- }
-
- @Test
- public void removeCallback_shouldNoLongerReceiveUpdate() {
- final Condition condition = mock(
- Condition.class);
- final Monitor
- monitor = new Monitor(mExecutor);
- final Monitor.Callback callback =
- mock(Monitor.Callback.class);
- final Monitor.Subscription.Token token = monitor.addSubscription(
- new Monitor.Subscription.Builder(callback).addCondition(condition).build()
- );
- monitor.removeSubscription(token);
- mExecutor.runAllReady();
- clearInvocations(callback);
-
- final ArgumentCaptor<Condition.Callback> conditionCallbackCaptor =
- ArgumentCaptor.forClass(Condition.Callback.class);
- verify(condition).addCallback(conditionCallbackCaptor.capture());
-
- final Condition.Callback conditionCallback = conditionCallbackCaptor.getValue();
- verify(condition).removeCallback(conditionCallback);
- }
-
- @Test
- public void removeCallback_removeLastCallback_removeCallbackFromAllConditions() {
- final Monitor.Callback callback1 =
- mock(Monitor.Callback.class);
- final Monitor.Callback callback2 =
- mock(Monitor.Callback.class);
- final Monitor.Subscription.Token subscription1 =
- mConditionMonitor.addSubscription(getDefaultBuilder(callback1).build());
- final Monitor.Subscription.Token subscription2 =
- mConditionMonitor.addSubscription(getDefaultBuilder(callback2).build());
-
- mConditionMonitor.removeSubscription(subscription1);
- mExecutor.runAllReady();
- mConditions.forEach(condition -> verify(condition, never()).removeCallback(any()));
-
- mConditionMonitor.removeSubscription(subscription2);
- mExecutor.runAllReady();
- mConditions.forEach(condition -> verify(condition).removeCallback(any()));
- }
-
- @Test
- public void updateCallbacks_allConditionsMet_reportTrue() {
- final Monitor.Callback callback =
- mock(Monitor.Callback.class);
- mConditionMonitor.addSubscription(getDefaultBuilder(callback).build());
- clearInvocations(callback);
-
- mCondition1.fakeUpdateCondition(true);
- mCondition2.fakeUpdateCondition(true);
- mCondition3.fakeUpdateCondition(true);
- mExecutor.runAllReady();
-
- verify(callback).onConditionsChanged(true);
- }
-
- @Test
- public void updateCallbacks_oneConditionStoppedMeeting_reportFalse() {
- final Monitor.Callback callback =
- mock(Monitor.Callback.class);
- mConditionMonitor.addSubscription(getDefaultBuilder(callback).build());
-
- mCondition1.fakeUpdateCondition(true);
- mCondition2.fakeUpdateCondition(true);
- mCondition3.fakeUpdateCondition(true);
- clearInvocations(callback);
-
- mCondition1.fakeUpdateCondition(false);
- mExecutor.runAllReady();
- verify(callback).onConditionsChanged(false);
- }
-
- @Test
- public void updateCallbacks_shouldOnlyUpdateWhenValueChanges() {
- final Monitor.Callback callback =
- mock(Monitor.Callback.class);
- mConditionMonitor.addSubscription(getDefaultBuilder(callback).build());
- mExecutor.runAllReady();
- verify(callback).onConditionsChanged(false);
- clearInvocations(callback);
-
- mCondition1.fakeUpdateCondition(true);
- mExecutor.runAllReady();
- verify(callback, never()).onConditionsChanged(anyBoolean());
-
- mCondition2.fakeUpdateCondition(true);
- mExecutor.runAllReady();
- verify(callback, never()).onConditionsChanged(anyBoolean());
-
- mCondition3.fakeUpdateCondition(true);
- mExecutor.runAllReady();
- verify(callback).onConditionsChanged(true);
- }
-
- @Test
- public void clearCondition_shouldUpdateValue() {
- mCondition1.fakeUpdateCondition(false);
- mCondition2.fakeUpdateCondition(true);
- mCondition3.fakeUpdateCondition(true);
-
- final Monitor.Callback callback =
- mock(Monitor.Callback.class);
- mConditionMonitor.addSubscription(getDefaultBuilder(callback).build());
- mExecutor.runAllReady();
- verify(callback).onConditionsChanged(false);
-
- mCondition1.clearCondition();
- mExecutor.runAllReady();
- verify(callback).onConditionsChanged(true);
- }
-
- @Test
- public void unsetCondition_shouldNotAffectValue() {
- final FakeCondition settableCondition = new FakeCondition(mScope, null, false);
- mCondition1.fakeUpdateCondition(true);
- mCondition2.fakeUpdateCondition(true);
- mCondition3.fakeUpdateCondition(true);
-
- final Monitor.Callback callback =
- mock(Monitor.Callback.class);
-
- mConditionMonitor.addSubscription(getDefaultBuilder(callback)
- .addCondition(settableCondition)
- .build());
-
- mExecutor.runAllReady();
- verify(callback).onConditionsChanged(true);
- }
-
- @Test
- public void setUnsetCondition_shouldAffectValue() {
- final FakeCondition settableCondition = new FakeCondition(mScope, null, false);
- mCondition1.fakeUpdateCondition(true);
- mCondition2.fakeUpdateCondition(true);
- mCondition3.fakeUpdateCondition(true);
-
- final Monitor.Callback callback =
- mock(Monitor.Callback.class);
-
- mConditionMonitor.addSubscription(getDefaultBuilder(callback)
- .addCondition(settableCondition)
- .build());
-
- mExecutor.runAllReady();
- verify(callback).onConditionsChanged(true);
- clearInvocations(callback);
-
- settableCondition.fakeUpdateCondition(false);
- mExecutor.runAllReady();
- verify(callback).onConditionsChanged(false);
- clearInvocations(callback);
-
-
- settableCondition.clearCondition();
- mExecutor.runAllReady();
- verify(callback).onConditionsChanged(true);
- }
-
- @Test
- public void clearingOverridingCondition_shouldBeExcluded() {
- final FakeCondition overridingCondition = new FakeCondition(mScope, true, true);
- mCondition1.fakeUpdateCondition(false);
- mCondition2.fakeUpdateCondition(false);
- mCondition3.fakeUpdateCondition(false);
-
- final Monitor.Callback callback =
- mock(Monitor.Callback.class);
-
- mConditionMonitor.addSubscription(getDefaultBuilder(callback)
- .addCondition(overridingCondition)
- .build());
-
- mExecutor.runAllReady();
- verify(callback).onConditionsChanged(true);
- clearInvocations(callback);
-
- overridingCondition.clearCondition();
- mExecutor.runAllReady();
- verify(callback).onConditionsChanged(false);
- }
-
- @Test
- public void settingUnsetOverridingCondition_shouldBeIncluded() {
- final FakeCondition overridingCondition = new FakeCondition(mScope, null, true);
- mCondition1.fakeUpdateCondition(false);
- mCondition2.fakeUpdateCondition(false);
- mCondition3.fakeUpdateCondition(false);
-
- final Monitor.Callback callback =
- mock(Monitor.Callback.class);
-
- mConditionMonitor.addSubscription(getDefaultBuilder(callback)
- .addCondition(overridingCondition)
- .build());
-
- mExecutor.runAllReady();
- verify(callback).onConditionsChanged(false);
- clearInvocations(callback);
-
- overridingCondition.fakeUpdateCondition(true);
- 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());
- }
-
- /**
- * 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));
- }
-
- @Test
- public void testLoggingCallback() {
- final Monitor monitor = new Monitor(mExecutor, Collections.emptySet(), mLogBuffer);
-
- final FakeCondition condition = new FakeCondition(mScope);
- final FakeCondition overridingCondition = new FakeCondition(
- mScope,
- /* initialValue= */ false,
- /* overriding= */ true);
-
- final Monitor.Callback callback = mock(Monitor.Callback.class);
- monitor.addSubscription(getDefaultBuilder(callback)
- .addCondition(condition)
- .addCondition(overridingCondition)
- .build());
- mExecutor.runAllReady();
-
- // condition set to true
- condition.fakeUpdateCondition(true);
- mExecutor.runAllReady();
- verify(mLogBuffer).logChange("", "FakeCondition", "True");
-
- // condition set to false
- condition.fakeUpdateCondition(false);
- mExecutor.runAllReady();
- verify(mLogBuffer).logChange("", "FakeCondition", "False");
-
- // condition unset
- condition.fakeClearCondition();
- mExecutor.runAllReady();
- verify(mLogBuffer).logChange("", "FakeCondition", "Invalid");
-
- // overriding condition set to true
- overridingCondition.fakeUpdateCondition(true);
- mExecutor.runAllReady();
- verify(mLogBuffer).logChange("", "FakeCondition[OVRD]", "True");
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionMonitorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionMonitorTest.kt
new file mode 100644
index 000000000000..dc45c5317b8e
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionMonitorTest.kt
@@ -0,0 +1,605 @@
+/*
+ * Copyright (C) 2025 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.shared.condition
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.log.TableLogBufferBase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import java.util.Arrays
+import java.util.function.Consumer
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ConditionMonitorTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+
+ private lateinit var condition1: FakeCondition
+ private lateinit var condition2: FakeCondition
+ private lateinit var condition3: FakeCondition
+ private lateinit var conditions: HashSet<Condition>
+ private val executor = FakeExecutor(FakeSystemClock())
+
+ @Mock private lateinit var logBuffer: TableLogBufferBase
+
+ private lateinit var conditionMonitor: Monitor
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ condition1 = Mockito.spy(FakeCondition(kosmos.testScope))
+ condition2 = Mockito.spy(FakeCondition(kosmos.testScope))
+ condition3 = Mockito.spy(FakeCondition(kosmos.testScope))
+ conditions = HashSet(listOf(condition1, condition2, condition3))
+
+ conditionMonitor = Monitor(executor)
+ }
+
+ fun getDefaultBuilder(callback: Monitor.Callback): Monitor.Subscription.Builder {
+ return Monitor.Subscription.Builder(callback).addConditions(conditions)
+ }
+
+ private fun createMockCondition(): Condition {
+ val condition: Condition = mock()
+ whenever(condition.isConditionSet).thenReturn(true)
+ return condition
+ }
+
+ @Test
+ fun testOverridingCondition() =
+ kosmos.runTest {
+ val overridingCondition = createMockCondition()
+ val regularCondition = createMockCondition()
+ val callback: Monitor.Callback = mock()
+ val referenceCallback: Monitor.Callback = mock()
+
+ val monitor = Monitor(executor)
+
+ monitor.addSubscription(
+ getDefaultBuilder(callback)
+ .addCondition(overridingCondition)
+ .addCondition(regularCondition)
+ .build()
+ )
+
+ monitor.addSubscription(
+ getDefaultBuilder(referenceCallback).addCondition(regularCondition).build()
+ )
+
+ executor.runAllReady()
+
+ whenever(overridingCondition.isOverridingCondition).thenReturn(true)
+ whenever(overridingCondition.isConditionMet).thenReturn(true)
+ whenever(regularCondition.isConditionMet).thenReturn(false)
+
+ val callbackCaptor = argumentCaptor<Condition.Callback>()
+
+ Mockito.verify(overridingCondition).addCallback(callbackCaptor.capture())
+
+ callbackCaptor.lastValue.onConditionChanged(overridingCondition)
+ executor.runAllReady()
+
+ Mockito.verify(callback).onConditionsChanged(eq(true))
+ Mockito.verify(referenceCallback).onConditionsChanged(eq(false))
+ Mockito.clearInvocations(callback)
+ Mockito.clearInvocations(referenceCallback)
+
+ whenever(regularCondition.isConditionMet).thenReturn(true)
+ whenever(overridingCondition.isConditionMet).thenReturn(false)
+
+ callbackCaptor.lastValue.onConditionChanged(overridingCondition)
+ executor.runAllReady()
+
+ Mockito.verify(callback).onConditionsChanged(eq(false))
+ Mockito.verify(referenceCallback, Mockito.never()).onConditionsChanged(anyBoolean())
+ }
+
+ /**
+ * Ensures that when multiple overriding conditions are present, it is the aggregate of those
+ * conditions that are considered.
+ */
+ @Test
+ fun testMultipleOverridingConditions() =
+ kosmos.runTest {
+ val overridingCondition = createMockCondition()
+ val overridingCondition2 = createMockCondition()
+ val regularCondition = createMockCondition()
+ val callback: Monitor.Callback = mock()
+
+ val monitor = Monitor(executor)
+
+ monitor.addSubscription(
+ getDefaultBuilder(callback)
+ .addCondition(overridingCondition)
+ .addCondition(overridingCondition2)
+ .build()
+ )
+
+ executor.runAllReady()
+
+ whenever(overridingCondition.isOverridingCondition).thenReturn(true)
+ whenever(overridingCondition.isConditionMet).thenReturn(true)
+ whenever(overridingCondition2.isOverridingCondition).thenReturn(true)
+ whenever(overridingCondition.isConditionMet).thenReturn(false)
+ whenever(regularCondition.isConditionMet).thenReturn(true)
+
+ val mCallbackCaptor = argumentCaptor<Condition.Callback>()
+
+ Mockito.verify(overridingCondition).addCallback(mCallbackCaptor.capture())
+
+ mCallbackCaptor.lastValue.onConditionChanged(overridingCondition)
+ executor.runAllReady()
+
+ Mockito.verify(callback).onConditionsChanged(eq(false))
+ Mockito.clearInvocations(callback)
+ }
+
+ // Ensure that updating a callback that is removed doesn't result in an exception due to the
+ // absence of the condition.
+ @Test
+ fun testUpdateRemovedCallback() =
+ kosmos.runTest {
+ val callback1: Monitor.Callback = mock()
+ val subscription1 =
+ conditionMonitor.addSubscription(getDefaultBuilder(callback1).build())
+ val monitorCallback = argumentCaptor<Condition.Callback>()
+ executor.runAllReady()
+ Mockito.verify(condition1).addCallback(monitorCallback.capture())
+ // This will execute first before the handler for onConditionChanged.
+ conditionMonitor.removeSubscription(subscription1)
+ monitorCallback.lastValue.onConditionChanged(condition1)
+ executor.runAllReady()
+ }
+
+ @Test
+ fun addCallback_addFirstCallback_addCallbackToAllConditions() {
+ val callback1: Monitor.Callback = mock()
+ conditionMonitor.addSubscription(getDefaultBuilder(callback1).build())
+ executor.runAllReady()
+ conditions.forEach(
+ Consumer { condition: Condition -> Mockito.verify(condition).addCallback(any()) }
+ )
+
+ val callback2: Monitor.Callback = mock()
+ conditionMonitor.addSubscription(getDefaultBuilder(callback2).build())
+ executor.runAllReady()
+ conditions.forEach(
+ Consumer { condition: Condition ->
+ Mockito.verify(condition, Mockito.times(1)).addCallback(any())
+ }
+ )
+ }
+
+ @Test
+ fun addCallback_addFirstCallback_reportWithDefaultValue() =
+ kosmos.runTest {
+ val callback: Monitor.Callback = mock()
+ conditionMonitor.addSubscription(getDefaultBuilder(callback).build())
+ executor.runAllReady()
+ Mockito.verify(callback).onConditionsChanged(false)
+ }
+
+ @Test
+ fun addCallback_addSecondCallback_reportWithExistingValue() =
+ kosmos.runTest {
+ val callback1: Monitor.Callback = mock()
+ val condition: Condition = mock()
+
+ whenever(condition.isConditionMet).thenReturn(true)
+ val monitor = Monitor(executor)
+ monitor.addSubscription(
+ Monitor.Subscription.Builder(callback1).addCondition(condition).build()
+ )
+
+ val callback2: Monitor.Callback = mock()
+ monitor.addSubscription(
+ Monitor.Subscription.Builder(callback2).addCondition(condition).build()
+ )
+ executor.runAllReady()
+ Mockito.verify(callback2).onConditionsChanged(eq(true))
+ }
+
+ @Test
+ fun addCallback_noConditions_reportAllConditionsMet() =
+ kosmos.runTest {
+ val monitor = Monitor(executor)
+ val callback: Monitor.Callback = mock()
+
+ monitor.addSubscription(Monitor.Subscription.Builder(callback).build())
+ executor.runAllReady()
+ Mockito.verify(callback).onConditionsChanged(true)
+ }
+
+ @Test
+ fun addCallback_preCondition_noConditions_reportAllConditionsMet() =
+ kosmos.runTest {
+ val monitor = Monitor(executor, HashSet<Condition?>(Arrays.asList(condition1)))
+ val callback: Monitor.Callback = mock()
+
+ monitor.addSubscription(Monitor.Subscription.Builder(callback).build())
+ executor.runAllReady()
+ Mockito.verify(callback, Mockito.never()).onConditionsChanged(true)
+ condition1.fakeUpdateCondition(true)
+ executor.runAllReady()
+ Mockito.verify(callback).onConditionsChanged(true)
+ }
+
+ @Test
+ fun removeCallback_noFailureOnDoubleRemove() =
+ kosmos.runTest {
+ val condition: Condition = mock()
+ val monitor = Monitor(executor)
+ val callback: Monitor.Callback = mock()
+ val token =
+ monitor.addSubscription(
+ Monitor.Subscription.Builder(callback).addCondition(condition).build()
+ )
+ monitor.removeSubscription(token)
+ executor.runAllReady()
+ // Ensure second removal doesn't cause an exception.
+ monitor.removeSubscription(token)
+ executor.runAllReady()
+ }
+
+ @Test
+ fun removeCallback_shouldNoLongerReceiveUpdate() =
+ kosmos.runTest {
+ val condition: Condition = mock()
+ val monitor = Monitor(executor)
+ val callback: Monitor.Callback = mock()
+ val token =
+ monitor.addSubscription(
+ Monitor.Subscription.Builder(callback).addCondition(condition).build()
+ )
+ monitor.removeSubscription(token)
+ executor.runAllReady()
+ Mockito.clearInvocations(callback)
+
+ val conditionCallbackCaptor = argumentCaptor<Condition.Callback>()
+ Mockito.verify(condition).addCallback(conditionCallbackCaptor.capture())
+
+ val conditionCallback = conditionCallbackCaptor.lastValue
+ Mockito.verify(condition).removeCallback(conditionCallback)
+ }
+
+ @Test
+ fun removeCallback_removeLastCallback_removeCallbackFromAllConditions() =
+ kosmos.runTest {
+ val callback1: Monitor.Callback = mock()
+ val callback2: Monitor.Callback = mock()
+ val subscription1 =
+ conditionMonitor.addSubscription(getDefaultBuilder(callback1).build())
+ val subscription2 =
+ conditionMonitor.addSubscription(getDefaultBuilder(callback2).build())
+
+ conditionMonitor.removeSubscription(subscription1)
+ executor.runAllReady()
+ conditions.forEach(
+ Consumer { condition: Condition ->
+ verify(condition, Mockito.never()).removeCallback(any())
+ }
+ )
+
+ conditionMonitor.removeSubscription(subscription2)
+ executor.runAllReady()
+ conditions.forEach(
+ Consumer { condition: Condition -> Mockito.verify(condition).removeCallback(any()) }
+ )
+ }
+
+ @Test
+ fun updateCallbacks_allConditionsMet_reportTrue() =
+ kosmos.runTest {
+ val callback: Monitor.Callback = mock()
+ conditionMonitor.addSubscription(getDefaultBuilder(callback).build())
+ Mockito.clearInvocations(callback)
+
+ condition1.fakeUpdateCondition(true)
+ condition2.fakeUpdateCondition(true)
+ condition3.fakeUpdateCondition(true)
+ executor.runAllReady()
+
+ Mockito.verify(callback).onConditionsChanged(true)
+ }
+
+ @Test
+ fun updateCallbacks_oneConditionStoppedMeeting_reportFalse() =
+ kosmos.runTest {
+ val callback: Monitor.Callback = mock()
+ conditionMonitor.addSubscription(getDefaultBuilder(callback).build())
+
+ condition1.fakeUpdateCondition(true)
+ condition2.fakeUpdateCondition(true)
+ condition3.fakeUpdateCondition(true)
+ Mockito.clearInvocations(callback)
+
+ condition1.fakeUpdateCondition(false)
+ executor.runAllReady()
+ Mockito.verify(callback).onConditionsChanged(false)
+ }
+
+ @Test
+ fun updateCallbacks_shouldOnlyUpdateWhenValueChanges() =
+ kosmos.runTest {
+ val callback: Monitor.Callback = mock()
+ conditionMonitor.addSubscription(getDefaultBuilder(callback).build())
+ executor.runAllReady()
+ Mockito.verify(callback).onConditionsChanged(false)
+ Mockito.clearInvocations(callback)
+
+ condition1.fakeUpdateCondition(true)
+ executor.runAllReady()
+ Mockito.verify(callback, Mockito.never()).onConditionsChanged(anyBoolean())
+
+ condition2.fakeUpdateCondition(true)
+ executor.runAllReady()
+ Mockito.verify(callback, Mockito.never()).onConditionsChanged(anyBoolean())
+
+ condition3.fakeUpdateCondition(true)
+ executor.runAllReady()
+ Mockito.verify(callback).onConditionsChanged(true)
+ }
+
+ @Test
+ fun clearCondition_shouldUpdateValue() =
+ kosmos.runTest {
+ condition1.fakeUpdateCondition(false)
+ condition2.fakeUpdateCondition(true)
+ condition3.fakeUpdateCondition(true)
+
+ val callback: Monitor.Callback = mock()
+ conditionMonitor.addSubscription(getDefaultBuilder(callback).build())
+ executor.runAllReady()
+ Mockito.verify(callback).onConditionsChanged(false)
+
+ condition1.clearCondition()
+ executor.runAllReady()
+ Mockito.verify(callback).onConditionsChanged(true)
+ }
+
+ @Test
+ fun unsetCondition_shouldNotAffectValue() =
+ kosmos.runTest {
+ val settableCondition = FakeCondition(testScope, null, false)
+ condition1.fakeUpdateCondition(true)
+ condition2.fakeUpdateCondition(true)
+ condition3.fakeUpdateCondition(true)
+
+ val callback: Monitor.Callback = mock()
+
+ conditionMonitor.addSubscription(
+ getDefaultBuilder(callback).addCondition(settableCondition).build()
+ )
+
+ executor.runAllReady()
+ Mockito.verify(callback).onConditionsChanged(true)
+ }
+
+ @Test
+ fun setUnsetCondition_shouldAffectValue() =
+ kosmos.runTest {
+ val settableCondition = FakeCondition(testScope, null, false)
+ condition1.fakeUpdateCondition(true)
+ condition2.fakeUpdateCondition(true)
+ condition3.fakeUpdateCondition(true)
+
+ val callback: Monitor.Callback = mock()
+
+ conditionMonitor.addSubscription(
+ getDefaultBuilder(callback).addCondition(settableCondition).build()
+ )
+
+ executor.runAllReady()
+ Mockito.verify(callback).onConditionsChanged(true)
+ Mockito.clearInvocations(callback)
+
+ settableCondition.fakeUpdateCondition(false)
+ executor.runAllReady()
+ Mockito.verify(callback).onConditionsChanged(false)
+ Mockito.clearInvocations(callback)
+
+ settableCondition.clearCondition()
+ executor.runAllReady()
+ Mockito.verify(callback).onConditionsChanged(true)
+ }
+
+ @Test
+ fun clearingOverridingCondition_shouldBeExcluded() =
+ kosmos.runTest {
+ val overridingCondition = FakeCondition(testScope, true, true)
+ condition1.fakeUpdateCondition(false)
+ condition2.fakeUpdateCondition(false)
+ condition3.fakeUpdateCondition(false)
+
+ val callback: Monitor.Callback = mock()
+
+ conditionMonitor.addSubscription(
+ getDefaultBuilder(callback).addCondition(overridingCondition).build()
+ )
+
+ executor.runAllReady()
+ Mockito.verify(callback).onConditionsChanged(true)
+ Mockito.clearInvocations(callback)
+
+ overridingCondition.clearCondition()
+ executor.runAllReady()
+ Mockito.verify(callback).onConditionsChanged(false)
+ }
+
+ @Test
+ fun settingUnsetOverridingCondition_shouldBeIncluded() =
+ kosmos.runTest {
+ val overridingCondition = FakeCondition(testScope, null, true)
+ condition1.fakeUpdateCondition(false)
+ condition2.fakeUpdateCondition(false)
+ condition3.fakeUpdateCondition(false)
+
+ val callback: Monitor.Callback = mock()
+
+ conditionMonitor.addSubscription(
+ getDefaultBuilder(callback).addCondition(overridingCondition).build()
+ )
+
+ executor.runAllReady()
+ Mockito.verify(callback).onConditionsChanged(false)
+ Mockito.clearInvocations(callback)
+
+ overridingCondition.fakeUpdateCondition(true)
+ executor.runAllReady()
+ Mockito.verify(callback).onConditionsChanged(true)
+ }
+
+ /**
+ * Ensures that the result of a condition being true leads to its nested condition being
+ * activated.
+ */
+ @Test
+ fun testNestedCondition() =
+ kosmos.runTest {
+ condition1.fakeUpdateCondition(false)
+ val callback: Monitor.Callback = mock()
+
+ condition2.fakeUpdateCondition(false)
+
+ // Create a nested condition
+ conditionMonitor.addSubscription(
+ Monitor.Subscription.Builder(
+ Monitor.Subscription.Builder(callback).addCondition(condition2).build()
+ )
+ .addCondition(condition1)
+ .build()
+ )
+
+ executor.runAllReady()
+
+ // Ensure the nested condition callback is not called at all.
+ Mockito.verify(callback, Mockito.never()).onActiveChanged(anyBoolean())
+ Mockito.verify(callback, Mockito.never()).onConditionsChanged(anyBoolean())
+
+ // Update the inner condition to true and ensure that the nested condition is not
+ // triggered.
+ condition2.fakeUpdateCondition(true)
+ Mockito.verify(callback, Mockito.never()).onConditionsChanged(anyBoolean())
+ condition2.fakeUpdateCondition(false)
+
+ // Set outer condition and make sure the inner condition becomes active and reports that
+ // conditions aren't met
+ condition1.fakeUpdateCondition(true)
+ executor.runAllReady()
+
+ Mockito.verify(callback).onActiveChanged(eq(true))
+ Mockito.verify(callback).onConditionsChanged(eq(false))
+
+ Mockito.clearInvocations(callback)
+
+ // Update the inner condition and make sure the callback is updated.
+ condition2.fakeUpdateCondition(true)
+ executor.runAllReady()
+
+ Mockito.verify(callback).onConditionsChanged(true)
+
+ Mockito.clearInvocations(callback)
+ // Invalidate outer condition and make sure callback is informed, but the last state is
+ // not affected.
+ condition1.fakeUpdateCondition(false)
+ executor.runAllReady()
+
+ Mockito.verify(callback).onActiveChanged(eq(false))
+ Mockito.verify(callback, Mockito.never()).onConditionsChanged(anyBoolean())
+ }
+
+ /** Ensure preconditions are applied to every subscription added to a monitor. */
+ @Test
+ fun testPreconditionMonitor() {
+ val callback: Monitor.Callback = mock()
+
+ condition2.fakeUpdateCondition(true)
+ val monitor = Monitor(executor, HashSet<Condition?>(listOf(condition1)))
+
+ monitor.addSubscription(
+ Monitor.Subscription.Builder(callback).addCondition(condition2).build()
+ )
+
+ executor.runAllReady()
+
+ Mockito.verify(callback, Mockito.never()).onActiveChanged(anyBoolean())
+ Mockito.verify(callback, Mockito.never()).onConditionsChanged(anyBoolean())
+
+ condition1.fakeUpdateCondition(true)
+ executor.runAllReady()
+
+ Mockito.verify(callback).onActiveChanged(eq(true))
+ Mockito.verify(callback).onConditionsChanged(eq(true))
+ }
+
+ @Test
+ fun testLoggingCallback() =
+ kosmos.runTest {
+ val monitor = Monitor(executor, emptySet(), logBuffer)
+
+ val condition = FakeCondition(testScope)
+ val overridingCondition =
+ FakeCondition(testScope, /* initialValue= */ false, /* overriding= */ true)
+
+ val callback: Monitor.Callback = mock()
+ monitor.addSubscription(
+ getDefaultBuilder(callback)
+ .addCondition(condition)
+ .addCondition(overridingCondition)
+ .build()
+ )
+ executor.runAllReady()
+
+ // condition set to true
+ condition.fakeUpdateCondition(true)
+ executor.runAllReady()
+ Mockito.verify(logBuffer).logChange("", "FakeCondition", "True")
+
+ // condition set to false
+ condition.fakeUpdateCondition(false)
+ executor.runAllReady()
+ Mockito.verify(logBuffer).logChange("", "FakeCondition", "False")
+
+ // condition unset
+ condition.fakeClearCondition()
+ executor.runAllReady()
+ Mockito.verify(logBuffer).logChange("", "FakeCondition", "Invalid")
+
+ // overriding condition set to true
+ overridingCondition.fakeUpdateCondition(true)
+ executor.runAllReady()
+ Mockito.verify(logBuffer).logChange("", "FakeCondition[OVRD]", "True")
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionTest.java
deleted file mode 100644
index a224843e592a..000000000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionTest.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * Copyright (C) 2022 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.shared.condition;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-
-import kotlinx.coroutines.CoroutineScope;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class ConditionTest extends SysuiTestCase {
- @Mock
- CoroutineScope mScope;
-
- private FakeCondition mCondition;
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- mCondition = spy(new FakeCondition(mScope));
- }
-
- @Test
- public void addCallback_addFirstCallback_triggerStart() {
- final Condition.Callback callback = mock(
- Condition.Callback.class);
- mCondition.addCallback(callback);
- verify(mCondition).start();
- }
-
- @Test
- public void addCallback_addMultipleCallbacks_triggerStartOnlyOnce() {
- final Condition.Callback callback1 = mock(
- Condition.Callback.class);
- final Condition.Callback callback2 = mock(
- Condition.Callback.class);
- final Condition.Callback callback3 = mock(
- Condition.Callback.class);
-
- mCondition.addCallback(callback1);
- mCondition.addCallback(callback2);
- mCondition.addCallback(callback3);
-
- verify(mCondition, times(1)).start();
- }
-
- @Test
- public void addCallback_alreadyStarted_triggerUpdate() {
- final Condition.Callback callback1 = mock(
- Condition.Callback.class);
- mCondition.addCallback(callback1);
-
- mCondition.fakeUpdateCondition(true);
-
- final Condition.Callback callback2 = mock(
- Condition.Callback.class);
- mCondition.addCallback(callback2);
- verify(callback2).onConditionChanged(mCondition);
- assertThat(mCondition.isConditionMet()).isTrue();
- }
-
- @Test
- public void removeCallback_removeLastCallback_triggerStop() {
- final Condition.Callback callback = mock(
- Condition.Callback.class);
- mCondition.addCallback(callback);
- verify(mCondition, never()).stop();
-
- mCondition.removeCallback(callback);
- verify(mCondition).stop();
- }
-
- @Test
- public void updateCondition_falseToTrue_reportTrue() {
- mCondition.fakeUpdateCondition(false);
-
- final Condition.Callback callback = mock(
- Condition.Callback.class);
- mCondition.addCallback(callback);
-
- mCondition.fakeUpdateCondition(true);
- verify(callback).onConditionChanged(eq(mCondition));
- assertThat(mCondition.isConditionMet()).isTrue();
- }
-
- @Test
- public void updateCondition_trueToFalse_reportFalse() {
- mCondition.fakeUpdateCondition(true);
-
- final Condition.Callback callback = mock(
- Condition.Callback.class);
- mCondition.addCallback(callback);
-
- mCondition.fakeUpdateCondition(false);
- verify(callback).onConditionChanged(eq(mCondition));
- assertThat(mCondition.isConditionMet()).isFalse();
- }
-
- @Test
- public void updateCondition_trueToTrue_reportNothing() {
- mCondition.fakeUpdateCondition(true);
-
- final Condition.Callback callback = mock(
- Condition.Callback.class);
- mCondition.addCallback(callback);
-
- mCondition.fakeUpdateCondition(true);
- verify(callback, never()).onConditionChanged(eq(mCondition));
- }
-
- @Test
- public void updateCondition_falseToFalse_reportNothing() {
- mCondition.fakeUpdateCondition(false);
-
- final Condition.Callback callback = mock(
- Condition.Callback.class);
- mCondition.addCallback(callback);
-
- mCondition.fakeUpdateCondition(false);
- verify(callback, never()).onConditionChanged(eq(mCondition));
- }
-
- @Test
- public void clearCondition_reportsNotSet() {
- mCondition.fakeUpdateCondition(false);
- assertThat(mCondition.isConditionSet()).isTrue();
- mCondition.clearCondition();
- assertThat(mCondition.isConditionSet()).isFalse();
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionTest.kt
new file mode 100644
index 000000000000..f72185b72de6
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionTest.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2025 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.shared.condition
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.runCurrent
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.testScope
+import com.google.common.truth.Truth
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito
+import org.mockito.Mockito.mock
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ConditionTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private lateinit var underTest: FakeCondition
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ underTest = Mockito.spy(FakeCondition(kosmos.testScope))
+ }
+
+ @Test
+ fun addCallback_addFirstCallback_triggerStart() =
+ kosmos.runTest {
+ val callback = mock<Condition.Callback>()
+ underTest.addCallback(callback)
+ runCurrent()
+ Mockito.verify(underTest).start()
+ }
+
+ @Test
+ fun addCallback_addMultipleCallbacks_triggerStartOnlyOnce() =
+ kosmos.runTest {
+ val callback1 = mock<Condition.Callback>()
+ val callback2 = mock<Condition.Callback>()
+ val callback3 = mock<Condition.Callback>()
+
+ underTest.addCallback(callback1)
+ underTest.addCallback(callback2)
+ underTest.addCallback(callback3)
+
+ runCurrent()
+ Mockito.verify(underTest).start()
+ }
+
+ @Test
+ fun addCallback_alreadyStarted_triggerUpdate() =
+ kosmos.runTest {
+ val callback1 = mock<Condition.Callback>()
+ underTest.addCallback(callback1)
+
+ underTest.fakeUpdateCondition(true)
+
+ val callback2 = mock<Condition.Callback>()
+ underTest.addCallback(callback2)
+ Mockito.verify(callback2).onConditionChanged(underTest)
+ Truth.assertThat(underTest.isConditionMet).isTrue()
+ }
+
+ @Test
+ fun removeCallback_removeLastCallback_triggerStop() =
+ kosmos.runTest {
+ val callback = mock<Condition.Callback>()
+ underTest.addCallback(callback)
+ Mockito.verify(underTest, Mockito.never()).stop()
+
+ underTest.removeCallback(callback)
+ Mockito.verify(underTest).stop()
+ }
+
+ @Test
+ fun updateCondition_falseToTrue_reportTrue() =
+ kosmos.runTest {
+ underTest.fakeUpdateCondition(false)
+
+ val callback = mock<Condition.Callback>()
+ underTest.addCallback(callback)
+
+ underTest.fakeUpdateCondition(true)
+ Mockito.verify(callback).onConditionChanged(eq(underTest))
+ Truth.assertThat(underTest.isConditionMet).isTrue()
+ }
+
+ @Test
+ fun updateCondition_trueToFalse_reportFalse() =
+ kosmos.runTest {
+ underTest.fakeUpdateCondition(true)
+
+ val callback = mock<Condition.Callback>()
+ underTest.addCallback(callback)
+
+ underTest.fakeUpdateCondition(false)
+ Mockito.verify(callback).onConditionChanged(eq(underTest))
+ Truth.assertThat(underTest.isConditionMet).isFalse()
+ }
+
+ @Test
+ fun updateCondition_trueToTrue_reportNothing() =
+ kosmos.runTest {
+ underTest.fakeUpdateCondition(true)
+
+ val callback = mock<Condition.Callback>()
+ underTest.addCallback(callback)
+
+ underTest.fakeUpdateCondition(true)
+ Mockito.verify(callback, Mockito.never()).onConditionChanged(eq(underTest))
+ }
+
+ @Test
+ fun updateCondition_falseToFalse_reportNothing() =
+ kosmos.runTest {
+ underTest.fakeUpdateCondition(false)
+
+ val callback = mock<Condition.Callback>()
+ underTest.addCallback(callback)
+
+ underTest.fakeUpdateCondition(false)
+ Mockito.verify(callback, Mockito.never()).onConditionChanged(eq(underTest))
+ }
+
+ @Test
+ fun clearCondition_reportsNotSet() =
+ kosmos.runTest {
+ underTest.fakeUpdateCondition(false)
+ Truth.assertThat(underTest.isConditionSet).isTrue()
+ underTest.clearCondition()
+ Truth.assertThat(underTest.isConditionSet).isFalse()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/FakeCondition.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/FakeCondition.java
deleted file mode 100644
index da660e2f6009..000000000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/FakeCondition.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2022 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.shared.condition;
-
-import kotlinx.coroutines.CoroutineScope;
-
-/**
- * Fake implementation of {@link Condition}, and provides a way for tests to update
- * condition fulfillment.
- */
-public class FakeCondition extends Condition {
- FakeCondition(CoroutineScope scope) {
- super(scope);
- }
-
- FakeCondition(CoroutineScope scope, Boolean initialValue, boolean overriding) {
- super(scope, initialValue, overriding);
- }
-
- @Override
- public void start() {
- }
-
- @Override
- public void stop() {
- }
-
- @Override
- public int getStartStrategy() {
- return START_EAGERLY;
- }
-
- public void fakeUpdateCondition(boolean isConditionMet) {
- updateCondition(isConditionMet);
- }
-
- public void fakeClearCondition() {
- clearCondition();
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/FakeCondition.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/FakeCondition.kt
new file mode 100644
index 000000000000..340249c269b1
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/FakeCondition.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2025 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.shared.condition
+
+import kotlinx.coroutines.CoroutineScope
+
+/**
+ * Fake implementation of [Condition], and provides a way for tests to update condition fulfillment.
+ */
+class FakeCondition : Condition {
+ constructor(scope: CoroutineScope) : super(scope)
+
+ constructor(
+ scope: CoroutineScope,
+ initialValue: Boolean?,
+ overriding: Boolean,
+ ) : super(scope, initialValue, overriding)
+
+ public override suspend fun start() {}
+
+ public override fun stop() {}
+
+ override val startStrategy: Int
+ get() = START_EAGERLY
+
+ fun fakeUpdateCondition(isConditionMet: Boolean) {
+ updateCondition(isConditionMet)
+ }
+
+ fun fakeClearCondition() {
+ clearCondition()
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/CombinedCondition.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/CombinedCondition.kt
index f276a82baaef..c30580fa01e7 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/CombinedCondition.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/CombinedCondition.kt
@@ -37,51 +37,43 @@ import kotlinx.coroutines.launch
* @param operand The [Evaluator.ConditionOperand] to apply to the conditions.
*/
@OptIn(ExperimentalCoroutinesApi::class)
-class CombinedCondition
-constructor(
+class CombinedCondition(
private val scope: CoroutineScope,
private val conditions: Collection<Condition>,
@Evaluator.ConditionOperand private val operand: Int,
) : Condition(scope, null, false) {
- private var job: Job? = null
private val _startStrategy by lazy { calculateStartStrategy() }
- override fun start() {
- job =
- scope.launch {
- val groupedConditions = conditions.groupBy { it.isOverridingCondition }
+ override suspend fun start() {
+ val groupedConditions = conditions.groupBy { it.isOverridingCondition }
- lazilyEvaluate(
- conditions = groupedConditions.getOrDefault(true, emptyList()),
- filterUnknown = true,
+ lazilyEvaluate(
+ conditions = groupedConditions.getOrDefault(true, emptyList()),
+ filterUnknown = true,
+ )
+ .distinctUntilChanged()
+ .flatMapLatest { overriddenValue ->
+ // If there are overriding conditions with values set, they take precedence.
+ if (overriddenValue == null) {
+ lazilyEvaluate(
+ conditions = groupedConditions.getOrDefault(false, emptyList()),
+ filterUnknown = false,
)
- .distinctUntilChanged()
- .flatMapLatest { overriddenValue ->
- // If there are overriding conditions with values set, they take precedence.
- if (overriddenValue == null) {
- lazilyEvaluate(
- conditions = groupedConditions.getOrDefault(false, emptyList()),
- filterUnknown = false,
- )
- } else {
- flowOf(overriddenValue)
- }
- }
- .collect { conditionMet ->
- if (conditionMet == null) {
- clearCondition()
- } else {
- updateCondition(conditionMet)
- }
- }
+ } else {
+ flowOf(overriddenValue)
+ }
+ }
+ .collect { conditionMet ->
+ if (conditionMet == null) {
+ clearCondition()
+ } else {
+ updateCondition(conditionMet)
+ }
}
}
- override fun stop() {
- job?.cancel()
- job = null
- }
+ override fun stop() {}
/**
* Evaluates a list of conditions lazily with support for short-circuiting. Conditions are
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Condition.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Condition.kt
index 69236b48ed51..44803bd22b45 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Condition.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Condition.kt
@@ -22,6 +22,8 @@ import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import java.lang.ref.WeakReference
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
/**
* Base class for a condition that needs to be fulfilled in order for [Monitor] to inform its
@@ -43,11 +45,12 @@ protected constructor(
) {
private val mTag: String = javaClass.simpleName
- private val _callbacks = mutableListOf<WeakReference<Callback>>()
- private var _started = false
+ private val callbacks = mutableListOf<WeakReference<Callback>>()
+ private var started = false
+ private var currentJob: Job? = null
/** Starts monitoring the condition. */
- protected abstract fun start()
+ protected abstract suspend fun start()
/** Stops monitoring the condition. */
protected abstract fun stop()
@@ -64,21 +67,21 @@ protected constructor(
*/
fun addCallback(callback: Callback) {
if (shouldLog()) Log.d(mTag, "adding callback")
- _callbacks.add(WeakReference(callback))
+ callbacks.add(WeakReference(callback))
- if (_started) {
+ if (started) {
callback.onConditionChanged(this)
return
}
- start()
- _started = true
+ currentJob = _scope.launch { start() }
+ started = true
}
/** Removes the provided callback from further receiving updates. */
fun removeCallback(callback: Callback) {
if (shouldLog()) Log.d(mTag, "removing callback")
- val iterator = _callbacks.iterator()
+ val iterator = callbacks.iterator()
while (iterator.hasNext()) {
val cb = iterator.next().get()
if (cb == null || cb === callback) {
@@ -86,12 +89,15 @@ protected constructor(
}
}
- if (_callbacks.isNotEmpty() || !_started) {
+ if (callbacks.isNotEmpty() || !started) {
return
}
stop()
- _started = false
+ currentJob?.cancel()
+ currentJob = null
+
+ started = false
}
/**
@@ -151,7 +157,7 @@ protected constructor(
}
private fun sendUpdate() {
- val iterator = _callbacks.iterator()
+ val iterator = callbacks.iterator()
while (iterator.hasNext()) {
val cb = iterator.next().get()
if (cb == null) {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/ConditionExtensions.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/ConditionExtensions.kt
index 0f535cdfa5cc..849b3d6d8d8b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/ConditionExtensions.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/ConditionExtensions.kt
@@ -19,7 +19,7 @@ fun Flow<Boolean>.toCondition(
return object : Condition(scope, initialValue, false) {
var job: Job? = null
- override fun start() {
+ override suspend fun start() {
job = scope.launch { collect { updateCondition(it) } }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/DeviceInactiveCondition.java b/packages/SystemUI/src/com/android/systemui/communal/DeviceInactiveCondition.java
deleted file mode 100644
index 4be9601f4277..000000000000
--- a/packages/SystemUI/src/com/android/systemui/communal/DeviceInactiveCondition.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2024 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.communal;
-
-import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
-
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.systemui.dagger.qualifiers.Application;
-import com.android.systemui.keyguard.WakefulnessLifecycle;
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
-import com.android.systemui.keyguard.shared.model.DozeStateModel;
-import com.android.systemui.shared.condition.Condition;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.kotlin.JavaAdapter;
-
-import kotlinx.coroutines.CoroutineScope;
-import kotlinx.coroutines.Job;
-
-import javax.inject.Inject;
-
-/**
- * Condition which estimates device inactivity in order to avoid launching a full-screen activity
- * while the user is actively using the device.
- */
-public class DeviceInactiveCondition extends Condition {
- private final KeyguardStateController mKeyguardStateController;
- private final WakefulnessLifecycle mWakefulnessLifecycle;
- private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- private final KeyguardInteractor mKeyguardInteractor;
- private final JavaAdapter mJavaAdapter;
- private Job mAnyDozeListenerJob;
- private boolean mAnyDoze;
- private final KeyguardStateController.Callback mKeyguardStateCallback =
- new KeyguardStateController.Callback() {
- @Override
- public void onKeyguardShowingChanged() {
- updateState();
- }
- };
- private final WakefulnessLifecycle.Observer mWakefulnessObserver =
- new WakefulnessLifecycle.Observer() {
- @Override
- public void onStartedGoingToSleep() {
- updateState();
- }
- };
- private final KeyguardUpdateMonitorCallback mKeyguardUpdateCallback =
- new KeyguardUpdateMonitorCallback() {
- @Override
- public void onDreamingStateChanged(boolean dreaming) {
- updateState();
- }
- };
-
- @Inject
- public DeviceInactiveCondition(@Application CoroutineScope scope,
- KeyguardStateController keyguardStateController,
- WakefulnessLifecycle wakefulnessLifecycle, KeyguardUpdateMonitor keyguardUpdateMonitor,
- KeyguardInteractor keyguardInteractor, JavaAdapter javaAdapter) {
- super(scope);
- mKeyguardStateController = keyguardStateController;
- mWakefulnessLifecycle = wakefulnessLifecycle;
- mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- mKeyguardInteractor = keyguardInteractor;
- mJavaAdapter = javaAdapter;
- }
-
- @Override
- protected void start() {
- updateState();
- mKeyguardStateController.addCallback(mKeyguardStateCallback);
- mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateCallback);
- mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
- mAnyDozeListenerJob = mJavaAdapter.alwaysCollectFlow(
- mKeyguardInteractor.getDozeTransitionModel(), dozeModel -> {
- mAnyDoze = !DozeStateModel.Companion.isDozeOff(dozeModel.getTo());
- updateState();
- });
- }
-
- @Override
- protected void stop() {
- mKeyguardStateController.removeCallback(mKeyguardStateCallback);
- mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateCallback);
- mWakefulnessLifecycle.removeObserver(mWakefulnessObserver);
- mAnyDozeListenerJob.cancel(null);
- }
-
- @Override
- public int getStartStrategy() {
- return START_EAGERLY;
- }
-
- private void updateState() {
- final boolean asleep = mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_ASLEEP;
- // Doze/AoD is also a dream, but we should never override it with low light as to the user
- // it's totally unrelated.
- updateCondition(!mAnyDoze && (asleep || mKeyguardStateController.isShowing()
- || mKeyguardUpdateMonitor.isDreaming()));
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/DeviceInactiveCondition.kt b/packages/SystemUI/src/com/android/systemui/communal/DeviceInactiveCondition.kt
new file mode 100644
index 000000000000..11c285b3e4e9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/DeviceInactiveCondition.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2024 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.communal
+
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
+import com.android.systemui.keyguard.shared.model.DozeTransitionModel
+import com.android.systemui.shared.condition.Condition
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.kotlin.JavaAdapter
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+
+/**
+ * Condition which estimates device inactivity in order to avoid launching a full-screen activity
+ * while the user is actively using the device.
+ */
+class DeviceInactiveCondition
+@Inject
+constructor(
+ @Application scope: CoroutineScope,
+ private val keyguardStateController: KeyguardStateController,
+ private val wakefulnessLifecycle: WakefulnessLifecycle,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val keyguardInteractor: KeyguardInteractor,
+ private val javaAdapter: JavaAdapter,
+) : Condition(scope) {
+ private var anyDozeListenerJob: Job? = null
+ private var anyDoze = false
+ private val keyguardStateCallback: KeyguardStateController.Callback =
+ object : KeyguardStateController.Callback {
+ override fun onKeyguardShowingChanged() {
+ updateState()
+ }
+ }
+ private val wakefulnessObserver: WakefulnessLifecycle.Observer =
+ object : WakefulnessLifecycle.Observer {
+ override fun onStartedGoingToSleep() {
+ updateState()
+ }
+ }
+ private val keyguardUpdateCallback: KeyguardUpdateMonitorCallback =
+ object : KeyguardUpdateMonitorCallback() {
+ override fun onDreamingStateChanged(dreaming: Boolean) {
+ updateState()
+ }
+ }
+
+ override suspend fun start() {
+ updateState()
+ keyguardStateController.addCallback(keyguardStateCallback)
+ keyguardUpdateMonitor.registerCallback(keyguardUpdateCallback)
+ wakefulnessLifecycle.addObserver(wakefulnessObserver)
+ anyDozeListenerJob =
+ javaAdapter.alwaysCollectFlow(keyguardInteractor.dozeTransitionModel) {
+ dozeModel: DozeTransitionModel ->
+ anyDoze = !isDozeOff(dozeModel.to)
+ updateState()
+ }
+ }
+
+ override fun stop() {
+ keyguardStateController.removeCallback(keyguardStateCallback)
+ keyguardUpdateMonitor.removeCallback(keyguardUpdateCallback)
+ wakefulnessLifecycle.removeObserver(wakefulnessObserver)
+ anyDozeListenerJob?.cancel(null)
+ }
+
+ override val startStrategy: Int
+ get() = START_EAGERLY
+
+ private fun updateState() {
+ val asleep = wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_ASLEEP
+ // Doze/AoD is also a dream, but we should never override it with low light as to the user
+ // it's totally unrelated.
+ updateCondition(
+ !anyDoze &&
+ (asleep || keyguardStateController.isShowing || keyguardUpdateMonitor.isDreaming)
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java b/packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java
deleted file mode 100644
index c17094b7456c..000000000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * 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.dreams.conditions;
-
-import com.android.systemui.assist.AssistManager;
-import com.android.systemui.assist.AssistManager.VisualQueryAttentionListener;
-import com.android.systemui.dagger.qualifiers.Application;
-import com.android.systemui.shared.condition.Condition;
-
-import kotlinx.coroutines.CoroutineScope;
-
-import javax.inject.Inject;
-
-/**
- * {@link AssistantAttentionCondition} provides a signal when assistant has the user's attention.
- */
-public class AssistantAttentionCondition extends Condition {
- private final AssistManager mAssistManager;
-
- private final VisualQueryAttentionListener mVisualQueryAttentionListener =
- new VisualQueryAttentionListener() {
- @Override
- public void onAttentionGained() {
- updateCondition(true);
- }
-
- @Override
- public void onAttentionLost() {
- updateCondition(false);
- }
- };
-
- @Inject
- public AssistantAttentionCondition(
- @Application CoroutineScope scope,
- AssistManager assistManager) {
- super(scope);
- mAssistManager = assistManager;
- }
-
- @Override
- protected void start() {
- mAssistManager.addVisualQueryAttentionListener(mVisualQueryAttentionListener);
- }
-
- @Override
- protected void stop() {
- mAssistManager.removeVisualQueryAttentionListener(mVisualQueryAttentionListener);
- }
-
- @Override
- public int getStartStrategy() {
- return START_EAGERLY;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.kt b/packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.kt
new file mode 100644
index 000000000000..838163b1d80a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2025 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.dreams.conditions
+
+import com.android.systemui.assist.AssistManager
+import com.android.systemui.assist.AssistManager.VisualQueryAttentionListener
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.shared.condition.Condition
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+
+/** [AssistantAttentionCondition] provides a signal when assistant has the user's attention. */
+class AssistantAttentionCondition
+@Inject
+constructor(@Application scope: CoroutineScope, private val assistManager: AssistManager) :
+ Condition(scope) {
+ private val visualQueryAttentionListener: VisualQueryAttentionListener =
+ object : VisualQueryAttentionListener {
+ override fun onAttentionGained() {
+ updateCondition(true)
+ }
+
+ override fun onAttentionLost() {
+ updateCondition(false)
+ }
+ }
+
+ override suspend fun start() {
+ assistManager.addVisualQueryAttentionListener(visualQueryAttentionListener)
+ }
+
+ public override fun stop() {
+ assistManager.removeVisualQueryAttentionListener(visualQueryAttentionListener)
+ }
+
+ override val startStrategy: Int
+ get() = START_EAGERLY
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java b/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java
deleted file mode 100644
index fb4ed1419688..000000000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * 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.dreams.conditions;
-
-import android.app.DreamManager;
-
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.systemui.dagger.qualifiers.Application;
-import com.android.systemui.shared.condition.Condition;
-
-import kotlinx.coroutines.CoroutineScope;
-
-import javax.inject.Inject;
-
-/**
- * {@link DreamCondition} provides a signal when a dream begins and ends.
- */
-public class DreamCondition extends Condition {
- private final DreamManager mDreamManager;
- private final KeyguardUpdateMonitor mUpdateMonitor;
-
- private final KeyguardUpdateMonitorCallback mUpdateCallback =
- new KeyguardUpdateMonitorCallback() {
- @Override
- public void onDreamingStateChanged(boolean dreaming) {
- updateCondition(dreaming);
- }
- };
-
- @Inject
- public DreamCondition(
- @Application CoroutineScope scope,
- DreamManager dreamManager,
- KeyguardUpdateMonitor monitor) {
- super(scope);
- mDreamManager = dreamManager;
- mUpdateMonitor = monitor;
- }
-
- @Override
- protected void start() {
- mUpdateMonitor.registerCallback(mUpdateCallback);
- updateCondition(mDreamManager.isDreaming());
- }
-
- @Override
- protected void stop() {
- mUpdateMonitor.removeCallback(mUpdateCallback);
- }
-
- @Override
- public int getStartStrategy() {
- return START_EAGERLY;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.kt b/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.kt
new file mode 100644
index 000000000000..6968132b7da3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2025 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.dreams.conditions
+
+import android.app.DreamManager
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.shared.condition.Condition
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+
+/** [DreamCondition] provides a signal when a dream begins and ends. */
+class DreamCondition
+@Inject
+constructor(
+ @Application scope: CoroutineScope,
+ private val _dreamManager: DreamManager,
+ private val _updateMonitor: KeyguardUpdateMonitor,
+) : Condition(scope) {
+ private val _updateCallback: KeyguardUpdateMonitorCallback =
+ object : KeyguardUpdateMonitorCallback() {
+ override fun onDreamingStateChanged(dreaming: Boolean) {
+ updateCondition(dreaming)
+ }
+ }
+
+ override suspend fun start() {
+ _updateMonitor.registerCallback(_updateCallback)
+ updateCondition(_dreamManager.isDreaming)
+ }
+
+ override fun stop() {
+ _updateMonitor.removeCallback(_updateCallback)
+ }
+
+ override val startStrategy: Int
+ get() = START_EAGERLY
+}
diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/AmbientLightModeMonitor.kt b/packages/SystemUI/src/com/android/systemui/lowlightclock/AmbientLightModeMonitor.kt
index 9e32dd8d74ae..c1658e1f1694 100644
--- a/packages/SystemUI/src/com/android/systemui/lowlightclock/AmbientLightModeMonitor.kt
+++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/AmbientLightModeMonitor.kt
@@ -122,7 +122,7 @@ constructor(
}
/** Interface of the ambient light mode callback, which gets triggered when the mode changes. */
- interface Callback {
+ fun interface Callback {
fun onChange(@AmbientLightMode mode: Int)
}
diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/DirectBootCondition.kt b/packages/SystemUI/src/com/android/systemui/lowlightclock/DirectBootCondition.kt
index 57d709835b2c..cdac61cea10b 100644
--- a/packages/SystemUI/src/com/android/systemui/lowlightclock/DirectBootCondition.kt
+++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/DirectBootCondition.kt
@@ -45,7 +45,7 @@ constructor(
.cancellable()
.distinctUntilChanged()
- override fun start() {
+ override suspend fun start() {
job = coroutineScope.launch { directBootFlow.collect { updateCondition(it) } }
updateCondition(!userManager.isUserUnlocked)
}
diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/ForceLowLightCondition.java b/packages/SystemUI/src/com/android/systemui/lowlightclock/ForceLowLightCondition.java
deleted file mode 100644
index 5ec81a9a94a1..000000000000
--- a/packages/SystemUI/src/com/android/systemui/lowlightclock/ForceLowLightCondition.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2024 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.lowlightclock;
-
-import android.text.TextUtils;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-
-import com.android.systemui.dagger.qualifiers.Application;
-import com.android.systemui.shared.condition.Condition;
-import com.android.systemui.statusbar.commandline.Command;
-import com.android.systemui.statusbar.commandline.CommandRegistry;
-
-import kotlinx.coroutines.CoroutineScope;
-
-import java.io.PrintWriter;
-import java.util.List;
-
-import javax.inject.Inject;
-
-/**
- * This condition registers for and fulfills cmd shell commands to force a device into or out of
- * low-light conditions.
- */
-public class ForceLowLightCondition extends Condition {
- /**
- * Command root
- */
- public static final String COMMAND_ROOT = "low-light";
- /**
- * Command for forcing device into low light.
- */
- public static final String COMMAND_ENABLE_LOW_LIGHT = "enable";
-
- /**
- * Command for preventing a device from entering low light.
- */
- public static final String COMMAND_DISABLE_LOW_LIGHT = "disable";
-
- /**
- * Command for clearing previously forced low-light conditions.
- */
- public static final String COMMAND_CLEAR_LOW_LIGHT = "clear";
-
- private static final String TAG = "ForceLowLightCondition";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
- /**
- * Default Constructor.
- *
- * @param commandRegistry command registry to register commands with.
- */
- @Inject
- public ForceLowLightCondition(
- @Application CoroutineScope scope,
- CommandRegistry commandRegistry
- ) {
- super(scope, null, true);
-
- if (DEBUG) {
- Log.d(TAG, "registering commands");
- }
- commandRegistry.registerCommand(COMMAND_ROOT, () -> new Command() {
- @Override
- public void execute(@NonNull PrintWriter pw, @NonNull List<String> args) {
- if (args.size() != 1) {
- pw.println("no command specified");
- help(pw);
- return;
- }
-
- final String cmd = args.get(0);
-
- if (TextUtils.equals(cmd, COMMAND_ENABLE_LOW_LIGHT)) {
- logAndPrint(pw, "forcing low light");
- updateCondition(true);
- } else if (TextUtils.equals(cmd, COMMAND_DISABLE_LOW_LIGHT)) {
- logAndPrint(pw, "forcing to not enter low light");
- updateCondition(false);
- } else if (TextUtils.equals(cmd, COMMAND_CLEAR_LOW_LIGHT)) {
- logAndPrint(pw, "clearing any forced low light");
- clearCondition();
- } else {
- pw.println("invalid command");
- help(pw);
- }
- }
-
- @Override
- public void help(@NonNull PrintWriter pw) {
- pw.println("Usage: adb shell cmd statusbar low-light <cmd>");
- pw.println("Supported commands:");
- pw.println(" - enable");
- pw.println(" forces device into low-light");
- pw.println(" - disable");
- pw.println(" forces device to not enter low-light");
- pw.println(" - clear");
- pw.println(" clears any previously forced state");
- }
-
- private void logAndPrint(PrintWriter pw, String message) {
- pw.println(message);
- if (DEBUG) {
- Log.d(TAG, message);
- }
- }
- });
- }
-
- @Override
- protected void start() {
- }
-
- @Override
- protected void stop() {
- }
-
- @Override
- public int getStartStrategy() {
- return START_EAGERLY;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/ForceLowLightCondition.kt b/packages/SystemUI/src/com/android/systemui/lowlightclock/ForceLowLightCondition.kt
new file mode 100644
index 000000000000..f75894da2c85
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/ForceLowLightCondition.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2025 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.lowlightclock
+
+import android.text.TextUtils
+import android.util.Log
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.shared.condition.Condition
+import com.android.systemui.statusbar.commandline.Command
+import com.android.systemui.statusbar.commandline.CommandRegistry
+import java.io.PrintWriter
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+
+/**
+ * This condition registers for and fulfills cmd shell commands to force a device into or out of
+ * low-light conditions.
+ */
+class ForceLowLightCondition
+@Inject
+constructor(@Application scope: CoroutineScope, commandRegistry: CommandRegistry) :
+ Condition(scope, null, true) {
+ /**
+ * Default Constructor.
+ *
+ * @param commandRegistry command registry to register commands with.
+ */
+ init {
+ if (DEBUG) {
+ Log.d(TAG, "registering commands")
+ }
+ commandRegistry.registerCommand(COMMAND_ROOT) {
+ object : Command {
+ override fun execute(pw: PrintWriter, args: List<String>) {
+ if (args.size != 1) {
+ pw.println("no command specified")
+ help(pw)
+ return
+ }
+
+ val cmd = args[0]
+
+ if (TextUtils.equals(cmd, COMMAND_ENABLE_LOW_LIGHT)) {
+ logAndPrint(pw, "forcing low light")
+ updateCondition(true)
+ } else if (TextUtils.equals(cmd, COMMAND_DISABLE_LOW_LIGHT)) {
+ logAndPrint(pw, "forcing to not enter low light")
+ updateCondition(false)
+ } else if (TextUtils.equals(cmd, COMMAND_CLEAR_LOW_LIGHT)) {
+ logAndPrint(pw, "clearing any forced low light")
+ clearCondition()
+ } else {
+ pw.println("invalid command")
+ help(pw)
+ }
+ }
+
+ override fun help(pw: PrintWriter) {
+ pw.println("Usage: adb shell cmd statusbar low-light <cmd>")
+ pw.println("Supported commands:")
+ pw.println(" - enable")
+ pw.println(" forces device into low-light")
+ pw.println(" - disable")
+ pw.println(" forces device to not enter low-light")
+ pw.println(" - clear")
+ pw.println(" clears any previously forced state")
+ }
+
+ private fun logAndPrint(pw: PrintWriter, message: String) {
+ pw.println(message)
+ if (DEBUG) {
+ Log.d(TAG, message)
+ }
+ }
+ }
+ }
+ }
+
+ override suspend fun start() {}
+
+ override fun stop() {}
+
+ override val startStrategy: Int
+ get() = START_EAGERLY
+
+ companion object {
+ /** Command root */
+ const val COMMAND_ROOT: String = "low-light"
+
+ /** Command for forcing device into low light. */
+ const val COMMAND_ENABLE_LOW_LIGHT: String = "enable"
+
+ /** Command for preventing a device from entering low light. */
+ const val COMMAND_DISABLE_LOW_LIGHT: String = "disable"
+
+ /** Command for clearing previously forced low-light conditions. */
+ const val COMMAND_CLEAR_LOW_LIGHT: String = "clear"
+
+ private const val TAG = "ForceLowLightCondition"
+ private val DEBUG = Log.isLoggable(TAG, Log.DEBUG)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightCondition.java b/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightCondition.java
deleted file mode 100644
index c1a24e7e020e..000000000000
--- a/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightCondition.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2024 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.lowlightclock;
-
-import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.dagger.qualifiers.Application;
-import com.android.systemui.shared.condition.Condition;
-
-import kotlinx.coroutines.CoroutineScope;
-
-import javax.inject.Inject;
-
-/**
- * Condition for monitoring when the device enters and exits lowlight mode.
- */
-public class LowLightCondition extends Condition {
- private final AmbientLightModeMonitor mAmbientLightModeMonitor;
- private final UiEventLogger mUiEventLogger;
-
- @Inject
- public LowLightCondition(@Application CoroutineScope scope,
- AmbientLightModeMonitor ambientLightModeMonitor,
- UiEventLogger uiEventLogger) {
- super(scope);
- mAmbientLightModeMonitor = ambientLightModeMonitor;
- mUiEventLogger = uiEventLogger;
- }
-
- @Override
- protected void start() {
- mAmbientLightModeMonitor.start(this::onLowLightChanged);
- }
-
- @Override
- protected void stop() {
- mAmbientLightModeMonitor.stop();
-
- // Reset condition met to false.
- updateCondition(false);
- }
-
- @Override
- public int getStartStrategy() {
- // As this condition keeps the lowlight sensor active, it should only run when needed.
- return START_WHEN_NEEDED;
- }
-
- private void onLowLightChanged(int lowLightMode) {
- if (lowLightMode == AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_UNDECIDED) {
- // Ignore undecided mode changes.
- return;
- }
-
- final boolean isLowLight = lowLightMode == AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK;
- if (isLowLight == isConditionMet()) {
- // No change in condition, don't do anything.
- return;
- }
- mUiEventLogger.log(isLowLight ? LowLightDockEvent.AMBIENT_LIGHT_TO_DARK
- : LowLightDockEvent.AMBIENT_LIGHT_TO_LIGHT);
- updateCondition(isLowLight);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightCondition.kt b/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightCondition.kt
new file mode 100644
index 000000000000..a29c666ff792
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightCondition.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2025 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.lowlightclock
+
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.shared.condition.Condition
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+
+/** Condition for monitoring when the device enters and exits lowlight mode. */
+class LowLightCondition
+@Inject
+constructor(
+ @Application scope: CoroutineScope,
+ private val ambientLightModeMonitor: AmbientLightModeMonitor,
+ private val uiEventLogger: UiEventLogger,
+) : Condition(scope) {
+ override suspend fun start() {
+ ambientLightModeMonitor.start { lowLightMode: Int -> onLowLightChanged(lowLightMode) }
+ }
+
+ override fun stop() {
+ ambientLightModeMonitor.stop()
+
+ // Reset condition met to false.
+ updateCondition(false)
+ }
+
+ override val startStrategy: Int
+ get() = // As this condition keeps the lowlight sensor active, it should only run when
+ // needed.
+ START_WHEN_NEEDED
+
+ private fun onLowLightChanged(lowLightMode: Int) {
+ if (lowLightMode == AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_UNDECIDED) {
+ // Ignore undecided mode changes.
+ return
+ }
+
+ val isLowLight = lowLightMode == AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK
+ if (isLowLight == isConditionMet) {
+ // No change in condition, don't do anything.
+ return
+ }
+ uiEventLogger.log(
+ if (isLowLight) LowLightDockEvent.AMBIENT_LIGHT_TO_DARK
+ else LowLightDockEvent.AMBIENT_LIGHT_TO_LIGHT
+ )
+ updateCondition(isLowLight)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/ScreenSaverEnabledCondition.java b/packages/SystemUI/src/com/android/systemui/lowlightclock/ScreenSaverEnabledCondition.java
deleted file mode 100644
index 81572554cb86..000000000000
--- a/packages/SystemUI/src/com/android/systemui/lowlightclock/ScreenSaverEnabledCondition.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2024 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.lowlightclock;
-
-import android.content.res.Resources;
-import android.database.ContentObserver;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.util.Log;
-
-import com.android.systemui.dagger.qualifiers.Application;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.shared.condition.Condition;
-import com.android.systemui.util.settings.SecureSettings;
-
-import kotlinx.coroutines.CoroutineScope;
-
-import javax.inject.Inject;
-
-/**
- * Condition for monitoring if the screensaver setting is enabled.
- */
-public class ScreenSaverEnabledCondition extends Condition {
- private static final String TAG = ScreenSaverEnabledCondition.class.getSimpleName();
-
- private final boolean mScreenSaverEnabledByDefaultConfig;
- private final SecureSettings mSecureSettings;
-
- private final ContentObserver mScreenSaverSettingObserver = new ContentObserver(null) {
- @Override
- public void onChange(boolean selfChange) {
- updateScreenSaverEnabledSetting();
- }
- };
-
- @Inject
- public ScreenSaverEnabledCondition(@Application CoroutineScope scope, @Main Resources resources,
- SecureSettings secureSettings) {
- super(scope);
- mScreenSaverEnabledByDefaultConfig = resources.getBoolean(
- com.android.internal.R.bool.config_dreamsEnabledByDefault);
- mSecureSettings = secureSettings;
- }
-
- @Override
- protected void start() {
- mSecureSettings.registerContentObserverForUserSync(
- Settings.Secure.SCREENSAVER_ENABLED,
- mScreenSaverSettingObserver, UserHandle.USER_CURRENT);
- updateScreenSaverEnabledSetting();
- }
-
- @Override
- protected void stop() {
- mSecureSettings.unregisterContentObserverSync(mScreenSaverSettingObserver);
- }
-
- @Override
- public int getStartStrategy() {
- return START_EAGERLY;
- }
-
- private void updateScreenSaverEnabledSetting() {
- final boolean enabled = mSecureSettings.getIntForUser(
- Settings.Secure.SCREENSAVER_ENABLED,
- mScreenSaverEnabledByDefaultConfig ? 1 : 0,
- UserHandle.USER_CURRENT) != 0;
- if (!enabled) {
- Log.i(TAG, "Disabling low-light clock because screen saver has been disabled");
- }
- updateCondition(enabled);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/ScreenSaverEnabledCondition.kt b/packages/SystemUI/src/com/android/systemui/lowlightclock/ScreenSaverEnabledCondition.kt
new file mode 100644
index 000000000000..f6b0f6276440
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/ScreenSaverEnabledCondition.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2025 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.lowlightclock
+
+import android.content.res.Resources
+import android.database.ContentObserver
+import android.os.UserHandle
+import android.provider.Settings
+import android.util.Log
+import com.android.internal.R
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.shared.condition.Condition
+import com.android.systemui.util.settings.SecureSettings
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+
+/** Condition for monitoring if the screensaver setting is enabled. */
+class ScreenSaverEnabledCondition
+@Inject
+constructor(
+ @Application scope: CoroutineScope,
+ @Main resources: Resources,
+ private val secureSettings: SecureSettings,
+) : Condition(scope) {
+ private val screenSaverEnabledByDefaultConfig =
+ resources.getBoolean(R.bool.config_dreamsEnabledByDefault)
+
+ private val screenSaverSettingObserver: ContentObserver =
+ object : ContentObserver(null) {
+ override fun onChange(selfChange: Boolean) {
+ updateScreenSaverEnabledSetting()
+ }
+ }
+
+ public override suspend fun start() {
+ secureSettings.registerContentObserverForUserSync(
+ Settings.Secure.SCREENSAVER_ENABLED,
+ screenSaverSettingObserver,
+ UserHandle.USER_CURRENT,
+ )
+ updateScreenSaverEnabledSetting()
+ }
+
+ override fun stop() {
+ secureSettings.unregisterContentObserverSync(screenSaverSettingObserver)
+ }
+
+ override val startStrategy: Int
+ get() = START_EAGERLY
+
+ private fun updateScreenSaverEnabledSetting() {
+ val enabled =
+ secureSettings.getIntForUser(
+ Settings.Secure.SCREENSAVER_ENABLED,
+ if (screenSaverEnabledByDefaultConfig) 1 else 0,
+ UserHandle.USER_CURRENT,
+ ) != 0
+ if (!enabled) {
+ Log.i(TAG, "Disabling low-light clock because screen saver has been disabled")
+ }
+ updateCondition(enabled)
+ }
+
+ companion object {
+ private val TAG: String = ScreenSaverEnabledCondition::class.java.simpleName
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/process/condition/SystemProcessCondition.java b/packages/SystemUI/src/com/android/systemui/process/condition/SystemProcessCondition.java
deleted file mode 100644
index ea2bf6a44884..000000000000
--- a/packages/SystemUI/src/com/android/systemui/process/condition/SystemProcessCondition.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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.dagger.qualifiers.Application;
-import com.android.systemui.process.ProcessWrapper;
-import com.android.systemui.shared.condition.Condition;
-
-import kotlinx.coroutines.CoroutineScope;
-
-import javax.inject.Inject;
-
-/**
- * {@link SystemProcessCondition} checks to make sure the current process is being ran by the
- * System User.
- */
-public class SystemProcessCondition extends Condition {
- private final ProcessWrapper mProcessWrapper;
-
- @Inject
- public SystemProcessCondition(@Application CoroutineScope scope,
- ProcessWrapper processWrapper) {
- super(scope);
- mProcessWrapper = processWrapper;
- }
-
- @Override
- protected void start() {
- updateCondition(mProcessWrapper.isSystemUser());
- }
-
- @Override
- protected void stop() {
- }
-
- @Override
- public int getStartStrategy() {
- return START_EAGERLY;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/process/condition/SystemProcessCondition.kt b/packages/SystemUI/src/com/android/systemui/process/condition/SystemProcessCondition.kt
new file mode 100644
index 000000000000..9a424c3aab41
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/process/condition/SystemProcessCondition.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2025 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.dagger.qualifiers.Application
+import com.android.systemui.process.ProcessWrapper
+import com.android.systemui.shared.condition.Condition
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+
+/**
+ * [SystemProcessCondition] checks to make sure the current process is being ran by the System User.
+ */
+class SystemProcessCondition
+@Inject
+constructor(@Application scope: CoroutineScope, private val processWrapper: ProcessWrapper) :
+ Condition(scope) {
+ override val startStrategy: Int
+ get() = START_EAGERLY
+
+ override suspend fun start() {
+ updateCondition(processWrapper.isSystemUser)
+ }
+
+ override fun stop() {}
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/ForceLowLightConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/ForceLowLightConditionTest.java
deleted file mode 100644
index 7297e0f3bff5..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/ForceLowLightConditionTest.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2024 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.lowlightclock;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.verify;
-
-import android.testing.AndroidTestingRunner;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.shared.condition.Condition;
-import com.android.systemui.statusbar.commandline.Command;
-import com.android.systemui.statusbar.commandline.CommandRegistry;
-
-import kotlin.jvm.functions.Function0;
-
-import kotlinx.coroutines.CoroutineScope;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.io.PrintWriter;
-import java.util.Arrays;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class ForceLowLightConditionTest extends SysuiTestCase {
- @Mock
- private CommandRegistry mCommandRegistry;
-
- @Mock
- private Condition.Callback mCallback;
-
- @Mock
- private PrintWriter mPrintWriter;
-
- @Mock
- CoroutineScope mScope;
-
- private ForceLowLightCondition mCondition;
- private Command mCommand;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mCondition = new ForceLowLightCondition(mScope, mCommandRegistry);
- mCondition.addCallback(mCallback);
- ArgumentCaptor<Function0<Command>> commandCaptor =
- ArgumentCaptor.forClass(Function0.class);
- verify(mCommandRegistry).registerCommand(eq(ForceLowLightCondition.COMMAND_ROOT),
- commandCaptor.capture());
- mCommand = commandCaptor.getValue().invoke();
- }
-
- @Test
- public void testEnableLowLight() {
- mCommand.execute(mPrintWriter,
- Arrays.asList(ForceLowLightCondition.COMMAND_ENABLE_LOW_LIGHT));
- verify(mCallback).onConditionChanged(mCondition);
- assertThat(mCondition.isConditionSet()).isTrue();
- assertThat(mCondition.isConditionMet()).isTrue();
- }
-
- @Test
- public void testDisableLowLight() {
- mCommand.execute(mPrintWriter,
- Arrays.asList(ForceLowLightCondition.COMMAND_DISABLE_LOW_LIGHT));
- verify(mCallback).onConditionChanged(mCondition);
- assertThat(mCondition.isConditionSet()).isTrue();
- assertThat(mCondition.isConditionMet()).isFalse();
- }
-
- @Test
- public void testClearEnableLowLight() {
- mCommand.execute(mPrintWriter,
- Arrays.asList(ForceLowLightCondition.COMMAND_ENABLE_LOW_LIGHT));
- verify(mCallback).onConditionChanged(mCondition);
- assertThat(mCondition.isConditionSet()).isTrue();
- assertThat(mCondition.isConditionMet()).isTrue();
- Mockito.clearInvocations(mCallback);
- mCommand.execute(mPrintWriter,
- Arrays.asList(ForceLowLightCondition.COMMAND_CLEAR_LOW_LIGHT));
- verify(mCallback).onConditionChanged(mCondition);
- assertThat(mCondition.isConditionSet()).isFalse();
- assertThat(mCondition.isConditionMet()).isFalse();
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/ForceLowLightConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/ForceLowLightConditionTest.kt
new file mode 100644
index 000000000000..f8ce242c39d2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/ForceLowLightConditionTest.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2025 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.lowlightclock
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.shared.condition.Condition
+import com.android.systemui.statusbar.commandline.Command
+import com.android.systemui.statusbar.commandline.CommandRegistry
+import com.google.common.truth.Truth
+import java.io.PrintWriter
+import java.util.Arrays
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ForceLowLightConditionTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+
+ @Mock private lateinit var commandRegistry: CommandRegistry
+
+ @Mock private lateinit var callback: Condition.Callback
+
+ @Mock private lateinit var printWriter: PrintWriter
+
+ private lateinit var condition: ForceLowLightCondition
+ private lateinit var command: Command
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ condition = ForceLowLightCondition(kosmos.testScope, commandRegistry)
+ condition.addCallback(callback)
+ val commandCaptor = argumentCaptor<() -> Command>()
+ Mockito.verify(commandRegistry)
+ .registerCommand(eq(ForceLowLightCondition.COMMAND_ROOT), commandCaptor.capture())
+ command = commandCaptor.lastValue.invoke()
+ }
+
+ @Test
+ fun testEnableLowLight() =
+ kosmos.runTest {
+ command.execute(
+ printWriter,
+ Arrays.asList(ForceLowLightCondition.COMMAND_ENABLE_LOW_LIGHT),
+ )
+ Mockito.verify(callback).onConditionChanged(condition)
+ Truth.assertThat(condition.isConditionSet).isTrue()
+ Truth.assertThat(condition.isConditionMet).isTrue()
+ }
+
+ @Test
+ fun testDisableLowLight() =
+ kosmos.runTest {
+ command.execute(printWriter, listOf(ForceLowLightCondition.COMMAND_DISABLE_LOW_LIGHT))
+ Mockito.verify(callback).onConditionChanged(condition)
+ Truth.assertThat(condition.isConditionSet).isTrue()
+ Truth.assertThat(condition.isConditionMet).isFalse()
+ }
+
+ @Test
+ fun testClearEnableLowLight() =
+ kosmos.runTest {
+ command.execute(printWriter, listOf(ForceLowLightCondition.COMMAND_ENABLE_LOW_LIGHT))
+ Mockito.verify(callback).onConditionChanged(condition)
+ Truth.assertThat(condition.isConditionSet).isTrue()
+ Truth.assertThat(condition.isConditionMet).isTrue()
+ Mockito.clearInvocations(callback)
+ command.execute(printWriter, listOf(ForceLowLightCondition.COMMAND_CLEAR_LOW_LIGHT))
+ Mockito.verify(callback).onConditionChanged(condition)
+ Truth.assertThat(condition.isConditionSet).isFalse()
+ Truth.assertThat(condition.isConditionMet).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/LowLightConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/LowLightConditionTest.java
deleted file mode 100644
index 2c216244985e..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/LowLightConditionTest.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2024 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.lowlightclock;
-
-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.reset;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.testing.AndroidTestingRunner;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.SysuiTestCase;
-
-import kotlinx.coroutines.CoroutineScope;
-
-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;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class LowLightConditionTest extends SysuiTestCase {
- @Mock
- private AmbientLightModeMonitor mAmbientLightModeMonitor;
- @Mock
- private UiEventLogger mUiEventLogger;
- @Mock
- CoroutineScope mScope;
- private LowLightCondition mCondition;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
-
- mCondition = new LowLightCondition(mScope, mAmbientLightModeMonitor, mUiEventLogger);
- mCondition.start();
- }
-
- @Test
- public void testLowLightFalse() {
- changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT);
- assertThat(mCondition.isConditionMet()).isFalse();
- }
-
- @Test
- public void testLowLightTrue() {
- changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK);
- assertThat(mCondition.isConditionMet()).isTrue();
- }
-
- @Test
- public void testUndecidedLowLightStateIgnored() {
- changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK);
- assertThat(mCondition.isConditionMet()).isTrue();
- changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_UNDECIDED);
- assertThat(mCondition.isConditionMet()).isTrue();
- }
-
- @Test
- public void testLowLightChange() {
- changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT);
- assertThat(mCondition.isConditionMet()).isFalse();
- changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK);
- assertThat(mCondition.isConditionMet()).isTrue();
- }
-
- @Test
- public void testResetIsConditionMetUponStop() {
- changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK);
- assertThat(mCondition.isConditionMet()).isTrue();
-
- mCondition.stop();
- assertThat(mCondition.isConditionMet()).isFalse();
- }
-
- @Test
- public void testLoggingAmbientLightNotLowToLow() {
- changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK);
- // Only logged once.
- verify(mUiEventLogger, times(1)).log(any());
- // Logged with the correct state.
- verify(mUiEventLogger).log(LowLightDockEvent.AMBIENT_LIGHT_TO_DARK);
- }
-
- @Test
- public void testLoggingAmbientLightLowToLow() {
- changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK);
- reset(mUiEventLogger);
-
- changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK);
- // Doesn't log.
- verify(mUiEventLogger, never()).log(any());
- }
-
- @Test
- public void testLoggingAmbientLightNotLowToNotLow() {
- changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT);
- // Doesn't log.
- verify(mUiEventLogger, never()).log(any());
- }
-
- @Test
- public void testLoggingAmbientLightLowToNotLow() {
- changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK);
- reset(mUiEventLogger);
-
- changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT);
- // Only logged once.
- verify(mUiEventLogger).log(any());
- // Logged with the correct state.
- verify(mUiEventLogger).log(LowLightDockEvent.AMBIENT_LIGHT_TO_LIGHT);
- }
-
- private void changeLowLightMode(int mode) {
- ArgumentCaptor<AmbientLightModeMonitor.Callback> ambientLightCallbackCaptor =
- ArgumentCaptor.forClass(AmbientLightModeMonitor.Callback.class);
- verify(mAmbientLightModeMonitor).start(ambientLightCallbackCaptor.capture());
- ambientLightCallbackCaptor.getValue().onChange(mode);
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/LowLightConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/LowLightConditionTest.kt
new file mode 100644
index 000000000000..da68442a70cc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/LowLightConditionTest.kt
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2025 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.lowlightclock
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.condition.testStart
+import com.android.systemui.condition.testStop
+import com.android.systemui.kosmos.runCurrent
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.argumentCaptor
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class LowLightConditionTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
+ @Mock private lateinit var ambientLightModeMonitor: AmbientLightModeMonitor
+
+ @Mock private lateinit var uiEventLogger: UiEventLogger
+
+ private lateinit var condition: LowLightCondition
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ condition = LowLightCondition(kosmos.testScope, ambientLightModeMonitor, uiEventLogger)
+ }
+
+ @Test
+ fun testLowLightFalse() =
+ kosmos.runTest {
+ testStart(condition)
+ changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT)
+ Truth.assertThat(condition.isConditionMet).isFalse()
+ }
+
+ @Test
+ fun testLowLightTrue() =
+ kosmos.runTest {
+ testStart(condition)
+ changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK)
+ Truth.assertThat(condition.isConditionMet).isTrue()
+ }
+
+ @Test
+ fun testUndecidedLowLightStateIgnored() =
+ kosmos.runTest {
+ testStart(condition)
+ changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK)
+ Truth.assertThat(condition.isConditionMet).isTrue()
+ changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_UNDECIDED)
+ Truth.assertThat(condition.isConditionMet).isTrue()
+ }
+
+ @Test
+ fun testLowLightChange() =
+ kosmos.runTest {
+ testStart(condition)
+ changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT)
+ Truth.assertThat(condition.isConditionMet).isFalse()
+ changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK)
+ Truth.assertThat(condition.isConditionMet).isTrue()
+ }
+
+ @Test
+ fun testResetIsConditionMetUponStop() =
+ kosmos.runTest {
+ testStart(condition)
+ runCurrent()
+ changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK)
+ Truth.assertThat(condition.isConditionMet).isTrue()
+
+ testStop(condition)
+ Truth.assertThat(condition.isConditionMet).isFalse()
+ }
+
+ @Test
+ fun testLoggingAmbientLightNotLowToLow() =
+ kosmos.runTest {
+ testStart(condition)
+ changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK)
+ // Only logged once.
+ Mockito.verify(uiEventLogger, Mockito.times(1)).log(ArgumentMatchers.any())
+ // Logged with the correct state.
+ Mockito.verify(uiEventLogger).log(LowLightDockEvent.AMBIENT_LIGHT_TO_DARK)
+ }
+
+ @Test
+ fun testLoggingAmbientLightLowToLow() =
+ kosmos.runTest {
+ testStart(condition)
+ changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK)
+ Mockito.reset(uiEventLogger)
+
+ changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK)
+ // Doesn't log.
+ Mockito.verify(uiEventLogger, Mockito.never()).log(ArgumentMatchers.any())
+ }
+
+ @Test
+ fun testLoggingAmbientLightNotLowToNotLow() =
+ kosmos.runTest {
+ testStart(condition)
+ changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT)
+ // Doesn't log.
+ Mockito.verify(uiEventLogger, Mockito.never()).log(ArgumentMatchers.any())
+ }
+
+ @Test
+ fun testLoggingAmbientLightLowToNotLow() =
+ kosmos.runTest {
+ testStart(condition)
+ runCurrent()
+ changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK)
+ Mockito.reset(uiEventLogger)
+
+ changeLowLightMode(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT)
+ // Only logged once.
+ Mockito.verify(uiEventLogger).log(ArgumentMatchers.any())
+ // Logged with the correct state.
+ Mockito.verify(uiEventLogger).log(LowLightDockEvent.AMBIENT_LIGHT_TO_LIGHT)
+ }
+
+ private fun changeLowLightMode(mode: Int) {
+ val ambientLightCallbackCaptor = argumentCaptor<AmbientLightModeMonitor.Callback>()
+
+ Mockito.verify(ambientLightModeMonitor).start(ambientLightCallbackCaptor.capture())
+ ambientLightCallbackCaptor.lastValue.onChange(mode)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/ScreenSaverEnabledConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/ScreenSaverEnabledConditionTest.java
deleted file mode 100644
index 366c071fb93f..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/ScreenSaverEnabledConditionTest.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2024 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.lowlightclock;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.res.Resources;
-import android.database.ContentObserver;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.testing.AndroidTestingRunner;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.util.settings.SecureSettings;
-
-import kotlinx.coroutines.CoroutineScope;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class ScreenSaverEnabledConditionTest extends SysuiTestCase {
- @Mock
- private Resources mResources;
- @Mock
- private SecureSettings mSecureSettings;
- @Mock
- CoroutineScope mScope;
- @Captor
- private ArgumentCaptor<ContentObserver> mSettingsObserverCaptor;
- private ScreenSaverEnabledCondition mCondition;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- // Default dreams to enabled by default
- doReturn(true).when(mResources).getBoolean(
- com.android.internal.R.bool.config_dreamsEnabledByDefault);
-
- mCondition = new ScreenSaverEnabledCondition(mScope, mResources, mSecureSettings);
- }
-
- @Test
- public void testScreenSaverInitiallyEnabled() {
- setScreenSaverEnabled(true);
- mCondition.start();
- assertThat(mCondition.isConditionMet()).isTrue();
- }
-
- @Test
- public void testScreenSaverInitiallyDisabled() {
- setScreenSaverEnabled(false);
- mCondition.start();
- assertThat(mCondition.isConditionMet()).isFalse();
- }
-
- @Test
- public void testScreenSaverStateChanges() {
- setScreenSaverEnabled(false);
- mCondition.start();
- assertThat(mCondition.isConditionMet()).isFalse();
-
- setScreenSaverEnabled(true);
- final ContentObserver observer = captureSettingsObserver();
- observer.onChange(/* selfChange= */ false);
- assertThat(mCondition.isConditionMet()).isTrue();
- }
-
- private void setScreenSaverEnabled(boolean enabled) {
- when(mSecureSettings.getIntForUser(eq(Settings.Secure.SCREENSAVER_ENABLED), anyInt(),
- eq(UserHandle.USER_CURRENT))).thenReturn(enabled ? 1 : 0);
- }
-
- private ContentObserver captureSettingsObserver() {
- verify(mSecureSettings).registerContentObserverForUserSync(
- eq(Settings.Secure.SCREENSAVER_ENABLED),
- mSettingsObserverCaptor.capture(), eq(UserHandle.USER_CURRENT));
- return mSettingsObserverCaptor.getValue();
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/ScreenSaverEnabledConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/ScreenSaverEnabledConditionTest.kt
new file mode 100644
index 000000000000..29237a71d085
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/ScreenSaverEnabledConditionTest.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2025 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.lowlightclock
+
+import android.content.res.Resources
+import android.database.ContentObserver
+import android.os.UserHandle
+import android.provider.Settings
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.condition.testStart
+import com.android.systemui.kosmos.runCurrent
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.util.settings.SecureSettings
+import com.google.common.truth.Truth
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ScreenSaverEnabledConditionTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
+ @Mock private lateinit var resources: Resources
+
+ @Mock private lateinit var secureSettings: SecureSettings
+
+ private val settingsObserverCaptor = argumentCaptor<ContentObserver>()
+ private lateinit var condition: ScreenSaverEnabledCondition
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ // Default dreams to enabled by default
+ whenever(resources.getBoolean(R.bool.config_dreamsEnabledByDefault)).thenReturn(true)
+
+ condition = ScreenSaverEnabledCondition(kosmos.testScope, resources, secureSettings)
+ }
+
+ @Test
+ fun testScreenSaverInitiallyEnabled() =
+ kosmos.runTest {
+ setScreenSaverEnabled(true)
+ testStart(condition)
+ Truth.assertThat(condition.isConditionMet).isTrue()
+ }
+
+ @Test
+ fun testScreenSaverInitiallyDisabled() =
+ kosmos.runTest {
+ setScreenSaverEnabled(false)
+ testStart(condition)
+ Truth.assertThat(condition.isConditionMet).isFalse()
+ }
+
+ @Test
+ fun testScreenSaverStateChanges() =
+ kosmos.runTest {
+ setScreenSaverEnabled(false)
+ testStart(condition)
+ Truth.assertThat(condition.isConditionMet).isFalse()
+
+ setScreenSaverEnabled(true)
+ runCurrent()
+ val observer = captureSettingsObserver()
+ observer.onChange(/* selfChange= */ false)
+ Truth.assertThat(condition.isConditionMet).isTrue()
+ }
+
+ private fun setScreenSaverEnabled(enabled: Boolean) {
+ whenever(
+ secureSettings.getIntForUser(
+ eq(Settings.Secure.SCREENSAVER_ENABLED),
+ any(),
+ eq(UserHandle.USER_CURRENT),
+ )
+ )
+ .thenReturn(if (enabled) 1 else 0)
+ }
+
+ private fun captureSettingsObserver(): ContentObserver {
+ Mockito.verify(secureSettings)
+ .registerContentObserverForUserSync(
+ eq(Settings.Secure.SCREENSAVER_ENABLED),
+ settingsObserverCaptor.capture(),
+ eq(UserHandle.USER_CURRENT),
+ )
+ return settingsObserverCaptor.lastValue
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/CombinedConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/CombinedConditionTest.kt
index 116a2caa6dda..e1e9aa3d193e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/CombinedConditionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/CombinedConditionTest.kt
@@ -35,8 +35,7 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class CombinedConditionTest : SysuiTestCase() {
- class FakeCondition
- constructor(
+ class FakeCondition(
scope: CoroutineScope,
initialValue: Boolean?,
overriding: Boolean = false,
@@ -46,7 +45,7 @@ class CombinedConditionTest : SysuiTestCase() {
val started: Boolean
get() = _started
- override fun start() {
+ override suspend fun start() {
_started = true
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/condition/KosmosConditionTestExtensions.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/condition/KosmosConditionTestExtensions.kt
new file mode 100644
index 000000000000..c976b578437f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/condition/KosmosConditionTestExtensions.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2025 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.condition
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.runCurrent
+import com.android.systemui.shared.condition.Condition
+
+private val testCallback =
+ Condition.Callback {
+ // This is a no-op
+ }
+
+fun Kosmos.testStart(condition: Condition) {
+ condition.addCallback(testCallback)
+ runCurrent()
+}
+
+fun Kosmos.testStop(condition: Condition) {
+ condition.removeCallback(testCallback)
+ runCurrent()
+}