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

Commit a6666f22 authored by Shan Huang's avatar Shan Huang
Browse files

Migrate IME to handle back with OnBackInvokedDispatcher.

We currently close the IME by having the target application forward KEYCODE_BACK to the IME process through InputMethodManager#dispatchInputEvent and having the IME handle the keycode in InputMethodService#onKeyDown. When apps opt in to OnBackInvokedDispatcher API, we will not dispatch KEYCODE_BACK to apps anymore. Thus we need to migrate IME to the new API for it to close on back invocation.

This implementation forwards OnBackInvokedCallbacks from the IME process
to the app process. This is necessary because all callbacks need to
exist in the app process for them to be considered by hardware back keys. While back gestures go through WM to resolve callbacks from the focused window, hw keys are directly sent to the focused window's ViewRootImpl, bypassing server side back nav logic.

Bug: 228358882
Test: atest CtsInputMethodTestCases:KeyboardVisibilityControlTest
Test: atest CtsInputMethodTestCases:InputMethodServiceTest
Test: atest CtsInputMethodTestCases
Change-Id: Ie207b63b11a56c9b2173f26b734a27b13ebccc60
parent 7d130a7e
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -800,6 +800,7 @@ package android.content.pm {
    method public boolean hasRequestForegroundServiceExemption();
    method public boolean isPrivilegedApp();
    method public boolean isSystemApp();
    method public void setEnableOnBackInvokedCallback(boolean);
    field public static final int PRIVATE_FLAG_PRIVILEGED = 8; // 0x8
    field public int privateFlags;
  }
+17 −0
Original line number Diff line number Diff line
@@ -2752,4 +2752,21 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
            mKnownActivityEmbeddingCerts.add(knownCert.toUpperCase(Locale.US));
        }
    }

    /**
     * Sets whether the application will use the {@link android.window.OnBackInvokedCallback}
     * navigation system instead of the {@link android.view.KeyEvent#KEYCODE_BACK} and related
     * callbacks. Intended to be used from tests only.
     *
     * @see #isOnBackInvokedCallbackEnabled()
     * @hide
     */
    @TestApi
    public void setEnableOnBackInvokedCallback(boolean isEnable) {
        if (isEnable) {
            privateFlagsExt |= PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK;
        } else {
            privateFlagsExt &= ~PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK;
        }
    }
}
+11 −4
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethod;
import android.view.inputmethod.InputMethodSession;
import android.view.inputmethod.InputMethodSubtype;
import android.window.ImeOnBackInvokedDispatcher;

import com.android.internal.inputmethod.CancellationGroup;
import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
@@ -194,7 +195,9 @@ class IInputMethodWrapper extends IInputMethod.Stub
            case DO_START_INPUT: {
                final SomeArgs args = (SomeArgs) msg.obj;
                final IBinder startInputToken = (IBinder) args.arg1;
                final IInputContext inputContext = (IInputContext) args.arg2;
                final IInputContext inputContext = (IInputContext) ((SomeArgs) args.arg2).arg1;
                final ImeOnBackInvokedDispatcher imeDispatcher =
                        (ImeOnBackInvokedDispatcher) ((SomeArgs) args.arg2).arg2;
                final EditorInfo info = (EditorInfo) args.arg3;
                final CancellationGroup cancellationGroup = (CancellationGroup) args.arg4;
                final boolean restarting = args.argi5 == 1;
@@ -205,7 +208,7 @@ class IInputMethodWrapper extends IInputMethod.Stub
                        : null;
                info.makeCompatible(mTargetSdkVersion);
                inputMethod.dispatchStartInputWithToken(ic, info, restarting, startInputToken,
                        navButtonFlags);
                        navButtonFlags, imeDispatcher);
                args.recycle();
                return;
            }
