Fix some split-screen IME glitches

First of all, this fixes some ime reporting issues. Specifically,
the ime state could be updated without a queued traversal which
would cause some messages to get lost.

Second, this has the Divider switch to "async" animation when
stack adjustment is no longer aligned with IME animation. This
happens when switching ime focus from one split to the other.

Additionally, I had to change the ime animations to remember
where they were before "switching directions" because it turns
out the insets logic varies when focus changes: sometimes you
get only a show() other times you get a hide() followed
immediately by a show()

Bug: 133381284
Bug: 149952263
Bug: 150702357
Test: manually open split-screen with editors in each split
      and try switching between them.
Change-Id: I8c92ce4e74651f08ef84d4f1c4b2ddb9625123f1
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
index 27b799b..333fa3c 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
@@ -19,6 +19,9 @@
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.view.Display.DEFAULT_DISPLAY;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
 import android.app.ActivityTaskManager;
 import android.content.Context;
 import android.content.res.Configuration;
@@ -35,6 +38,8 @@
 import android.view.View;
 import android.view.WindowContainerTransaction;
 
+import androidx.annotation.Nullable;
+
 import com.android.internal.policy.DividerSnapAlgorithm;
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
@@ -69,6 +74,7 @@
     static final boolean DEBUG = true;
 
     static final int DEFAULT_APP_TRANSITION_DURATION = 336;
+    static final float ADJUSTED_NONFOCUS_DIM = 0.3f;
 
     private final Optional<Lazy<Recents>> mRecentsOptionalLazy;
 
@@ -121,42 +127,92 @@
                 }
             };
 
