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

Commit 05c55d75 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Update cursor placement for handwriting initiation" into main

parents c0b1d8b3 5db833a2
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -119,3 +119,10 @@ flag {
  is_fixed_read_only: true
  bug: "324676775"
}

flag {
  name: "handwriting_cursor_position"
  namespace: "text"
  description: "When handwriting is initiated in an unfocused TextView, cursor is placed at the end of the closest paragraph."
  bug: "323376217"
}
+15 −1
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package android.view;

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

import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -557,7 +559,8 @@ public class HandwritingInitiator {
    }

    private void requestFocusWithoutReveal(View view) {
        if (view instanceof EditText editText && !mState.mStylusDownWithinEditorBounds) {
        if (!handwritingCursorPosition() && view instanceof EditText editText
                && !mState.mStylusDownWithinEditorBounds) {
            // If the stylus down point was inside the EditText's bounds, then the EditText will
            // automatically set its cursor position nearest to the stylus down point when it
            // gains focus. If the stylus down point was outside the EditText's bounds (within
@@ -576,6 +579,17 @@ public class HandwritingInitiator {
        } else {
            view.requestFocus();
        }
        if (handwritingCursorPosition() && view instanceof EditText editText) {
            // Move the cursor to the end of the paragraph closest to the stylus down point.
            view.getLocationInWindow(mTempLocation);
            int line = editText.getLineAtCoordinate(mState.mStylusDownY - mTempLocation[1]);
            int paragraphEnd = TextUtils.indexOf(editText.getText(), '\n',
                    editText.getLayout().getLineStart(line));
            if (paragraphEnd < 0) {
                paragraphEnd = editText.getText().length();
            }
            editText.setSelection(paragraphEnd);
        }
    }

    /**
+2 −1
Original line number Diff line number Diff line
@@ -15490,8 +15490,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        return x;
    }
    /** @hide */
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    int getLineAtCoordinate(float y) {
    public int getLineAtCoordinate(float y) {
        y -= getTotalPaddingTop();
        // Clamp the position to inside of the view.
        y = Math.max(0.0f, y);
+67 −9
Original line number Diff line number Diff line
@@ -23,6 +23,8 @@ import static android.view.MotionEvent.ACTION_UP;
import static android.view.inputmethod.Flags.initiationWithoutInputConnection;
import static android.view.stylus.HandwritingTestUtil.createView;

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

import static com.google.common.truth.Truth.assertThat;

import static org.junit.Assume.assumeFalse;
@@ -129,6 +131,44 @@ public class HandwritingInitiatorTest {
    public void onTouchEvent_startHandwriting_when_stylusMoveOnce_withinHWArea() {
        mTestView1.setText("hello");
        when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4);
        when(mTestView1.getLineAtCoordinate(anyFloat())).thenReturn(0);

        mHandwritingInitiator.onInputConnectionCreated(mTestView1);
        final int x1 = (sHwArea1.left + sHwArea1.right) / 2;
        final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2;
        MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
        boolean onTouchEventResult1 = mHandwritingInitiator.onTouchEvent(stylusEvent1);

        final int x2 = x1 + mHandwritingSlop * 2;
        final int y2 = y1;

        MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0);
        boolean onTouchEventResult2 = mHandwritingInitiator.onTouchEvent(stylusEvent2);

        // Stylus movement within HandwritingArea should trigger IMM.startHandwriting once.
        verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView1);
        assertThat(onTouchEventResult1).isFalse();
        // After IMM.startHandwriting is triggered, onTouchEvent should return true for ACTION_MOVE
        // events so that the events are not dispatched to the view tree.
        assertThat(onTouchEventResult2).isTrue();
        if (handwritingCursorPosition()) {
            // Cursor is placed at the end of the text.
            verify(mTestView1).setSelection(5);
        } else {
            // Since the stylus down point was inside the TextView's bounds, the handwriting
            // initiator does not need to set the cursor position.
            verify(mTestView1, never()).setSelection(anyInt());
        }
    }

    @Test
    public void onTouchEvent_startHandwriting_multipleParagraphs() {
        // End of line 0 is offset 10, end of line 1 is offset 20, end of line 2 is offset 30, end
        // of line 3 is offset 40.
        mTestView1.setText("line 0    \nline 1   \nline 2   \nline 3   ");
        mTestView1.layout(0, 0, 500, 500);
        when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4);
        when(mTestView1.getLineAtCoordinate(anyFloat())).thenReturn(2);

        mHandwritingInitiator.onInputConnectionCreated(mTestView1);
        final int x1 = (sHwArea1.left + sHwArea1.right) / 2;
@@ -148,10 +188,15 @@ public class HandwritingInitiatorTest {
        // After IMM.startHandwriting is triggered, onTouchEvent should return true for ACTION_MOVE
        // events so that the events are not dispatched to the view tree.
        assertThat(onTouchEventResult2).isTrue();
        // Since the stylus down point was inside the TextView's bounds, the handwriting initiator
        // does not need to set the cursor position.
        if (handwritingCursorPosition()) {
            // Cursor is placed at the end of the paragraph containing line 2.
            verify(mTestView1).setSelection(30);
        } else {
            // Since the stylus down point was inside the TextView's bounds, the handwriting
            // initiator does not need to set the cursor position.
            verify(mTestView1, never()).setSelection(anyInt());
        }
    }

    @Test
    public void onTouchEvent_startHandwritingOnce_when_stylusMoveMultiTimes_withinHWArea() {
@@ -197,6 +242,7 @@ public class HandwritingInitiatorTest {
    public void onTouchEvent_startHandwriting_when_stylusMove_withinExtendedHWArea() {
        mTestView1.setText("hello");
        when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4);
        when(mTestView1.getLineAtCoordinate(anyFloat())).thenReturn(0);

        if (!mInitiateWithoutConnection) {
            mHandwritingInitiator.onInputConnectionCreated(mTestView1);
@@ -214,10 +260,15 @@ public class HandwritingInitiatorTest {

        // Stylus movement within extended HandwritingArea should trigger IMM.startHandwriting once.
        verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView1);
        // Since the stylus down point was outside the TextView's bounds, the handwriting initiator
        // sets the cursor position.
        if (handwritingCursorPosition()) {
            // Cursor is placed at the end of the text.
            verify(mTestView1).setSelection(5);
        } else {
            // Since the stylus down point was outside the TextView's bounds, the handwriting
            // initiator sets the cursor position.
            verify(mTestView1).setSelection(4);
        }
    }

    @Test
    public void onTouchEvent_startHandwriting_servedViewUpdateAfterStylusMove() {
@@ -246,6 +297,8 @@ public class HandwritingInitiatorTest {
    public void onTouchEvent_startHandwriting_servedViewUpdate_stylusMoveInExtendedHWArea() {
        mTestView1.setText("hello");
        when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4);
        when(mTestView1.getLineAtCoordinate(anyFloat())).thenReturn(0);

        // The stylus down point is between mTestView1 and  mTestView2, but it is within the
        // extended handwriting area of both views. It is closer to mTestView1.
        final int x1 = sHwArea1.right + HW_BOUNDS_OFFSETS_RIGHT_PX / 2;
@@ -278,10 +331,15 @@ public class HandwritingInitiatorTest {
        // Handwriting is started for this view since  the stylus down point is closest to this
        // view.
        verify(mHandwritingInitiator).startHandwriting(mTestView1);
        // Since the stylus down point was outside the TextView's bounds, the handwriting initiator
        // sets the cursor position.
        if (handwritingCursorPosition()) {
            // Cursor is placed at the end of the text.
            verify(mTestView1).setSelection(5);
        } else {
            // Since the stylus down point was outside the TextView's bounds, the handwriting
            //  initiator sets the cursor position.
            verify(mTestView1).setSelection(4);
        }
    }


    @Test