@@ -348,13 +351,17 @@ class IInputMethodWrapper extends IInputMethod.Stub
    @Override
    public void startInput(IBinder startInputToken, IInputContext inputContext,
            EditorInfo attribute, boolean restarting,
            @InputMethodNavButtonFlags int navButtonFlags) {
            @InputMethodNavButtonFlags int navButtonFlags,
            @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
        if (mCancellationGroup == null) {
            Log.e(TAG, "startInput must be called after bindInput.");
            mCancellationGroup = new CancellationGroup();
        }
        SomeArgs args = SomeArgs.obtain();
        args.arg1 = inputContext;
        args.arg2 = imeDispatcher;
        mCaller.executeOrSendMessage(mCaller.obtainMessageOOOOII(DO_START_INPUT, startInputToken,
                inputContext, attribute, mCancellationGroup, restarting ? 1 : 0, navButtonFlags));
                args, attribute, mCancellationGroup, restarting ? 1 : 0, navButtonFlags));
    }

    @BinderThread
+69 −1
Original line number Diff line number Diff line
@@ -101,6 +101,7 @@ import android.view.BatchedInputEventReceiver.SimpleBatchedInputEventReceiver;
import android.view.Choreographer;
import android.view.Gravity;
import android.view.InputChannel;
import android.view.InputDevice;
import android.view.InputEventReceiver;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
@@ -134,6 +135,9 @@ import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.window.ImeOnBackInvokedDispatcher;
import android.window.OnBackInvokedCallback;
import android.window.OnBackInvokedDispatcher;
import android.window.WindowMetricsHelper;

