Loading quickstep/src/com/android/quickstep/TaskOverlayFactory.java +82 −7 Original line number Diff line number Diff line Loading @@ -16,18 +16,22 @@ package com.android.quickstep; import static com.android.launcher3.Flags.enableRefactorTaskThumbnail; import static com.android.quickstep.views.OverviewActionsView.DISABLED_NO_THUMBNAIL; import static com.android.quickstep.views.OverviewActionsView.DISABLED_ROTATED; import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Insets; import android.graphics.Matrix; import android.graphics.Rect; import android.graphics.RectF; import android.os.Build; import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.android.launcher3.BaseActivity; Loading @@ -38,6 +42,7 @@ import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.util.ResourceBasedOverride; import com.android.launcher3.views.ActivityContext; import com.android.launcher3.views.Snackbar; import com.android.quickstep.task.util.TaskOverlayHelper; import com.android.quickstep.util.RecentsOrientedState; import com.android.quickstep.views.DesktopTaskView; import com.android.quickstep.views.GroupedTaskView; Loading Loading @@ -128,12 +133,43 @@ public class TaskOverlayFactory implements ResourceBasedOverride { private T mActionsView; protected ImageActionsApi mImageApi; protected TaskOverlayHelper mHelper; protected TaskOverlay(TaskContainer taskContainer) { mApplicationContext = taskContainer.getTaskView().getContext().getApplicationContext(); mTaskContainer = taskContainer; mImageApi = new ImageActionsApi( mApplicationContext, mTaskContainer::getThumbnail); if (enableRefactorTaskThumbnail()) { mHelper = new TaskOverlayHelper(mTaskContainer.getTask(), this); } mImageApi = new ImageActionsApi(mApplicationContext, this::getThumbnail); } /** * Initialize the overlay when a Task is bound to the TaskView. */ public void init() { if (enableRefactorTaskThumbnail()) { mHelper.init(); } } /** * Destroy the overlay when the TaskView is recycled. */ public void destroy() { if (enableRefactorTaskThumbnail()) { mHelper.destroy(); } } protected @Nullable Bitmap getThumbnail() { return enableRefactorTaskThumbnail() ? mHelper.getEnabledState().getThumbnail() : mTaskContainer.getThumbnailViewDeprecated().getThumbnail(); } protected boolean isRealSnapshot() { return enableRefactorTaskThumbnail() ? mHelper.getEnabledState().isRealSnapshot() : mTaskContainer.getThumbnailViewDeprecated().isRealSnapshot(); } protected T getActionsView() { Loading @@ -151,15 +187,25 @@ public class TaskOverlayFactory implements ResourceBasedOverride { /** * Called when the current task is interactive for the user * * @deprecated TODO(b/350931107): Remove this interface once TaskOverlayFactoryGo is updated */ @Deprecated public void initOverlay(Task task, ThumbnailData thumbnail, Matrix matrix, boolean rotated) { initOverlay(task, thumbnail.getThumbnail(), matrix, rotated); } /** * Called when the current task is interactive for the user */ public void initOverlay(Task task, @Nullable Bitmap thumbnail, Matrix matrix, boolean rotated) { getActionsView().updateDisabledFlags(DISABLED_NO_THUMBNAIL, thumbnail == null); if (thumbnail != null) { getActionsView().updateDisabledFlags(DISABLED_ROTATED, rotated); boolean isAllowedByPolicy = mTaskContainer.isRealSnapshot(); getActionsView().setCallbacks(new OverlayUICallbacksImpl(isAllowedByPolicy, task)); getActionsView().setCallbacks(new OverlayUICallbacksImpl(isRealSnapshot(), task)); } } Loading @@ -183,8 +229,8 @@ public class TaskOverlayFactory implements ResourceBasedOverride { */ @SuppressLint("NewApi") protected void saveScreenshot(Task task) { if (mTaskContainer.isRealSnapshot()) { mImageApi.saveScreenshot(mTaskContainer.getThumbnail(), if (isRealSnapshot()) { mImageApi.saveScreenshot(getThumbnail(), getTaskSnapshotBounds(), getTaskSnapshotInsets(), task.key); } else { showBlockedByPolicyMessage(); Loading Loading @@ -259,7 +305,36 @@ public class TaskOverlayFactory implements ResourceBasedOverride { */ @RequiresApi(api = Build.VERSION_CODES.Q) public Insets getTaskSnapshotInsets() { return mTaskContainer.getScaledInsets(); Bitmap thumbnail = getThumbnail(); if (thumbnail == null) { return Insets.NONE; } RectF bitmapRect = new RectF( 0, 0, thumbnail.getWidth(), thumbnail.getHeight()); View snapshotView = mTaskContainer.getSnapshotView(); RectF viewRect = new RectF(0, 0, snapshotView.getMeasuredWidth(), snapshotView.getMeasuredHeight()); // The position helper matrix tells us how to transform the bitmap to fit the view, the // inverse tells us where the view would be in the bitmaps coordinates. The insets are // the difference between the bitmap bounds and the projected view bounds. Matrix boundsToBitmapSpace = new Matrix(); Matrix thumbnailMatrix = enableRefactorTaskThumbnail() ? mHelper.getEnabledState().getThumbnailMatrix() : mTaskContainer.getThumbnailViewDeprecated().getThumbnailMatrix(); thumbnailMatrix.invert(boundsToBitmapSpace); RectF boundsInBitmapSpace = new RectF(); boundsToBitmapSpace.mapRect(boundsInBitmapSpace, viewRect); RecentsViewContainer container = RecentsViewContainer.containerFromContext( getTaskView().getContext()); int bottomInset = container.getDeviceProfile().isTablet ? Math.round(bitmapRect.bottom - boundsInBitmapSpace.bottom) : 0; return Insets.of(0, 0, 0, bottomInset); } /** Loading quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewData.kt +7 −0 Original line number Diff line number Diff line Loading @@ -24,4 +24,11 @@ class RecentsViewData { // This is typically a View concern but it is used to invalidate rendering in other Views val scale = MutableStateFlow(1f) // Whether the current RecentsView state supports task overlays. // TODO(b/331753115): Derive from RecentsView state flow once migrated to MVVM. val overlayEnabled = MutableStateFlow(false) // The settled set of visible taskIds that is updated after RecentsView scroll settles. val settledFullyVisibleTaskIds = MutableStateFlow(emptySet<Int>()) } quickstep/src/com/android/quickstep/task/thumbnail/TaskOverlayUiState.kt 0 → 100644 +31 −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.task.thumbnail import android.graphics.Bitmap import android.graphics.Matrix /** Ui state for [com.android.quickstep.TaskOverlayFactory.TaskOverlay] */ sealed class TaskOverlayUiState { data object Disabled : TaskOverlayUiState() data class Enabled( val isRealSnapshot: Boolean, val thumbnail: Bitmap?, val thumbnailMatrix: Matrix ) : TaskOverlayUiState() } quickstep/src/com/android/quickstep/task/util/TaskOverlayHelper.kt 0 → 100644 +90 −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.task.util import android.util.Log import com.android.quickstep.TaskOverlayFactory import com.android.quickstep.task.thumbnail.TaskOverlayUiState import com.android.quickstep.task.thumbnail.TaskOverlayUiState.Disabled import com.android.quickstep.task.thumbnail.TaskOverlayUiState.Enabled import com.android.quickstep.task.viewmodel.TaskOverlayViewModel import com.android.quickstep.views.RecentsView import com.android.quickstep.views.RecentsViewContainer import com.android.systemui.shared.recents.model.Task import kotlinx.coroutines.Job import kotlinx.coroutines.MainScope import kotlinx.coroutines.launch /** * Helper for [TaskOverlayFactory.TaskOverlay] to interact with [TaskOverlayViewModel], this helper * should merge with [TaskOverlayFactory.TaskOverlay] when it's migrated to MVVM. */ class TaskOverlayHelper(val task: Task, val overlay: TaskOverlayFactory.TaskOverlay<*>) { private lateinit var job: Job private var uiState: TaskOverlayUiState = Disabled // TODO(b/335649589): Ideally create and obtain this from DI. This ViewModel should be scoped // to [TaskView], and also shared between [TaskView] and [TaskThumbnailView] // This is using a lazy for now because the dependencies cannot be obtained without DI. private val taskOverlayViewModel by lazy { val recentsView = RecentsViewContainer.containerFromContext<RecentsViewContainer>( overlay.taskView.context ) .getOverviewPanel<RecentsView<*, *>>() TaskOverlayViewModel(task, recentsView.mRecentsViewData, recentsView.mTasksRepository) } // TODO(b/331753115): TaskOverlay should listen for state changes and react. val enabledState: Enabled get() = uiState as Enabled fun init() { // TODO(b/335396935): This should be changed to TaskView's scope. job = MainScope().launch { taskOverlayViewModel.overlayState.collect { uiState = it if (it is Enabled) { Log.d( TAG, "initOverlay - taskId: ${task.key.id}, thumbnail: ${it.thumbnail}" ) overlay.initOverlay( task, it.thumbnail, it.thumbnailMatrix, /* rotated= */ false ) } else { Log.d(TAG, "reset - taskId: ${task.key.id}") overlay.reset() } } } } fun destroy() { job.cancel() uiState = Disabled overlay.reset() } companion object { private const val TAG = "TaskOverlayHelper" } } quickstep/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModel.kt 0 → 100644 +59 −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.task.viewmodel import android.graphics.Matrix import com.android.quickstep.recents.data.RecentTasksRepository import com.android.quickstep.recents.viewmodel.RecentsViewData import com.android.quickstep.task.thumbnail.TaskOverlayUiState.Disabled 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.distinctUntilChangedBy import kotlinx.coroutines.flow.map /** View model for TaskOverlay */ class TaskOverlayViewModel( task: Task, recentsViewData: RecentsViewData, tasksRepository: RecentTasksRepository, ) { val overlayState = combine( recentsViewData.overlayEnabled, recentsViewData.settledFullyVisibleTaskIds.map { it.contains(task.key.id) }, tasksRepository .getTaskDataById(task.key.id) .map { it?.thumbnail } .distinctUntilChangedBy { it?.snapshotId } ) { isOverlayEnabled, isFullyVisible, thumbnailData -> if (isOverlayEnabled && isFullyVisible) { Enabled( isRealSnapshot = (thumbnailData?.isRealSnapshot ?: false) && !task.isLocked, thumbnailData?.thumbnail, // TODO(b/343101424): Use PreviewPositionHelper, listen from a common source // with // TaskThumbnailView. Matrix.IDENTITY_MATRIX ) } else { Disabled } } .distinctUntilChanged() } Loading
quickstep/src/com/android/quickstep/TaskOverlayFactory.java +82 −7 Original line number Diff line number Diff line Loading @@ -16,18 +16,22 @@ package com.android.quickstep; import static com.android.launcher3.Flags.enableRefactorTaskThumbnail; import static com.android.quickstep.views.OverviewActionsView.DISABLED_NO_THUMBNAIL; import static com.android.quickstep.views.OverviewActionsView.DISABLED_ROTATED; import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Insets; import android.graphics.Matrix; import android.graphics.Rect; import android.graphics.RectF; import android.os.Build; import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.android.launcher3.BaseActivity; Loading @@ -38,6 +42,7 @@ import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.util.ResourceBasedOverride; import com.android.launcher3.views.ActivityContext; import com.android.launcher3.views.Snackbar; import com.android.quickstep.task.util.TaskOverlayHelper; import com.android.quickstep.util.RecentsOrientedState; import com.android.quickstep.views.DesktopTaskView; import com.android.quickstep.views.GroupedTaskView; Loading Loading @@ -128,12 +133,43 @@ public class TaskOverlayFactory implements ResourceBasedOverride { private T mActionsView; protected ImageActionsApi mImageApi; protected TaskOverlayHelper mHelper; protected TaskOverlay(TaskContainer taskContainer) { mApplicationContext = taskContainer.getTaskView().getContext().getApplicationContext(); mTaskContainer = taskContainer; mImageApi = new ImageActionsApi( mApplicationContext, mTaskContainer::getThumbnail); if (enableRefactorTaskThumbnail()) { mHelper = new TaskOverlayHelper(mTaskContainer.getTask(), this); } mImageApi = new ImageActionsApi(mApplicationContext, this::getThumbnail); } /** * Initialize the overlay when a Task is bound to the TaskView. */ public void init() { if (enableRefactorTaskThumbnail()) { mHelper.init(); } } /** * Destroy the overlay when the TaskView is recycled. */ public void destroy() { if (enableRefactorTaskThumbnail()) { mHelper.destroy(); } } protected @Nullable Bitmap getThumbnail() { return enableRefactorTaskThumbnail() ? mHelper.getEnabledState().getThumbnail() : mTaskContainer.getThumbnailViewDeprecated().getThumbnail(); } protected boolean isRealSnapshot() { return enableRefactorTaskThumbnail() ? mHelper.getEnabledState().isRealSnapshot() : mTaskContainer.getThumbnailViewDeprecated().isRealSnapshot(); } protected T getActionsView() { Loading @@ -151,15 +187,25 @@ public class TaskOverlayFactory implements ResourceBasedOverride { /** * Called when the current task is interactive for the user * * @deprecated TODO(b/350931107): Remove this interface once TaskOverlayFactoryGo is updated */ @Deprecated public void initOverlay(Task task, ThumbnailData thumbnail, Matrix matrix, boolean rotated) { initOverlay(task, thumbnail.getThumbnail(), matrix, rotated); } /** * Called when the current task is interactive for the user */ public void initOverlay(Task task, @Nullable Bitmap thumbnail, Matrix matrix, boolean rotated) { getActionsView().updateDisabledFlags(DISABLED_NO_THUMBNAIL, thumbnail == null); if (thumbnail != null) { getActionsView().updateDisabledFlags(DISABLED_ROTATED, rotated); boolean isAllowedByPolicy = mTaskContainer.isRealSnapshot(); getActionsView().setCallbacks(new OverlayUICallbacksImpl(isAllowedByPolicy, task)); getActionsView().setCallbacks(new OverlayUICallbacksImpl(isRealSnapshot(), task)); } } Loading @@ -183,8 +229,8 @@ public class TaskOverlayFactory implements ResourceBasedOverride { */ @SuppressLint("NewApi") protected void saveScreenshot(Task task) { if (mTaskContainer.isRealSnapshot()) { mImageApi.saveScreenshot(mTaskContainer.getThumbnail(), if (isRealSnapshot()) { mImageApi.saveScreenshot(getThumbnail(), getTaskSnapshotBounds(), getTaskSnapshotInsets(), task.key); } else { showBlockedByPolicyMessage(); Loading Loading @@ -259,7 +305,36 @@ public class TaskOverlayFactory implements ResourceBasedOverride { */ @RequiresApi(api = Build.VERSION_CODES.Q) public Insets getTaskSnapshotInsets() { return mTaskContainer.getScaledInsets(); Bitmap thumbnail = getThumbnail(); if (thumbnail == null) { return Insets.NONE; } RectF bitmapRect = new RectF( 0, 0, thumbnail.getWidth(), thumbnail.getHeight()); View snapshotView = mTaskContainer.getSnapshotView(); RectF viewRect = new RectF(0, 0, snapshotView.getMeasuredWidth(), snapshotView.getMeasuredHeight()); // The position helper matrix tells us how to transform the bitmap to fit the view, the // inverse tells us where the view would be in the bitmaps coordinates. The insets are // the difference between the bitmap bounds and the projected view bounds. Matrix boundsToBitmapSpace = new Matrix(); Matrix thumbnailMatrix = enableRefactorTaskThumbnail() ? mHelper.getEnabledState().getThumbnailMatrix() : mTaskContainer.getThumbnailViewDeprecated().getThumbnailMatrix(); thumbnailMatrix.invert(boundsToBitmapSpace); RectF boundsInBitmapSpace = new RectF(); boundsToBitmapSpace.mapRect(boundsInBitmapSpace, viewRect); RecentsViewContainer container = RecentsViewContainer.containerFromContext( getTaskView().getContext()); int bottomInset = container.getDeviceProfile().isTablet ? Math.round(bitmapRect.bottom - boundsInBitmapSpace.bottom) : 0; return Insets.of(0, 0, 0, bottomInset); } /** Loading
quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewData.kt +7 −0 Original line number Diff line number Diff line Loading @@ -24,4 +24,11 @@ class RecentsViewData { // This is typically a View concern but it is used to invalidate rendering in other Views val scale = MutableStateFlow(1f) // Whether the current RecentsView state supports task overlays. // TODO(b/331753115): Derive from RecentsView state flow once migrated to MVVM. val overlayEnabled = MutableStateFlow(false) // The settled set of visible taskIds that is updated after RecentsView scroll settles. val settledFullyVisibleTaskIds = MutableStateFlow(emptySet<Int>()) }
quickstep/src/com/android/quickstep/task/thumbnail/TaskOverlayUiState.kt 0 → 100644 +31 −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.task.thumbnail import android.graphics.Bitmap import android.graphics.Matrix /** Ui state for [com.android.quickstep.TaskOverlayFactory.TaskOverlay] */ sealed class TaskOverlayUiState { data object Disabled : TaskOverlayUiState() data class Enabled( val isRealSnapshot: Boolean, val thumbnail: Bitmap?, val thumbnailMatrix: Matrix ) : TaskOverlayUiState() }
quickstep/src/com/android/quickstep/task/util/TaskOverlayHelper.kt 0 → 100644 +90 −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.task.util import android.util.Log import com.android.quickstep.TaskOverlayFactory import com.android.quickstep.task.thumbnail.TaskOverlayUiState import com.android.quickstep.task.thumbnail.TaskOverlayUiState.Disabled import com.android.quickstep.task.thumbnail.TaskOverlayUiState.Enabled import com.android.quickstep.task.viewmodel.TaskOverlayViewModel import com.android.quickstep.views.RecentsView import com.android.quickstep.views.RecentsViewContainer import com.android.systemui.shared.recents.model.Task import kotlinx.coroutines.Job import kotlinx.coroutines.MainScope import kotlinx.coroutines.launch /** * Helper for [TaskOverlayFactory.TaskOverlay] to interact with [TaskOverlayViewModel], this helper * should merge with [TaskOverlayFactory.TaskOverlay] when it's migrated to MVVM. */ class TaskOverlayHelper(val task: Task, val overlay: TaskOverlayFactory.TaskOverlay<*>) { private lateinit var job: Job private var uiState: TaskOverlayUiState = Disabled // TODO(b/335649589): Ideally create and obtain this from DI. This ViewModel should be scoped // to [TaskView], and also shared between [TaskView] and [TaskThumbnailView] // This is using a lazy for now because the dependencies cannot be obtained without DI. private val taskOverlayViewModel by lazy { val recentsView = RecentsViewContainer.containerFromContext<RecentsViewContainer>( overlay.taskView.context ) .getOverviewPanel<RecentsView<*, *>>() TaskOverlayViewModel(task, recentsView.mRecentsViewData, recentsView.mTasksRepository) } // TODO(b/331753115): TaskOverlay should listen for state changes and react. val enabledState: Enabled get() = uiState as Enabled fun init() { // TODO(b/335396935): This should be changed to TaskView's scope. job = MainScope().launch { taskOverlayViewModel.overlayState.collect { uiState = it if (it is Enabled) { Log.d( TAG, "initOverlay - taskId: ${task.key.id}, thumbnail: ${it.thumbnail}" ) overlay.initOverlay( task, it.thumbnail, it.thumbnailMatrix, /* rotated= */ false ) } else { Log.d(TAG, "reset - taskId: ${task.key.id}") overlay.reset() } } } } fun destroy() { job.cancel() uiState = Disabled overlay.reset() } companion object { private const val TAG = "TaskOverlayHelper" } }
quickstep/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModel.kt 0 → 100644 +59 −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.task.viewmodel import android.graphics.Matrix import com.android.quickstep.recents.data.RecentTasksRepository import com.android.quickstep.recents.viewmodel.RecentsViewData import com.android.quickstep.task.thumbnail.TaskOverlayUiState.Disabled 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.distinctUntilChangedBy import kotlinx.coroutines.flow.map /** View model for TaskOverlay */ class TaskOverlayViewModel( task: Task, recentsViewData: RecentsViewData, tasksRepository: RecentTasksRepository, ) { val overlayState = combine( recentsViewData.overlayEnabled, recentsViewData.settledFullyVisibleTaskIds.map { it.contains(task.key.id) }, tasksRepository .getTaskDataById(task.key.id) .map { it?.thumbnail } .distinctUntilChangedBy { it?.snapshotId } ) { isOverlayEnabled, isFullyVisible, thumbnailData -> if (isOverlayEnabled && isFullyVisible) { Enabled( isRealSnapshot = (thumbnailData?.isRealSnapshot ?: false) && !task.isLocked, thumbnailData?.thumbnail, // TODO(b/343101424): Use PreviewPositionHelper, listen from a common source // with // TaskThumbnailView. Matrix.IDENTITY_MATRIX ) } else { Disabled } } .distinctUntilChanged() }