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

Commit a51168aa authored by Jorim Jaggi's avatar Jorim Jaggi
Browse files

Correct layout/draw/animation interleaving for insets callbacks

See WindowInsetsAnimationCallback.onPrepare for detailed
description of new behavior.

Also rename InsetsSourceConsumer.setVisible to setRequestedVisible
to communicate that this is client intent vs. current state.

Test: windowinsetstest application
Bug: 111084606
Change-Id: Id35c60e0f59a8aa4f0d300220391d1e2b96a91fd
parent 7b469d73
Loading
Loading
Loading
Loading
+6 −4
Original line number Diff line number Diff line
@@ -51599,9 +51599,10 @@ package android.view {
    method public boolean dispatchUnhandledMove(android.view.View, int);
    method protected void dispatchVisibilityChanged(@NonNull android.view.View, int);
    method public void dispatchWindowFocusChanged(boolean);
    method public void dispatchWindowInsetsAnimationFinished(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation);
    method public void dispatchWindowInsetsAnimationFinish(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation);
    method public void dispatchWindowInsetsAnimationPrepare(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation);
    method @NonNull public android.view.WindowInsets dispatchWindowInsetsAnimationProgress(@NonNull android.view.WindowInsets);
    method @NonNull public android.view.WindowInsetsAnimationCallback.AnimationBounds dispatchWindowInsetsAnimationStarted(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation, @NonNull android.view.WindowInsetsAnimationCallback.AnimationBounds);
    method @NonNull public android.view.WindowInsetsAnimationCallback.AnimationBounds dispatchWindowInsetsAnimationStart(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation, @NonNull android.view.WindowInsetsAnimationCallback.AnimationBounds);
    method public void dispatchWindowSystemUiVisiblityChanged(int);
    method public void dispatchWindowVisibilityChanged(int);
    method @CallSuper public void draw(android.graphics.Canvas);
@@ -53281,9 +53282,10 @@ package android.view {
  }
  public interface WindowInsetsAnimationCallback {
    method public default void onFinished(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation);
    method public default void onFinish(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation);
    method public default void onPrepare(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation);
    method @NonNull public android.view.WindowInsets onProgress(@NonNull android.view.WindowInsets);
    method @NonNull public default android.view.WindowInsetsAnimationCallback.AnimationBounds onStarted(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation, @NonNull android.view.WindowInsetsAnimationCallback.AnimationBounds);
    method @NonNull public default android.view.WindowInsetsAnimationCallback.AnimationBounds onStart(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation, @NonNull android.view.WindowInsetsAnimationCallback.AnimationBounds);
  }
  public static final class WindowInsetsAnimationCallback.AnimationBounds {
+15 −4
Original line number Diff line number Diff line
@@ -16,17 +16,28 @@

package android.view;

import android.view.InsetsController.LayoutInsetsDuringAnimation;
import android.view.WindowInsetsAnimationCallback.AnimationBounds;
import android.view.WindowInsetsAnimationCallback.InsetsAnimation;

/**
 * Provide an interface to let InsetsAnimationControlImpl call back into its owner.
 * @hide
 */
public interface InsetsAnimationControlCallbacks {

    /**
     * Dispatch the animation started event to all listeners.
     * @param animation
     * Executes the necessary code to start the animation in the correct order, including:
     * <ul>
     *     <li>Dispatch {@link WindowInsetsAnimationCallback#onPrepare}</li>
     *     <li>Update insets state and run layout according to {@code layoutDuringAnimation}</li>
     *     <li>Dispatch {@link WindowInsetsAnimationCallback#onStart}</li>
     *     <li>Dispatch {@link WindowInsetsAnimationControlListener#onReady}</li>
     * </ul>
     */
    void dispatchAnimationStarted(WindowInsetsAnimationCallback.InsetsAnimation animation,
            WindowInsetsAnimationCallback.AnimationBounds bounds);
    void startAnimation(InsetsAnimationControlImpl controller,
            WindowInsetsAnimationControlListener listener, int types, InsetsAnimation animation,
            AnimationBounds bounds, @LayoutInsetsDuringAnimation int layoutDuringAnimation);

    /**
     * Schedule the apply by posting the animation callback.
+7 −6
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package android.view;

import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_HIDDEN;
import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
import static android.view.InsetsState.ISIDE_BOTTOM;
import static android.view.InsetsState.ISIDE_FLOATING;
import static android.view.InsetsState.ISIDE_LEFT;
@@ -30,6 +32,7 @@ import android.util.ArraySet;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.SparseSetArray;
import android.view.InsetsController.LayoutInsetsDuringAnimation;
import android.view.InsetsState.InternalInsetsSide;
import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
import android.view.WindowInsets.Type.InsetsType;
@@ -80,7 +83,8 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll
    public InsetsAnimationControlImpl(SparseArray<InsetsSourceControl> controls, Rect frame,
            InsetsState state, WindowInsetsAnimationControlListener listener,
            @InsetsType int types,
            InsetsAnimationControlCallbacks controller, long durationMs, boolean fade) {
            InsetsAnimationControlCallbacks controller, long durationMs, boolean fade,
            @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation) {
        mControls = controls;
        mListener = listener;
        mTypes = types;
@@ -95,14 +99,11 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll
        mFrame = new Rect(frame);
        buildTypeSourcesMap(mTypeSideMap, mSideSourceMap, mControls);

        // TODO: Check for controllability first and wait for IME if needed.
        listener.onReady(this, types);

        mAnimation = new WindowInsetsAnimationCallback.InsetsAnimation(mTypes,
                InsetsController.INTERPOLATOR, durationMs);
        mAnimation.setAlpha(getCurrentAlpha());
        mController.dispatchAnimationStarted(mAnimation,
                new AnimationBounds(mHiddenInsets, mShownInsets));
        mController.startAnimation(this, listener, types, mAnimation,
                new AnimationBounds(mHiddenInsets, mShownInsets), layoutInsetsDuringAnimation);
    }

    @Override
+98 −15
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ import android.util.SparseArray;
import android.view.InsetsSourceConsumer.ShowResult;
import android.view.InsetsState.InternalInsetsType;
import android.view.SurfaceControl.Transaction;
import android.view.ViewTreeObserver.OnPreDrawListener;
import android.view.WindowInsets.Type;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsAnimationCallback.AnimationBounds;
@@ -47,6 +48,8 @@ import android.view.animation.PathInterpolator;
import com.android.internal.annotations.VisibleForTesting;

import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;

/**
@@ -66,6 +69,37 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
    @IntDef ({DIRECTION_NONE, DIRECTION_SHOW, DIRECTION_HIDE})
    private @interface AnimationDirection{}

    /**
     * Layout mode during insets animation: The views should be laid out as if the changing inset
     * types are fully shown. Before starting the animation, {@link View#onApplyWindowInsets} will
     * be called as if the changing insets types are shown, which will result in the views being
     * laid out as if the insets are fully shown.
     */
    static final int LAYOUT_INSETS_DURING_ANIMATION_SHOWN = 0;

    /**
     * Layout mode during insets animation: The views should be laid out as if the changing inset
     * types are fully hidden. Before starting the animation, {@link View#onApplyWindowInsets} will
     * be called as if the changing insets types are hidden, which will result in the views being
     * laid out as if the insets are fully hidden.
     */
    static final int LAYOUT_INSETS_DURING_ANIMATION_HIDDEN = 1;

    /**
     * Determines the behavior of how the views should be laid out during an insets animation that
     * is controlled by the application by calling {@link #controlWindowInsetsAnimation}.
     * <p>
     * When the animation is system-initiated, the layout mode is always chosen such that the
     * pre-animation layout will represent the opposite of the starting state, i.e. when insets
     * are appearing, {@link #LAYOUT_INSETS_DURING_ANIMATION_SHOWN} will be used. When insets
     * are disappearing, {@link #LAYOUT_INSETS_DURING_ANIMATION_HIDDEN} will be used.
     */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(value = {LAYOUT_INSETS_DURING_ANIMATION_SHOWN,
            LAYOUT_INSETS_DURING_ANIMATION_HIDDEN})
    @interface LayoutInsetsDuringAnimation {
    }

    /**
     * Translation animation evaluator.
     */
@@ -109,11 +143,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
        @Override
        public void onReady(WindowInsetsAnimationController controller, int types) {
            mController = controller;
            if (mShow) {
                showDirectly(types);
            } else {
                hideDirectly(types);
            }

            mAnimationDirection = mShow ? DIRECTION_SHOW : DIRECTION_HIDE;
            mAnimator = ObjectAnimator.ofObject(
                    controller,
@@ -131,7 +161,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
                    onAnimationFinish();
                }
            });
            mStartingAnimation = true;
            mAnimator.start();
            mStartingAnimation = false;
        }

        @Override
@@ -185,6 +217,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
    private int mPendingTypesToShow;

    private int mLastLegacySoftInputMode;
    private boolean mStartingAnimation;

    private SyncRtSurfaceTransactionApplier mApplier;

@@ -312,7 +345,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
                // Only one animator (with multiple InsetsType) can run at a time.
                // previous one should be cancelled for simplicity.
                cancelExistingAnimation();
            } else if (consumer.isVisible()
            } else if (consumer.isRequestedVisible()
                    && (mAnimationDirection == DIRECTION_NONE
                    || mAnimationDirection == DIRECTION_HIDE)) {
                // no-op: already shown or animating in (because window visibility is
@@ -338,7 +371,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
            InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
            if (mAnimationDirection == DIRECTION_SHOW) {
                cancelExistingAnimation();
            } else if (!consumer.isVisible()
            } else if (!consumer.isRequestedVisible()
                    && (mAnimationDirection == DIRECTION_NONE
                    || mAnimationDirection == DIRECTION_HIDE)) {
                // no-op: already hidden or animating out.
@@ -363,12 +396,14 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
            listener.onCancelled();
            return;
        }
        controlAnimationUnchecked(types, listener, mFrame, fromIme, durationMs, false /* fade */);
        controlAnimationUnchecked(types, listener, mFrame, fromIme, durationMs, false /* fade */,
                getLayoutInsetsDuringAnimationMode(types));
    }

    private void controlAnimationUnchecked(@InsetsType int types,
            WindowInsetsAnimationControlListener listener, Rect frame, boolean fromIme,
            long durationMs, boolean fade) {
            long durationMs, boolean fade,
            @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation) {
        if (types == 0) {
            // nothing to animate.
            return;
@@ -398,7 +433,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
        }

        final InsetsAnimationControlImpl controller = new InsetsAnimationControlImpl(controls,
                frame, mState, listener, typesReady, this, durationMs, fade);
                frame, mState, listener, typesReady, this, durationMs, fade,
                layoutInsetsDuringAnimation);
        mAnimationControls.add(controller);
    }