import com.android.internal.annotations.GuardedBy;
@@ -344,6 +348,9 @@ public class InputMethodService extends AbstractInputMethodService {
     * A circular buffer of size MAX_EVENTS_BUFFER in case IME is taking too long to add ink view.
     **/
    private RingBuffer<MotionEvent> mPendingEvents;
    private ImeOnBackInvokedDispatcher mImeDispatcher;
    private Boolean mBackCallbackRegistered = false;
    private final OnBackInvokedCallback mCompatBackCallback = this::compatHandleBack;

    /**
     * Returns whether {@link InputMethodService} is responsible for rendering the back button and
@@ -797,7 +804,13 @@ public class InputMethodService extends AbstractInputMethodService {
        @Override
        public final void dispatchStartInputWithToken(@Nullable InputConnection inputConnection,
                @NonNull EditorInfo editorInfo, boolean restarting,
                @NonNull IBinder startInputToken, @InputMethodNavButtonFlags int navButtonFlags) {
                @NonNull IBinder startInputToken, @InputMethodNavButtonFlags int navButtonFlags,
                @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
            mImeDispatcher = imeDispatcher;
            if (mWindow != null) {
                mWindow.getOnBackInvokedDispatcher().setImeOnBackInvokedDispatcher(
                        imeDispatcher);
            }
            mPrivOps.reportStartInputAsync(startInputToken);
            mNavigationBarController.onNavButtonFlagsChanged(navButtonFlags);
            if (restarting) {
@@ -1496,6 +1509,10 @@ public class InputMethodService extends AbstractInputMethodService {
                Context.LAYOUT_INFLATER_SERVICE);
        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.initSoftInputWindow");
        mWindow = new SoftInputWindow(this, mTheme, mDispatcherState);
        if (mImeDispatcher != null) {
            mWindow.getOnBackInvokedDispatcher()
                    .setImeOnBackInvokedDispatcher(mImeDispatcher);
        }
        mNavigationBarController.onSoftInputWindowCreated(mWindow);
        {
            final Window window = mWindow.getWindow();
@@ -1608,6 +1625,8 @@ public class InputMethodService extends AbstractInputMethodService {
            // when IME developers are doing something unsupported.
            InputMethodPrivilegedOperationsRegistry.remove(mToken);
        }
        unregisterCompatOnBackInvokedCallback();
        mImeDispatcher = null;
    }

    /**
@@ -2568,9 +2587,47 @@ public class InputMethodService extends AbstractInputMethodService {
        cancelImeSurfaceRemoval();
        mInShowWindow = false;
        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
        registerCompatOnBackInvokedCallback();
    }


    /**
     * Registers an {@link OnBackInvokedCallback} to handle back invocation when ahead-of-time
     *  back dispatching is enabled. We keep the {@link KeyEvent#KEYCODE_BACK} based legacy code
     *  around to handle back on older devices.
     */
    private void registerCompatOnBackInvokedCallback() {
        if (mBackCallbackRegistered) {
            return;
        }
        if (mWindow != null) {
            mWindow.getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
                    OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCompatBackCallback);
            mBackCallbackRegistered = true;
        }
    }

    private void unregisterCompatOnBackInvokedCallback() {
        if (!mBackCallbackRegistered) {
            return;
        }
        if (mWindow != null) {
            mWindow.getOnBackInvokedDispatcher()
                    .unregisterOnBackInvokedCallback(mCompatBackCallback);
            mBackCallbackRegistered = false;
        }
    }

    private KeyEvent createBackKeyEvent(int action, boolean isTracking) {
        final long when = SystemClock.uptimeMillis();
        return new KeyEvent(when, when, action,
                KeyEvent.KEYCODE_BACK, 0 /* repeat */, 0 /* metaState */,
                KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
                KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY
                        | (isTracking ? KeyEvent.FLAG_TRACKING : 0),
                InputDevice.SOURCE_KEYBOARD);
    }

    private boolean prepareWindow(boolean showInput) {
        boolean doShowInput = false;
        mDecorViewVisible = true;
@@ -2658,6 +2715,7 @@ public class InputMethodService extends AbstractInputMethodService {
        }
        mLastWasInFullscreenMode = mIsFullscreen;
        updateFullscreenMode();
        unregisterCompatOnBackInvokedCallback();
    }

    /**
@@ -3797,4 +3855,14 @@ public class InputMethodService extends AbstractInputMethodService {
            proto.end(token);
        }
    };

    private void compatHandleBack() {
        final KeyEvent downEvent = createBackKeyEvent(
                KeyEvent.ACTION_DOWN, false /* isTracking */);
        onKeyDown(KeyEvent.KEYCODE_BACK, downEvent);
        final boolean hasStartedTracking =
                (downEvent.getFlags() & KeyEvent.FLAG_START_TRACKING) != 0;
        final KeyEvent upEvent = createBackKeyEvent(KeyEvent.ACTION_UP, hasStartedTracking);
        onKeyUp(KeyEvent.KEYCODE_BACK, upEvent);
    }
}
+22 −18
Original line number Diff line number Diff line
@@ -6107,6 +6107,28 @@ public final class ViewRootImpl implements ViewParent,

        @Override
        protected int onProcess(QueuedInputEvent q) {
            if (q.mEvent instanceof KeyEvent) {
                final KeyEvent event = (KeyEvent) q.mEvent;

                // If the new back dispatch is enabled, intercept KEYCODE_BACK before it reaches the
                // view tree or IME, and invoke the appropriate {@link OnBackInvokedCallback}.
                if (isBack(event)
                        && mContext != null
                        && WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(mContext)) {
                    OnBackInvokedCallback topCallback =
                            getOnBackInvokedDispatcher().getTopCallback();
                    if (event.getAction() == KeyEvent.ACTION_UP) {
                        if (topCallback != null) {
                            topCallback.onBackInvoked();
                            return FINISH_HANDLED;
                        }
                    } else {
                        // Drop other actions such as {@link KeyEvent.ACTION_DOWN}.
                        return FINISH_NOT_HANDLED;
                    }
                }
            }

            if (mInputQueue != null && q.mEvent instanceof KeyEvent) {
                mInputQueue.sendInputEvent(q.mEvent, q, true, this);
                return DEFER;
@@ -6458,24 +6480,6 @@ public final class ViewRootImpl implements ViewParent,
                return FINISH_HANDLED;
            }

            // If the new back dispatch is enabled, intercept KEYCODE_BACK before it reaches the
            // view tree and invoke the appropriate {@link OnBackInvokedCallback}.
            if (isBack(event)
                    && mContext != null
                    && WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(mContext)) {
                OnBackInvokedCallback topCallback =
                        getOnBackInvokedDispatcher().getTopCallback();
                if (event.getAction() == KeyEvent.ACTION_UP) {
                    if (topCallback != null) {
                        topCallback.onBackInvoked();
                        return FINISH_HANDLED;
                    }
                } else {
                    // Drop other actions such as {@link KeyEvent.ACTION_DOWN}.
                    return FINISH_NOT_HANDLED;
                }
            }

            // Deliver the key to the view hierarchy.
            if (mView.dispatchKeyEvent(event)) {
                return FINISH_HANDLED;
Loading