summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Bryce Lee <brycelee@google.com> 2025-03-22 13:13:13 -0700
committer Android (Google) Code Review <android-gerrit@google.com> 2025-03-22 13:13:13 -0700
commit68c80f9a909b04f257808b8e8ba9d7e65cb6896b (patch)
tree0a9abcfcfdf5170254f96eee877c03c7e8c16e54
parentcda8332969831337cb68982e4b66764bee9005e9 (diff)
parentbdb510af8e97a897c1bf97650be73380c5d43749 (diff)
Merge changes Ie6b4dd45,Id10a59d6 into main
* changes: Listen to DisplayStateInteractor for LowLightMonitor display changes. Convert LowLightMonitor to kotlin.
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/lowlightclock/LowLightMonitorTest.java198
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/lowlightclock/LowLightMonitorTest.kt271
-rw-r--r--packages/SystemUI/src/com/android/systemui/lowlightclock/AmbientLightModeMonitor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightMonitor.java148
-rw-r--r--packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightMonitor.kt116
-rw-r--r--packages/SystemUI/src/com/android/systemui/lowlightclock/dagger/LowLightModule.java152
-rw-r--r--packages/SystemUI/src/com/android/systemui/lowlightclock/dagger/LowLightModule.kt146
7 files changed, 534 insertions, 499 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/lowlightclock/LowLightMonitorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/lowlightclock/LowLightMonitorTest.java
deleted file mode 100644
index b177e07d09b6..000000000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/lowlightclock/LowLightMonitorTest.java
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- * 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 static com.android.dream.lowlight.LowLightDreamManager.AMBIENT_LIGHT_MODE_LOW_LIGHT;
-import static com.android.dream.lowlight.LowLightDreamManager.AMBIENT_LIGHT_MODE_REGULAR;
-import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_ON;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.ComponentName;
-import android.content.pm.PackageManager;
-import android.testing.TestableLooper;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.dream.lowlight.LowLightDreamManager;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.keyguard.ScreenLifecycle;
-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 dagger.Lazy;
-
-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;
-
-import java.util.Set;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-@TestableLooper.RunWithLooper()
-public class LowLightMonitorTest extends SysuiTestCase {
-
- @Mock
- private Lazy<LowLightDreamManager> mLowLightDreamManagerLazy;
- @Mock
- private LowLightDreamManager mLowLightDreamManager;
- @Mock
- private Monitor mMonitor;
- @Mock
- private ScreenLifecycle mScreenLifecycle;
- @Mock
- private LowLightLogger mLogger;
-
- private LowLightMonitor mLowLightMonitor;
-
- @Mock
- Lazy<Set<Condition>> mLazyConditions;
-
- @Mock
- private PackageManager mPackageManager;
-
- @Mock
- private ComponentName mDreamComponent;
-
- FakeExecutor mBackgroundExecutor = new FakeExecutor(new FakeSystemClock());
-
- Condition mCondition = mock(Condition.class);
- Set<Condition> mConditionSet = Set.of(mCondition);
-
- @Captor
- ArgumentCaptor<Monitor.Subscription> mPreconditionsSubscriptionCaptor;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- when(mLowLightDreamManagerLazy.get()).thenReturn(mLowLightDreamManager);
- when(mLazyConditions.get()).thenReturn(mConditionSet);
- mLowLightMonitor = new LowLightMonitor(mLowLightDreamManagerLazy,
- mMonitor, mLazyConditions, mScreenLifecycle, mLogger, mDreamComponent,
- mPackageManager, mBackgroundExecutor);
- }
-
- @Test
- public void testSetAmbientLowLightWhenInLowLight() {
- mLowLightMonitor.onConditionsChanged(true);
- mBackgroundExecutor.runAllReady();
- // Verify setting low light when condition is true
- verify(mLowLightDreamManager).setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT);
- }
-
- @Test
- public void testExitAmbientLowLightWhenNotInLowLight() {
- mLowLightMonitor.onConditionsChanged(true);
- mLowLightMonitor.onConditionsChanged(false);
- mBackgroundExecutor.runAllReady();
- // Verify ambient light toggles back to light mode regular
- verify(mLowLightDreamManager).setAmbientLightMode(AMBIENT_LIGHT_MODE_REGULAR);
- }
-
- @Test
- public void testStartMonitorLowLightConditionsWhenScreenTurnsOn() {
- mLowLightMonitor.onScreenTurnedOn();
- mBackgroundExecutor.runAllReady();
-
- // Verify subscribing to low light conditions monitor when screen turns on.
- verify(mMonitor).addSubscription(any());
- }
-
- @Test
- public void testStopMonitorLowLightConditionsWhenScreenTurnsOff() {
- final Monitor.Subscription.Token token = mock(Monitor.Subscription.Token.class);
- when(mMonitor.addSubscription(any())).thenReturn(token);
- mLowLightMonitor.onScreenTurnedOn();
-
- // Verify removing subscription when screen turns off.
- mLowLightMonitor.onScreenTurnedOff();
- mBackgroundExecutor.runAllReady();
- verify(mMonitor).removeSubscription(token);
- }
-
- @Test
- public void testSubscribeToLowLightConditionsOnlyOnceWhenScreenTurnsOn() {
- final Monitor.Subscription.Token token = mock(Monitor.Subscription.Token.class);
- when(mMonitor.addSubscription(any())).thenReturn(token);
-
- mLowLightMonitor.onScreenTurnedOn();
- mLowLightMonitor.onScreenTurnedOn();
- mBackgroundExecutor.runAllReady();
- // Verify subscription is only added once.
- verify(mMonitor, times(1)).addSubscription(any());
- }
-
- @Test
- public void testSubscribedToExpectedConditions() {
- final Monitor.Subscription.Token token = mock(Monitor.Subscription.Token.class);
- when(mMonitor.addSubscription(any())).thenReturn(token);
-
- mLowLightMonitor.onScreenTurnedOn();
- mLowLightMonitor.onScreenTurnedOn();
- mBackgroundExecutor.runAllReady();
- Set<Condition> conditions = captureConditions();
- // Verify Monitor is subscribed to the expected conditions
- assertThat(conditions).isEqualTo(mConditionSet);
- }
-
- @Test
- public void testNotUnsubscribeIfNotSubscribedWhenScreenTurnsOff() {
- mLowLightMonitor.onScreenTurnedOff();
- mBackgroundExecutor.runAllReady();
- // Verify doesn't remove subscription since there is none.
- verify(mMonitor, never()).removeSubscription(any());
- }
-
- @Test
- public void testSubscribeIfScreenIsOnWhenStarting() {
- when(mScreenLifecycle.getScreenState()).thenReturn(SCREEN_ON);
- mLowLightMonitor.start();
- mBackgroundExecutor.runAllReady();
- // Verify to add subscription on start if the screen state is on
- verify(mMonitor, times(1)).addSubscription(any());
- }
-
- @Test
- public void testNoSubscribeIfDreamNotPresent() {
- LowLightMonitor lowLightMonitor = new LowLightMonitor(mLowLightDreamManagerLazy,
- mMonitor, mLazyConditions, mScreenLifecycle, mLogger, null, mPackageManager,
- mBackgroundExecutor);
- when(mScreenLifecycle.getScreenState()).thenReturn(SCREEN_ON);
- lowLightMonitor.start();
- mBackgroundExecutor.runAllReady();
- verify(mScreenLifecycle, never()).addObserver(any());
- }
-
- private Set<Condition> captureConditions() {
- verify(mMonitor).addSubscription(mPreconditionsSubscriptionCaptor.capture());
- return mPreconditionsSubscriptionCaptor.getValue().getConditions();
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/lowlightclock/LowLightMonitorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/lowlightclock/LowLightMonitorTest.kt
new file mode 100644
index 000000000000..11f0f4394a85
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/lowlightclock/LowLightMonitorTest.kt
@@ -0,0 +1,271 @@
+/*
+ * 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.ComponentName
+import android.content.pm.PackageManager
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.dream.lowlight.LowLightDreamManager
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.domain.interactor.displayStateInteractor
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.kosmos.runCurrent
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.shared.condition.Condition
+import com.android.systemui.shared.condition.Monitor
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth
+import dagger.Lazy
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.clearInvocations
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@RunWithLooper
+class LowLightMonitorTest : SysuiTestCase() {
+ val kosmos = testKosmos().useUnconfinedTestDispatcher()
+
+ @Mock private lateinit var lowLightDreamManagerLazy: Lazy<LowLightDreamManager>
+
+ @Mock private lateinit var lowLightDreamManager: LowLightDreamManager
+
+ private val monitor: Monitor = prepareMonitor()
+
+ @Mock private lateinit var logger: LowLightLogger
+
+ private lateinit var lowLightMonitor: LowLightMonitor
+
+ @Mock private lateinit var lazyConditions: Lazy<Set<Condition>>
+
+ @Mock private lateinit var packageManager: PackageManager
+
+ @Mock private lateinit var dreamComponent: ComponentName
+
+ private val condition = mock<Condition>()
+
+ private val conditionSet = setOf(condition)
+
+ @Captor
+ private lateinit var preconditionsSubscriptionCaptor: ArgumentCaptor<Monitor.Subscription>
+
+ private fun prepareMonitor(): Monitor {
+ val monitor = mock<Monitor>()
+ whenever(monitor.addSubscription(ArgumentMatchers.any())).thenReturn(mock())
+
+ return monitor
+ }
+
+ private fun setDisplayOn(screenOn: Boolean) {
+ kosmos.displayRepository.setDefaultDisplayOff(!screenOn)
+ }
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(lowLightDreamManagerLazy.get()).thenReturn(lowLightDreamManager)
+ whenever(lazyConditions.get()).thenReturn(conditionSet)
+ lowLightMonitor =
+ LowLightMonitor(
+ lowLightDreamManagerLazy,
+ monitor,
+ lazyConditions,
+ kosmos.displayStateInteractor,
+ logger,
+ dreamComponent,
+ packageManager,
+ kosmos.testScope.backgroundScope,
+ )
+ whenever(monitor.addSubscription(ArgumentMatchers.any())).thenReturn(mock())
+ val subscriptionCaptor = argumentCaptor<Monitor.Subscription>()
+
+ setDisplayOn(false)
+
+ lowLightMonitor.start()
+ verify(monitor).addSubscription(subscriptionCaptor.capture())
+ clearInvocations(monitor)
+
+ subscriptionCaptor.firstValue.callback.onConditionsChanged(true)
+ }
+
+ private fun getConditionCallback(monitor: Monitor): Monitor.Callback {
+ val subscriptionCaptor = argumentCaptor<Monitor.Subscription>()
+ verify(monitor).addSubscription(subscriptionCaptor.capture())
+ return subscriptionCaptor.firstValue.callback
+ }
+
+ @Test
+ fun testSetAmbientLowLightWhenInLowLight() =
+ kosmos.runTest {
+ // Turn on screen
+ setDisplayOn(true)
+
+ // Set conditions to true
+ val callback = getConditionCallback(monitor)
+ callback.onConditionsChanged(true)
+
+ // Verify setting low light when condition is true
+ Mockito.verify(lowLightDreamManager)
+ .setAmbientLightMode(LowLightDreamManager.AMBIENT_LIGHT_MODE_LOW_LIGHT)
+ }
+
+ @Test
+ fun testExitAmbientLowLightWhenNotInLowLight() =
+ kosmos.runTest {
+ // Turn on screen
+ setDisplayOn(true)
+
+ // Set conditions to true then false
+ val callback = getConditionCallback(monitor)
+ callback.onConditionsChanged(true)
+ clearInvocations(lowLightDreamManager)
+ callback.onConditionsChanged(false)
+
+ // Verify ambient light toggles back to light mode regular
+ Mockito.verify(lowLightDreamManager)
+ .setAmbientLightMode(LowLightDreamManager.AMBIENT_LIGHT_MODE_REGULAR)
+ }
+
+ @Test
+ fun testStopMonitorLowLightConditionsWhenScreenTurnsOff() =
+ kosmos.runTest {
+ val token = mock<Monitor.Subscription.Token>()
+ whenever(monitor.addSubscription(ArgumentMatchers.any())).thenReturn(token)
+
+ setDisplayOn(true)
+
+ // Verify removing subscription when screen turns off.
+ setDisplayOn(false)
+ Mockito.verify(monitor).removeSubscription(token)
+ }
+
+ @Test
+ fun testSubscribeToLowLightConditionsOnlyOnceWhenScreenTurnsOn() =
+ kosmos.runTest {
+ val token = mock<Monitor.Subscription.Token>()
+ whenever(monitor.addSubscription(ArgumentMatchers.any())).thenReturn(token)
+
+ setDisplayOn(true)
+ setDisplayOn(true)
+ // Verify subscription is only added once.
+ Mockito.verify(monitor, Mockito.times(1)).addSubscription(ArgumentMatchers.any())
+ }
+
+ @Test
+ fun testSubscribedToExpectedConditions() =
+ kosmos.runTest {
+ val token = mock<Monitor.Subscription.Token>()
+ whenever(monitor.addSubscription(ArgumentMatchers.any())).thenReturn(token)
+
+ setDisplayOn(true)
+
+ val conditions = captureConditions()
+ // Verify Monitor is subscribed to the expected conditions
+ Truth.assertThat(conditions).isEqualTo(conditionSet)
+ }
+
+ @Test
+ fun testNotUnsubscribeIfNotSubscribedWhenScreenTurnsOff() =
+ kosmos.runTest {
+ setDisplayOn(true)
+ clearInvocations(monitor)
+ setDisplayOn(false)
+ runCurrent()
+ // Verify doesn't remove subscription since there is none.
+ Mockito.verify(monitor).removeSubscription(ArgumentMatchers.any())
+ }
+
+ @Test
+ fun testSubscribeIfScreenIsOnWhenStarting() =
+ kosmos.runTest {
+ val monitor = prepareMonitor()
+
+ setDisplayOn(true)
+
+ val targetMonitor =
+ LowLightMonitor(
+ lowLightDreamManagerLazy,
+ monitor,
+ lazyConditions,
+ displayStateInteractor,
+ logger,
+ dreamComponent,
+ packageManager,
+ testScope.backgroundScope,
+ )
+
+ // start
+ targetMonitor.start()
+
+ val callback = getConditionCallback(monitor)
+ clearInvocations(monitor)
+ callback.onConditionsChanged(true)
+
+ // Verify to add subscription on start and when the screen state is on
+ Mockito.verify(monitor).addSubscription(ArgumentMatchers.any())
+ }
+
+ @Test
+ fun testNoSubscribeIfDreamNotPresent() =
+ kosmos.runTest {
+ val monitor = prepareMonitor()
+
+ setDisplayOn(true)
+
+ val lowLightMonitor =
+ LowLightMonitor(
+ lowLightDreamManagerLazy,
+ monitor,
+ lazyConditions,
+ displayStateInteractor,
+ logger,
+ null,
+ packageManager,
+ testScope,
+ )
+
+ // start
+ lowLightMonitor.start()
+
+ val callback = getConditionCallback(monitor)
+ clearInvocations(monitor)
+ callback.onConditionsChanged(true)
+
+ // Verify to add subscription on start and when the screen state is on
+ Mockito.verify(monitor, never()).addSubscription(ArgumentMatchers.any())
+ }
+
+ private fun captureConditions(): Set<Condition?> {
+ Mockito.verify(monitor).addSubscription(preconditionsSubscriptionCaptor.capture())
+ return preconditionsSubscriptionCaptor.value.conditions
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/AmbientLightModeMonitor.kt b/packages/SystemUI/src/com/android/systemui/lowlightclock/AmbientLightModeMonitor.kt
index c1658e1f1694..5e9e930812a6 100644
--- a/packages/SystemUI/src/com/android/systemui/lowlightclock/AmbientLightModeMonitor.kt
+++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/AmbientLightModeMonitor.kt
@@ -23,7 +23,7 @@ import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.util.Log
import com.android.systemui.Dumpable
-import com.android.systemui.lowlightclock.dagger.LowLightModule.LIGHT_SENSOR
+import com.android.systemui.lowlightclock.dagger.LowLightModule.Companion.LIGHT_SENSOR
import com.android.systemui.util.sensors.AsyncSensorManager
import java.io.PrintWriter
import java.util.Optional
diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightMonitor.java b/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightMonitor.java
deleted file mode 100644
index e5eec64ac615..000000000000
--- a/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightMonitor.java
+++ /dev/null
@@ -1,148 +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.android.dream.lowlight.LowLightDreamManager.AMBIENT_LIGHT_MODE_LOW_LIGHT;
-import static com.android.dream.lowlight.LowLightDreamManager.AMBIENT_LIGHT_MODE_REGULAR;
-import static com.android.systemui.dreams.dagger.DreamModule.LOW_LIGHT_DREAM_SERVICE;
-import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_ON;
-import static com.android.systemui.lowlightclock.dagger.LowLightModule.LOW_LIGHT_PRECONDITIONS;
-
-import android.content.ComponentName;
-import android.content.pm.PackageManager;
-
-import androidx.annotation.Nullable;
-
-import com.android.dream.lowlight.LowLightDreamManager;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.SystemUser;
-import com.android.systemui.keyguard.ScreenLifecycle;
-import com.android.systemui.shared.condition.Condition;
-import com.android.systemui.shared.condition.Monitor;
-import com.android.systemui.util.condition.ConditionalCoreStartable;
-
-import dagger.Lazy;
-
-import java.util.Set;
-import java.util.concurrent.Executor;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-/**
- * Tracks environment (low-light or not) in order to correctly show or hide a low-light clock while
- * dreaming.
- */
-public class LowLightMonitor extends ConditionalCoreStartable implements Monitor.Callback,
- ScreenLifecycle.Observer {
- private static final String TAG = "LowLightMonitor";
-
- private final Lazy<LowLightDreamManager> mLowLightDreamManager;
- private final Monitor mConditionsMonitor;
- private final Lazy<Set<Condition>> mLowLightConditions;
- private Monitor.Subscription.Token mSubscriptionToken;
- private ScreenLifecycle mScreenLifecycle;
- private final LowLightLogger mLogger;
-
- private final ComponentName mLowLightDreamService;
-
- private final PackageManager mPackageManager;
-
- private final Executor mExecutor;
-
- @Inject
- public LowLightMonitor(Lazy<LowLightDreamManager> lowLightDreamManager,
- @SystemUser Monitor conditionsMonitor,
- @Named(LOW_LIGHT_PRECONDITIONS) Lazy<Set<Condition>> lowLightConditions,
- ScreenLifecycle screenLifecycle,
- LowLightLogger lowLightLogger,
- @Nullable @Named(LOW_LIGHT_DREAM_SERVICE) ComponentName lowLightDreamService,
- PackageManager packageManager,
- @Background Executor backgroundExecutor) {
- super(conditionsMonitor);
- mLowLightDreamManager = lowLightDreamManager;
- mConditionsMonitor = conditionsMonitor;
- mLowLightConditions = lowLightConditions;
- mScreenLifecycle = screenLifecycle;
- mLogger = lowLightLogger;
- mLowLightDreamService = lowLightDreamService;
- mPackageManager = packageManager;
- mExecutor = backgroundExecutor;
- }
-
- @Override
- public void onConditionsChanged(boolean allConditionsMet) {
- mExecutor.execute(() -> {
- mLogger.d(TAG, "Low light enabled: " + allConditionsMet);
-
- mLowLightDreamManager.get().setAmbientLightMode(allConditionsMet
- ? AMBIENT_LIGHT_MODE_LOW_LIGHT : AMBIENT_LIGHT_MODE_REGULAR);
- });
- }
-
- @Override
- public void onScreenTurnedOn() {
- mExecutor.execute(() -> {
- if (mSubscriptionToken == null) {
- mLogger.d(TAG, "Screen turned on. Subscribing to low light conditions.");
-
- mSubscriptionToken = mConditionsMonitor.addSubscription(
- new Monitor.Subscription.Builder(this)
- .addConditions(mLowLightConditions.get())
- .build());
- }
- });
- }
-
-
- @Override
- public void onScreenTurnedOff() {
- mExecutor.execute(() -> {
- if (mSubscriptionToken != null) {
- mLogger.d(TAG, "Screen turned off. Removing subscription to low light conditions.");
-
- mConditionsMonitor.removeSubscription(mSubscriptionToken);
- mSubscriptionToken = null;
- }
- });
- }
-
- @Override
- protected void onStart() {
- mExecutor.execute(() -> {
- if (mLowLightDreamService != null) {
- // Note that the dream service is disabled by default. This prevents the dream from
- // appearing in settings on devices that don't have it explicitly excluded (done in
- // the settings overlay). Therefore, the component is enabled if it is to be used
- // here.
- mPackageManager.setComponentEnabledSetting(
- mLowLightDreamService,
- PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
- PackageManager.DONT_KILL_APP
- );
- } else {
- // If there is no low light dream service, do not observe conditions.
- return;
- }
-
- mScreenLifecycle.addObserver(this);
- if (mScreenLifecycle.getScreenState() == SCREEN_ON) {
- onScreenTurnedOn();
- }
- });
-
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightMonitor.kt b/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightMonitor.kt
new file mode 100644
index 000000000000..137226332e38
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/LowLightMonitor.kt
@@ -0,0 +1,116 @@
+/*
+ * 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.ComponentName
+import android.content.pm.PackageManager
+import com.android.dream.lowlight.LowLightDreamManager
+import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.SystemUser
+import com.android.systemui.dreams.dagger.DreamModule
+import com.android.systemui.lowlightclock.dagger.LowLightModule
+import com.android.systemui.shared.condition.Condition
+import com.android.systemui.shared.condition.Monitor
+import com.android.systemui.util.condition.ConditionalCoreStartable
+import com.android.systemui.util.kotlin.BooleanFlowOperators.not
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import dagger.Lazy
+import javax.inject.Inject
+import javax.inject.Named
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.launch
+
+/**
+ * Tracks environment (low-light or not) in order to correctly show or hide a low-light clock while
+ * dreaming.
+ */
+class LowLightMonitor
+@Inject
+constructor(
+ private val lowLightDreamManager: Lazy<LowLightDreamManager>,
+ @param:SystemUser private val conditionsMonitor: Monitor,
+ @param:Named(LowLightModule.LOW_LIGHT_PRECONDITIONS)
+ private val lowLightConditions: Lazy<Set<Condition>>,
+ displayStateInteractor: DisplayStateInteractor,
+ private val logger: LowLightLogger,
+ @param:Named(DreamModule.LOW_LIGHT_DREAM_SERVICE)
+ private val lowLightDreamService: ComponentName?,
+ private val packageManager: PackageManager,
+ @Background private val scope: CoroutineScope,
+) : ConditionalCoreStartable(conditionsMonitor) {
+ private val isScreenOn = not(displayStateInteractor.isDefaultDisplayOff).distinctUntilChanged()
+
+ private val isLowLight = conflatedCallbackFlow {
+ val token =
+ conditionsMonitor.addSubscription(
+ Monitor.Subscription.Builder { trySend(it) }
+ .addConditions(lowLightConditions.get())
+ .build()
+ )
+
+ awaitClose { conditionsMonitor.removeSubscription(token) }
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ override fun onStart() {
+ scope.launch {
+ if (lowLightDreamService != null) {
+ // Note that the dream service is disabled by default. This prevents the dream from
+ // appearing in settings on devices that don't have it explicitly excluded (done in
+ // the settings overlay). Therefore, the component is enabled if it is to be used
+ // here.
+ packageManager.setComponentEnabledSetting(
+ lowLightDreamService,
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+ PackageManager.DONT_KILL_APP,
+ )
+ } else {
+ // If there is no low light dream service, do not observe conditions.
+ return@launch
+ }
+
+ isScreenOn
+ .flatMapLatest {
+ if (it) {
+ isLowLight
+ } else {
+ flowOf(false)
+ }
+ }
+ .distinctUntilChanged()
+ .collect {
+ logger.d(TAG, "Low light enabled: $it")
+ lowLightDreamManager
+ .get()
+ .setAmbientLightMode(
+ if (it) LowLightDreamManager.AMBIENT_LIGHT_MODE_LOW_LIGHT
+ else LowLightDreamManager.AMBIENT_LIGHT_MODE_REGULAR
+ )
+ }
+ }
+ }
+
+ companion object {
+ private const val TAG = "LowLightMonitor"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/dagger/LowLightModule.java b/packages/SystemUI/src/com/android/systemui/lowlightclock/dagger/LowLightModule.java
deleted file mode 100644
index f8072f2f79b4..000000000000
--- a/packages/SystemUI/src/com/android/systemui/lowlightclock/dagger/LowLightModule.java
+++ /dev/null
@@ -1,152 +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.dagger;
-
-import android.annotation.Nullable;
-import android.content.res.Resources;
-import android.hardware.Sensor;
-
-import com.android.dream.lowlight.dagger.LowLightDreamModule;
-import com.android.systemui.CoreStartable;
-import com.android.systemui.communal.DeviceInactiveCondition;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.log.LogBuffer;
-import com.android.systemui.log.LogBufferFactory;
-import com.android.systemui.lowlightclock.AmbientLightModeMonitor;
-import com.android.systemui.lowlightclock.DirectBootCondition;
-import com.android.systemui.lowlightclock.ForceLowLightCondition;
-import com.android.systemui.lowlightclock.LowLightCondition;
-import com.android.systemui.lowlightclock.LowLightDisplayController;
-import com.android.systemui.lowlightclock.LowLightMonitor;
-import com.android.systemui.lowlightclock.ScreenSaverEnabledCondition;
-import com.android.systemui.res.R;
-import com.android.systemui.shared.condition.Condition;
-
-import dagger.Binds;
-import dagger.BindsOptionalOf;
-import dagger.Module;
-import dagger.Provides;
-import dagger.multibindings.ClassKey;
-import dagger.multibindings.IntoMap;
-import dagger.multibindings.IntoSet;
-
-import javax.inject.Named;
-
-@Module(includes = LowLightDreamModule.class)
-public abstract class LowLightModule {
- public static final String Y_TRANSLATION_ANIMATION_OFFSET =
- "y_translation_animation_offset";
- public static final String Y_TRANSLATION_ANIMATION_DURATION_MILLIS =
- "y_translation_animation_duration_millis";
- public static final String ALPHA_ANIMATION_IN_START_DELAY_MILLIS =
- "alpha_animation_in_start_delay_millis";
- public static final String ALPHA_ANIMATION_DURATION_MILLIS =
- "alpha_animation_duration_millis";
- public static final String LOW_LIGHT_PRECONDITIONS = "low_light_preconditions";
- public static final String LIGHT_SENSOR = "low_light_monitor_light_sensor";
-
-
- /**
- * Provides a {@link LogBuffer} for logs related to low-light features.
- */
- @Provides
- @SysUISingleton
- @LowLightLog
- public static LogBuffer provideLowLightLogBuffer(LogBufferFactory factory) {
- return factory.create("LowLightLog", 250);
- }
-
- @Binds
- @IntoSet
- @Named(LOW_LIGHT_PRECONDITIONS)
- abstract Condition bindScreenSaverEnabledCondition(ScreenSaverEnabledCondition condition);
-
- @Provides
- @IntoSet
- @Named(LOW_LIGHT_PRECONDITIONS)
- static Condition provideLowLightCondition(LowLightCondition lowLightCondition,
- DirectBootCondition directBootCondition) {
- // Start lowlight if we are either in lowlight or in direct boot. The ordering of the
- // conditions matters here since we don't want to start the lowlight condition if
- // we are in direct boot mode.
- return directBootCondition.or(lowLightCondition);
- }
-
- @Binds
- @IntoSet
- @Named(LOW_LIGHT_PRECONDITIONS)
- abstract Condition bindForceLowLightCondition(ForceLowLightCondition condition);
-
- @Binds
- @IntoSet
- @Named(LOW_LIGHT_PRECONDITIONS)
- abstract Condition bindDeviceInactiveCondition(DeviceInactiveCondition condition);
-
- @BindsOptionalOf
- abstract LowLightDisplayController bindsLowLightDisplayController();
-
- @BindsOptionalOf
- @Nullable
- @Named(LIGHT_SENSOR)
- abstract Sensor bindsLightSensor();
-
- @BindsOptionalOf
- abstract AmbientLightModeMonitor.DebounceAlgorithm bindsDebounceAlgorithm();
-
- /**
- *
- */
- @Provides
- @Named(Y_TRANSLATION_ANIMATION_OFFSET)
- static int providesAnimationInOffset(@Main Resources resources) {
- return resources.getDimensionPixelOffset(
- R.dimen.low_light_clock_translate_animation_offset);
- }
-
- /**
- *
- */
- @Provides
- @Named(Y_TRANSLATION_ANIMATION_DURATION_MILLIS)
- static long providesAnimationDurationMillis(@Main Resources resources) {
- return resources.getInteger(R.integer.low_light_clock_translate_animation_duration_ms);
- }
-
- /**
- *
- */
- @Provides
- @Named(ALPHA_ANIMATION_IN_START_DELAY_MILLIS)
- static long providesAlphaAnimationInStartDelayMillis(@Main Resources resources) {
- return resources.getInteger(R.integer.low_light_clock_alpha_animation_in_start_delay_ms);
- }
-
- /**
- *
- */
- @Provides
- @Named(ALPHA_ANIMATION_DURATION_MILLIS)
- static long providesAlphaAnimationDurationMillis(@Main Resources resources) {
- return resources.getInteger(R.integer.low_light_clock_alpha_animation_duration_ms);
- }
- /** Inject into LowLightMonitor. */
- @Binds
- @IntoMap
- @ClassKey(LowLightMonitor.class)
- abstract CoreStartable bindLowLightMonitor(LowLightMonitor lowLightMonitor);
-}
diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/dagger/LowLightModule.kt b/packages/SystemUI/src/com/android/systemui/lowlightclock/dagger/LowLightModule.kt
new file mode 100644
index 000000000000..6b3254e928ec
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/dagger/LowLightModule.kt
@@ -0,0 +1,146 @@
+/*
+ * 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.dagger
+
+import android.content.res.Resources
+import android.hardware.Sensor
+import com.android.dream.lowlight.dagger.LowLightDreamModule
+import com.android.systemui.CoreStartable
+import com.android.systemui.communal.DeviceInactiveCondition
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.lowlightclock.AmbientLightModeMonitor.DebounceAlgorithm
+import com.android.systemui.lowlightclock.DirectBootCondition
+import com.android.systemui.lowlightclock.ForceLowLightCondition
+import com.android.systemui.lowlightclock.LowLightCondition
+import com.android.systemui.lowlightclock.LowLightDisplayController
+import com.android.systemui.lowlightclock.LowLightMonitor
+import com.android.systemui.lowlightclock.ScreenSaverEnabledCondition
+import com.android.systemui.res.R
+import com.android.systemui.shared.condition.Condition
+import dagger.Binds
+import dagger.BindsOptionalOf
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
+import javax.inject.Named
+
+@Module(includes = [LowLightDreamModule::class])
+abstract class LowLightModule {
+ @Binds
+ @IntoSet
+ @Named(LOW_LIGHT_PRECONDITIONS)
+ abstract fun bindScreenSaverEnabledCondition(condition: ScreenSaverEnabledCondition): Condition
+
+ @Binds
+ @IntoSet
+ @Named(LOW_LIGHT_PRECONDITIONS)
+ abstract fun bindForceLowLightCondition(condition: ForceLowLightCondition): Condition
+
+ @Binds
+ @IntoSet
+ @Named(LOW_LIGHT_PRECONDITIONS)
+ abstract fun bindDeviceInactiveCondition(condition: DeviceInactiveCondition): Condition
+
+ @BindsOptionalOf abstract fun bindsLowLightDisplayController(): LowLightDisplayController
+
+ @BindsOptionalOf @Named(LIGHT_SENSOR) abstract fun bindsLightSensor(): Sensor
+
+ @BindsOptionalOf abstract fun bindsDebounceAlgorithm(): DebounceAlgorithm
+
+ /** Inject into LowLightMonitor. */
+ @Binds
+ @IntoMap
+ @ClassKey(LowLightMonitor::class)
+ abstract fun bindLowLightMonitor(lowLightMonitor: LowLightMonitor): CoreStartable
+
+ companion object {
+ const val Y_TRANSLATION_ANIMATION_OFFSET: String = "y_translation_animation_offset"
+ const val Y_TRANSLATION_ANIMATION_DURATION_MILLIS: String =
+ "y_translation_animation_duration_millis"
+ const val ALPHA_ANIMATION_IN_START_DELAY_MILLIS: String =
+ "alpha_animation_in_start_delay_millis"
+ const val ALPHA_ANIMATION_DURATION_MILLIS: String = "alpha_animation_duration_millis"
+ const val LOW_LIGHT_PRECONDITIONS: String = "low_light_preconditions"
+ const val LIGHT_SENSOR: String = "low_light_monitor_light_sensor"
+
+ /** Provides a [LogBuffer] for logs related to low-light features. */
+ @JvmStatic
+ @Provides
+ @SysUISingleton
+ @LowLightLog
+ fun provideLowLightLogBuffer(factory: LogBufferFactory): LogBuffer {
+ return factory.create("LowLightLog", 250)
+ }
+
+ @Provides
+ @IntoSet
+ @Named(LOW_LIGHT_PRECONDITIONS)
+ fun provideLowLightCondition(
+ lowLightCondition: LowLightCondition,
+ directBootCondition: DirectBootCondition,
+ ): Condition {
+ // Start lowlight if we are either in lowlight or in direct boot. The ordering of the
+ // conditions matters here since we don't want to start the lowlight condition if
+ // we are in direct boot mode.
+ return directBootCondition.or(lowLightCondition)
+ }
+
+ /** */
+ @JvmStatic
+ @Provides
+ @Named(Y_TRANSLATION_ANIMATION_OFFSET)
+ fun providesAnimationInOffset(@Main resources: Resources): Int {
+ return resources.getDimensionPixelOffset(
+ R.dimen.low_light_clock_translate_animation_offset
+ )
+ }
+
+ /** */
+ @JvmStatic
+ @Provides
+ @Named(Y_TRANSLATION_ANIMATION_DURATION_MILLIS)
+ fun providesAnimationDurationMillis(@Main resources: Resources): Long {
+ return resources
+ .getInteger(R.integer.low_light_clock_translate_animation_duration_ms)
+ .toLong()
+ }
+
+ /** */
+ @JvmStatic
+ @Provides
+ @Named(ALPHA_ANIMATION_IN_START_DELAY_MILLIS)
+ fun providesAlphaAnimationInStartDelayMillis(@Main resources: Resources): Long {
+ return resources
+ .getInteger(R.integer.low_light_clock_alpha_animation_in_start_delay_ms)
+ .toLong()
+ }
+
+ /** */
+ @JvmStatic
+ @Provides
+ @Named(ALPHA_ANIMATION_DURATION_MILLIS)
+ fun providesAlphaAnimationDurationMillis(@Main resources: Resources): Long {
+ return resources
+ .getInteger(R.integer.low_light_clock_alpha_animation_duration_ms)
+ .toLong()
+ }
+ }
+}