Merge "Migrate rotation animation to shell transition" into sc-v2-dev
This commit is contained in:
@@ -87,8 +87,11 @@ public final class TransitionInfo implements Parcelable {
|
||||
/** The container has voice session. */
|
||||
public static final int FLAG_IS_VOICE_INTERACTION = 1 << 4;
|
||||
|
||||
/** The container is the display. */
|
||||
public static final int FLAG_IS_DISPLAY = 1 << 5;
|
||||
|
||||
/** The first unused bit. This can be used by remotes to attach custom flags to this change. */
|
||||
public static final int FLAG_FIRST_CUSTOM = 1 << 5;
|
||||
public static final int FLAG_FIRST_CUSTOM = 1 << 6;
|
||||
|
||||
/** @hide */
|
||||
@IntDef(prefix = { "FLAG_" }, value = {
|
||||
@@ -98,6 +101,7 @@ public final class TransitionInfo implements Parcelable {
|
||||
FLAG_TRANSLUCENT,
|
||||
FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT,
|
||||
FLAG_IS_VOICE_INTERACTION,
|
||||
FLAG_IS_DISPLAY,
|
||||
FLAG_FIRST_CUSTOM
|
||||
})
|
||||
public @interface ChangeFlags {}
|
||||
@@ -276,6 +280,9 @@ public final class TransitionInfo implements Parcelable {
|
||||
if ((flags & FLAG_IS_VOICE_INTERACTION) != 0) {
|
||||
sb.append((sb.length() == 0 ? "" : "|") + "IS_VOICE_INTERACTION");
|
||||
}
|
||||
if ((flags & FLAG_IS_DISPLAY) != 0) {
|
||||
sb.append((sb.length() == 0 ? "" : "|") + "IS_DISPLAY");
|
||||
}
|
||||
if ((flags & FLAG_FIRST_CUSTOM) != 0) {
|
||||
sb.append((sb.length() == 0 ? "" : "|") + "FIRST_CUSTOM");
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import static android.view.WindowManager.TRANSIT_OPEN;
|
||||
import static android.view.WindowManager.TRANSIT_RELAUNCH;
|
||||
import static android.view.WindowManager.TRANSIT_TO_BACK;
|
||||
import static android.view.WindowManager.TRANSIT_TO_FRONT;
|
||||
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
|
||||
import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION;
|
||||
import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
|
||||
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
|
||||
@@ -110,6 +111,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
|
||||
|
||||
private final int mCurrentUserId;
|
||||
|
||||
private ScreenRotationAnimation mRotationAnimation;
|
||||
|
||||
DefaultTransitionHandler(@NonNull TransactionPool transactionPool, Context context,
|
||||
@NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
|
||||
mTransactionPool = transactionPool;
|
||||
@@ -138,6 +141,12 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
|
||||
|
||||
final Runnable onAnimFinish = () -> {
|
||||
if (!animations.isEmpty()) return;
|
||||
|
||||
if (mRotationAnimation != null) {
|
||||
mRotationAnimation.kill();
|
||||
mRotationAnimation = null;
|
||||
}
|
||||
|
||||
mAnimations.remove(transition);
|
||||
finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
|
||||
};
|
||||
@@ -145,6 +154,17 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
|
||||
final int wallpaperTransit = getWallpaperTransitType(info);
|
||||
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
|
||||
final TransitionInfo.Change change = info.getChanges().get(i);
|
||||
|
||||
if (info.getType() == TRANSIT_CHANGE && change.getMode() == TRANSIT_CHANGE
|
||||
&& (change.getEndRotation() != change.getStartRotation())
|
||||
&& (change.getFlags() & FLAG_IS_DISPLAY) != 0) {
|
||||
mRotationAnimation = new ScreenRotationAnimation(mContext, mSurfaceSession,
|
||||
mTransactionPool, startTransaction, change, info.getRootLeash());
|
||||
mRotationAnimation.startAnimation(animations, onAnimFinish,
|
||||
mTransitionAnimationScaleSetting, mMainExecutor, mAnimExecutor);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (change.getMode() == TRANSIT_CHANGE) {
|
||||
// No default animation for this, so just update bounds/position.
|
||||
startTransaction.setPosition(change.getLeash(),
|
||||
@@ -162,8 +182,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
|
||||
|
||||
Animation a = loadAnimation(info, change, wallpaperTransit);
|
||||
if (a != null) {
|
||||
startAnimInternal(animations, a, change.getLeash(), onAnimFinish,
|
||||
null /* position */);
|
||||
startSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish,
|
||||
mTransactionPool, mMainExecutor, mAnimExecutor, null /* position */);
|
||||
|
||||
if (info.getAnimationOptions() != null) {
|
||||
attachThumbnail(animations, onAnimFinish, change, info.getAnimationOptions());
|
||||
@@ -204,14 +224,24 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
|
||||
final int overrideType = options != null ? options.getType() : ANIM_NONE;
|
||||
final boolean canCustomContainer = isTask ? !sDisableCustomTaskAnimationProperty : true;
|
||||
|
||||
if (type == TRANSIT_RELAUNCH) {
|
||||
a = mTransitionAnimation.createRelaunchAnimation(
|
||||
change.getEndAbsBounds(), mInsets, change.getEndAbsBounds());
|
||||
} else if (type == TRANSIT_KEYGUARD_GOING_AWAY) {
|
||||
if (type == TRANSIT_KEYGUARD_GOING_AWAY) {
|
||||
a = mTransitionAnimation.loadKeyguardExitAnimation(flags,
|
||||
(changeFlags & FLAG_SHOW_WALLPAPER) != 0);
|
||||
} else if (type == TRANSIT_KEYGUARD_UNOCCLUDE) {
|
||||
a = mTransitionAnimation.loadKeyguardUnoccludeAnimation();
|
||||
} else if ((changeFlags & FLAG_IS_VOICE_INTERACTION) != 0) {
|
||||
if (isOpeningType) {
|
||||
a = mTransitionAnimation.loadVoiceActivityOpenAnimation(enter);
|
||||
} else {
|
||||
a = mTransitionAnimation.loadVoiceActivityExitAnimation(enter);
|
||||
}
|
||||
} else if (changeMode == TRANSIT_CHANGE) {
|
||||
// In the absence of a specific adapter, we just want to keep everything stationary.
|
||||
a = new AlphaAnimation(1.f, 1.f);
|
||||
a.setDuration(TransitionAnimation.DEFAULT_APP_TRANSITION_DURATION);
|
||||
} else if (type == TRANSIT_RELAUNCH) {
|
||||
a = mTransitionAnimation.createRelaunchAnimation(
|
||||
change.getEndAbsBounds(), mInsets, change.getEndAbsBounds());
|
||||
} else if (overrideType == ANIM_CUSTOM
|
||||
&& (canCustomContainer || options.getOverrideTaskTransition())) {
|
||||
a = mTransitionAnimation.loadAnimationRes(options.getPackageName(), enter
|
||||
@@ -231,6 +261,9 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
|
||||
a = mTransitionAnimation.createThumbnailEnterExitAnimationLocked(enter, scaleUp,
|
||||
change.getEndAbsBounds(), type, wallpaperTransit, options.getThumbnail(),
|
||||
options.getTransitionBounds());
|
||||
} else if ((changeFlags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0 && isOpeningType) {
|
||||
// This received a transferred starting window, so don't animate
|
||||
return null;
|
||||
} else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_OPEN) {
|
||||
a = mTransitionAnimation.loadDefaultAnimationAttr(enter
|
||||
? R.styleable.WindowAnimation_wallpaperIntraOpenEnterAnimation
|
||||
@@ -247,15 +280,6 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
|
||||
a = mTransitionAnimation.loadDefaultAnimationAttr(enter
|
||||
? R.styleable.WindowAnimation_wallpaperCloseEnterAnimation
|
||||
: R.styleable.WindowAnimation_wallpaperCloseExitAnimation);
|
||||
} else if ((changeFlags & FLAG_IS_VOICE_INTERACTION) != 0) {
|
||||
if (isOpeningType) {
|
||||
a = mTransitionAnimation.loadVoiceActivityOpenAnimation(enter);
|
||||
} else {
|
||||
a = mTransitionAnimation.loadVoiceActivityExitAnimation(enter);
|
||||
}
|
||||
} else if ((changeFlags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0 && isOpeningType) {
|
||||
// This received a transferred starting window, so don't animate
|
||||
return null;
|
||||
} else if (type == TRANSIT_OPEN) {
|
||||
if (isTask) {
|
||||
a = mTransitionAnimation.loadDefaultAnimationAttr(enter
|
||||
@@ -294,10 +318,6 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
|
||||
a = mTransitionAnimation.loadDefaultAnimationAttr(enter
|
||||
? R.styleable.WindowAnimation_taskToBackEnterAnimation
|
||||
: R.styleable.WindowAnimation_taskToBackExitAnimation);
|
||||
} else if (changeMode == TRANSIT_CHANGE) {
|
||||
// In the absence of a specific adapter, we just want to keep everything stationary.
|
||||
a = new AlphaAnimation(1.f, 1.f);
|
||||
a.setDuration(TransitionAnimation.DEFAULT_APP_TRANSITION_DURATION);
|
||||
}
|
||||
|
||||
if (a != null) {
|
||||
@@ -311,10 +331,12 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
|
||||
return a;
|
||||
}
|
||||
|
||||
private void startAnimInternal(@NonNull ArrayList<Animator> animations, @NonNull Animation anim,
|
||||
@NonNull SurfaceControl leash, @NonNull Runnable finishCallback,
|
||||
static void startSurfaceAnimation(@NonNull ArrayList<Animator> animations,
|
||||
@NonNull Animation anim, @NonNull SurfaceControl leash,
|
||||
@NonNull Runnable finishCallback, @NonNull TransactionPool pool,
|
||||
@NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor,
|
||||
@Nullable Point position) {
|
||||
final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
|
||||
final SurfaceControl.Transaction transaction = pool.acquire();
|
||||
final ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
|
||||
final Transformation transformation = new Transformation();
|
||||
final float[] matrix = new float[9];
|
||||
@@ -332,8 +354,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
|
||||
applyTransformation(va.getDuration(), transaction, leash, anim, transformation, matrix,
|
||||
position);
|
||||
|
||||
mTransactionPool.release(transaction);
|
||||
mMainExecutor.execute(() -> {
|
||||
pool.release(transaction);
|
||||
mainExecutor.execute(() -> {
|
||||
animations.remove(va);
|
||||
finishCallback.run();
|
||||
});
|
||||
@@ -350,7 +372,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
|
||||
}
|
||||
});
|
||||
animations.add(va);
|
||||
mAnimExecutor.execute(va::start);
|
||||
animExecutor.execute(va::start);
|
||||
}
|
||||
|
||||
private void attachThumbnail(@NonNull ArrayList<Animator> animations,
|
||||
@@ -398,8 +420,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
|
||||
};
|
||||
a.restrictDuration(MAX_ANIMATION_DURATION);
|
||||
a.scaleCurrentDuration(mTransitionAnimationScaleSetting);
|
||||
startAnimInternal(animations, a, wt.getSurface(), finisher,
|
||||
new Point(bounds.left, bounds.top));
|
||||
startSurfaceAnimation(animations, a, wt.getSurface(), finisher, mTransactionPool,
|
||||
mMainExecutor, mAnimExecutor, new Point(bounds.left, bounds.top));
|
||||
}
|
||||
|
||||
private void attachThumbnailAnimation(@NonNull ArrayList<Animator> animations,
|
||||
@@ -422,7 +444,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
|
||||
};
|
||||
a.restrictDuration(MAX_ANIMATION_DURATION);
|
||||
a.scaleCurrentDuration(mTransitionAnimationScaleSetting);
|
||||
startAnimInternal(animations, a, wt.getSurface(), finisher, null /* position */);
|
||||
startSurfaceAnimation(animations, a, wt.getSurface(), finisher, mTransactionPool,
|
||||
mMainExecutor, mAnimExecutor, null /* position */);
|
||||
}
|
||||
|
||||
private static int getWallpaperTransitType(TransitionInfo info) {
|
||||
|
||||
@@ -0,0 +1,497 @@
|
||||
/*
|
||||
* 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.hardware.HardwareBuffer.RGBA_8888;
|
||||
import static android.hardware.HardwareBuffer.USAGE_PROTECTED_CONTENT;
|
||||
import static android.util.RotationUtils.deltaRotation;
|
||||
|
||||
import static com.android.wm.shell.transition.DefaultTransitionHandler.startSurfaceAnimation;
|
||||
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.ColorSpace;
|
||||
import android.graphics.GraphicBuffer;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Rect;
|
||||
import android.hardware.HardwareBuffer;
|
||||
import android.media.Image;
|
||||
import android.media.ImageReader;
|
||||
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.TransitionInfo;
|
||||
|
||||
import com.android.internal.R;
|
||||
import com.android.wm.shell.common.ShellExecutor;
|
||||
import com.android.wm.shell.common.TransactionPool;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* This class handles the rotation animation when the device is rotated.
|
||||
*
|
||||
* <p>
|
||||
* The screen rotation animation is composed of 4 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
|
||||
*
|
||||
* <li> The entering Blackframe: <p>
|
||||
* The enter Blackframe is similar to the exit Blackframe but is only used when a custom
|
||||
* rotation animation is used and matches the new content size instead of the screenshot.
|
||||
* </ul>
|
||||
*/
|
||||
class ScreenRotationAnimation {
|
||||
|
||||
/** How much to multiply the policy's type layer, to reserve room
|
||||
* for multiple windows of the same type and Z-ordering adjustment
|
||||
* with TYPE_LAYER_OFFSET. */
|
||||
static final int TYPE_LAYER_MULTIPLIER = 10000;
|
||||
static final int WINDOW_FREEZE_LAYER = TYPE_LAYER_MULTIPLIER * 200;
|
||||
|
||||
/*
|
||||
* Layers for screen rotation animation. We put these layers above
|
||||
* WINDOW_FREEZE_LAYER so that screen freeze will cover all windows.
|
||||
*/
|
||||
private static final int SCREEN_FREEZE_LAYER_BASE = WINDOW_FREEZE_LAYER + TYPE_LAYER_MULTIPLIER;
|
||||
|
||||
static final int MAX_ANIMATION_DURATION = 10 * 1000;
|
||||
|
||||
private final Context mContext;
|
||||
private final TransactionPool mTransactionPool;
|
||||
private final float[] mTmpFloats = new float[9];
|
||||
// Complete transformations being applied.
|
||||
private final Matrix mSnapshotInitialMatrix = new Matrix();
|
||||
/** The leash of display. */
|
||||
private final SurfaceControl mSurfaceControl;
|
||||
private final Rect mStartBounds = new Rect();
|
||||
private final Rect mEndBounds = new Rect();
|
||||
|
||||
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 SurfaceControl mAnimLeash;
|
||||
private Transaction mTransaction;
|
||||
|
||||
// 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;
|
||||
|
||||
/** 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) {
|
||||
mContext = context;
|
||||
mTransactionPool = pool;
|
||||
|
||||
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();
|
||||
|
||||
mStartBounds.set(change.getStartAbsBounds());
|
||||
mEndBounds.set(change.getEndAbsBounds());
|
||||
|
||||
mAnimLeash = new SurfaceControl.Builder(session)
|
||||
.setParent(rootLeash)
|
||||
.setEffectLayer()
|
||||
.setCallsite("ShellRotationAnimation")
|
||||
.setName("Animation leash of screenshot rotation")
|
||||
.build();
|
||||
|
||||
try {
|
||||
SurfaceControl.LayerCaptureArgs args =
|
||||
new SurfaceControl.LayerCaptureArgs.Builder(mSurfaceControl)
|
||||
.setCaptureSecureLayers(true)
|
||||
.setAllowProtected(true)
|
||||
.setSourceCrop(new Rect(0, 0, mStartWidth, mStartHeight))
|
||||
.build();
|
||||
SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =
|
||||
SurfaceControl.captureLayers(args);
|
||||
if (screenshotBuffer == null) {
|
||||
Slog.w(TAG, "Unable to take screenshot of display");
|
||||
return;
|
||||
}
|
||||
|
||||
mBackColorSurface = new SurfaceControl.Builder(session)
|
||||
.setParent(rootLeash)
|
||||
.setColorLayer()
|
||||
.setCallsite("ShellRotationAnimation")
|
||||
.setName("BackColorSurface")
|
||||
.build();
|
||||
|
||||
mScreenshotLayer = new SurfaceControl.Builder(session)
|
||||
.setParent(mAnimLeash)
|
||||
.setBLASTLayer()
|
||||
.setSecure(screenshotBuffer.containsSecureLayers())
|
||||
.setCallsite("ShellRotationAnimation")
|
||||
.setName("RotationLayer")
|
||||
.build();
|
||||
|
||||
HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer();
|
||||
mStartLuma = getMedianBorderLuma(hardwareBuffer, screenshotBuffer.getColorSpace());
|
||||
|
||||
GraphicBuffer buffer = GraphicBuffer.createFromHardwareBuffer(
|
||||
screenshotBuffer.getHardwareBuffer());
|
||||
|
||||
t.setLayer(mBackColorSurface, -1);
|
||||
t.setColor(mBackColorSurface, new float[]{mStartLuma, mStartLuma, mStartLuma});
|
||||
t.setAlpha(mBackColorSurface, 1);
|
||||
t.show(mBackColorSurface);
|
||||
|
||||
t.setPosition(mAnimLeash, 0, 0);
|
||||
t.setAlpha(mAnimLeash, 1);
|
||||
t.show(mAnimLeash);
|
||||
|
||||
t.setLayer(mScreenshotLayer, SCREEN_FREEZE_LAYER_BASE);
|
||||
t.setBuffer(mScreenshotLayer, buffer);
|
||||
t.setColorSpace(mScreenshotLayer, screenshotBuffer.getColorSpace());
|
||||
t.show(mScreenshotLayer);
|
||||
|
||||
} catch (Surface.OutOfResourcesException e) {
|
||||
Slog.w(TAG, "Unable to allocate freeze surface", e);
|
||||
}
|
||||
|
||||
setRotation(t);
|
||||
t.apply();
|
||||
}
|
||||
|
||||
private void setRotation(SurfaceControl.Transaction t) {
|
||||
// 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.
|
||||
int delta = deltaRotation(mEndRotation, mStartRotation);
|
||||
createRotationMatrix(delta, mStartWidth, mStartHeight, mSnapshotInitialMatrix);
|
||||
setRotationTransform(t, mSnapshotInitialMatrix);
|
||||
}
|
||||
|
||||
private void setRotationTransform(SurfaceControl.Transaction t, Matrix matrix) {
|
||||
if (mScreenshotLayer == null) {
|
||||
return;
|
||||
}
|
||||
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]);
|
||||
|
||||
t.setAlpha(mScreenshotLayer, (float) 1.0);
|
||||
t.show(mScreenshotLayer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if animating.
|
||||
*/
|
||||
public boolean startAnimation(@NonNull ArrayList<Animator> animations,
|
||||
@NonNull Runnable finishCallback, float animationScale,
|
||||
@NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
|
||||
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);
|
||||
|
||||
// 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);
|
||||
|
||||
mTransaction = mTransactionPool.acquire();
|
||||
startDisplayRotation(animations, finishCallback, mainExecutor, animExecutor);
|
||||
startScreenshotRotationAnimation(animations, finishCallback, mainExecutor, animExecutor);
|
||||
//startColorAnimation(mTransaction, animationScale);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void startDisplayRotation(@NonNull ArrayList<Animator> animations,
|
||||
@NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor,
|
||||
@NonNull ShellExecutor animExecutor) {
|
||||
startSurfaceAnimation(animations, mRotateEnterAnimation, mSurfaceControl, finishCallback,
|
||||
mTransactionPool, mainExecutor, animExecutor, null /* position */);
|
||||
}
|
||||
|
||||
private void startScreenshotRotationAnimation(@NonNull ArrayList<Animator> animations,
|
||||
@NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor,
|
||||
@NonNull ShellExecutor animExecutor) {
|
||||
startSurfaceAnimation(animations, mRotateExitAnimation, mAnimLeash, finishCallback,
|
||||
mTransactionPool, mainExecutor, animExecutor, null /* position */);
|
||||
}
|
||||
|
||||
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() {
|
||||
Transaction t = mTransaction != null ? mTransaction : mTransactionPool.acquire();
|
||||
if (mAnimLeash.isValid()) {
|
||||
t.remove(mAnimLeash);
|
||||
}
|
||||
|
||||
if (mScreenshotLayer != null) {
|
||||
if (mScreenshotLayer.isValid()) {
|
||||
t.remove(mScreenshotLayer);
|
||||
}
|
||||
mScreenshotLayer = null;
|
||||
|
||||
if (mBackColorSurface != null) {
|
||||
if (mBackColorSurface.isValid()) {
|
||||
t.remove(mBackColorSurface);
|
||||
}
|
||||
mBackColorSurface = null;
|
||||
}
|
||||
}
|
||||
t.apply();
|
||||
mTransactionPool.release(t);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the provided {@link HardwareBuffer} and converts it to a bitmap to then sample the
|
||||
* luminance at the borders of the bitmap
|
||||
* @return the average luminance of all the pixels at the borders of the bitmap
|
||||
*/
|
||||
private static float getMedianBorderLuma(HardwareBuffer hardwareBuffer, ColorSpace colorSpace) {
|
||||
// Cannot read content from buffer with protected usage.
|
||||
if (hardwareBuffer == null || hardwareBuffer.getFormat() != RGBA_8888
|
||||
|| hasProtectedContent(hardwareBuffer)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ImageReader ir = ImageReader.newInstance(hardwareBuffer.getWidth(),
|
||||
hardwareBuffer.getHeight(), hardwareBuffer.getFormat(), 1);
|
||||
ir.getSurface().attachAndQueueBufferWithColorSpace(hardwareBuffer, colorSpace);
|
||||
Image image = ir.acquireLatestImage();
|
||||
if (image == null || image.getPlanes().length == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
Image.Plane plane = image.getPlanes()[0];
|
||||
ByteBuffer buffer = plane.getBuffer();
|
||||
int width = image.getWidth();
|
||||
int height = image.getHeight();
|
||||
int pixelStride = plane.getPixelStride();
|
||||
int rowStride = plane.getRowStride();
|
||||
float[] borderLumas = new float[2 * width + 2 * height];
|
||||
|
||||
// Grab the top and bottom borders
|
||||
int l = 0;
|
||||
for (int x = 0; x < width; x++) {
|
||||
borderLumas[l++] = getPixelLuminance(buffer, x, 0, pixelStride, rowStride);
|
||||
borderLumas[l++] = getPixelLuminance(buffer, x, height - 1, pixelStride, rowStride);
|
||||
}
|
||||
|
||||
// Grab the left and right borders
|
||||
for (int y = 0; y < height; y++) {
|
||||
borderLumas[l++] = getPixelLuminance(buffer, 0, y, pixelStride, rowStride);
|
||||
borderLumas[l++] = getPixelLuminance(buffer, width - 1, y, pixelStride, rowStride);
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
ir.close();
|
||||
|
||||
// Oh, is this too simple and inefficient for you?
|
||||
// How about implementing a O(n) solution? https://en.wikipedia.org/wiki/Median_of_medians
|
||||
Arrays.sort(borderLumas);
|
||||
return borderLumas[borderLumas.length / 2];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether the hardwareBuffer passed in is marked as protected.
|
||||
*/
|
||||
private static boolean hasProtectedContent(HardwareBuffer hardwareBuffer) {
|
||||
return (hardwareBuffer.getUsage() & USAGE_PROTECTED_CONTENT) == USAGE_PROTECTED_CONTENT;
|
||||
}
|
||||
|
||||
private static float getPixelLuminance(ByteBuffer buffer, int x, int y,
|
||||
int pixelStride, int rowStride) {
|
||||
int offset = y * rowStride + x * pixelStride;
|
||||
int pixel = 0;
|
||||
pixel |= (buffer.get(offset) & 0xff) << 16; // R
|
||||
pixel |= (buffer.get(offset + 1) & 0xff) << 8; // G
|
||||
pixel |= (buffer.get(offset + 2) & 0xff); // B
|
||||
pixel |= (buffer.get(offset + 3) & 0xff) << 24; // A
|
||||
return Color.valueOf(pixel).luminance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the average border luma by taking a screenshot of the {@param surfaceControl}.
|
||||
* @see #getMedianBorderLuma(HardwareBuffer, ColorSpace)
|
||||
*/
|
||||
private static float getLumaOfSurfaceControl(Rect bounds, SurfaceControl surfaceControl) {
|
||||
if (surfaceControl == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
Rect crop = new Rect(0, 0, bounds.width(), bounds.height());
|
||||
SurfaceControl.ScreenshotHardwareBuffer buffer =
|
||||
SurfaceControl.captureLayers(surfaceControl, crop, 1);
|
||||
if (buffer == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return getMedianBorderLuma(buffer.getHardwareBuffer(), buffer.getColorSpace());
|
||||
}
|
||||
|
||||
private static void createRotationMatrix(int rotation, int width, int height,
|
||||
Matrix outMatrix) {
|
||||
switch (rotation) {
|
||||
case Surface.ROTATION_0:
|
||||
outMatrix.reset();
|
||||
break;
|
||||
case Surface.ROTATION_90:
|
||||
outMatrix.setRotate(90, 0, 0);
|
||||
outMatrix.postTranslate(height, 0);
|
||||
break;
|
||||
case Surface.ROTATION_180:
|
||||
outMatrix.setRotate(180, 0, 0);
|
||||
outMatrix.postTranslate(width, height);
|
||||
break;
|
||||
case Surface.ROTATION_270:
|
||||
outMatrix.setRotate(270, 0, 0);
|
||||
outMatrix.postTranslate(0, width);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,7 @@ import static android.view.WindowManager.TRANSIT_OPEN;
|
||||
import static android.view.WindowManager.TRANSIT_TO_BACK;
|
||||
import static android.view.WindowManager.TRANSIT_TO_FRONT;
|
||||
import static android.view.WindowManager.transitTypeToString;
|
||||
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
|
||||
import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION;
|
||||
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
|
||||
import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
|
||||
@@ -1097,6 +1098,10 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
|
||||
flags |= FLAG_IS_VOICE_INTERACTION;
|
||||
}
|
||||
}
|
||||
final DisplayContent dc = wc.asDisplayContent();
|
||||
if (dc != null) {
|
||||
flags |= FLAG_IS_DISPLAY;
|
||||
}
|
||||
if (isWallpaper(wc)) {
|
||||
flags |= FLAG_IS_WALLPAPER;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user