diff options
Diffstat (limited to 'libs')
4 files changed, 376 insertions, 458 deletions
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index 00fb298ea1cc..43ce1668c4df 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -535,5 +535,7 @@ <!-- The vertical margin that needs to be preserved between the scaled window bounds and the original window bounds (once the surface is scaled enough to do so) --> <dimen name="cross_task_back_vertical_margin">8dp</dimen> + <!-- The offset from the left edge of the entering page for the cross-activity animation --> + <dimen name="cross_activity_back_entering_start_offset">96dp</dimen> </resources> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java deleted file mode 100644 index d6f7c367f772..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java +++ /dev/null @@ -1,455 +0,0 @@ -/* - * Copyright (C) 2022 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.wm.shell.back; - -import static android.view.RemoteAnimationTarget.MODE_CLOSING; -import static android.view.RemoteAnimationTarget.MODE_OPENING; -import static android.window.BackEvent.EDGE_RIGHT; - -import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY; -import static com.android.wm.shell.back.BackAnimationConstants.UPDATE_SYSUI_FLAGS_THRESHOLD; -import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; -import android.annotation.NonNull; -import android.content.Context; -import android.graphics.Matrix; -import android.graphics.PointF; -import android.graphics.Rect; -import android.graphics.RectF; -import android.os.RemoteException; -import android.util.FloatProperty; -import android.util.TypedValue; -import android.view.IRemoteAnimationFinishedCallback; -import android.view.IRemoteAnimationRunner; -import android.view.RemoteAnimationTarget; -import android.view.SurfaceControl; -import android.view.animation.Interpolator; -import android.window.BackEvent; -import android.window.BackMotionEvent; -import android.window.BackProgressAnimator; -import android.window.IOnBackInvokedCallback; - -import com.android.internal.dynamicanimation.animation.SpringAnimation; -import com.android.internal.dynamicanimation.animation.SpringForce; -import com.android.internal.policy.ScreenDecorationsUtils; -import com.android.internal.protolog.common.ProtoLog; -import com.android.wm.shell.animation.Interpolators; -import com.android.wm.shell.common.annotations.ShellMainThread; - -import javax.inject.Inject; - -/** Class that defines cross-activity animation. */ -@ShellMainThread -public class CrossActivityBackAnimation extends ShellBackAnimation { - /** - * Minimum scale of the entering/closing window. - */ - private static final float MIN_WINDOW_SCALE = 0.9f; - - /** Duration of post animation after gesture committed. */ - private static final int POST_ANIMATION_DURATION = 350; - private static final Interpolator INTERPOLATOR = Interpolators.STANDARD_DECELERATE; - private static final FloatProperty<CrossActivityBackAnimation> ENTER_PROGRESS_PROP = - new FloatProperty<>("enter-alpha") { - @Override - public void setValue(CrossActivityBackAnimation anim, float value) { - anim.setEnteringProgress(value); - } - - @Override - public Float get(CrossActivityBackAnimation object) { - return object.getEnteringProgress(); - } - }; - private static final FloatProperty<CrossActivityBackAnimation> LEAVE_PROGRESS_PROP = - new FloatProperty<>("leave-alpha") { - @Override - public void setValue(CrossActivityBackAnimation anim, float value) { - anim.setLeavingProgress(value); - } - - @Override - public Float get(CrossActivityBackAnimation object) { - return object.getLeavingProgress(); - } - }; - private static final float MIN_WINDOW_ALPHA = 0.01f; - private static final float WINDOW_X_SHIFT_DP = 48; - private static final int SCALE_FACTOR = 100; - // TODO(b/264710590): Use the progress commit threshold from ViewConfiguration once it exists. - private static final float TARGET_COMMIT_PROGRESS = 0.5f; - private static final float ENTER_ALPHA_THRESHOLD = 0.22f; - - private final Rect mStartTaskRect = new Rect(); - private final float mCornerRadius; - - // The closing window properties. - private final RectF mClosingRect = new RectF(); - - // The entering window properties. - private final Rect mEnteringStartRect = new Rect(); - private final RectF mEnteringRect = new RectF(); - private final SpringAnimation mEnteringProgressSpring; - private final SpringAnimation mLeavingProgressSpring; - // Max window x-shift in pixels. - private final float mWindowXShift; - private final BackAnimationRunner mBackAnimationRunner; - - private float mEnteringProgress = 0f; - private float mLeavingProgress = 0f; - - private final PointF mInitialTouchPos = new PointF(); - - private final Matrix mTransformMatrix = new Matrix(); - - private final float[] mTmpFloat9 = new float[9]; - - private RemoteAnimationTarget mEnteringTarget; - private RemoteAnimationTarget mClosingTarget; - private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction(); - - private boolean mBackInProgress = false; - private boolean mIsRightEdge; - private boolean mTriggerBack = false; - - private PointF mTouchPos = new PointF(); - private IRemoteAnimationFinishedCallback mFinishCallback; - - private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator(); - - private final BackAnimationBackground mBackground; - - @Inject - public CrossActivityBackAnimation(Context context, BackAnimationBackground background) { - mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context); - mBackAnimationRunner = new BackAnimationRunner( - new Callback(), new Runner(), context, CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY); - mBackground = background; - mEnteringProgressSpring = new SpringAnimation(this, ENTER_PROGRESS_PROP); - mEnteringProgressSpring.setSpring(new SpringForce() - .setStiffness(SpringForce.STIFFNESS_MEDIUM) - .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY)); - mLeavingProgressSpring = new SpringAnimation(this, LEAVE_PROGRESS_PROP); - mLeavingProgressSpring.setSpring(new SpringForce() - .setStiffness(SpringForce.STIFFNESS_MEDIUM) - .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY)); - mWindowXShift = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, WINDOW_X_SHIFT_DP, - context.getResources().getDisplayMetrics()); - } - - /** - * Returns 1 if x >= edge1, 0 if x <= edge0, and a smoothed value between the two. - * From https://en.wikipedia.org/wiki/Smoothstep - */ - private static float smoothstep(float edge0, float edge1, float x) { - if (x < edge0) return 0; - if (x >= edge1) return 1; - - x = (x - edge0) / (edge1 - edge0); - return x * x * (3 - 2 * x); - } - - /** - * Linearly map x from range (a1, a2) to range (b1, b2). - */ - private static float mapLinear(float x, float a1, float a2, float b1, float b2) { - return b1 + (x - a1) * (b2 - b1) / (a2 - a1); - } - - /** - * Linearly map a normalized value from (0, 1) to (min, max). - */ - private static float mapRange(float value, float min, float max) { - return min + (value * (max - min)); - } - - private void startBackAnimation() { - if (mEnteringTarget == null || mClosingTarget == null) { - ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Entering target or closing target is null."); - return; - } - mTransaction.setAnimationTransaction(); - - // Offset start rectangle to align task bounds. - mStartTaskRect.set(mClosingTarget.windowConfiguration.getBounds()); - mStartTaskRect.offsetTo(0, 0); - - // Draw background with task background color. - mBackground.ensureBackground(mClosingTarget.windowConfiguration.getBounds(), - mEnteringTarget.taskInfo.taskDescription.getBackgroundColor(), mTransaction); - setEnteringProgress(0); - setLeavingProgress(0); - } - - private void applyTransform(SurfaceControl leash, RectF targetRect, float targetAlpha) { - if (leash == null || !leash.isValid()) { - return; - } - - final float scale = targetRect.width() / mStartTaskRect.width(); - mTransformMatrix.reset(); - mTransformMatrix.setScale(scale, scale); - mTransformMatrix.postTranslate(targetRect.left, targetRect.top); - mTransaction.setAlpha(leash, targetAlpha) - .setMatrix(leash, mTransformMatrix, mTmpFloat9) - .setWindowCrop(leash, mStartTaskRect) - .setCornerRadius(leash, mCornerRadius); - } - - private void finishAnimation() { - if (mEnteringTarget != null) { - if (mEnteringTarget.leash != null && mEnteringTarget.leash.isValid()) { - mTransaction.setCornerRadius(mEnteringTarget.leash, 0); - mEnteringTarget.leash.release(); - } - mEnteringTarget = null; - } - if (mClosingTarget != null) { - if (mClosingTarget.leash != null) { - mClosingTarget.leash.release(); - } - mClosingTarget = null; - } - if (mBackground != null) { - mBackground.removeBackground(mTransaction); - } - - mTransaction.apply(); - mBackInProgress = false; - mTransformMatrix.reset(); - mInitialTouchPos.set(0, 0); - - if (mFinishCallback != null) { - try { - mFinishCallback.onAnimationFinished(); - } catch (RemoteException e) { - e.printStackTrace(); - } - mFinishCallback = null; - } - mEnteringProgressSpring.animateToFinalPosition(0); - mEnteringProgressSpring.skipToEnd(); - mLeavingProgressSpring.animateToFinalPosition(0); - mLeavingProgressSpring.skipToEnd(); - } - - private void onGestureProgress(@NonNull BackEvent backEvent) { - if (!mBackInProgress) { - mIsRightEdge = backEvent.getSwipeEdge() == EDGE_RIGHT; - mInitialTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY()); - mBackInProgress = true; - } - mTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY()); - - float progress = backEvent.getProgress(); - float springProgress = (mTriggerBack - ? mapLinear(progress, 0f, 1, TARGET_COMMIT_PROGRESS, 1) - : mapLinear(progress, 0, 1f, 0, TARGET_COMMIT_PROGRESS)) * SCALE_FACTOR; - mLeavingProgressSpring.animateToFinalPosition(springProgress); - mEnteringProgressSpring.animateToFinalPosition(springProgress); - mBackground.onBackProgressed(progress); - } - - private void onGestureCommitted() { - if (mEnteringTarget == null || mClosingTarget == null || mClosingTarget.leash == null - || mEnteringTarget.leash == null || !mEnteringTarget.leash.isValid() - || !mClosingTarget.leash.isValid()) { - finishAnimation(); - return; - } - // End the fade animations - mLeavingProgressSpring.cancel(); - mEnteringProgressSpring.cancel(); - - // We enter phase 2 of the animation, the starting coordinates for phase 2 are the current - // coordinate of the gesture driven phase. - mEnteringRect.round(mEnteringStartRect); - mTransaction.hide(mClosingTarget.leash); - - ValueAnimator valueAnimator = - ValueAnimator.ofFloat(1f, 0f).setDuration(POST_ANIMATION_DURATION); - valueAnimator.setInterpolator(INTERPOLATOR); - valueAnimator.addUpdateListener(animation -> { - float progress = animation.getAnimatedFraction(); - updatePostCommitEnteringAnimation(progress); - if (progress > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD) { - mBackground.resetStatusBarCustomization(); - } - mTransaction.apply(); - }); - - valueAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mBackground.resetStatusBarCustomization(); - finishAnimation(); - } - }); - valueAnimator.start(); - } - - private void updatePostCommitEnteringAnimation(float progress) { - float left = mapRange(progress, mEnteringStartRect.left, mStartTaskRect.left); - float top = mapRange(progress, mEnteringStartRect.top, mStartTaskRect.top); - float width = mapRange(progress, mEnteringStartRect.width(), mStartTaskRect.width()); - float height = mapRange(progress, mEnteringStartRect.height(), mStartTaskRect.height()); - float alpha = mapRange(progress, getPreCommitEnteringAlpha(), 1.0f); - mEnteringRect.set(left, top, left + width, top + height); - applyTransform(mEnteringTarget.leash, mEnteringRect, alpha); - } - - private float getPreCommitEnteringAlpha() { - return Math.max(smoothstep(ENTER_ALPHA_THRESHOLD, 0.7f, mEnteringProgress), - MIN_WINDOW_ALPHA); - } - - private float getEnteringProgress() { - return mEnteringProgress * SCALE_FACTOR; - } - - private void setEnteringProgress(float value) { - mEnteringProgress = value / SCALE_FACTOR; - if (mEnteringTarget != null && mEnteringTarget.leash != null) { - transformWithProgress( - mEnteringProgress, - getPreCommitEnteringAlpha(), - mEnteringTarget.leash, - mEnteringRect, - -mWindowXShift, - 0 - ); - } - } - - private float getPreCommitLeavingAlpha() { - return Math.max(1 - smoothstep(0, ENTER_ALPHA_THRESHOLD, mLeavingProgress), - MIN_WINDOW_ALPHA); - } - - private float getLeavingProgress() { - return mLeavingProgress * SCALE_FACTOR; - } - - private void setLeavingProgress(float value) { - mLeavingProgress = value / SCALE_FACTOR; - if (mClosingTarget != null && mClosingTarget.leash != null) { - transformWithProgress( - mLeavingProgress, - getPreCommitLeavingAlpha(), - mClosingTarget.leash, - mClosingRect, - 0, - mIsRightEdge ? 0 : mWindowXShift - ); - } - } - - private void transformWithProgress(float progress, float alpha, SurfaceControl surface, - RectF targetRect, float deltaXMin, float deltaXMax) { - - final int width = mStartTaskRect.width(); - final int height = mStartTaskRect.height(); - - final float interpolatedProgress = INTERPOLATOR.getInterpolation(progress); - final float closingScale = MIN_WINDOW_SCALE - + (1 - interpolatedProgress) * (1 - MIN_WINDOW_SCALE); - final float closingWidth = closingScale * width; - final float closingHeight = (float) height / width * closingWidth; - - // Move the window along the X axis. - float closingLeft = mStartTaskRect.left + (width - closingWidth) / 2; - closingLeft += mapRange(interpolatedProgress, deltaXMin, deltaXMax); - - // Move the window along the Y axis. - final float closingTop = (height - closingHeight) * 0.5f; - targetRect.set( - closingLeft, closingTop, closingLeft + closingWidth, closingTop + closingHeight); - - applyTransform(surface, targetRect, Math.max(alpha, MIN_WINDOW_ALPHA)); - mTransaction.apply(); - } - - @Override - public BackAnimationRunner getRunner() { - return mBackAnimationRunner; - } - - private final class Callback extends IOnBackInvokedCallback.Default { - @Override - public void onBackStarted(BackMotionEvent backEvent) { - mTriggerBack = backEvent.getTriggerBack(); - mProgressAnimator.onBackStarted(backEvent, - CrossActivityBackAnimation.this::onGestureProgress); - } - - @Override - public void onBackProgressed(@NonNull BackMotionEvent backEvent) { - mTriggerBack = backEvent.getTriggerBack(); - mProgressAnimator.onBackProgressed(backEvent); - } - - @Override - public void onBackCancelled() { - mProgressAnimator.onBackCancelled(() -> { - // mProgressAnimator can reach finish stage earlier than mLeavingProgressSpring, - // and if we release all animation leash first, the leavingProgressSpring won't - // able to update the animation anymore, which cause flicker. - // Here should force update the closing animation target to the final stage before - // release it. - setLeavingProgress(0); - finishAnimation(); - }); - } - - @Override - public void onBackInvoked() { - mProgressAnimator.reset(); - onGestureCommitted(); - } - } - - private final class Runner extends IRemoteAnimationRunner.Default { - @Override - public void onAnimationStart( - int transit, - RemoteAnimationTarget[] apps, - RemoteAnimationTarget[] wallpapers, - RemoteAnimationTarget[] nonApps, - IRemoteAnimationFinishedCallback finishedCallback) { - ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Start back to activity animation."); - for (RemoteAnimationTarget a : apps) { - if (a.mode == MODE_CLOSING) { - mClosingTarget = a; - } - if (a.mode == MODE_OPENING) { - mEnteringTarget = a; - } - } - - startBackAnimation(); - mFinishCallback = finishedCallback; - } - - @Override - public void onAnimationCancelled() { - finishAnimation(); - } - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt new file mode 100644 index 000000000000..edf29dd484fc --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt @@ -0,0 +1,367 @@ +/* + * Copyright (C) 2022 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.wm.shell.back + +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.ValueAnimator +import android.content.Context +import android.content.res.Configuration +import android.graphics.Matrix +import android.graphics.PointF +import android.graphics.Rect +import android.graphics.RectF +import android.os.RemoteException +import android.view.Display +import android.view.IRemoteAnimationFinishedCallback +import android.view.IRemoteAnimationRunner +import android.view.RemoteAnimationTarget +import android.view.SurfaceControl +import android.view.animation.DecelerateInterpolator +import android.view.animation.Interpolator +import android.window.BackEvent +import android.window.BackMotionEvent +import android.window.BackProgressAnimator +import android.window.IOnBackInvokedCallback +import com.android.internal.jank.Cuj +import com.android.internal.policy.ScreenDecorationsUtils +import com.android.internal.protolog.common.ProtoLog +import com.android.wm.shell.R +import com.android.wm.shell.RootTaskDisplayAreaOrganizer +import com.android.wm.shell.animation.Interpolators +import com.android.wm.shell.common.annotations.ShellMainThread +import com.android.wm.shell.protolog.ShellProtoLogGroup +import javax.inject.Inject +import kotlin.math.abs +import kotlin.math.max +import kotlin.math.min + +/** Class that defines cross-activity animation. */ +@ShellMainThread +class CrossActivityBackAnimation @Inject constructor( + private val context: Context, + private val background: BackAnimationBackground, + private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer +) : ShellBackAnimation() { + + private val startClosingRect = RectF() + private val targetClosingRect = RectF() + private val currentClosingRect = RectF() + + private val startEnteringRect = RectF() + private val targetEnteringRect = RectF() + private val currentEnteringRect = RectF() + + private val taskBoundsRect = Rect() + + private val cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context) + + private val backAnimationRunner = BackAnimationRunner( + Callback(), Runner(), context, Cuj.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY + ) + private val initialTouchPos = PointF() + private val transformMatrix = Matrix() + private val tmpFloat9 = FloatArray(9) + private var enteringTarget: RemoteAnimationTarget? = null + private var closingTarget: RemoteAnimationTarget? = null + private val transaction = SurfaceControl.Transaction() + private var triggerBack = false + private var finishCallback: IRemoteAnimationFinishedCallback? = null + private val progressAnimator = BackProgressAnimator() + private val displayBoundsMargin = + context.resources.getDimension(R.dimen.cross_task_back_vertical_margin) + private val enteringStartOffset = + context.resources.getDimension(R.dimen.cross_activity_back_entering_start_offset) + + private val gestureInterpolator = Interpolators.STANDARD_DECELERATE + private val postCommitInterpolator = Interpolators.FAST_OUT_SLOW_IN + private val verticalMoveInterpolator: Interpolator = DecelerateInterpolator() + + private var scrimLayer: SurfaceControl? = null + private var maxScrimAlpha: Float = 0f + + override fun getRunner() = backAnimationRunner + + private fun startBackAnimation(backMotionEvent: BackMotionEvent) { + if (enteringTarget == null || closingTarget == null) { + ProtoLog.d( + ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW, + "Entering target or closing target is null." + ) + return + } + triggerBack = backMotionEvent.triggerBack + initialTouchPos.set(backMotionEvent.touchX, backMotionEvent.touchY) + + transaction.setAnimationTransaction() + + // Offset start rectangle to align task bounds. + taskBoundsRect.set(closingTarget!!.windowConfiguration.bounds) + taskBoundsRect.offsetTo(0, 0) + + startClosingRect.set(taskBoundsRect) + + // scale closing target into the middle for rhs and to the right for lhs + targetClosingRect.set(startClosingRect) + targetClosingRect.scaleCentered(MAX_SCALE) + if (backMotionEvent.swipeEdge != BackEvent.EDGE_RIGHT) { + targetClosingRect.offset( + startClosingRect.right - targetClosingRect.right - displayBoundsMargin, 0f + ) + } + + // the entering target starts 96dp to the left of the screen edge... + startEnteringRect.set(startClosingRect) + startEnteringRect.offset(-enteringStartOffset, 0f) + + // ...and gets scaled in sync with the closing target + targetEnteringRect.set(startEnteringRect) + targetEnteringRect.scaleCentered(MAX_SCALE) + + // Draw background with task background color. + background.ensureBackground( + closingTarget!!.windowConfiguration.bounds, + enteringTarget!!.taskInfo.taskDescription!!.backgroundColor, transaction + ) + ensureScrimLayer() + transaction.apply() + } + + private fun onGestureProgress(backEvent: BackEvent) { + val progress = gestureInterpolator.getInterpolation(backEvent.progress) + background.onBackProgressed(progress) + currentClosingRect.setInterpolatedRectF(startClosingRect, targetClosingRect, progress) + val yOffset = getYOffset(currentClosingRect, backEvent.touchY) + currentClosingRect.offset(0f, yOffset) + applyTransform(closingTarget?.leash, currentClosingRect, 1f) + currentEnteringRect.setInterpolatedRectF(startEnteringRect, targetEnteringRect, progress) + currentEnteringRect.offset(0f, yOffset) + applyTransform(enteringTarget?.leash, currentEnteringRect, 1f) + transaction.apply() + } + + private fun getYOffset(centeredRect: RectF, touchY: Float): Float { + val screenHeight = taskBoundsRect.height() + // Base the window movement in the Y axis on the touch movement in the Y axis. + val rawYDelta = touchY - initialTouchPos.y + val yDirection = (if (rawYDelta < 0) -1 else 1) + // limit yDelta interpretation to 1/2 of screen height in either direction + val deltaYRatio = min(screenHeight / 2f, abs(rawYDelta)) / (screenHeight / 2f) + val interpolatedYRatio: Float = verticalMoveInterpolator.getInterpolation(deltaYRatio) + // limit y-shift so surface never passes 8dp screen margin + val deltaY = yDirection * interpolatedYRatio * max( + 0f, (screenHeight - centeredRect.height()) / 2f - displayBoundsMargin + ) + return deltaY + } + + private fun onGestureCommitted() { + if (closingTarget?.leash == null || enteringTarget?.leash == null || + !enteringTarget!!.leash.isValid || !closingTarget!!.leash.isValid + ) { + finishAnimation() + return + } + + // We enter phase 2 of the animation, the starting coordinates for phase 2 are the current + // coordinate of the gesture driven phase. Let's update the start and target rects and kick + // off the animator + startClosingRect.set(currentClosingRect) + startEnteringRect.set(currentEnteringRect) + targetEnteringRect.set(taskBoundsRect) + targetClosingRect.set(taskBoundsRect) + targetClosingRect.offset(currentClosingRect.left + enteringStartOffset, 0f) + + val valueAnimator = ValueAnimator.ofFloat(1f, 0f).setDuration(POST_ANIMATION_DURATION) + valueAnimator.addUpdateListener { animation: ValueAnimator -> + val progress = animation.animatedFraction + onPostCommitProgress(progress) + if (progress > 1 - BackAnimationConstants.UPDATE_SYSUI_FLAGS_THRESHOLD) { + background.resetStatusBarCustomization() + } + } + valueAnimator.addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + background.resetStatusBarCustomization() + finishAnimation() + } + }) + valueAnimator.start() + } + + private fun onPostCommitProgress(linearProgress: Float) { + val closingAlpha = max(1f - linearProgress * 2, 0f) + val progress = postCommitInterpolator.getInterpolation(linearProgress) + scrimLayer?.let { transaction.setAlpha(it, maxScrimAlpha * (1f - linearProgress)) } + currentClosingRect.setInterpolatedRectF(startClosingRect, targetClosingRect, progress) + applyTransform(closingTarget?.leash, currentClosingRect, closingAlpha) + currentEnteringRect.setInterpolatedRectF(startEnteringRect, targetEnteringRect, progress) + applyTransform(enteringTarget?.leash, currentEnteringRect, 1f) + transaction.apply() + } + + private fun finishAnimation() { + enteringTarget?.let { + if (it.leash != null && it.leash.isValid) { + transaction.setCornerRadius(it.leash, 0f) + it.leash.release() + } + enteringTarget = null + } + + closingTarget?.leash?.release() + closingTarget = null + + background.removeBackground(transaction) + transaction.apply() + transformMatrix.reset() + initialTouchPos.set(0f, 0f) + try { + finishCallback?.onAnimationFinished() + } catch (e: RemoteException) { + e.printStackTrace() + } + finishCallback = null + removeScrimLayer() + } + + private fun applyTransform(leash: SurfaceControl?, rect: RectF, alpha: Float) { + if (leash == null || !leash.isValid) return + val scale = rect.width() / taskBoundsRect.width() + transformMatrix.reset() + transformMatrix.setScale(scale, scale) + transformMatrix.postTranslate(rect.left, rect.top) + transaction.setAlpha(leash, alpha) + .setMatrix(leash, transformMatrix, tmpFloat9) + .setCrop(leash, taskBoundsRect) + .setCornerRadius(leash, cornerRadius) + } + + private fun ensureScrimLayer() { + if (scrimLayer != null) return + val isDarkTheme: Boolean = isDarkMode(context) + val scrimBuilder = SurfaceControl.Builder() + .setName("Cross-Activity back animation scrim") + .setCallsite("CrossActivityBackAnimation") + .setColorLayer() + .setOpaque(false) + .setHidden(false) + + rootTaskDisplayAreaOrganizer.attachToDisplayArea(Display.DEFAULT_DISPLAY, scrimBuilder) + scrimLayer = scrimBuilder.build() + val colorComponents = floatArrayOf(0f, 0f, 0f) + maxScrimAlpha = if (isDarkTheme) MAX_SCRIM_ALPHA_DARK else MAX_SCRIM_ALPHA_LIGHT + transaction + .setColor(scrimLayer, colorComponents) + .setAlpha(scrimLayer!!, maxScrimAlpha) + .setRelativeLayer(scrimLayer!!, closingTarget!!.leash, -1) + .show(scrimLayer) + } + + private fun removeScrimLayer() { + scrimLayer?.let { + if (it.isValid) { + transaction.remove(it).apply() + } + } + scrimLayer = null + } + + + private inner class Callback : IOnBackInvokedCallback.Default() { + override fun onBackStarted(backMotionEvent: BackMotionEvent) { + startBackAnimation(backMotionEvent) + progressAnimator.onBackStarted(backMotionEvent) { backEvent: BackEvent -> + onGestureProgress(backEvent) + } + } + + override fun onBackProgressed(backEvent: BackMotionEvent) { + triggerBack = backEvent.triggerBack + progressAnimator.onBackProgressed(backEvent) + } + + override fun onBackCancelled() { + progressAnimator.onBackCancelled { + finishAnimation() + } + } + + override fun onBackInvoked() { + progressAnimator.reset() + onGestureCommitted() + } + } + + private inner class Runner : IRemoteAnimationRunner.Default() { + override fun onAnimationStart( + transit: Int, + apps: Array<RemoteAnimationTarget>, + wallpapers: Array<RemoteAnimationTarget>?, + nonApps: Array<RemoteAnimationTarget>?, + finishedCallback: IRemoteAnimationFinishedCallback + ) { + ProtoLog.d( + ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW, "Start back to activity animation." + ) + for (a in apps) { + when (a.mode) { + RemoteAnimationTarget.MODE_CLOSING -> closingTarget = a + RemoteAnimationTarget.MODE_OPENING -> enteringTarget = a + } + } + finishCallback = finishedCallback + } + + override fun onAnimationCancelled() { + finishAnimation() + } + } + + companion object { + /** Max scale of the entering/closing window.*/ + private const val MAX_SCALE = 0.9f + + /** Duration of post animation after gesture committed. */ + private const val POST_ANIMATION_DURATION = 300L + + private const val MAX_SCRIM_ALPHA_DARK = 0.8f + private const val MAX_SCRIM_ALPHA_LIGHT = 0.2f + } +} + +private fun isDarkMode(context: Context): Boolean { + return context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == + Configuration.UI_MODE_NIGHT_YES +} + +private fun RectF.setInterpolatedRectF(start: RectF, target: RectF, progress: Float) { + require(!(progress < 0 || progress > 1)) { "Progress value must be between 0 and 1" } + left = start.left + (target.left - start.left) * progress + top = start.top + (target.top - start.top) * progress + right = start.right + (target.right - start.right) * progress + bottom = start.bottom + (target.bottom - start.bottom) * progress +} + +private fun RectF.scaleCentered( + scale: Float, + pivotX: Float = left + width() / 2, + pivotY: Float = top + height() / 2 +) { + offset(-pivotX, -pivotY) // move pivot to origin + scale(scale) + offset(pivotX, pivotY) // Move back to the original position +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java index 9ded6ea1d187..703eb199f260 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java @@ -61,6 +61,7 @@ import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; import com.android.internal.util.test.FakeSettingsProvider; +import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.sysui.ShellCommandHandler; @@ -113,6 +114,8 @@ public class BackAnimationControllerTest extends ShellTestCase { private InputManager mInputManager; @Mock private ShellCommandHandler mShellCommandHandler; + @Mock + private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer; private BackAnimationController mController; private TestableContentResolver mContentResolver; @@ -133,7 +136,8 @@ public class BackAnimationControllerTest extends ShellTestCase { mShellInit = spy(new ShellInit(mShellExecutor)); mShellBackAnimationRegistry = new ShellBackAnimationRegistry( - new CrossActivityBackAnimation(mContext, mAnimationBackground), + new CrossActivityBackAnimation( + mContext, mAnimationBackground, mRootTaskDisplayAreaOrganizer), new CrossTaskBackAnimation(mContext, mAnimationBackground), /* dialogCloseAnimation= */ null, new CustomizeActivityAnimation(mContext, mAnimationBackground), @@ -528,8 +532,8 @@ public class BackAnimationControllerTest extends ShellTestCase { @Test public void testBackToActivity() throws RemoteException { - final CrossActivityBackAnimation animation = new CrossActivityBackAnimation(mContext, - mAnimationBackground); + final CrossActivityBackAnimation animation = new CrossActivityBackAnimation( + mContext, mAnimationBackground, mRootTaskDisplayAreaOrganizer); verifySystemBackBehavior(BackNavigationInfo.TYPE_CROSS_ACTIVITY, animation.getRunner()); } |