Loading core/java/android/view/HandwritingInitiator.java +32 −2 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Region; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.TextView; import com.android.internal.annotations.VisibleForTesting; Loading Loading @@ -81,6 +82,8 @@ public class HandwritingInitiator { private int mConnectionCount = 0; private final InputMethodManager mImm; private final int[] mTempLocation = new int[2]; private final Rect mTempRect = new Rect(); private final RectF mTempRectF = new RectF(); Loading Loading @@ -429,7 +432,19 @@ public class HandwritingInitiator { return null; } private static void requestFocusWithoutReveal(View view) { private void requestFocusWithoutReveal(View view) { if (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 // the extended handwriting bounds), then we must calculate and set the cursor // position manually. view.getLocationInWindow(mTempLocation); int offset = editText.getOffsetForPosition( mState.mStylusDownX - mTempLocation[0], mState.mStylusDownY - mTempLocation[1]); editText.setSelection(offset); } if (view.getRevealOnFocusHint()) { view.setRevealOnFocusHint(false); view.requestFocus(); Loading Loading @@ -457,6 +472,10 @@ public class HandwritingInitiator { if (getViewHandwritingArea(connectedView, handwritingArea) && isInHandwritingArea(handwritingArea, x, y, connectedView, isHover) && shouldTriggerStylusHandwritingForView(connectedView)) { if (!isHover && mState != null) { mState.mStylusDownWithinEditorBounds = contains(handwritingArea, x, y, 0f, 0f, 0f, 0f); } return connectedView; } } Loading @@ -475,7 +494,12 @@ public class HandwritingInitiator { } final float distance = distance(handwritingArea, x, y); if (distance == 0f) return view; if (distance == 0f) { if (!isHover && mState != null) { mState.mStylusDownWithinEditorBounds = true; } return view; } if (distance < minDistance) { minDistance = distance; bestCandidate = view; Loading Loading @@ -657,6 +681,12 @@ public class HandwritingInitiator { */ private boolean mExceedHandwritingSlop; /** * Whether the stylus down point of the MotionEvent sequence was within the editor's bounds * (not including the extended handwriting bounds). */ private boolean mStylusDownWithinEditorBounds; /** * A view which has requested focus and is pending input connection creation. When an input * connection is created for the view, a handwriting session should be started for the view. Loading core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java +19 −2 Original line number Diff line number Diff line Loading @@ -88,8 +88,8 @@ public class HandwritingInitiatorTest { } private HandwritingInitiator mHandwritingInitiator; private View mTestView1; private View mTestView2; private EditText mTestView1; private EditText mTestView2; private Context mContext; @Before Loading Loading @@ -123,6 +123,9 @@ public class HandwritingInitiatorTest { @Test public void onTouchEvent_startHandwriting_when_stylusMoveOnce_withinHWArea() { mTestView1.setText("hello"); when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4); mHandwritingInitiator.onInputConnectionCreated(mTestView1); final int x1 = (sHwArea1.left + sHwArea1.right) / 2; final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2; Loading @@ -141,6 +144,9 @@ 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. verify(mTestView1, never()).setSelection(anyInt()); } @Test Loading Loading @@ -185,6 +191,9 @@ public class HandwritingInitiatorTest { @Test public void onTouchEvent_startHandwriting_when_stylusMove_withinExtendedHWArea() { mTestView1.setText("hello"); when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4); mHandwritingInitiator.onInputConnectionCreated(mTestView1); final int x1 = sHwArea1.left - HW_BOUNDS_OFFSETS_LEFT_PX / 2; final int y1 = sHwArea1.top - HW_BOUNDS_OFFSETS_TOP_PX / 2; Loading @@ -199,6 +208,9 @@ 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. verify(mTestView1).setSelection(4); } @Test Loading @@ -221,6 +233,8 @@ public class HandwritingInitiatorTest { @Test public void onTouchEvent_startHandwriting_inputConnectionBuilt_stylusMoveInExtendedHWArea() { mTestView1.setText("hello"); when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4); // 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; Loading @@ -241,6 +255,9 @@ public class HandwritingInitiatorTest { // the stylus down point is closest to this view. mHandwritingInitiator.onInputConnectionCreated(mTestView1); verify(mHandwritingInitiator).startHandwriting(mTestView1); // Since the stylus down point was outside the TextView's bounds, the handwriting initiator // sets the cursor position. verify(mTestView1).setSelection(4); } @Test Loading core/tests/coretests/src/android/view/stylus/HandwritingTestUtil.java +15 −4 Original line number Diff line number Diff line Loading @@ -16,6 +16,9 @@ package android.view.stylus; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; Loading @@ -26,22 +29,23 @@ import android.graphics.Rect; import android.graphics.Region; import android.view.View; import android.view.ViewGroup; import android.widget.EditText; import androidx.test.platform.app.InstrumentationRegistry; public class HandwritingTestUtil { public static View createView(Rect handwritingArea) { public static EditText createView(Rect handwritingArea) { return createView(handwritingArea, true /* autoHandwritingEnabled */, true /* isStylusHandwritingAvailable */); } public static View createView(Rect handwritingArea, boolean autoHandwritingEnabled, public static EditText createView(Rect handwritingArea, boolean autoHandwritingEnabled, boolean isStylusHandwritingAvailable) { return createView(handwritingArea, autoHandwritingEnabled, isStylusHandwritingAvailable, 0, 0, 0, 0); } public static View createView(Rect handwritingArea, boolean autoHandwritingEnabled, public static EditText createView(Rect handwritingArea, boolean autoHandwritingEnabled, boolean isStylusHandwritingAvailable, float handwritingBoundsOffsetLeft, float handwritingBoundsOffsetTop, float handwritingBoundsOffsetRight, float handwritingBoundsOffsetBottom) { Loading @@ -68,7 +72,7 @@ public class HandwritingTestUtil { } }; View view = spy(new View(context)); EditText view = spy(new EditText(context)); when(view.isAttachedToWindow()).thenReturn(true); when(view.isAggregatedVisible()).thenReturn(true); when(view.isStylusHandwritingAvailable()).thenReturn(isStylusHandwritingAvailable); Loading @@ -77,6 +81,13 @@ public class HandwritingTestUtil { when(view.getHandwritingBoundsOffsetTop()).thenReturn(handwritingBoundsOffsetTop); when(view.getHandwritingBoundsOffsetRight()).thenReturn(handwritingBoundsOffsetRight); when(view.getHandwritingBoundsOffsetBottom()).thenReturn(handwritingBoundsOffsetBottom); doAnswer(invocation -> { int[] outLocation = invocation.getArgument(0); outLocation[0] = handwritingArea.left; outLocation[1] = handwritingArea.top; return null; }).when(view).getLocationInWindow(any()); when(view.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(0); view.setAutoHandwritingEnabled(autoHandwritingEnabled); parent.addView(view); return view; Loading Loading
core/java/android/view/HandwritingInitiator.java +32 −2 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Region; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.TextView; import com.android.internal.annotations.VisibleForTesting; Loading Loading @@ -81,6 +82,8 @@ public class HandwritingInitiator { private int mConnectionCount = 0; private final InputMethodManager mImm; private final int[] mTempLocation = new int[2]; private final Rect mTempRect = new Rect(); private final RectF mTempRectF = new RectF(); Loading Loading @@ -429,7 +432,19 @@ public class HandwritingInitiator { return null; } private static void requestFocusWithoutReveal(View view) { private void requestFocusWithoutReveal(View view) { if (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 // the extended handwriting bounds), then we must calculate and set the cursor // position manually. view.getLocationInWindow(mTempLocation); int offset = editText.getOffsetForPosition( mState.mStylusDownX - mTempLocation[0], mState.mStylusDownY - mTempLocation[1]); editText.setSelection(offset); } if (view.getRevealOnFocusHint()) { view.setRevealOnFocusHint(false); view.requestFocus(); Loading Loading @@ -457,6 +472,10 @@ public class HandwritingInitiator { if (getViewHandwritingArea(connectedView, handwritingArea) && isInHandwritingArea(handwritingArea, x, y, connectedView, isHover) && shouldTriggerStylusHandwritingForView(connectedView)) { if (!isHover && mState != null) { mState.mStylusDownWithinEditorBounds = contains(handwritingArea, x, y, 0f, 0f, 0f, 0f); } return connectedView; } } Loading @@ -475,7 +494,12 @@ public class HandwritingInitiator { } final float distance = distance(handwritingArea, x, y); if (distance == 0f) return view; if (distance == 0f) { if (!isHover && mState != null) { mState.mStylusDownWithinEditorBounds = true; } return view; } if (distance < minDistance) { minDistance = distance; bestCandidate = view; Loading Loading @@ -657,6 +681,12 @@ public class HandwritingInitiator { */ private boolean mExceedHandwritingSlop; /** * Whether the stylus down point of the MotionEvent sequence was within the editor's bounds * (not including the extended handwriting bounds). */ private boolean mStylusDownWithinEditorBounds; /** * A view which has requested focus and is pending input connection creation. When an input * connection is created for the view, a handwriting session should be started for the view. Loading
core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java +19 −2 Original line number Diff line number Diff line Loading @@ -88,8 +88,8 @@ public class HandwritingInitiatorTest { } private HandwritingInitiator mHandwritingInitiator; private View mTestView1; private View mTestView2; private EditText mTestView1; private EditText mTestView2; private Context mContext; @Before Loading Loading @@ -123,6 +123,9 @@ public class HandwritingInitiatorTest { @Test public void onTouchEvent_startHandwriting_when_stylusMoveOnce_withinHWArea() { mTestView1.setText("hello"); when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4); mHandwritingInitiator.onInputConnectionCreated(mTestView1); final int x1 = (sHwArea1.left + sHwArea1.right) / 2; final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2; Loading @@ -141,6 +144,9 @@ 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. verify(mTestView1, never()).setSelection(anyInt()); } @Test Loading Loading @@ -185,6 +191,9 @@ public class HandwritingInitiatorTest { @Test public void onTouchEvent_startHandwriting_when_stylusMove_withinExtendedHWArea() { mTestView1.setText("hello"); when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4); mHandwritingInitiator.onInputConnectionCreated(mTestView1); final int x1 = sHwArea1.left - HW_BOUNDS_OFFSETS_LEFT_PX / 2; final int y1 = sHwArea1.top - HW_BOUNDS_OFFSETS_TOP_PX / 2; Loading @@ -199,6 +208,9 @@ 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. verify(mTestView1).setSelection(4); } @Test Loading @@ -221,6 +233,8 @@ public class HandwritingInitiatorTest { @Test public void onTouchEvent_startHandwriting_inputConnectionBuilt_stylusMoveInExtendedHWArea() { mTestView1.setText("hello"); when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4); // 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; Loading @@ -241,6 +255,9 @@ public class HandwritingInitiatorTest { // the stylus down point is closest to this view. mHandwritingInitiator.onInputConnectionCreated(mTestView1); verify(mHandwritingInitiator).startHandwriting(mTestView1); // Since the stylus down point was outside the TextView's bounds, the handwriting initiator // sets the cursor position. verify(mTestView1).setSelection(4); } @Test Loading
core/tests/coretests/src/android/view/stylus/HandwritingTestUtil.java +15 −4 Original line number Diff line number Diff line Loading @@ -16,6 +16,9 @@ package android.view.stylus; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; Loading @@ -26,22 +29,23 @@ import android.graphics.Rect; import android.graphics.Region; import android.view.View; import android.view.ViewGroup; import android.widget.EditText; import androidx.test.platform.app.InstrumentationRegistry; public class HandwritingTestUtil { public static View createView(Rect handwritingArea) { public static EditText createView(Rect handwritingArea) { return createView(handwritingArea, true /* autoHandwritingEnabled */, true /* isStylusHandwritingAvailable */); } public static View createView(Rect handwritingArea, boolean autoHandwritingEnabled, public static EditText createView(Rect handwritingArea, boolean autoHandwritingEnabled, boolean isStylusHandwritingAvailable) { return createView(handwritingArea, autoHandwritingEnabled, isStylusHandwritingAvailable, 0, 0, 0, 0); } public static View createView(Rect handwritingArea, boolean autoHandwritingEnabled, public static EditText createView(Rect handwritingArea, boolean autoHandwritingEnabled, boolean isStylusHandwritingAvailable, float handwritingBoundsOffsetLeft, float handwritingBoundsOffsetTop, float handwritingBoundsOffsetRight, float handwritingBoundsOffsetBottom) { Loading @@ -68,7 +72,7 @@ public class HandwritingTestUtil { } }; View view = spy(new View(context)); EditText view = spy(new EditText(context)); when(view.isAttachedToWindow()).thenReturn(true); when(view.isAggregatedVisible()).thenReturn(true); when(view.isStylusHandwritingAvailable()).thenReturn(isStylusHandwritingAvailable); Loading @@ -77,6 +81,13 @@ public class HandwritingTestUtil { when(view.getHandwritingBoundsOffsetTop()).thenReturn(handwritingBoundsOffsetTop); when(view.getHandwritingBoundsOffsetRight()).thenReturn(handwritingBoundsOffsetRight); when(view.getHandwritingBoundsOffsetBottom()).thenReturn(handwritingBoundsOffsetBottom); doAnswer(invocation -> { int[] outLocation = invocation.getArgument(0); outLocation[0] = handwritingArea.left; outLocation[1] = handwritingArea.top; return null; }).when(view).getLocationInWindow(any()); when(view.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(0); view.setAutoHandwritingEnabled(autoHandwritingEnabled); parent.addView(view); return view; Loading