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

Commit e6069c98 authored by Johannes Gallmann's avatar Johannes Gallmann
Browse files

Fix back swipes/presses ignored during IME hide animation

This change unregisters all IME callbacks when a IME hide animation starts, such that new back events immediately start dispatching to the next back callback.

In case the hide animation is interrupted causing the IME to reappear (before fully disappearing), the previously unregistered IME back callbacks are reregistered to restore a valid callback state.

Bug: 375986921
Test: Manual, i.e. verifying that quick double back swipes are handled correctly (including after an interrupted IME hide animation)
Test: ImeBackAnimationControllerTest
Flag: android.view.inputmethod.refactor_insets_controller
Change-Id: Ic6b2229cae21e91c85f32c5dec4ca1845962ae31
parent 4a10fd11
Loading
Loading
Loading
Loading
+9 −2
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ import android.util.Log;
import android.view.animation.BackGestureInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
import android.view.inputmethod.Flags;
import android.view.inputmethod.ImeTracker;
import android.window.BackEvent;
import android.window.OnBackAnimationCallback;
@@ -142,10 +143,16 @@ public class ImeBackAnimationController implements OnBackAnimationCallback {
            // control has been cancelled by the system. This can happen in multi-window mode for
            // example (i.e. split-screen or activity-embedding)
            notifyHideIme();
            return;
        }
        } else {
            startPostCommitAnim(/*hideIme*/ true);
        }
        if (Flags.refactorInsetsController()) {
            // Unregister all IME back callbacks so that back events are sent to the next callback
            // even while the hide animation is playing
            mInsetsController.getHost().getInputMethodManager().getImeOnBackInvokedDispatcher()
                    .preliminaryClear();
        }
    }

    private void setPreCommitProgress(float progress) {
        if (isHideAnimationInProgress()) return;
+13 −0
Original line number Diff line number Diff line
@@ -1344,6 +1344,11 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
            boolean fromPredictiveBack) {
        final boolean visible = layoutInsetsDuringAnimation == LAYOUT_INSETS_DURING_ANIMATION_SHOWN;

        if (Flags.refactorInsetsController() && !fromPredictiveBack && !visible
                && (types & ime()) != 0 && (mRequestedVisibleTypes & ime()) != 0) {
            // Clear IME back callbacks if a IME hide animation is requested
            mHost.getInputMethodManager().getImeOnBackInvokedDispatcher().preliminaryClear();
        }
        // Basically, we accept the requested visibilities from the upstream callers...
        setRequestedVisibleTypes(visible ? types : 0, types);

@@ -1921,6 +1926,14 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
        final @InsetsType int requestedVisibleTypes =
                (mRequestedVisibleTypes & ~mask) | (visibleTypes & mask);
        if (mRequestedVisibleTypes != requestedVisibleTypes) {
            if (Flags.refactorInsetsController() && (mRequestedVisibleTypes & ime()) == 0
                    && (requestedVisibleTypes & ime()) != 0) {
                // In case the IME back callbacks have been preliminarily cleared before, let's
                // reregister them. This can happen if an IME hide animation was interrupted and the
                // IME is requested to be shown again.
                getHost().getInputMethodManager().getImeOnBackInvokedDispatcher()
                        .undoPreliminaryClear();
            }
            ProtoLog.d(IME_INSETS_CONTROLLER, "Setting requestedVisibleTypes to %d (was %d)",
                    requestedVisibleTypes, mRequestedVisibleTypes);
            mRequestedVisibleTypes = requestedVisibleTypes;
+8 −0
Original line number Diff line number Diff line
@@ -3666,6 +3666,14 @@ public final class InputMethodManager {
                0 /* startInputFlags */, 0 /* softInputMode */, 0 /* windowFlags */);
    }

    /**
     * Returns the ImeOnBackInvokedDispatcher.
     * @hide
     */
    public ImeOnBackInvokedDispatcher getImeOnBackInvokedDispatcher() {
        return mImeDispatcher;
    }

    /**
     * Check the next served view if needs to start input.
     */
+28 −0
Original line number Diff line number Diff line
@@ -203,6 +203,34 @@ public class ImeOnBackInvokedDispatcher implements OnBackInvokedDispatcher, Parc
        mImeCallbacks.remove(callback);
    }

    /**
     * Unregisters all callbacks on the receiving dispatcher but keeps a reference of the callbacks
     * in case the clearance is reverted in
     * {@link ImeOnBackInvokedDispatcher#undoPreliminaryClear()}.
     */
    public void preliminaryClear() {
        // Unregister previously registered callbacks if there's any.
        if (getReceivingDispatcher() != null) {
            for (ImeOnBackInvokedCallback callback : mImeCallbacks) {
                getReceivingDispatcher().unregisterOnBackInvokedCallback(callback);
            }
        }
    }

    /**
     * Reregisters all callbacks on the receiving dispatcher that have previously been cleared by
     * calling {@link ImeOnBackInvokedDispatcher#preliminaryClear()}. This can happen if an IME hide
     * animation is interrupted causing the IME to reappear.
     */
    public void undoPreliminaryClear() {
        if (getReceivingDispatcher() != null) {
            for (ImeOnBackInvokedCallback callback : mImeCallbacks) {
                getReceivingDispatcher().registerOnBackInvokedCallbackUnchecked(callback,
                        callback.mPriority);
            }
        }
    }

    /** Clears all registered callbacks on the instance. */
    public void clear() {
        // Unregister previously registered callbacks if there's any.
+3 −3
Original line number Diff line number Diff line
@@ -161,7 +161,7 @@ public class ImeBackAnimationControllerTest {
        mBackAnimationController.onBackInvoked();
        // verify that InputMethodManager#notifyImeHidden is called (which is the case whenever
        // getInputMethodManager is called from ImeBackAnimationController)
        verify(mViewRootInsetsControllerHost, times(1)).getInputMethodManager();
        verify(mViewRootInsetsControllerHost, times(2)).getInputMethodManager();
        // verify that ImeBackAnimationController does not take control over IME insets
        verify(mInsetsController, never()).controlWindowInsetsAnimation(anyInt(), any(), any(),
                anyBoolean(), anyLong(), any(), anyInt(), anyBoolean());
@@ -180,7 +180,7 @@ public class ImeBackAnimationControllerTest {
        mBackAnimationController.onBackInvoked();
        // verify that InputMethodManager#notifyImeHidden is called (which is the case whenever
        // getInputMethodManager is called from ImeBackAnimationController)
        verify(mViewRootInsetsControllerHost, times(1)).getInputMethodManager();
        verify(mViewRootInsetsControllerHost, times(2)).getInputMethodManager();
        // verify that ImeBackAnimationController does not take control over IME insets
        verify(mInsetsController, never()).controlWindowInsetsAnimation(anyInt(), any(), any(),
                anyBoolean(), anyLong(), any(), anyInt(), anyBoolean());
@@ -300,7 +300,7 @@ public class ImeBackAnimationControllerTest {
            mBackAnimationController.onBackInvoked();
            // verify that InputMethodManager#notifyImeHidden is called (which is the case whenever
            // getInputMethodManager is called from ImeBackAnimationController)
            verify(mViewRootInsetsControllerHost, times(1)).getInputMethodManager();
            verify(mViewRootInsetsControllerHost, times(2)).getInputMethodManager();
        });
    }