Make overlay run in background instead of unconfined (main)
This means that when TaskOverlayViewModel.overlayState is combining flows it does it on background. If this combination results in an emission then there is still a message to main but if it does not emit (e.g. no change filtered by distinctUntilChanged()) then it will not result in a continuation on main. Bug: 387511656 Flag: com.android.launcher3.enable_refactor_task_thumbnail Test: Checking perfetto traces shows that this reduces the number of continuations on main significantly Test: Manual - overlay presence + select functionality Test: Presubmit (TAPL/image tests etc) Change-Id: I840cb86e91db5a0626191b5eab145978ef4192e9
This commit is contained in:
@@ -191,6 +191,7 @@ class RecentsDependencies private constructor(private val appContext: Context) {
|
||||
recentsViewData = inject(),
|
||||
recentTasksRepository = inject(),
|
||||
getThumbnailPositionUseCase = inject(),
|
||||
dispatcherProvider = inject(),
|
||||
)
|
||||
}
|
||||
GetThumbnailUseCase::class.java -> GetThumbnailUseCase(taskRepository = inject())
|
||||
|
||||
@@ -74,6 +74,7 @@ class TaskOverlayHelper(val task: Task, val overlay: TaskOverlayFactory.TaskOver
|
||||
recentsViewData = RecentsDependencies.get(),
|
||||
getThumbnailPositionUseCase = RecentsDependencies.get(),
|
||||
recentTasksRepository = RecentsDependencies.get(),
|
||||
dispatcherProvider = RecentsDependencies.get(),
|
||||
)
|
||||
viewModel.overlayState
|
||||
.onEach {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.android.quickstep.task.viewmodel
|
||||
|
||||
import android.graphics.Matrix
|
||||
import com.android.launcher3.util.coroutines.DispatcherProvider
|
||||
import com.android.quickstep.recents.data.RecentTasksRepository
|
||||
import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase
|
||||
import com.android.quickstep.recents.usecase.ThumbnailPositionState.MatrixScaling
|
||||
@@ -27,6 +28,7 @@ import com.android.quickstep.task.thumbnail.TaskOverlayUiState.Enabled
|
||||
import com.android.systemui.shared.recents.model.Task
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
/** View model for TaskOverlay */
|
||||
@@ -35,11 +37,14 @@ class TaskOverlayViewModel(
|
||||
recentsViewData: RecentsViewData,
|
||||
private val getThumbnailPositionUseCase: GetThumbnailPositionUseCase,
|
||||
recentTasksRepository: RecentTasksRepository,
|
||||
dispatcherProvider: DispatcherProvider,
|
||||
) {
|
||||
val overlayState =
|
||||
combine(
|
||||
recentsViewData.overlayEnabled,
|
||||
recentsViewData.settledFullyVisibleTaskIds.map { it.contains(task.key.id) },
|
||||
recentsViewData.settledFullyVisibleTaskIds
|
||||
.map { it.contains(task.key.id) }
|
||||
.distinctUntilChanged(),
|
||||
recentTasksRepository.getThumbnailById(task.key.id),
|
||||
) { isOverlayEnabled, isFullyVisible, thumbnailData ->
|
||||
if (isOverlayEnabled && isFullyVisible) {
|
||||
@@ -52,6 +57,7 @@ class TaskOverlayViewModel(
|
||||
}
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.flowOn(dispatcherProvider.background)
|
||||
|
||||
fun getThumbnailPositionState(width: Int, height: Int, isRtl: Boolean): ThumbnailPositionState {
|
||||
val matrix: Matrix
|
||||
|
||||
+93
-69
@@ -22,6 +22,7 @@ import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import android.graphics.Matrix
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.android.launcher3.util.TestDispatcherProvider
|
||||
import com.android.quickstep.recents.data.FakeTasksRepository
|
||||
import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase
|
||||
import com.android.quickstep.recents.usecase.ThumbnailPositionState.MatrixScaling
|
||||
@@ -33,7 +34,10 @@ import com.android.quickstep.task.viewmodel.TaskOverlayViewModel.ThumbnailPositi
|
||||
import com.android.systemui.shared.recents.model.Task
|
||||
import com.android.systemui.shared.recents.model.ThumbnailData
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
@@ -41,6 +45,7 @@ import org.mockito.kotlin.mock
|
||||
import org.mockito.kotlin.whenever
|
||||
|
||||
/** Test for [TaskOverlayViewModel] */
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class TaskOverlayViewModelTest {
|
||||
private val task =
|
||||
@@ -58,104 +63,123 @@ class TaskOverlayViewModelTest {
|
||||
private val recentsViewData = RecentsViewData()
|
||||
private val tasksRepository = FakeTasksRepository()
|
||||
private val mGetThumbnailPositionUseCase = mock<GetThumbnailPositionUseCase>()
|
||||
private val dispatcher = UnconfinedTestDispatcher()
|
||||
private val testScope = TestScope(dispatcher)
|
||||
private val systemUnderTest =
|
||||
TaskOverlayViewModel(task, recentsViewData, mGetThumbnailPositionUseCase, tasksRepository)
|
||||
TaskOverlayViewModel(
|
||||
task,
|
||||
recentsViewData,
|
||||
mGetThumbnailPositionUseCase,
|
||||
tasksRepository,
|
||||
TestDispatcherProvider(dispatcher),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun initialStateIsDisabled() = runTest {
|
||||
assertThat(systemUnderTest.overlayState.first()).isEqualTo(Disabled)
|
||||
}
|
||||
fun initialStateIsDisabled() =
|
||||
testScope.runTest { assertThat(systemUnderTest.overlayState.first()).isEqualTo(Disabled) }
|
||||
|
||||
@Test
|
||||
fun recentsViewOverlayDisabled_Disabled() = runTest {
|
||||
recentsViewData.overlayEnabled.value = false
|
||||
recentsViewData.settledFullyVisibleTaskIds.value = setOf(TASK_ID)
|
||||
fun recentsViewOverlayDisabled_Disabled() =
|
||||
testScope.runTest {
|
||||
recentsViewData.overlayEnabled.value = false
|
||||
recentsViewData.settledFullyVisibleTaskIds.value = setOf(TASK_ID)
|
||||
|
||||
assertThat(systemUnderTest.overlayState.first()).isEqualTo(Disabled)
|
||||
}
|
||||
assertThat(systemUnderTest.overlayState.first()).isEqualTo(Disabled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun taskNotFullyVisible_Disabled() = runTest {
|
||||
recentsViewData.overlayEnabled.value = true
|
||||
recentsViewData.settledFullyVisibleTaskIds.value = setOf()
|
||||
fun taskNotFullyVisible_Disabled() =
|
||||
testScope.runTest {
|
||||
recentsViewData.overlayEnabled.value = true
|
||||
recentsViewData.settledFullyVisibleTaskIds.value = setOf()
|
||||
|
||||
assertThat(systemUnderTest.overlayState.first()).isEqualTo(Disabled)
|
||||
}
|
||||
assertThat(systemUnderTest.overlayState.first()).isEqualTo(Disabled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun noThumbnail_Enabled() = runTest {
|
||||
recentsViewData.overlayEnabled.value = true
|
||||
recentsViewData.settledFullyVisibleTaskIds.value = setOf(TASK_ID)
|
||||
task.isLocked = false
|
||||
fun noThumbnail_Enabled() =
|
||||
testScope.runTest {
|
||||
recentsViewData.overlayEnabled.value = true
|
||||
recentsViewData.settledFullyVisibleTaskIds.value = setOf(TASK_ID)
|
||||
task.isLocked = false
|
||||
|
||||
assertThat(systemUnderTest.overlayState.first())
|
||||
.isEqualTo(Enabled(isRealSnapshot = false, thumbnail = null))
|
||||
}
|
||||
assertThat(systemUnderTest.overlayState.first())
|
||||
.isEqualTo(Enabled(isRealSnapshot = false, thumbnail = null))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun withThumbnail_RealSnapshot_NotLocked_Enabled() = runTest {
|
||||
recentsViewData.overlayEnabled.value = true
|
||||
recentsViewData.settledFullyVisibleTaskIds.value = setOf(TASK_ID)
|
||||
tasksRepository.seedTasks(listOf(task))
|
||||
tasksRepository.seedThumbnailData(mapOf(TASK_ID to thumbnailData))
|
||||
tasksRepository.setVisibleTasks(setOf(TASK_ID))
|
||||
thumbnailData.isRealSnapshot = true
|
||||
task.isLocked = false
|
||||
fun withThumbnail_RealSnapshot_NotLocked_Enabled() =
|
||||
testScope.runTest {
|
||||
recentsViewData.overlayEnabled.value = true
|
||||
recentsViewData.settledFullyVisibleTaskIds.value = setOf(TASK_ID)
|
||||
tasksRepository.seedTasks(listOf(task))
|
||||
tasksRepository.seedThumbnailData(mapOf(TASK_ID to thumbnailData))
|
||||
tasksRepository.setVisibleTasks(setOf(TASK_ID))
|
||||
thumbnailData.isRealSnapshot = true
|
||||
task.isLocked = false
|
||||
|
||||
assertThat(systemUnderTest.overlayState.first())
|
||||
.isEqualTo(Enabled(isRealSnapshot = true, thumbnail = thumbnailData.thumbnail))
|
||||
}
|
||||
assertThat(systemUnderTest.overlayState.first())
|
||||
.isEqualTo(Enabled(isRealSnapshot = true, thumbnail = thumbnailData.thumbnail))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun withThumbnail_RealSnapshot_Locked_Enabled() = runTest {
|
||||
recentsViewData.overlayEnabled.value = true
|
||||
recentsViewData.settledFullyVisibleTaskIds.value = setOf(TASK_ID)
|
||||
tasksRepository.seedTasks(listOf(task))
|
||||
tasksRepository.seedThumbnailData(mapOf(TASK_ID to thumbnailData))
|
||||
tasksRepository.setVisibleTasks(setOf(TASK_ID))
|
||||
thumbnailData.isRealSnapshot = true
|
||||
task.isLocked = true
|
||||
fun withThumbnail_RealSnapshot_Locked_Enabled() =
|
||||
testScope.runTest {
|
||||
recentsViewData.overlayEnabled.value = true
|
||||
recentsViewData.settledFullyVisibleTaskIds.value = setOf(TASK_ID)
|
||||
tasksRepository.seedTasks(listOf(task))
|
||||
tasksRepository.seedThumbnailData(mapOf(TASK_ID to thumbnailData))
|
||||
tasksRepository.setVisibleTasks(setOf(TASK_ID))
|
||||
thumbnailData.isRealSnapshot = true
|
||||
task.isLocked = true
|
||||
|
||||
assertThat(systemUnderTest.overlayState.first())
|
||||
.isEqualTo(Enabled(isRealSnapshot = false, thumbnail = thumbnailData.thumbnail))
|
||||
}
|
||||
assertThat(systemUnderTest.overlayState.first())
|
||||
.isEqualTo(Enabled(isRealSnapshot = false, thumbnail = thumbnailData.thumbnail))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun withThumbnail_FakeSnapshot_Enabled() = runTest {
|
||||
recentsViewData.overlayEnabled.value = true
|
||||
recentsViewData.settledFullyVisibleTaskIds.value = setOf(TASK_ID)
|
||||
tasksRepository.seedTasks(listOf(task))
|
||||
tasksRepository.seedThumbnailData(mapOf(TASK_ID to thumbnailData))
|
||||
tasksRepository.setVisibleTasks(setOf(TASK_ID))
|
||||
thumbnailData.isRealSnapshot = false
|
||||
task.isLocked = false
|
||||
fun withThumbnail_FakeSnapshot_Enabled() =
|
||||
testScope.runTest {
|
||||
recentsViewData.overlayEnabled.value = true
|
||||
recentsViewData.settledFullyVisibleTaskIds.value = setOf(TASK_ID)
|
||||
tasksRepository.seedTasks(listOf(task))
|
||||
tasksRepository.seedThumbnailData(mapOf(TASK_ID to thumbnailData))
|
||||
tasksRepository.setVisibleTasks(setOf(TASK_ID))
|
||||
thumbnailData.isRealSnapshot = false
|
||||
task.isLocked = false
|
||||
|
||||
assertThat(systemUnderTest.overlayState.first())
|
||||
.isEqualTo(Enabled(isRealSnapshot = false, thumbnail = thumbnailData.thumbnail))
|
||||
}
|
||||
assertThat(systemUnderTest.overlayState.first())
|
||||
.isEqualTo(Enabled(isRealSnapshot = false, thumbnail = thumbnailData.thumbnail))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getThumbnailMatrix_MissingThumbnail() = runTest {
|
||||
val isRtl = true
|
||||
fun getThumbnailMatrix_MissingThumbnail() =
|
||||
testScope.runTest {
|
||||
val isRtl = true
|
||||
|
||||
whenever(mGetThumbnailPositionUseCase.run(TASK_ID, CANVAS_WIDTH, CANVAS_HEIGHT, isRtl))
|
||||
.thenReturn(MissingThumbnail)
|
||||
whenever(mGetThumbnailPositionUseCase.run(TASK_ID, CANVAS_WIDTH, CANVAS_HEIGHT, isRtl))
|
||||
.thenReturn(MissingThumbnail)
|
||||
|
||||
assertThat(systemUnderTest.getThumbnailPositionState(CANVAS_WIDTH, CANVAS_HEIGHT, isRtl))
|
||||
.isEqualTo(ThumbnailPositionState(Matrix.IDENTITY_MATRIX, isRotated = false))
|
||||
}
|
||||
assertThat(
|
||||
systemUnderTest.getThumbnailPositionState(CANVAS_WIDTH, CANVAS_HEIGHT, isRtl)
|
||||
)
|
||||
.isEqualTo(ThumbnailPositionState(Matrix.IDENTITY_MATRIX, isRotated = false))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getThumbnailMatrix_MatrixScaling() = runTest {
|
||||
val isRtl = true
|
||||
val isRotated = true
|
||||
fun getThumbnailMatrix_MatrixScaling() =
|
||||
testScope.runTest {
|
||||
val isRtl = true
|
||||
val isRotated = true
|
||||
|
||||
whenever(mGetThumbnailPositionUseCase.run(TASK_ID, CANVAS_WIDTH, CANVAS_HEIGHT, isRtl))
|
||||
.thenReturn(MatrixScaling(MATRIX, isRotated))
|
||||
whenever(mGetThumbnailPositionUseCase.run(TASK_ID, CANVAS_WIDTH, CANVAS_HEIGHT, isRtl))
|
||||
.thenReturn(MatrixScaling(MATRIX, isRotated))
|
||||
|
||||
assertThat(systemUnderTest.getThumbnailPositionState(CANVAS_WIDTH, CANVAS_HEIGHT, isRtl))
|
||||
.isEqualTo(ThumbnailPositionState(MATRIX, isRotated))
|
||||
}
|
||||
assertThat(
|
||||
systemUnderTest.getThumbnailPositionState(CANVAS_WIDTH, CANVAS_HEIGHT, isRtl)
|
||||
)
|
||||
.isEqualTo(ThumbnailPositionState(MATRIX, isRotated))
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TASK_ID = 0
|
||||
|
||||
Reference in New Issue
Block a user