-    private IWindowContainer mLastImeTarget = null;
-    private boolean mShouldAdjustForIme = false;
-
     private DisplayImeController.ImePositionProcessor mImePositionProcessor =
             new DisplayImeController.ImePositionProcessor() {
-                private int mStartTop = 0;
-                private int mFinalTop = 0;
+                /**
+                 * These are the y positions of the top of the IME surface when it is hidden and
+                 * when it is shown respectively. These are NOT necessarily the top of the visible
+                 * IME itself.
+                 */
+                private int mHiddenTop = 0;
+                private int mShownTop = 0;
+
+                // The following are target states (what we are curretly animating towards).
+                /**
+                 * {@code true} if, at the end of the animation, the split task positions should be
+                 * adjusted by height of the IME. This happens when the secondary split is the IME
+                 * target.
+                 */
+                private boolean mTargetAdjusted = false;
+                /**
+                 * {@code true} if, at the end of the animation, the IME should be shown/visible
+                 * regardless of what has focus.
+                 */
+                private boolean mTargetShown = false;
+
+                // The following are the current (most recent) states set during animation
+                /**
+                 * {@code true} if the secondary split has IME focus.
+                 */
+                private boolean mSecondaryHasFocus = false;
+                /** The dimming currently applied to the primary/secondary splits. */
+                private float mLastPrimaryDim = 0.f;
+                private float mLastSecondaryDim = 0.f;
+                /** The most recent y position of the top of the IME surface */
+                private int mLastAdjustTop = -1;
+
+                // The following are states reached last time an animation fully completed.
+                /** {@code true} if the IME was shown/visible by the last-completed animation. */
+                private boolean mImeWasShown = false;
+                /**
+                 * {@code true} if the split positions were adjusted by the last-completed
+                 * animation.
+                 */
+                private boolean mAdjusted = false;
+
+                /**
+                 * When some aspect of split-screen needs to animate independent from the IME,
+                 * this will be non-null and control split animation.
+                 */
+                @Nullable
+                private ValueAnimator mAnimation = null;
+
+                private boolean getSecondaryHasFocus(int displayId) {
+                    try {
+                        IWindowContainer imeSplit = ActivityTaskManager.getTaskOrganizerController()
+                                .getImeTarget(displayId);
+                        return imeSplit != null
+                                && (imeSplit.asBinder() == mSplits.mSecondary.token.asBinder());
+                    } catch (RemoteException e) {
+                        Slog.w(TAG, "Failed to get IME target", e);
+                    }
+                    return false;
+                }
+
                 @Override
-                public void onImeStartPositioning(int displayId, int imeTop, int finalImeTop,
-                        boolean showing, SurfaceControl.Transaction t) {
-                    mStartTop = imeTop;
-                    mFinalTop = finalImeTop;
-                    if (showing) {
-                        try {
-                            mLastImeTarget = ActivityTaskManager.getTaskOrganizerController()
-                                    .getImeTarget(displayId);
-                            mShouldAdjustForIme = mLastImeTarget != null
-                                    && !mSplitLayout.mDisplayLayout.isLandscape()
-                                    && (mLastImeTarget.asBinder()
-                                    == mSplits.mSecondary.token.asBinder());
-                        } catch (RemoteException e) {
-                            Slog.w(TAG, "Failed to get IME target", e);
-                        }
+                public void onImeStartPositioning(int displayId, int hiddenTop, int shownTop,
+                        boolean imeShouldShow, SurfaceControl.Transaction t) {
+                    mSecondaryHasFocus = getSecondaryHasFocus(displayId);
+                    mTargetAdjusted = imeShouldShow && mSecondaryHasFocus
+                            && !mSplitLayout.mDisplayLayout.isLandscape();
+                    mHiddenTop = hiddenTop;
+                    mShownTop = shownTop;
+                    mTargetShown = imeShouldShow;
+                    if (mLastAdjustTop < 0) {
+                        mLastAdjustTop = imeShouldShow ? hiddenTop : shownTop;
                     }
-                    if (!mShouldAdjustForIme) {
-                        setAdjustedForIme(false);
-                        return;
+                    if (mAnimation != null || (mImeWasShown && imeShouldShow
+                            && mTargetAdjusted != mAdjusted)) {
+                        // We need to animate adjustment independently of the IME position, so
+                        // start our own animation to drive adjustment. This happens when a
+                        // different split's editor has gained focus while the IME is still visible.
+                        startAsyncAnimation();
                     }
-                    mView.setAdjustedForIme(showing, showing
-                            ? DisplayImeController.ANIMATION_DURATION_SHOW_MS
-                            : DisplayImeController.ANIMATION_DURATION_HIDE_MS);
                     // Reposition the server's secondary split position so that it evaluates
                     // insets properly.
                     WindowContainerTransaction wct = new WindowContainerTransaction();
-                    if (showing) {
-                        mSplitLayout.updateAdjustedBounds(finalImeTop, imeTop, finalImeTop);
+                    if (mTargetAdjusted) {
+                        mSplitLayout.updateAdjustedBounds(mShownTop, mHiddenTop, mShownTop);
                         wct.setBounds(mSplits.mSecondary.token, mSplitLayout.mAdjustedSecondary);
                     } else {
                         wct.setBounds(mSplits.mSecondary.token, mSplitLayout.mSecondary);
@@ -166,34 +222,106 @@
                                 .applyContainerTransaction(wct, null /* organizer */);
                     } catch (RemoteException e) {
                     }
-                    setAdjustedForIme(showing);
+
+                    // Update all the adjusted-for-ime states
+                    mView.setAdjustedForIme(mTargetShown, mTargetShown
+                            ? DisplayImeController.ANIMATION_DURATION_SHOW_MS
+                            : DisplayImeController.ANIMATION_DURATION_HIDE_MS);
+                    setAdjustedForIme(mTargetShown);
                 }
 
                 @Override
                 public void onImePositionChanged(int displayId, int imeTop,
                         SurfaceControl.Transaction t) {
-                    if (!mShouldAdjustForIme) {
+                    if (mAnimation != null) {
+                        // Not synchronized with IME anymore, so return.
                         return;
                     }
-                    mSplitLayout.updateAdjustedBounds(imeTop, mStartTop, mFinalTop);
-                    mView.resizeSplitSurfaces(t, mSplitLayout.mAdjustedPrimary,
-                            mSplitLayout.mAdjustedSecondary);
-                    final boolean showing = mFinalTop < mStartTop;
-                    final float progress = ((float) (imeTop - mStartTop)) / (mFinalTop - mStartTop);
-                    final float fraction = showing ? progress : 1.f - progress;
-                    mView.setResizeDimLayer(t, true /* primary */, fraction * 0.3f);
+                    final float fraction = ((float) imeTop - mHiddenTop) / (mShownTop - mHiddenTop);
+                    final float progress = mTargetShown ? fraction : 1.f - fraction;
+                    onProgress(progress, t);
                 }
 
                 @Override
-                public void onImeEndPositioning(int displayId, int imeTop,
-                        boolean showing, SurfaceControl.Transaction t) {
-                    if (!mShouldAdjustForIme) {
+                public void onImeEndPositioning(int displayId, boolean cancelled,
+                        SurfaceControl.Transaction t) {
+                    if (mAnimation != null) {
+                        // Not synchronized with IME anymore, so return.
                         return;
                     }
-                    mSplitLayout.updateAdjustedBounds(imeTop, mStartTop, mFinalTop);
-                    mView.resizeSplitSurfaces(t, mSplitLayout.mAdjustedPrimary,
-                            mSplitLayout.mAdjustedSecondary);
-                    mView.setResizeDimLayer(t, true /* primary */, showing ? 0.3f : 0.f);
+                    onEnd(cancelled, t);
+                }
+
+                private void onProgress(float progress, SurfaceControl.Transaction t) {
+                    if (mTargetAdjusted != mAdjusted) {
+                        final float fraction = mTargetAdjusted ? progress : 1.f - progress;
+                        mLastAdjustTop =
+                                (int) (fraction * mShownTop + (1.f - fraction) * mHiddenTop);
+                        mSplitLayout.updateAdjustedBounds(mLastAdjustTop, mHiddenTop, mShownTop);
+                        mView.resizeSplitSurfaces(t, mSplitLayout.mAdjustedPrimary,
+                                mSplitLayout.mAdjustedSecondary);
+                    }
+                    final float invProg = 1.f - progress;
+                    final float targetPrimaryDim = (mSecondaryHasFocus && mTargetShown)
+                            ? ADJUSTED_NONFOCUS_DIM : 0.f;
+                    final float targetSecondaryDim = (!mSecondaryHasFocus && mTargetShown)
+                            ? ADJUSTED_NONFOCUS_DIM : 0.f;
+                    mView.setResizeDimLayer(t, true /* primary */,
+                            mLastPrimaryDim * invProg + progress * targetPrimaryDim);
+                    mView.setResizeDimLayer(t, false /* primary */,
+                            mLastSecondaryDim * invProg + progress * targetSecondaryDim);
+                }
+
+                private void onEnd(boolean cancelled, SurfaceControl.Transaction t) {
+                    if (!cancelled) {
+                        onProgress(1.f, t);
+                        mAdjusted = mTargetAdjusted;
+                        mImeWasShown = mTargetShown;
+                        mLastAdjustTop = mAdjusted ? mShownTop : mHiddenTop;
+                        mLastPrimaryDim =
+                                (mSecondaryHasFocus && mTargetShown) ? ADJUSTED_NONFOCUS_DIM : 0.f;
+                        mLastSecondaryDim =
+                                (!mSecondaryHasFocus && mTargetShown) ? ADJUSTED_NONFOCUS_DIM : 0.f;
+                    }
+                }
+
+                private void startAsyncAnimation() {
+                    if (mAnimation != null) {
+                        mAnimation.cancel();
+                    }
+                    mAnimation = ValueAnimator.ofFloat(0.f, 1.f);
+                    mAnimation.setDuration(DisplayImeController.ANIMATION_DURATION_SHOW_MS);
+                    if (mTargetAdjusted != mAdjusted) {
+                        final float fraction =
+                                ((float) mLastAdjustTop - mHiddenTop) / (mShownTop - mHiddenTop);
+                        final float progress = mTargetAdjusted ? fraction : 1.f - fraction;
+                        mAnimation.setCurrentFraction(progress);
+                    }
+
+                    mAnimation.addUpdateListener(animation -> {
+                        SurfaceControl.Transaction t = mTransactionPool.acquire();
+                        float value = (float) animation.getAnimatedValue();
+                        onProgress(value, t);
+                        t.apply();
+                        mTransactionPool.release(t);
+                    });
+                    mAnimation.setInterpolator(DisplayImeController.INTERPOLATOR);
+                    mAnimation.addListener(new AnimatorListenerAdapter() {
+                        private boolean mCancel = false;
+                        @Override
+                        public void onAnimationCancel(Animator animation) {
+                            mCancel = true;
+                        }
+                        @Override
+                        public void onAnimationEnd(Animator animation) {
+                            SurfaceControl.Transaction t = mTransactionPool.acquire();
+                            onEnd(mCancel, t);
+                            t.apply();
+                            mTransactionPool.release(t);
+                            mAnimation = null;
+                        }
+                    });
+                    mAnimation.start();
                 }
             };