diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt new file mode 100644 index 0000000000..6dd67def47 --- /dev/null +++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2023 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.util + +import android.animation.ObjectAnimator +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import android.view.View +import com.android.launcher3.DeviceProfile +import com.android.launcher3.anim.PendingAnimation +import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource +import com.android.quickstep.views.TaskThumbnailView +import com.android.quickstep.views.TaskView +import com.android.quickstep.views.TaskView.TaskIdAttributeContainer +import java.util.function.Supplier + +/** + * Utils class to help run animations for initiating split screen from launcher. + * Will be expanded with future refactors. Works in conjunction with the state stored in + * [SplitSelectStateController] + */ +class SplitAnimationController(val splitSelectStateController: SplitSelectStateController) { + companion object { + // Break this out into maybe enums? Abstractions into its own classes? Tbd. + data class SplitAnimInitProps( + val originalView: View, + val originalBitmap: Bitmap?, + val iconDrawable: Drawable, + val fadeWithThumbnail: Boolean, + val isStagedTask: Boolean, + val iconView: View? + ) + } + + /** + * Returns different elements to animate for the initial split selection animation + * depending on the state of the surface from which the split was initiated + */ + fun getFirstAnimInitViews(taskViewSupplier: Supplier, + splitSelectSourceSupplier: Supplier) + : SplitAnimInitProps { + if (!splitSelectStateController.isAnimateCurrentTaskDismissal) { + // Initiating from home + val splitSelectSource = splitSelectSourceSupplier.get() + return SplitAnimInitProps(splitSelectSource.view, originalBitmap = null, + splitSelectSource.drawable, fadeWithThumbnail = false, isStagedTask = true, + iconView = null) + } else if (splitSelectStateController.isDismissingFromSplitPair) { + // Initiating split from overview, but on a split pair + val taskView = taskViewSupplier.get() + for (container : TaskIdAttributeContainer in taskView.taskIdAttributeContainers) { + if (container.task.key.id == splitSelectStateController.initialTaskId) { + return SplitAnimInitProps(container.thumbnailView, + container.thumbnailView.thumbnail, container.iconView.drawable!!, + fadeWithThumbnail = true, isStagedTask = true, + iconView = container.iconView + ) + } + } + throw IllegalStateException("Attempting to init split from existing split pair " + + "without a valid taskIdAttributeContainer") + } else { + // Initiating split from overview on fullscreen task TaskView + val taskView = taskViewSupplier.get() + return SplitAnimInitProps(taskView.thumbnail, taskView.thumbnail.thumbnail, + taskView.iconView.drawable!!, fadeWithThumbnail = true, isStagedTask = true, + taskView.iconView + ) + } + } + + /** + * 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. + * + * @param builder Adds animation to this + * @param taskIdAttributeContainer container of the app that **was not** selected + * @param isPrimaryTaskSplitting if true, task that was split would be top/left in the pair + * (opposite of that representing [taskIdAttributeContainer]) + */ + fun addInitialSplitFromPair(taskIdAttributeContainer: TaskIdAttributeContainer, + builder: PendingAnimation, deviceProfile: DeviceProfile, + taskViewWidth: Int, taskViewHeight: Int, + isPrimaryTaskSplitting: Boolean) { + val thumbnail = taskIdAttributeContainer.thumbnailView + val iconView: View = taskIdAttributeContainer.iconView + builder.add(ObjectAnimator.ofFloat(thumbnail, TaskThumbnailView.SPLASH_ALPHA, 1f)) + thumbnail.setShowSplashForSplitSelection(true) + if (deviceProfile.isLandscape) { + // Center view first so scaling happens uniformly, alternatively we can move pivotX to 0 + val centerThumbnailTranslationX: Float = (taskViewWidth - thumbnail.width) / 2f + val centerIconTranslationX: Float = (taskViewWidth - iconView.width) / 2f + val finalScaleX: Float = taskViewWidth.toFloat() / thumbnail.width + builder.add(ObjectAnimator.ofFloat(thumbnail, + TaskThumbnailView.SPLIT_SELECT_TRANSLATE_X, centerThumbnailTranslationX)) + // icons are anchored from Gravity.END, so need to use negative translation + builder.add(ObjectAnimator.ofFloat(iconView, View.TRANSLATION_X, + -centerIconTranslationX)) + builder.add(ObjectAnimator.ofFloat(thumbnail, View.SCALE_X, finalScaleX)) + + // Reset other dimensions + // TODO(b/271468547), can't set Y translate to 0, need to account for top space + thumbnail.scaleY = 1f + val translateYResetVal: Float = if (!isPrimaryTaskSplitting) 0f else + deviceProfile.overviewTaskThumbnailTopMarginPx.toFloat() + builder.add(ObjectAnimator.ofFloat(thumbnail, + TaskThumbnailView.SPLIT_SELECT_TRANSLATE_Y, + translateYResetVal)) + } else { + val thumbnailSize = taskViewHeight - deviceProfile.overviewTaskThumbnailTopMarginPx + // Center view first so scaling happens uniformly, alternatively we can move pivotY to 0 + // primary thumbnail has layout margin above it, so secondary thumbnail needs to take + // that into account. We should migrate to only using translations otherwise this + // asymmetry causes problems.. + + // Icon defaults to center | horizontal, we add additional translation for split + val centerIconTranslationX = 0f + var centerThumbnailTranslationY: Float + + // TODO(b/271468547), primary thumbnail has layout margin above it, so secondary + // thumbnail needs to take that into account. We should migrate to only using + // translations otherwise this asymmetry causes problems.. + if (isPrimaryTaskSplitting) { + centerThumbnailTranslationY = (thumbnailSize - thumbnail.height) / 2f + centerThumbnailTranslationY += deviceProfile.overviewTaskThumbnailTopMarginPx + .toFloat() + } else { + centerThumbnailTranslationY = (thumbnailSize - thumbnail.height) / 2f + } + val finalScaleY: Float = thumbnailSize.toFloat() / thumbnail.height + builder.add(ObjectAnimator.ofFloat(thumbnail, + TaskThumbnailView.SPLIT_SELECT_TRANSLATE_Y, centerThumbnailTranslationY)) + + // icons are anchored from Gravity.END, so need to use negative translation + builder.add(ObjectAnimator.ofFloat(iconView, View.TRANSLATION_X, + centerIconTranslationX)) + builder.add(ObjectAnimator.ofFloat(thumbnail, View.SCALE_Y, finalScaleY)) + + // Reset other dimensions + thumbnail.scaleX = 1f + builder.add(ObjectAnimator.ofFloat(thumbnail, + TaskThumbnailView.SPLIT_SELECT_TRANSLATE_X, 0f)) + } + } +} diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java index 5214f7c68c..1f4085f5c5 100644 --- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java +++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java @@ -82,6 +82,7 @@ public class SplitSelectStateController { private final Context mContext; private final Handler mHandler; private final RecentsModel mRecentTasksModel; + private final SplitAnimationController mSplitAnimationController; private StatsLogManager mStatsLogManager; private final SystemUiProxy mSystemUiProxy; private final StateManager mStateManager; @@ -96,6 +97,11 @@ public class SplitSelectStateController { private boolean mRecentsAnimationRunning; /** If {@code true}, animates the existing task view split placeholder view */ private boolean mAnimateCurrentTaskDismissal; + /** + * Acts as a subset of {@link #mAnimateCurrentTaskDismissal}, we can't be dismissing from a + * split pair task view without wanting to animate current task dismissal overall + */ + private boolean mDismissingFromSplitPair; @Nullable private UserHandle mUser; /** If not null, this is the TaskView we want to launch from */ @@ -116,6 +122,7 @@ public class SplitSelectStateController { mStateManager = stateManager; mDepthController = depthController; mRecentTasksModel = recentsModel; + mSplitAnimationController = new SplitAnimationController(this); } /** @@ -399,6 +406,18 @@ public class SplitSelectStateController { mAnimateCurrentTaskDismissal = animateCurrentTaskDismissal; } + public boolean isDismissingFromSplitPair() { + return mDismissingFromSplitPair; + } + + public void setDismissingFromSplitPair(boolean dismissingFromSplitPair) { + mDismissingFromSplitPair = dismissingFromSplitPair; + } + + public SplitAnimationController getSplitAnimationController() { + return mSplitAnimationController; + } + /** * Requires Shell Transitions */ @@ -506,6 +525,7 @@ public class SplitSelectStateController { mItemInfo = null; mSplitEvent = null; mAnimateCurrentTaskDismissal = false; + mDismissingFromSplitPair = false; } /** @@ -532,6 +552,10 @@ public class SplitSelectStateController { return mInitialTaskId; } + public int getSecondTaskId() { + return mSecondTaskId; + } + private boolean isSecondTaskIntentSet() { return (mSecondTaskId != INVALID_TASK_ID || mSecondTaskIntent != null); } diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java index 5414068d84..ccc2df61c9 100644 --- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java +++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java @@ -496,7 +496,7 @@ public class DesktopTaskView extends TaskView { } @Override - void setThumbnailVisibility(int visibility) { + void setThumbnailVisibility(int visibility, int taskId) { for (int i = 0; i < mSnapshotViewMap.size(); i++) { mSnapshotViewMap.valueAt(i).setVisibility(visibility); } diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java index e9498fd0f9..0e05032423 100644 --- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java +++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java @@ -1,5 +1,7 @@ package com.android.quickstep.views; +import static android.app.ActivityTaskManager.INVALID_TASK_ID; + import static com.android.launcher3.util.SplitConfigurationOptions.DEFAULT_SPLIT_RATIO; import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT; @@ -25,6 +27,7 @@ import com.android.quickstep.TaskIconCache; import com.android.quickstep.TaskThumbnailCache; import com.android.quickstep.util.CancellableTask; import com.android.quickstep.util.RecentsOrientedState; +import com.android.quickstep.util.SplitSelectStateController; import com.android.quickstep.util.TaskViewSimulator; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.ThumbnailData; @@ -267,6 +270,19 @@ public class GroupedTaskView extends TaskView { @Override protected int getLastSelectedChildTaskIndex() { + SplitSelectStateController splitSelectController = + getRecentsView().getSplitSelectController(); + if (splitSelectController.isDismissingFromSplitPair()) { + // return the container index of the task that wasn't initially selected to split with + // because that is the only remaining app that can be selected. The coordinate checks + // below aren't reliable since both of those views may be gone/transformed + int initSplitTaskId = getThisTaskCurrentlyInSplitSelection(); + if (initSplitTaskId != INVALID_TASK_ID) { + return initSplitTaskId == mTask.key.id ? 1 : 0; + } + } + + // Check which of the two apps was selected if (isCoordInView(mIconView2, mLastTouchDownPosition) || isCoordInView(mSnapshotView2, mLastTouchDownPosition)) { return 1; @@ -296,9 +312,30 @@ public class GroupedTaskView extends TaskView { if (mSplitBoundsConfig == null || mSnapshotView == null || mSnapshotView2 == null) { return; } - getPagedOrientationHandler().measureGroupedTaskViewThumbnailBounds(mSnapshotView, - mSnapshotView2, widthSize, heightSize, mSplitBoundsConfig, - mActivity.getDeviceProfile(), getLayoutDirection() == LAYOUT_DIRECTION_RTL); + int initSplitTaskId = getThisTaskCurrentlyInSplitSelection(); + if (initSplitTaskId == INVALID_TASK_ID) { + getPagedOrientationHandler().measureGroupedTaskViewThumbnailBounds(mSnapshotView, + mSnapshotView2, widthSize, heightSize, mSplitBoundsConfig, + mActivity.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()); + mSnapshotView2.applySplitSelectTranslateX(mSnapshotView2.getTranslationX()); + mSnapshotView2.applySplitSelectTranslateY(mSnapshotView2.getTranslationY()); + } else { + // Currently being split with this taskView, let the non-split selected thumbnail + // take up full thumbnail area + TaskIdAttributeContainer container = + mTaskIdAttributeContainer[initSplitTaskId == mTask.key.id ? 1 : 0]; + container.getThumbnailView().measure(widthMeasureSpec, + View.MeasureSpec.makeMeasureSpec( + heightSize - + mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx, + MeasureSpec.EXACTLY)); + } updateIconPlacement(); } @@ -379,21 +416,27 @@ public class GroupedTaskView extends TaskView { mSnapshotView2.refreshSplashView(); } + @Override + protected void resetViewTransforms() { + super.resetViewTransforms(); + mSnapshotView2.resetViewTransforms(); + } + /** - * Sets visibility for thumbnails and associated elements (DWB banners). - * IconView is unaffected. + * Sets visibility for thumbnails and associated elements (DWB banners). + * IconView is unaffected. * - * When setting INVISIBLE, sets the visibility for the last selected child task. - * When setting VISIBLE (as a reset), sets the visibility for both tasks. + * When setting INVISIBLE, sets the visibility for the last selected child task. + * When setting VISIBLE (as a reset), sets the visibility for both tasks. */ @Override - void setThumbnailVisibility(int visibility) { + void setThumbnailVisibility(int visibility, int taskId) { if (visibility == VISIBLE) { mSnapshotView.setVisibility(visibility); mDigitalWellBeingToast.setBannerVisibility(visibility); mSnapshotView2.setVisibility(visibility); mDigitalWellBeingToast2.setBannerVisibility(visibility); - } else if (getLastSelectedChildTaskIndex() == 0) { + } else if (taskId == getTaskIds()[0]) { mSnapshotView.setVisibility(visibility); mDigitalWellBeingToast.setBannerVisibility(visibility); } else { diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index 717300eef1..614ef81add 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -184,6 +184,7 @@ import com.android.quickstep.util.DesktopTask; import com.android.quickstep.util.GroupTask; import com.android.quickstep.util.LayoutUtils; import com.android.quickstep.util.RecentsOrientedState; +import com.android.quickstep.util.SplitAnimationController.Companion.SplitAnimInitProps; import com.android.quickstep.util.SplitAnimationTimings; import com.android.quickstep.util.SplitSelectStateController; import com.android.quickstep.util.SurfaceTransaction; @@ -192,6 +193,7 @@ import com.android.quickstep.util.TaskViewSimulator; import com.android.quickstep.util.TaskVisualsChangeListener; import com.android.quickstep.util.TransformParams; import com.android.quickstep.util.VibrationConstants; +import com.android.quickstep.views.TaskView.TaskIdAttributeContainer; import com.android.systemui.plugins.ResourceProvider; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.ThumbnailData; @@ -935,7 +937,7 @@ public abstract class RecentsView mSplitHiddenTaskView, () -> mSplitSelectSource); if (mSplitSelectStateController.isAnimateCurrentTaskDismissal()) { // Create the split select animation from Overview - mSplitHiddenTaskView.setThumbnailVisibility(INVISIBLE); - anim.setViewAlpha(mSplitHiddenTaskView.getIconView(), 0, clampToProgress(LINEAR, + mSplitHiddenTaskView.setThumbnailVisibility(INVISIBLE, + mSplitSelectStateController.getInitialTaskId()); + anim.setViewAlpha(splitAnimInitProps.getIconView(), 0, clampToProgress(LINEAR, timings.getIconFadeStartOffset(), timings.getIconFadeEndOffset())); - mFirstFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity, - mSplitHiddenTaskView.getThumbnail(), - mSplitHiddenTaskView.getThumbnail().getThumbnail(), - mSplitHiddenTaskView.getIconView().getDrawable(), startingTaskRect); - mFirstFloatingTaskView.setAlpha(1); - mFirstFloatingTaskView.addStagingAnimation(anim, startingTaskRect, mTempRect, - true /* fadeWithThumbnail */, true /* isStagedTask */); - } else { - // Create the split select animation from Home - mFirstFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity, - mSplitSelectSource.view, null /* thumbnail */, - mSplitSelectSource.drawable, startingTaskRect); - mFirstFloatingTaskView.setAlpha(1); - mFirstFloatingTaskView.addStagingAnimation(anim, startingTaskRect, mTempRect, - false /* fadeWithThumbnail */, true /* isStagedTask */); } + mFirstFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity, + splitAnimInitProps.getOriginalView(), + splitAnimInitProps.getOriginalBitmap(), + splitAnimInitProps.getIconDrawable(), startingTaskRect); + mFirstFloatingTaskView.setAlpha(1); + mFirstFloatingTaskView.addStagingAnimation(anim, startingTaskRect, mTempRect, + splitAnimInitProps.getFadeWithThumbnail(), splitAnimInitProps.isStagedTask()); + // Allow user to click staged app to launch into fullscreen if (ENABLE_LAUNCH_FROM_STAGED_APP.get()) { mFirstFloatingTaskView.setOnClickListener(this::animateToFullscreen); @@ -4450,7 +4449,7 @@ public abstract class RecentsView{ + thumbnail.refreshSplashView(); + mSplitHiddenTaskView.updateSnapshotRadius(); + }); + } else if (isInitiatingSplitFromTaskView) { + // Splitting from Overview for fullscreen task createTaskDismissAnimation(builder, mSplitHiddenTaskView, true, false, duration, true /* dismissingForSplitSelection*/); } else { @@ -4608,7 +4635,8 @@ public abstract class RecentsView SPLASH_ALPHA = + new FloatProperty("splashAlpha") { + @Override + public void setValue(TaskThumbnailView thumbnail, float splashAlpha) { + thumbnail.setSplashAlpha(splashAlpha); + } + + @Override + public Float get(TaskThumbnailView 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") { + @Override + public void setValue(TaskThumbnailView thumbnail, float splitSelectTranslateX) { + thumbnail.applySplitSelectTranslateX(splitSelectTranslateX); + } + + @Override + public Float get(TaskThumbnailView 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") { + @Override + public void setValue(TaskThumbnailView thumbnail, float splitSelectTranslateY) { + thumbnail.applySplitSelectTranslateY(splitSelectTranslateY); + } + + @Override + public Float get(TaskThumbnailView thumbnailView) { + return thumbnailView.mSplitSelectTranslateY; + } + }; + private final BaseActivity mActivity; @Nullable private TaskOverlay mOverlay; @@ -111,6 +152,10 @@ public class TaskThumbnailView extends View { private int mSplashAlpha = 0; private boolean mOverlayEnabled; + /** Used as a placeholder when the original thumbnail animates out to. */ + private boolean mShowSplashForSplitSelection; + private float mSplitSelectTranslateX; + private float mSplitSelectTranslateY; public TaskThumbnailView(Context context) { this(context, null); @@ -342,10 +387,17 @@ public class TaskThumbnailView extends View { // Draw splash above thumbnail to hide inconsistencies in rotation and aspect ratios. if (shouldShowSplashView()) { + float cornerRadiusX = cornerRadius; + float cornerRadiusY = cornerRadius; + if (mShowSplashForSplitSelection) { + cornerRadiusX = cornerRadius / getScaleX(); + cornerRadiusY = cornerRadius / getScaleY(); + } + // Always draw background for hiding inconsistencies, even if splash view is not yet // loaded (which can happen as task icons are loaded asynchronously in the background) - canvas.drawRoundRect(x, y, width + 1, height + 1, cornerRadius, - cornerRadius, mSplashBackgroundPaint); + canvas.drawRoundRect(x, y, width + 1, height + 1, cornerRadiusX, + cornerRadiusY, mSplashBackgroundPaint); if (mSplashView != null) { mSplashView.layout((int) x, (int) (y + 1), (int) width, (int) height - 1); mSplashView.draw(canvas); @@ -353,6 +405,31 @@ public class TaskThumbnailView extends View { } } + /** See {@link #SPLIT_SELECT_TRANSLATE_X} */ + protected void applySplitSelectTranslateX(float splitSelectTranslateX) { + mSplitSelectTranslateX = splitSelectTranslateX; + applyTranslateX(); + } + + /** See {@link #SPLIT_SELECT_TRANSLATE_Y} */ + protected void applySplitSelectTranslateY(float splitSelectTranslateY) { + mSplitSelectTranslateY = splitSelectTranslateY; + applyTranslateY(); + } + + private void applyTranslateX() { + setTranslationX(mSplitSelectTranslateX); + } + + private void applyTranslateY() { + setTranslationY(mSplitSelectTranslateY); + } + + protected void resetViewTransforms() { + mSplitSelectTranslateX = 0; + mSplitSelectTranslateY = 0; + } + public TaskView getTaskView() { return (TaskView) getParent(); } @@ -373,7 +450,12 @@ public class TaskThumbnailView extends View { */ public boolean shouldShowSplashView() { return isThumbnailAspectRatioDifferentFromThumbnailData() - || isThumbnailRotationDifferentFromTask(); + || isThumbnailRotationDifferentFromTask() + || mShowSplashForSplitSelection; + } + + public void setShowSplashForSplitSelection(boolean showSplashForSplitSelection) { + mShowSplashForSplitSelection = showSplashForSplitSelection; } protected void refreshSplashView() { @@ -396,7 +478,6 @@ public class TaskThumbnailView extends View { imageView.setScaleType(ImageView.ScaleType.MATRIX); Matrix matrix = new Matrix(); - float drawableWidth = mSplashViewDrawable.getIntrinsicWidth(); float drawableHeight = mSplashViewDrawable.getIntrinsicHeight(); float viewWidth = getMeasuredWidth(); @@ -408,12 +489,13 @@ public class TaskThumbnailView extends View { float nonGridScale = getTaskView() == null ? 1 : 1 / getTaskView().getNonGridScale(); float recentsMaxScale = getTaskView() == null || getTaskView().getRecentsView() == null ? 1 : 1 / getTaskView().getRecentsView().getMaxScaleForFullScreen(); - float scale = nonGridScale * recentsMaxScale; + float scaleX = nonGridScale * recentsMaxScale * (1 / getScaleX()); + float scaleY = nonGridScale * recentsMaxScale * (1 / getScaleY()); // Center the image in the view. matrix.setTranslate(centeredDrawableLeft, centeredDrawableTop); // Apply scale transformation after translation, pivoting around center of view. - matrix.postScale(scale, scale, viewCenterX, viewCenterY); + matrix.postScale(scaleX, scaleY, viewCenterX, viewCenterY); imageView.setImageMatrix(matrix); mSplashView = imageView; diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java index ab72f2d10e..a06e1f8225 100644 --- a/quickstep/src/com/android/quickstep/views/TaskView.java +++ b/quickstep/src/com/android/quickstep/views/TaskView.java @@ -16,6 +16,7 @@ package com.android.quickstep.views; +import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.view.Display.DEFAULT_DISPLAY; import static android.widget.Toast.LENGTH_SHORT; @@ -41,6 +42,7 @@ import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.annotation.IdRes; import android.app.ActivityOptions; +import android.app.ActivityTaskManager; import android.content.Context; import android.content.Intent; import android.graphics.Canvas; @@ -706,9 +708,12 @@ public class TaskView extends FrameLayout implements Reusable { } SplitSelectStateController splitSelectStateController = recentsView.getSplitSelectController(); - if (splitSelectStateController.isSplitSelectActive() && - splitSelectStateController.getInitialTaskId() == getTask().key.id) { - // Prevent taps on the this taskview if it's being animated into split select state + // Disable taps for split selection animation unless we have multiple tasks + boolean disableTapsForSplitSelect = + splitSelectStateController.isSplitSelectActive() + && splitSelectStateController.getInitialTaskId() == getTask().key.id + && !containsMultipleTasks(); + if (disableTapsForSplitSelect) { return false; } @@ -718,6 +723,25 @@ public class TaskView extends FrameLayout implements Reusable { return super.dispatchTouchEvent(ev); } + /** + * @return taskId that split selection was initiated with, + * {@link ActivityTaskManager#INVALID_TASK_ID} if no tasks in this TaskView are part of + * split selection + */ + protected int getThisTaskCurrentlyInSplitSelection() { + SplitSelectStateController splitSelectController = + getRecentsView().getSplitSelectController(); + int initSplitTaskId = INVALID_TASK_ID; + for (TaskIdAttributeContainer container : getTaskIdAttributeContainers()) { + int taskId = container.getTask().key.id; + if (taskId == splitSelectController.getInitialTaskId()) { + initSplitTaskId = taskId; + break; + } + } + return initSplitTaskId; + } + private void onClick(View view) { if (getTask() == null) { return; @@ -747,6 +771,8 @@ public class TaskView extends FrameLayout implements Reusable { /** * Returns the task index of the last selected child task (0 or 1). + * If we contain multiple tasks and this TaskView is used as part of split selection, the + * selected child task index will be that of the remaining task. */ protected int getLastSelectedChildTaskIndex() { return 0; @@ -1084,6 +1110,8 @@ public class TaskView extends FrameLayout implements Reusable { DeviceProfile deviceProfile = mActivity.getDeviceProfile(); int thumbnailTopMargin = deviceProfile.overviewTaskThumbnailTopMarginPx; + // 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(); snapshotParams.topMargin = thumbnailTopMargin; mSnapshotView.setLayoutParams(snapshotParams); @@ -1179,6 +1207,7 @@ public class TaskView extends FrameLayout implements Reusable { setAlpha(mStableAlpha); setIconScaleAndDim(1); setColorTint(0, 0); + mSnapshotView.resetViewTransforms(); } public void setStableAlpha(float parentAlpha) { @@ -1720,10 +1749,12 @@ public class TaskView extends FrameLayout implements Reusable { } /** - * Sets visibility for the thumbnail and associated elements (DWB banners and action chips). - * IconView is unaffected. + * Sets visibility for the thumbnail and associated elements (DWB banners and action chips). + * IconView is unaffected. + * + * @param taskId is only used when setting visibility to a non-{@link View#VISIBLE} value */ - void setThumbnailVisibility(int visibility) { + void setThumbnailVisibility(int visibility, int taskId) { for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); if (child != mIconView) { diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java index 628aa9a28d..295baa3a88 100644 --- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java +++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java @@ -635,6 +635,9 @@ public class PortraitPagedViewHandler implements PagedOrientationHandler { primarySnapshot.setTranslationX(0); } secondarySnapshot.setTranslationY(spaceAboveSnapshot); + + // Reset unused translations + primarySnapshot.setTranslationY(0); } else { int deviceHeightWithoutTaskbar = dp.availableHeightPx - dp.taskbarSize; float scale = (float) totalThumbnailHeight / deviceHeightWithoutTaskbar; @@ -669,6 +672,10 @@ public class PortraitPagedViewHandler implements PagedOrientationHandler { View.MeasureSpec.makeMeasureSpec(secondarySnapshotWidth, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(secondarySnapshotHeight, View.MeasureSpec.EXACTLY)); + primarySnapshot.setScaleX(1); + secondarySnapshot.setScaleX(1); + primarySnapshot.setScaleY(1); + secondarySnapshot.setScaleY(1); } @Override @@ -699,13 +706,13 @@ public class PortraitPagedViewHandler implements PagedOrientationHandler { : deviceProfile.getInsets().left; int fullscreenMidpointFromBottom = ((deviceProfile.widthPx - fullscreenInsetThickness) / 2); - float midpointFromBottomPct = (float) fullscreenMidpointFromBottom + float midpointFromEndPct = (float) fullscreenMidpointFromBottom / deviceProfile.widthPx; float insetPct = (float) fullscreenInsetThickness / deviceProfile.widthPx; int spaceAboveSnapshots = 0; int overviewThumbnailAreaThickness = groupedTaskViewWidth - spaceAboveSnapshots; int bottomToMidpointOffset = (int) (overviewThumbnailAreaThickness - * midpointFromBottomPct); + * midpointFromEndPct); int insetOffset = (int) (overviewThumbnailAreaThickness * insetPct); if (deviceProfile.isSeascape()) {