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

Commit 4c33684f authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Adds Screen Capture common view models" into main

parents 25e66006 19438234
Loading
Loading
Loading
Loading
+156 −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.ui.viewmodel

import android.content.ComponentName
import androidx.compose.ui.graphics.Color
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
import com.android.systemui.screencapture.common.data.repository.fakeScreenCaptureIconRepository
import com.android.systemui.screencapture.common.data.repository.fakeScreenCaptureLabelRepository
import com.android.systemui.screencapture.common.data.repository.fakeScreenCaptureThumbnailRepository
import com.android.systemui.screencapture.common.domain.interactor.screenCaptureIconInteractor
import com.android.systemui.screencapture.common.domain.interactor.screenCaptureLabelInteractor
import com.android.systemui.screencapture.common.domain.interactor.screenCaptureThumbnailInteractor
import com.android.systemui.screencapture.common.domain.model.ScreenCaptureRecentTask
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

@SmallTest
@RunWith(AndroidJUnit4::class)
class RecentTaskViewModelTest : SysuiTestCase() {

    private val kosmos = testKosmosNew()

    val fakeComponent = ComponentName("FakePackage", "FakeClass")

    @Test
    fun constructor_initializesFields() =
        kosmos.runTest {
            // Arrange
            val fakeTask =
                ScreenCaptureRecentTask(
                    taskId = 1,
                    displayId = 2,
                    userId = 3,
                    component = fakeComponent,
                    backgroundColor = 0x99123456.toInt(),
                    splitBounds = null,
                )

            // Act
            val viewModel =
                RecentTaskViewModel(
                    task = fakeTask,
                    iconInteractor = screenCaptureIconInteractor,
                    labelInteractor = screenCaptureLabelInteractor,
                    thumbnailInteractor = screenCaptureThumbnailInteractor,
                )

            // Assert
            with(viewModel) {
                assertThat(task).isEqualTo(fakeTask)
                assertThat(icon).isNull()
                assertThat(label).isNull()
                assertThat(thumbnail).isNull()
                assertThat(backgroundColorOpaque).isEqualTo(Color(0xFF123456))
            }
        }

    @Test
    fun constructor_noBackgroundColor_initializesBackgroundColorOpaqueToBlack() =
        kosmos.runTest {
            // Arrange
            val fakeTask =
                ScreenCaptureRecentTask(
                    taskId = 1,
                    displayId = 2,
                    userId = 3,
                    component = fakeComponent,
                    backgroundColor = null,
                    splitBounds = null,
                )

            // Act
            val viewModel =
                RecentTaskViewModel(
                    task = fakeTask,
                    iconInteractor = screenCaptureIconInteractor,
                    labelInteractor = screenCaptureLabelInteractor,
                    thumbnailInteractor = screenCaptureThumbnailInteractor,
                )

            // Assert
            with(viewModel) {
                assertThat(task).isEqualTo(fakeTask)
                assertThat(icon).isNull()
                assertThat(label).isNull()
                assertThat(thumbnail).isNull()
                assertThat(backgroundColorOpaque).isEqualTo(Color.Black)
            }
        }

    @Test
    fun onActivated_loadsResources() =
        kosmos.runTest {
            // Arrange
            val fakeTask =
                ScreenCaptureRecentTask(
                    taskId = 1,
                    displayId = 2,
                    userId = 3,
                    component = fakeComponent,
                    backgroundColor = 0x99123456.toInt(),
                    splitBounds = null,
                )
            val viewModel =
                RecentTaskViewModel(
                    task = fakeTask,
                    iconInteractor = screenCaptureIconInteractor,
                    labelInteractor = screenCaptureLabelInteractor,
                    thumbnailInteractor = screenCaptureThumbnailInteractor,
                )
            with(viewModel) {
                assertThat(task).isEqualTo(fakeTask)
                assertThat(icon).isNull()
                assertThat(label).isNull()
                assertThat(thumbnail).isNull()
                assertThat(backgroundColorOpaque).isEqualTo(Color(0xFF123456))
            }

            // Act
            val job = testScope.launch { viewModel.activate() }

            // Assert
            with(viewModel) {
                assertThat(task).isEqualTo(fakeTask)
                assertThat(icon).isEqualTo(fakeScreenCaptureIconRepository.fakeIcon)
                assertThat(label).isEqualTo(fakeScreenCaptureLabelRepository.fakeLabel)
                assertThat(thumbnail).isEqualTo(fakeScreenCaptureThumbnailRepository.fakeThumbnail)
                assertThat(backgroundColorOpaque).isEqualTo(Color(0xFF123456))
            }

            // Cleanup
            job.cancel()
        }
}
+47 −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.ui.viewmodel

