blob: c03aa3f44614728482cd835d59d1f2efd1d39d80 [file] [log] [blame]
/*
* Copyright (C) 2020 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 static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static com.android.launcher3.states.RotationHelper.deltaRotation;
import static com.android.launcher3.touch.PagedOrientationHandler.MATRIX_POST_TRANSLATE;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
import static com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
import static com.android.quickstep.util.RecentsOrientedState.postDisplayRotation;
import static com.android.quickstep.util.RecentsOrientedState.preDisplayRotation;
import android.animation.TimeInterpolator;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.Log;
import androidx.annotation.NonNull;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
import com.android.launcher3.util.TraceHelper;
import com.android.quickstep.AnimatedFloat;
import com.android.quickstep.BaseActivityInterface;
import com.android.quickstep.TaskAnimationManager;
import com.android.quickstep.views.TaskView.FullscreenDrawParams;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.recents.utilities.PreviewPositionHelper;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams.Builder;
/**
* A utility class which emulates the layout behavior of TaskView and RecentsView
*/
public class TaskViewSimulator implements TransformParams.BuilderProxy {
private static final String TAG = "TaskViewSimulator";
private static final boolean DEBUG = false;
private final Rect mTmpCropRect = new Rect();
private final RectF mTempRectF = new RectF();
private final float[] mTempPoint = new float[2];
private final Context mContext;
private final BaseActivityInterface mSizeStrategy;
@NonNull
private RecentsOrientedState mOrientationState;
private final boolean mIsRecentsRtl;
private final Rect mTaskRect = new Rect();
private final PointF mPivot = new PointF();
private DeviceProfile mDp;
@StagePosition
private int mStagePosition = STAGE_POSITION_UNDEFINED;
private final Matrix mMatrix = new Matrix();
private final Matrix mMatrixTmp = new Matrix();
// Thumbnail view properties
private final Rect mThumbnailPosition = new Rect();
private final ThumbnailData mThumbnailData = new ThumbnailData();
private final PreviewPositionHelper mPositionHelper = new PreviewPositionHelper();
private final Matrix mInversePositionMatrix = new Matrix();
// TaskView properties
private final FullscreenDrawParams mCurrentFullscreenParams;
public final AnimatedFloat taskPrimaryTranslation = new AnimatedFloat();
public final AnimatedFloat taskSecondaryTranslation = new AnimatedFloat();
// RecentsView properties
public final AnimatedFloat recentsViewScale = new AnimatedFloat();
public final AnimatedFloat fullScreenProgress = new AnimatedFloat();
public final AnimatedFloat recentsViewSecondaryTranslation = new AnimatedFloat();
public final AnimatedFloat recentsViewPrimaryTranslation = new AnimatedFloat();
public final AnimatedFloat recentsViewScroll = new AnimatedFloat();
// Cached calculations
private boolean mLayoutValid = false;
private int mOrientationStateId;
private SplitBounds mSplitBounds;
private Boolean mDrawsBelowRecents = null;
private boolean mIsGridTask;
private int mTaskRectTranslationX;
private int mTaskRectTranslationY;
public TaskViewSimulator(Context context, BaseActivityInterface sizeStrategy) {
mContext = context;
mSizeStrategy = sizeStrategy;
// TODO(b/187074722): Don't create this per-TaskViewSimulator
mOrientationState = TraceHelper.allowIpcs("",
() -> new RecentsOrientedState(context, sizeStrategy, i -> { }));
mOrientationState.setGestureActive(true);
mCurrentFullscreenParams = new FullscreenDrawParams(context);
mOrientationStateId = mOrientationState.getStateId();
Resources resources = context.getResources();
mIsRecentsRtl = mOrientationState.getOrientationHandler().getRecentsRtlSetting(resources);
}
/**
* Sets the device profile for the current state
*/
public void setDp(DeviceProfile dp) {
mDp = dp;
mLayoutValid = false;
mOrientationState.setDeviceProfile(dp);
}
/**
* Sets the orientation state used for this animation
*/
public void setOrientationState(@NonNull RecentsOrientedState orientationState) {
mOrientationState = orientationState;
mLayoutValid = false;
}
/**
* @see com.android.quickstep.views.RecentsView#FULLSCREEN_PROGRESS
*/
public float getFullScreenScale() {
if (mDp == null) {
return 1;
}
if (mIsGridTask) {
mSizeStrategy.calculateGridTaskSize(mContext, mDp, mTaskRect,
mOrientationState.getOrientationHandler());
} else {
mSizeStrategy.calculateTaskSize(mContext, mDp, mTaskRect);
}
Rect fullTaskSize;
if (mSplitBounds != null) {
// The task rect changes according to the staged split task sizes, but recents
// fullscreen scale and pivot remains the same since the task fits into the existing
// sized task space bounds
fullTaskSize = new Rect(mTaskRect);
mOrientationState.getOrientationHandler()
.setSplitTaskSwipeRect(mDp, mTaskRect, mSplitBounds, mStagePosition);
mTaskRect.offset(mTaskRectTranslationX, mTaskRectTranslationY);
} else {
fullTaskSize = mTaskRect;
}
fullTaskSize.offset(mTaskRectTranslationX, mTaskRectTranslationY);
return mOrientationState.getFullScreenScaleAndPivot(fullTaskSize, mDp, mPivot);
}
/**
* Sets the targets which the simulator will control
*/
public void setPreview(RemoteAnimationTargetCompat runningTarget) {
setPreviewBounds(runningTarget.startScreenSpaceBounds, runningTarget.contentInsets);
}
/**
* Sets the targets which the simulator will control specifically for targets to animate when
* in split screen
*
* @param splitInfo set to {@code null} when not in staged split mode
*/
public void setPreview(RemoteAnimationTargetCompat runningTarget, SplitBounds splitInfo) {
setPreview(runningTarget);
mSplitBounds = splitInfo;
if (mSplitBounds == null) {
mStagePosition = STAGE_POSITION_UNDEFINED;
return;
}
mStagePosition = mThumbnailPosition.equals(splitInfo.leftTopBounds) ?
STAGE_POSITION_TOP_OR_LEFT :
STAGE_POSITION_BOTTOM_OR_RIGHT;
}
/**
* Sets the targets which the simulator will control
*/
public void setPreviewBounds(Rect bounds, Rect insets) {
mThumbnailData.insets.set(insets);
// TODO: What is this?
mThumbnailData.windowingMode = WINDOWING_MODE_FULLSCREEN;
mThumbnailPosition.set(bounds);
mLayoutValid = false;
}
/**
* Updates the scroll for RecentsView
*/
public void setScroll(float scroll) {
recentsViewScroll.value = scroll;
}
public void setDrawsBelowRecents(boolean drawsBelowRecents) {
mDrawsBelowRecents = drawsBelowRecents;
}
/**
* Sets whether the task is part of overview grid and not being focused.
*/
public void setIsGridTask(boolean isGridTask) {
mIsGridTask = isGridTask;
}
/**
* Apply translations on TaskRect's starting location.
*/
public void setTaskRectTranslation(int taskRectTranslationX, int taskRectTranslationY) {
mTaskRectTranslationX = taskRectTranslationX;
mTaskRectTranslationY = taskRectTranslationY;
}
/**
* Adds animation for all the components corresponding to transition from an app to overview.
*/
public void addAppToOverviewAnim(PendingAnimation pa, TimeInterpolator interpolator) {
pa.addFloat(fullScreenProgress, AnimatedFloat.VALUE, 1, 0, interpolator);
pa.addFloat(recentsViewScale, AnimatedFloat.VALUE, getFullScreenScale(), 1, interpolator);
}
/**
* Adds animation for all the components corresponding to transition from overview to the app.
*/
public void addOverviewToAppAnim(PendingAnimation pa, TimeInterpolator interpolator) {
pa.addFloat(fullScreenProgress, AnimatedFloat.VALUE, 0, 1, interpolator);
pa.addFloat(recentsViewScale, AnimatedFloat.VALUE, 1, getFullScreenScale(), interpolator);
}
/**
* Returns the current clipped/visible window bounds in the window coordinate space
*/
public RectF getCurrentCropRect() {
// Crop rect is the inverse of thumbnail matrix
RectF insets = mCurrentFullscreenParams.mCurrentDrawnInsets;
mTempRectF.set(-insets.left, -insets.top,
mTaskRect.width() + insets.right, mTaskRect.height() + insets.bottom);
mInversePositionMatrix.mapRect(mTempRectF);
return mTempRectF;
}
/**
* Returns the current task bounds in the Launcher coordinate space.
*/
public RectF getCurrentRect() {
RectF result = getCurrentCropRect();
mMatrixTmp.set(mMatrix);
preDisplayRotation(mOrientationState.getDisplayRotation(), mDp.widthPx, mDp.heightPx,
mMatrixTmp);
mMatrixTmp.mapRect(result);
return result;
}
public RecentsOrientedState getOrientationState() {
return mOrientationState;
}
/**
* Returns the current transform applied to the window
*/
public Matrix getCurrentMatrix() {
return mMatrix;
}
/**
* Applies the rotation on the matrix to so that it maps from launcher coordinate space to
* window coordinate space.
*/
public void applyWindowToHomeRotation(Matrix matrix) {
matrix.postTranslate(mDp.windowX, mDp.windowY);
postDisplayRotation(deltaRotation(
mOrientationState.getRecentsActivityRotation(),
mOrientationState.getDisplayRotation()),
mDp.widthPx, mDp.heightPx, matrix);
}
/**
* Applies the target to the previously set parameters
*/
public void apply(TransformParams params) {
if (mDp == null || mThumbnailPosition.isEmpty()) {
return;
}
if (!mLayoutValid || mOrientationStateId != mOrientationState.getStateId()) {
mLayoutValid = true;
mOrientationStateId = mOrientationState.getStateId();
getFullScreenScale();
if (TaskAnimationManager.SHELL_TRANSITIONS_ROTATION) {
// With shell transitions, the display is rotated early so we need to actually use
// the rotation when the gesture starts
mThumbnailData.rotation = mOrientationState.getTouchRotation();
} else {
mThumbnailData.rotation = mOrientationState.getDisplayRotation();
}
// mIsRecentsRtl is the inverse of TaskView RTL.
boolean isRtlEnabled = !mIsRecentsRtl;
mPositionHelper.updateThumbnailMatrix(
mThumbnailPosition, mThumbnailData, mTaskRect.width(), mTaskRect.height(),
mDp.widthPx, mDp.taskbarSize, mDp.isTablet,
mOrientationState.getRecentsActivityRotation(), isRtlEnabled);
mPositionHelper.getMatrix().invert(mInversePositionMatrix);
if (DEBUG) {
Log.d(TAG, " taskRect: " + mTaskRect);
}
}
float fullScreenProgress = Utilities.boundToRange(this.fullScreenProgress.value, 0, 1);
mCurrentFullscreenParams.setProgress(fullScreenProgress, recentsViewScale.value,
/* taskViewScale= */1f, mTaskRect.width(), mDp, mPositionHelper);
// Apply thumbnail matrix
RectF insets = mCurrentFullscreenParams.mCurrentDrawnInsets;
float scale = mCurrentFullscreenParams.mScale;
float taskWidth = mTaskRect.width();
float taskHeight = mTaskRect.height();
mMatrix.set(mPositionHelper.getMatrix());
mMatrix.postTranslate(insets.left, insets.top);
mMatrix.postScale(scale, scale);
// Apply TaskView matrix: taskRect, translate
mMatrix.postTranslate(mTaskRect.left, mTaskRect.top);
mOrientationState.getOrientationHandler().setPrimary(mMatrix, MATRIX_POST_TRANSLATE,
taskPrimaryTranslation.value);
mOrientationState.getOrientationHandler().setSecondary(mMatrix, MATRIX_POST_TRANSLATE,
taskSecondaryTranslation.value);
mOrientationState.getOrientationHandler().setPrimary(
mMatrix, MATRIX_POST_TRANSLATE, recentsViewScroll.value);
// Apply RecentsView matrix
mMatrix.postScale(recentsViewScale.value, recentsViewScale.value, mPivot.x, mPivot.y);
mOrientationState.getOrientationHandler().setSecondary(mMatrix, MATRIX_POST_TRANSLATE,
recentsViewSecondaryTranslation.value);
mOrientationState.getOrientationHandler().setPrimary(mMatrix, MATRIX_POST_TRANSLATE,
recentsViewPrimaryTranslation.value);
applyWindowToHomeRotation(mMatrix);
// Crop rect is the inverse of thumbnail matrix
mTempRectF.set(-insets.left, -insets.top,
taskWidth + insets.right, taskHeight + insets.bottom);
mInversePositionMatrix.mapRect(mTempRectF);
mTempRectF.roundOut(mTmpCropRect);
params.applySurfaceParams(params.createSurfaceParams(this));
if (!DEBUG) {
return;
}
Log.d(TAG, "progress: " + fullScreenProgress
+ " scale: " + scale
+ " recentsViewScale: " + recentsViewScale.value
+ " crop: " + mTmpCropRect
+ " radius: " + getCurrentCornerRadius()
+ " taskW: " + taskWidth + " H: " + taskHeight
+ " taskRect: " + mTaskRect
+ " taskPrimaryT: " + taskPrimaryTranslation.value
+ " recentsPrimaryT: " + recentsViewPrimaryTranslation.value
+ " recentsSecondaryT: " + recentsViewSecondaryTranslation.value
+ " taskSecondaryT: " + taskSecondaryTranslation.value
+ " recentsScroll: " + recentsViewScroll.value
+ " pivot: " + mPivot
);
}
@Override
public void onBuildTargetParams(
Builder builder, RemoteAnimationTargetCompat app, TransformParams params) {
builder.withMatrix(mMatrix)
.withWindowCrop(mTmpCropRect)
.withCornerRadius(getCurrentCornerRadius());
// If mDrawsBelowRecents is unset, no reordering will be enforced.
if (mDrawsBelowRecents != null) {
// In legacy transitions, the animation leashes remain in same hierarchy in the
// TaskDisplayArea, so we don't want to bump the layer too high otherwise it will
// conflict with layers that WM core positions (ie. the input consumers). For shell
// transitions, the animation leashes are reparented to an animation container so we
// can bump layers as needed.
builder.withLayer(mDrawsBelowRecents
? Integer.MIN_VALUE + 1
: ENABLE_SHELL_TRANSITIONS ? Integer.MAX_VALUE : 0);
}
}
/**
* Returns the corner radius that should be applied to the target so that it matches the
* TaskView
*/
public float getCurrentCornerRadius() {
float visibleRadius = mCurrentFullscreenParams.mCurrentDrawnCornerRadius;
mTempPoint[0] = visibleRadius;
mTempPoint[1] = 0;
mInversePositionMatrix.mapVectors(mTempPoint);
// Ideally we should use square-root. This is an optimization as one of the dimension is 0.
return Math.max(Math.abs(mTempPoint[0]), Math.abs(mTempPoint[1]));
}
}