From 52aada013d6977c9d6cd43b590a1816c62e5a979 Mon Sep 17 00:00:00 2001 From: Tony Date: Thu, 9 May 2019 18:03:42 -0500 Subject: [PATCH] Fix fullscreen and landscape task insets Previously, we were incorrectly using the thumbnail insets, which were insufficient for fullscreen tasks, and ignoring rotated tasks entirely. Now we check what part of the thumbnail is actually clipped (e.g. based on the scale we apply to get it to fit in the same width as all the other tasks), and fill that out when setting the fullscreen progress on each task. Test: - Quick switch (from home and from apps) to: - "Normal" apps such as Calculator in portrait and landscape - Immersive portrait apps such as Elder Scrolls: Blades - Immersive landscape apps such as Stardew Valley - Swipe up from an app with the above cases in the adjacent task, ensure the thumbnail gets clipped from fullscreen to its final position in recents - Do the above tests from landscape launcher as well Bug: 130020567 Change-Id: I6f6b7f0ee134d464b3704990db9e1d3a01e6de0b --- .../uioverrides/states/QuickSwitchState.java | 16 +--- .../QuickSwitchTouchController.java | 2 +- .../LauncherActivityControllerHelper.java | 37 +++------ .../quickstep/util/ClipAnimationHelper.java | 17 ++++ .../quickstep/views/TaskThumbnailView.java | 77 ++++++++++------- .../com/android/quickstep/views/TaskView.java | 82 +++++++++++++------ 6 files changed, 137 insertions(+), 94 deletions(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java index fa07e27a97..c26a1d0578 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java @@ -17,13 +17,10 @@ package com.android.launcher3.uioverrides.states; import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS; -import android.graphics.Rect; - import com.android.launcher3.Launcher; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.quickstep.util.ClipAnimationHelper; import com.android.quickstep.views.RecentsView; -import com.android.quickstep.views.TaskThumbnailView; import com.android.quickstep.views.TaskView; /** @@ -45,18 +42,9 @@ public class QuickSwitchState extends OverviewState { if (recentsView.getTaskViewCount() == 0) { return super.getOverviewScaleAndTranslation(launcher); } - // Compute scale and translation y such that the most recent task view fills the screen. - TaskThumbnailView dummyThumbnail = recentsView.getTaskViewAt(0).getThumbnail(); + TaskView dummyTask = recentsView.getTaskViewAt(0); ClipAnimationHelper clipAnimationHelper = new ClipAnimationHelper(launcher); - clipAnimationHelper.fromTaskThumbnailView(dummyThumbnail, recentsView); - Rect targetRect = new Rect(); - recentsView.getTaskSize(targetRect); - clipAnimationHelper.updateTargetRect(targetRect); - float toScale = clipAnimationHelper.getSourceRect().width() - / clipAnimationHelper.getTargetRect().width(); - float toTranslationY = clipAnimationHelper.getSourceRect().centerY() - - clipAnimationHelper.getTargetRect().centerY(); - return new ScaleAndTranslation(toScale, 0, toTranslationY); + return clipAnimationHelper.getOverviewFullscreenScaleAndTranslation(dummyTask); } @Override diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java index 3b664b79dd..a1a790c2b6 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java @@ -124,7 +124,7 @@ public class QuickSwitchTouchController extends AbstractStateChangeTouchControll @Override protected void updateProgress(float progress) { super.updateProgress(progress); - updateFullscreenProgress(progress); + updateFullscreenProgress(Utilities.boundToRange(progress, 0, 1)); } private void updateFullscreenProgress(float progress) { diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java index e932452edd..c33d25ccf6 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java @@ -301,34 +301,19 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe return; } - // Setup the clip animation helper source/target rects in the final transformed state - // of the recents view (a scale/translationY may be applied prior to this animation - // starting to line up the side pages during swipe up) - float prevRvScale = recentsView.getScaleX(); - float prevRvTransY = recentsView.getTranslationY(); - float targetRvScale = endState.getOverviewScaleAndTranslation(launcher).scale; - SCALE_PROPERTY.set(recentsView, targetRvScale); - recentsView.setTranslationY(0); ClipAnimationHelper clipHelper = new ClipAnimationHelper(launcher); - float tmpCurveScale = v.getCurveScale(); - v.setCurveScale(1f); - clipHelper.fromTaskThumbnailView(v.getThumbnail(), (RecentsView) v.getParent(), null); - v.setCurveScale(tmpCurveScale); - SCALE_PROPERTY.set(recentsView, prevRvScale); - recentsView.setTranslationY(prevRvTransY); + LauncherState.ScaleAndTranslation fromScaleAndTranslation + = clipHelper.getOverviewFullscreenScaleAndTranslation(v); + LauncherState.ScaleAndTranslation endScaleAndTranslation + = endState.getOverviewScaleAndTranslation(launcher); - if (!clipHelper.getSourceRect().isEmpty() && !clipHelper.getTargetRect().isEmpty()) { - float fromScale = clipHelper.getSourceRect().width() - / clipHelper.getTargetRect().width(); - float fromTranslationY = clipHelper.getSourceRect().centerY() - - clipHelper.getTargetRect().centerY(); - Animator scale = ObjectAnimator.ofFloat(recentsView, SCALE_PROPERTY, fromScale, 1); - Animator translateY = ObjectAnimator.ofFloat(recentsView, TRANSLATION_Y, - fromTranslationY, 0); - scale.setInterpolator(LINEAR); - translateY.setInterpolator(LINEAR); - anim.playTogether(scale, translateY); - } + Animator scale = ObjectAnimator.ofFloat(recentsView, SCALE_PROPERTY, + fromScaleAndTranslation.scale, endScaleAndTranslation.scale); + Animator translateY = ObjectAnimator.ofFloat(recentsView, TRANSLATION_Y, + fromScaleAndTranslation.translationY, endScaleAndTranslation.translationY); + scale.setInterpolator(LINEAR); + translateY.setInterpolator(LINEAR); + anim.playTogether(scale, translateY); } @Override diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java index 3109921fbb..a650113d55 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java @@ -35,12 +35,14 @@ import androidx.annotation.Nullable; import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.DeviceProfile; +import com.android.launcher3.LauncherState; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.views.BaseDragLayer; import com.android.quickstep.RecentsModel; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskThumbnailView; +import com.android.quickstep.views.TaskView; import com.android.systemui.shared.recents.ISystemUiProxy; import com.android.systemui.shared.recents.utilities.RectFEvaluator; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; @@ -280,6 +282,21 @@ public class ClipAnimationHelper { } } + /** + * Compute scale and translation y such that the specified task view fills the screen. + */ + public LauncherState.ScaleAndTranslation getOverviewFullscreenScaleAndTranslation(TaskView v) { + TaskThumbnailView thumbnailView = v.getThumbnail(); + RecentsView recentsView = v.getRecentsView(); + fromTaskThumbnailView(thumbnailView, recentsView); + Rect taskSize = new Rect(); + recentsView.getTaskSize(taskSize); + updateTargetRect(taskSize); + float scale = mSourceRect.width() / mTargetRect.width(); + float translationY = mSourceRect.centerY() - mSourceRect.top - mTargetRect.centerY(); + return new LauncherState.ScaleAndTranslation(scale, 0, translationY); + } + private void updateStackBoundsToMultiWindowTaskSize(BaseDraggingActivity activity) { ISystemUiProxy sysUiProxy = RecentsModel.INSTANCE.get(activity).getSystemUiProxy(); if (sysUiProxy != null) { diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java index 7e15d523d3..a9184ecefb 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java @@ -33,6 +33,7 @@ import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; +import android.graphics.RectF; import android.graphics.Shader; import android.util.AttributeSet; import android.util.FloatProperty; @@ -59,7 +60,7 @@ public class TaskThumbnailView extends View { private final static ColorMatrix COLOR_MATRIX = new ColorMatrix(); private final static ColorMatrix SATURATION_COLOR_MATRIX = new ColorMatrix(); - private final static Rect EMPTY_RECT = new Rect(); + private final static RectF EMPTY_RECT_F = new RectF(); public static final Property DIM_ALPHA = new FloatProperty("dimAlpha") { @@ -87,10 +88,9 @@ public class TaskThumbnailView extends View { private final Matrix mMatrix = new Matrix(); private float mClipBottom = -1; - private Rect mScaledInsets = new Rect(); - private Rect mCurrentDrawnInsets = new Rect(); - private float mCurrentDrawnCornerRadius; - private boolean mIsRotated; + // Contains the portion of the thumbnail that is clipped when fullscreen progress = 0. + private RectF mClippedInsets = new RectF(); + private TaskView.FullscreenDrawParams mFullscreenParams; private Task mTask; private ThumbnailData mThumbnailData; @@ -118,7 +118,7 @@ public class TaskThumbnailView extends View { mDimmingPaintAfterClearing.setColor(Color.BLACK); mActivity = BaseActivity.fromContext(context); mIsDarkTextTheme = Themes.getAttrBoolean(mActivity, R.attr.isWorkspaceDarkText); - setCurrentDrawnInsetsAndRadius(EMPTY_RECT, mCornerRadius); + mFullscreenParams = new TaskView.FullscreenDrawParams(mCornerRadius); } public void bind(Task task) { @@ -201,23 +201,27 @@ public class TaskThumbnailView extends View { @Override protected void onDraw(Canvas canvas) { + RectF currentDrawnInsets = mFullscreenParams.mCurrentDrawnInsets; + canvas.save(); + canvas.translate(currentDrawnInsets.left, currentDrawnInsets.top); + canvas.scale(mFullscreenParams.mScale, mFullscreenParams.mScale); // Draw the insets if we're being drawn fullscreen (we do this for quick switch). drawOnCanvas(canvas, - -mCurrentDrawnInsets.left, - -mCurrentDrawnInsets.top, - getMeasuredWidth() + mCurrentDrawnInsets.right, - getMeasuredHeight() + mCurrentDrawnInsets.bottom, - mCurrentDrawnCornerRadius); + -currentDrawnInsets.left, + -currentDrawnInsets.top, + getMeasuredWidth() + currentDrawnInsets.right, + getMeasuredHeight() + currentDrawnInsets.bottom, + mFullscreenParams.mCurrentDrawnCornerRadius); + canvas.restore(); } - public Rect getInsetsToDrawInFullscreen(boolean isMultiWindowMode) { - // Don't show insets in the wrong orientation or in multi window mode. - return mIsRotated || isMultiWindowMode ? EMPTY_RECT : mScaledInsets; + public RectF getInsetsToDrawInFullscreen(boolean isMultiWindowMode) { + // Don't show insets in multi window mode. + return isMultiWindowMode ? EMPTY_RECT_F : mClippedInsets; } - public void setCurrentDrawnInsetsAndRadius(Rect insets, float radius) { - mCurrentDrawnInsets.set(insets); - mCurrentDrawnCornerRadius = radius; + public void setFullscreenParams(TaskView.FullscreenDrawParams fullscreenParams) { + mFullscreenParams = fullscreenParams; invalidate(); } @@ -275,7 +279,7 @@ public class TaskThumbnailView extends View { } private void updateThumbnailMatrix() { - mIsRotated = false; + boolean isRotated = false; mClipBottom = -1; if (mBitmapShader != null && mThumbnailData != null) { float scale = mThumbnailData.scale; @@ -296,30 +300,28 @@ public class TaskThumbnailView extends View { final Configuration configuration = getContext().getResources().getConfiguration(); // Rotate the screenshot if not in multi-window mode - mIsRotated = FeatureFlags.OVERVIEW_USE_SCREENSHOT_ORIENTATION && + isRotated = FeatureFlags.OVERVIEW_USE_SCREENSHOT_ORIENTATION && configuration.orientation != mThumbnailData.orientation && !mActivity.isInMultiWindowMode() && mThumbnailData.windowingMode == WINDOWING_MODE_FULLSCREEN; // Scale the screenshot to always fit the width of the card. - thumbnailScale = mIsRotated + thumbnailScale = isRotated ? getMeasuredWidth() / thumbnailHeight : getMeasuredWidth() / thumbnailWidth; } - mScaledInsets.set(thumbnailInsets); - Utilities.scaleRect(mScaledInsets, thumbnailScale); - - if (mIsRotated) { + if (isRotated) { int rotationDir = profile.isVerticalBarLayout() && !profile.isSeascape() ? -1 : 1; mMatrix.setRotate(90 * rotationDir); int newLeftInset = rotationDir == 1 ? thumbnailInsets.bottom : thumbnailInsets.top; int newTopInset = rotationDir == 1 ? thumbnailInsets.left : thumbnailInsets.right; - mMatrix.postTranslate(-newLeftInset * scale, -newTopInset * scale); + mClippedInsets.offsetTo(newLeftInset * scale, newTopInset * scale); if (rotationDir == -1) { // Crop the right/bottom side of the screenshot rather than left/top float excessHeight = thumbnailWidth * thumbnailScale - getMeasuredHeight(); - mMatrix.postTranslate(0, -excessHeight); + mClippedInsets.offset(0, excessHeight); } + mMatrix.postTranslate(-mClippedInsets.left, -mClippedInsets.top); // Move the screenshot to the thumbnail window (rotation moved it out). if (rotationDir == 1) { mMatrix.postTranslate(mThumbnailData.thumbnail.getHeight(), 0); @@ -327,13 +329,28 @@ public class TaskThumbnailView extends View { mMatrix.postTranslate(0, mThumbnailData.thumbnail.getWidth()); } } else { - mMatrix.setTranslate(-mThumbnailData.insets.left * scale, - -mThumbnailData.insets.top * scale); + mClippedInsets.offsetTo(thumbnailInsets.left * scale, thumbnailInsets.top * scale); + mMatrix.setTranslate(-mClippedInsets.left, -mClippedInsets.top); } + + final float widthWithInsets; + final float heightWithInsets; + if (isRotated) { + widthWithInsets = mThumbnailData.thumbnail.getHeight() * thumbnailScale; + heightWithInsets = mThumbnailData.thumbnail.getWidth() * thumbnailScale; + } else { + widthWithInsets = mThumbnailData.thumbnail.getWidth() * thumbnailScale; + heightWithInsets = mThumbnailData.thumbnail.getHeight() * thumbnailScale; + } + mClippedInsets.left *= thumbnailScale; + mClippedInsets.top *= thumbnailScale; + mClippedInsets.right = widthWithInsets - mClippedInsets.left - getMeasuredWidth(); + mClippedInsets.bottom = heightWithInsets - mClippedInsets.top - getMeasuredHeight(); + mMatrix.postScale(thumbnailScale, thumbnailScale); mBitmapShader.setLocalMatrix(mMatrix); - float bitmapHeight = Math.max((mIsRotated ? thumbnailWidth : thumbnailHeight) + float bitmapHeight = Math.max((isRotated ? thumbnailWidth : thumbnailHeight) * thumbnailScale, 0); if (Math.round(bitmapHeight) < getMeasuredHeight()) { mClipBottom = bitmapHeight; @@ -341,7 +358,7 @@ public class TaskThumbnailView extends View { mPaint.setShader(mBitmapShader); } - if (mIsRotated) { + if (isRotated) { // The overlay doesn't really work when the screenshot is rotated, so don't add it. mOverlay.reset(); } else { diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java index 6cd46d94a1..2b86f5e109 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java @@ -32,6 +32,7 @@ import android.content.Context; import android.content.res.Resources; import android.graphics.Outline; import android.graphics.Rect; +import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Handler; @@ -166,7 +167,7 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { private float mCurveScale; private float mZoomScale; private float mFullscreenProgress; - private final Rect mCurrentDrawnInsets = new Rect(); + private final FullscreenDrawParams mCurrentFullscreenParams; private final float mCornerRadius; private final float mWindowCornerRadius; private final BaseDraggingActivity mActivity; @@ -214,7 +215,8 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { }); mCornerRadius = TaskCornerRadius.get(context); mWindowCornerRadius = QuickStepContract.getWindowCornerRadius(context.getResources()); - mOutlineProvider = new TaskOutlineProvider(getResources(), mCornerRadius); + mCurrentFullscreenParams = new FullscreenDrawParams(mCornerRadius); + mOutlineProvider = new TaskOutlineProvider(getResources(), mCurrentFullscreenParams); setOutlineProvider(mOutlineProvider); } @@ -540,26 +542,26 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { private static final class TaskOutlineProvider extends ViewOutlineProvider { private final int mMarginTop; - private final Rect mInsets = new Rect(); - private float mRadius; + private FullscreenDrawParams mFullscreenParams; - TaskOutlineProvider(Resources res, float radius) { + TaskOutlineProvider(Resources res, FullscreenDrawParams fullscreenParams) { mMarginTop = res.getDimensionPixelSize(R.dimen.task_thumbnail_top_margin); - mRadius = radius; + mFullscreenParams = fullscreenParams; } - public void setCurrentDrawnInsetsAndRadius(Rect insets, float radius) { - mInsets.set(insets); - mRadius = radius; + public void setFullscreenParams(FullscreenDrawParams params) { + mFullscreenParams = params; } @Override public void getOutline(View view, Outline outline) { - outline.setRoundRect(-mInsets.left, - mMarginTop - mInsets.top, - view.getWidth() + mInsets.right, - view.getHeight() + mInsets.bottom, - mRadius); + RectF insets = mFullscreenParams.mCurrentDrawnInsets; + float scale = mFullscreenParams.mScale; + outline.setRoundRect(0, + (int) (mMarginTop * scale), + (int) ((insets.left + view.getWidth() + insets.right) * scale), + (int) ((insets.top + view.getHeight() + insets.bottom) * scale), + mFullscreenParams.mCurrentDrawnCornerRadius); } } @@ -658,17 +660,25 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { TaskThumbnailView thumbnail = getThumbnail(); boolean isMultiWindowMode = mActivity.getDeviceProfile().isMultiWindowMode; - Rect insets = thumbnail.getInsetsToDrawInFullscreen(isMultiWindowMode); - mCurrentDrawnInsets.set((int) (insets.left * mFullscreenProgress), - (int) (insets.top * mFullscreenProgress), - (int) (insets.right * mFullscreenProgress), - (int) (insets.bottom * mFullscreenProgress)); + RectF insets = thumbnail.getInsetsToDrawInFullscreen(isMultiWindowMode); + float currentInsetsLeft = insets.left * mFullscreenProgress; + float currentInsetsRight = insets.right * mFullscreenProgress; + mCurrentFullscreenParams.setInsets(currentInsetsLeft, + insets.top * mFullscreenProgress, + currentInsetsRight, + insets.bottom * mFullscreenProgress); float fullscreenCornerRadius = isMultiWindowMode ? 0 : mWindowCornerRadius; - float cornerRadius = Utilities.mapRange(mFullscreenProgress, mCornerRadius, - fullscreenCornerRadius) / getRecentsView().getScaleX(); + mCurrentFullscreenParams.setCornerRadius(Utilities.mapRange(mFullscreenProgress, + mCornerRadius, fullscreenCornerRadius) / getRecentsView().getScaleX()); + // We scaled the thumbnail to fit the content (excluding insets) within task view width. + // Now that we are drawing left/right insets again, we need to scale down to fit them. + if (getWidth() > 0) { + mCurrentFullscreenParams.setScale(getWidth() + / (getWidth() + currentInsetsLeft + currentInsetsRight)); + } - thumbnail.setCurrentDrawnInsetsAndRadius(mCurrentDrawnInsets, cornerRadius); - mOutlineProvider.setCurrentDrawnInsetsAndRadius(mCurrentDrawnInsets, cornerRadius); + thumbnail.setFullscreenParams(mCurrentFullscreenParams); + mOutlineProvider.setFullscreenParams(mCurrentFullscreenParams); invalidateOutline(); } @@ -686,4 +696,30 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { } return mShowScreenshot; } + + /** + * We update and subsequently draw these in {@link #setFullscreenProgress(float)}. + */ + static class FullscreenDrawParams { + RectF mCurrentDrawnInsets = new RectF(); + float mCurrentDrawnCornerRadius; + /** The current scale we apply to the thumbnail to adjust for new left/right insets. */ + float mScale = 1; + + public FullscreenDrawParams(float cornerRadius) { + setCornerRadius(cornerRadius); + } + + public void setInsets(float left, float top, float right, float bottom) { + mCurrentDrawnInsets.set(left, top, right, bottom); + } + + public void setCornerRadius(float cornerRadius) { + mCurrentDrawnCornerRadius = cornerRadius; + } + + public void setScale(float scale) { + mScale = scale; + } + } }