Loading packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayComponentInstanceProviderTest.kt 0 → 100644 +62 −0 Original line number Diff line number Diff line /* * 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.display.data.repository import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.display.dagger.SystemUIDisplaySubcomponent import com.android.systemui.testKosmos import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.mock import org.mockito.kotlin.verify @RunWith(AndroidJUnit4::class) @SmallTest class DisplayComponentInstanceProviderTest : SysuiTestCase() { private val kosmos = testKosmos() private val underTest = DisplayComponentInstanceProvider(kosmos.fakeSysuiDisplayComponentFactory) @Test fun createInstance_notifiesLifecycleListenersWithStart() { val lifecycleListeners = (1..10).map { mock<SystemUIDisplaySubcomponent.LifecycleListener>() } kosmos.sysUiDefaultDisplaySubcomponentLifecycleListeners += lifecycleListeners underTest.createInstance(displayId = 123) lifecycleListeners.forEach { verify(it).start() } } @Test fun destroyInstance_notifiesLifecycleListenersWithStop() { val lifecycleListeners = (1..10).map { mock<SystemUIDisplaySubcomponent.LifecycleListener>() } kosmos.sysUiDefaultDisplaySubcomponentLifecycleListeners += lifecycleListeners val subcomponent = underTest.createInstance(displayId = 123) underTest.destroyInstance(subcomponent!!) lifecycleListeners.forEach { verify(it).stop() } } } packages/SystemUI/src/com/android/systemui/display/dagger/PerDisplayCommonModule.kt +5 −0 Original line number Diff line number Diff line Loading @@ -32,6 +32,7 @@ import com.android.systemui.display.domain.interactor.DisplayStateInteractorImpl import dagger.Binds import dagger.Module import dagger.Provides import dagger.multibindings.Multibinds import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope Loading @@ -39,6 +40,10 @@ import kotlinx.coroutines.CoroutineScope @Module interface PerDisplayCommonModule { @Multibinds @DisplayAware fun lifecycleListeners(): Set<SystemUIDisplaySubcomponent.LifecycleListener> @Binds @PerDisplaySingleton @DisplayAware Loading packages/SystemUI/src/com/android/systemui/display/dagger/SystemUIDisplaySubcomponent.kt +20 −0 Original line number Diff line number Diff line Loading @@ -48,6 +48,8 @@ interface SystemUIDisplaySubcomponent { @get:DisplayAware val statusBarIconRefreshInteractor: StatusBarIconRefreshInteractor @get:DisplayAware val lifecycleListeners: Set<LifecycleListener> @Subcomponent.Factory interface Factory { fun create(@BindsInstance @DisplayId displayId: Int): SystemUIDisplaySubcomponent Loading @@ -71,4 +73,22 @@ interface SystemUIDisplaySubcomponent { * TODO(b/408503553): Remove this annotation once the flag is cleaned up. */ @Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class DisplayLib /** * Listens for lifecycle events of the [SystemUIDisplaySubcomponent], which correspond to the * lifecycle of the display associated with this [Subcomponent]. */ interface LifecycleListener { /** * Called when the display associated with this [SystemUIDisplaySubcomponent] has been * created, and the [Subcomponent] has been created. */ fun start() {} /** * Called when the display associated with this [SystemUIDisplaySubcomponent] has been * removed, and the component will be destroyed. */ fun stop() {} } } packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayComponentRepository.kt +16 −1 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.systemui.display.data.repository import com.android.app.displaylib.PerDisplayInstanceProviderWithTeardown import com.android.app.displaylib.PerDisplayInstanceRepositoryImpl import com.android.app.displaylib.PerDisplayRepository import com.android.app.tracing.ListenersTracing.forEachTraced import com.android.app.tracing.traceSection import com.android.systemui.dagger.SysUISingleton import com.android.systemui.display.dagger.SystemUIDisplaySubcomponent Loading @@ -33,12 +34,26 @@ class DisplayComponentInstanceProvider constructor(private val componentFactory: SystemUIDisplaySubcomponent.Factory) : PerDisplayInstanceProviderWithTeardown<SystemUIDisplaySubcomponent> { override fun createInstance(displayId: Int): SystemUIDisplaySubcomponent? = runCatching { componentFactory.create(displayId) }.getOrNull() runCatching { componentFactory.create(displayId).also { subComponent -> subComponent.lifecycleListeners.forEachTraced( "Notifying listeners of a display component creation" ) { it.start() } } } .getOrNull() override fun destroyInstance(instance: SystemUIDisplaySubcomponent) { traceSection("Destroying a display component instance") { instance.displayCoroutineScope.cancel("Cancelling scope associated to the display.") } instance.lifecycleListeners.forEachTraced( "Notifying listeners of a display component destruction" ) { it.stop() } } } Loading packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayRepositoryKosmos.kt +7 −0 Original line number Diff line number Diff line Loading @@ -35,6 +35,10 @@ import kotlinx.coroutines.test.UnconfinedTestDispatcher val Kosmos.displayRepository by Fixture { FakeDisplayRepository() } val Kosmos.sysUiDefaultDisplaySubcomponentLifecycleListeners by Fixture { mutableSetOf<SystemUIDisplaySubcomponent.LifecycleListener>() } fun Kosmos.createFakeDisplaySubcomponent( coroutineScope: CoroutineScope = testScope.backgroundScope, displayStateRepository: DisplayStateRepository = mock<DisplayStateRepository>(), Loading @@ -54,6 +58,9 @@ fun Kosmos.createFakeDisplaySubcomponent( override val statusBarIconRefreshInteractor: StatusBarIconRefreshInteractor = statusbarIconRefreshInteractorFromConstructor override val lifecycleListeners: Set<SystemUIDisplaySubcomponent.LifecycleListener> = sysUiDefaultDisplaySubcomponentLifecycleListeners } } Loading Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayComponentInstanceProviderTest.kt 0 → 100644 +62 −0 Original line number Diff line number Diff line /* * 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.display.data.repository import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.display.dagger.SystemUIDisplaySubcomponent import com.android.systemui.testKosmos import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.mock import org.mockito.kotlin.verify @RunWith(AndroidJUnit4::class) @SmallTest class DisplayComponentInstanceProviderTest : SysuiTestCase() { private val kosmos = testKosmos() private val underTest = DisplayComponentInstanceProvider(kosmos.fakeSysuiDisplayComponentFactory) @Test fun createInstance_notifiesLifecycleListenersWithStart() { val lifecycleListeners = (1..10).map { mock<SystemUIDisplaySubcomponent.LifecycleListener>() } kosmos.sysUiDefaultDisplaySubcomponentLifecycleListeners += lifecycleListeners underTest.createInstance(displayId = 123) lifecycleListeners.forEach { verify(it).start() } } @Test fun destroyInstance_notifiesLifecycleListenersWithStop() { val lifecycleListeners = (1..10).map { mock<SystemUIDisplaySubcomponent.LifecycleListener>() } kosmos.sysUiDefaultDisplaySubcomponentLifecycleListeners += lifecycleListeners val subcomponent = underTest.createInstance(displayId = 123) underTest.destroyInstance(subcomponent!!) lifecycleListeners.forEach { verify(it).stop() } } }
packages/SystemUI/src/com/android/systemui/display/dagger/PerDisplayCommonModule.kt +5 −0 Original line number Diff line number Diff line Loading @@ -32,6 +32,7 @@ import com.android.systemui.display.domain.interactor.DisplayStateInteractorImpl import dagger.Binds import dagger.Module import dagger.Provides import dagger.multibindings.Multibinds import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope Loading @@ -39,6 +40,10 @@ import kotlinx.coroutines.CoroutineScope @Module interface PerDisplayCommonModule { @Multibinds @DisplayAware fun lifecycleListeners(): Set<SystemUIDisplaySubcomponent.LifecycleListener> @Binds @PerDisplaySingleton @DisplayAware Loading
packages/SystemUI/src/com/android/systemui/display/dagger/SystemUIDisplaySubcomponent.kt +20 −0 Original line number Diff line number Diff line Loading @@ -48,6 +48,8 @@ interface SystemUIDisplaySubcomponent { @get:DisplayAware val statusBarIconRefreshInteractor: StatusBarIconRefreshInteractor @get:DisplayAware val lifecycleListeners: Set<LifecycleListener> @Subcomponent.Factory interface Factory { fun create(@BindsInstance @DisplayId displayId: Int): SystemUIDisplaySubcomponent Loading @@ -71,4 +73,22 @@ interface SystemUIDisplaySubcomponent { * TODO(b/408503553): Remove this annotation once the flag is cleaned up. */ @Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class DisplayLib /** * Listens for lifecycle events of the [SystemUIDisplaySubcomponent], which correspond to the * lifecycle of the display associated with this [Subcomponent]. */ interface LifecycleListener { /** * Called when the display associated with this [SystemUIDisplaySubcomponent] has been * created, and the [Subcomponent] has been created. */ fun start() {} /** * Called when the display associated with this [SystemUIDisplaySubcomponent] has been * removed, and the component will be destroyed. */ fun stop() {} } }
packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayComponentRepository.kt +16 −1 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.systemui.display.data.repository import com.android.app.displaylib.PerDisplayInstanceProviderWithTeardown import com.android.app.displaylib.PerDisplayInstanceRepositoryImpl import com.android.app.displaylib.PerDisplayRepository import com.android.app.tracing.ListenersTracing.forEachTraced import com.android.app.tracing.traceSection import com.android.systemui.dagger.SysUISingleton import com.android.systemui.display.dagger.SystemUIDisplaySubcomponent Loading @@ -33,12 +34,26 @@ class DisplayComponentInstanceProvider constructor(private val componentFactory: SystemUIDisplaySubcomponent.Factory) : PerDisplayInstanceProviderWithTeardown<SystemUIDisplaySubcomponent> { override fun createInstance(displayId: Int): SystemUIDisplaySubcomponent? = runCatching { componentFactory.create(displayId) }.getOrNull() runCatching { componentFactory.create(displayId).also { subComponent -> subComponent.lifecycleListeners.forEachTraced( "Notifying listeners of a display component creation" ) { it.start() } } } .getOrNull() override fun destroyInstance(instance: SystemUIDisplaySubcomponent) { traceSection("Destroying a display component instance") { instance.displayCoroutineScope.cancel("Cancelling scope associated to the display.") } instance.lifecycleListeners.forEachTraced( "Notifying listeners of a display component destruction" ) { it.stop() } } } Loading
packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayRepositoryKosmos.kt +7 −0 Original line number Diff line number Diff line Loading @@ -35,6 +35,10 @@ import kotlinx.coroutines.test.UnconfinedTestDispatcher val Kosmos.displayRepository by Fixture { FakeDisplayRepository() } val Kosmos.sysUiDefaultDisplaySubcomponentLifecycleListeners by Fixture { mutableSetOf<SystemUIDisplaySubcomponent.LifecycleListener>() } fun Kosmos.createFakeDisplaySubcomponent( coroutineScope: CoroutineScope = testScope.backgroundScope, displayStateRepository: DisplayStateRepository = mock<DisplayStateRepository>(), Loading @@ -54,6 +58,9 @@ fun Kosmos.createFakeDisplaySubcomponent( override val statusBarIconRefreshInteractor: StatusBarIconRefreshInteractor = statusbarIconRefreshInteractorFromConstructor override val lifecycleListeners: Set<SystemUIDisplaySubcomponent.LifecycleListener> = sysUiDefaultDisplaySubcomponentLifecycleListeners } } Loading