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

Commit 467fd168 authored by Taran Singh's avatar Taran Singh
Browse files

Introduce new IMS method for Stylus events and dispatch

As of today we show the InkWindow and dispatch Stylus events without
waiting. At the time of dispatch, InkWindow is not added and first stylus gesture
is skipped. In order to deliver the first gesture to IME, we should wait until
InkWindow is added and IME's Inking View is attached.

However, waiting for it will introduce latency in the system. To counter
that, we introduce a new API method to listen for Stylus MotionEvent.
IME can override the API method to listen to MotionEvents sooner.

By default, the stylus MotionEvents are buffered until Ink view is visible.

Change-Id: I3a18067da45fd15bf0f6e377da0a82cdd6df1d41
Fix: 222081673
Test: atest StylusHandwriting
parent e2722fe9
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -18818,6 +18818,7 @@ package android.inputmethodservice {
    method public void onStartInput(android.view.inputmethod.EditorInfo, boolean);
    method public void onStartInputView(android.view.inputmethod.EditorInfo, boolean);
    method public boolean onStartStylusHandwriting();
    method public void onStylusHandwritingMotionEvent(@NonNull android.view.MotionEvent);
    method public void onUnbindInput();
    method @Deprecated public void onUpdateCursor(android.graphics.Rect);
    method public void onUpdateCursorAnchorInfo(android.view.inputmethod.CursorAnchorInfo);
+78 −0
Original line number Diff line number Diff line
@@ -27,6 +27,8 @@ import android.content.Context;
import android.os.IBinder;
import android.util.Slog;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.WindowManager;

import com.android.internal.policy.PhoneWindow;
@@ -40,6 +42,9 @@ final class InkWindow extends PhoneWindow {

    private final WindowManager mWindowManager;
    private boolean mIsViewAdded;
    private View mInkView;
    private InkVisibilityListener mInkViewVisibilityListener;
    private ViewTreeObserver.OnGlobalLayoutListener mGlobalLayoutListener;

    public InkWindow(@NonNull Context context) {
        super(context);
@@ -102,4 +107,77 @@ final class InkWindow extends PhoneWindow {
        lp.token = token;
        setAttributes(lp);
    }

    @Override
    public void addContentView(View view, ViewGroup.LayoutParams params) {
        if (mInkView == null) {
            mInkView = view;
        } else if (mInkView != view) {
            throw new IllegalStateException("Only one Child Inking view is permitted.");
        }
        super.addContentView(view, params);
        initInkViewVisibilityListener();
    }

    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        mInkView = view;
        super.setContentView(view, params);
        initInkViewVisibilityListener();
    }

    @Override
    public void setContentView(View view) {
        mInkView = view;
        super.setContentView(view);
        initInkViewVisibilityListener();
    }

    @Override
    public void clearContentView() {
        if (mGlobalLayoutListener != null && mInkView != null) {
            mInkView.getViewTreeObserver().removeOnGlobalLayoutListener(mGlobalLayoutListener);
        }
        mGlobalLayoutListener = null;
        mInkView = null;
        super.clearContentView();
    }

    /**
    * Listener used by InkWindow to time the dispatching of {@link MotionEvent}s to Ink view, once
    * it is visible to user.
    */
    interface InkVisibilityListener {
        void onInkViewVisible();
    }

    void setInkViewVisibilityListener(InkVisibilityListener listener) {
        mInkViewVisibilityListener = listener;
        initInkViewVisibilityListener();
    }

    void initInkViewVisibilityListener() {
        if (mInkView == null || mInkViewVisibilityListener == null
                || mGlobalLayoutListener != null) {
            return;
        }
        mGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                if (mInkView.isVisibleToUser()) {
                    if (mInkViewVisibilityListener != null) {
                        mInkViewVisibilityListener.onInkViewVisible();
                    }
                    mInkView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                    mGlobalLayoutListener = null;
                }
            }
        };
        mInkView.getViewTreeObserver().addOnGlobalLayoutListener(mGlobalLayoutListener);
    }

    boolean isInkViewVisible() {
        return getDecorView().getVisibility() == View.VISIBLE
                && mInkView != null && mInkView.isVisibleToUser();
    }
}
+45 −4
Original line number Diff line number Diff line
@@ -142,6 +142,7 @@ import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
import com.android.internal.inputmethod.ImeTracing;
import com.android.internal.inputmethod.InputMethodPrivilegedOperations;
import com.android.internal.inputmethod.InputMethodPrivilegedOperationsRegistry;
import com.android.internal.util.RingBuffer;
import com.android.internal.view.IInlineSuggestionsRequestCallback;
import com.android.internal.view.IInputContext;
import com.android.internal.view.InlineSuggestionsRequestInfo;
@@ -332,6 +333,17 @@ public class InputMethodService extends AbstractInputMethodService {
    private static final String PROP_CAN_RENDER_GESTURAL_NAV_BUTTONS =
            "persist.sys.ime.can_render_gestural_nav_buttons";

    /**
     * Number of {@link MotionEvent} to buffer if IME is not ready with Ink view.
     * This number may be configured eventually based on device's touch sampling frequency.
     */
    private static final int MAX_EVENTS_BUFFER = 500;

    /**
     * A circular buffer of size MAX_EVENTS_BUFFER in case IME is taking too long to add ink view.
     **/
    private RingBuffer<MotionEvent> mPendingEvents;

    /**
     * Returns whether {@link InputMethodService} is responsible for rendering the back button and
     * the IME switcher button or not when the gestural navigation is enabled.
@@ -957,7 +969,8 @@ public class InputMethodService extends AbstractInputMethodService {
            mInkWindow.show();

            // deliver previous @param stylusEvents
            stylusEvents.forEach(mInkWindow.getDecorView()::dispatchTouchEvent);
            stylusEvents.forEach(InputMethodService.this::onStylusHandwritingMotionEvent);

            // create receiver for channel
            mHandwritingEventReceiver = new SimpleBatchedInputEventReceiver(
                    channel,
@@ -966,11 +979,11 @@ public class InputMethodService extends AbstractInputMethodService {
                        if (!(event instanceof MotionEvent)) {
                            return false;
                        }
                        return mInkWindow.getDecorView().dispatchTouchEvent((MotionEvent) event);
                        onStylusHandwritingMotionEvent((MotionEvent) event);
                        return true;
                    });
        }


        /**
         * {@inheritDoc}
         * @hide
@@ -2360,7 +2373,8 @@ public class InputMethodService extends AbstractInputMethodService {
     *
     * If the IME supports handwriting for the current input, it should return {@code true},
     * ensure its inking views are attached to the {@link #getStylusHandwritingWindow()}, and handle
     * stylus input received on the ink window via {@link #getCurrentInputConnection()}.
     * stylus input received from {@link #onStylusHandwritingMotionEvent(MotionEvent)} on the
     * {@link #getStylusHandwritingWindow()} via {@link #getCurrentInputConnection()}.
     * @return {@code true} if IME can honor the request, {@code false} if IME cannot at this time.
     */
    public boolean onStartStylusHandwriting() {
@@ -2368,6 +2382,33 @@ public class InputMethodService extends AbstractInputMethodService {
        return false;
    }

    /**
     * Called after {@link #onStartStylusHandwriting()} returns {@code true} for every Stylus
     * {@link MotionEvent}.
     * By default, this method forwards all {@link MotionEvent}s to the
     * {@link #getStylusHandwritingWindow()} once its visible, however IME can override it to
     * receive them sooner.
     * @param motionEvent {@link MotionEvent} from stylus.
     */
    public void onStylusHandwritingMotionEvent(@NonNull MotionEvent motionEvent) {
        if (mInkWindow.isInkViewVisible()) {
            mInkWindow.getDecorView().dispatchTouchEvent(motionEvent);
        } else {
            if (mPendingEvents == null) {
                mPendingEvents = new RingBuffer(MotionEvent.class, MAX_EVENTS_BUFFER);
            }
            mPendingEvents.append(motionEvent);
            mInkWindow.setInkViewVisibilityListener(() -> {
                if (mPendingEvents != null && !mPendingEvents.isEmpty()) {
                    for (MotionEvent event : mPendingEvents.toArray()) {
                        mInkWindow.getDecorView().dispatchTouchEvent(event);
                    }
                    mPendingEvents.clear();
                }
            });
        }
    }

    /**
     * Called when the current stylus handwriting session was finished (either by the system or
     * via {@link #finishStylusHandwriting()}.