Merge "Cancel existing insets animation if the side is changed" into main
diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java
index 229e8ee7..4f74198 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -221,13 +221,13 @@
 
     @Override
     public boolean setControl(@Nullable InsetsSourceControl control, int[] showTypes,
-            int[] hideTypes) {
+            int[] hideTypes, int[] cancelTypes) {
         if (Flags.refactorInsetsController()) {
-            return super.setControl(control, showTypes, hideTypes);
+            return super.setControl(control, showTypes, hideTypes, cancelTypes);
         } else {
             ImeTracing.getInstance().triggerClientDump("ImeInsetsSourceConsumer#setControl",
                     mController.getHost().getInputMethodManager(), null /* icProto */);
-            if (!super.setControl(control, showTypes, hideTypes)) {
+            if (!super.setControl(control, showTypes, hideTypes, cancelTypes)) {
                 return false;
             }
             if (control == null && !mIsRequestedVisibleAwaitingLeash) {
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 97facc1..4fead2a 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -371,6 +371,7 @@
         mPendingInsets = mLayoutInsetsDuringAnimation == LAYOUT_INSETS_DURING_ANIMATION_SHOWN
                 ? mShownInsets : mHiddenInsets;
         mPendingAlpha = 1f;
+        mPendingFraction = 1f;
         applyChangeInsets(null);
         mCancelled = true;
         mListener.onCancelled(mReadyDispatched ? this : null);
@@ -486,6 +487,17 @@
         if (controls == null) {
             return;
         }
+
+        final boolean visible = mPendingFraction == 0
+                // The first frame of ANIMATION_TYPE_SHOW should be invisible since it is
+                // animated from the hidden state.
+                ? mAnimationType != ANIMATION_TYPE_SHOW
+                : mPendingFraction < 1f || (mFinished
+                        ? mShownOnFinish
+                        // If the animation is cancelled, mFinished and mShownOnFinish are not set.
+                        // Here uses mLayoutInsetsDuringAnimation to decide if it should be visible.
+                        : mLayoutInsetsDuringAnimation == LAYOUT_INSETS_DURING_ANIMATION_SHOWN);
+
         // TODO: Implement behavior when inset spans over multiple types
         for (int i = controls.size() - 1; i >= 0; i--) {
             final InsetsSourceControl control = controls.valueAt(i);
@@ -498,12 +510,6 @@
             }
             addTranslationToMatrix(side, offset, mTmpMatrix, mTmpFrame);
 
-            // The first frame of ANIMATION_TYPE_SHOW should be invisible since it is animated from
-            // the hidden state.
-            final boolean visible = mPendingFraction == 0
-                    ? mAnimationType != ANIMATION_TYPE_SHOW
-                    : !mFinished || mShownOnFinish;
-
             if (outState != null && source != null) {
                 outState.addSource(new InsetsSource(source)
                         .setVisible(visible)
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 8ac5532..d08873c 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -957,6 +957,7 @@
         int consumedControlCount = 0;
         final @InsetsType int[] showTypes = new int[1];
         final @InsetsType int[] hideTypes = new int[1];
+        final @InsetsType int[] cancelTypes = new int[1];
         ImeTracker.Token statsToken = null;
 
         // Ensure to update all existing source consumers
@@ -982,7 +983,7 @@
 
             // control may be null, but we still need to update the control to null if it got
             // revoked.
-            consumer.setControl(control, showTypes, hideTypes);
+            consumer.setControl(control, showTypes, hideTypes, cancelTypes);
         }
 
         // Ensure to create source consumers if not available yet.
@@ -990,7 +991,7 @@
             for (int i = mTmpControlArray.size() - 1; i >= 0; i--) {
                 final InsetsSourceControl control = mTmpControlArray.valueAt(i);
                 getSourceConsumer(control.getId(), control.getType())
-                        .setControl(control, showTypes, hideTypes);
+                        .setControl(control, showTypes, hideTypes, cancelTypes);
             }
         }
 
@@ -1002,6 +1003,10 @@
         }
         mTmpControlArray.clear();
 
+        if (cancelTypes[0] != 0) {
+            cancelExistingControllers(cancelTypes[0]);
+        }
+
         // Do not override any animations that the app started in the OnControllableInsetsChanged
         // listeners.
         int animatingTypes = invokeControllableInsetsChangedListeners();
@@ -2154,12 +2159,12 @@
                         new InsetsSourceControl(ID_IME_CAPTION_BAR, captionBar(),
                                 null /* leash */, false /* initialVisible */,
                                 new Point(), Insets.NONE),
-                        new int[1], new int[1]);
+                        new int[1], new int[1], new int[1]);
             } else {
                 mState.removeSource(ID_IME_CAPTION_BAR);
                 InsetsSourceConsumer sourceConsumer = mSourceConsumers.get(ID_IME_CAPTION_BAR);
                 if (sourceConsumer != null) {
-                    sourceConsumer.setControl(null, new int[1], new int[1]);
+                    sourceConsumer.setControl(null, new int[1], new int[1], new int[1]);
                 }
             }
             mHost.notifyInsetsChanged();
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index da788a7..17f33c1 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -122,7 +122,7 @@
 
     /**
      * Updates the control delivered from the server.
-
+     *
      * @param showTypes An integer array with a single entry that determines which types a show
      *                  animation should be run after setting the control.
      * @param hideTypes An integer array with a single entry that determines which types a hide
@@ -130,7 +130,7 @@
      * @return Whether the control has changed from the server
      */
     public boolean setControl(@Nullable InsetsSourceControl control,
-            @InsetsType int[] showTypes, @InsetsType int[] hideTypes) {
+            @InsetsType int[] showTypes, @InsetsType int[] hideTypes, int[] cancelTypes) {
         if (Objects.equals(mSourceControl, control)) {
             if (mSourceControl != null && mSourceControl != control) {
                 mSourceControl.release(SurfaceControl::release);
@@ -165,6 +165,12 @@
             // Reset the applier to the default one which has the most lightweight implementation.
             setSurfaceParamsApplier(InsetsAnimationControlRunner.SurfaceParamsApplier.DEFAULT);
         } else {
+            if (lastControl != null && InsetsSource.getInsetSide(lastControl.getInsetsHint())
+                    != InsetsSource.getInsetSide(control.getInsetsHint())) {
+                // The source has been moved to a different side. The coordinates are stale.
+                // Canceling existing animation if there is any.
+                cancelTypes[0] |= mType;
+            }
             final boolean requestedVisible = isRequestedVisibleAwaitingControl();
             final SurfaceControl oldLeash = lastControl != null ? lastControl.getLeash() : null;
             final SurfaceControl newLeash = control.getLeash();
diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index ba6f62c..d7f6a29 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -100,14 +100,14 @@
         topConsumer.setControl(
                 new InsetsSourceControl(ID_STATUS_BAR, WindowInsets.Type.statusBars(),
                         mStatusLeash, true, new Point(0, 0), Insets.of(0, 100, 0, 0)),
-                new int[1], new int[1]);
+                new int[1], new int[1], new int[1]);
 
         InsetsSourceConsumer navConsumer = new InsetsSourceConsumer(ID_NAVIGATION_BAR,
                 WindowInsets.Type.navigationBars(), mInsetsState, mMockController);
         navConsumer.setControl(
                 new InsetsSourceControl(ID_NAVIGATION_BAR, WindowInsets.Type.navigationBars(),
                         mNavLeash, true, new Point(400, 0), Insets.of(0, 0, 100, 0)),
-                new int[1], new int[1]);
+                new int[1], new int[1], new int[1]);
         mMockController.setRequestedVisibleTypes(0, WindowInsets.Type.navigationBars());
         navConsumer.applyLocalVisibilityOverride();
 
diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
index d6d45e8..3a8f7ee 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
@@ -117,7 +117,23 @@
         mConsumer.setControl(
                 new InsetsSourceControl(ID_STATUS_BAR, statusBars(), mLeash,
                         true /* initialVisible */, new Point(), Insets.NONE),
-                new int[1], new int[1]);
+                new int[1], new int[1], new int[1]);
+    }
+
+    @Test
+    public void testSetControl_cancelAnimation() {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            final InsetsSourceControl newControl = new InsetsSourceControl(mConsumer.getControl());
+
+            // Change the side of the insets hint.
+            newControl.setInsetsHint(Insets.of(0, 0, 0, 100));
+
+            int[] cancelTypes = {0};
+            mConsumer.setControl(newControl, new int[1], new int[1], cancelTypes);
+
+            assertEquals(statusBars(), cancelTypes[0]);
+        });
+
     }
 
     @Test
