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; + } + } }