Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a5e10572 authored by Jorim Jaggi's avatar Jorim Jaggi Committed by Winson Chung
Browse files

Lock free animations (2/2)

Second CL that migrates WSA to use SurfaceAnimator

We start our synchronized app transition journey by showing that
the concept works by using WindowState animations as proof of
concept.

The main class in this CL are SurfaceAnimator and
SurfaceAnimatorRunner. When we start an animation on a Window, we
create a new bufferless surface, called "The Leash", in the
hierarchy and attach the surface of WindowState onto it, while
attaching the leash onto the old surface parent which is still
responsible for z-layering.

Then, we pass off the Leash into SurfaceAnimationRunner, which then
changes the surface properties of Leash in every animation frame,
without holding the WM lock. While it's doing that we can still
update the z-layering of the window, or even relayout the window
of needed - the important surfaces for this are still under WM's
control.

In case the animation is finished the window surface gets
reparented to its original parent, and the leash is abandoned.
Note that the reparenting is done in the same transaction as
processing the animation finish, such that we don't end up with
a flicker in case of a disappearing animation, where the window
surface gets destroyed.

In case the animation needs to be cancelled, WM can revoke control
of the leash by reparenting the window surface. Even if the
cancellation signal is heavily delayed, WM immediately regains
control over the surface by reparenting it within a transaction.

We also introduce the concept of animating a WindowContainer. We
clean up isAnimating:
- isLocalAnimating: is the container itself animating
- isAnimating: is the container or one of its parents animating
- isSelfOrChildAnimating: is local animating or any child
animating.

SurfaceAnimationRunner also needs it's own thread so it's not getting
bogged down by any WM lock contention by processing regular
animation frames. We call that thread android.anim.lf (lockfree).

Now, imagine that SurfaceAnimationAnimator would sit behind an IPC in
another process and instead of animating WindowState, we'd animate
AppWindowToken. Then, synchronized app transitions would be done.

Test: go/wm-smoke
Test: SurfaceAnimatorTest
Test: SurfaceAnimationRunnerTest
Test: WindowContainerTests
Bug: 64674361

Change-Id: Idf59daa90361af57fce1128d19a0c0dbf5971d18
parent 1f823962
Loading
Loading
Loading
Loading
+49 −0
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import android.graphics.Bitmap;
import android.graphics.GraphicBuffer;
import android.graphics.PixelFormat;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
import android.os.IBinder;
@@ -37,8 +38,12 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.os.Process;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.Log;
import android.view.Surface.OutOfResourcesException;

import com.android.internal.annotations.GuardedBy;

import dalvik.system.CloseGuard;
import libcore.util.NativeAllocationRegistry;

