Unregister IME callback in onBackInvoked immediately

By removing the IME callback in onBackInvoked immediately, a new back gesture that starts during the IME hide animation can already dispatch to a new back callback. Currently, back gestures that start during the (predictive back) IME hide animation are simply ignored. This CL fixes that.

Bug: 322836622
Flag: ACONFIG android.view.inputmethod.predictive_back_ime DISABLED
Test: separate CL
Change-Id: Iab628e23982915b5c5f17896436157794e90b649
diff --git a/core/java/android/view/ImeBackAnimationController.java b/core/java/android/view/ImeBackAnimationController.java
index 665fac1..911f7b2 100644
--- a/core/java/android/view/ImeBackAnimationController.java
+++ b/core/java/android/view/ImeBackAnimationController.java
@@ -31,9 +31,12 @@
 import android.view.animation.BackGestureInterpolator;
 import android.view.animation.Interpolator;
 import android.view.animation.PathInterpolator;
+import android.view.inputmethod.ImeTracker;
 import android.window.BackEvent;
 import android.window.OnBackAnimationCallback;
 
+import com.android.internal.inputmethod.SoftInputShowHideReason;
+
 /**
  * Controller for IME predictive back animation
  *
@@ -192,6 +195,24 @@
         mPostCommitAnimator.setDuration(
                 triggerBack ? POST_COMMIT_DURATION_MS : POST_COMMIT_CANCEL_DURATION_MS);
         mPostCommitAnimator.start();
+        if (triggerBack) {
+            mInsetsController.setPredictiveBackImeHideAnimInProgress(true);
+            notifyHideIme();
+        }
+    }
+
+    private void notifyHideIme() {
+        ImeTracker.Token statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE,
+                ImeTracker.ORIGIN_CLIENT,
+                SoftInputShowHideReason.HIDE_SOFT_INPUT_REQUEST_HIDE_WITH_CONTROL, true);
+        // This notifies the IME that it is being hidden. In response, the IME will unregister the
+        // animation callback, such that new back gestures happening during the post-commit phase of
+        // the hide animation can already dispatch to a new callback.
+        // Note that the IME will call hide() in InsetsController. InsetsController will not animate
+        // that hide request if it sees that ImeBackAnimationController is already animating
+        // the IME away
+        mInsetsController.getHost().getInputMethodManager()
+                .notifyImeHidden(mInsetsController.getHost().getWindowToken(), statsToken);
     }
 
     private void reset() {
@@ -200,6 +221,7 @@
         mLastProgress = 0f;
         mTriggerBack = false;
         mIsPreCommitAnimationInProgress = false;
+        mInsetsController.setPredictiveBackImeHideAnimInProgress(false);
     }
 
     private void resetPostCommitAnimator() {
diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java
index 6caf4d6..3def604 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -243,7 +243,8 @@
      * {@link InputMethodManager#showSoftInput(View, int)} is called.
      */
     public void onShowRequested() {
-        if (mAnimationState == ANIMATION_STATE_HIDE) {
+        if (mAnimationState == ANIMATION_STATE_HIDE
+                || mController.isPredictiveBackImeHideAnimInProgress()) {
             mHasPendingRequest = true;
         }
     }
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 1f6ceca..8c00fbb 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -651,6 +651,7 @@
     private @Appearance int mAppearanceControlled;
     private @Appearance int mAppearanceFromResource;
     private boolean mBehaviorControlled;
+    private boolean mIsPredictiveBackImeHideAnimInProgress;
 
     private final Runnable mPendingControlTimeout = this::abortPendingImeControlRequest;
     private final ArrayList<OnControllableInsetsChangedListener> mControllableInsetsChangedListeners
@@ -1027,6 +1028,14 @@
         reportRequestedVisibleTypes();
     }
 
+    void setPredictiveBackImeHideAnimInProgress(boolean isInProgress) {
+        mIsPredictiveBackImeHideAnimInProgress = isInProgress;
+    }
+
+    boolean isPredictiveBackImeHideAnimInProgress() {
+        return mIsPredictiveBackImeHideAnimInProgress;
+    }
+
     @Override
     public void show(@InsetsType int types) {
         show(types, false /* fromIme */, null /* statsToken */);
@@ -1090,7 +1099,8 @@
                 }
                 continue;
             }
-            if (fromIme && animationType == ANIMATION_TYPE_USER) {
+            if (fromIme && animationType == ANIMATION_TYPE_USER
+                    && !mIsPredictiveBackImeHideAnimInProgress) {
                 // App is already controlling the IME, don't cancel it.
                 if (isIme) {
                     ImeTracker.forLogging().onFailed(
@@ -1186,7 +1196,8 @@
                 }
             }
             if (!requestedVisible && animationType == ANIMATION_TYPE_NONE
-                    || animationType == ANIMATION_TYPE_HIDE) {
+                    || animationType == ANIMATION_TYPE_HIDE || (animationType
+                    == ANIMATION_TYPE_USER && mIsPredictiveBackImeHideAnimInProgress)) {
                 // no-op: already hidden or animating out (because window visibility is
                 // applied before starting animation).
                 if (isImeAnimation) {