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

Commit b5cefed9 authored by Justin Ghan's avatar Justin Ghan Committed by Android (Google) Code Review
Browse files

Merge "Start handwriting for stylus tap at end of line" into main

parents f1cbe59a 8c524314
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;