@@ -153,6 +158,13 @@ public class SurfaceControl implements Parcelable {
    private final String mName;
    long mNativeObject; // package visibility only for Surface.java access

    // TODO: Move this to native.
    private final Object mSizeLock = new Object();
    @GuardedBy("mSizeLock")
    private int mWidth;
    @GuardedBy("mSizeLock")
    private int mHeight;

    static Transaction sGlobalTransaction;
    static long sTransactionNestCount = 0;

@@ -567,6 +579,8 @@ public class SurfaceControl implements Parcelable {
        }

        mName = name;
        mWidth = w;
        mHeight = h;
        mNativeObject = nativeCreate(session, name, w, h, format, flags,
            parent != null ? parent.mNativeObject : 0, windowType, ownerUid);
        if (mNativeObject == 0) {
@@ -582,6 +596,8 @@ public class SurfaceControl implements Parcelable {
    // event logging.
    public SurfaceControl(SurfaceControl other) {
        mName = other.mName;
        mWidth = other.mWidth;
        mHeight = other.mHeight;
        mNativeObject = other.mNativeObject;
        other.mCloseGuard.close();
        other.mNativeObject = 0;
@@ -590,6 +606,8 @@ public class SurfaceControl implements Parcelable {

    private SurfaceControl(Parcel in) {
        mName = in.readString();
        mWidth = in.readInt();
        mHeight = in.readInt();
        mNativeObject = nativeReadFromParcel(in);
        if (mNativeObject == 0) {
            throw new IllegalArgumentException("Couldn't read SurfaceControl from parcel=" + in);
@@ -605,6 +623,8 @@ public class SurfaceControl implements Parcelable {
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(mName);
        dest.writeInt(mWidth);
        dest.writeInt(mHeight);
        nativeWriteToParcel(mNativeObject, dest);
    }

@@ -922,6 +942,18 @@ public class SurfaceControl implements Parcelable {
        }
    }

    public int getWidth() {
        synchronized (mSizeLock) {
            return mWidth;
        }
    }

    public int getHeight() {
        synchronized (mSizeLock) {
            return mHeight;
        }
    }

    @Override
    public String toString() {
        return "Surface(name=" + mName + ")/@0x" +
@@ -1282,6 +1314,7 @@ public class SurfaceControl implements Parcelable {
                nativeGetNativeTransactionFinalizer(), 512);
        private long mNativeObject;

        private final ArrayMap<SurfaceControl, Point> mResizedSurfaces = new ArrayMap<>();
        Runnable mFreeNativeResources;

        public Transaction() {
@@ -1312,9 +1345,22 @@ public class SurfaceControl implements Parcelable {
         * Jankier version of apply. Avoid use (b/28068298).
         */
        public void apply(boolean sync) {
            applyResizedSurfaces();
            nativeApplyTransaction(mNativeObject, sync);
        }

        private void applyResizedSurfaces() {
            for (int i = mResizedSurfaces.size() - 1; i >= 0; i--) {
                final Point size = mResizedSurfaces.valueAt(i);
                final SurfaceControl surfaceControl = mResizedSurfaces.keyAt(i);
                synchronized (surfaceControl.mSizeLock) {
                    surfaceControl.mWidth = size.x;
                    surfaceControl.mHeight = size.y;
                }
            }
            mResizedSurfaces.clear();
        }

        public Transaction show(SurfaceControl sc) {
            sc.checkNotReleased();
            nativeSetFlags(mNativeObject, sc.mNativeObject, 0, SURFACE_HIDDEN);
@@ -1335,6 +1381,7 @@ public class SurfaceControl implements Parcelable {

        public Transaction setSize(SurfaceControl sc, int w, int h) {
            sc.checkNotReleased();
            mResizedSurfaces.put(sc, new Point(w, h));
            nativeSetSize(mNativeObject, sc.mNativeObject, w, h);
            return this;
        }
@@ -1567,6 +1614,8 @@ public class SurfaceControl implements Parcelable {
         * other transaction as if it had been applied.
         */
        public Transaction merge(Transaction other) {
            mResizedSurfaces.putAll(other.mResizedSurfaces);
            other.mResizedSurfaces.clear();
            nativeMergeTransaction(mNativeObject, other.mNativeObject);
            return this;
        }
+16 −5
Original line number Diff line number Diff line
@@ -66,6 +66,7 @@ import android.view.IApplicationToken;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.view.animation.Transformation;

import com.android.internal.util.ToBooleanFunction;
import com.android.server.input.InputApplicationHandle;
@@ -75,6 +76,7 @@ import com.android.server.wm.WindowManagerService.H;
import java.io.PrintWriter;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.LinkedList;

class AppTokenList extends ArrayList<AppWindowToken> {
}
@@ -231,7 +233,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree
            // If this initial window is animating, stop it -- we will do an animation to reveal
            // it from behind the starting window, so there is no need for it to also be doing its
            // own stuff.
            winAnimator.clearAnimation();
            win.cancelAnimation();
            if (getController() != null) {
                getController().removeStartingWindow();
            }
@@ -389,7 +391,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree
        }

        for (int i = mChildren.size() - 1; i >= 0 && !delayed; i--) {
            if ((mChildren.get(i)).isWindowAnimationSet()) {
            if ((mChildren.get(i)).isSelfOrChildAnimating()) {
                delayed = true;
            }
        }
@@ -610,8 +612,12 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree
     */
    private void destroySurfaces(boolean cleanupOnResume) {
        boolean destroyedSomething = false;
        for (int i = mChildren.size() - 1; i >= 0; i--) {
            final WindowState win = mChildren.get(i);

        // Copying to a different list as multiple children can be removed.
        // TODO: Not sure why this is needed.
        final LinkedList<WindowState> children = new LinkedList<>(mChildren);
        for (int i = children.size() - 1; i >= 0; i--) {
            final WindowState win = children.get(i);
            destroyedSomething |= win.destroySurface(cleanupOnResume, mAppStopped);
        }
        if (destroyedSomething) {
@@ -1320,7 +1326,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree
                            + " pv=" + w.mPolicyVisibility
                            + " mDrawState=" + winAnimator.drawStateToString()
                            + " ph=" + w.isParentWindowHidden() + " th=" + hiddenRequested
                            + " a=" + winAnimator.mAnimating);
                            + " a=" + winAnimator.isAnimationSet());
                }
            }

@@ -1519,6 +1525,11 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree
        return mAppAnimator.animLayerAdjustment;
    }

    @Override
    boolean isSelfAnimating() {
        return mAppAnimator.isAnimating();
    }

    @Override
    void dump(PrintWriter pw, String prefix) {
        super.dump(pw, prefix);
+21 −33
Original line number Diff line number Diff line
@@ -146,6 +146,7 @@ import android.view.InputDevice;
import android.view.MagnificationSpec;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import android.view.SurfaceSession;

import com.android.internal.annotations.VisibleForTesting;
@@ -339,6 +340,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
            new ApplySurfaceChangesTransactionState();
    private final ScreenshotApplicationState mScreenshotApplicationState =
            new ScreenshotApplicationState();
    private final Transaction mTmpTransaction = new Transaction();

    // True if this display is in the process of being removed. Used to determine if the removal of
    // the display's direct children should be allowed.
@@ -381,27 +383,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo

    private final Consumer<WindowState> mUpdateWindowsForAnimator = w -> {
        WindowStateAnimator winAnimator = w.mWinAnimator;
        if (winAnimator.hasSurface()) {
            final boolean wasAnimating = winAnimator.mWasAnimating;
            final boolean nowAnimating = winAnimator.stepAnimationLocked(
                    mTmpWindowAnimator.mCurrentTime);
            winAnimator.mWasAnimating = nowAnimating;
            mTmpWindowAnimator.orAnimating(nowAnimating);

            if (DEBUG_WALLPAPER) Slog.v(TAG,
                    w + ": wasAnimating=" + wasAnimating + ", nowAnimating=" + nowAnimating);

            if (wasAnimating && !winAnimator.mAnimating
                    && mWallpaperController.isWallpaperTarget(w)) {
                mTmpWindowAnimator.mBulkUpdateParams |= SET_WALLPAPER_MAY_CHANGE;
                pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
                if (DEBUG_LAYOUT_REPEATS) {
                    mService.mWindowPlacerLocked.debugLayoutRepeats(
                            "updateWindowsAndWallpaperLocked 2", pendingLayoutChanges);
                }
            }
        }

        final AppWindowToken atoken = w.mAppToken;
        if (winAnimator.mDrawState == READY_TO_SHOW) {
            if (atoken == null || atoken.allDrawn) {
@@ -434,13 +415,13 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo

        // If this window is animating, make a note that we have an animating window and take
        // care of a request to run a detached wallpaper animation.
        if (winAnimator.mAnimating) {
            if (winAnimator.mAnimation != null) {
                if ((flags & FLAG_SHOW_WALLPAPER) != 0
                        && winAnimator.mAnimation.getDetachWallpaper()) {
        if (winAnimator.isAnimationSet()) {
            final AnimationAdapter anim = w.getAnimation();
            if (anim != null) {
                if ((flags & FLAG_SHOW_WALLPAPER) != 0 && anim.getDetachWallpaper()) {
                    mTmpWindow = w;
                }
                final int color = winAnimator.mAnimation.getBackgroundColor();
                final int color = anim.getBackgroundColor();
                if (color != 0) {
                    final TaskStack stack = w.getStack();
                    if (stack != null) {
@@ -448,7 +429,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
                    }
                }
            }
            mTmpWindowAnimator.setAnimating(true);
        }

        // If this window's app token is running a detached wallpaper animation, make a note so
@@ -684,7 +664,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
            mWallpaperController.updateWallpaperVisibility();
        }

        w.handleWindowMovedIfNeeded();
        // Use mTmpTransaction instead of mPendingTransaction because we don't want to commit
        // other changes in mPendingTransaction at this point.
        w.handleWindowMovedIfNeeded(mTmpTransaction);
        SurfaceControl.mergeToGlobalTransaction(mTmpTransaction);

        final WindowStateAnimator winAnimator = w.mWinAnimator;

@@ -720,7 +703,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
                }
            }
            final TaskStack stack = w.getStack();
            if ((!winAnimator.isAnimationStarting() && !winAnimator.isWaitingForOpening())
            if ((!winAnimator.isWaitingForOpening())
                    || (stack != null && stack.isAnimatingBounds())) {
                // Updates the shown frame before we set up the surface. This is needed
                // because the resizing could change the top-left position (in addition to
@@ -736,6 +719,12 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
                winAnimator.computeShownFrameLocked();
            }
            winAnimator.setSurfaceBoundariesLocked(mTmpRecoveringMemory /* recoveringMemory */);

            // Since setSurfaceBoundariesLocked applies the clipping, we need to apply the position
            // to the surface of the window container as well. Use mTmpTransaction instead of
            // mPendingTransaction to avoid committing any existing changes in there.
            w.updateSurfacePosition(mTmpTransaction);
            SurfaceControl.mergeToGlobalTransaction(mTmpTransaction);
        }

        final AppWindowToken atoken = w.mAppToken;
@@ -2617,8 +2606,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
        forAllWindows(w -> {
            if (w.mAppToken == null && policy.canBeHiddenByKeyguardLw(w)
                    && w.wouldBeVisibleIfPolicyIgnored() && !w.isVisible()) {
                w.mWinAnimator.setAnimation(
                        policy.createHiddenByKeyguardExit(onWallpaper, goingToShade));
                w.startAnimation(policy.createHiddenByKeyguardExit(onWallpaper, goingToShade));
            }
        }, true /* traverseTopToBottom */);
    }
@@ -3775,13 +3763,13 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
    }

    @Override
    void destroyAfterPendingTransaction(SurfaceControl surface) {
    public void destroyAfterPendingTransaction(SurfaceControl surface) {
        mPendingDestroyingSurfaces.add(surface);
    }

    /**
     * Destroys any surfaces that have been put into the pending list with
     * {@link #destroyAfterTransaction}.
     * {@link #destroyAfterPendingTransaction}.
     */
    void onPendingTransactionApplied() {
        for (int i = mPendingDestroyingSurfaces.size() - 1; i >= 0; i--) {
+22 −20
Original line number Diff line number Diff line
@@ -751,10 +751,10 @@ public class DockedStackDividerController {

                // There might be an old window delaying the animation start - clear it.
                if (mDelayedImeWin != null) {
                    mDelayedImeWin.mWinAnimator.endDelayingAnimationStart();
                    mDelayedImeWin.endDelayingAnimationStart();
                }
                mDelayedImeWin = imeWin;
                imeWin.mWinAnimator.startDelayingAnimationStart();
                imeWin.startDelayingAnimationStart();
            }

            // If we are already waiting for something to be drawn, clear out the old one so it
@@ -765,9 +765,10 @@ public class DockedStackDividerController {
                mService.mWaitingForDrawnCallback.run();
            }
            mService.mWaitingForDrawnCallback = () -> {
                synchronized (mService.mWindowMap) {
                    mAnimationStartDelayed = false;
                    if (mDelayedImeWin != null) {
                    mDelayedImeWin.mWinAnimator.endDelayingAnimationStart();
                        mDelayedImeWin.endDelayingAnimationStart();
                    }
                    // If the adjust status changed since this was posted, only notify
                    // the new states and don't animate.
@@ -784,6 +785,7 @@ public class DockedStackDividerController {
                    }
                    notifyAdjustedForImeChanged(
                            mAdjustedForIme || mAdjustedForDivider, duration);
                }
            };
        } else {
            notifyAdjustedForImeChanged(
+12 −23
Original line number Diff line number Diff line
@@ -29,7 +29,6 @@ import android.util.ArrayMap;
import android.view.Choreographer;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import android.view.animation.Transformation;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -94,9 +93,6 @@ class SurfaceAnimationRunner {
        synchronized (mLock) {
            if (mPendingAnimations.containsKey(leash)) {
                mPendingAnimations.remove(leash);
                // TODO: Releasing the leash is problematic if reparenting hasn't happened yet.
                // Fix with transaction
                //leash.release();
                return;
            }
            final ValueAnimator anim = mRunningAnimations.get(leash);
@@ -105,7 +101,6 @@ class SurfaceAnimationRunner {
                SurfaceAnimationThread.getHandler().post(() -> {
                    anim.cancel();
                    applyTransaction();
                    //leash.release();
                });
            }
        }
@@ -123,7 +118,7 @@ class SurfaceAnimationRunner {

        // Animation length is already expected to be scaled.
        result.overrideDurationScale(1.0f);
        result.setDuration(a.animSpec.getDuration());
        result.setDuration(a.mAnimSpec.getDuration());
        result.addUpdateListener(animation -> {
            applyTransformation(a, mFrameTransaction, result.getCurrentPlayTime());

@@ -136,7 +131,7 @@ class SurfaceAnimationRunner {

            @Override
            public void onAnimationStart(Animator animation) {
                mFrameTransaction.show(a.leash);
                mFrameTransaction.show(a.mLeash);
            }

            @Override
@@ -147,26 +142,20 @@ class SurfaceAnimationRunner {
            @Override
            public void onAnimationEnd(Animator animation) {
                synchronized (mLock) {
                    mRunningAnimations.remove(a.leash);
                    mRunningAnimations.remove(a.mLeash);
                }
                if (!mCancelled) {
                    // Post on other thread that we can push final state without jank.
                    AnimationThread.getHandler().post(() -> {
                        a.finishCallback.run();

                        // Make sure to release the leash after finishCallback has been invoked such
                        // that reparenting is done already when releasing the leash.
                        a.leash.release();
                    });
                    AnimationThread.getHandler().post(a.mFinishCallback);
                }
            }
        });
        result.start();
        mRunningAnimations.put(a.leash, result);
        mRunningAnimations.put(a.mLeash, result);
    }

    private void applyTransformation(RunningAnimation a, Transaction t, long currentPlayTime) {
        a.animSpec.apply(t, a.leash, currentPlayTime);
        a.mAnimSpec.apply(t, a.mLeash, currentPlayTime);
    }

    private void stepAnimation(long frameTimeNanos) {
@@ -189,14 +178,14 @@ class SurfaceAnimationRunner {
    }

    private static final class RunningAnimation {
        final AnimationSpec animSpec;
        final SurfaceControl leash;
        final Runnable finishCallback;
        final AnimationSpec mAnimSpec;
        final SurfaceControl mLeash;
        final Runnable mFinishCallback;

        RunningAnimation(AnimationSpec animSpec, SurfaceControl leash, Runnable finishCallback) {
            this.animSpec = animSpec;
            this.leash = leash;
            this.finishCallback = finishCallback;
            mAnimSpec = animSpec;
            mLeash = leash;
            mFinishCallback = finishCallback;
        }
    }

Loading