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

Commit 8c524314 authored by Justin Ghan's avatar Justin Ghan
Browse files

Start handwriting for stylus tap at end of line

To make it easier to insert a period, we will initiate handwriting when
there is a stylus tap at the end of a line (if the text field is already
focused and not empty with the cursor at the end of that line).

Bug: 323423905
Test: atest StylusHandwritingTest
Change-Id: Ide9300b36ae1b8a98faa2283d1c7539c44375e2d
parent 432a62b8
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -120,6 +120,13 @@ flag {
  bug: "324676775"
}

flag {
  name: "handwriting_end_of_line_tap"
  namespace: "text"
  description: "Initiate handwriting when stylus taps at the end of a line in a focused non-empty TextView with the cursor at the end of that line"
  bug: "323376217"
}

flag {
  name: "handwriting_cursor_position"
  namespace: "text"
+50 −0
Original line number Diff line number Diff line
@@ -13118,6 +13118,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
            return superResult;
        }
        // At this point, the event is not a long press, otherwise it would be handled above.
        if (Flags.handwritingEndOfLineTap() && action == MotionEvent.ACTION_UP
                && shouldStartHandwritingForEndOfLineTap(event)) {
            InputMethodManager imm = getInputMethodManager();
            if (imm != null) {
                imm.startStylusHandwriting(this);
                return true;
            }
        }
        final boolean touchIsFinished = (action == MotionEvent.ACTION_UP)
                && (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();
@@ -13166,6 +13176,46 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        return superResult;
    }
    /**
     * If handwriting is supported, the TextView is already focused and not empty, and the cursor is
     * at the end of a line, a stylus tap after the end of the line will trigger handwriting.
     */
    private boolean shouldStartHandwritingForEndOfLineTap(MotionEvent actionUpEvent) {
        if (!onCheckIsTextEditor()
                || !isEnabled()
                || !isAutoHandwritingEnabled()
                || TextUtils.isEmpty(mText)
                || didTouchFocusSelect()
                || mLayout == null
                || !actionUpEvent.isStylusPointer()) {
            return false;
        }
        int cursorOffset = getSelectionStart();
        if (cursorOffset < 0 || getSelectionEnd() != cursorOffset) {
            return false;
        }
        int cursorLine = mLayout.getLineForOffset(cursorOffset);
        int cursorLineEnd = mLayout.getLineEnd(cursorLine);
        if (cursorLine != mLayout.getLineCount() - 1) {
            cursorLineEnd--;
        }
        if (cursorLineEnd != cursorOffset) {
            return false;
        }
        // Check that the stylus down point is within the same line as the cursor.
        if (getLineAtCoordinate(actionUpEvent.getY()) != cursorLine) {
            return false;
        }
        // Check that the stylus down point is after the end of the line.
        float localX = convertToLocalHorizontalCoordinate(actionUpEvent.getX());
        if (mLayout.getParagraphDirection(cursorLine) == Layout.DIR_RIGHT_TO_LEFT
                ? localX >= mLayout.getLineLeft(cursorLine)
                : localX <= mLayout.getLineRight(cursorLine)) {
            return false;
        }
        return isStylusHandwritingAvailable();
    }
    /**
     * Returns true when need to show UIs, e.g. floating toolbar, etc, for finger based interaction.
     *
+33 −4
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.server.inputmethod;

import static com.android.text.flags.Flags.handwritingEndOfLineTap;

import android.Manifest;
import android.annotation.AnyThread;
import android.annotation.NonNull;
@@ -30,6 +32,7 @@ import android.hardware.input.InputManagerGlobal;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.Slog;
import android.view.BatchedInputEventReceiver;
@@ -66,6 +69,7 @@ final class HandwritingModeController {
    // Use getHandwritingBufferSize() and not this value directly.
    private static final int LONG_EVENT_BUFFER_SIZE = EVENT_BUFFER_SIZE * 20;
    private static final long HANDWRITING_DELEGATION_IDLE_TIMEOUT_MS = 3000;
    private static final long AFTER_STYLUS_UP_ALLOW_PERIOD_MS = 200L;

    private final Context mContext;
    // This must be the looper for the UiThread.
@@ -78,6 +82,7 @@ final class HandwritingModeController {
    private InputEventReceiver mHandwritingEventReceiver;
    private Runnable mInkWindowInitRunnable;
    private boolean mRecordingGesture;
    private boolean mRecordingGestureAfterStylusUp;
    private int mCurrentDisplayId;
    // when set, package names are used for handwriting delegation.
    private @Nullable String mDelegatePackageName;
@@ -155,6 +160,15 @@ final class HandwritingModeController {
    }

    boolean isStylusGestureOngoing() {
        if (mRecordingGestureAfterStylusUp && !mHandwritingBuffer.isEmpty()) {
            // If it is less than AFTER_STYLUS_UP_ALLOW_PERIOD_MS after the stylus up event, return
            // true so that handwriting can start.
            MotionEvent lastEvent = mHandwritingBuffer.get(mHandwritingBuffer.size() - 1);
            if (lastEvent.getActionMasked() == MotionEvent.ACTION_UP) {
                return SystemClock.uptimeMillis() - lastEvent.getEventTime()
                        < AFTER_STYLUS_UP_ALLOW_PERIOD_MS;
            }
        }
        return mRecordingGesture;
    }

@@ -277,7 +291,7 @@ final class HandwritingModeController {
            Slog.e(TAG, "Cannot start handwriting session: Invalid request id: " + requestId);
            return null;
        }
        if (!mRecordingGesture || mHandwritingBuffer.isEmpty()) {
        if (!isStylusGestureOngoing()) {
            Slog.e(TAG, "Cannot start handwriting session: No stylus gesture is being recorded.");
            return null;
        }
@@ -300,6 +314,7 @@ final class HandwritingModeController {
        mHandwritingEventReceiver.dispose();
        mHandwritingEventReceiver = null;
        mRecordingGesture = false;
        mRecordingGestureAfterStylusUp = false;

        if (mHandwritingSurface.isIntercepting()) {
            throw new IllegalStateException(
@@ -362,6 +377,7 @@ final class HandwritingModeController {
            clearPendingHandwritingDelegation();
        }
        mRecordingGesture = false;
        mRecordingGestureAfterStylusUp = false;
    }

    private boolean onInputEvent(InputEvent ev) {
@@ -412,15 +428,20 @@ final class HandwritingModeController {
        if ((TextUtils.isEmpty(mDelegatePackageName) || mDelegationConnectionlessFlow)
                && (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL)) {
            mRecordingGesture = false;
            if (handwritingEndOfLineTap() && action == MotionEvent.ACTION_UP) {
                mRecordingGestureAfterStylusUp = true;
            } else {
                mHandwritingBuffer.clear();
                return;
            }
        }

        if (action == MotionEvent.ACTION_DOWN) {
            clearBufferIfRecordingAfterStylusUp();
            mRecordingGesture = true;
        }

        if (!mRecordingGesture) {
        if (!mRecordingGesture && !mRecordingGestureAfterStylusUp) {
            return;
        }

@@ -430,12 +451,20 @@ final class HandwritingModeController {
                        + " The rest of the gesture will not be recorded.");
            }
            mRecordingGesture = false;
            clearBufferIfRecordingAfterStylusUp();
            return;
        }

        mHandwritingBuffer.add(MotionEvent.obtain(event));
    }

    private void clearBufferIfRecordingAfterStylusUp() {
        if (mRecordingGestureAfterStylusUp) {
            mHandwritingBuffer.clear();
            mRecordingGestureAfterStylusUp = false;
        }
    }

    static final class HandwritingSession {
        private final int mRequestId;
        private final InputChannel mHandwritingChannel;