@@ -180,7 +196,7 @@
     @Test
     public void testRestore() {
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            mConsumer.setControl(null, new int[1], new int[1]);
+            mConsumer.setControl(null, new int[1], new int[1], new int[1]);
             mSurfaceParamsApplied = false;
             mController.setRequestedVisibleTypes(0 /* visibleTypes */, statusBars());
             assertFalse(mSurfaceParamsApplied);
@@ -188,7 +204,7 @@
             mConsumer.setControl(
                     new InsetsSourceControl(ID_STATUS_BAR, statusBars(), mLeash,
                             true /* initialVisible */, new Point(), Insets.NONE),
-                    new int[1], hideTypes);
+                    new int[1], hideTypes, new int[1]);
             assertEquals(statusBars(), hideTypes[0]);
             assertFalse(mRemoveSurfaceCalled);
         });
@@ -198,7 +214,7 @@
     public void testRestore_noAnimation() {
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             mController.setRequestedVisibleTypes(0 /* visibleTypes */, statusBars());
-            mConsumer.setControl(null, new int[1], new int[1]);
+            mConsumer.setControl(null, new int[1], new int[1], new int[1]);
             mLeash = new SurfaceControl.Builder(mSession)
                     .setName("testSurface")
                     .build();
@@ -207,7 +223,7 @@
             mConsumer.setControl(
                     new InsetsSourceControl(ID_STATUS_BAR, statusBars(), mLeash,
                             false /* initialVisible */, new Point(), Insets.NONE),
-                    new int[1], hideTypes);
+                    new int[1], hideTypes, new int[1]);
             assertTrue(mRemoveSurfaceCalled);
             assertEquals(0, hideTypes[0]);
         });
