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

Commit 5ed50cc1 authored by Jorim Jaggi's avatar Jorim Jaggi
Browse files

Implement proper lifecycle for WindowInsetsAnimationController

- Ensure that finish actually works.
- Ensure that cancelling also works.
- Ensure that if the app is losing control, the cancel callback
  will be invoked.

Test: InsetsControllerTest
Test: InsetsAnimationControlImplTest
Bug: 111084606
Change-Id: I74d15fe2aa66c054b104c81795e7ab67336c9d04
parent e4f393f6
Loading
Loading
Loading
Loading
+51 −4
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import static android.view.InsetsState.INSET_SIDE_BOTTOM;
import static android.view.InsetsState.INSET_SIDE_LEFT;
import static android.view.InsetsState.INSET_SIDE_RIGHT;
import static android.view.InsetsState.INSET_SIDE_TOP;
import static android.view.InsetsState.toPublicType;

import android.annotation.Nullable;
import android.graphics.Insets;
@@ -33,6 +34,7 @@ import android.util.SparseSetArray;
import android.view.InsetsState.InsetSide;
import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
import android.view.WindowInsets.Type.InsetType;
import android.view.WindowInsetsAnimationListener.InsetsAnimation;
import android.view.WindowManager.LayoutParams;

import com.android.internal.annotations.VisibleForTesting;
@@ -66,8 +68,12 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll
    private final Supplier<SyncRtSurfaceTransactionApplier> mTransactionApplierSupplier;
    private final InsetsController mController;
    private final WindowInsetsAnimationListener.InsetsAnimation mAnimation;
    private final Rect mFrame;
    private Insets mCurrentInsets;
    private Insets mPendingInsets;
    private boolean mFinished;
    private boolean mCancelled;
    private int mFinishedShownTypes;

    @VisibleForTesting
    public InsetsAnimationControlImpl(SparseArray<InsetsSourceConsumer> consumers, Rect frame,
@@ -86,6 +92,7 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll
                null /* typeSideMap */);
        mShownInsets = calculateInsets(mInitialInsetsState, frame, consumers, true /* shown */,
                mTypeSideMap);
        mFrame = new Rect(frame);
        buildTypeSourcesMap(mTypeSideMap, mSideSourceMap, mConsumers);

        // TODO: Check for controllability first and wait for IME if needed.
@@ -119,12 +126,26 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll

    @Override
    public void changeInsets(Insets insets) {
        if (mFinished) {
            throw new IllegalStateException(
                    "Can't change insets on an animation that is finished.");
        }
        if (mCancelled) {
            throw new IllegalStateException(
                    "Can't change insets on an animation that is cancelled.");
        }
        mPendingInsets = sanitize(insets);
        mController.scheduleApplyChangeInsets();
    }

    @VisibleForTesting
    public void applyChangeInsets(InsetsState state) {
    /**
     * @return Whether the finish callback of this animation should be invoked.
     */
    public boolean applyChangeInsets(InsetsState state) {
        if (mCancelled) {
            return false;
        }
        final Insets offset = Insets.subtract(mShownInsets, mPendingInsets);
        ArrayList<SurfaceParams> params = new ArrayList<>();
        if (offset.left != 0) {
@@ -144,13 +165,40 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll
        SyncRtSurfaceTransactionApplier applier = mTransactionApplierSupplier.get();
        applier.scheduleApply(params.toArray(new SurfaceParams[params.size()]));
        mCurrentInsets = mPendingInsets;
        if (mFinished) {
            mController.notifyFinished(this, mFinishedShownTypes);
        }
        return mFinished;
    }

    @Override
    public void finish(int shownTypes) {
        // TODO
        if (mCancelled) {
            return;
        }
        InsetsState state = new InsetsState(mController.getState());
        for (int i = mConsumers.size() - 1; i >= 0; i--) {
            InsetsSourceConsumer consumer = mConsumers.valueAt(i);
            boolean visible = (shownTypes & toPublicType(consumer.getType())) != 0;
            state.getSource(consumer.getType()).setVisible(visible);
        }
        Insets insets = getInsetsFromState(state, mFrame, null /* typeSideMap */);
        changeInsets(insets);
        mFinished = true;
        mFinishedShownTypes = shownTypes;
    }

        mController.dispatchAnimationFinished(mAnimation);
    @VisibleForTesting
    public void onCancelled() {
        if (mFinished) {
            return;
        }
        mCancelled = true;
        mListener.onCancelled();
    }

    InsetsAnimation getAnimation() {
        return mAnimation;
    }

    private Insets calculateInsets(InsetsState state, Rect frame,
@@ -225,4 +273,3 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll
        }
    }
}
+58 −17
Original line number Diff line number Diff line
@@ -17,6 +17,8 @@
package android.view;

