blob: bbc35a35e93f172e81361cf30c8daf6240107c3a [file] [log] [blame]
/*
* Copyright (C) 2017 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.server.wm;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.util.TimeUtils.NANOS_PER_MS;
import static android.view.Choreographer.CALLBACK_TRAVERSAL;
import static android.view.Choreographer.getSfInstance;
import android.animation.AnimationHandler;
import android.animation.AnimationHandler.AnimationFrameCallbackProvider;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.Nullable;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Insets;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.hardware.power.Boost;
import android.os.Handler;
import android.os.PowerManagerInternal;
import android.os.Trace;
import android.util.ArrayMap;
import android.util.Log;
import android.view.Choreographer;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.window.ScreenCapture;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.server.AnimationThread;
import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Supplier;
/**
* Class to run animations without holding the window manager lock.
*/
class SurfaceAnimationRunner {
private static final String TAG = SurfaceAnimationRunner.class.getSimpleName();
private final Object mLock = new Object();
/**
* Lock for cancelling animations. Must be acquired on it's own, or after acquiring
* {@link #mLock}
*/
private final Object mCancelLock = new Object();
/**
* Lock for synchronizing {@link #mEdgeExtensions} to prevent race conditions when managing
* created edge extension surfaces.
*/
private final Object mEdgeExtensionLock = new Object();
@VisibleForTesting
Choreographer mChoreographer;
private final Handler mAnimationThreadHandler = AnimationThread.getHandler();
private final Handler mSurfaceAnimationHandler = SurfaceAnimationThread.getHandler();
private final Runnable mApplyTransactionRunnable = this::applyTransaction;
private final AnimationHandler mAnimationHandler;
private final Transaction mFrameTransaction;
private final AnimatorFactory mAnimatorFactory;
private final PowerManagerInternal mPowerManagerInternal;
private boolean mApplyScheduled;
// Executor to perform the edge extension.
// With two threads because in practice we will want to extend two surfaces in one animation,
// in which case we want to be able to parallelize those two extensions to cut down latency in
// starting the animation.
private final ExecutorService mEdgeExtensionExecutor = Executors.newFixedThreadPool(2);
@GuardedBy("mLock")
@VisibleForTesting
final ArrayMap<SurfaceControl, RunningAnimation> mPendingAnimations = new ArrayMap<>();
@GuardedBy("mLock")
@VisibleForTesting
final ArrayMap<SurfaceControl, RunningAnimation> mPreProcessingAnimations = new ArrayMap<>();
@GuardedBy("mLock")
@VisibleForTesting
final ArrayMap<SurfaceControl, RunningAnimation> mRunningAnimations = new ArrayMap<>();
@GuardedBy("mLock")
private boolean mAnimationStartDeferred;
// Mapping animation leashes to a list of edge extension surfaces associated with them
@GuardedBy("mEdgeExtensionLock")
private final ArrayMap<SurfaceControl, ArrayList<SurfaceControl>> mEdgeExtensions =
new ArrayMap<>();
/**
* There should only ever be one instance of this class. Usual spot for it is with
* {@link WindowManagerService}
*/
SurfaceAnimationRunner(Supplier<Transaction> transactionFactory,
PowerManagerInternal powerManagerInternal) {
this(null /* callbackProvider */, null /* animatorFactory */,
transactionFactory.get(), powerManagerInternal);
}
@VisibleForTesting
SurfaceAnimationRunner(@Nullable AnimationFrameCallbackProvider callbackProvider,
AnimatorFactory animatorFactory, Transaction frameTransaction,
PowerManagerInternal powerManagerInternal) {
mSurfaceAnimationHandler.runWithScissors(() -> mChoreographer = getSfInstance(),
0 /* timeout */);
mFrameTransaction = frameTransaction;
mAnimationHandler = new AnimationHandler();
mAnimationHandler.setProvider(callbackProvider != null
? callbackProvider
: new SfVsyncFrameCallbackProvider(mChoreographer));
mAnimatorFactory = animatorFactory != null
? animatorFactory
: SfValueAnimator::new;
mPowerManagerInternal = powerManagerInternal;
}
/**
* Defers starting of animations until {@link #continueStartingAnimations} is called. This
* method is NOT nestable.
*
* @see #continueStartingAnimations
*/
void deferStartingAnimations() {
synchronized (mLock) {
mAnimationStartDeferred = true;
}
}
/**
* Continues starting of animations.
*
* @see #deferStartingAnimations
*/
void continueStartingAnimations() {
synchronized (mLock) {
mAnimationStartDeferred = false;
if (!mPendingAnimations.isEmpty() && mPreProcessingAnimations.isEmpty()) {
mChoreographer.postFrameCallback(this::startAnimations);
}
}
}
void startAnimation(AnimationSpec a, SurfaceControl animationLeash, Transaction t,
Runnable finishCallback) {
synchronized (mLock) {
final RunningAnimation runningAnim = new RunningAnimation(a, animationLeash,
finishCallback);
boolean requiresEdgeExtension = requiresEdgeExtension(a);
if (requiresEdgeExtension) {
final ArrayList<SurfaceControl> extensionSurfaces = new ArrayList<>();
synchronized (mEdgeExtensionLock) {
mEdgeExtensions.put(animationLeash, extensionSurfaces);
}
mPreProcessingAnimations.put(animationLeash, runningAnim);
// We must wait for t to be committed since otherwise the leash doesn't have the
// windows we want to screenshot and extend as children.
t.addTransactionCommittedListener(mEdgeExtensionExecutor, () -> {
if (!animationLeash.isValid()) {
Log.e(TAG, "Animation leash is not valid");
synchronized (mEdgeExtensionLock) {
mEdgeExtensions.remove(animationLeash);
}
synchronized (mLock) {
mPreProcessingAnimations.remove(animationLeash);
}
return;
}
final WindowAnimationSpec animationSpec = a.asWindowAnimationSpec();
final Transaction edgeExtensionCreationTransaction = new Transaction();
edgeExtendWindow(animationLeash,
animationSpec.getRootTaskBounds(), animationSpec.getAnimation(),
edgeExtensionCreationTransaction);
synchronized (mLock) {
// only run if animation is not yet canceled by this point
if (mPreProcessingAnimations.get(animationLeash) == runningAnim) {
// In the case the animation is cancelled, edge extensions are removed
// onAnimationLeashLost which is called before onAnimationCancelled.
// So we need to check if the edge extensions have already been removed
// or not, and if so we don't want to apply the transaction.
synchronized (mEdgeExtensionLock) {
if (!mEdgeExtensions.isEmpty()) {
edgeExtensionCreationTransaction.apply();
}
}
mPreProcessingAnimations.remove(animationLeash);
mPendingAnimations.put(animationLeash, runningAnim);
if (!mAnimationStartDeferred && mPreProcessingAnimations.isEmpty()) {
mChoreographer.postFrameCallback(this::startAnimations);
}
}
}
});
}
if (!requiresEdgeExtension) {
mPendingAnimations.put(animationLeash, runningAnim);
if (!mAnimationStartDeferred && mPreProcessingAnimations.isEmpty()) {
mChoreographer.postFrameCallback(this::startAnimations);
}
}
// Some animations (e.g. move animations) require the initial transform to be
// applied immediately.
applyTransformation(runningAnim, t, 0 /* currentPlayTime */);
}
}
private boolean requiresEdgeExtension(AnimationSpec a) {
return a.asWindowAnimationSpec() != null && a.asWindowAnimationSpec().hasExtension();
}
void onAnimationCancelled(SurfaceControl leash) {
synchronized (mLock) {
if (mPendingAnimations.containsKey(leash)) {
mPendingAnimations.remove(leash);
return;
}
if (mPreProcessingAnimations.containsKey(leash)) {
mPreProcessingAnimations.remove(leash);
return;
}
final RunningAnimation anim = mRunningAnimations.get(leash);
if (anim != null) {
mRunningAnimations.remove(leash);
synchronized (mCancelLock) {
anim.mCancelled = true;
}
mSurfaceAnimationHandler.post(() -> {
anim.mAnim.cancel();
applyTransaction();
});
}
}
}
@GuardedBy("mLock")
private void startPendingAnimationsLocked() {
for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
startAnimationLocked(mPendingAnimations.valueAt(i));
}
mPendingAnimations.clear();
}
@GuardedBy("mLock")
private void startAnimationLocked(RunningAnimation a) {
final ValueAnimator anim = mAnimatorFactory.makeAnimator();
// Animation length is already expected to be scaled.
anim.overrideDurationScale(1.0f);
anim.setDuration(a.mAnimSpec.getDuration());
anim.addUpdateListener(animation -> {
synchronized (mCancelLock) {
if (!a.mCancelled) {
final long duration = anim.getDuration();
long currentPlayTime = anim.getCurrentPlayTime();
if (currentPlayTime > duration) {
currentPlayTime = duration;
}
applyTransformation(a, mFrameTransaction, currentPlayTime);
}
}
// Transaction will be applied in the commit phase.
scheduleApplyTransaction();
});
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
synchronized (mCancelLock) {
if (!a.mCancelled) {
// TODO: change this back to use show instead of alpha when b/138459974 is
// fixed.
mFrameTransaction.setAlpha(a.mLeash, 1);
}
}
}
@Override
public void onAnimationEnd(Animator animation) {
synchronized (mLock) {
mRunningAnimations.remove(a.mLeash);
synchronized (mCancelLock) {
if (!a.mCancelled) {
// Post on other thread that we can push final state without jank.
mAnimationThreadHandler.post(a.mFinishCallback);
}
}
}
}
});
a.mAnim = anim;
mRunningAnimations.put(a.mLeash, a);
anim.start();
if (a.mAnimSpec.canSkipFirstFrame()) {
// If we can skip the first frame, we start one frame later.
anim.setCurrentPlayTime(mChoreographer.getFrameIntervalNanos() / NANOS_PER_MS);
}
// Immediately start the animation by manually applying an animation frame. Otherwise, the
// start time would only be set in the next frame, leading to a delay.
anim.doAnimationFrame(mChoreographer.getFrameTime());
}
private void applyTransformation(RunningAnimation a, Transaction t, long currentPlayTime) {
a.mAnimSpec.apply(t, a.mLeash, currentPlayTime);
}
private void startAnimations(long frameTimeNanos) {
synchronized (mLock) {
if (!mPreProcessingAnimations.isEmpty()) {
// We only want to start running animations once all mPreProcessingAnimations have
// been processed to ensure preprocessed animations start in sync.
// NOTE: This means we might delay running animations that require preprocessing if
// new animations that also require preprocessing are requested before the previous
// ones have finished (see b/227449117).
return;
}
startPendingAnimationsLocked();
}
mPowerManagerInternal.setPowerBoost(Boost.INTERACTION, 0);
}
private void scheduleApplyTransaction() {
if (!mApplyScheduled) {
mChoreographer.postCallback(CALLBACK_TRAVERSAL, mApplyTransactionRunnable,
null /* token */);
mApplyScheduled = true;
}
}
private void applyTransaction() {
mFrameTransaction.setAnimationTransaction();
mFrameTransaction.setFrameTimelineVsync(mChoreographer.getVsyncId());
mFrameTransaction.apply();
mApplyScheduled = false;
}
private void edgeExtendWindow(SurfaceControl leash, Rect bounds, Animation a,
Transaction transaction) {
final Transformation transformationAtStart = new Transformation();
a.getTransformationAt(0, transformationAtStart);
final Transformation transformationAtEnd = new Transformation();
a.getTransformationAt(1, transformationAtEnd);
// We want to create an extension surface that is the maximal size and the animation will
// take care of cropping any part that overflows.
final Insets maxExtensionInsets = Insets.min(
transformationAtStart.getInsets(), transformationAtEnd.getInsets());
final int targetSurfaceHeight = bounds.height();
final int targetSurfaceWidth = bounds.width();
if (maxExtensionInsets.left < 0) {
final Rect edgeBounds = new Rect(bounds.left, bounds.top, bounds.left + 1,
bounds.bottom);
final Rect extensionRect = new Rect(0, 0,
-maxExtensionInsets.left, targetSurfaceHeight);
final int xPos = bounds.left + maxExtensionInsets.left;
final int yPos = bounds.top;
createExtensionSurface(leash, edgeBounds,
extensionRect, xPos, yPos, "Left Edge Extension", transaction);
}
if (maxExtensionInsets.top < 0) {
final Rect edgeBounds = new Rect(bounds.left, bounds.top, targetSurfaceWidth,
bounds.top + 1);
final Rect extensionRect = new Rect(0, 0,
targetSurfaceWidth, -maxExtensionInsets.top);
final int xPos = bounds.left;
final int yPos = bounds.top + maxExtensionInsets.top;
createExtensionSurface(leash, edgeBounds,
extensionRect, xPos, yPos, "Top Edge Extension", transaction);
}
if (maxExtensionInsets.right < 0) {
final Rect edgeBounds = new Rect(bounds.right - 1, bounds.top, bounds.right,
bounds.bottom);
final Rect extensionRect = new Rect(0, 0,
-maxExtensionInsets.right, targetSurfaceHeight);
final int xPos = bounds.right;
final int yPos = bounds.top;
createExtensionSurface(leash, edgeBounds,
extensionRect, xPos, yPos, "Right Edge Extension", transaction);
}
if (maxExtensionInsets.bottom < 0) {
final Rect edgeBounds = new Rect(bounds.left, bounds.bottom - 1,
bounds.right, bounds.bottom);
final Rect extensionRect = new Rect(0, 0,
targetSurfaceWidth, -maxExtensionInsets.bottom);
final int xPos = bounds.left;
final int yPos = bounds.bottom;
createExtensionSurface(leash, edgeBounds,
extensionRect, xPos, yPos, "Bottom Edge Extension", transaction);
}
}
private void createExtensionSurface(SurfaceControl leash, Rect edgeBounds,
Rect extensionRect, int xPos, int yPos, String layerName,
Transaction startTransaction) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "createExtensionSurface");
doCreateExtensionSurface(leash, edgeBounds, extensionRect, xPos, yPos, layerName,
startTransaction);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
private void doCreateExtensionSurface(SurfaceControl leash, Rect edgeBounds,
Rect extensionRect, int xPos, int yPos, String layerName,
Transaction startTransaction) {
ScreenCapture.LayerCaptureArgs captureArgs =
new ScreenCapture.LayerCaptureArgs.Builder(leash /* surfaceToExtend */)
.setSourceCrop(edgeBounds)
.setFrameScale(1)
.setPixelFormat(PixelFormat.RGBA_8888)
.setChildrenOnly(true)
.setAllowProtected(true)
.setCaptureSecureLayers(true)
.build();
final ScreenCapture.ScreenshotHardwareBuffer edgeBuffer =
ScreenCapture.captureLayers(captureArgs);
if (edgeBuffer == null) {
// The leash we are trying to screenshot may have been removed by this point, which is
// likely the reason for ending up with a null edgeBuffer, in which case we just want to
// return and do nothing.
Log.e(TAG, "Failed to create edge extension - edge buffer is null");
return;
}
final SurfaceControl edgeExtensionLayer = new SurfaceControl.Builder()
.setName(layerName)
.setHidden(true)
.setCallsite("DefaultTransitionHandler#startAnimation")
.setOpaque(true)
.setBufferSize(extensionRect.width(), extensionRect.height())
.build();
BitmapShader shader = new BitmapShader(edgeBuffer.asBitmap(),
android.graphics.Shader.TileMode.CLAMP,
android.graphics.Shader.TileMode.CLAMP);
final Paint paint = new Paint();
paint.setShader(shader);
final Surface surface = new Surface(edgeExtensionLayer);
Canvas c = surface.lockHardwareCanvas();
c.drawRect(extensionRect, paint);
surface.unlockCanvasAndPost(c);
surface.release();
synchronized (mEdgeExtensionLock) {
if (!mEdgeExtensions.containsKey(leash)) {
// The animation leash has already been removed, so we don't want to attach the
// edgeExtension layer and should immediately remove it instead.
startTransaction.remove(edgeExtensionLayer);
return;
}
startTransaction.reparent(edgeExtensionLayer, leash);
startTransaction.setLayer(edgeExtensionLayer, Integer.MIN_VALUE);
startTransaction.setPosition(edgeExtensionLayer, xPos, yPos);
startTransaction.setVisibility(edgeExtensionLayer, true);
mEdgeExtensions.get(leash).add(edgeExtensionLayer);
}
}
private float getScaleXForExtensionSurface(Rect edgeBounds, Rect extensionRect) {
if (edgeBounds.width() == extensionRect.width()) {
// Top or bottom edge extension, no need to scale the X axis of the extension surface.
return 1;
}
if (edgeBounds.width() == 1) {
// Left or right edge extension, scale the surface to be the extensionRect's width.
return extensionRect.width();
}
throw new RuntimeException("Unexpected edgeBounds and extensionRect widths");
}
private float getScaleYForExtensionSurface(Rect edgeBounds, Rect extensionRect) {
if (edgeBounds.height() == extensionRect.height()) {
// Left or right edge extension, no need to scale the Y axis of the extension surface.
return 1;
}
if (edgeBounds.height() == 1) {
// Top or bottom edge extension, scale the surface to be the extensionRect's height.
return extensionRect.height();
}
throw new RuntimeException("Unexpected edgeBounds and extensionRect heights");
}
private static final class RunningAnimation {
final AnimationSpec mAnimSpec;
final SurfaceControl mLeash;
final Runnable mFinishCallback;
ValueAnimator mAnim;
@GuardedBy("mCancelLock")
private boolean mCancelled;
RunningAnimation(AnimationSpec animSpec, SurfaceControl leash, Runnable finishCallback) {
mAnimSpec = animSpec;
mLeash = leash;
mFinishCallback = finishCallback;
}
}
protected void onAnimationLeashLost(SurfaceControl animationLeash,
Transaction t) {
synchronized (mEdgeExtensionLock) {
if (!mEdgeExtensions.containsKey(animationLeash)) {
return;
}
final ArrayList<SurfaceControl> edgeExtensions = mEdgeExtensions.get(animationLeash);
for (int i = 0; i < edgeExtensions.size(); i++) {
final SurfaceControl extension = edgeExtensions.get(i);
t.remove(extension);
}
mEdgeExtensions.remove(animationLeash);
}
}
@VisibleForTesting
interface AnimatorFactory {
ValueAnimator makeAnimator();
}
/**
* Value animator that uses sf-vsync signal to tick.
*/
private class SfValueAnimator extends ValueAnimator {
SfValueAnimator() {
setFloatValues(0f, 1f);
}
@Override
public AnimationHandler getAnimationHandler() {
return mAnimationHandler;
}
}
}