Loading packages/SystemUI/multivalentTests/src/com/android/systemui/screencapture/common/data/repository/ScreenCaptureRecentTaskRepositoryImplTest.kt 0 → 100644 +133 −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.screencapture.common.data.repository import android.annotation.SuppressLint import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.kosmos.backgroundScope import com.android.systemui.kosmos.currentValue import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.mediaprojection.appselector.data.RecentTask import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider import com.android.systemui.shared.system.taskStackChangeListeners import com.android.systemui.testKosmosNew import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.launch import org.junit.Test import org.junit.runner.RunWith @SuppressLint("VisibleForTests") @SmallTest @RunWith(AndroidJUnit4::class) class ScreenCaptureRecentTaskRepositoryImplTest : SysuiTestCase() { private val kosmos = testKosmosNew() private val fakeRecentTask1 = RecentTask( taskId = 1, displayId = 1, userId = 1, topActivityComponent = null, baseIntentComponent = null, colorBackground = null, isForegroundTask = true, userType = RecentTask.UserType.STANDARD, splitBounds = null, ) private val fakeRecentTask2 = RecentTask( taskId = 2, displayId = 2, userId = 2, topActivityComponent = null, baseIntentComponent = null, colorBackground = null, isForegroundTask = false, userType = RecentTask.UserType.STANDARD, splitBounds = null, ) private val fakeRecentTaskListProvider = FakeRecentTaskListProvider() @Test fun recentTasks_emitsCurrentValue() = kosmos.runTest { // Arrange val repository = ScreenCaptureRecentTaskRepositoryImpl( scope = backgroundScope, bgContext = testDispatcher, recentTaskListProvider = fakeRecentTaskListProvider.apply { fakeRecentTasks = listOf(fakeRecentTask1) }, taskStackChangeListeners = taskStackChangeListeners, ) // Act val result = currentValue(repository.recentTasks) // Assert assertThat(result).containsExactly(fakeRecentTask1) } @Test fun recentTasks_whenTaskStackChanges_Updates() = kosmos.runTest { // Arrange val repository = ScreenCaptureRecentTaskRepositoryImpl( scope = backgroundScope, bgContext = testDispatcher, recentTaskListProvider = fakeRecentTaskListProvider.apply { fakeRecentTasks = listOf(fakeRecentTask1) }, taskStackChangeListeners = taskStackChangeListeners, ) var result: List<RecentTask>? = null val job = testScope.launch { repository.recentTasks.collect { result = it } } assertThat(result).containsExactly(fakeRecentTask1) // Act fakeRecentTaskListProvider.fakeRecentTasks = listOf(fakeRecentTask1, fakeRecentTask2) taskStackChangeListeners.listenerImpl.onTaskStackChanged() // Assert assertThat(result).containsExactly(fakeRecentTask1, fakeRecentTask2).inOrder() // Cleanup job.cancel() } private class FakeRecentTaskListProvider : RecentTaskListProvider { var fakeRecentTasks = emptyList<RecentTask>() var loadRecentTasksCallCount = 0 override suspend fun loadRecentTasks(): List<RecentTask> { loadRecentTasksCallCount++ return fakeRecentTasks } } } packages/SystemUI/src/com/android/systemui/screencapture/common/CommonModule.kt +11 −0 Original line number Diff line number Diff line Loading @@ -18,10 +18,14 @@ package com.android.systemui.screencapture.common import android.content.Context import com.android.launcher3.icons.IconFactory import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider import com.android.systemui.mediaprojection.appselector.data.ShellRecentTaskListProvider import com.android.systemui.screencapture.common.data.repository.ScreenCaptureIconRepository import com.android.systemui.screencapture.common.data.repository.ScreenCaptureIconRepositoryImpl import com.android.systemui.screencapture.common.data.repository.ScreenCaptureLabelRepository import com.android.systemui.screencapture.common.data.repository.ScreenCaptureLabelRepositoryImpl import com.android.systemui.screencapture.common.data.repository.ScreenCaptureRecentTaskRepository import com.android.systemui.screencapture.common.data.repository.ScreenCaptureRecentTaskRepositoryImpl import com.android.systemui.screencapture.common.data.repository.ScreenCaptureThumbnailRepository import com.android.systemui.screencapture.common.data.repository.ScreenCaptureThumbnailRepositoryImpl import dagger.Binds Loading Loading @@ -50,6 +54,13 @@ interface CommonModule { impl: ScreenCaptureThumbnailRepositoryImpl ): ScreenCaptureThumbnailRepository @Binds fun bindRecentTaskRepository( impl: ScreenCaptureRecentTaskRepositoryImpl ): ScreenCaptureRecentTaskRepository @Binds fun bindRecentTaskListProvider(impl: ShellRecentTaskListProvider): RecentTaskListProvider companion object { @Provides @ScreenCapture Loading packages/SystemUI/src/com/android/systemui/screencapture/common/data/repository/ScreenCaptureRecentTaskRepository.kt 0 → 100644 +72 −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.screencapture.common.data.repository import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.kairos.awaitClose import com.android.systemui.mediaprojection.appselector.data.RecentTask import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider import com.android.systemui.screencapture.common.ScreenCapture import com.android.systemui.screencapture.common.ScreenCaptureScope import com.android.systemui.shared.system.TaskStackChangeListener import com.android.systemui.shared.system.TaskStackChangeListeners import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.utils.coroutines.flow.mapLatestConflated import javax.inject.Inject import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn /** Repository of the current recent tasks. */ interface ScreenCaptureRecentTaskRepository { /** The current recent tasks. */ val recentTasks: StateFlow<List<RecentTask>?> } /** The default implementation of [ScreenCaptureRecentTaskRepository]. */ @ScreenCaptureScope class ScreenCaptureRecentTaskRepositoryImpl @Inject constructor( @ScreenCapture scope: CoroutineScope, @Background bgContext: CoroutineContext, recentTaskListProvider: RecentTaskListProvider, taskStackChangeListeners: TaskStackChangeListeners, ) : ScreenCaptureRecentTaskRepository { override val recentTasks: StateFlow<List<RecentTask>?> = conflatedCallbackFlow { val listener = object : TaskStackChangeListener { override fun onTaskStackChanged() { trySend(Unit) } } taskStackChangeListeners.registerTaskStackListener(listener) awaitClose { taskStackChangeListeners.unregisterTaskStackListener(listener) } } .onStart { emit(Unit) } .mapLatestConflated { recentTaskListProvider.loadRecentTasks() } .flowOn(bgContext) .stateIn(scope, SharingStarted.Eagerly, null) } packages/SystemUI/tests/utils/src/com/android/systemui/screencapture/common/data/repository/FakeScreenCaptureRecentTaskRepository.kt 0 → 100644 +35 −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.screencapture.common.data.repository import com.android.systemui.mediaprojection.appselector.data.RecentTask import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow class FakeScreenCaptureRecentTaskRepository : ScreenCaptureRecentTaskRepository { private val _recentTasks = MutableStateFlow<List<RecentTask>?>(null) override val recentTasks: StateFlow<List<RecentTask>?> = _recentTasks.asStateFlow() fun setRecentTasks(tasks: List<RecentTask>?) { _recentTasks.value = tasks } fun setRecentTasks(vararg tasks: RecentTask) { _recentTasks.value = tasks.toList() } } packages/SystemUI/tests/utils/src/com/android/systemui/screencapture/common/data/repository/ScreenCaptureRecentTaskRepositoryKosmos.kt 0 → 100644 +21 −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.screencapture.common.data.repository import com.android.systemui.kosmos.Kosmos val Kosmos.fakeRecentTaskRepository by Kosmos.Fixture { FakeScreenCaptureRecentTaskRepository() } Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/screencapture/common/data/repository/ScreenCaptureRecentTaskRepositoryImplTest.kt 0 → 100644 +133 −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.screencapture.common.data.repository import android.annotation.SuppressLint import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.kosmos.backgroundScope import com.android.systemui.kosmos.currentValue import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.mediaprojection.appselector.data.RecentTask import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider import com.android.systemui.shared.system.taskStackChangeListeners import com.android.systemui.testKosmosNew import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.launch import org.junit.Test import org.junit.runner.RunWith @SuppressLint("VisibleForTests") @SmallTest @RunWith(AndroidJUnit4::class) class ScreenCaptureRecentTaskRepositoryImplTest : SysuiTestCase() { private val kosmos = testKosmosNew() private val fakeRecentTask1 = RecentTask( taskId = 1, displayId = 1, userId = 1, topActivityComponent = null, baseIntentComponent = null, colorBackground = null, isForegroundTask = true, userType = RecentTask.UserType.STANDARD, splitBounds = null, ) private val fakeRecentTask2 = RecentTask( taskId = 2, displayId = 2, userId = 2, topActivityComponent = null, baseIntentComponent = null, colorBackground = null, isForegroundTask = false, userType = RecentTask.UserType.STANDARD, splitBounds = null, ) private val fakeRecentTaskListProvider = FakeRecentTaskListProvider() @Test fun recentTasks_emitsCurrentValue() = kosmos.runTest { // Arrange val repository = ScreenCaptureRecentTaskRepositoryImpl( scope = backgroundScope, bgContext = testDispatcher, recentTaskListProvider = fakeRecentTaskListProvider.apply { fakeRecentTasks = listOf(fakeRecentTask1) }, taskStackChangeListeners = taskStackChangeListeners, ) // Act val result = currentValue(repository.recentTasks) // Assert assertThat(result).containsExactly(fakeRecentTask1) } @Test fun recentTasks_whenTaskStackChanges_Updates() = kosmos.runTest { // Arrange val repository = ScreenCaptureRecentTaskRepositoryImpl( scope = backgroundScope, bgContext = testDispatcher, recentTaskListProvider = fakeRecentTaskListProvider.apply { fakeRecentTasks = listOf(fakeRecentTask1) }, taskStackChangeListeners = taskStackChangeListeners, ) var result: List<RecentTask>? = null val job = testScope.launch { repository.recentTasks.collect { result = it } } assertThat(result).containsExactly(fakeRecentTask1) // Act fakeRecentTaskListProvider.fakeRecentTasks = listOf(fakeRecentTask1, fakeRecentTask2) taskStackChangeListeners.listenerImpl.onTaskStackChanged() // Assert assertThat(result).containsExactly(fakeRecentTask1, fakeRecentTask2).inOrder() // Cleanup job.cancel() } private class FakeRecentTaskListProvider : RecentTaskListProvider { var fakeRecentTasks = emptyList<RecentTask>() var loadRecentTasksCallCount = 0 override suspend fun loadRecentTasks(): List<RecentTask> { loadRecentTasksCallCount++ return fakeRecentTasks } } }
packages/SystemUI/src/com/android/systemui/screencapture/common/CommonModule.kt +11 −0 Original line number Diff line number Diff line Loading @@ -18,10 +18,14 @@ package com.android.systemui.screencapture.common import android.content.Context import com.android.launcher3.icons.IconFactory import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider import com.android.systemui.mediaprojection.appselector.data.ShellRecentTaskListProvider import com.android.systemui.screencapture.common.data.repository.ScreenCaptureIconRepository import com.android.systemui.screencapture.common.data.repository.ScreenCaptureIconRepositoryImpl import com.android.systemui.screencapture.common.data.repository.ScreenCaptureLabelRepository import com.android.systemui.screencapture.common.data.repository.ScreenCaptureLabelRepositoryImpl import com.android.systemui.screencapture.common.data.repository.ScreenCaptureRecentTaskRepository import com.android.systemui.screencapture.common.data.repository.ScreenCaptureRecentTaskRepositoryImpl import com.android.systemui.screencapture.common.data.repository.ScreenCaptureThumbnailRepository import com.android.systemui.screencapture.common.data.repository.ScreenCaptureThumbnailRepositoryImpl import dagger.Binds Loading Loading @@ -50,6 +54,13 @@ interface CommonModule { impl: ScreenCaptureThumbnailRepositoryImpl ): ScreenCaptureThumbnailRepository @Binds fun bindRecentTaskRepository( impl: ScreenCaptureRecentTaskRepositoryImpl ): ScreenCaptureRecentTaskRepository @Binds fun bindRecentTaskListProvider(impl: ShellRecentTaskListProvider): RecentTaskListProvider companion object { @Provides @ScreenCapture Loading
packages/SystemUI/src/com/android/systemui/screencapture/common/data/repository/ScreenCaptureRecentTaskRepository.kt 0 → 100644 +72 −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.screencapture.common.data.repository import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.kairos.awaitClose import com.android.systemui.mediaprojection.appselector.data.RecentTask import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider import com.android.systemui.screencapture.common.ScreenCapture import com.android.systemui.screencapture.common.ScreenCaptureScope import com.android.systemui.shared.system.TaskStackChangeListener import com.android.systemui.shared.system.TaskStackChangeListeners import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.utils.coroutines.flow.mapLatestConflated import javax.inject.Inject import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn /** Repository of the current recent tasks. */ interface ScreenCaptureRecentTaskRepository { /** The current recent tasks. */ val recentTasks: StateFlow<List<RecentTask>?> } /** The default implementation of [ScreenCaptureRecentTaskRepository]. */ @ScreenCaptureScope class ScreenCaptureRecentTaskRepositoryImpl @Inject constructor( @ScreenCapture scope: CoroutineScope, @Background bgContext: CoroutineContext, recentTaskListProvider: RecentTaskListProvider, taskStackChangeListeners: TaskStackChangeListeners, ) : ScreenCaptureRecentTaskRepository { override val recentTasks: StateFlow<List<RecentTask>?> = conflatedCallbackFlow { val listener = object : TaskStackChangeListener { override fun onTaskStackChanged() { trySend(Unit) } } taskStackChangeListeners.registerTaskStackListener(listener) awaitClose { taskStackChangeListeners.unregisterTaskStackListener(listener) } } .onStart { emit(Unit) } .mapLatestConflated { recentTaskListProvider.loadRecentTasks() } .flowOn(bgContext) .stateIn(scope, SharingStarted.Eagerly, null) }
packages/SystemUI/tests/utils/src/com/android/systemui/screencapture/common/data/repository/FakeScreenCaptureRecentTaskRepository.kt 0 → 100644 +35 −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.screencapture.common.data.repository import com.android.systemui.mediaprojection.appselector.data.RecentTask import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow class FakeScreenCaptureRecentTaskRepository : ScreenCaptureRecentTaskRepository { private val _recentTasks = MutableStateFlow<List<RecentTask>?>(null) override val recentTasks: StateFlow<List<RecentTask>?> = _recentTasks.asStateFlow() fun setRecentTasks(tasks: List<RecentTask>?) { _recentTasks.value = tasks } fun setRecentTasks(vararg tasks: RecentTask) { _recentTasks.value = tasks.toList() } }
packages/SystemUI/tests/utils/src/com/android/systemui/screencapture/common/data/repository/ScreenCaptureRecentTaskRepositoryKosmos.kt 0 → 100644 +21 −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.screencapture.common.data.repository import com.android.systemui.kosmos.Kosmos val Kosmos.fakeRecentTaskRepository by Kosmos.Fixture { FakeScreenCaptureRecentTaskRepository() }