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

Commit 73c20638 authored by Jorim Jaggi's avatar Jorim Jaggi Committed by Android (Google) Code Review
Browse files

Merge "Handle cases when multiple type are animating"

parents 8fda7a56 1f2c7eb7
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import android.util.SparseIntArray;
import android.util.SparseSetArray;
import android.view.InsetsController.LayoutInsetsDuringAnimation;
import android.view.InsetsState.InternalInsetsSide;
import android.view.InsetsState.InternalInsetsType;
import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsAnimationCallback.AnimationBounds;
@@ -92,6 +93,7 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll
        mController = controller;
        mInitialInsetsState = new InsetsState(state, true /* copySources */);
        mCurrentInsets = getInsetsFromState(mInitialInsetsState, frame, null /* typeSideMap */);
        mPendingInsets = mCurrentInsets;
        mHiddenInsets = calculateInsets(mInitialInsetsState, frame, controls, false /* shown */,
                null /* typeSideMap */);
        mShownInsets = calculateInsets(mInitialInsetsState, frame, controls, true /* shown */,
@@ -131,6 +133,10 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll
        return mTypes;
    }

    boolean controlsInternalType(@InternalInsetsType int type) {
        return InsetsState.toInternalType(mTypes).contains(type);
    }

    @Override
    public void setInsetsAndAlpha(Insets insets, float alpha, float fraction) {
        if (mFinished) {
+87 −49
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.graphics.Insets;
import android.graphics.Rect;
import android.net.InvalidPacketException.ErrorCode;
import android.os.RemoteException;
import android.util.ArraySet;
import android.util.Log;
@@ -61,15 +62,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation

    private static final int ANIMATION_DURATION_SHOW_MS = 275;
    private static final int ANIMATION_DURATION_HIDE_MS = 340;
    private static final int DIRECTION_NONE = 0;
    private static final int DIRECTION_SHOW = 1;
    private static final int DIRECTION_HIDE = 2;

    static final Interpolator INTERPOLATOR = new PathInterpolator(0.4f, 0f, 0.2f, 1f);

    @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
@@ -101,6 +96,28 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
    @interface LayoutInsetsDuringAnimation {
    }

    /** Not running an animation. */
    @VisibleForTesting
    public static final int ANIMATION_TYPE_NONE = -1;

    /** Running animation will show insets */
    @VisibleForTesting
    public static final int ANIMATION_TYPE_SHOW = 0;

    /** Running animation will hide insets */
    @VisibleForTesting
    public static final int ANIMATION_TYPE_HIDE = 1;

    /** Running animation is controlled by user via {@link #controlWindowInsetsAnimation} */
    @VisibleForTesting
    public static final int ANIMATION_TYPE_USER = 2;

    @Retention(RetentionPolicy.SOURCE)
    @IntDef(value = {ANIMATION_TYPE_NONE, ANIMATION_TYPE_SHOW, ANIMATION_TYPE_HIDE,
            ANIMATION_TYPE_USER})
    @interface AnimationType {
    }

    /**
     * Translation animation evaluator.
     */
@@ -145,7 +162,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
        public void onReady(WindowInsetsAnimationController controller, int types) {
            mController = controller;

            mAnimationDirection = mShow ? DIRECTION_SHOW : DIRECTION_HIDE;
            mAnimator = ObjectAnimator.ofObject(
                    controller,
                    new InsetsProperty(),
@@ -176,7 +192,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
        }

        private void onAnimationFinish() {
            mAnimationDirection = DIRECTION_NONE;
            mController.finish(mShow);
        }

@@ -193,6 +208,20 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
        }
    }

    /**
     * Represents a running animation
     */
    private static class RunningAnimation {

        RunningAnimation(InsetsAnimationControlImpl control, int type) {
            this.control = control;
            this.type = type;
        }

        final InsetsAnimationControlImpl control;
        final @AnimationType int type;
    }

    private final String TAG = "InsetsControllerImpl";

    private final InsetsState mState = new InsetsState();
@@ -203,7 +232,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
    private final ViewRootImpl mViewRoot;

    private final SparseArray<InsetsSourceControl> mTmpControlArray = new SparseArray<>();
    private final ArrayList<InsetsAnimationControlImpl> mAnimationControls = new ArrayList<>();
    private final ArrayList<RunningAnimation> mRunningAnimations = new ArrayList<>();
    private final ArrayList<InsetsAnimationControlImpl> mTmpFinishedControls = new ArrayList<>();
    private WindowInsets mLastInsets;

@@ -213,7 +242,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation

    private final Rect mLastLegacyContentInsets = new Rect();
    private final Rect mLastLegacyStableInsets = new Rect();
    private @AnimationDirection int mAnimationDirection;

    private int mPendingTypesToShow;

@@ -226,7 +254,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
        mViewRoot = viewRoot;
        mAnimCallback = () -> {
            mAnimCallbackScheduled = false;
            if (mAnimationControls.isEmpty()) {
            if (mRunningAnimations.isEmpty()) {
                return;
            }
            if (mViewRoot.mView == null) {
@@ -236,9 +264,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation

            mTmpFinishedControls.clear();
            InsetsState state = new InsetsState(mState, true /* copySources */);
            for (int i = mAnimationControls.size() - 1; i >= 0; i--) {
                InsetsAnimationControlImpl control = mAnimationControls.get(i);
                if (mAnimationControls.get(i).applyChangeInsets(state)) {
            for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
                InsetsAnimationControlImpl control = mRunningAnimations.get(i).control;
                if (control.applyChangeInsets(state)) {
                    mTmpFinishedControls.add(control);
                }
            }
@@ -349,18 +377,13 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
        int typesReady = 0;
        final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
        for (int i = internalTypes.size() - 1; i >= 0; i--) {
            InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
            if (mAnimationDirection == DIRECTION_HIDE) {
                // Only one animator (with multiple InsetsType) can run at a time.
                // previous one should be cancelled for simplicity.
                cancelExistingAnimation();
            } else if (consumer.isRequestedVisible()
                    && (mAnimationDirection == DIRECTION_NONE
                    || mAnimationDirection == DIRECTION_HIDE)) {
            @InternalInsetsType int internalType = internalTypes.valueAt(i);
            @AnimationType int animationType = getAnimationType(internalType);
            InsetsSourceConsumer consumer = getSourceConsumer(internalType);
            if (mState.getSource(internalType).isVisible() && animationType == ANIMATION_TYPE_NONE
                    || animationType == ANIMATION_TYPE_SHOW) {
                // no-op: already shown or animating in (because window visibility is
                // applied before starting animation).
                // TODO: When we have more than one types: handle specific case when
                // show animation is going on, but the current type is not becoming visible.
                continue;
            }
            typesReady |= InsetsState.toPublicType(consumer.getType());
@@ -377,12 +400,11 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
        int typesReady = 0;
        final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
        for (int i = internalTypes.size() - 1; i >= 0; i--) {
            InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
            if (mAnimationDirection == DIRECTION_SHOW) {
                cancelExistingAnimation();
            } else if (!consumer.isRequestedVisible()
                    && (mAnimationDirection == DIRECTION_NONE
                    || mAnimationDirection == DIRECTION_HIDE)) {
            @InternalInsetsType int internalType = internalTypes.valueAt(i);
            @AnimationType int animationType = getAnimationType(internalType);
            InsetsSourceConsumer consumer = getSourceConsumer(internalType);
            if (!mState.getSource(internalType).isVisible() && animationType == ANIMATION_TYPE_NONE
                    || animationType == ANIMATION_TYPE_HIDE) {
                // no-op: already hidden or animating out.
                continue;
            }
@@ -394,11 +416,13 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
    @Override
    public void controlWindowInsetsAnimation(@InsetsType int types, long durationMs,
            WindowInsetsAnimationControlListener listener) {
        controlWindowInsetsAnimation(types, listener, false /* fromIme */, durationMs);
        controlWindowInsetsAnimation(types, listener, false /* fromIme */, durationMs,
                ANIMATION_TYPE_USER);
    }

    private void controlWindowInsetsAnimation(@InsetsType int types,
            WindowInsetsAnimationControlListener listener, boolean fromIme, long durationMs) {
            WindowInsetsAnimationControlListener listener, boolean fromIme, long durationMs,
            @AnimationType int animationType) {
        // If the frame of our window doesn't span the entire display, the control API makes very
        // little sense, as we don't deal with negative insets. So just cancel immediately.
        if (!mState.getDisplayFrame().equals(mFrame)) {
@@ -406,12 +430,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
            return;
        }
        controlAnimationUnchecked(types, listener, mFrame, fromIme, durationMs, false /* fade */,
                getLayoutInsetsDuringAnimationMode(types));
                animationType, getLayoutInsetsDuringAnimationMode(types));
    }

    private void controlAnimationUnchecked(@InsetsType int types,
            WindowInsetsAnimationControlListener listener, Rect frame, boolean fromIme,
            long durationMs, boolean fade,
            long durationMs, boolean fade, @AnimationType int animationType,
            @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation) {
        if (types == 0) {
            // nothing to animate.
@@ -444,7 +468,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
        final InsetsAnimationControlImpl controller = new InsetsAnimationControlImpl(controls,
                frame, mState, listener, typesReady, this, durationMs, fade,
                layoutInsetsDuringAnimation);
        mAnimationControls.add(controller);
        mRunningAnimations.add(new RunningAnimation(controller, animationType));
    }

    /**
@@ -523,10 +547,10 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
    }

    private void cancelExistingControllers(@InsetsType int types) {
        for (int i = mAnimationControls.size() - 1; i >= 0; i--) {
            InsetsAnimationControlImpl control = mAnimationControls.get(i);
        for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
            InsetsAnimationControlImpl control = mRunningAnimations.get(i).control;
            if ((control.getTypes() & types) != 0) {
                cancelAnimation(control);
                cancelAnimation(control, true /* invokeCallback */);
            }
        }
    }
@@ -534,7 +558,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
    @VisibleForTesting
    @Override
    public void notifyFinished(InsetsAnimationControlImpl controller, boolean shown) {
        mAnimationControls.remove(controller);
        cancelAnimation(controller, false /* invokeCallback */);
        if (shown) {
            showDirectly(controller.getTypes());
        } else {
@@ -554,17 +578,24 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
    }

    void notifyControlRevoked(InsetsSourceConsumer consumer) {
        for (int i = mAnimationControls.size() - 1; i >= 0; i--) {
            InsetsAnimationControlImpl control = mAnimationControls.get(i);
        for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
            InsetsAnimationControlImpl control = mRunningAnimations.get(i).control;
            if ((control.getTypes() & toPublicType(consumer.getType())) != 0) {
                cancelAnimation(control);
                cancelAnimation(control, true /* invokeCallback */);
            }
        }
    }

    private void cancelAnimation(InsetsAnimationControlImpl control) {
    private void cancelAnimation(InsetsAnimationControlImpl control, boolean invokeCallback) {
        if (invokeCallback) {
            control.onCancelled();
        mAnimationControls.remove(control);
        }
        for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
            if (mRunningAnimations.get(i).control == control) {
                mRunningAnimations.remove(i);
                break;
            }
        }
    }

    private void applyLocalVisibilityOverride() {
@@ -622,8 +653,15 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
        }
    }

    boolean isAnimating() {
        return mAnimationDirection != DIRECTION_NONE;
    @VisibleForTesting
    public @AnimationType int getAnimationType(@InternalInsetsType int type) {
        for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
            InsetsAnimationControlImpl control = mRunningAnimations.get(i).control;
            if (control.controlsInternalType(type)) {
                return mRunningAnimations.get(i).type;
            }
        }
        return ANIMATION_TYPE_NONE;
    }

    private InsetsSourceConsumer createConsumerOfType(int type) {
@@ -665,8 +703,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
        // and hidden state insets are correct.
        controlAnimationUnchecked(
                types, listener, mState.getDisplayFrame(), fromIme, listener.getDurationMs(),
                true /* fade */, show
                        ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN
                true /* fade */, show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE,
                show ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN
                        : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN);
    }

+3 −1
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package android.view;

import static android.view.InsetsController.ANIMATION_TYPE_NONE;

import android.annotation.IntDef;
import android.annotation.Nullable;
import android.view.InsetsState.InternalInsetsType;
@@ -172,7 +174,7 @@ public class InsetsSourceConsumer {

    private void applyHiddenToControl() {
        if (mSourceControl == null || mSourceControl.getLeash() == null
                || mController.isAnimating()) {
                || mController.getAnimationType(mType) != ANIMATION_TYPE_NONE) {
            return;
        }

+51 −15
Original line number Diff line number Diff line
@@ -16,9 +16,13 @@

package android.view;

import static android.view.InsetsController.ANIMATION_TYPE_HIDE;
import static android.view.InsetsController.ANIMATION_TYPE_NONE;
import static android.view.InsetsController.ANIMATION_TYPE_SHOW;
import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
import static android.view.WindowInsets.Type.statusBars;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;

@@ -40,12 +44,15 @@ import android.platform.test.annotations.Presubmit;
import android.view.WindowInsets.Type;
import android.view.WindowManager.BadTokenException;
import android.view.WindowManager.LayoutParams;
import android.view.test.InsetsModeSession;
import android.widget.TextView;

import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;

import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -69,6 +76,17 @@ public class InsetsControllerTest {
    private SurfaceSession mSession = new SurfaceSession();
    private SurfaceControl mLeash;
    private ViewRootImpl mViewRoot;
    private static InsetsModeSession sInsetsModeSession;

    @BeforeClass
    public static void setupOnce() {
        sInsetsModeSession = new InsetsModeSession(NEW_INSETS_MODE_FULL);
    }

    @AfterClass
    public static void tearDownOnce() {
        sInsetsModeSession.close();
    }

    @Before
    public void setup() {
@@ -86,6 +104,11 @@ public class InsetsControllerTest {
            }
            mController = new InsetsController(mViewRoot);
            final Rect rect = new Rect(5, 5, 5, 5);
            mController.getState().getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 10));
            mController.getState().getSource(ITYPE_NAVIGATION_BAR).setFrame(
                    new Rect(0, 90, 100, 100));
            mController.getState().getSource(ITYPE_IME).setFrame(new Rect(0, 50, 100, 100));
            mController.getState().setDisplayFrame(new Rect(0, 0, 100, 100));
            mController.calculateInsets(
                    false,
                    false,
@@ -93,7 +116,6 @@ public class InsetsControllerTest {
                            Insets.of(10, 10, 10, 10), rect, rect, rect, rect),
                    rect, rect, SOFT_INPUT_ADJUST_RESIZE);
            mController.onFrameChanged(new Rect(0, 0, 100, 100));
            mController.getState().setDisplayFrame(new Rect(0, 0, 100, 100));
        });
        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
    }
@@ -205,18 +227,24 @@ public class InsetsControllerTest {

        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
            int types = Type.navigationBars() | Type.systemBars();
            // test show select types.
            mController.show(types);
            // test hide select types.
            mController.hide(types);
            assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
            assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_STATUS_BAR));
            mController.cancelExistingAnimation();
            assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
            assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
            assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
            assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_STATUS_BAR));
            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());

            // test hide all
            mController.hide(types);
            mController.show(types);
            assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
            assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_STATUS_BAR));
            mController.cancelExistingAnimation();
            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
            assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
            assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
        });
        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -271,30 +299,38 @@ public class InsetsControllerTest {

        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
            // start two animations and see if previous is cancelled and final state is reached.
            mController.show(Type.navigationBars());
            mController.show(Type.systemBars());
            mController.cancelExistingAnimation();
            assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
            assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());

            mController.hide(Type.navigationBars());
            mController.hide(Type.systemBars());
            assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
            assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_STATUS_BAR));
            mController.cancelExistingAnimation();
            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());

            mController.show(Type.navigationBars());
            mController.show(Type.systemBars());
            assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
            assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_STATUS_BAR));
            mController.cancelExistingAnimation();
            assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
            assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());

            int types = Type.navigationBars() | Type.systemBars();
            // show two at a time and hide one by one.
            mController.show(types);
            mController.hide(Type.navigationBars());
            assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
            assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_STATUS_BAR));
            mController.cancelExistingAnimation();
            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
            assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());

            mController.hide(Type.systemBars());
            assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
            assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_STATUS_BAR));
            mController.cancelExistingAnimation();
            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());