blob: 8ab5043dc5d9d800b960cf172e33e6d341ddb4aa [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.wm;
import static com.android.server.wm.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_SCREEN_ROTATION;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.GraphicBuffer;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.view.Surface;
import android.view.SurfaceControl;
import com.android.server.protolog.common.ProtoLog;
import java.util.function.Supplier;
/**
* This class handles "freezing" of an Animatable. The Animatable in question should implement
* Freezable.
*
* The point of this is to enable WindowContainers to each be capable of freezing themselves.
* Freezing means taking a snapshot and placing it above everything in the sub-hierarchy.
* The "placing above" requires that a parent surface be inserted above the target surface so that
* the target surface and the snapshot are siblings.
*
* The overall flow for a transition using this would be:
* 1. Set transition and record animatable in mChangingApps
* 2. Call {@link #freeze} to set-up the leashes and cover with a snapshot.
* 3. When transition participants are ready, start SurfaceAnimator with this as a parameter
* 4. SurfaceAnimator will then {@link #takeLeashForAnimation} instead of creating another leash.
* 5. The animation system should eventually clean this up via {@link #unfreeze}.
*/
class SurfaceFreezer {
private final Freezable mAnimatable;
private final WindowManagerService mWmService;
private SurfaceControl mLeash;
Snapshot mSnapshot = null;
final Rect mFreezeBounds = new Rect();
/**
* @param animatable The object to animate.
*/
SurfaceFreezer(Freezable animatable, WindowManagerService service) {
mAnimatable = animatable;
mWmService = service;
}
/**
* Freeze the target surface. This is done by creating a leash (inserting a parent surface
* above the target surface) and then taking a snapshot and placing it over the target surface.
*
* @param startBounds The original bounds (on screen) of the surface we are snapshotting.
*/
void freeze(SurfaceControl.Transaction t, Rect startBounds) {
mFreezeBounds.set(startBounds);
mLeash = SurfaceAnimator.createAnimationLeash(mAnimatable, mAnimatable.getSurfaceControl(),
t, ANIMATION_TYPE_SCREEN_ROTATION, startBounds.width(), startBounds.height(),
startBounds.left, startBounds.top, false /* hidden */,
mWmService.mTransactionFactory);
mAnimatable.onAnimationLeashCreated(t, mLeash);
SurfaceControl freezeTarget = mAnimatable.getFreezeSnapshotTarget();
if (freezeTarget != null) {
GraphicBuffer snapshot = createSnapshotBuffer(freezeTarget, startBounds);
if (snapshot != null) {
mSnapshot = new Snapshot(mWmService.mSurfaceFactory, t, snapshot, mLeash);
}
}
}
/**
* Used by {@link SurfaceAnimator}. This "transfers" the leash to be used for animation.
* By transferring the leash, this will no longer try to clean-up the leash when finished.
*/
SurfaceControl takeLeashForAnimation() {
SurfaceControl out = mLeash;
mLeash = null;
return out;
}
/**
* Clean-up the snapshot and remove leash. If the leash was taken, this just cleans-up the
* snapshot.
*/
void unfreeze(SurfaceControl.Transaction t) {
if (mSnapshot != null) {
mSnapshot.cancelAnimation(t, false /* restarting */);
}
if (mLeash == null) {
return;
}
SurfaceControl leash = mLeash;
mLeash = null;
final boolean scheduleAnim = SurfaceAnimator.removeLeash(t, mAnimatable, leash,
false /* destroy */);
if (scheduleAnim) {
mWmService.scheduleAnimationLocked();
}
}
boolean hasLeash() {
return mLeash != null;
}
private static GraphicBuffer createSnapshotBuffer(@NonNull SurfaceControl target,
@Nullable Rect bounds) {
Rect cropBounds = null;
if (bounds != null) {
cropBounds = new Rect(bounds);
cropBounds.offsetTo(0, 0);
}
final SurfaceControl.ScreenshotGraphicBuffer screenshotBuffer =
SurfaceControl.captureLayers(
target, cropBounds, 1.f /* frameScale */, PixelFormat.RGBA_8888);
final GraphicBuffer buffer = screenshotBuffer != null ? screenshotBuffer.getGraphicBuffer()
: null;
if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) {
return null;
}
return buffer;
}
class Snapshot {
private SurfaceControl mSurfaceControl;
private AnimationAdapter mAnimation;
private SurfaceAnimator.OnAnimationFinishedCallback mFinishedCallback;
/**
* @param t Transaction to create the thumbnail in.
* @param thumbnailHeader A thumbnail or placeholder for thumbnail to initialize with.
*/
Snapshot(Supplier<Surface> surfaceFactory, SurfaceControl.Transaction t,
GraphicBuffer thumbnailHeader, SurfaceControl parent) {
Surface drawSurface = surfaceFactory.get();
// We can't use a delegating constructor since we need to
// reference this::onAnimationFinished
final int width = thumbnailHeader.getWidth();
final int height = thumbnailHeader.getHeight();
mSurfaceControl = mAnimatable.makeAnimationLeash()
.setName("snapshot anim: " + mAnimatable.toString())
.setBufferSize(width, height)
.setFormat(PixelFormat.TRANSLUCENT)
.setParent(parent)
.build();
ProtoLog.i(WM_SHOW_TRANSACTIONS, " THUMBNAIL %s: CREATE", mSurfaceControl);
// Transfer the thumbnail to the surface
drawSurface.copyFrom(mSurfaceControl);
drawSurface.attachAndQueueBuffer(thumbnailHeader);
drawSurface.release();
t.show(mSurfaceControl);
// We parent the thumbnail to the container, and just place it on top of anything else
// in the container.
t.setLayer(mSurfaceControl, Integer.MAX_VALUE);
}
void destroy(SurfaceControl.Transaction t) {
if (mSurfaceControl == null) {
return;
}
t.remove(mSurfaceControl);
mSurfaceControl = null;
}
/**
* Starts an animation.
*
* @param anim The object that bridges the controller, {@link SurfaceAnimator}, with the
* component responsible for running the animation. It runs the animation with
* {@link AnimationAdapter#startAnimation} once the hierarchy with
* the Leash has been set up.
* @param animationFinishedCallback The callback being triggered when the animation
* finishes.
*/
void startAnimation(SurfaceControl.Transaction t, AnimationAdapter anim, int type,
@Nullable SurfaceAnimator.OnAnimationFinishedCallback animationFinishedCallback) {
cancelAnimation(t, true /* restarting */);
mAnimation = anim;
mFinishedCallback = animationFinishedCallback;
if (mSurfaceControl == null) {
cancelAnimation(t, false /* restarting */);
return;
}
mAnimation.startAnimation(mSurfaceControl, t, type, animationFinishedCallback);
}
/**
* Cancels the animation, and resets the leash.
*
* @param t The transaction to use for all cancelling surface operations.
* @param restarting Whether we are restarting the animation.
*/
void cancelAnimation(SurfaceControl.Transaction t, boolean restarting) {
final SurfaceControl leash = mSurfaceControl;
final AnimationAdapter animation = mAnimation;
final SurfaceAnimator.OnAnimationFinishedCallback animationFinishedCallback =
mFinishedCallback;
mAnimation = null;
mFinishedCallback = null;
if (animation != null) {
animation.onAnimationCancelled(leash);
if (!restarting) {
if (animationFinishedCallback != null) {
animationFinishedCallback.onAnimationFinished(
ANIMATION_TYPE_APP_TRANSITION, animation);
}
}
}
if (!restarting) {
destroy(t);
}
}
}
/** freezable */
public interface Freezable extends SurfaceAnimator.Animatable {
/**
* @return The surface to take a snapshot of. If this returns {@code null}, no snapshot
* will be generated (but the rest of the freezing logic will still happen).
*/
@Nullable SurfaceControl getFreezeSnapshotTarget();
}
}