Loading quickstep/src/com/android/quickstep/recents/data/RecentTasksRepository.kt 0 → 100644 +37 −0 Original line number Diff line number Diff line /* * 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.systemui.shared.recents.model.Task import kotlinx.coroutines.flow.Flow interface RecentTasksRepository { /** Gets all the recent tasks, refreshing from data sources if [forceRefresh] is true. */ fun getAllTaskData(forceRefresh: Boolean = false): Flow<List<Task>> /** * Gets the data associated with a task that has id [taskId]. Flow will settle on null if the * task was not found. */ fun getTaskDataById(taskId: Int): Flow<Task?> /** * Sets the tasks that are visible, indicating that properties relating to visuals need to be * populated e.g. icons/thumbnails etc. */ fun setVisibleTasks(visibleTaskIdList: List<Int>) } quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt +4 −4 Original line number Diff line number Diff line Loading @@ -38,7 +38,7 @@ class TasksRepository( private val recentsModel: RecentTasksDataSource, private val taskThumbnailDataSource: TaskThumbnailDataSource, private val taskIconCache: TaskIconCache, ) { ) : RecentTasksRepository { private val groupedTaskData = MutableStateFlow(emptyList<GroupTask>()) private val _taskData = groupedTaskData.map { groupTaskList -> groupTaskList.flatMap { it.tasks } } Loading @@ -53,17 +53,17 @@ class TasksRepository( tasks } fun getAllTaskData(forceRefresh: Boolean = false): Flow<List<Task>> { override fun getAllTaskData(forceRefresh: Boolean): Flow<List<Task>> { if (forceRefresh) { recentsModel.getTasks { groupedTaskData.value = it } } return taskData } fun getTaskDataById(taskId: Int): Flow<Task?> = override fun getTaskDataById(taskId: Int): Flow<Task?> = taskData.map { taskList -> taskList.firstOrNull { it.key.id == taskId } } fun setVisibleTasks(visibleTaskIdList: List<Int>) { override fun setVisibleTasks(visibleTaskIdList: List<Int>) { this.visibleTaskIds.value = visibleTaskIdList.toSet() } Loading quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt +10 −2 Original line number Diff line number Diff line Loading @@ -16,11 +16,19 @@ package com.android.quickstep.task.thumbnail import com.android.systemui.shared.recents.model.Task import android.graphics.Bitmap import android.graphics.Rect import androidx.annotation.ColorInt sealed class TaskThumbnailUiState { data object Uninitialized : TaskThumbnailUiState() data object LiveTile : TaskThumbnailUiState() data class BackgroundOnly(@ColorInt val backgroundColor: Int) : TaskThumbnailUiState() data class Snapshot( val bitmap: Bitmap, val drawnRect: Rect, @ColorInt val backgroundColor: Int ) : TaskThumbnailUiState() } data class TaskThumbnail(val task: Task, val isRunning: Boolean) data class TaskThumbnail(val taskId: Int, val isRunning: Boolean) quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt +33 −13 Original line number Diff line number Diff line Loading @@ -19,15 +19,20 @@ package com.android.quickstep.task.thumbnail import android.content.Context import android.content.res.Configuration import android.graphics.Canvas import android.graphics.Color import android.graphics.Outline import android.graphics.Paint import android.graphics.PorterDuff import android.graphics.PorterDuffXfermode import android.graphics.Rect import android.util.AttributeSet import android.view.View import android.view.ViewOutlineProvider import androidx.annotation.ColorInt import com.android.launcher3.Utilities 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.Uninitialized import com.android.quickstep.util.TaskCornerRadius import com.android.quickstep.views.RecentsView Loading @@ -42,17 +47,26 @@ class TaskThumbnailView : View { // to [TaskView], and also shared between [TaskView] and [TaskThumbnailView] // This is using a lazy for now because the dependencies cannot be obtained without DI. val viewModel by lazy { TaskThumbnailViewModel( val recentsView = RecentsViewContainer.containerFromContext<RecentsViewContainer>(context) .getOverviewPanel<RecentsView<*, *>>() .mRecentsViewData, (parent as TaskView).taskViewData TaskThumbnailViewModel( recentsView.mRecentsViewData, (parent as TaskView).taskViewData, recentsView.mTasksRepository, ) } private var uiState: TaskThumbnailUiState = Uninitialized private var inheritedScale: Float = 1f private val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG) private val _measuredBounds = Rect() private val measuredBounds: Rect get() { _measuredBounds.set(0, 0, measuredWidth, measuredHeight) return _measuredBounds } private var cornerRadius: Float = TaskCornerRadius.get(context) private var fullscreenCornerRadius: Float = QuickStepContract.getWindowCornerRadius(context) Loading Loading @@ -85,24 +99,25 @@ class TaskThumbnailView : View { outlineProvider = object : ViewOutlineProvider() { override fun getOutline(view: View, outline: Outline) { outline.setRoundRect( 0, 0, view.measuredWidth, view.measuredHeight, getCurrentCornerRadius() ) outline.setRoundRect(measuredBounds, getCurrentCornerRadius()) } } } override fun onDraw(canvas: Canvas) { when (uiState) { is Uninitialized -> {} when (val uiStateVal = uiState) { is Uninitialized -> drawBackgroundOnly(canvas, Color.BLACK) is LiveTile -> drawTransparentUiState(canvas) is Snapshot -> drawSnapshotState(canvas, uiStateVal) is BackgroundOnly -> drawBackgroundOnly(canvas, uiStateVal.backgroundColor) } } private fun drawBackgroundOnly(canvas: Canvas, @ColorInt backgroundColor: Int) { backgroundPaint.color = backgroundColor canvas.drawRect(measuredBounds, backgroundPaint) } override fun onConfigurationChanged(newConfig: Configuration?) { super.onConfigurationChanged(newConfig) Loading @@ -112,7 +127,12 @@ class TaskThumbnailView : View { } private fun drawTransparentUiState(canvas: Canvas) { canvas.drawRect(0f, 0f, measuredWidth.toFloat(), measuredHeight.toFloat(), CLEAR_PAINT) canvas.drawRect(measuredBounds, CLEAR_PAINT) } private fun drawSnapshotState(canvas: Canvas, snapshot: Snapshot) { drawBackgroundOnly(canvas, snapshot.backgroundColor) canvas.drawBitmap(snapshot.bitmap, snapshot.drawnRect, measuredBounds, null) } private fun getCurrentCornerRadius() = Loading quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModel.kt +55 −11 Original line number Diff line number Diff line Loading @@ -16,32 +16,76 @@ package com.android.quickstep.task.thumbnail import android.annotation.ColorInt import android.graphics.Rect import androidx.core.graphics.ColorUtils import com.android.quickstep.recents.data.RecentTasksRepository import com.android.quickstep.recents.viewmodel.RecentsViewData 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.Uninitialized import com.android.quickstep.task.viewmodel.TaskViewData import com.android.systemui.shared.recents.model.Task import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map class TaskThumbnailViewModel(recentsViewData: RecentsViewData, taskViewData: TaskViewData) { private val task = MutableStateFlow<TaskThumbnail?>(null) @OptIn(ExperimentalCoroutinesApi::class) class TaskThumbnailViewModel( recentsViewData: RecentsViewData, taskViewData: TaskViewData, private val tasksRepository: RecentTasksRepository, ) { private val task = MutableStateFlow<Flow<Task?>>(flowOf(null)) private var boundTaskIsRunning = false val recentsFullscreenProgress = recentsViewData.fullscreenProgress val inheritedScale = combine(recentsViewData.scale, taskViewData.scale) { recentsScale, taskScale -> recentsScale * taskScale } val uiState = task.map { taskVal -> val uiState: Flow<TaskThumbnailUiState> = task .flatMapLatest { taskFlow -> taskFlow.map { taskVal -> when { taskVal == null -> Uninitialized taskVal.isRunning -> LiveTile boundTaskIsRunning -> LiveTile isBackgroundOnly(taskVal) -> BackgroundOnly(taskVal.colorBackground.removeAlpha()) isSnapshotState(taskVal) -> { val bitmap = taskVal.thumbnail?.thumbnail!! Snapshot( bitmap, Rect(0, 0, bitmap.width, bitmap.height), taskVal.colorBackground.removeAlpha() ) } else -> Uninitialized } } } .distinctUntilChanged() fun bind(task: TaskThumbnail) { this.task.value = task fun bind(taskThumbnail: TaskThumbnail) { boundTaskIsRunning = taskThumbnail.isRunning task.value = tasksRepository.getTaskDataById(taskThumbnail.taskId) } private fun isBackgroundOnly(task: Task): Boolean = task.isLocked || task.thumbnail == null private fun isSnapshotState(task: Task): Boolean { val thumbnailPresent = task.thumbnail?.thumbnail != null val taskLocked = task.isLocked return thumbnailPresent && !taskLocked } @ColorInt private fun Int.removeAlpha(): Int = ColorUtils.setAlphaComponent(this, 0xff) } Loading
quickstep/src/com/android/quickstep/recents/data/RecentTasksRepository.kt 0 → 100644 +37 −0 Original line number Diff line number Diff line /* * 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.systemui.shared.recents.model.Task import kotlinx.coroutines.flow.Flow interface RecentTasksRepository { /** Gets all the recent tasks, refreshing from data sources if [forceRefresh] is true. */ fun getAllTaskData(forceRefresh: Boolean = false): Flow<List<Task>> /** * Gets the data associated with a task that has id [taskId]. Flow will settle on null if the * task was not found. */ fun getTaskDataById(taskId: Int): Flow<Task?> /** * Sets the tasks that are visible, indicating that properties relating to visuals need to be * populated e.g. icons/thumbnails etc. */ fun setVisibleTasks(visibleTaskIdList: List<Int>) }
quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt +4 −4 Original line number Diff line number Diff line Loading @@ -38,7 +38,7 @@ class TasksRepository( private val recentsModel: RecentTasksDataSource, private val taskThumbnailDataSource: TaskThumbnailDataSource, private val taskIconCache: TaskIconCache, ) { ) : RecentTasksRepository { private val groupedTaskData = MutableStateFlow(emptyList<GroupTask>()) private val _taskData = groupedTaskData.map { groupTaskList -> groupTaskList.flatMap { it.tasks } } Loading @@ -53,17 +53,17 @@ class TasksRepository( tasks } fun getAllTaskData(forceRefresh: Boolean = false): Flow<List<Task>> { override fun getAllTaskData(forceRefresh: Boolean): Flow<List<Task>> { if (forceRefresh) { recentsModel.getTasks { groupedTaskData.value = it } } return taskData } fun getTaskDataById(taskId: Int): Flow<Task?> = override fun getTaskDataById(taskId: Int): Flow<Task?> = taskData.map { taskList -> taskList.firstOrNull { it.key.id == taskId } } fun setVisibleTasks(visibleTaskIdList: List<Int>) { override fun setVisibleTasks(visibleTaskIdList: List<Int>) { this.visibleTaskIds.value = visibleTaskIdList.toSet() } Loading
quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt +10 −2 Original line number Diff line number Diff line Loading @@ -16,11 +16,19 @@ package com.android.quickstep.task.thumbnail import com.android.systemui.shared.recents.model.Task import android.graphics.Bitmap import android.graphics.Rect import androidx.annotation.ColorInt sealed class TaskThumbnailUiState { data object Uninitialized : TaskThumbnailUiState() data object LiveTile : TaskThumbnailUiState() data class BackgroundOnly(@ColorInt val backgroundColor: Int) : TaskThumbnailUiState() data class Snapshot( val bitmap: Bitmap, val drawnRect: Rect, @ColorInt val backgroundColor: Int ) : TaskThumbnailUiState() } data class TaskThumbnail(val task: Task, val isRunning: Boolean) data class TaskThumbnail(val taskId: Int, val isRunning: Boolean)
quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt +33 −13 Original line number Diff line number Diff line Loading @@ -19,15 +19,20 @@ package com.android.quickstep.task.thumbnail import android.content.Context import android.content.res.Configuration import android.graphics.Canvas import android.graphics.Color import android.graphics.Outline import android.graphics.Paint import android.graphics.PorterDuff import android.graphics.PorterDuffXfermode import android.graphics.Rect import android.util.AttributeSet import android.view.View import android.view.ViewOutlineProvider import androidx.annotation.ColorInt import com.android.launcher3.Utilities 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.Uninitialized import com.android.quickstep.util.TaskCornerRadius import com.android.quickstep.views.RecentsView Loading @@ -42,17 +47,26 @@ class TaskThumbnailView : View { // to [TaskView], and also shared between [TaskView] and [TaskThumbnailView] // This is using a lazy for now because the dependencies cannot be obtained without DI. val viewModel by lazy { TaskThumbnailViewModel( val recentsView = RecentsViewContainer.containerFromContext<RecentsViewContainer>(context) .getOverviewPanel<RecentsView<*, *>>() .mRecentsViewData, (parent as TaskView).taskViewData TaskThumbnailViewModel( recentsView.mRecentsViewData, (parent as TaskView).taskViewData, recentsView.mTasksRepository, ) } private var uiState: TaskThumbnailUiState = Uninitialized private var inheritedScale: Float = 1f private val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG) private val _measuredBounds = Rect() private val measuredBounds: Rect get() { _measuredBounds.set(0, 0, measuredWidth, measuredHeight) return _measuredBounds } private var cornerRadius: Float = TaskCornerRadius.get(context) private var fullscreenCornerRadius: Float = QuickStepContract.getWindowCornerRadius(context) Loading Loading @@ -85,24 +99,25 @@ class TaskThumbnailView : View { outlineProvider = object : ViewOutlineProvider() { override fun getOutline(view: View, outline: Outline) { outline.setRoundRect( 0, 0, view.measuredWidth, view.measuredHeight, getCurrentCornerRadius() ) outline.setRoundRect(measuredBounds, getCurrentCornerRadius()) } } } override fun onDraw(canvas: Canvas) { when (uiState) { is Uninitialized -> {} when (val uiStateVal = uiState) { is Uninitialized -> drawBackgroundOnly(canvas, Color.BLACK) is LiveTile -> drawTransparentUiState(canvas) is Snapshot -> drawSnapshotState(canvas, uiStateVal) is BackgroundOnly -> drawBackgroundOnly(canvas, uiStateVal.backgroundColor) } } private fun drawBackgroundOnly(canvas: Canvas, @ColorInt backgroundColor: Int) { backgroundPaint.color = backgroundColor canvas.drawRect(measuredBounds, backgroundPaint) } override fun onConfigurationChanged(newConfig: Configuration?) { super.onConfigurationChanged(newConfig) Loading @@ -112,7 +127,12 @@ class TaskThumbnailView : View { } private fun drawTransparentUiState(canvas: Canvas) { canvas.drawRect(0f, 0f, measuredWidth.toFloat(), measuredHeight.toFloat(), CLEAR_PAINT) canvas.drawRect(measuredBounds, CLEAR_PAINT) } private fun drawSnapshotState(canvas: Canvas, snapshot: Snapshot) { drawBackgroundOnly(canvas, snapshot.backgroundColor) canvas.drawBitmap(snapshot.bitmap, snapshot.drawnRect, measuredBounds, null) } private fun getCurrentCornerRadius() = Loading
quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModel.kt +55 −11 Original line number Diff line number Diff line Loading @@ -16,32 +16,76 @@ package com.android.quickstep.task.thumbnail import android.annotation.ColorInt import android.graphics.Rect import androidx.core.graphics.ColorUtils import com.android.quickstep.recents.data.RecentTasksRepository import com.android.quickstep.recents.viewmodel.RecentsViewData 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.Uninitialized import com.android.quickstep.task.viewmodel.TaskViewData import com.android.systemui.shared.recents.model.Task import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map class TaskThumbnailViewModel(recentsViewData: RecentsViewData, taskViewData: TaskViewData) { private val task = MutableStateFlow<TaskThumbnail?>(null) @OptIn(ExperimentalCoroutinesApi::class) class TaskThumbnailViewModel( recentsViewData: RecentsViewData, taskViewData: TaskViewData, private val tasksRepository: RecentTasksRepository, ) { private val task = MutableStateFlow<Flow<Task?>>(flowOf(null)) private var boundTaskIsRunning = false val recentsFullscreenProgress = recentsViewData.fullscreenProgress val inheritedScale = combine(recentsViewData.scale, taskViewData.scale) { recentsScale, taskScale -> recentsScale * taskScale } val uiState = task.map { taskVal -> val uiState: Flow<TaskThumbnailUiState> = task .flatMapLatest { taskFlow -> taskFlow.map { taskVal -> when { taskVal == null -> Uninitialized taskVal.isRunning -> LiveTile boundTaskIsRunning -> LiveTile isBackgroundOnly(taskVal) -> BackgroundOnly(taskVal.colorBackground.removeAlpha()) isSnapshotState(taskVal) -> { val bitmap = taskVal.thumbnail?.thumbnail!! Snapshot( bitmap, Rect(0, 0, bitmap.width, bitmap.height), taskVal.colorBackground.removeAlpha() ) } else -> Uninitialized } } } .distinctUntilChanged() fun bind(task: TaskThumbnail) { this.task.value = task fun bind(taskThumbnail: TaskThumbnail) { boundTaskIsRunning = taskThumbnail.isRunning task.value = tasksRepository.getTaskDataById(taskThumbnail.taskId) } private fun isBackgroundOnly(task: Task): Boolean = task.isLocked || task.thumbnail == null private fun isSnapshotState(task: Task): Boolean { val thumbnailPresent = task.thumbnail?.thumbnail != null val taskLocked = task.isLocked return thumbnailPresent && !taskLocked } @ColorInt private fun Int.removeAlpha(): Int = ColorUtils.setAlphaComponent(this, 0xff) }