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

Commit 26d404f7 authored by Taran Singh's avatar Taran Singh
Browse files

Scribe initiation without InputConnection

Allow Stylus handwriting to initiate handwriting without waiting for
InputConnection to be established.
When an editor view receives focus, it sends startInput binder call immidiately.
Focused editor (servedView) is then ready for any
startStylusHandwriting() calls.

This should help shave few millis from initiation latency.

Note: this intentionally doesn't optimize delegation since that is
migrating to newer APIs.

Bug: 308827131
Bug: 324670412

Test: atest HandwritingInitiatorTest StylusHandwritingTest

Change-Id: I0174e15fbbedf88dcd68ebe98333b6b07871985a
parent d2d7a20f
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -21,8 +21,11 @@ import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_UP;
import static android.view.MotionEvent.TOOL_TYPE_FINGER;
import static android.view.MotionEvent.TOOL_TYPE_STYLUS;
import static android.view.inputmethod.Flags.initiationWithoutInputConnection;


import static org.junit.Assume.assumeFalse;

import android.app.Instrumentation;
import android.content.Context;
import android.perftests.utils.BenchmarkState;
@@ -186,6 +189,7 @@ public class HandwritingInitiatorPerfTest {

    @Test
    public void onInputConnectionCreated() {
        assumeFalse(initiationWithoutInputConnection());
        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
        final View view = new View(mContext);
        final EditorInfo editorInfo = new EditorInfo();
@@ -199,6 +203,7 @@ public class HandwritingInitiatorPerfTest {

    @Test
    public void onInputConnectionClosed() {
        assumeFalse(initiationWithoutInputConnection());
        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
        final View view = new View(mContext);
        while (state.keepRunning()) {
+112 −21
Original line number Diff line number Diff line
@@ -23,8 +23,10 @@ import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.view.inputmethod.Flags;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.Editor;
import android.widget.TextView;

import com.android.internal.annotations.VisibleForTesting;
@@ -80,6 +82,16 @@ public class HandwritingInitiator {
     * connections and only set mConnectedView to null when mConnectionCount is zero.
     */
    private int mConnectionCount = 0;

    /**
     * The reference to the View that currently has focus.
     * This replaces mConnecteView when {@code Flags#intitiationWithoutInputConnection()} is
     * enabled.
     */
    @Nullable
    @VisibleForTesting
    public WeakReference<View> mFocusedView = null;

    private final InputMethodManager mImm;

    private final int[] mTempLocation = new int[2];
@@ -112,9 +124,15 @@ public class HandwritingInitiator {
     *
     * If the stylus is hovering on an unconnected editor that supports handwriting, we always show
     * the hover icon.
     * TODO(b/308827131): Rename to FocusedView after Flag is flipped.
     */
    private boolean mShowHoverIconForConnectedView = true;

    /** When flag is enabled, touched editors don't wait for InputConnection for initiation.
     * However, delegation still waits for InputConnection.
     */
    private final boolean mInitiateWithoutConnection = Flags.initiationWithoutInputConnection();

    @VisibleForTesting
    public HandwritingInitiator(@NonNull ViewConfiguration viewConfiguration,
            @NonNull InputMethodManager inputMethodManager) {
@@ -201,8 +219,8 @@ public class HandwritingInitiator {
                    View candidateView = findBestCandidateView(mState.mStylusDownX,
                            mState.mStylusDownY, /* isHover */ false);
                    if (candidateView != null) {
                        if (candidateView == getConnectedView()) {
                            if (!candidateView.hasFocus()) {
                        if (candidateView == getConnectedOrFocusedView()) {
                            if (!mInitiateWithoutConnection && !candidateView.hasFocus()) {
                                requestFocusWithoutReveal(candidateView);
                            }
                            startHandwriting(candidateView);
@@ -217,9 +235,18 @@ public class HandwritingInitiator {
                            candidateView.getHandwritingDelegatorCallback().run();
                            mState.mHasPreparedHandwritingDelegation = true;
                        } else {
                            if (!mInitiateWithoutConnection) {
                                mState.mPendingConnectedView = new WeakReference<>(candidateView);
                            }
                            if (!candidateView.hasFocus()) {
                                requestFocusWithoutReveal(candidateView);
                            }
                            if (mInitiateWithoutConnection
                                    && updateFocusedView(candidateView,
                                            /* fromTouchEvent */ true)) {
                                startHandwriting(candidateView);
                            }
                        }
                    }
                }
                return mState.mHasInitiatedHandwriting || mState.mHasPreparedHandwritingDelegation;
@@ -244,11 +271,7 @@ public class HandwritingInitiator {
     */
    public void onDelegateViewFocused(@NonNull View view) {
        if (view == getConnectedView()) {
            if (tryAcceptStylusHandwritingDelegation(view)) {
                // A handwriting delegate view is accepted and handwriting starts; hide the
                // hover icon.
                mShowHoverIconForConnectedView = false;
            }
            tryAcceptStylusHandwritingDelegation(view);
        }
    }

@@ -260,6 +283,10 @@ public class HandwritingInitiator {
     * @see  #onInputConnectionClosed(View)
     */
    public void onInputConnectionCreated(@NonNull View view) {
        if (mInitiateWithoutConnection && !view.isHandwritingDelegate()) {
            // When flag is enabled, only delegation continues to wait for InputConnection.
            return;
        }
        if (!view.isAutoHandwritingEnabled()) {
            clearConnectedView();
            return;
@@ -274,12 +301,15 @@ public class HandwritingInitiator {
            // A new view just gain focus. By default, we should show hover icon for it.
            mShowHoverIconForConnectedView = true;
            if (view.isHandwritingDelegate() && tryAcceptStylusHandwritingDelegation(view)) {
                // A handwriting delegate view is accepted and handwriting starts; hide the
                // hover icon.
                // tryAcceptStylusHandwritingDelegation should set boolean below, however, we
                // cannot mock IMM to return true for acceptStylusDelegation().
                // TODO(b/324670412): we should move any dependent tests to integration and remove
                //  the assignment below.
                mShowHoverIconForConnectedView = false;
                return;
            }
            if (mState != null && mState.mPendingConnectedView != null
            if (!mInitiateWithoutConnection && mState != null
                    && mState.mPendingConnectedView != null
                    && mState.mPendingConnectedView.get() == view) {
                startHandwriting(view);
            }
@@ -293,6 +323,9 @@ public class HandwritingInitiator {
     * @param view the view that closed the InputConnection.
     */
    public void onInputConnectionClosed(@NonNull View view) {
        if (mInitiateWithoutConnection && !view.isHandwritingDelegate()) {
            return;
        }
        final View connectedView = getConnectedView();
        if (connectedView == null) return;
        if (connectedView == view) {
@@ -306,6 +339,48 @@ public class HandwritingInitiator {
        }
    }

    @Nullable
    private View getFocusedView() {
        if (mFocusedView == null) return null;
        return mFocusedView.get();
    }

    /**
     * Clear the tracked focused view tracked for handwriting initiation.
     * @param view the focused view.
     */
    public void clearFocusedView(View view) {
        if (view == null || mFocusedView == null) {
            return;
        }
        if (mFocusedView.get() == view) {
            mFocusedView = null;
        }
    }

    /**
     * Called when new {@link Editor} is focused.
     * @return {@code true} if handwriting can initiate for given view.
     */
    @VisibleForTesting
    public boolean updateFocusedView(@NonNull View view, boolean fromTouchEvent) {
        if (!view.shouldInitiateHandwriting()) {
            mFocusedView = null;
            return false;
        }

        final View focusedView = getFocusedView();
        if (focusedView != view) {
            mFocusedView = new WeakReference<>(view);
            // A new view just gain focus. By default, we should show hover icon for it.
            mShowHoverIconForConnectedView = true;
        }
        if (!fromTouchEvent) {
            tryAcceptStylusHandwritingDelegation(view);
        }
        return true;
    }

    /** Starts a stylus handwriting session for the view. */
    @VisibleForTesting
    public void startHandwriting(@NonNull View view) {
@@ -324,6 +399,9 @@ public class HandwritingInitiator {
     */
    @VisibleForTesting
    public boolean tryAcceptStylusHandwritingDelegation(@NonNull View view) {
        if (!view.isHandwritingDelegate() || (mState != null && mState.mHasInitiatedHandwriting)) {
            return false;
        }
        String delegatorPackageName =
                view.getAllowedHandwritingDelegatorPackageName();
        if (delegatorPackageName == null) {
@@ -337,6 +415,9 @@ public class HandwritingInitiator {
            if (view instanceof TextView) {
                ((TextView) view).hideHint();
            }
            // A handwriting delegate view is accepted and handwriting starts; hide the
            // hover icon.
            mShowHoverIconForConnectedView = false;
            return true;
        }
        return false;
@@ -377,16 +458,25 @@ public class HandwritingInitiator {
            return PointerIcon.getSystemIcon(context, PointerIcon.TYPE_HANDWRITING);
        }

        if (hoverView != getConnectedView()) {
        if (hoverView != getConnectedOrFocusedView()) {
            // The stylus is hovering on another view that supports handwriting. We should show
            // hover icon. Also reset the mShowHoverIconForConnectedView so that hover
            // icon is displayed again next time when the stylus hovers on connected view.
            // hover icon. Also reset the mShowHoverIconForFocusedView so that hover
            // icon is displayed again next time when the stylus hovers on focused view.
            mShowHoverIconForConnectedView = true;
            return PointerIcon.getSystemIcon(context, PointerIcon.TYPE_HANDWRITING);
        }
        return null;
    }

    // TODO(b/308827131): Remove once Flag is flipped.
    private View getConnectedOrFocusedView() {
        if (mInitiateWithoutConnection) {
            return mFocusedView == null ? null : mFocusedView.get();
        } else {
            return mConnectedView == null ? null : mConnectedView.get();
        }
    }

    private View getCachedHoverTarget() {
        if (mCachedHoverTarget == null) {
            return null;
@@ -458,20 +548,21 @@ public class HandwritingInitiator {
     */
    @Nullable
    private View findBestCandidateView(float x, float y, boolean isHover) {
        // TODO(b/308827131): Rename to FocusedView after Flag is flipped.
        // If the connectedView is not null and do not set any handwriting area, it will check
        // whether the connectedView's boundary contains the initial stylus position. If true,
        // directly return the connectedView.
        final View connectedView = getConnectedView();
        if (connectedView != null) {
        final View connectedOrFocusedView = getConnectedOrFocusedView();
        if (connectedOrFocusedView != null) {
            Rect handwritingArea = mTempRect;
            if (getViewHandwritingArea(connectedView, handwritingArea)
                    && isInHandwritingArea(handwritingArea, x, y, connectedView, isHover)
                    && shouldTriggerStylusHandwritingForView(connectedView)) {
            if (getViewHandwritingArea(connectedOrFocusedView, handwritingArea)
                    && isInHandwritingArea(handwritingArea, x, y, connectedOrFocusedView, isHover)
                    && shouldTriggerStylusHandwritingForView(connectedOrFocusedView)) {
                if (!isHover && mState != null) {
                    mState.mStylusDownWithinEditorBounds =
                            contains(handwritingArea, x, y, 0f, 0f, 0f, 0f);
                }
                return connectedView;
                return connectedOrFocusedView;
            }
        }

+14 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package android.view.inputmethod;

import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.inputmethod.Flags.FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR;
import static android.view.inputmethod.Flags.initiationWithoutInputConnection;
import static android.view.inputmethod.InputConnection.CURSOR_UPDATE_IMMEDIATE;
import static android.view.inputmethod.InputConnection.CURSOR_UPDATE_MONITOR;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.DISPLAY_ID;
@@ -1950,6 +1951,10 @@ public final class InputMethodManager {
        if (mServedView != null) {
            clearedView = mServedView;
            mServedView = null;
            if (initiationWithoutInputConnection() && clearedView.getViewRootImpl() != null) {
                clearedView.getViewRootImpl().getHandwritingInitiator()
                        .clearFocusedView(clearedView);
            }
        }
        if (clearedView != null) {
            if (DEBUG) {
@@ -2932,6 +2937,10 @@ public final class InputMethodManager {
            switch (res.result) {
                case InputBindResult.ResultCode.ERROR_NOT_IME_TARGET_WINDOW:
                    mRestartOnNextWindowFocus = true;
                    if (initiationWithoutInputConnection()) {
                        mServedView.getViewRootImpl().getHandwritingInitiator().clearFocusedView(
                                mServedView);
                    }
                    mServedView = null;
                    break;
            }
@@ -3094,6 +3103,11 @@ public final class InputMethodManager {
            return false;
        }
        mServedView = mNextServedView;
        if (initiationWithoutInputConnection() && mServedView.onCheckIsTextEditor()
                && mServedView.isHandwritingDelegate()) {
            mServedView.getViewRootImpl().getHandwritingInitiator().onDelegateViewFocused(
                    mServedView);
        }
        if (mServedInputConnection != null) {
            mServedInputConnection.finishComposingTextFromImm();
        }
+8 −0
Original line number Diff line number Diff line
@@ -62,3 +62,11 @@ flag {
    bug: "311791923"
    is_fixed_read_only: true
}

flag {
    name: "initiation_without_input_connection"
    namespace: "input_method"
    description: "Feature flag for initiating handwriting without InputConnection"
    bug: "308827131"
    is_fixed_read_only: true
}
+91 −29

File changed.

Preview size limit exceeded, changes collapsed.