diff options
author | 2025-03-22 13:13:13 -0700 | |
---|---|---|
committer | 2025-03-22 13:13:13 -0700 | |
commit | 68c80f9a909b04f257808b8e8ba9d7e65cb6896b (patch) | |
tree | 0a9abcfcfdf5170254f96eee877c03c7e8c16e54 | |
parent | cda8332969831337cb68982e4b66764bee9005e9 (diff) | |
parent | bdb510af8e97a897c1bf97650be73380c5d43749 (diff) |
Merge changes Ie6b4dd45,Id10a59d6 into main
* changes:
Listen to DisplayStateInteractor for LowLightMonitor display changes.
Convert LowLightMonitor to kotlin.
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() + } + } +} |