import static android.view.InsetsState.TYPE_IME;
import static android.view.InsetsState.toPublicType;
import static android.view.WindowInsets.Type.all;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -99,6 +101,7 @@ public class InsetsController implements WindowInsetsController {

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

    private boolean mAnimCallbackScheduled;
@@ -107,7 +110,6 @@ public class InsetsController implements WindowInsetsController {

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

    private int mPendingTypesToShow;
@@ -122,19 +124,29 @@ public class InsetsController implements WindowInsetsController {
                return;
            }

            mTmpFinishedControls.clear();
            InsetsState state = new InsetsState(mState, true /* copySources */);
            for (int i = mAnimationControls.size() - 1; i >= 0; i--) {
                mAnimationControls.get(i).applyChangeInsets(state);
                InsetsAnimationControlImpl control = mAnimationControls.get(i);
                if (mAnimationControls.get(i).applyChangeInsets(state)) {
                    mTmpFinishedControls.add(control);
                }
            }

            WindowInsets insets = state.calculateInsets(mFrame, mLastInsets.isRound(),
                    mLastInsets.shouldAlwaysConsumeNavBar(), mLastInsets.getDisplayCutout(),
                    mLastLegacyContentInsets, mLastLegacyStableInsets, mLastLegacySoftInputMode,
                    null /* typeSideMap */);
            mViewRoot.mView.dispatchWindowInsetsAnimationProgress(insets);

            for (int i = mTmpFinishedControls.size() - 1; i >= 0; i--) {
                dispatchAnimationFinished(mTmpFinishedControls.get(i).getAnimation());
            }
        };
    }

    void onFrameChanged(Rect frame) {
    @VisibleForTesting
    public void onFrameChanged(Rect frame) {
        if (mFrame.equals(frame)) {
            return;
        }
@@ -279,7 +291,8 @@ public class InsetsController implements WindowInsetsController {
            // nothing to animate.
            return;
        }
        // TODO: Check whether we already have a controller.
        cancelExistingControllers(types);

        final ArraySet<Integer> internalTypes = mState.toInternalType(types);
        final SparseArray<InsetsSourceConsumer> consumers = new SparseArray<>();

@@ -321,7 +334,7 @@ public class InsetsController implements WindowInsetsController {
                    // Show request
                    switch(consumer.requestShow(fromIme)) {
                        case ShowResult.SHOW_IMMEDIATELY:
                            typesReady |= InsetsState.toPublicType(TYPE_IME);
                            typesReady |= InsetsState.toPublicType(consumer.getType());
                            break;
                        case ShowResult.SHOW_DELAYED:
                            isReady = false;
@@ -365,6 +378,36 @@ public class InsetsController implements WindowInsetsController {
        return typesReady;
    }

    private void cancelExistingControllers(@InsetType int types) {
        for (int i = mAnimationControls.size() - 1; i >= 0; i--) {
            InsetsAnimationControlImpl control = mAnimationControls.get(i);
            if ((control.getTypes() & types) != 0) {
                cancelAnimation(control);
            }
        }
    }

    @VisibleForTesting
    public void notifyFinished(InsetsAnimationControlImpl controller, int shownTypes) {
        mAnimationControls.remove(controller);
        hideDirectly(controller.getTypes() & ~shownTypes);
        showDirectly(controller.getTypes() & shownTypes);
    }

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

    private void cancelAnimation(InsetsAnimationControlImpl control) {
        control.onCancelled();
        mAnimationControls.remove(control);
    }

    private void applyLocalVisibilityOverride() {
        for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
            final InsetsSourceConsumer controller = mSourceConsumers.valueAt(i);
@@ -455,8 +498,13 @@ public class InsetsController implements WindowInsetsController {
        }

        WindowInsetsAnimationControlListener listener = new WindowInsetsAnimationControlListener() {

            private WindowInsetsAnimationController mController;
            private ObjectAnimator mAnimator;

            @Override
            public void onReady(WindowInsetsAnimationController controller, int types) {
                mController = controller;
                if (show) {
                    showDirectly(types);
                } else {
@@ -474,10 +522,6 @@ public class InsetsController implements WindowInsetsController {
                        : ANIMATION_DURATION_HIDE_MS);
                mAnimator.setInterpolator(INTERPOLATOR);
                mAnimator.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationCancel(Animator animation) {
                        onAnimationFinish();
                    }

                    @Override
                    public void onAnimationEnd(Animator animation) {
@@ -488,15 +532,15 @@ public class InsetsController implements WindowInsetsController {
            }

            @Override
            public void onCancelled() {}
            public void onCancelled() {
                mAnimator.cancel();
            }

            private void onAnimationFinish() {
                mAnimationDirection = DIRECTION_NONE;
                mController.finish(show ? types : 0);
            }
        };
        // TODO: Instead of clearing this here, properly wire up
        // InsetsAnimationControlImpl.finish() to remove this from mAnimationControls.
        mAnimationControls.clear();

        // Show/hide animations always need to be relative to the display frame, in order that shown
        // and hidden state insets are correct.
@@ -522,10 +566,7 @@ public class InsetsController implements WindowInsetsController {
     */
    @VisibleForTesting
    public void cancelExistingAnimation() {
        mAnimationDirection = DIRECTION_NONE;
        if (mAnimator != null) {
            mAnimator.cancel();
        }
        cancelExistingControllers(all());
    }

    void dump(String prefix, PrintWriter pw) {
+3 −0
Original line number Diff line number Diff line
@@ -77,6 +77,9 @@ public class InsetsSourceConsumer {
        if (applyLocalVisibilityOverride()) {
            mController.notifyVisibilityChanged();
        }
        if (mSourceControl == null) {
            mController.notifyControlRevoked(this);
        }
    }

    @VisibleForTesting
+1 −1
Original line number Diff line number Diff line
@@ -10956,7 +10956,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
    }
    void dispatchWindowInsetsAnimationFinished(InsetsAnimation animation) {
        if (mListenerInfo != null && mListenerInfo.mOnApplyWindowInsetsListener != null) {
        if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationListener != null) {
            mListenerInfo.mWindowInsetsAnimationListener.onFinished(animation);
        }
    }
+23 −0
Original line number Diff line number Diff line
@@ -129,6 +129,29 @@ public class InsetsAnimationControlImplTest {
        assertPosition(navParams.matrix, new Rect(400, 0, 500, 500), new Rect(460, 0, 560, 500));
    }

    @Test
    public void testFinishing() {
        when(mMockController.getState()).thenReturn(mInsetsState);
        mController.finish(sideBars());
        mController.applyChangeInsets(mInsetsState);
        assertFalse(mInsetsState.getSource(TYPE_TOP_BAR).isVisible());
        assertTrue(mInsetsState.getSource(TYPE_NAVIGATION_BAR).isVisible());
        assertEquals(Insets.of(0, 0, 100, 0), mController.getCurrentInsets());
        verify(mMockController).notifyFinished(eq(mController), eq(sideBars()));
    }

    @Test
    public void testCancelled() {
        mController.onCancelled();
        try {
            mController.changeInsets(Insets.NONE);
            fail("Expected exception to be thrown");
        } catch (IllegalStateException ignored) {
        }
        verify(mMockListener).onCancelled();
        mController.finish(sideBars());
    }

    private void assertPosition(Matrix m, Rect original, Rect transformed) {
        RectF rect = new RectF(original);
        rect.offsetTo(0, 0);
Loading