Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0a05a16a authored by Govinda Wasserman's avatar Govinda Wasserman Committed by Android (Google) Code Review
Browse files

Merge "Adds recent task repository" into main

parents f7beed1e bb7de483
Loading
Loading
Loading
Loading
+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
        }
    }
}
+11 −0
Original line number Diff line number Diff line
@@ -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
@@ -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
+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)
}
+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()
    }
}
+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() }