import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.runTest
import com.android.systemui.screencapture.common.domain.interactor.screenCaptureRecentTaskInteractor
import com.android.systemui.testKosmosNew
import com.google.common.truth.Truth
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidJUnit4::class)
class RecentTasksViewModelImplTest : SysuiTestCase() {

    private val kosmos = testKosmosNew()

    @Test
    fun recentTasks_returnsRecentTasksFromRepository() =
        kosmos.runTest {
            // Arrange
            val viewModel = RecentTasksViewModelImpl(screenCaptureRecentTaskInteractor)

            // Act
            val result = viewModel.recentTasks

            // Assert
            Truth.assertThat(result).isSameInstanceAs(screenCaptureRecentTaskInteractor.recentTasks)
        }
}
+4 −0
Original line number Diff line number Diff line
@@ -28,6 +28,8 @@ import com.android.systemui.screencapture.common.data.repository.ScreenCaptureRe
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 com.android.systemui.screencapture.common.ui.viewmodel.RecentTasksViewModel
import com.android.systemui.screencapture.common.ui.viewmodel.RecentTasksViewModelImpl
import dagger.Binds
import dagger.Module
import dagger.Provides
@@ -61,6 +63,8 @@ interface CommonModule {

    @Binds fun bindRecentTaskListProvider(impl: ShellRecentTaskListProvider): RecentTaskListProvider

    @Binds fun bindRecentTasksViewModel(impl: RecentTasksViewModelImpl): RecentTasksViewModel

    companion object {
        @Provides
        @ScreenCapture
+70 −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.ui.viewmodel

import android.graphics.Bitmap
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.Color
import com.android.app.tracing.coroutines.launchTraced
import com.android.systemui.lifecycle.HydratedActivatable
import com.android.systemui.screencapture.common.domain.interactor.ScreenCaptureIconInteractor
import com.android.systemui.screencapture.common.domain.interactor.ScreenCaptureLabelInteractor
import com.android.systemui.screencapture.common.domain.interactor.ScreenCaptureThumbnailInteractor
import com.android.systemui.screencapture.common.domain.model.ScreenCaptureRecentTask
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.coroutineScope

/** Data for a UI to display a recent task. */
class RecentTaskViewModel
@AssistedInject
constructor(
    @Assisted val task: ScreenCaptureRecentTask,
    private val iconInteractor: ScreenCaptureIconInteractor,
    private val labelInteractor: ScreenCaptureLabelInteractor,
    private val thumbnailInteractor: ScreenCaptureThumbnailInteractor,
) : HydratedActivatable() {

    var icon by mutableStateOf<Result<Bitmap>?>(null)
        private set

    var label by mutableStateOf<Result<CharSequence>?>(null)
        private set

    var thumbnail by mutableStateOf<Result<Bitmap>?>(null)
        private set

    val backgroundColorOpaque: Color = task.backgroundColor.opaque()

    override suspend fun onActivated() {
        coroutineScope {
            launchTraced { icon = iconInteractor.loadIcon(task) }
            launchTraced { label = labelInteractor.loadLabel(task) }
            launchTraced { thumbnail = thumbnailInteractor.loadThumbnail(task) }
        }
    }

    private fun Int?.opaque(): Color = this?.let { Color(it).copy(alpha = 1f) } ?: Color.Black

    @AssistedFactory
    interface Factory {
        fun create(task: ScreenCaptureRecentTask): RecentTaskViewModel
    }
}
+60 −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.ui.viewmodel

import com.android.systemui.screencapture.common.domain.interactor.ScreenCaptureRecentTaskInteractor
import com.android.systemui.screencapture.common.domain.model.ScreenCaptureRecentTask
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow

/**
 * Interface for view models concerned with recent tasks.
 *
 * Example Usage:
 * ```
 * class FooViewModel(vm: RecentTasksViewModelImpl) : RecentTasksViewModel by vm
 * ```
 *
 * And then in compose:
 * ```
 * @Composable
 * fun Foo(viewModel: FooViewModel, modelFactory: RecentTaskViewModel.Factory) {
 *     val recentTasks by viewModel.recentTasks.collectAsState()
 *     LazyRow {
 *         recentTasks?.let {
 *             items(it) { task ->
 *                 val model by rememberViewModel("FooTraceName", task) {
 *                     modelFactory.create(task)
 *                 }
 *                 // ...
 *             }
 *         }
 *     }
 * }
 * ```
 */
interface RecentTasksViewModel {
    /** The current list of recent tasks. */
    val recentTasks: Flow<List<ScreenCaptureRecentTask>?>
}

/** The default implementation of [RecentTasksViewModel]. */
class RecentTasksViewModelImpl @Inject constructor(interactor: ScreenCaptureRecentTaskInteractor) :
    RecentTasksViewModel {

    override val recentTasks: Flow<List<ScreenCaptureRecentTask>?> = interactor.recentTasks
}
Loading