Add splash state to new TTV
Bug: 334826842
Test: test classes added in CL
Flag: com.android.launcher3.enable_refactor_task_thumbnail
Change-Id: Ia7c2de18b5ff932430946df6b4c27929d715e79c
diff --git a/quickstep/res/layout/task_thumbnail.xml b/quickstep/res/layout/task_thumbnail.xml
index b1fe89e..784a094 100644
--- a/quickstep/res/layout/task_thumbnail.xml
+++ b/quickstep/res/layout/task_thumbnail.xml
@@ -39,4 +39,16 @@
android:background="@color/overview_foreground_scrim_color"
android:alpha="0" />
+ <FrameLayout
+ android:id="@+id/splash_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone">
+ <ImageView
+ android:id="@+id/splash_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:importantForAccessibility="no" />
+ </FrameLayout>
</com.android.quickstep.task.thumbnail.TaskThumbnailView>
\ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/recents/data/RecentsDeviceProfile.kt b/quickstep/src/com/android/quickstep/recents/data/RecentsDeviceProfile.kt
new file mode 100644
index 0000000..feed2fd
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/data/RecentsDeviceProfile.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.quickstep.recents.data
+
+/**
+ * Container to hold [com.android.launcher3.DeviceProfile] related to Recents.
+ *
+ * @property isLargeScreen whether the current device posture has a large screen
+ */
+data class RecentsDeviceProfile(
+ val isLargeScreen: Boolean,
+ val widthPx: Int,
+ val heightPx: Int,
+)
diff --git a/quickstep/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepository.kt b/quickstep/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepository.kt
index adf904c..13cf56d 100644
--- a/quickstep/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepository.kt
+++ b/quickstep/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepository.kt
@@ -16,21 +16,6 @@
package com.android.quickstep.recents.data
-import com.android.quickstep.views.RecentsViewContainer
-
-/**
- * Repository for shrink down version of [com.android.launcher3.DeviceProfile] that only contains
- * data related to Recents.
- */
-class RecentsDeviceProfileRepository(private val container: RecentsViewContainer) {
-
- fun getRecentsDeviceProfile() =
- with(container.deviceProfile) { RecentsDeviceProfile(isLargeScreen = isTablet) }
-
- /**
- * Container to hold [com.android.launcher3.DeviceProfile] related to Recents.
- *
- * @property isLargeScreen whether the current device posture has a large screen
- */
- data class RecentsDeviceProfile(val isLargeScreen: Boolean)
+interface RecentsDeviceProfileRepository {
+ fun getRecentsDeviceProfile(): RecentsDeviceProfile
}
diff --git a/quickstep/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepositoryImpl.kt b/quickstep/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepositoryImpl.kt
new file mode 100644
index 0000000..ce39ff1
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepositoryImpl.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.quickstep.recents.data
+
+import com.android.quickstep.views.RecentsViewContainer
+
+/**
+ * Repository for shrink down version of [com.android.launcher3.DeviceProfile] that only contains
+ * data related to Recents.
+ */
+class RecentsDeviceProfileRepositoryImpl(private val container: RecentsViewContainer) :
+ RecentsDeviceProfileRepository {
+
+ override fun getRecentsDeviceProfile() =
+ with(container.deviceProfile) {
+ RecentsDeviceProfile(isLargeScreen = isTablet, widthPx = widthPx, heightPx = heightPx)
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/recents/data/RecentsRotationState.kt b/quickstep/src/com/android/quickstep/recents/data/RecentsRotationState.kt
new file mode 100644
index 0000000..2c2a744
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/data/RecentsRotationState.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.quickstep.recents.data
+
+import android.view.Surface
+
+/**
+ * Container to hold orientation/rotation related information related to Recents.
+ *
+ * @property activityRotation rotation of the activity hosting RecentsView
+ */
+data class RecentsRotationState(
+ @Surface.Rotation val activityRotation: Int,
+ @Surface.Rotation val orientationHandlerRotation: Int,
+)
diff --git a/quickstep/src/com/android/quickstep/recents/data/RecentsRotationStateRepository.kt b/quickstep/src/com/android/quickstep/recents/data/RecentsRotationStateRepository.kt
index 6ead704..ed074d2 100644
--- a/quickstep/src/com/android/quickstep/recents/data/RecentsRotationStateRepository.kt
+++ b/quickstep/src/com/android/quickstep/recents/data/RecentsRotationStateRepository.kt
@@ -16,20 +16,6 @@
package com.android.quickstep.recents.data
-import com.android.quickstep.util.RecentsOrientedState
-
-/**
- * Repository for [RecentsRotationState] which holds orientation/rotation related information
- * related to Recents
- */
-class RecentsRotationStateRepository(private val state: RecentsOrientedState) {
- fun getRecentsRotationState() =
- with(state) { RecentsRotationState(activityRotation = recentsActivityRotation) }
-
- /**
- * Container to hold orientation/rotation related information related to Recents.
- *
- * @property activityRotation rotation of the activity hosting RecentsView
- */
- data class RecentsRotationState(val activityRotation: Int)
+interface RecentsRotationStateRepository {
+ fun getRecentsRotationState(): RecentsRotationState
}
diff --git a/quickstep/src/com/android/quickstep/recents/data/RecentsRotationStateRepositoryImpl.kt b/quickstep/src/com/android/quickstep/recents/data/RecentsRotationStateRepositoryImpl.kt
new file mode 100644
index 0000000..8417b06
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/data/RecentsRotationStateRepositoryImpl.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.quickstep.recents.data
+
+import com.android.quickstep.util.RecentsOrientedState
+
+/**
+ * Repository for [RecentsRotationState] which holds orientation/rotation related information
+ * related to Recents
+ */
+class RecentsRotationStateRepositoryImpl(private val state: RecentsOrientedState) :
+ RecentsRotationStateRepository {
+ override fun getRecentsRotationState() =
+ with(state) {
+ RecentsRotationState(
+ activityRotation = recentsActivityRotation,
+ orientationHandlerRotation = orientationHandler.rotation
+ )
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
index f73db5a..71be75b 100644
--- a/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
+++ b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
@@ -130,9 +130,15 @@
icon,
contentDescription,
title ->
- continuation.resume(
- TaskIconQueryResponse(icon, contentDescription, title)
- )
+ icon.constantState?.let {
+ continuation.resume(
+ TaskIconQueryResponse(
+ it.newDrawable().mutate(),
+ contentDescription,
+ title
+ )
+ )
+ }
}
continuation.invokeOnCancellation { cancellableTask?.cancel() }
}
@@ -157,7 +163,7 @@
}
}
-private data class TaskIconQueryResponse(
+data class TaskIconQueryResponse(
val icon: Drawable,
val contentDescription: String,
val title: String
diff --git a/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt b/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
index 3a6d5c0..eba7688 100644
--- a/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
+++ b/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
@@ -26,6 +26,9 @@
import com.android.quickstep.recents.usecase.GetThumbnailUseCase
import com.android.quickstep.recents.usecase.SysUiStatusNavFlagsUseCase
import com.android.quickstep.recents.viewmodel.RecentsViewData
+import com.android.quickstep.task.thumbnail.GetSplashSizeUseCase
+import com.android.quickstep.task.thumbnail.SplashAlphaUseCase
+import com.android.quickstep.task.thumbnail.TaskThumbnailViewData
import com.android.quickstep.task.viewmodel.TaskContainerData
import com.android.quickstep.task.viewmodel.TaskOverlayViewModel
import com.android.quickstep.task.viewmodel.TaskThumbnailViewModel
@@ -33,7 +36,6 @@
import com.android.quickstep.task.viewmodel.TaskViewModel
import com.android.quickstep.views.TaskViewType
import com.android.systemui.shared.recents.model.Task
-import java.util.logging.Level
internal typealias RecentsScopeId = String
@@ -145,13 +147,16 @@
TaskViewData(taskViewType)
}
TaskContainerData::class.java -> TaskContainerData()
+ TaskThumbnailViewData::class.java -> TaskThumbnailViewData()
TaskThumbnailViewModel::class.java ->
TaskThumbnailViewModel(
recentsViewData = inject(),
taskViewData = inject(scopeId, extras),
- taskContainerData = inject(),
+ taskContainerData = inject(scopeId),
getThumbnailPositionUseCase = inject(),
- tasksRepository = inject()
+ tasksRepository = inject(),
+ splashAlphaUseCase = inject(scopeId),
+ getSplashSizeUseCase = inject(scopeId),
)
TaskOverlayViewModel::class.java -> {
val task = extras["Task"] as Task
@@ -171,6 +176,20 @@
rotationStateRepository = inject(),
tasksRepository = inject()
)
+ SplashAlphaUseCase::class.java ->
+ SplashAlphaUseCase(
+ recentsViewData = inject(),
+ taskContainerData = inject(scopeId),
+ taskThumbnailViewData = inject(scopeId),
+ tasksRepository = inject(),
+ rotationStateRepository = inject(),
+ )
+ GetSplashSizeUseCase::class.java ->
+ GetSplashSizeUseCase(
+ taskThumbnailViewData = inject(scopeId),
+ taskViewData = inject(scopeId, extras),
+ deviceProfileRepository = inject(),
+ )
else -> {
log("Factory for ${modelClass.simpleName} not defined!", Log.ERROR)
error("Factory for ${modelClass.simpleName} not defined!")
diff --git a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewData.kt b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewData.kt
index 0d38fb9..f5e0243 100644
--- a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewData.kt
+++ b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewData.kt
@@ -34,4 +34,6 @@
// Color tint on foreground scrim
val tintAmount = MutableStateFlow(0f)
+
+ val thumbnailSplashProgress = MutableStateFlow(0f)
}
diff --git a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt
index edeca2d..6148d4b 100644
--- a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt
+++ b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt
@@ -49,4 +49,8 @@
fun setTintAmount(tintAmount: Float) {
recentsViewData.tintAmount.value = tintAmount
}
+
+ fun updateThumbnailSplashProgress(taskThumbnailSplashAlpha: Float) {
+ recentsViewData.thumbnailSplashProgress.value = taskThumbnailSplashAlpha
+ }
}
diff --git a/quickstep/src/com/android/quickstep/recents/viewmodel/TaskContainerViewModel.kt b/quickstep/src/com/android/quickstep/recents/viewmodel/TaskContainerViewModel.kt
index 8b8bc3e..168c1e0 100644
--- a/quickstep/src/com/android/quickstep/recents/viewmodel/TaskContainerViewModel.kt
+++ b/quickstep/src/com/android/quickstep/recents/viewmodel/TaskContainerViewModel.kt
@@ -19,13 +19,20 @@
import android.graphics.Bitmap
import com.android.quickstep.recents.usecase.GetThumbnailUseCase
import com.android.quickstep.recents.usecase.SysUiStatusNavFlagsUseCase
+import com.android.quickstep.task.thumbnail.SplashAlphaUseCase
+import kotlinx.coroutines.flow.firstOrNull
+import kotlinx.coroutines.runBlocking
class TaskContainerViewModel(
private val sysUiStatusNavFlagsUseCase: SysUiStatusNavFlagsUseCase,
- private val getThumbnailUseCase: GetThumbnailUseCase
+ private val getThumbnailUseCase: GetThumbnailUseCase,
+ private val splashAlphaUseCase: SplashAlphaUseCase,
) {
fun getThumbnail(taskId: Int): Bitmap? = getThumbnailUseCase.run(taskId)
fun getSysUiStatusNavFlags(taskId: Int) =
sysUiStatusNavFlagsUseCase.getSysUiStatusNavFlags(taskId)
+
+ fun shouldShowThumbnailSplash(taskId: Int): Boolean =
+ (runBlocking { splashAlphaUseCase.execute(taskId).firstOrNull() } ?: 0f) > 0f
}
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/GetSplashSizeUseCase.kt b/quickstep/src/com/android/quickstep/task/thumbnail/GetSplashSizeUseCase.kt
new file mode 100644
index 0000000..145957a
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/GetSplashSizeUseCase.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.quickstep.task.thumbnail
+
+import android.graphics.Point
+import android.graphics.drawable.Drawable
+import com.android.quickstep.recents.data.RecentsDeviceProfileRepository
+import com.android.quickstep.task.viewmodel.TaskViewData
+import kotlin.math.min
+
+class GetSplashSizeUseCase(
+ private val taskThumbnailViewData: TaskThumbnailViewData,
+ private val taskViewData: TaskViewData,
+ private val deviceProfileRepository: RecentsDeviceProfileRepository,
+) {
+ fun execute(splashImage: Drawable): Point {
+ val recentsDeviceProfile = deviceProfileRepository.getRecentsDeviceProfile()
+ val screenWidth = recentsDeviceProfile.widthPx
+ val screenHeight = recentsDeviceProfile.heightPx
+ val scaleAtFullscreen =
+ min(
+ screenWidth / taskThumbnailViewData.width.value,
+ screenHeight / taskThumbnailViewData.height.value,
+ )
+ val scaleFactor: Float = 1f / taskViewData.nonGridScale.value / scaleAtFullscreen
+ return Point(
+ (splashImage.intrinsicWidth * scaleFactor / taskThumbnailViewData.scaleX.value).toInt(),
+ (splashImage.intrinsicHeight * scaleFactor / taskThumbnailViewData.scaleY.value)
+ .toInt(),
+ )
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/SplashAlphaUseCase.kt b/quickstep/src/com/android/quickstep/task/thumbnail/SplashAlphaUseCase.kt
new file mode 100644
index 0000000..e5618fc
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/SplashAlphaUseCase.kt
@@ -0,0 +1,90 @@
+/*
+ * 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.quickstep.task.thumbnail
+
+import android.graphics.Bitmap
+import android.view.Surface
+import com.android.quickstep.recents.data.RecentTasksRepository
+import com.android.quickstep.recents.data.RecentsRotationStateRepository
+import com.android.quickstep.recents.viewmodel.RecentsViewData
+import com.android.quickstep.task.viewmodel.TaskContainerData
+import com.android.systemui.shared.recents.utilities.PreviewPositionHelper
+import com.android.systemui.shared.recents.utilities.Utilities
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+
+class SplashAlphaUseCase(
+ private val recentsViewData: RecentsViewData,
+ private val taskContainerData: TaskContainerData,
+ private val taskThumbnailViewData: TaskThumbnailViewData,
+ private val tasksRepository: RecentTasksRepository,
+ private val rotationStateRepository: RecentsRotationStateRepository,
+) {
+ fun execute(taskId: Int): Flow<Float> =
+ combine(
+ taskThumbnailViewData.width,
+ taskThumbnailViewData.height,
+ tasksRepository.getThumbnailById(taskId),
+ taskContainerData.thumbnailSplashProgress,
+ recentsViewData.thumbnailSplashProgress
+ ) { width, height, thumbnailData, taskSplashProgress, globalSplashProgress ->
+ val thumbnail = thumbnailData?.thumbnail
+ when {
+ thumbnail == null -> 1f
+ taskSplashProgress > 0f -> taskSplashProgress
+ globalSplashProgress > 0f &&
+ isInaccurateThumbnail(thumbnail, thumbnailData.rotation, width, height) ->
+ globalSplashProgress
+ else -> 0f
+ }
+ }
+ .distinctUntilChanged()
+
+ private fun isInaccurateThumbnail(
+ thumbnail: Bitmap,
+ thumbnailRotation: Int,
+ width: Int,
+ height: Int
+ ): Boolean {
+ return isThumbnailAspectRatioDifferentFromThumbnailData(thumbnail, width, height) ||
+ isThumbnailRotationDifferentFromTask(thumbnailRotation)
+ }
+
+ private fun isThumbnailAspectRatioDifferentFromThumbnailData(
+ thumbnail: Bitmap,
+ viewWidth: Int,
+ viewHeight: Int
+ ): Boolean {
+ val viewAspect: Float = viewWidth / viewHeight.toFloat()
+ val thumbnailAspect: Float = thumbnail.width / thumbnail.height.toFloat()
+ return Utilities.isRelativePercentDifferenceGreaterThan(
+ viewAspect,
+ thumbnailAspect,
+ PreviewPositionHelper.MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT
+ )
+ }
+
+ private fun isThumbnailRotationDifferentFromTask(thumbnailRotation: Int): Boolean {
+ val rotationState = rotationStateRepository.getRecentsRotationState()
+ return if (rotationState.orientationHandlerRotation == Surface.ROTATION_0) {
+ (rotationState.activityRotation - thumbnailRotation) % 2 != 0
+ } else {
+ rotationState.orientationHandlerRotation != thumbnailRotation
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt
index 3b3a811..aa7d26c 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt
@@ -17,6 +17,9 @@
package com.android.quickstep.task.thumbnail
import android.graphics.Bitmap
+import android.graphics.Point
+import android.graphics.drawable.Drawable
+import android.view.Surface
import androidx.annotation.ColorInt
sealed class TaskThumbnailUiState {
@@ -26,8 +29,21 @@
data class BackgroundOnly(@ColorInt val backgroundColor: Int) : TaskThumbnailUiState()
- data class Snapshot(val bitmap: Bitmap, @ColorInt val backgroundColor: Int) :
- TaskThumbnailUiState()
+ data class SnapshotSplash(
+ val snapshot: Snapshot,
+ val splash: Splash,
+ ) : TaskThumbnailUiState()
+
+ data class Snapshot(
+ val bitmap: Bitmap,
+ @Surface.Rotation val thumbnailRotation: Int,
+ @ColorInt val backgroundColor: Int
+ )
+
+ data class Splash(
+ val icon: Drawable?,
+ val size: Point,
+ )
}
data class TaskThumbnail(val taskId: Int, val isRunning: Boolean)
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
index 8659734..41aee52 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
@@ -28,6 +28,7 @@
import android.widget.ImageView
import androidx.annotation.ColorInt
import androidx.core.view.isVisible
+import androidx.core.view.updateLayoutParams
import com.android.launcher3.R
import com.android.launcher3.Utilities
import com.android.launcher3.util.ViewPool
@@ -36,10 +37,12 @@
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.SnapshotSplash
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
import com.android.quickstep.task.viewmodel.TaskThumbnailViewModel
import com.android.quickstep.util.TaskCornerRadius
import com.android.systemui.shared.system.QuickStepContract
+import kotlin.math.abs
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -50,6 +53,7 @@
class TaskThumbnailView : FrameLayout, ViewPool.Reusable {
+ private val viewData: TaskThumbnailViewData by RecentsDependencies.inject(this)
private val viewModel: TaskThumbnailViewModel by RecentsDependencies.inject(this)
private lateinit var viewAttachedScope: CoroutineScope
@@ -57,6 +61,8 @@
private val scrimView: View by lazy { findViewById(R.id.task_thumbnail_scrim) }
private val liveTileView: LiveTileView by lazy { findViewById(R.id.task_thumbnail_live_tile) }
private val thumbnailView: ImageView by lazy { findViewById(R.id.task_thumbnail) }
+ private val splashContainer: FrameLayout by lazy { findViewById(R.id.splash_container) }
+ private val splashIcon: ImageView by lazy { findViewById(R.id.splash_icon) }
private var uiState: TaskThumbnailUiState = Uninitialized
private var inheritedScale: Float = 1f
@@ -92,7 +98,7 @@
when (viewModelUiState) {
is Uninitialized -> {}
is LiveTile -> drawLiveWindow()
- is Snapshot -> drawSnapshot(viewModelUiState)
+ is SnapshotSplash -> drawSnapshotSplash(viewModelUiState)
is BackgroundOnly -> drawBackground(viewModelUiState.backgroundColor)
}
}
@@ -100,6 +106,9 @@
viewModel.dimProgress
.onEach { dimProgress -> scrimView.alpha = dimProgress }
.launchIn(viewAttachedScope)
+ viewModel.splashAlpha
+ .onEach { splashAlpha -> splashContainer.alpha = splashAlpha }
+ .launchIn(viewAttachedScope)
viewModel.cornerRadiusProgress.onEach { invalidateOutline() }.launchIn(viewAttachedScope)
viewModel.inheritedScale
.onEach { viewModelInheritedScale ->
@@ -126,13 +135,35 @@
uiState = Uninitialized
}
+ override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
+ super.onLayout(changed, left, top, right, bottom)
+ if (changed) {
+ viewData.width.value = abs(right - left)
+ viewData.height.value = abs(bottom - top)
+ }
+ }
+
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
- if (uiState is Snapshot) {
+ if (uiState is SnapshotSplash) {
setImageMatrix()
}
}
+ override fun setScaleX(scaleX: Float) {
+ super.setScaleX(scaleX)
+ viewData.scaleX.value = scaleX
+ // Splash icon should ignore scale
+ splashIcon.scaleX = 1 / scaleX
+ }
+
+ override fun setScaleY(scaleY: Float) {
+ super.setScaleY(scaleY)
+ viewData.scaleY.value = scaleY
+ // Splash icon should ignore scale
+ splashIcon.scaleY = 1 / scaleY
+ }
+
override fun onConfigurationChanged(newConfig: Configuration?) {
super.onConfigurationChanged(newConfig)
@@ -144,6 +175,7 @@
private fun resetViews() {
liveTileView.isVisible = false
thumbnailView.isVisible = false
+ splashContainer.alpha = 0f
scrimView.alpha = 0f
setBackgroundColor(Color.BLACK)
}
@@ -156,6 +188,18 @@
liveTileView.isVisible = true
}
+ private fun drawSnapshotSplash(snapshotSplash: SnapshotSplash) {
+ drawSnapshot(snapshotSplash.snapshot)
+
+ splashContainer.isVisible = true
+ splashContainer.setBackgroundColor(snapshotSplash.snapshot.backgroundColor)
+ splashIcon.setImageDrawable(snapshotSplash.splash.icon)
+ splashIcon.updateLayoutParams<LayoutParams> {
+ width = snapshotSplash.splash.size.x
+ height = snapshotSplash.splash.size.y
+ }
+ }
+
private fun drawSnapshot(snapshot: Snapshot) {
drawBackground(snapshot.backgroundColor)
thumbnailView.setImageBitmap(snapshot.bitmap)
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewData.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewData.kt
new file mode 100644
index 0000000..1f8c0bc
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewData.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.quickstep.task.thumbnail
+
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class TaskThumbnailViewData {
+ val width = MutableStateFlow(0)
+ val height = MutableStateFlow(0)
+ val scaleX = MutableStateFlow(1f)
+ val scaleY = MutableStateFlow(1f)
+}
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskContainerData.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskContainerData.kt
index 769424c..5f2de94 100644
--- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskContainerData.kt
+++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskContainerData.kt
@@ -20,4 +20,6 @@
class TaskContainerData {
val taskMenuOpenProgress = MutableStateFlow(0f)
+
+ val thumbnailSplashProgress = MutableStateFlow(0f)
}
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt
index 0753bb9..de33919 100644
--- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt
+++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt
@@ -18,16 +18,21 @@
import android.annotation.ColorInt
import android.graphics.Matrix
+import android.graphics.Point
import androidx.core.graphics.ColorUtils
import com.android.quickstep.recents.data.RecentTasksRepository
import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase
import com.android.quickstep.recents.usecase.ThumbnailPositionState
import com.android.quickstep.recents.viewmodel.RecentsViewData
+import com.android.quickstep.task.thumbnail.GetSplashSizeUseCase
+import com.android.quickstep.task.thumbnail.SplashAlphaUseCase
import com.android.quickstep.task.thumbnail.TaskThumbnail
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.SnapshotSplash
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Splash
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
import com.android.systemui.shared.recents.model.Task
import kotlin.math.max
@@ -48,9 +53,12 @@
taskViewData: TaskViewData,
taskContainerData: TaskContainerData,
private val tasksRepository: RecentTasksRepository,
- private val getThumbnailPositionUseCase: GetThumbnailPositionUseCase
+ private val getThumbnailPositionUseCase: GetThumbnailPositionUseCase,
+ private val splashAlphaUseCase: SplashAlphaUseCase,
+ private val getSplashSizeUseCase: GetSplashSizeUseCase,
) {
private val task = MutableStateFlow<Flow<Task?>>(flowOf(null))
+ private val splashProgress = MutableStateFlow(flowOf(0f))
private lateinit var taskThumbnail: TaskThumbnail
/**
@@ -70,6 +78,7 @@
tintAmount ->
max(taskMenuOpenProgress * MAX_SCRIM_ALPHA, tintAmount)
}
+ val splashAlpha = splashProgress.flatMapLatest { it }
val uiState: Flow<TaskThumbnailUiState> =
task
.flatMapLatest { taskFlow ->
@@ -79,10 +88,8 @@
taskThumbnail.isRunning -> LiveTile
isBackgroundOnly(taskVal) ->
BackgroundOnly(taskVal.colorBackground.removeAlpha())
- isSnapshotState(taskVal) -> {
- val bitmap = taskVal.thumbnail?.thumbnail!!
- Snapshot(bitmap, taskVal.colorBackground.removeAlpha())
- }
+ isSnapshotSplashState(taskVal) ->
+ SnapshotSplash(createSnapshotState(taskVal), createSplashState(taskVal))
else -> Uninitialized
}
}
@@ -92,6 +99,7 @@
fun bind(taskThumbnail: TaskThumbnail) {
this.taskThumbnail = taskThumbnail
task.value = tasksRepository.getTaskDataById(taskThumbnail.taskId)
+ splashProgress.value = splashAlphaUseCase.execute(taskThumbnail.taskId)
}
fun getThumbnailPositionState(width: Int, height: Int, isRtl: Boolean): Matrix {
@@ -108,13 +116,25 @@
private fun isBackgroundOnly(task: Task): Boolean = task.isLocked || task.thumbnail == null
- private fun isSnapshotState(task: Task): Boolean {
+ private fun isSnapshotSplashState(task: Task): Boolean {
val thumbnailPresent = task.thumbnail?.thumbnail != null
val taskLocked = task.isLocked
return thumbnailPresent && !taskLocked
}
+ private fun createSnapshotState(task: Task): Snapshot {
+ val thumbnailData = task.thumbnail
+ val bitmap = thumbnailData?.thumbnail!!
+ return Snapshot(bitmap, thumbnailData.rotation, task.colorBackground.removeAlpha())
+ }
+
+ private fun createSplashState(task: Task): Splash {
+ val taskIcon = task.icon
+ val size = if (taskIcon == null) Point() else getSplashSizeUseCase.execute(taskIcon)
+ return Splash(taskIcon, size)
+ }
+
@ColorInt private fun Int.removeAlpha(): Int = ColorUtils.setAlphaComponent(this, 0xff)
private companion object {
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewData.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewData.kt
index 7a9ecf2..07dfc29 100644
--- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewData.kt
+++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewData.kt
@@ -23,6 +23,8 @@
// This is typically a View concern but it is used to invalidate rendering in other Views
val scale = MutableStateFlow(1f)
+ val nonGridScale = MutableStateFlow(1f)
+
// TODO(b/331753115): This property should not be in TaskViewData once TaskView is MVVM.
/** Whether outline of TaskView is formed by outline thumbnail view(s). */
val isOutlineFormedByThumbnailView: Boolean = taskViewType != TaskViewType.DESKTOP
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewModel.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewModel.kt
index ec75d59..30ee360 100644
--- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewModel.kt
+++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewModel.kt
@@ -22,4 +22,8 @@
fun updateScale(scale: Float) {
taskViewData.scale.value = scale
}
+
+ fun updateNonGridScale(nonGridScale: Float) {
+ taskViewData.nonGridScale.value = nonGridScale
+ }
}
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index e31a828..0335fa1 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -187,7 +187,6 @@
) {
val snapshot = taskContainer.snapshotView
val iconView: View = taskContainer.iconView.asView()
- // TODO(334826842): Switch to splash state in TaskThumbnailView
if (!enableRefactorTaskThumbnail()) {
val thumbnailViewDeprecated = taskContainer.thumbnailViewDeprecated
builder.add(
@@ -198,6 +197,15 @@
)
)
thumbnailViewDeprecated.setShowSplashForSplitSelection(true)
+ } else {
+ builder.add(
+ ValueAnimator.ofFloat(0f, 1f).apply {
+ addUpdateListener {
+ taskContainer.taskContainerData.thumbnailSplashProgress.value =
+ it.animatedFraction
+ }
+ }
+ )
}
// With the new `IconAppChipView`, we always want to keep the chip pinned to the
// top left of the task / thumbnail.
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 46f6feb..8e232ee 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -193,7 +193,9 @@
import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
import com.android.quickstep.recents.data.RecentTasksRepository;
import com.android.quickstep.recents.data.RecentsDeviceProfileRepository;
+import com.android.quickstep.recents.data.RecentsDeviceProfileRepositoryImpl;
import com.android.quickstep.recents.data.RecentsRotationStateRepository;
+import com.android.quickstep.recents.data.RecentsRotationStateRepositoryImpl;
import com.android.quickstep.recents.di.RecentsDependencies;
import com.android.quickstep.recents.viewmodel.RecentsViewData;
import com.android.quickstep.recents.viewmodel.RecentsViewModel;
@@ -828,10 +830,10 @@
);
recentsDependencies.provide(RecentsRotationStateRepository.class,
- () -> new RecentsRotationStateRepository(mOrientationState));
+ () -> new RecentsRotationStateRepositoryImpl(mOrientationState));
recentsDependencies.provide(RecentsDeviceProfileRepository.class,
- () -> new RecentsDeviceProfileRepository(mContainer));
+ () -> new RecentsDeviceProfileRepositoryImpl(mContainer));
} else {
mRecentsViewModel = null;
}
@@ -3297,6 +3299,10 @@
}
private void setTaskThumbnailSplashAlpha(float taskThumbnailSplashAlpha) {
+ if (enableRefactorTaskThumbnail()) {
+ mRecentsViewModel.updateThumbnailSplashProgress(taskThumbnailSplashAlpha);
+ return;
+ }
int taskCount = getTaskViewCount();
if (taskCount == 0) {
return;
@@ -4894,7 +4900,6 @@
mSplitHiddenTaskView.getWidth(), mSplitHiddenTaskView.getHeight(),
primaryTaskSelected);
builder.addOnFrameCallback(() -> {
- // TODO(b/334826842): Handle splash icon for new TTV.
if (!enableRefactorTaskThumbnail()) {
taskContainer.getThumbnailViewDeprecated().refreshSplashView();
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskContainer.kt b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
index e7a8720..b1a25b5 100644
--- a/quickstep/src/com/android/quickstep/views/TaskContainer.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
@@ -69,7 +69,8 @@
private val taskContainerViewModel: TaskContainerViewModel by lazy {
TaskContainerViewModel(
sysUiStatusNavFlagsUseCase = RecentsDependencies.get(),
- getThumbnailUseCase = RecentsDependencies.get()
+ getThumbnailUseCase = RecentsDependencies.get(),
+ splashAlphaUseCase = RecentsDependencies.get(),
)
}
@@ -81,7 +82,7 @@
val taskViewScope = RecentsDependencies.getScope(taskView)
linkTo(taskViewScope)
- val taskContainerScope = RecentsDependencies.getScope(this)
+ val taskContainerScope = RecentsDependencies.getScope(this@TaskContainer)
linkTo(taskContainerScope)
}
} else {
@@ -112,7 +113,8 @@
// TODO(b/334826842): Support shouldShowSplashView for new TTV.
val shouldShowSplashView: Boolean
get() =
- if (enableRefactorTaskThumbnail()) false
+ if (enableRefactorTaskThumbnail())
+ taskContainerViewModel.shouldShowThumbnailSplash(task.key.id)
else thumbnailViewDeprecated.shouldShowSplashView()
// TODO(b/350743460) Support sysUiStatusNavFlags for new TTV.
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index f2f036a..b2abe69 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -285,6 +285,9 @@
set(value) {
field = value
applyScale()
+ if (enableRefactorTaskThumbnail()) {
+ taskViewModel.updateNonGridScale(value)
+ }
}
private var dismissScale = 1f
@@ -1051,11 +1054,9 @@
if (isQuickSwitch) {
setFreezeRecentTasksReordering()
}
- // TODO(b/334826842) add splash functionality to new TTV
- if (!enableRefactorTaskThumbnail()) {
- disableStartingWindow =
- firstContainer.thumbnailViewDeprecated.shouldShowSplashView()
- }
+ // TODO(b/334826842) no work required - add splash functionality to new TTV -
+ // cold start e.g. restart device. Small splash moving to bigger splash
+ disableStartingWindow = firstContainer.shouldShowSplashView
}
Executors.UI_HELPER_EXECUTOR.execute {
if (
@@ -1396,7 +1397,7 @@
protected open fun refreshTaskThumbnailSplash() {
if (!enableRefactorTaskThumbnail()) {
- // TODO(b/334826842) add splash functionality to new TTV
+ // TODO(b/342560598) handle onTaskIconChanged
taskContainers.forEach { it.thumbnailViewDeprecated.refreshSplashView() }
}
}
@@ -1420,7 +1421,6 @@
protected open fun applyThumbnailSplashAlpha() {
if (!enableRefactorTaskThumbnail()) {
- // TODO(b/334826842) add splash functionality to new TTV
taskContainers.forEach {
it.thumbnailViewDeprecated.setSplashAlpha(taskThumbnailSplashAlpha)
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeRecentsDeviceProfileRepository.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeRecentsDeviceProfileRepository.kt
new file mode 100644
index 0000000..cdfbd16
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeRecentsDeviceProfileRepository.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.quickstep.recents.data
+
+class FakeRecentsDeviceProfileRepository : RecentsDeviceProfileRepository {
+ private var recentsDeviceProfile =
+ RecentsDeviceProfile(
+ isLargeScreen = false,
+ widthPx = 1080,
+ heightPx = 1920,
+ )
+
+ override fun getRecentsDeviceProfile() = recentsDeviceProfile
+
+ fun setRecentsDeviceProfile(newValue: RecentsDeviceProfile) {
+ recentsDeviceProfile = newValue
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeRecentsRotationStateRepository.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeRecentsRotationStateRepository.kt
new file mode 100644
index 0000000..c328672
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeRecentsRotationStateRepository.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.quickstep.recents.data
+
+import android.view.Surface
+
+class FakeRecentsRotationStateRepository : RecentsRotationStateRepository {
+ private var recentsRotationState =
+ RecentsRotationState(
+ activityRotation = Surface.ROTATION_0,
+ orientationHandlerRotation = Surface.ROTATION_0
+ )
+
+ override fun getRecentsRotationState() = recentsRotationState
+
+ fun setRecentsRotationState(newValue: RecentsRotationState) {
+ recentsRotationState = newValue
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskIconDataSource.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskIconDataSource.kt
index 242bc73..fee4979 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskIconDataSource.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskIconDataSource.kt
@@ -23,10 +23,12 @@
import com.android.systemui.shared.recents.model.Task
import com.google.common.truth.Truth.assertThat
import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
class FakeTaskIconDataSource : TaskIconDataSource {
- val taskIdToDrawable: Map<Int, Drawable> = (0..10).associateWith { mock() }
+ val taskIdToDrawable: Map<Int, Drawable> = (0..10).associateWith { mockCopyableDrawable() }
+
val taskIdToUpdatingTask: MutableMap<Int, () -> Unit> = mutableMapOf()
var shouldLoadSynchronously: Boolean = true
@@ -49,6 +51,17 @@
}
return null
}
+
+ private fun mockCopyableDrawable(): Drawable {
+ val mutableDrawable = mock<Drawable>()
+ val immutableDrawable =
+ mock<Drawable>().apply { whenever(mutate()).thenReturn(mutableDrawable) }
+ val constantState =
+ mock<Drawable.ConstantState>().apply {
+ whenever(newDrawable()).thenReturn(immutableDrawable)
+ }
+ return mutableDrawable.apply { whenever(this.constantState).thenReturn(constantState) }
+ }
}
fun Task.assertHasIconDataFromSource(fakeTaskIconDataSource: FakeTaskIconDataSource) {
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt
index 19990a8..ec1da5a 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt
@@ -24,6 +24,7 @@
class FakeTasksRepository : RecentTasksRepository {
private var thumbnailDataMap: Map<Int, ThumbnailData> = emptyMap()
+ private var taskIconDataMap: Map<Int, TaskIconQueryResponse> = emptyMap()
private var tasks: MutableStateFlow<List<Task>> = MutableStateFlow(emptyList())
private var visibleTasks: MutableStateFlow<List<Int>> = MutableStateFlow(emptyList())
@@ -37,7 +38,17 @@
override fun setVisibleTasks(visibleTaskIdList: List<Int>) {
visibleTasks.value = visibleTaskIdList
- tasks.value = tasks.value.map { it.apply { thumbnail = thumbnailDataMap[it.key.id] } }
+ tasks.value =
+ tasks.value.map {
+ it.apply {
+ thumbnail = thumbnailDataMap[it.key.id]
+ taskIconDataMap[it.key.id].let { taskIconData ->
+ icon = taskIconData?.icon
+ titleDescription = taskIconData?.contentDescription
+ title = taskIconData?.title
+ }
+ }
+ }
}
fun seedTasks(tasks: List<Task>) {
@@ -47,4 +58,8 @@
fun seedThumbnailData(thumbnailDataMap: Map<Int, ThumbnailData>) {
this.thumbnailDataMap = thumbnailDataMap
}
+
+ fun seedIconData(iconDataMap: Map<Int, TaskIconQueryResponse>) {
+ this.taskIconDataMap = iconDataMap
+ }
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepositoryTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepositoryImplTest.kt
similarity index 84%
rename from quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepositoryTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepositoryImplTest.kt
index eff926d..e74fe4b 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepositoryTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepositoryImplTest.kt
@@ -25,12 +25,12 @@
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
-/** Test for [RecentsDeviceProfileRepository] */
+/** Test for [RecentsDeviceProfileRepositoryImpl] */
@RunWith(AndroidJUnit4::class)
-class RecentsDeviceProfileRepositoryTest : FakeInvariantDeviceProfileTest() {
+class RecentsDeviceProfileRepositoryImplTest : FakeInvariantDeviceProfileTest() {
private val recentsViewContainer = mock<RecentsViewContainer>()
- private val systemUnderTest = RecentsDeviceProfileRepository(recentsViewContainer)
+ private val systemUnderTest = RecentsDeviceProfileRepositoryImpl(recentsViewContainer)
@Test
fun deviceProfileMappedCorrectly() {
@@ -39,6 +39,6 @@
whenever(recentsViewContainer.deviceProfile).thenReturn(tabletDeviceProfile)
assertThat(systemUnderTest.getRecentsDeviceProfile())
- .isEqualTo(RecentsDeviceProfileRepository.RecentsDeviceProfile(isLargeScreen = true))
+ .isEqualTo(RecentsDeviceProfile(isLargeScreen = true, widthPx = 1600, heightPx = 2560))
}
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/RecentsRotationStateRepositoryTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/RecentsRotationStateRepositoryImplTest.kt
similarity index 71%
rename from quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/RecentsRotationStateRepositoryTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/RecentsRotationStateRepositoryImplTest.kt
index 1f4da26..017f037 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/RecentsRotationStateRepositoryTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/RecentsRotationStateRepositoryImplTest.kt
@@ -16,26 +16,32 @@
package com.android.quickstep.recents.data
+import android.view.Surface.ROTATION_270
import android.view.Surface.ROTATION_90
+import com.android.quickstep.orientation.SeascapePagedViewHandler
import com.android.quickstep.util.RecentsOrientedState
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
-/** Test for [RecentsRotationStateRepository] */
-class RecentsRotationStateRepositoryTest {
+/** Test for [RecentsRotationStateRepositoryImpl] */
+class RecentsRotationStateRepositoryImplTest {
private val recentsOrientedState = mock<RecentsOrientedState>()
- private val systemUnderTest = RecentsRotationStateRepository(recentsOrientedState)
+ private val systemUnderTest = RecentsRotationStateRepositoryImpl(recentsOrientedState)
@Test
fun orientedStateMappedCorrectly() {
whenever(recentsOrientedState.recentsActivityRotation).thenReturn(ROTATION_90)
+ whenever(recentsOrientedState.orientationHandler).thenReturn(SeascapePagedViewHandler())
assertThat(systemUnderTest.getRecentsRotationState())
.isEqualTo(
- RecentsRotationStateRepository.RecentsRotationState(activityRotation = ROTATION_90)
+ RecentsRotationState(
+ activityRotation = ROTATION_90,
+ orientationHandlerRotation = ROTATION_270
+ )
)
}
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/usecase/GetThumbnailPositionUseCaseTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/usecase/GetThumbnailPositionUseCaseTest.kt
index e657d59..02f1d11 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/usecase/GetThumbnailPositionUseCaseTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/usecase/GetThumbnailPositionUseCaseTest.kt
@@ -24,9 +24,9 @@
import android.graphics.Rect
import android.view.Surface.ROTATION_90
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.quickstep.recents.data.FakeRecentsDeviceProfileRepository
+import com.android.quickstep.recents.data.FakeRecentsRotationStateRepository
import com.android.quickstep.recents.data.FakeTasksRepository
-import com.android.quickstep.recents.data.RecentsDeviceProfileRepository
-import com.android.quickstep.recents.data.RecentsRotationStateRepository
import com.android.quickstep.recents.usecase.ThumbnailPositionState.MatrixScaling
import com.android.quickstep.recents.usecase.ThumbnailPositionState.MissingThumbnail
import com.android.systemui.shared.recents.model.Task
@@ -56,8 +56,8 @@
}
)
- private val deviceProfileRepository = mock<RecentsDeviceProfileRepository>()
- private val rotationStateRepository = mock<RecentsRotationStateRepository>()
+ private val deviceProfileRepository = FakeRecentsDeviceProfileRepository()
+ private val rotationStateRepository = FakeRecentsRotationStateRepository()
private val tasksRepository = FakeTasksRepository()
private val previewPositionHelper = mock<PreviewPositionHelper>()
@@ -93,15 +93,18 @@
tasksRepository.setVisibleTasks(listOf(TASK_ID))
val isLargeScreen = true
+ deviceProfileRepository.setRecentsDeviceProfile(
+ deviceProfileRepository.getRecentsDeviceProfile().copy(isLargeScreen = isLargeScreen)
+ )
val activityRotation = ROTATION_90
+ rotationStateRepository.setRecentsRotationState(
+ rotationStateRepository
+ .getRecentsRotationState()
+ .copy(activityRotation = activityRotation)
+ )
val isRtl = true
val isRotated = true
- whenever(deviceProfileRepository.getRecentsDeviceProfile())
- .thenReturn(RecentsDeviceProfileRepository.RecentsDeviceProfile(isLargeScreen))
- whenever(rotationStateRepository.getRecentsRotationState())
- .thenReturn(RecentsRotationStateRepository.RecentsRotationState(activityRotation))
-
whenever(previewPositionHelper.matrix).thenReturn(MATRIX)
whenever(previewPositionHelper.isOrientationChanged).thenReturn(isRotated)
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/GetSplashSizeUseCaseTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/GetSplashSizeUseCaseTest.kt
new file mode 100644
index 0000000..13e8b09
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/GetSplashSizeUseCaseTest.kt
@@ -0,0 +1,84 @@
+/*
+ * 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.quickstep.task.thumbnail
+
+import android.graphics.Point
+import android.graphics.drawable.Drawable
+import com.android.quickstep.recents.data.FakeRecentsDeviceProfileRepository
+import com.android.quickstep.task.viewmodel.TaskViewData
+import com.android.quickstep.views.TaskViewType
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+class GetSplashSizeUseCaseTest {
+ private val taskThumbnailViewData = TaskThumbnailViewData()
+ private val taskViewData = TaskViewData(TaskViewType.SINGLE)
+ private val recentsDeviceProfileRepository = FakeRecentsDeviceProfileRepository()
+ private val systemUnderTest =
+ GetSplashSizeUseCase(taskThumbnailViewData, taskViewData, recentsDeviceProfileRepository)
+
+ @Test
+ fun execute_whenNoScaleRequired_returnsIntrinsicSize() {
+ taskThumbnailViewData.width.value =
+ recentsDeviceProfileRepository.getRecentsDeviceProfile().widthPx
+ taskThumbnailViewData.height.value =
+ recentsDeviceProfileRepository.getRecentsDeviceProfile().heightPx
+
+ assertThat(systemUnderTest.execute(createIcon(100, 100))).isEqualTo(Point(100, 100))
+ }
+
+ @Test
+ fun execute_whenThumbnailViewIsSmallerThanScreen_returnsScaledSize() {
+ taskThumbnailViewData.width.value =
+ recentsDeviceProfileRepository.getRecentsDeviceProfile().widthPx / 2
+ taskThumbnailViewData.height.value =
+ recentsDeviceProfileRepository.getRecentsDeviceProfile().heightPx / 2
+
+ assertThat(systemUnderTest.execute(createIcon(100, 100))).isEqualTo(Point(50, 50))
+ }
+
+ @Test
+ fun execute_whenThumbnailViewIsSmallerThanScreen_withNonGridScale_returnsScaledSize() {
+ taskThumbnailViewData.width.value =
+ recentsDeviceProfileRepository.getRecentsDeviceProfile().widthPx / 2
+ taskThumbnailViewData.height.value =
+ recentsDeviceProfileRepository.getRecentsDeviceProfile().heightPx / 2
+ taskViewData.nonGridScale.value = 2f
+
+ assertThat(systemUnderTest.execute(createIcon(100, 100))).isEqualTo(Point(25, 25))
+ }
+
+ @Test
+ fun execute_whenThumbnailViewIsSmallerThanScreen_withThumbnailViewScale_returnsScaledSize() {
+ taskThumbnailViewData.width.value =
+ recentsDeviceProfileRepository.getRecentsDeviceProfile().widthPx / 2
+ taskThumbnailViewData.height.value =
+ recentsDeviceProfileRepository.getRecentsDeviceProfile().heightPx / 2
+ taskThumbnailViewData.scaleX.value = 2f
+ taskThumbnailViewData.scaleY.value = 2f
+
+ assertThat(systemUnderTest.execute(createIcon(100, 100))).isEqualTo(Point(25, 25))
+ }
+
+ private fun createIcon(width: Int, height: Int): Drawable =
+ mock<Drawable>().apply {
+ whenever(intrinsicWidth).thenReturn(width)
+ whenever(intrinsicHeight).thenReturn(height)
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/SplashAlphaUseCaseTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/SplashAlphaUseCaseTest.kt
new file mode 100644
index 0000000..e083046
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/SplashAlphaUseCaseTest.kt
@@ -0,0 +1,152 @@
+/*
+ * 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.quickstep.task.thumbnail
+
+import android.content.ComponentName
+import android.content.Intent
+import android.graphics.Bitmap
+import android.graphics.Color
+import android.graphics.drawable.Drawable
+import android.view.Surface
+import android.view.Surface.ROTATION_90
+import com.android.quickstep.recents.data.FakeRecentsRotationStateRepository
+import com.android.quickstep.recents.data.FakeTasksRepository
+import com.android.quickstep.recents.data.TaskIconQueryResponse
+import com.android.quickstep.recents.viewmodel.RecentsViewData
+import com.android.quickstep.task.viewmodel.TaskContainerData
+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.flow.first
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+class SplashAlphaUseCaseTest {
+ private val recentsViewData = RecentsViewData()
+ private val taskContainerData = TaskContainerData()
+ private val taskThumbnailViewData = TaskThumbnailViewData()
+ private val recentTasksRepository = FakeTasksRepository()
+ private val recentsRotationStateRepository = FakeRecentsRotationStateRepository()
+ private val systemUnderTest =
+ SplashAlphaUseCase(
+ recentsViewData,
+ taskContainerData,
+ taskThumbnailViewData,
+ recentTasksRepository,
+ recentsRotationStateRepository
+ )
+
+ @Test
+ fun execute_withNullThumbnail_showsSplash() = runTest {
+ assertThat(systemUnderTest.execute(0).first()).isEqualTo(SPLASH_SHOWN)
+ }
+
+ @Test
+ fun execute_withTaskSpecificSplashAlpha_showsSplash() = runTest {
+ setupTask(2)
+ taskContainerData.thumbnailSplashProgress.value = 0.7f
+
+ assertThat(systemUnderTest.execute(2).first()).isEqualTo(0.7f)
+ }
+
+ @Test
+ fun execute_withNoGlobalSplashEnabled_doesntShowSplash() = runTest {
+ setupTask(2)
+
+ assertThat(systemUnderTest.execute(2).first()).isEqualTo(SPLASH_HIDDEN)
+ }
+
+ @Test
+ fun execute_withSameAspectRatioAndRotation_withGlobalSplashEnabled_doesntShowSplash() =
+ runTest {
+ setupTask(2)
+ recentsViewData.thumbnailSplashProgress.value = 0.5f
+ taskThumbnailViewData.width.value = THUMBNAIL_WIDTH * 2
+ taskThumbnailViewData.height.value = THUMBNAIL_HEIGHT * 2
+
+ assertThat(systemUnderTest.execute(2).first()).isEqualTo(SPLASH_HIDDEN)
+ }
+
+ @Test
+ fun execute_withDifferentAspectRatioAndSameRotation_showsSplash() = runTest {
+ setupTask(2)
+ recentsViewData.thumbnailSplashProgress.value = 0.5f
+ taskThumbnailViewData.width.value = THUMBNAIL_WIDTH
+ taskThumbnailViewData.height.value = THUMBNAIL_HEIGHT * 2
+
+ assertThat(systemUnderTest.execute(2).first()).isEqualTo(0.5f)
+ }
+
+ @Test
+ fun execute_withSameAspectRatioAndDifferentRotation_showsSplash() = runTest {
+ setupTask(2, createThumbnailData(rotation = ROTATION_90))
+ recentsViewData.thumbnailSplashProgress.value = 0.5f
+ taskThumbnailViewData.width.value = THUMBNAIL_WIDTH * 2
+ taskThumbnailViewData.height.value = THUMBNAIL_HEIGHT * 2
+
+ assertThat(systemUnderTest.execute(2).first()).isEqualTo(0.5f)
+ }
+
+ @Test
+ fun execute_withDifferentAspectRatioAndRotation_showsSplash() = runTest {
+ setupTask(2, createThumbnailData(rotation = ROTATION_90))
+ recentsViewData.thumbnailSplashProgress.value = 0.5f
+ taskThumbnailViewData.width.value = THUMBNAIL_WIDTH
+ taskThumbnailViewData.height.value = THUMBNAIL_HEIGHT * 2
+
+ assertThat(systemUnderTest.execute(2).first()).isEqualTo(0.5f)
+ }
+
+ private val tasks = (0..5).map(::createTaskWithId)
+
+ private fun setupTask(taskId: Int, thumbnailData: ThumbnailData = createThumbnailData()) {
+ recentTasksRepository.seedThumbnailData(mapOf(taskId to thumbnailData))
+ val expectedIconData = createIconData("Task $taskId")
+ recentTasksRepository.seedIconData(mapOf(taskId to expectedIconData))
+ recentTasksRepository.seedTasks(tasks)
+ recentTasksRepository.setVisibleTasks(listOf(taskId))
+ }
+
+ private fun createThumbnailData(
+ rotation: Int = Surface.ROTATION_0,
+ width: Int = THUMBNAIL_WIDTH,
+ height: Int = THUMBNAIL_HEIGHT
+ ): ThumbnailData {
+ val bitmap = mock<Bitmap>()
+ whenever(bitmap.width).thenReturn(width)
+ whenever(bitmap.height).thenReturn(height)
+
+ return ThumbnailData(thumbnail = bitmap, rotation = rotation)
+ }
+
+ private fun createIconData(title: String) = TaskIconQueryResponse(mock<Drawable>(), "", title)
+
+ private fun createTaskWithId(taskId: Int) =
+ Task(Task.TaskKey(taskId, 0, Intent(), ComponentName("", ""), 0, 2000)).apply {
+ colorBackground = Color.argb(taskId, taskId, taskId, taskId)
+ }
+
+ companion object {
+ const val THUMBNAIL_WIDTH = 100
+ const val THUMBNAIL_HEIGHT = 200
+
+ const val SPLASH_HIDDEN = 0f
+ const val SPLASH_SHOWN = 1f
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt
index 3de5669..877528e 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt
@@ -21,8 +21,12 @@
import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.Matrix
+import android.graphics.Point
+import android.graphics.drawable.Drawable
+import android.view.Surface
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.quickstep.recents.data.FakeTasksRepository
+import com.android.quickstep.recents.data.TaskIconQueryResponse
import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase
import com.android.quickstep.recents.usecase.ThumbnailPositionState.MatrixScaling
import com.android.quickstep.recents.usecase.ThumbnailPositionState.MissingThumbnail
@@ -30,6 +34,8 @@
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.SnapshotSplash
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Splash
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
import com.android.quickstep.task.viewmodel.TaskContainerData
import com.android.quickstep.task.viewmodel.TaskThumbnailViewModel
@@ -40,8 +46,10 @@
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
@@ -53,18 +61,27 @@
private val taskContainerData = TaskContainerData()
private val tasksRepository = FakeTasksRepository()
private val mGetThumbnailPositionUseCase = mock<GetThumbnailPositionUseCase>()
+ private val splashAlphaUseCase: SplashAlphaUseCase = mock()
+ private val getSplashSizeUseCase: GetSplashSizeUseCase = mock()
private val systemUnderTest by lazy {
TaskThumbnailViewModel(
recentsViewData,
taskViewData,
taskContainerData,
tasksRepository,
- mGetThumbnailPositionUseCase
+ mGetThumbnailPositionUseCase,
+ splashAlphaUseCase,
+ getSplashSizeUseCase,
)
}
private val tasks = (0..5).map(::createTaskWithId)
+ @Before
+ fun setUp() {
+ whenever(getSplashSizeUseCase.execute(any())).thenReturn(Point())
+ }
+
@Test
fun initialStateIsUninitialized() = runTest {
assertThat(systemUnderTest.uiState.first()).isEqualTo(Uninitialized)
@@ -147,9 +164,11 @@
}
@Test
- fun bindStoppedTaskWithThumbnail_thenStateIs_Snapshot_withAlphaRemoved() = runTest {
- val expectedThumbnailData = createThumbnailData()
+ fun bindStoppedTaskWithThumbnail_thenStateIs_SnapshotSplash_withAlphaRemoved() = runTest {
+ val expectedThumbnailData = createThumbnailData(rotation = Surface.ROTATION_270)
tasksRepository.seedThumbnailData(mapOf(2 to expectedThumbnailData))
+ val expectedIconData = createIconData("Task 2")
+ tasksRepository.seedIconData(mapOf(2 to expectedIconData))
tasksRepository.seedTasks(tasks)
tasksRepository.setVisibleTasks(listOf(2))
val recentTask = TaskThumbnail(taskId = 2, isRunning = false)
@@ -157,17 +176,23 @@
systemUnderTest.bind(recentTask)
assertThat(systemUnderTest.uiState.first())
.isEqualTo(
- Snapshot(
- backgroundColor = Color.rgb(2, 2, 2),
- bitmap = expectedThumbnailData.thumbnail!!,
+ SnapshotSplash(
+ Snapshot(
+ backgroundColor = Color.rgb(2, 2, 2),
+ bitmap = expectedThumbnailData.thumbnail!!,
+ thumbnailRotation = Surface.ROTATION_270,
+ ),
+ Splash(expectedIconData.icon, Point())
)
)
}
@Test
- fun bindNonVisibleStoppedTask_whenMadeVisible_thenStateIsSnapshot() = runTest {
+ fun bindNonVisibleStoppedTask_whenMadeVisible_thenStateIsSnapshotSplash() = runTest {
val expectedThumbnailData = createThumbnailData()
tasksRepository.seedThumbnailData(mapOf(2 to expectedThumbnailData))
+ val expectedIconData = createIconData("Task 2")
+ tasksRepository.seedIconData(mapOf(2 to expectedIconData))
tasksRepository.seedTasks(tasks)
val recentTask = TaskThumbnail(taskId = 2, isRunning = false)
@@ -177,14 +202,35 @@
tasksRepository.setVisibleTasks(listOf(2))
assertThat(systemUnderTest.uiState.first())
.isEqualTo(
- Snapshot(
- backgroundColor = Color.rgb(2, 2, 2),
- bitmap = expectedThumbnailData.thumbnail!!,
+ SnapshotSplash(
+ Snapshot(
+ backgroundColor = Color.rgb(2, 2, 2),
+ bitmap = expectedThumbnailData.thumbnail!!,
+ thumbnailRotation = Surface.ROTATION_0,
+ ),
+ Splash(expectedIconData.icon, Point())
)
)
}
@Test
+ fun bindStoppedTask_thenStateContainsSplashSizeFromUseCase() = runTest {
+ val expectedSplashSize = Point(100, 150)
+ whenever(getSplashSizeUseCase.execute(any())).thenReturn(expectedSplashSize)
+ val expectedThumbnailData = createThumbnailData(rotation = Surface.ROTATION_270)
+ tasksRepository.seedThumbnailData(mapOf(2 to expectedThumbnailData))
+ val expectedIconData = createIconData("Task 2")
+ tasksRepository.seedIconData(mapOf(2 to expectedIconData))
+ tasksRepository.seedTasks(tasks)
+ tasksRepository.setVisibleTasks(listOf(2))
+ val recentTask = TaskThumbnail(taskId = 2, isRunning = false)
+
+ systemUnderTest.bind(recentTask)
+ val uiState = systemUnderTest.uiState.first() as SnapshotSplash
+ assertThat(uiState.splash.size).isEqualTo(expectedSplashSize)
+ }
+
+ @Test
fun getSnapshotMatrix_MissingThumbnail() = runTest {
val taskId = 2
val recentTask = TaskThumbnail(taskId = taskId, isRunning = false)
@@ -238,14 +284,16 @@
colorBackground = Color.argb(taskId, taskId, taskId, taskId)
}
- private fun createThumbnailData(): ThumbnailData {
+ private fun createThumbnailData(rotation: Int = Surface.ROTATION_0): ThumbnailData {
val bitmap = mock<Bitmap>()
whenever(bitmap.width).thenReturn(THUMBNAIL_WIDTH)
whenever(bitmap.height).thenReturn(THUMBNAIL_HEIGHT)
- return ThumbnailData(thumbnail = bitmap)
+ return ThumbnailData(thumbnail = bitmap, rotation = rotation)
}
+ private fun createIconData(title: String) = TaskIconQueryResponse(mock<Drawable>(), "", title)
+
companion object {
const val THUMBNAIL_WIDTH = 100
const val THUMBNAIL_HEIGHT = 200