@@ -412,7 +448,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
        boolean isReady = true;
        for (int i = internalTypes.size() - 1; i >= 0; i--) {
            InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
            boolean setVisible = !consumer.isVisible();
            boolean setVisible = !consumer.isRequestedVisible();
            if (setVisible) {
                // Show request
                switch(consumer.requestShow(fromIme)) {
@@ -454,6 +490,29 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
        return typesReady;
    }

    private @LayoutInsetsDuringAnimation int getLayoutInsetsDuringAnimationMode(
            @InsetsType int types) {

        final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);

        // Generally, we want to layout the opposite of the current state. This is to make animation
        // callbacks easy to use: The can capture the layout values and then treat that as end-state
        // during the animation.
        //
        // However, if controlling multiple sources, we want to treat it as shown if any of the
        // types is currently hidden.
        for (int i = internalTypes.size() - 1; i >= 0; i--) {
            InsetsSourceConsumer consumer = mSourceConsumers.get(internalTypes.valueAt(i));
            if (consumer == null) {
                continue;
            }
            if (!consumer.isRequestedVisible()) {
                return LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
            }
        }
        return LAYOUT_INSETS_DURING_ANIMATION_HIDDEN;
    }

    private void cancelExistingControllers(@InsetsType int types) {
        for (int i = mAnimationControls.size() - 1; i >= 0; i--) {
            InsetsAnimationControlImpl control = mAnimationControls.get(i);
@@ -597,7 +656,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
        // and hidden state insets are correct.
        controlAnimationUnchecked(
                types, listener, mState.getDisplayFrame(), fromIme, listener.getDurationMs(),
                true /* fade */);
                true /* fade */, show
                        ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN
                        : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN);
    }

    private void hideDirectly(@InsetsType int types) {
@@ -629,18 +690,40 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation

    @VisibleForTesting
    @Override
    public void dispatchAnimationStarted(InsetsAnimation animation, AnimationBounds bounds) {
        mViewRoot.mView.dispatchWindowInsetsAnimationStarted(animation, bounds);
    public void startAnimation(InsetsAnimationControlImpl controller,
            WindowInsetsAnimationControlListener listener, int types, InsetsAnimation animation,
            AnimationBounds bounds, int layoutDuringAnimation) {
        if (layoutDuringAnimation == LAYOUT_INSETS_DURING_ANIMATION_SHOWN) {
            showDirectly(types);
        } else {
            hideDirectly(types);
        }
        mViewRoot.mView.dispatchWindowInsetsAnimationPrepare(animation);
        mViewRoot.mView.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
            @Override
            public boolean onPreDraw() {
                mViewRoot.mView.getViewTreeObserver().removeOnPreDrawListener(this);
                mViewRoot.mView.dispatchWindowInsetsAnimationStart(animation, bounds);
                listener.onReady(controller, types);
                return true;
            }
        });
        mViewRoot.mView.invalidate();
    }

    @VisibleForTesting
    public void dispatchAnimationFinished(InsetsAnimation animation) {
        mViewRoot.mView.dispatchWindowInsetsAnimationFinished(animation);
        mViewRoot.mView.dispatchWindowInsetsAnimationFinish(animation);
    }

    @VisibleForTesting
    @Override
    public void scheduleApplyChangeInsets() {
        if (mStartingAnimation) {
            mAnimCallback.run();
            mAnimCallbackScheduled = false;
            return;
        }
        if (!mAnimCallbackScheduled) {
            mViewRoot.mChoreographer.postCallback(Choreographer.CALLBACK_INSETS_ANIMATION,
                    mAnimCallback, null /* token*/);
+16 −12
Original line number Diff line number Diff line
@@ -53,7 +53,7 @@ public class InsetsSourceConsumer {
    }

    protected final InsetsController mController;
    protected boolean mVisible;
    protected boolean mRequestedVisible;
    private final Supplier<Transaction> mTransactionSupplier;
    private final @InternalInsetsType int mType;
    private final InsetsState mState;
@@ -66,7 +66,7 @@ public class InsetsSourceConsumer {
        mState = state;
        mTransactionSupplier = transactionSupplier;
        mController = controller;
        mVisible = InsetsState.getDefaultVisibility(type);
        mRequestedVisible = InsetsState.getDefaultVisibility(type);
    }

    public void setControl(@Nullable InsetsSourceControl control) {
@@ -94,12 +94,12 @@ public class InsetsSourceConsumer {

    @VisibleForTesting
    public void show() {
        setVisible(true);
        setRequestedVisible(true);
    }

    @VisibleForTesting
    public void hide() {
        setVisible(false);
        setRequestedVisible(false);
    }

    /**
@@ -126,16 +126,16 @@ public class InsetsSourceConsumer {
        if (mSourceControl == null) {
            return false;
        }
        if (mState.getSource(mType).isVisible() == mVisible) {
        if (mState.getSource(mType).isVisible() == mRequestedVisible) {
            return false;
        }
        mState.getSource(mType).setVisible(mVisible);
        mState.getSource(mType).setVisible(mRequestedVisible);
        return true;
    }

    @VisibleForTesting
    public boolean isVisible() {
        return mVisible;
    public boolean isRequestedVisible() {
        return mRequestedVisible;
    }

    /**
@@ -157,11 +157,15 @@ public class InsetsSourceConsumer {
        // no-op for types that always return ShowResult#SHOW_IMMEDIATELY.
    }

    private void setVisible(boolean visible) {
        if (mVisible == visible) {
    /**
     * Sets requested visibility from the client, regardless of whether we are able to control it at
     * the moment.
     */
    private void setRequestedVisible(boolean requestedVisible) {
        if (mRequestedVisible == requestedVisible) {
            return;
        }
        mVisible = visible;
        mRequestedVisible = requestedVisible;
        applyLocalVisibilityOverride();
        mController.notifyVisibilityChanged();
    }
@@ -173,7 +177,7 @@ public class InsetsSourceConsumer {
        }

        final Transaction t = mTransactionSupplier.get();
        if (mVisible) {
        if (mRequestedVisible) {
            t.show(mSourceControl.getLeash());
        } else {
            t.hide(mSourceControl.getLeash());
Loading