mirror of
https://github.com/LawnchairLauncher/lawnchair.git
synced 2026-03-01 16:26:47 +00:00
411 lines
18 KiB
Java
411 lines
18 KiB
Java
|
|
/*
|
||
|
|
* Copyright (C) 2021 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.transition;
|
||
|
|
|
||
|
|
import static android.util.RotationUtils.deltaRotation;
|
||
|
|
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
|
||
|
|
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_JUMPCUT;
|
||
|
|
import static android.view.WindowManagerPolicyConstants.SCREEN_FREEZE_LAYER_BASE;
|
||
|
|
|
||
|
|
import static com.android.wm.shell.transition.DefaultTransitionHandler.buildSurfaceAnimation;
|
||
|
|
import static com.android.wm.shell.transition.Transitions.TAG;
|
||
|
|
|
||
|
|
import android.animation.Animator;
|
||
|
|
import android.animation.AnimatorListenerAdapter;
|
||
|
|
import android.animation.ArgbEvaluator;
|
||
|
|
import android.animation.ValueAnimator;
|
||
|
|
import android.annotation.NonNull;
|
||
|
|
import android.content.Context;
|
||
|
|
import android.graphics.Color;
|
||
|
|
import android.graphics.Matrix;
|
||
|
|
import android.graphics.Rect;
|
||
|
|
import android.hardware.HardwareBuffer;
|
||
|
|
import android.util.Slog;
|
||
|
|
import android.view.Surface;
|
||
|
|
import android.view.SurfaceControl;
|
||
|
|
import android.view.SurfaceControl.Transaction;
|
||
|
|
import android.view.SurfaceSession;
|
||
|
|
import android.view.animation.Animation;
|
||
|
|
import android.view.animation.AnimationUtils;
|
||
|
|
import android.window.ScreenCapture;
|
||
|
|
import android.window.TransitionInfo;
|
||
|
|
|
||
|
|
import com.android.internal.R;
|
||
|
|
import com.android.internal.policy.TransitionAnimation;
|
||
|
|
import com.android.wm.shell.common.ShellExecutor;
|
||
|
|
import com.android.wm.shell.common.TransactionPool;
|
||
|
|
|
||
|
|
import java.util.ArrayList;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* This class handles the rotation animation when the device is rotated.
|
||
|
|
*
|
||
|
|
* <p>
|
||
|
|
* The screen rotation animation is composed of 3 different part:
|
||
|
|
* <ul>
|
||
|
|
* <li> The screenshot: <p>
|
||
|
|
* A screenshot of the whole screen prior the change of orientation is taken to hide the
|
||
|
|
* element resizing below. The screenshot is then animated to rotate and cross-fade to
|
||
|
|
* the new orientation with the content in the new orientation.
|
||
|
|
*
|
||
|
|
* <li> The windows on the display: <p>y
|
||
|
|
* Once the device is rotated, the screen and its content are in the new orientation. The
|
||
|
|
* animation first rotate the new content into the old orientation to then be able to
|
||
|
|
* animate to the new orientation
|
||
|
|
*
|
||
|
|
* <li> The Background color frame: <p>
|
||
|
|
* To have the animation seem more seamless, we add a color transitioning background behind the
|
||
|
|
* exiting and entering layouts. We compute the brightness of the start and end
|
||
|
|
* layouts and transition from the two brightness values as grayscale underneath the animation
|
||
|
|
* </ul>
|
||
|
|
*/
|
||
|
|
class ScreenRotationAnimation {
|
||
|
|
static final int MAX_ANIMATION_DURATION = 10 * 1000;
|
||
|
|
|
||
|
|
private final Context mContext;
|
||
|
|
private final TransactionPool mTransactionPool;
|
||
|
|
private final float[] mTmpFloats = new float[9];
|
||
|
|
/** The leash of the changing window container. */
|
||
|
|
private final SurfaceControl mSurfaceControl;
|
||
|
|
|
||
|
|
private final int mAnimHint;
|
||
|
|
private final int mStartWidth;
|
||
|
|
private final int mStartHeight;
|
||
|
|
private final int mEndWidth;
|
||
|
|
private final int mEndHeight;
|
||
|
|
private final int mStartRotation;
|
||
|
|
private final int mEndRotation;
|
||
|
|
|
||
|
|
/** This layer contains the actual screenshot that is to be faded out. */
|
||
|
|
private SurfaceControl mScreenshotLayer;
|
||
|
|
/**
|
||
|
|
* Only used for screen rotation and not custom animations. Layered behind all other layers
|
||
|
|
* to avoid showing any "empty" spots
|
||
|
|
*/
|
||
|
|
private SurfaceControl mBackColorSurface;
|
||
|
|
/** The leash using to animate screenshot layer. */
|
||
|
|
private final SurfaceControl mAnimLeash;
|
||
|
|
|
||
|
|
// The current active animation to move from the old to the new rotated
|
||
|
|
// state. Which animation is run here will depend on the old and new
|
||
|
|
// rotations.
|
||
|
|
private Animation mRotateExitAnimation;
|
||
|
|
private Animation mRotateEnterAnimation;
|
||
|
|
private Animation mRotateAlphaAnimation;
|
||
|
|
|
||
|
|
/** Intensity of light/whiteness of the layout before rotation occurs. */
|
||
|
|
private float mStartLuma;
|
||
|
|
/** Intensity of light/whiteness of the layout after rotation occurs. */
|
||
|
|
private float mEndLuma;
|
||
|
|
|
||
|
|
ScreenRotationAnimation(Context context, SurfaceSession session, TransactionPool pool,
|
||
|
|
Transaction t, TransitionInfo.Change change, SurfaceControl rootLeash, int animHint) {
|
||
|
|
mContext = context;
|
||
|
|
mTransactionPool = pool;
|
||
|
|
mAnimHint = animHint;
|
||
|
|
|
||
|
|
mSurfaceControl = change.getLeash();
|
||
|
|
mStartWidth = change.getStartAbsBounds().width();
|
||
|
|
mStartHeight = change.getStartAbsBounds().height();
|
||
|
|
mEndWidth = change.getEndAbsBounds().width();
|
||
|
|
mEndHeight = change.getEndAbsBounds().height();
|
||
|
|
mStartRotation = change.getStartRotation();
|
||
|
|
mEndRotation = change.getEndRotation();
|
||
|
|
|
||
|
|
mAnimLeash = new SurfaceControl.Builder(session)
|
||
|
|
.setParent(rootLeash)
|
||
|
|
.setEffectLayer()
|
||
|
|
.setCallsite("ShellRotationAnimation")
|
||
|
|
.setName("Animation leash of screenshot rotation")
|
||
|
|
.build();
|
||
|
|
|
||
|
|
try {
|
||
|
|
if (change.getSnapshot() != null) {
|
||
|
|
mScreenshotLayer = change.getSnapshot();
|
||
|
|
t.reparent(mScreenshotLayer, mAnimLeash);
|
||
|
|
mStartLuma = change.getSnapshotLuma();
|
||
|
|
} else {
|
||
|
|
ScreenCapture.LayerCaptureArgs args =
|
||
|
|
new ScreenCapture.LayerCaptureArgs.Builder(mSurfaceControl)
|
||
|
|
.setCaptureSecureLayers(true)
|
||
|
|
.setAllowProtected(true)
|
||
|
|
.setSourceCrop(new Rect(0, 0, mStartWidth, mStartHeight))
|
||
|
|
.setHintForSeamlessTransition(true)
|
||
|
|
.build();
|
||
|
|
ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer =
|
||
|
|
ScreenCapture.captureLayers(args);
|
||
|
|
if (screenshotBuffer == null) {
|
||
|
|
Slog.w(TAG, "Unable to take screenshot of display");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
mScreenshotLayer = new SurfaceControl.Builder(session)
|
||
|
|
.setParent(mAnimLeash)
|
||
|
|
.setBLASTLayer()
|
||
|
|
.setSecure(screenshotBuffer.containsSecureLayers())
|
||
|
|
.setOpaque(true)
|
||
|
|
.setCallsite("ShellRotationAnimation")
|
||
|
|
.setName("RotationLayer")
|
||
|
|
.build();
|
||
|
|
|
||
|
|
TransitionAnimation.configureScreenshotLayer(t, mScreenshotLayer, screenshotBuffer);
|
||
|
|
final HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer();
|
||
|
|
t.show(mScreenshotLayer);
|
||
|
|
if (!isCustomRotate()) {
|
||
|
|
mStartLuma = TransitionAnimation.getBorderLuma(hardwareBuffer,
|
||
|
|
screenshotBuffer.getColorSpace(), mSurfaceControl);
|
||
|
|
}
|
||
|
|
hardwareBuffer.close();
|
||
|
|
}
|
||
|
|
|
||
|
|
t.setLayer(mAnimLeash, SCREEN_FREEZE_LAYER_BASE);
|
||
|
|
t.show(mAnimLeash);
|
||
|
|
// Crop the real content in case it contains a larger child layer, e.g. wallpaper.
|
||
|
|
t.setCrop(mSurfaceControl, new Rect(0, 0, mEndWidth, mEndHeight));
|
||
|
|
|
||
|
|
if (!isCustomRotate()) {
|
||
|
|
mBackColorSurface = new SurfaceControl.Builder(session)
|
||
|
|
.setParent(rootLeash)
|
||
|
|
.setColorLayer()
|
||
|
|
.setOpaque(true)
|
||
|
|
.setCallsite("ShellRotationAnimation")
|
||
|
|
.setName("BackColorSurface")
|
||
|
|
.build();
|
||
|
|
|
||
|
|
t.setLayer(mBackColorSurface, -1);
|
||
|
|
t.setColor(mBackColorSurface, new float[]{mStartLuma, mStartLuma, mStartLuma});
|
||
|
|
t.show(mBackColorSurface);
|
||
|
|
}
|
||
|
|
|
||
|
|
} catch (Surface.OutOfResourcesException e) {
|
||
|
|
Slog.w(TAG, "Unable to allocate freeze surface", e);
|
||
|
|
}
|
||
|
|
|
||
|
|
setScreenshotTransform(t);
|
||
|
|
t.apply();
|
||
|
|
}
|
||
|
|
|
||
|
|
private boolean isCustomRotate() {
|
||
|
|
return mAnimHint == ROTATION_ANIMATION_CROSSFADE || mAnimHint == ROTATION_ANIMATION_JUMPCUT;
|
||
|
|
}
|
||
|
|
|
||
|
|
private void setScreenshotTransform(SurfaceControl.Transaction t) {
|
||
|
|
if (mScreenshotLayer == null) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
final Matrix matrix = new Matrix();
|
||
|
|
final int delta = deltaRotation(mEndRotation, mStartRotation);
|
||
|
|
if (delta != 0) {
|
||
|
|
// Compute the transformation matrix that must be applied to the snapshot to make it
|
||
|
|
// stay in the same original position with the current screen rotation.
|
||
|
|
switch (delta) {
|
||
|
|
case Surface.ROTATION_90:
|
||
|
|
matrix.setRotate(90, 0, 0);
|
||
|
|
matrix.postTranslate(mStartHeight, 0);
|
||
|
|
break;
|
||
|
|
case Surface.ROTATION_180:
|
||
|
|
matrix.setRotate(180, 0, 0);
|
||
|
|
matrix.postTranslate(mStartWidth, mStartHeight);
|
||
|
|
break;
|
||
|
|
case Surface.ROTATION_270:
|
||
|
|
matrix.setRotate(270, 0, 0);
|
||
|
|
matrix.postTranslate(0, mStartWidth);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
} else if ((mEndWidth > mStartWidth) == (mEndHeight > mStartHeight)
|
||
|
|
&& (mEndWidth != mStartWidth || mEndHeight != mStartHeight)) {
|
||
|
|
// Display resizes without rotation change.
|
||
|
|
final float scale = Math.max((float) mEndWidth / mStartWidth,
|
||
|
|
(float) mEndHeight / mStartHeight);
|
||
|
|
matrix.setScale(scale, scale);
|
||
|
|
}
|
||
|
|
matrix.getValues(mTmpFloats);
|
||
|
|
float x = mTmpFloats[Matrix.MTRANS_X];
|
||
|
|
float y = mTmpFloats[Matrix.MTRANS_Y];
|
||
|
|
t.setPosition(mScreenshotLayer, x, y);
|
||
|
|
t.setMatrix(mScreenshotLayer,
|
||
|
|
mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y],
|
||
|
|
mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Returns true if any animations were added to `animations`.
|
||
|
|
*/
|
||
|
|
boolean buildAnimation(@NonNull ArrayList<Animator> animations,
|
||
|
|
@NonNull Runnable finishCallback, float animationScale,
|
||
|
|
@NonNull ShellExecutor mainExecutor) {
|
||
|
|
if (mScreenshotLayer == null) {
|
||
|
|
// Can't do animation.
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// TODO : Found a way to get right end luma and re-enable color frame animation.
|
||
|
|
// End luma value is very not stable so it will cause more flicker is we run background
|
||
|
|
// color frame animation.
|
||
|
|
//mEndLuma = getLumaOfSurfaceControl(mEndBounds, mSurfaceControl);
|
||
|
|
|
||
|
|
final boolean customRotate = isCustomRotate();
|
||
|
|
if (customRotate) {
|
||
|
|
mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
|
||
|
|
mAnimHint == ROTATION_ANIMATION_JUMPCUT ? R.anim.rotation_animation_jump_exit
|
||
|
|
: R.anim.rotation_animation_xfade_exit);
|
||
|
|
mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
|
||
|
|
R.anim.rotation_animation_enter);
|
||
|
|
mRotateAlphaAnimation = AnimationUtils.loadAnimation(mContext,
|
||
|
|
R.anim.screen_rotate_alpha);
|
||
|
|
} else {
|
||
|
|
// Figure out how the screen has moved from the original rotation.
|
||
|
|
int delta = deltaRotation(mEndRotation, mStartRotation);
|
||
|
|
switch (delta) { /* Counter-Clockwise Rotations */
|
||
|
|
case Surface.ROTATION_0:
|
||
|
|
mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
|
||
|
|
R.anim.screen_rotate_0_exit);
|
||
|
|
mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
|
||
|
|
R.anim.rotation_animation_enter);
|
||
|
|
break;
|
||
|
|
case Surface.ROTATION_90:
|
||
|
|
mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
|
||
|
|
R.anim.screen_rotate_plus_90_exit);
|
||
|
|
mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
|
||
|
|
R.anim.screen_rotate_plus_90_enter);
|
||
|
|
break;
|
||
|
|
case Surface.ROTATION_180:
|
||
|
|
mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
|
||
|
|
R.anim.screen_rotate_180_exit);
|
||
|
|
mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
|
||
|
|
R.anim.screen_rotate_180_enter);
|
||
|
|
break;
|
||
|
|
case Surface.ROTATION_270:
|
||
|
|
mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
|
||
|
|
R.anim.screen_rotate_minus_90_exit);
|
||
|
|
mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
|
||
|
|
R.anim.screen_rotate_minus_90_enter);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
mRotateExitAnimation.initialize(mEndWidth, mEndHeight, mStartWidth, mStartHeight);
|
||
|
|
mRotateExitAnimation.restrictDuration(MAX_ANIMATION_DURATION);
|
||
|
|
mRotateExitAnimation.scaleCurrentDuration(animationScale);
|
||
|
|
mRotateEnterAnimation.initialize(mEndWidth, mEndHeight, mStartWidth, mStartHeight);
|
||
|
|
mRotateEnterAnimation.restrictDuration(MAX_ANIMATION_DURATION);
|
||
|
|
mRotateEnterAnimation.scaleCurrentDuration(animationScale);
|
||
|
|
|
||
|
|
if (customRotate) {
|
||
|
|
mRotateAlphaAnimation.initialize(mEndWidth, mEndHeight, mStartWidth, mStartHeight);
|
||
|
|
mRotateAlphaAnimation.restrictDuration(MAX_ANIMATION_DURATION);
|
||
|
|
mRotateAlphaAnimation.scaleCurrentDuration(animationScale);
|
||
|
|
|
||
|
|
buildScreenshotAlphaAnimation(animations, finishCallback, mainExecutor);
|
||
|
|
startDisplayRotation(animations, finishCallback, mainExecutor);
|
||
|
|
} else {
|
||
|
|
startDisplayRotation(animations, finishCallback, mainExecutor);
|
||
|
|
startScreenshotRotationAnimation(animations, finishCallback, mainExecutor);
|
||
|
|
//startColorAnimation(mTransaction, animationScale);
|
||
|
|
}
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
private void startDisplayRotation(@NonNull ArrayList<Animator> animations,
|
||
|
|
@NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) {
|
||
|
|
buildSurfaceAnimation(animations, mRotateEnterAnimation, mSurfaceControl, finishCallback,
|
||
|
|
mTransactionPool, mainExecutor, null /* position */, 0 /* cornerRadius */,
|
||
|
|
null /* clipRect */);
|
||
|
|
}
|
||
|
|
|
||
|
|
private void startScreenshotRotationAnimation(@NonNull ArrayList<Animator> animations,
|
||
|
|
@NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) {
|
||
|
|
buildSurfaceAnimation(animations, mRotateExitAnimation, mAnimLeash, finishCallback,
|
||
|
|
mTransactionPool, mainExecutor, null /* position */, 0 /* cornerRadius */,
|
||
|
|
null /* clipRect */);
|
||
|
|
}
|
||
|
|
|
||
|
|
private void buildScreenshotAlphaAnimation(@NonNull ArrayList<Animator> animations,
|
||
|
|
@NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) {
|
||
|
|
buildSurfaceAnimation(animations, mRotateAlphaAnimation, mAnimLeash, finishCallback,
|
||
|
|
mTransactionPool, mainExecutor, null /* position */, 0 /* cornerRadius */,
|
||
|
|
null /* clipRect */);
|
||
|
|
}
|
||
|
|
|
||
|
|
private void startColorAnimation(float animationScale, @NonNull ShellExecutor animExecutor) {
|
||
|
|
int colorTransitionMs = mContext.getResources().getInteger(
|
||
|
|
R.integer.config_screen_rotation_color_transition);
|
||
|
|
final float[] rgbTmpFloat = new float[3];
|
||
|
|
final int startColor = Color.rgb(mStartLuma, mStartLuma, mStartLuma);
|
||
|
|
final int endColor = Color.rgb(mEndLuma, mEndLuma, mEndLuma);
|
||
|
|
final long duration = colorTransitionMs * (long) animationScale;
|
||
|
|
final Transaction t = mTransactionPool.acquire();
|
||
|
|
|
||
|
|
final ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
|
||
|
|
// Animation length is already expected to be scaled.
|
||
|
|
va.overrideDurationScale(1.0f);
|
||
|
|
va.setDuration(duration);
|
||
|
|
va.addUpdateListener(animation -> {
|
||
|
|
final long currentPlayTime = Math.min(va.getDuration(), va.getCurrentPlayTime());
|
||
|
|
final float fraction = currentPlayTime / va.getDuration();
|
||
|
|
applyColor(startColor, endColor, rgbTmpFloat, fraction, mBackColorSurface, t);
|
||
|
|
});
|
||
|
|
va.addListener(new AnimatorListenerAdapter() {
|
||
|
|
@Override
|
||
|
|
public void onAnimationCancel(Animator animation) {
|
||
|
|
applyColor(startColor, endColor, rgbTmpFloat, 1f /* fraction */, mBackColorSurface,
|
||
|
|
t);
|
||
|
|
mTransactionPool.release(t);
|
||
|
|
}
|
||
|
|
|
||
|
|
@Override
|
||
|
|
public void onAnimationEnd(Animator animation) {
|
||
|
|
applyColor(startColor, endColor, rgbTmpFloat, 1f /* fraction */, mBackColorSurface,
|
||
|
|
t);
|
||
|
|
mTransactionPool.release(t);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
animExecutor.execute(va::start);
|
||
|
|
}
|
||
|
|
|
||
|
|
public void kill() {
|
||
|
|
final Transaction t = mTransactionPool.acquire();
|
||
|
|
if (mAnimLeash.isValid()) {
|
||
|
|
t.remove(mAnimLeash);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (mScreenshotLayer != null && mScreenshotLayer.isValid()) {
|
||
|
|
t.remove(mScreenshotLayer);
|
||
|
|
}
|
||
|
|
if (mBackColorSurface != null && mBackColorSurface.isValid()) {
|
||
|
|
t.remove(mBackColorSurface);
|
||
|
|
}
|
||
|
|
t.apply();
|
||
|
|
mTransactionPool.release(t);
|
||
|
|
}
|
||
|
|
|
||
|
|
private static void applyColor(int startColor, int endColor, float[] rgbFloat,
|
||
|
|
float fraction, SurfaceControl surface, SurfaceControl.Transaction t) {
|
||
|
|
final int color = (Integer) ArgbEvaluator.getInstance().evaluate(fraction, startColor,
|
||
|
|
endColor);
|
||
|
|
Color middleColor = Color.valueOf(color);
|
||
|
|
rgbFloat[0] = middleColor.red();
|
||
|
|
rgbFloat[1] = middleColor.green();
|
||
|
|
rgbFloat[2] = middleColor.blue();
|
||
|
|
if (surface.isValid()) {
|
||
|
|
t.setColor(surface, rgbFloat);
|
||
|
|
}
|
||
|
|
t.apply();
|
||
|
|
}
|
||
|
|
}
|