diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig index eced590686..163fc1736a 100644 --- a/aconfig/launcher.aconfig +++ b/aconfig/launcher.aconfig @@ -230,6 +230,13 @@ flag { bug: "323886237" } +flag { + name: "enable_refactor_task_thumbnail" + namespace: "launcher" + description: "Enables rewritten version of TaskThumbnailViews in Overview" + bug: "331753115" +} + flag { name: "enable_handle_delayed_gesture_callbacks" namespace: "launcher" diff --git a/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java b/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java index 253147d96b..0eb8775211 100644 --- a/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java +++ b/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java @@ -56,7 +56,7 @@ import com.android.launcher3.views.ArrowTipView; import com.android.quickstep.util.AssistContentRequester; import com.android.quickstep.util.RecentsOrientedState; import com.android.quickstep.views.GoOverviewActionsView; -import com.android.quickstep.views.TaskThumbnailView; +import com.android.quickstep.views.TaskThumbnailViewDeprecated; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.ThumbnailData; @@ -101,7 +101,7 @@ public final class TaskOverlayFactoryGo extends TaskOverlayFactory { /** * Create a new overlay instance for the given View */ - public TaskOverlayGo createOverlay(TaskThumbnailView thumbnailView) { + public TaskOverlayGo createOverlay(TaskThumbnailViewDeprecated thumbnailView) { return new TaskOverlayGo(thumbnailView, mContentRequester); } @@ -120,7 +120,7 @@ public final class TaskOverlayFactoryGo extends TaskOverlayFactory { private OverlayDialogGo mDialog; private ArrowTipView mArrowTipView; - private TaskOverlayGo(TaskThumbnailView taskThumbnailView, + private TaskOverlayGo(TaskThumbnailViewDeprecated taskThumbnailView, AssistContentRequester assistContentRequester) { super(taskThumbnailView); mFactoryContentRequester = assistContentRequester; diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml index 9d599c9faf..9f648a7e69 100644 --- a/quickstep/res/layout/task.xml +++ b/quickstep/res/layout/task.xml @@ -28,7 +28,7 @@ launcher:focusBorderColor="?androidprv:attr/materialColorOutline" launcher:hoverBorderColor="?androidprv:attr/materialColorPrimary"> - diff --git a/quickstep/res/layout/task_desktop.xml b/quickstep/res/layout/task_desktop.xml index 3cafcfd027..36d7f86f25 100644 --- a/quickstep/res/layout/task_desktop.xml +++ b/quickstep/res/layout/task_desktop.xml @@ -42,7 +42,7 @@ views that do not inherint from TaskView only or create a generic TaskView that have N number of tasks. --> - - - diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java index 18b8e3e0f5..0d9564d4f8 100644 --- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java +++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java @@ -46,7 +46,7 @@ import com.android.quickstep.views.GroupedTaskView; import com.android.quickstep.views.OverviewActionsView; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.RecentsViewContainer; -import com.android.quickstep.views.TaskThumbnailView; +import com.android.quickstep.views.TaskThumbnailViewDeprecated; import com.android.quickstep.views.TaskView; import com.android.quickstep.views.TaskView.TaskIdAttributeContainer; import com.android.systemui.shared.recents.model.Task; @@ -107,7 +107,7 @@ public class TaskOverlayFactory implements ResourceBasedOverride { return shortcuts; } - public TaskOverlay createOverlay(TaskThumbnailView thumbnailView) { + public TaskOverlay createOverlay(TaskThumbnailViewDeprecated thumbnailView) { return new TaskOverlay(thumbnailView); } @@ -149,14 +149,14 @@ public class TaskOverlayFactory implements ResourceBasedOverride { public static class TaskOverlay { protected final Context mApplicationContext; - protected final TaskThumbnailView mThumbnailView; + protected final TaskThumbnailViewDeprecated mThumbnailView; private T mActionsView; protected ImageActionsApi mImageApi; - protected TaskOverlay(TaskThumbnailView taskThumbnailView) { - mApplicationContext = taskThumbnailView.getContext().getApplicationContext(); - mThumbnailView = taskThumbnailView; + protected TaskOverlay(TaskThumbnailViewDeprecated taskThumbnailViewDeprecated) { + mApplicationContext = taskThumbnailViewDeprecated.getContext().getApplicationContext(); + mThumbnailView = taskThumbnailViewDeprecated; mImageApi = new ImageActionsApi( mApplicationContext, mThumbnailView::getThumbnail); } @@ -169,7 +169,7 @@ public class TaskOverlayFactory implements ResourceBasedOverride { return mActionsView; } - public TaskThumbnailView getThumbnailView() { + public TaskThumbnailViewDeprecated getThumbnailView() { return mThumbnailView; } diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java index 9d10ac146c..94667a4aac 100644 --- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java +++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java @@ -53,7 +53,7 @@ import com.android.quickstep.orientation.RecentsPagedOrientationHandler; import com.android.quickstep.views.GroupedTaskView; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.RecentsViewContainer; -import com.android.quickstep.views.TaskThumbnailView; +import com.android.quickstep.views.TaskThumbnailViewDeprecated; import com.android.quickstep.views.TaskView; import com.android.quickstep.views.TaskView.TaskIdAttributeContainer; import com.android.systemui.shared.recents.model.Task; @@ -158,7 +158,7 @@ public interface TaskShortcutFactory { private Handler mHandler; private final RecentsView mRecentsView; - private final TaskThumbnailView mThumbnailView; + private final TaskThumbnailViewDeprecated mThumbnailView; private final TaskView mTaskView; private final LauncherEvent mLauncherEvent; diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java index 450e9601ed..d89d399af9 100644 --- a/quickstep/src/com/android/quickstep/TaskViewUtils.java +++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java @@ -80,7 +80,7 @@ import com.android.quickstep.util.TransformParams; import com.android.quickstep.views.DesktopTaskView; import com.android.quickstep.views.GroupedTaskView; import com.android.quickstep.views.RecentsView; -import com.android.quickstep.views.TaskThumbnailView; +import com.android.quickstep.views.TaskThumbnailViewDeprecated; import com.android.quickstep.views.TaskView; import com.android.systemui.animation.RemoteAnimationTargetCompat; import com.android.systemui.shared.recents.model.Task; @@ -334,7 +334,7 @@ public final class TaskViewUtils { // During animation we apply transformation on the thumbnailView (and not the rootView) // to follow the TaskViewSimulator. So the final matrix applied on the thumbnailView is: // Mt K(0)` K(t) Mt` - TaskThumbnailView[] thumbnails = v.getThumbnails(); + TaskThumbnailViewDeprecated[] thumbnails = v.getThumbnails(); // In case simulator copies and thumbnail size do no match, ensure we get the lesser. // This ensures we do not create arrays with empty elements or attempt to references @@ -344,7 +344,7 @@ public final class TaskViewUtils { Matrix[] mt = new Matrix[matrixSize]; Matrix[] mti = new Matrix[matrixSize]; for (int i = 0; i < matrixSize; i++) { - TaskThumbnailView ttv = thumbnails[i]; + TaskThumbnailViewDeprecated ttv = thumbnails[i]; RectF localBounds = new RectF(0, 0, ttv.getWidth(), ttv.getHeight()); float[] tvBoundsMapped = new float[]{0, 0, ttv.getWidth(), ttv.getHeight()}; getDescendantCoordRelativeToAncestor(ttv, ttv.getRootView(), tvBoundsMapped, false); @@ -391,7 +391,7 @@ public final class TaskViewUtils { out.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - for (TaskThumbnailView ttv : thumbnails) { + for (TaskThumbnailViewDeprecated ttv : thumbnails) { ttv.setAnimationMatrix(null); } } diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt new file mode 100644 index 0000000000..0843ae3d33 --- /dev/null +++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.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 com.android.systemui.shared.recents.model.Task + +sealed class TaskThumbnailUiState { + data object Uninitialized : TaskThumbnailUiState() + data object LiveTile : TaskThumbnailUiState() +} + +data class TaskThumbnail(val task: Task, 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 new file mode 100644 index 0000000000..d51069f8e8 --- /dev/null +++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt @@ -0,0 +1,80 @@ +/* + * 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.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.PorterDuff +import android.graphics.PorterDuffXfermode +import android.util.AttributeSet +import android.view.View +import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.* +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.launch + +class TaskThumbnailView : View { + // TODO(b/335649589): Ideally create and obtain this from DI. This ViewModel should be scoped + // to [TaskView], and also shared between [TaskView] and [TaskThumbnailView] + val viewModel = TaskThumbnailViewModel() + + private var uiState: TaskThumbnailUiState = Uninitialized + + constructor(context: Context?) : super(context) + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) + constructor( + context: Context?, + attrs: AttributeSet?, + defStyleAttr: Int, + ) : super(context, attrs, defStyleAttr) + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + // TODO(b/335396935) replace MainScope with shorter lifecycle. + MainScope().launch { + viewModel.uiState.collect { viewModelUiState -> + uiState = viewModelUiState + invalidate() + } + } + } + + override fun onDraw(canvas: Canvas) { + when (uiState) { + is Uninitialized -> {} + is LiveTile -> drawTransparentUiState(canvas) + } + } + + private fun drawTransparentUiState(canvas: Canvas) { + canvas.drawRoundRect( + 0f, + 0f, + measuredWidth.toFloat(), + measuredHeight.toFloat(), + // TODO(b/334826840) add rounded corners + 0f, + 0f, + CLEAR_PAINT + ) + } + + companion object { + private val CLEAR_PAINT = + Paint().apply { xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) } + } +} diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModel.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModel.kt new file mode 100644 index 0000000000..9925873c7d --- /dev/null +++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModel.kt @@ -0,0 +1,35 @@ +/* + * 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 +import kotlinx.coroutines.flow.StateFlow + +class TaskThumbnailViewModel { + private val _uiState: MutableStateFlow = + MutableStateFlow(TaskThumbnailUiState.Uninitialized) + val uiState: StateFlow = _uiState + + fun bind(task: TaskThumbnail) { + _uiState.value = + if (task.isRunning) { + TaskThumbnailUiState.LiveTile + } else { + TaskThumbnailUiState.Uninitialized + } + } +} diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt index 021c455624..f430d79ce2 100644 --- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt +++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt @@ -65,7 +65,7 @@ import com.android.quickstep.views.IconAppChipView import com.android.quickstep.views.RecentsView import com.android.quickstep.views.RecentsViewContainer import com.android.quickstep.views.SplitInstructionsView -import com.android.quickstep.views.TaskThumbnailView +import com.android.quickstep.views.TaskThumbnailViewDeprecated import com.android.quickstep.views.TaskView import com.android.quickstep.views.TaskView.TaskIdAttributeContainer import com.android.quickstep.views.TaskViewIcon @@ -160,9 +160,9 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC /** * When selecting first app from split pair, second app's thumbnail remains. This animates the * second thumbnail by expanding it to take up the full taskViewWidth/Height and overlaying it - * with [TaskThumbnailView]'s splashView. Adds animations to the provided builder. Note: The app - * that **was not** selected as the first split app should be the container that's passed - * through. + * with [TaskThumbnailViewDeprecated]'s splashView. Adds animations to the provided builder. + * Note: The app that **was not** selected as the first split app should be the container that's + * passed through. * * @param builder Adds animation to this * @param taskIdAttributeContainer container of the app that **was not** selected @@ -179,7 +179,7 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC ) { val thumbnail = taskIdAttributeContainer.thumbnailView val iconView: View = taskIdAttributeContainer.iconView.asView() - builder.add(ObjectAnimator.ofFloat(thumbnail, TaskThumbnailView.SPLASH_ALPHA, 1f)) + builder.add(ObjectAnimator.ofFloat(thumbnail, TaskThumbnailViewDeprecated.SPLASH_ALPHA, 1f)) thumbnail.setShowSplashForSplitSelection(true) // With the new `IconAppChipView`, we always want to keep the chip pinned to the // top left of the task / thumbnail. @@ -202,7 +202,7 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC builder.add( ObjectAnimator.ofFloat( thumbnail, - TaskThumbnailView.SPLIT_SELECT_TRANSLATE_X, + TaskThumbnailViewDeprecated.SPLIT_SELECT_TRANSLATE_X, centerThumbnailTranslationX ) ) @@ -224,7 +224,7 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC builder.add( ObjectAnimator.ofFloat( thumbnail, - TaskThumbnailView.SPLIT_SELECT_TRANSLATE_Y, + TaskThumbnailViewDeprecated.SPLIT_SELECT_TRANSLATE_Y, translateYResetVal ) ) @@ -252,7 +252,7 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC builder.add( ObjectAnimator.ofFloat( thumbnail, - TaskThumbnailView.SPLIT_SELECT_TRANSLATE_Y, + TaskThumbnailViewDeprecated.SPLIT_SELECT_TRANSLATE_Y, centerThumbnailTranslationY ) ) @@ -266,7 +266,11 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC // Reset other dimensions thumbnail.scaleX = 1f builder.add( - ObjectAnimator.ofFloat(thumbnail, TaskThumbnailView.SPLIT_SELECT_TRANSLATE_X, 0f) + ObjectAnimator.ofFloat( + thumbnail, + TaskThumbnailViewDeprecated.SPLIT_SELECT_TRANSLATE_X, + 0f + ) ) } } @@ -276,43 +280,59 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC * [pendingAnimation]. Assumes that animation will be the final split placeholder launch anim. * * [secondPlaceholderEndingBounds] refers to the second placeholder view that gets added on - * screen, not the logical second app. - * For landscape it's the left app and for portrait the top one. + * screen, not the logical second app. For landscape it's the left app and for portrait the top + * one. */ - fun addDividerPlaceholderViewToAnim(pendingAnimation: PendingAnimation, - container: RecentsViewContainer, - secondPlaceholderEndingBounds: Rect, - context: Context) : View { + fun addDividerPlaceholderViewToAnim( + pendingAnimation: PendingAnimation, + container: RecentsViewContainer, + secondPlaceholderEndingBounds: Rect, + context: Context + ): View { val mSplitDividerPlaceholderView = View(context) val recentsView = container.getOverviewPanel>() - val dp : com.android.launcher3.DeviceProfile = container.getDeviceProfile() + val dp: com.android.launcher3.DeviceProfile = container.getDeviceProfile() // Add it before/under the most recently added first floating taskView - val firstAddedSplitViewIndex: Int = container.getDragLayer().indexOfChild( - recentsView.splitSelectController.firstFloatingTaskView) + val firstAddedSplitViewIndex: Int = + container + .getDragLayer() + .indexOfChild(recentsView.splitSelectController.firstFloatingTaskView) container.getDragLayer().addView(mSplitDividerPlaceholderView, firstAddedSplitViewIndex) val lp = mSplitDividerPlaceholderView.layoutParams as InsettableFrameLayout.LayoutParams lp.topMargin = 0 if (dp.isLeftRightSplit) { lp.height = secondPlaceholderEndingBounds.height() - lp.width = container.asContext().resources. - getDimensionPixelSize(R.dimen.split_divider_handle_region_height) - mSplitDividerPlaceholderView.translationX = secondPlaceholderEndingBounds.right - lp.width / 2f + lp.width = + container + .asContext() + .resources + .getDimensionPixelSize(R.dimen.split_divider_handle_region_height) + mSplitDividerPlaceholderView.translationX = + secondPlaceholderEndingBounds.right - lp.width / 2f mSplitDividerPlaceholderView.translationY = 0f } else { - lp.height = container.asContext().resources + lp.height = + container + .asContext() + .resources .getDimensionPixelSize(R.dimen.split_divider_handle_region_height) lp.width = secondPlaceholderEndingBounds.width() - mSplitDividerPlaceholderView.translationY = secondPlaceholderEndingBounds.top - lp.height / 2f + mSplitDividerPlaceholderView.translationY = + secondPlaceholderEndingBounds.top - lp.height / 2f mSplitDividerPlaceholderView.translationX = 0f } mSplitDividerPlaceholderView.alpha = 0f - mSplitDividerPlaceholderView.setBackgroundColor(container.asContext().resources - .getColor(R.color.taskbar_background_dark)) + mSplitDividerPlaceholderView.setBackgroundColor( + container.asContext().resources.getColor(R.color.taskbar_background_dark) + ) val timings = AnimUtils.getDeviceSplitToConfirmTimings(dp.isTablet) - pendingAnimation.setViewAlpha(mSplitDividerPlaceholderView, 1f, - Interpolators.clampToProgress(timings.stagedRectScaleXInterpolator, 0.4f, 1f)) + pendingAnimation.setViewAlpha( + mSplitDividerPlaceholderView, + 1f, + Interpolators.clampToProgress(timings.stagedRectScaleXInterpolator, 0.4f, 1f) + ) return mSplitDividerPlaceholderView } diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java index 78b17632a3..964f531df9 100644 --- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java +++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java @@ -74,10 +74,10 @@ public class DesktopTaskView extends TaskView { @NonNull private List mTasks = new ArrayList<>(); - private final ArrayList mSnapshotViews = new ArrayList<>(); + private final ArrayList mSnapshotViews = new ArrayList<>(); - /** Maps {@code taskIds} to corresponding {@link TaskThumbnailView}s */ - private final SparseArray mSnapshotViewMap = new SparseArray<>(); + /** Maps {@code taskIds} to corresponding {@link TaskThumbnailViewDeprecated}s */ + private final SparseArray mSnapshotViewMap = new SparseArray<>(); private final ArrayList> mPendingThumbnailRequests = new ArrayList<>(); @@ -175,13 +175,14 @@ public class DesktopTaskView extends TaskView { if (mSnapshotViews.size() > mTasks.size()) { int diff = mSnapshotViews.size() - mTasks.size(); for (int i = 0; i < diff; i++) { - TaskThumbnailView snapshotView = mSnapshotViews.remove(0); + TaskThumbnailViewDeprecated snapshotView = mSnapshotViews.remove(0); removeView(snapshotView); } } else if (mSnapshotViews.size() < mTasks.size()) { int diff = mTasks.size() - mSnapshotViews.size(); for (int i = 0; i < diff; i++) { - TaskThumbnailView snapshotView = new TaskThumbnailView(getContext()); + TaskThumbnailViewDeprecated snapshotView = + new TaskThumbnailViewDeprecated(getContext()); mSnapshotViews.add(snapshotView); // Add snapshots from to position after the initial child views. addView(snapshotView, mChildCountAtInflation, @@ -191,7 +192,7 @@ public class DesktopTaskView extends TaskView { for (int i = 0; i < mTasks.size(); i++) { Task task = mTasks.get(i); - TaskThumbnailView snapshotView = mSnapshotViews.get(i); + TaskThumbnailViewDeprecated snapshotView = mSnapshotViews.get(i); snapshotView.bind(task); mSnapshotViewMap.put(task.key.id, snapshotView); } @@ -217,13 +218,13 @@ public class DesktopTaskView extends TaskView { mTaskIdAttributeContainer = new TaskIdAttributeContainer[Math.max(mTasks.size(), 2)]; for (int i = 0; i < mTasks.size(); i++) { Task task = mTasks.get(i); - TaskThumbnailView thumbnailView = mSnapshotViewMap.get(task.key.id); + TaskThumbnailViewDeprecated thumbnailView = mSnapshotViewMap.get(task.key.id); mTaskIdAttributeContainer[i] = createAttributeContainer(task, thumbnailView); } } private TaskIdAttributeContainer createAttributeContainer(Task task, - TaskThumbnailView thumbnailView) { + TaskThumbnailViewDeprecated thumbnailView) { return new TaskIdAttributeContainer(task, thumbnailView, createIconView(task), STAGE_POSITION_UNDEFINED); } @@ -250,14 +251,14 @@ public class DesktopTaskView extends TaskView { } @Override - public TaskThumbnailView getThumbnail() { + public TaskThumbnailViewDeprecated getThumbnail() { // TODO(b/249371338): returning single thumbnail. This won't work well with multiple tasks. Task task = getTask(); if (task != null) { return mSnapshotViewMap.get(task.key.id); } // Return the place holder snapshot views. Callers expect this to be non-null - return mSnapshotView; + return mTaskThumbnailViewDeprecated; } @Override @@ -277,7 +278,8 @@ public class DesktopTaskView extends TaskView { for (Task task : mTasks) { CancellableTask thumbLoadRequest = thumbnailCache.updateThumbnailInBackground(task, thumbnailData -> { - TaskThumbnailView thumbnailView = mSnapshotViewMap.get(task.key.id); + TaskThumbnailViewDeprecated thumbnailView = + mSnapshotViewMap.get(task.key.id); if (thumbnailView != null) { thumbnailView.setThumbnail(task, thumbnailData); } @@ -290,7 +292,7 @@ public class DesktopTaskView extends TaskView { } else { if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) { for (Task task : mTasks) { - TaskThumbnailView thumbnailView = mSnapshotViewMap.get(task.key.id); + TaskThumbnailViewDeprecated thumbnailView = mSnapshotViewMap.get(task.key.id); if (thumbnailView != null) { thumbnailView.setThumbnail(null, null); } @@ -306,11 +308,11 @@ public class DesktopTaskView extends TaskView { DeviceProfile deviceProfile = mContainer.getDeviceProfile(); int thumbnailTopMargin = deviceProfile.overviewTaskThumbnailTopMarginPx; - LayoutParams snapshotParams = (LayoutParams) mSnapshotView.getLayoutParams(); + LayoutParams snapshotParams = (LayoutParams) mTaskThumbnailViewDeprecated.getLayoutParams(); snapshotParams.topMargin = thumbnailTopMargin; for (int i = 0; i < mSnapshotViewMap.size(); i++) { - TaskThumbnailView thumbnailView = mSnapshotViewMap.valueAt(i); + TaskThumbnailViewDeprecated thumbnailView = mSnapshotViewMap.valueAt(i); thumbnailView.setLayoutParams(snapshotParams); } } @@ -367,11 +369,11 @@ public class DesktopTaskView extends TaskView { void refreshThumbnails(@Nullable HashMap thumbnailDatas) { // Sets new thumbnails based on the incoming data and refreshes the rest. // Create a copy of the thumbnail map, so we can track thumbnails that need refreshing. - SparseArray thumbnailsToRefresh = mSnapshotViewMap.clone(); + SparseArray thumbnailsToRefresh = mSnapshotViewMap.clone(); if (thumbnailDatas != null) { for (Task task : mTasks) { int key = task.key.id; - TaskThumbnailView thumbnailView = thumbnailsToRefresh.get(key); + TaskThumbnailViewDeprecated thumbnailView = thumbnailsToRefresh.get(key); ThumbnailData thumbnailData = thumbnailDatas.get(key); if (thumbnailView != null && thumbnailData != null) { thumbnailView.setThumbnail(task, thumbnailData); @@ -388,8 +390,9 @@ public class DesktopTaskView extends TaskView { } @Override - public TaskThumbnailView[] getThumbnails() { - TaskThumbnailView[] thumbnails = new TaskThumbnailView[mSnapshotViewMap.size()]; + public TaskThumbnailViewDeprecated[] getThumbnails() { + TaskThumbnailViewDeprecated[] thumbnails = + new TaskThumbnailViewDeprecated[mSnapshotViewMap.size()]; for (int i = 0; i < thumbnails.length; i++) { thumbnails[i] = mSnapshotViewMap.valueAt(i); } @@ -402,7 +405,7 @@ public class DesktopTaskView extends TaskView { // Clear any references to the thumbnail (it will be re-read either from the cache or the // system on next bind) for (Task task : mTasks) { - TaskThumbnailView thumbnailView = mSnapshotViewMap.get(task.key.id); + TaskThumbnailViewDeprecated thumbnailView = mSnapshotViewMap.get(task.key.id); if (thumbnailView != null) { thumbnailView.setThumbnail(task, null); } @@ -453,7 +456,7 @@ public class DesktopTaskView extends TaskView { int thumbWidth = (int) (taskSize.width() * scaleWidth); int thumbHeight = (int) (taskSize.height() * scaleHeight); - TaskThumbnailView thumbnailView = mSnapshotViewMap.get(task.key.id); + TaskThumbnailViewDeprecated thumbnailView = mSnapshotViewMap.get(task.key.id); if (thumbnailView != null) { thumbnailView.measure(MeasureSpec.makeMeasureSpec(thumbWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(thumbHeight, MeasureSpec.EXACTLY)); @@ -495,7 +498,7 @@ public class DesktopTaskView extends TaskView { mBackgroundView.setVisibility(VISIBLE); } for (int i = 0; i < mSnapshotViewMap.size(); i++) { - TaskThumbnailView thumbnailView = mSnapshotViewMap.valueAt(i); + TaskThumbnailViewDeprecated thumbnailView = mSnapshotViewMap.valueAt(i); thumbnailView.getTaskOverlay().setFullscreenProgress(progress); } updateSnapshotRadius(); diff --git a/quickstep/src/com/android/quickstep/views/FloatingTaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/FloatingTaskThumbnailView.java index d869fed37e..e5a733391e 100644 --- a/quickstep/src/com/android/quickstep/views/FloatingTaskThumbnailView.java +++ b/quickstep/src/com/android/quickstep/views/FloatingTaskThumbnailView.java @@ -31,7 +31,7 @@ import androidx.annotation.Nullable; /** * A child view of {@link com.android.quickstep.views.FloatingTaskView} to draw the thumbnail in a * rounded corner frame. While the purpose of this class sounds similar to - * {@link TaskThumbnailView}, it doesn't need a lot of complex logic in {@link TaskThumbnailView} + * {@link TaskThumbnailViewDeprecated}, it doesn't need a lot of complex logic in {@link TaskThumbnailViewDeprecated} * in relation to moving with {@link RecentsView}. */ public class FloatingTaskThumbnailView extends View { diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java index 9e1c856455..a12cf8364b 100644 --- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java +++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java @@ -60,7 +60,8 @@ public class GroupedTaskView extends TaskView { @Nullable private Task mSecondaryTask; - private TaskThumbnailView mSnapshotView2; + // TODO(b/336612373): Support new TTV for GroupedTaskView + private TaskThumbnailViewDeprecated mSnapshotView2; private TaskViewIcon mIconView2; @Nullable private CancellableTask mThumbnailLoadRequest2; @@ -68,7 +69,8 @@ public class GroupedTaskView extends TaskView { private CancellableTask mIconLoadRequest2; private final float[] mIcon2CenterCoords = new float[2]; private TransformingTouchDelegate mIcon2TouchDelegate; - @Nullable private SplitBounds mSplitBoundsConfig; + @Nullable + private SplitBounds mSplitBoundsConfig; private final DigitalWellBeingToast mDigitalWellBeingToast2; public GroupedTaskView(Context context) { @@ -91,13 +93,17 @@ public class GroupedTaskView extends TaskView { return Unit.INSTANCE; } bounds.set( - Math.min(mSnapshotView.getLeft() + Math.round(mSnapshotView.getTranslationX()), + Math.min(mTaskThumbnailViewDeprecated.getLeft() + Math.round( + mTaskThumbnailViewDeprecated.getTranslationX()), mSnapshotView2.getLeft() + Math.round(mSnapshotView2.getTranslationX())), - Math.min(mSnapshotView.getTop() + Math.round(mSnapshotView.getTranslationY()), + Math.min(mTaskThumbnailViewDeprecated.getTop() + Math.round( + mTaskThumbnailViewDeprecated.getTranslationY()), mSnapshotView2.getTop() + Math.round(mSnapshotView2.getTranslationY())), - Math.max(mSnapshotView.getRight() + Math.round(mSnapshotView.getTranslationX()), + Math.max(mTaskThumbnailViewDeprecated.getRight() + Math.round( + mTaskThumbnailViewDeprecated.getTranslationX()), mSnapshotView2.getRight() + Math.round(mSnapshotView2.getTranslationX())), - Math.max(mSnapshotView.getBottom() + Math.round(mSnapshotView.getTranslationY()), + Math.max(mTaskThumbnailViewDeprecated.getBottom() + Math.round( + mTaskThumbnailViewDeprecated.getTranslationY()), mSnapshotView2.getBottom() + Math.round(mSnapshotView2.getTranslationY()))); return Unit.INSTANCE; } @@ -130,7 +136,7 @@ public class GroupedTaskView extends TaskView { if (mSplitBoundsConfig == null) { return; } - mSnapshotView.getPreviewPositionHelper().setSplitBounds( + mTaskThumbnailViewDeprecated.getPreviewPositionHelper().setSplitBounds( convertLauncherSplitBoundsToShell(splitBoundsConfig), PreviewPositionHelper.STAGE_POSITION_TOP_OR_LEFT); mSnapshotView2.getPreviewPositionHelper().setSplitBounds( @@ -304,8 +310,8 @@ public class GroupedTaskView extends TaskView { } @Override - public TaskThumbnailView[] getThumbnails() { - return new TaskThumbnailView[]{mSnapshotView, mSnapshotView2}; + public TaskThumbnailViewDeprecated[] getThumbnails() { + return new TaskThumbnailViewDeprecated[]{mTaskThumbnailViewDeprecated, mSnapshotView2}; } @Override @@ -349,20 +355,24 @@ public class GroupedTaskView extends TaskView { int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); setMeasuredDimension(widthSize, heightSize); - if (mSplitBoundsConfig == null || mSnapshotView == null || mSnapshotView2 == null) { + if (mSplitBoundsConfig == null || mTaskThumbnailViewDeprecated == null + || mSnapshotView2 == null) { return; } int initSplitTaskId = getThisTaskCurrentlyInSplitSelection(); if (initSplitTaskId == INVALID_TASK_ID) { - getPagedOrientationHandler().measureGroupedTaskViewThumbnailBounds(mSnapshotView, + getPagedOrientationHandler().measureGroupedTaskViewThumbnailBounds( + mTaskThumbnailViewDeprecated, mSnapshotView2, widthSize, heightSize, mSplitBoundsConfig, mContainer.getDeviceProfile(), getLayoutDirection() == LAYOUT_DIRECTION_RTL); // Should we be having a separate translation step apart from the measuring above? // The following only applies to large screen for now, but for future reference // we'd want to abstract this out in PagedViewHandlers to get the primary/secondary // translation directions - mSnapshotView.applySplitSelectTranslateX(mSnapshotView.getTranslationX()); - mSnapshotView.applySplitSelectTranslateY(mSnapshotView.getTranslationY()); + mTaskThumbnailViewDeprecated.applySplitSelectTranslateX( + mTaskThumbnailViewDeprecated.getTranslationX()); + mTaskThumbnailViewDeprecated.applySplitSelectTranslateY( + mTaskThumbnailViewDeprecated.getTranslationY()); mSnapshotView2.applySplitSelectTranslateX(mSnapshotView2.getTranslationX()); mSnapshotView2.applySplitSelectTranslateY(mSnapshotView2.getTranslationY()); } else { @@ -446,8 +456,9 @@ public class GroupedTaskView extends TaskView { mSplitBoundsConfig); } else { getPagedOrientationHandler().setSplitIconParams(mIconView.asView(), mIconView2.asView(), - taskIconHeight, mSnapshotView.getMeasuredWidth(), - mSnapshotView.getMeasuredHeight(), getMeasuredHeight(), getMeasuredWidth(), + taskIconHeight, mTaskThumbnailViewDeprecated.getMeasuredWidth(), + mTaskThumbnailViewDeprecated.getMeasuredHeight(), getMeasuredHeight(), + getMeasuredWidth(), isRtl, deviceProfile, mSplitBoundsConfig); } } @@ -510,12 +521,12 @@ public class GroupedTaskView extends TaskView { @Override void setThumbnailVisibility(int visibility, int taskId) { if (visibility == VISIBLE) { - mSnapshotView.setVisibility(visibility); + mTaskThumbnailViewDeprecated.setVisibility(visibility); mDigitalWellBeingToast.setBannerVisibility(visibility); mSnapshotView2.setVisibility(visibility); mDigitalWellBeingToast2.setBannerVisibility(visibility); } else if (taskId == getTaskIds()[0]) { - mSnapshotView.setVisibility(visibility); + mTaskThumbnailViewDeprecated.setVisibility(visibility); mDigitalWellBeingToast.setBannerVisibility(visibility); } else { mSnapshotView2.setVisibility(visibility); diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index ae6f703981..599a92a1c5 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -130,6 +130,7 @@ import androidx.core.graphics.ColorUtils; import com.android.internal.jank.Cuj; import com.android.launcher3.BaseActivity.MultiWindowModeChangedListener; import com.android.launcher3.DeviceProfile; +import com.android.launcher3.Flags; import com.android.launcher3.Insettable; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.PagedView; @@ -1045,8 +1046,9 @@ public abstract class RecentsView TEMP_PARAMS = new MainThreadInitializedObject<>(FullscreenDrawParams::new); - public static final Property DIM_ALPHA = - new FloatProperty("dimAlpha") { + public static final Property DIM_ALPHA = + new FloatProperty("dimAlpha") { @Override - public void setValue(TaskThumbnailView thumbnail, float dimAlpha) { + public void setValue(TaskThumbnailViewDeprecated thumbnail, float dimAlpha) { thumbnail.setDimAlpha(dimAlpha); } @Override - public Float get(TaskThumbnailView thumbnailView) { + public Float get(TaskThumbnailViewDeprecated thumbnailView) { return thumbnailView.mDimAlpha; } }; - public static final Property SPLASH_ALPHA = - new FloatProperty("splashAlpha") { + public static final Property SPLASH_ALPHA = + new FloatProperty("splashAlpha") { @Override - public void setValue(TaskThumbnailView thumbnail, float splashAlpha) { + public void setValue(TaskThumbnailViewDeprecated thumbnail, float splashAlpha) { thumbnail.setSplashAlpha(splashAlpha); } @Override - public Float get(TaskThumbnailView thumbnailView) { + public Float get(TaskThumbnailViewDeprecated thumbnailView) { return thumbnailView.mSplashAlpha / 255f; } }; /** Use to animate thumbnail translationX while first app in split selection is initiated */ - public static final Property SPLIT_SELECT_TRANSLATE_X = - new FloatProperty("splitSelectTranslateX") { + public static final Property SPLIT_SELECT_TRANSLATE_X = + new FloatProperty("splitSelectTranslateX") { @Override - public void setValue(TaskThumbnailView thumbnail, float splitSelectTranslateX) { + public void setValue(TaskThumbnailViewDeprecated thumbnail, + float splitSelectTranslateX) { thumbnail.applySplitSelectTranslateX(splitSelectTranslateX); } @Override - public Float get(TaskThumbnailView thumbnailView) { + public Float get(TaskThumbnailViewDeprecated thumbnailView) { return thumbnailView.mSplitSelectTranslateX; } }; /** Use to animate thumbnail translationY while first app in split selection is initiated */ - public static final Property SPLIT_SELECT_TRANSLATE_Y = - new FloatProperty("splitSelectTranslateY") { + public static final Property SPLIT_SELECT_TRANSLATE_Y = + new FloatProperty("splitSelectTranslateY") { @Override - public void setValue(TaskThumbnailView thumbnail, float splitSelectTranslateY) { + public void setValue(TaskThumbnailViewDeprecated thumbnail, + float splitSelectTranslateY) { thumbnail.applySplitSelectTranslateY(splitSelectTranslateY); } @Override - public Float get(TaskThumbnailView thumbnailView) { + public Float get(TaskThumbnailViewDeprecated thumbnailView) { return thumbnailView.mSplitSelectTranslateY; } }; @@ -156,15 +161,16 @@ public class TaskThumbnailView extends View { private float mSplitSelectTranslateX; private float mSplitSelectTranslateY; - public TaskThumbnailView(Context context) { + public TaskThumbnailViewDeprecated(Context context) { this(context, null); } - public TaskThumbnailView(Context context, @Nullable AttributeSet attrs) { + public TaskThumbnailViewDeprecated(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } - public TaskThumbnailView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + public TaskThumbnailViewDeprecated(Context context, @Nullable AttributeSet attrs, + int defStyleAttr) { super(context, attrs, defStyleAttr); mPaint.setFilterBitmap(true); mBackgroundPaint.setColor(Color.WHITE); @@ -180,7 +186,6 @@ public class TaskThumbnailView extends View { /** * Updates the thumbnail to draw the provided task - * @param task */ public void bind(Task task) { getTaskOverlay().reset(); @@ -194,6 +199,7 @@ public class TaskThumbnailView extends View { /** * Updates the thumbnail. + * * @param refreshNow whether the {@code thumbnailData} will be used to redraw immediately. * In most cases, we use the {@link #setThumbnail(Task, ThumbnailData)} * version with {@code refreshNow} is true. The only exception is @@ -227,6 +233,7 @@ public class TaskThumbnailView extends View { /** * Updates the shader, paint, matrix to redraw. + * * @param shouldRefreshOverlay whether to re-initialize overlay */ private void refresh(boolean shouldRefreshOverlay) { @@ -253,7 +260,6 @@ public class TaskThumbnailView extends View { *

* If dimAlpha is 0, no dimming is applied; if dimAlpha is 1, the thumbnail will be the * extracted background color. - * */ public void setDimAlpha(float dimAlpha) { mDimAlpha = dimAlpha; @@ -286,6 +292,7 @@ public class TaskThumbnailView extends View { /** * Get the scaled insets that are being used to draw the task view. This is a subsection of * the full snapshot. + * * @return the insets in snapshot bitmap coordinates. */ @RequiresApi(api = Build.VERSION_CODES.Q) diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java index a033243399..1274f0ecd7 100644 --- a/quickstep/src/com/android/quickstep/views/TaskView.java +++ b/quickstep/src/com/android/quickstep/views/TaskView.java @@ -25,6 +25,7 @@ import static com.android.app.animation.Interpolators.LINEAR; import static com.android.launcher3.Flags.enableCursorHoverStates; import static com.android.launcher3.Flags.enableGridOnlyOverview; import static com.android.launcher3.Flags.enableOverviewIconMenu; +import static com.android.launcher3.Flags.enableRefactorTaskThumbnail; import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS; @@ -106,6 +107,8 @@ import com.android.quickstep.TaskThumbnailCache; import com.android.quickstep.TaskUtils; import com.android.quickstep.TaskViewUtils; import com.android.quickstep.orientation.RecentsPagedOrientationHandler; +import com.android.quickstep.task.thumbnail.TaskThumbnail; +import com.android.quickstep.task.thumbnail.TaskThumbnailView; import com.android.quickstep.util.ActiveGestureLog; import com.android.quickstep.util.BorderAnimator; import com.android.quickstep.util.RecentsOrientedState; @@ -127,6 +130,7 @@ import java.util.List; import java.util.function.Consumer; import java.util.stream.Stream; + /** * A task in the Recents view. */ @@ -318,13 +322,15 @@ public class TaskView extends FrameLayout implements Reusable { @Override public Float get(TaskView taskView) { - return taskView.mSnapshotView.getScaleX(); + return taskView.mTaskThumbnailViewDeprecated.getScaleX(); } }; @Nullable protected Task mTask; - protected TaskThumbnailView mSnapshotView; + @Nullable // can be null when enableRefactorTaskThumbnail() == true + protected TaskThumbnailViewDeprecated mTaskThumbnailViewDeprecated; + protected TaskThumbnailView mTaskThumbnailView; protected TaskViewIcon mIconView; protected final DigitalWellBeingToast mDigitalWellBeingToast; protected float mFullscreenProgress; @@ -463,10 +469,11 @@ public class TaskView extends FrameLayout implements Reusable { } protected Unit updateBorderBounds(@NonNull Rect bounds) { - bounds.set(mSnapshotView.getLeft() + Math.round(mSnapshotView.getTranslationX()), - mSnapshotView.getTop() + Math.round(mSnapshotView.getTranslationY()), - mSnapshotView.getRight() + Math.round(mSnapshotView.getTranslationX()), - mSnapshotView.getBottom() + Math.round(mSnapshotView.getTranslationY())); + View snapshotView = getSnapshotView(); + bounds.set(snapshotView.getLeft() + Math.round(snapshotView.getTranslationX()), + snapshotView.getTop() + Math.round(snapshotView.getTranslationY()), + snapshotView.getRight() + Math.round(snapshotView.getTranslationX()), + snapshotView.getBottom() + Math.round(snapshotView.getTranslationY())); return Unit.INSTANCE; } @@ -478,6 +485,17 @@ public class TaskView extends FrameLayout implements Reusable { return mTaskViewId; } + /** + * Updates [TaskThumbnailView] to reflect the latest [Task] state (i.e., task isRunning). + */ + public void notifyIsRunningTaskUpdated() { + // TODO(b/335649589): TaskView's VM will already have access to TaskThumbnailView's VM + // so there will be no need to access TaskThumbnailView's VM through the TaskThumbnailView + if (mTask != null) { + bindTaskThumbnailView(); + } + } + /** * Builds proto for logging */ @@ -515,7 +533,14 @@ public class TaskView extends FrameLayout implements Reusable { @Override protected void onFinishInflate() { super.onFinishInflate(); - mSnapshotView = findViewById(R.id.snapshot); + mTaskThumbnailViewDeprecated = findViewById(R.id.snapshot); + if (enableRefactorTaskThumbnail()) { + mTaskThumbnailView = new TaskThumbnailView(mContext); + mTaskThumbnailView.setLayoutParams(mTaskThumbnailViewDeprecated.getLayoutParams()); + int indexOfSnapshotView = indexOfChild(mTaskThumbnailViewDeprecated); + addView(mTaskThumbnailView, indexOfSnapshotView); + mTaskThumbnailViewDeprecated.setVisibility(View.GONE); + } ViewStub iconViewStub = findViewById(R.id.icon); if (enableOverviewIconMenu()) { iconViewStub.setLayoutResource(R.layout.icon_app_chip_view); @@ -650,12 +675,23 @@ public class TaskView extends FrameLayout implements Reusable { cancelPendingLoadTasks(); mTask = task; mTaskIdContainer[0] = mTask.key.id; - mTaskIdAttributeContainer[0] = new TaskIdAttributeContainer(task, mSnapshotView, mIconView, + mTaskIdAttributeContainer[0] = new TaskIdAttributeContainer(task, + mTaskThumbnailViewDeprecated, mIconView, STAGE_POSITION_UNDEFINED); - mSnapshotView.bind(task); + if (enableRefactorTaskThumbnail()) { + bindTaskThumbnailView(); + } else { + mTaskThumbnailViewDeprecated.bind(task); + } setOrientationState(orientedState); } + private void bindTaskThumbnailView() { + // TODO(b/335649589): TaskView's VM will already have access to TaskThumbnailView's VM + // so there will be no need to access TaskThumbnailView's VM through the TaskThumbnailView + mTaskThumbnailView.getViewModel().bind(new TaskThumbnail(mTask, isRunningTask())); + } + /** * Sets up an on-click listener and the visibility for show_windows icon on top of the task. */ @@ -745,25 +781,29 @@ public class TaskView extends FrameLayout implements Reusable { return null; } - public TaskThumbnailView getThumbnail() { - return mSnapshotView; + public TaskThumbnailViewDeprecated getThumbnail() { + return mTaskThumbnailViewDeprecated; } void refreshThumbnails(@Nullable HashMap thumbnailDatas) { + if (enableRefactorTaskThumbnail()) { + // TODO(b/334825222) add thumbnail logic + return; + } if (mTask != null && thumbnailDatas != null) { final ThumbnailData thumbnailData = thumbnailDatas.get(mTask.key.id); if (thumbnailData != null) { - mSnapshotView.setThumbnail(mTask, thumbnailData); + mTaskThumbnailViewDeprecated.setThumbnail(mTask, thumbnailData); return; } } - mSnapshotView.refresh(); + mTaskThumbnailViewDeprecated.refresh(); } /** TODO(b/197033698) Remove all usages of above method and migrate to this one */ - public TaskThumbnailView[] getThumbnails() { - return new TaskThumbnailView[]{mSnapshotView}; + public TaskThumbnailViewDeprecated[] getThumbnails() { + return new TaskThumbnailViewDeprecated[]{mTaskThumbnailViewDeprecated}; } public TaskViewIcon getIconView() { @@ -948,7 +988,10 @@ public class TaskView extends FrameLayout implements Reusable { if (isQuickswitch) { opts.setFreezeRecentTasksReordering(); } - opts.setDisableStartingWindow(mSnapshotView.shouldShowSplashView()); + // TODO(b/334826842) add splash functionality to new TTV + if (!enableRefactorTaskThumbnail()) { + opts.setDisableStartingWindow(mTaskThumbnailViewDeprecated.shouldShowSplashView()); + } Task.TaskKey key = mTask.key; UI_HELPER_EXECUTOR.execute(() -> { if (!ActivityManagerWrapper.getInstance().startActivityFromRecents(key, opts)) { @@ -1069,7 +1112,10 @@ public class TaskView extends FrameLayout implements Reusable { if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) { mThumbnailLoadRequest = thumbnailCache.updateThumbnailInBackground( mTask, thumbnail -> { - mSnapshotView.setThumbnail(mTask, thumbnail); + if (!enableRefactorTaskThumbnail()) { + // TODO(b/334825222) add thumbnail state + mTaskThumbnailViewDeprecated.setThumbnail(mTask, thumbnail); + } }); } if (needsUpdate(changes, FLAG_UPDATE_ICON)) { @@ -1087,7 +1133,10 @@ public class TaskView extends FrameLayout implements Reusable { } } else { if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) { - mSnapshotView.setThumbnail(null, null); + if (!enableRefactorTaskThumbnail()) { + // TODO(b/334825222) add thumbnail state + mTaskThumbnailViewDeprecated.setThumbnail(null, null); + } // Reset the task thumbnail reference as well (it will be fetched from the cache or // reloaded next time we need it) mTask.thumbnail = null; @@ -1195,11 +1244,15 @@ public class TaskView extends FrameLayout implements Reusable { // TODO(b/271468547), we should default to setting trasnlations only on the snapshot instead // of a hybrid of both margins and translations - LayoutParams snapshotParams = (LayoutParams) mSnapshotView.getLayoutParams(); + LayoutParams snapshotParams = (LayoutParams) getSnapshotView().getLayoutParams(); snapshotParams.topMargin = thumbnailTopMargin; - mSnapshotView.setLayoutParams(snapshotParams); + getSnapshotView().setLayoutParams(snapshotParams); - mSnapshotView.getTaskOverlay().updateOrientationState(orientationState); + // TODO(b/335606129) Investigate the usage of [TaskOverlay] in the new TaskThumbnailView. + // and if it's still necessary we should support that in the new TTV class. + if (!enableRefactorTaskThumbnail()) { + mTaskThumbnailViewDeprecated.getTaskOverlay().updateOrientationState(orientationState); + } mDigitalWellBeingToast.initialize(mTask); } @@ -1288,7 +1341,10 @@ public class TaskView extends FrameLayout implements Reusable { setAlpha(mStableAlpha); setIconScaleAndDim(1); setColorTint(0, 0); - mSnapshotView.resetViewTransforms(); + if (!enableRefactorTaskThumbnail()) { + // TODO(b/335399428) add split select functionality to new TTV + mTaskThumbnailViewDeprecated.resetViewTransforms(); + } } public void setStableAlpha(float parentAlpha) { @@ -1301,7 +1357,12 @@ public class TaskView extends FrameLayout implements Reusable { resetPersistentViewTransforms(); // Clear any references to the thumbnail (it will be re-read either from the cache or the // system on next bind) - mSnapshotView.setThumbnail(mTask, null); + // TODO(b/334825222): Implement thumbnail/snapshot for the new [TaskThumbnailView]. + if (enableRefactorTaskThumbnail()) { + notifyIsRunningTaskUpdated(); + } else { + mTaskThumbnailViewDeprecated.setThumbnail(mTask, null); + } setOverlayEnabled(false); onTaskListVisibilityChanged(false); mBorderEnabled = false; @@ -1316,10 +1377,10 @@ public class TaskView extends FrameLayout implements Reusable { super.onLayout(changed, left, top, right, bottom); if (mContainer.getDeviceProfile().isTablet) { setPivotX(getLayoutDirection() == LAYOUT_DIRECTION_RTL ? 0 : right - left); - setPivotY(mSnapshotView.getTop()); + setPivotY(getSnapshotView().getTop()); } else { setPivotX((right - left) * 0.5f); - setPivotY(mSnapshotView.getTop() + mSnapshotView.getHeight() * 0.5f); + setPivotY(getSnapshotView().getTop() + getSnapshotView().getHeight() * 0.5f); } SYSTEM_GESTURE_EXCLUSION_RECT.get(0).set(0, 0, getWidth(), getHeight()); setSystemGestureExclusionRects(SYSTEM_GESTURE_EXCLUSION_RECT); @@ -1387,11 +1448,17 @@ public class TaskView extends FrameLayout implements Reusable { } protected void applyThumbnailSplashAlpha() { - mSnapshotView.setSplashAlpha(mTaskThumbnailSplashAlpha); + if (!enableRefactorTaskThumbnail()) { + // TODO(b/334826842) add splash functionality to new TTV + mTaskThumbnailViewDeprecated.setSplashAlpha(mTaskThumbnailSplashAlpha); + } } protected void refreshTaskThumbnailSplash() { - mSnapshotView.refreshSplashView(); + if (!enableRefactorTaskThumbnail()) { + // TODO(b/334826842) add splash functionality to new TTV + mTaskThumbnailViewDeprecated.refreshSplashView(); + } } private void setSplitSelectTranslationX(float x) { @@ -1670,7 +1737,11 @@ public class TaskView extends FrameLayout implements Reusable { progress = Utilities.boundToRange(progress, 0, 1); mFullscreenProgress = progress; mIconView.setVisibility(progress < 1 ? VISIBLE : INVISIBLE); - mSnapshotView.getTaskOverlay().setFullscreenProgress(progress); + if (!enableRefactorTaskThumbnail()) { + // TODO(b/334826840) Add corner rounding to new TTV + mTaskThumbnailViewDeprecated.getTaskOverlay().setFullscreenProgress(progress); + } + RecentsView recentsView = mContainer.getOverviewPanel(); // Animate icons and DWB banners in/out, except in QuickSwitch state, when tiles are // oversized and banner would look disproportionately large. @@ -1683,7 +1754,10 @@ public class TaskView extends FrameLayout implements Reusable { protected void updateSnapshotRadius() { updateCurrentFullscreenParams(); - mSnapshotView.setFullscreenParams(mCurrentFullscreenParams); + if (!enableRefactorTaskThumbnail()) { + // TODO(b/334826840) Add corner rounding to new TTV + mTaskThumbnailViewDeprecated.setFullscreenParams(mCurrentFullscreenParams); + } } void updateCurrentFullscreenParams() { @@ -1796,7 +1870,11 @@ public class TaskView extends FrameLayout implements Reusable { } public void setOverlayEnabled(boolean overlayEnabled) { - mSnapshotView.setOverlayEnabled(overlayEnabled); + // TODO(b/335606129) Investigate the usage of [TaskOverlay] in the new TaskThumbnailView. + // and if it's still necessary we should support that in the new TTV class. + if (!enableRefactorTaskThumbnail()) { + mTaskThumbnailViewDeprecated.setOverlayEnabled(overlayEnabled); + } } public void initiateSplitSelect(SplitPositionOption splitPositionOption) { @@ -1808,7 +1886,10 @@ public class TaskView extends FrameLayout implements Reusable { * Set a color tint on the snapshot and supporting views. */ public void setColorTint(float amount, int tintColor) { - mSnapshotView.setDimAlpha(amount); + if (!enableRefactorTaskThumbnail()) { + // TODO(b/334832108) Add scrim to new TTV + mTaskThumbnailViewDeprecated.setDimAlpha(amount); + } mIconView.setIconColorTint(tintColor, amount); mDigitalWellBeingToast.setBannerColorTint(tintColor, amount); } @@ -1834,6 +1915,10 @@ public class TaskView extends FrameLayout implements Reusable { } } + private View getSnapshotView() { + return enableRefactorTaskThumbnail() ? mTaskThumbnailView : mTaskThumbnailViewDeprecated; + } + /** * We update and subsequently draw these in {@link #setFullscreenProgress(float)}. */ @@ -1878,7 +1963,7 @@ public class TaskView extends FrameLayout implements Reusable { } public class TaskIdAttributeContainer { - private final TaskThumbnailView mThumbnailView; + private final TaskThumbnailViewDeprecated mThumbnailView; private final Task mTask; private final TaskViewIcon mIconView; /** Defaults to STAGE_POSITION_UNDEFINED if in not a split screen task view */ @@ -1886,7 +1971,7 @@ public class TaskView extends FrameLayout implements Reusable { @IdRes private final int mA11yNodeId; - public TaskIdAttributeContainer(Task task, TaskThumbnailView thumbnailView, + public TaskIdAttributeContainer(Task task, TaskThumbnailViewDeprecated thumbnailView, TaskViewIcon iconView, int stagePosition) { this.mTask = task; this.mThumbnailView = thumbnailView; @@ -1896,7 +1981,7 @@ public class TaskView extends FrameLayout implements Reusable { R.id.split_bottomRight_appInfo : R.id.split_topLeft_appInfo; } - public TaskThumbnailView getThumbnailView() { + public TaskThumbnailViewDeprecated getThumbnailView() { return mThumbnailView; } 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 new file mode 100644 index 0000000000..e71192fe33 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt @@ -0,0 +1,54 @@ +/* + * 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 androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.systemui.shared.recents.model.Task +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class TaskThumbnailViewModelTest { + private val systemUnderTest = TaskThumbnailViewModel() + + @Test + fun initialStateIsUninitialized() { + assertThat(systemUnderTest.uiState.value).isEqualTo(TaskThumbnailUiState.Uninitialized) + } + + @Test + fun bindRunningTask_thenStateIs_LiveTile() { + val taskThumbnail = TaskThumbnail(Task(), isRunning = true) + systemUnderTest.bind(taskThumbnail) + + assertThat(systemUnderTest.uiState.value).isEqualTo(TaskThumbnailUiState.LiveTile) + } + + @Test + fun bindRunningTaskThenStoppedTask_thenStateIs_Uninitialized() { + // TODO(b/334825222): Change the expectation here when snapshot state is implemented + val task = Task() + val runningTask = TaskThumbnail(task, isRunning = true) + val stoppedTask = TaskThumbnail(task, isRunning = false) + systemUnderTest.bind(runningTask) + assertThat(systemUnderTest.uiState.value).isEqualTo(TaskThumbnailUiState.LiveTile) + + systemUnderTest.bind(stoppedTask) + assertThat(systemUnderTest.uiState.value).isEqualTo(TaskThumbnailUiState.Uninitialized) + } +} diff --git a/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt b/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt index 68c9bf9799..4ffb6bd3d3 100644 --- a/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt +++ b/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt @@ -31,7 +31,7 @@ import com.android.launcher3.taskbar.TaskbarActivityContext import com.android.launcher3.util.SplitConfigurationOptions import com.android.quickstep.views.GroupedTaskView import com.android.quickstep.views.IconView -import com.android.quickstep.views.TaskThumbnailView +import com.android.quickstep.views.TaskThumbnailViewDeprecated import com.android.quickstep.views.TaskView import com.android.quickstep.views.TaskView.TaskIdAttributeContainer import com.android.systemui.shared.recents.model.Task @@ -55,7 +55,7 @@ class SplitAnimationControllerTest { private val mockSplitSelectStateController: SplitSelectStateController = mock() // TaskView private val mockTaskView: TaskView = mock() - private val mockThumbnailView: TaskThumbnailView = mock() + private val mockThumbnailView: TaskThumbnailViewDeprecated = mock() private val mockBitmap: Bitmap = mock() private val mockIconView: IconView = mock() private val mockTaskViewDrawable: Drawable = mock() @@ -200,7 +200,16 @@ class SplitAnimationControllerTest { doNothing() .whenever(spySplitAnimationController) .composeRecentsSplitLaunchAnimatorLegacy( - any(), any(), any(), any(), any(), any(), any(), any(), any()) + any(), + any(), + any(), + any(), + any(), + any(), + any(), + any(), + any() + ) spySplitAnimationController.playSplitLaunchAnimation( mockGroupedTaskView, @@ -219,7 +228,16 @@ class SplitAnimationControllerTest { verify(spySplitAnimationController) .composeRecentsSplitLaunchAnimatorLegacy( - any(), any(), any(), any(), any(), any(), any(), any(), any()) + any(), + any(), + any(), + any(), + any(), + any(), + any(), + any(), + any() + ) } @Test