@@ -235,7 +251,8 @@
 
             // Initial IME insets source control with its leash.
             imeConsumer.setControl(new InsetsSourceControl(ID_IME, ime(), mLeash,
-                    false /* initialVisible */, new Point(), Insets.NONE), new int[1], new int[1]);
+                    false /* initialVisible */, new Point(), Insets.NONE), new int[1], new int[1],
+                    new int[1]);
             mSurfaceParamsApplied = false;
 
             // Verify when the app requests controlling show IME animation, the IME leash
@@ -244,7 +261,8 @@
                     null /* interpolator */, null /* cancellationSignal */, null /* listener */);
             assertEquals(ANIMATION_TYPE_USER, insetsController.getAnimationType(ime()));
             imeConsumer.setControl(new InsetsSourceControl(ID_IME, ime(), mLeash,
-                    true /* initialVisible */, new Point(), Insets.NONE), new int[1], new int[1]);
+                    true /* initialVisible */, new Point(), Insets.NONE), new int[1], new int[1],
+                    new int[1]);
             assertFalse(mSurfaceParamsApplied);
         });
     }
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 8f28f59..6067a99 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -384,16 +384,19 @@
         }
         final boolean serverVisibleChanged = mServerVisible != isServerVisible;
         setServerVisible(isServerVisible);
-        final boolean positionChanged = updateInsetsControlPosition(windowState);
-        if (mControl != null && !positionChanged
-                // The insets hint would be updated if the position is changed. Here updates it for
-                // the possible change of the bounds or the server visibility.
-                && (updateInsetsHint()
-                        || serverVisibleChanged
-                                && android.view.inputmethod.Flags.refactorInsetsController())) {
-            // Only call notifyControlChanged here when the position is not changed. Otherwise, it
-            // is called or is scheduled to be called during updateInsetsControlPosition.
-            mStateController.notifyControlChanged(mControlTarget, this);
+        if (mControl != null) {
+            final boolean positionChanged = updateInsetsControlPosition(windowState);
+            if (!(positionChanged || mHasPendingPosition)
+                    // The insets hint would be updated while changing the position. Here updates it
+                    // for the possible change of the bounds or the server visibility.
+                    && (updateInsetsHint()
+                            || (android.view.inputmethod.Flags.refactorInsetsController()))
+                                    && serverVisibleChanged) {
+                // Only call notifyControlChanged here when the position hasn't been or won't be
+                // changed. Otherwise, it has been called or scheduled to be called during
+                // updateInsetsControlPosition.
+                mStateController.notifyControlChanged(mControlTarget, this);
+            }
         }
     }
 
@@ -409,6 +412,7 @@
             mPosition.set(position.x, position.y);
             if (windowState != null && windowState.getWindowFrames().didFrameSizeChange()
                     && windowState.mWinAnimator.getShown() && mWindowContainer.okToDisplay()) {
+                mHasPendingPosition = true;
                 windowState.applyWithNextDraw(mSetControlPositionConsumer);
             } else {
                 Transaction t = mWindowContainer.getSyncTransaction();