Loading packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt +3 −2 Original line number Diff line number Diff line Loading @@ -32,13 +32,14 @@ import javax.inject.Inject * A centralized class maintaining the state of the status bar window. * * @deprecated use * [com.android.systemui.statusbar.window.data.repository.StatusBarWindowStateRepository] instead. * [com.android.systemui.statusbar.window.data.repository.StatusBarWindowStateRepositoryStore.defaultDisplay] * repo instead. * * Classes that want to get updates about the status bar window state should subscribe to this class * via [addListener] and should NOT add their own callback on [CommandQueue]. */ @SysUISingleton @Deprecated("Use StatusBarWindowRepository instead") @Deprecated("Use StatusBarWindowStateRepositoryStore.defaultDisplay instead") class StatusBarWindowStateController @Inject constructor(@DisplayId private val thisDisplayId: Int, commandQueue: CommandQueue) { Loading packages/SystemUI/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepository.kt→packages/SystemUI/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStatePerDisplayRepository.kt +21 −9 Original line number Diff line number Diff line Loading @@ -22,13 +22,13 @@ import android.app.StatusBarManager.WINDOW_STATE_HIDING import android.app.StatusBarManager.WINDOW_STATE_SHOWING import android.app.StatusBarManager.WINDOW_STATUS_BAR import android.app.StatusBarManager.WindowVisibleState import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.DisplayId import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.window.data.model.StatusBarWindowState import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import javax.inject.Inject import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.SharingStarted Loading @@ -40,16 +40,23 @@ import kotlinx.coroutines.flow.stateIn * * Classes that want to get updates about the status bar window state should subscribe to * [windowState] and should NOT add their own callback on [CommandQueue]. * * Each concrete implementation of this class will be for a specific display ID. Use * [StatusBarWindowStateRepositoryStore] to fetch a concrete implementation for a certain display. */ @SysUISingleton class StatusBarWindowStateRepository @Inject interface StatusBarWindowStatePerDisplayRepository { /** Emits the current window state for the status bar on this specific display. */ val windowState: StateFlow<StatusBarWindowState> } class StatusBarWindowStatePerDisplayRepositoryImpl @AssistedInject constructor( @Assisted private val thisDisplayId: Int, private val commandQueue: CommandQueue, @DisplayId private val thisDisplayId: Int, @Application private val scope: CoroutineScope, ) { val windowState: StateFlow<StatusBarWindowState> = ) : StatusBarWindowStatePerDisplayRepository { override val windowState: StateFlow<StatusBarWindowState> = conflatedCallbackFlow { val callback = object : CommandQueue.Callbacks { Loading Loading @@ -84,3 +91,8 @@ constructor( } } } @AssistedFactory interface StatusBarWindowStatePerDisplayRepositoryFactory { fun create(@Assisted displayId: Int): StatusBarWindowStatePerDisplayRepositoryImpl } packages/SystemUI/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepositoryStore.kt 0 → 100644 +60 −0 Original line number Diff line number Diff line /* * 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.statusbar.window.data.repository import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.DisplayId import java.lang.ref.WeakReference import javax.inject.Inject /** * Singleton class to create instances of [StatusBarWindowStatePerDisplayRepository] for a specific * display. * * Repository instances for a specific display should be cached so that if multiple classes request * a repository for the same display ID, we only create the repository once. */ interface StatusBarWindowStateRepositoryStore { val defaultDisplay: StatusBarWindowStatePerDisplayRepository fun forDisplay(displayId: Int): StatusBarWindowStatePerDisplayRepository } @SysUISingleton class StatusBarWindowStateRepositoryStoreImpl @Inject constructor( @DisplayId private val displayId: Int, private val factory: StatusBarWindowStatePerDisplayRepositoryFactory, ) : StatusBarWindowStateRepositoryStore { // Use WeakReferences to store the repositories so that the repositories are kept around so long // as some UI holds a reference to them, but the repositories are cleaned up once no UI is using // them anymore. // See Change-Id Ib490062208506d646add2fe7e5e5d4df5fb3e66e for similar behavior in // MobileConnectionsRepositoryImpl. private val repositoryCache = mutableMapOf<Int, WeakReference<StatusBarWindowStatePerDisplayRepository>>() override val defaultDisplay = factory.create(displayId) override fun forDisplay(displayId: Int): StatusBarWindowStatePerDisplayRepository { synchronized(repositoryCache) { return repositoryCache[displayId]?.get() ?: factory.create(displayId).also { repositoryCache[displayId] = WeakReference(it) } } } } packages/SystemUI/tests/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepositoryTest.kt→packages/SystemUI/tests/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStatePerDisplayRepositoryTest.kt +6 −2 Original line number Diff line number Diff line Loading @@ -39,12 +39,16 @@ import org.mockito.kotlin.argumentCaptor @SmallTest @OptIn(ExperimentalCoroutinesApi::class) class StatusBarWindowStateRepositoryTest : SysuiTestCase() { class StatusBarWindowStatePerDisplayRepositoryTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val commandQueue = kosmos.commandQueue private val underTest = StatusBarWindowStateRepository(commandQueue, DISPLAY_ID, testScope.backgroundScope) StatusBarWindowStatePerDisplayRepositoryImpl( DISPLAY_ID, commandQueue, testScope.backgroundScope, ) private val callback: CommandQueue.Callbacks get() { Loading packages/SystemUI/tests/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepositoryStoreTest.kt 0 → 100644 +123 −0 Original line number Diff line number Diff line /* * 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.statusbar.window.data.repository import android.app.StatusBarManager.WINDOW_STATE_HIDDEN import android.app.StatusBarManager.WINDOW_STATE_SHOWING import android.app.StatusBarManager.WINDOW_STATUS_BAR import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope import com.android.systemui.settings.displayTracker import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.commandQueue import com.android.systemui.statusbar.window.data.model.StatusBarWindowState import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlin.test.Test import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.mockito.Mockito.verify import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.reset @SmallTest @OptIn(ExperimentalCoroutinesApi::class) class StatusBarWindowStateRepositoryStoreTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val commandQueue = kosmos.commandQueue private val defaultDisplayId = kosmos.displayTracker.defaultDisplayId private val underTest = kosmos.statusBarWindowStateRepositoryStore @Test fun defaultDisplay_repoIsForDefaultDisplay() = testScope.runTest { val repo = underTest.defaultDisplay val latest by collectLastValue(repo.windowState) testScope.runCurrent() val callbackCaptor = argumentCaptor<CommandQueue.Callbacks>() verify(commandQueue).addCallback(callbackCaptor.capture()) val callback = callbackCaptor.firstValue // WHEN a default display callback is sent callback.setWindowState(defaultDisplayId, WINDOW_STATUS_BAR, WINDOW_STATE_SHOWING) // THEN its value is used assertThat(latest).isEqualTo(StatusBarWindowState.Showing) // WHEN a non-default display callback is sent callback.setWindowState(defaultDisplayId + 1, WINDOW_STATUS_BAR, WINDOW_STATE_HIDDEN) // THEN its value is NOT used assertThat(latest).isEqualTo(StatusBarWindowState.Showing) } @Test fun forDisplay_repoIsForSpecifiedDisplay() = testScope.runTest { // The repository store will always create a repository for the default display, which // will always add a callback to commandQueue. Ignore that callback here. testScope.runCurrent() reset(commandQueue) val displayId = defaultDisplayId + 15 val repo = underTest.forDisplay(displayId) val latest by collectLastValue(repo.windowState) testScope.runCurrent() val callbackCaptor = argumentCaptor<CommandQueue.Callbacks>() verify(commandQueue).addCallback(callbackCaptor.capture()) val callback = callbackCaptor.firstValue // WHEN a default display callback is sent callback.setWindowState(defaultDisplayId, WINDOW_STATUS_BAR, WINDOW_STATE_SHOWING) // THEN its value is NOT used assertThat(latest).isEqualTo(StatusBarWindowState.Hidden) // WHEN a callback for this display is sent callback.setWindowState(displayId, WINDOW_STATUS_BAR, WINDOW_STATE_SHOWING) // THEN its value is used assertThat(latest).isEqualTo(StatusBarWindowState.Showing) } @Test fun forDisplay_reusesRepoForSameDisplayId() = testScope.runTest { // The repository store will always create a repository for the default display, which // will always add a callback to commandQueue. Ignore that callback here. testScope.runCurrent() reset(commandQueue) val displayId = defaultDisplayId + 15 val firstRepo = underTest.forDisplay(displayId) testScope.runCurrent() val secondRepo = underTest.forDisplay(displayId) testScope.runCurrent() assertThat(firstRepo).isEqualTo(secondRepo) // Verify that we only added 1 CommandQueue.Callback because we only created 1 repo. val callbackCaptor = argumentCaptor<CommandQueue.Callbacks>() verify(commandQueue).addCallback(callbackCaptor.capture()) } } Loading
packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt +3 −2 Original line number Diff line number Diff line Loading @@ -32,13 +32,14 @@ import javax.inject.Inject * A centralized class maintaining the state of the status bar window. * * @deprecated use * [com.android.systemui.statusbar.window.data.repository.StatusBarWindowStateRepository] instead. * [com.android.systemui.statusbar.window.data.repository.StatusBarWindowStateRepositoryStore.defaultDisplay] * repo instead. * * Classes that want to get updates about the status bar window state should subscribe to this class * via [addListener] and should NOT add their own callback on [CommandQueue]. */ @SysUISingleton @Deprecated("Use StatusBarWindowRepository instead") @Deprecated("Use StatusBarWindowStateRepositoryStore.defaultDisplay instead") class StatusBarWindowStateController @Inject constructor(@DisplayId private val thisDisplayId: Int, commandQueue: CommandQueue) { Loading
packages/SystemUI/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepository.kt→packages/SystemUI/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStatePerDisplayRepository.kt +21 −9 Original line number Diff line number Diff line Loading @@ -22,13 +22,13 @@ import android.app.StatusBarManager.WINDOW_STATE_HIDING import android.app.StatusBarManager.WINDOW_STATE_SHOWING import android.app.StatusBarManager.WINDOW_STATUS_BAR import android.app.StatusBarManager.WindowVisibleState import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.DisplayId import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.window.data.model.StatusBarWindowState import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import javax.inject.Inject import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.SharingStarted Loading @@ -40,16 +40,23 @@ import kotlinx.coroutines.flow.stateIn * * Classes that want to get updates about the status bar window state should subscribe to * [windowState] and should NOT add their own callback on [CommandQueue]. * * Each concrete implementation of this class will be for a specific display ID. Use * [StatusBarWindowStateRepositoryStore] to fetch a concrete implementation for a certain display. */ @SysUISingleton class StatusBarWindowStateRepository @Inject interface StatusBarWindowStatePerDisplayRepository { /** Emits the current window state for the status bar on this specific display. */ val windowState: StateFlow<StatusBarWindowState> } class StatusBarWindowStatePerDisplayRepositoryImpl @AssistedInject constructor( @Assisted private val thisDisplayId: Int, private val commandQueue: CommandQueue, @DisplayId private val thisDisplayId: Int, @Application private val scope: CoroutineScope, ) { val windowState: StateFlow<StatusBarWindowState> = ) : StatusBarWindowStatePerDisplayRepository { override val windowState: StateFlow<StatusBarWindowState> = conflatedCallbackFlow { val callback = object : CommandQueue.Callbacks { Loading Loading @@ -84,3 +91,8 @@ constructor( } } } @AssistedFactory interface StatusBarWindowStatePerDisplayRepositoryFactory { fun create(@Assisted displayId: Int): StatusBarWindowStatePerDisplayRepositoryImpl }
packages/SystemUI/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepositoryStore.kt 0 → 100644 +60 −0 Original line number Diff line number Diff line /* * 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.statusbar.window.data.repository import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.DisplayId import java.lang.ref.WeakReference import javax.inject.Inject /** * Singleton class to create instances of [StatusBarWindowStatePerDisplayRepository] for a specific * display. * * Repository instances for a specific display should be cached so that if multiple classes request * a repository for the same display ID, we only create the repository once. */ interface StatusBarWindowStateRepositoryStore { val defaultDisplay: StatusBarWindowStatePerDisplayRepository fun forDisplay(displayId: Int): StatusBarWindowStatePerDisplayRepository } @SysUISingleton class StatusBarWindowStateRepositoryStoreImpl @Inject constructor( @DisplayId private val displayId: Int, private val factory: StatusBarWindowStatePerDisplayRepositoryFactory, ) : StatusBarWindowStateRepositoryStore { // Use WeakReferences to store the repositories so that the repositories are kept around so long // as some UI holds a reference to them, but the repositories are cleaned up once no UI is using // them anymore. // See Change-Id Ib490062208506d646add2fe7e5e5d4df5fb3e66e for similar behavior in // MobileConnectionsRepositoryImpl. private val repositoryCache = mutableMapOf<Int, WeakReference<StatusBarWindowStatePerDisplayRepository>>() override val defaultDisplay = factory.create(displayId) override fun forDisplay(displayId: Int): StatusBarWindowStatePerDisplayRepository { synchronized(repositoryCache) { return repositoryCache[displayId]?.get() ?: factory.create(displayId).also { repositoryCache[displayId] = WeakReference(it) } } } }
packages/SystemUI/tests/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepositoryTest.kt→packages/SystemUI/tests/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStatePerDisplayRepositoryTest.kt +6 −2 Original line number Diff line number Diff line Loading @@ -39,12 +39,16 @@ import org.mockito.kotlin.argumentCaptor @SmallTest @OptIn(ExperimentalCoroutinesApi::class) class StatusBarWindowStateRepositoryTest : SysuiTestCase() { class StatusBarWindowStatePerDisplayRepositoryTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val commandQueue = kosmos.commandQueue private val underTest = StatusBarWindowStateRepository(commandQueue, DISPLAY_ID, testScope.backgroundScope) StatusBarWindowStatePerDisplayRepositoryImpl( DISPLAY_ID, commandQueue, testScope.backgroundScope, ) private val callback: CommandQueue.Callbacks get() { Loading
packages/SystemUI/tests/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepositoryStoreTest.kt 0 → 100644 +123 −0 Original line number Diff line number Diff line /* * 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.statusbar.window.data.repository import android.app.StatusBarManager.WINDOW_STATE_HIDDEN import android.app.StatusBarManager.WINDOW_STATE_SHOWING import android.app.StatusBarManager.WINDOW_STATUS_BAR import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope import com.android.systemui.settings.displayTracker import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.commandQueue import com.android.systemui.statusbar.window.data.model.StatusBarWindowState import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlin.test.Test import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.mockito.Mockito.verify import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.reset @SmallTest @OptIn(ExperimentalCoroutinesApi::class) class StatusBarWindowStateRepositoryStoreTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val commandQueue = kosmos.commandQueue private val defaultDisplayId = kosmos.displayTracker.defaultDisplayId private val underTest = kosmos.statusBarWindowStateRepositoryStore @Test fun defaultDisplay_repoIsForDefaultDisplay() = testScope.runTest { val repo = underTest.defaultDisplay val latest by collectLastValue(repo.windowState) testScope.runCurrent() val callbackCaptor = argumentCaptor<CommandQueue.Callbacks>() verify(commandQueue).addCallback(callbackCaptor.capture()) val callback = callbackCaptor.firstValue // WHEN a default display callback is sent callback.setWindowState(defaultDisplayId, WINDOW_STATUS_BAR, WINDOW_STATE_SHOWING) // THEN its value is used assertThat(latest).isEqualTo(StatusBarWindowState.Showing) // WHEN a non-default display callback is sent callback.setWindowState(defaultDisplayId + 1, WINDOW_STATUS_BAR, WINDOW_STATE_HIDDEN) // THEN its value is NOT used assertThat(latest).isEqualTo(StatusBarWindowState.Showing) } @Test fun forDisplay_repoIsForSpecifiedDisplay() = testScope.runTest { // The repository store will always create a repository for the default display, which // will always add a callback to commandQueue. Ignore that callback here. testScope.runCurrent() reset(commandQueue) val displayId = defaultDisplayId + 15 val repo = underTest.forDisplay(displayId) val latest by collectLastValue(repo.windowState) testScope.runCurrent() val callbackCaptor = argumentCaptor<CommandQueue.Callbacks>() verify(commandQueue).addCallback(callbackCaptor.capture()) val callback = callbackCaptor.firstValue // WHEN a default display callback is sent callback.setWindowState(defaultDisplayId, WINDOW_STATUS_BAR, WINDOW_STATE_SHOWING) // THEN its value is NOT used assertThat(latest).isEqualTo(StatusBarWindowState.Hidden) // WHEN a callback for this display is sent callback.setWindowState(displayId, WINDOW_STATUS_BAR, WINDOW_STATE_SHOWING) // THEN its value is used assertThat(latest).isEqualTo(StatusBarWindowState.Showing) } @Test fun forDisplay_reusesRepoForSameDisplayId() = testScope.runTest { // The repository store will always create a repository for the default display, which // will always add a callback to commandQueue. Ignore that callback here. testScope.runCurrent() reset(commandQueue) val displayId = defaultDisplayId + 15 val firstRepo = underTest.forDisplay(displayId) testScope.runCurrent() val secondRepo = underTest.forDisplay(displayId) testScope.runCurrent() assertThat(firstRepo).isEqualTo(secondRepo) // Verify that we only added 1 CommandQueue.Callback because we only created 1 repo. val callbackCaptor = argumentCaptor<CommandQueue.Callbacks>() verify(commandQueue).addCallback(callbackCaptor.capture()) } }