From 8edf79d479abef6f7c010f5cabcf603942f2c13a Mon Sep 17 00:00:00 2001 From: Haoyu Zhang Date: Mon, 27 Feb 2023 13:34:15 -0800 Subject: [PATCH] [Scribe] HandwritingInitiator prioritize currently focused editor Bug: 266984582 Test: atest HandwritingInitiatorTest Change-Id: Id52346e46187ef3bff712336c889e92efaad9968 --- .../android/view/HandwritingInitiator.java | 11 +- .../view/stylus/HandwritingInitiatorTest.java | 171 ++++++++++++------ 2 files changed, 118 insertions(+), 64 deletions(-) diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java index 98f61a3663810..740d5d562d8f5 100644 --- a/core/java/android/view/HandwritingInitiator.java +++ b/core/java/android/view/HandwritingInitiator.java @@ -381,9 +381,6 @@ public class HandwritingInitiator { */ @Nullable private View findBestCandidateView(float x, float y) { - float minDistance = Float.MAX_VALUE; - View bestCandidate = null; - // 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. @@ -392,14 +389,12 @@ public class HandwritingInitiator { Rect handwritingArea = getViewHandwritingArea(connectedView); if (isInHandwritingArea(handwritingArea, x, y, connectedView) && shouldTriggerStylusHandwritingForView(connectedView)) { - final float distance = distance(handwritingArea, x, y); - if (distance == 0f) return connectedView; - - bestCandidate = connectedView; - minDistance = distance; + return connectedView; } } + float minDistance = Float.MAX_VALUE; + View bestCandidate = null; // Check the registered handwriting areas. final List handwritableViewInfos = mHandwritingAreasTracker.computeViewInfos(); diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java index 76f5277cfe0b3..a3dec84e78a82 100644 --- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java +++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java @@ -64,10 +64,19 @@ public class HandwritingInitiatorTest { private static final int HW_BOUNDS_OFFSETS_BOTTOM_PX = 40; private int mHandwritingSlop = 4; - private static final Rect sHwArea = new Rect(100, 200, 500, 500); + private static final Rect sHwArea1; + private static final Rect sHwArea2; + + static { + sHwArea1 = new Rect(100, 200, 500, 500); + // The extended handwriting area bounds of the two views are overlapping. + int hwArea2Top = sHwArea1.bottom + HW_BOUNDS_OFFSETS_TOP_PX / 2; + sHwArea2 = new Rect(sHwArea1.left, hwArea2Top, sHwArea1.right, hwArea2Top + 300); + } private HandwritingInitiator mHandwritingInitiator; - private View mTestView; + private View mTestView1; + private View mTestView2; private Context mContext; @Before @@ -82,20 +91,27 @@ public class HandwritingInitiatorTest { mHandwritingInitiator = spy(new HandwritingInitiator(viewConfiguration, inputMethodManager)); - mTestView = createView(sHwArea, true /* autoHandwritingEnabled */, - true /* isStylusHandwritingAvailable */, + mTestView1 = createView(sHwArea1, /* autoHandwritingEnabled= */ true, + /* isStylusHandwritingAvailable= */ true, HW_BOUNDS_OFFSETS_LEFT_PX, HW_BOUNDS_OFFSETS_TOP_PX, HW_BOUNDS_OFFSETS_RIGHT_PX, HW_BOUNDS_OFFSETS_BOTTOM_PX); - mHandwritingInitiator.updateHandwritingAreasForView(mTestView); + mTestView2 = createView(sHwArea2, /* autoHandwritingEnabled= */ true, + /* isStylusHandwritingAvailable= */ true, + HW_BOUNDS_OFFSETS_LEFT_PX, + HW_BOUNDS_OFFSETS_TOP_PX, + HW_BOUNDS_OFFSETS_RIGHT_PX, + HW_BOUNDS_OFFSETS_BOTTOM_PX); + mHandwritingInitiator.updateHandwritingAreasForView(mTestView1); + mHandwritingInitiator.updateHandwritingAreasForView(mTestView2); } @Test public void onTouchEvent_startHandwriting_when_stylusMoveOnce_withinHWArea() { - mHandwritingInitiator.onInputConnectionCreated(mTestView); - final int x1 = (sHwArea.left + sHwArea.right) / 2; - final int y1 = (sHwArea.top + sHwArea.bottom) / 2; + 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); @@ -106,7 +122,7 @@ public class HandwritingInitiatorTest { boolean onTouchEventResult2 = mHandwritingInitiator.onTouchEvent(stylusEvent2); // Stylus movement within HandwritingArea should trigger IMM.startHandwriting once. - verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView); + 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. @@ -115,9 +131,9 @@ public class HandwritingInitiatorTest { @Test public void onTouchEvent_startHandwritingOnce_when_stylusMoveMultiTimes_withinHWArea() { - mHandwritingInitiator.onInputConnectionCreated(mTestView); - final int x1 = (sHwArea.left + sHwArea.right) / 2; - final int y1 = (sHwArea.top + sHwArea.bottom) / 2; + 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); @@ -140,7 +156,7 @@ public class HandwritingInitiatorTest { boolean onTouchEventResult5 = mHandwritingInitiator.onTouchEvent(stylusEvent5); // It only calls startHandwriting once for each ACTION_DOWN. - verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView); + verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView1); assertThat(onTouchEventResult1).isFalse(); // stylusEvent2 does not trigger IMM.startHandwriting since the touch slop distance has not // been exceeded. onTouchEvent should return false so that the event is dispatched to the @@ -155,9 +171,9 @@ public class HandwritingInitiatorTest { @Test public void onTouchEvent_startHandwriting_when_stylusMove_withinExtendedHWArea() { - mHandwritingInitiator.onInputConnectionCreated(mTestView); - final int x1 = sHwArea.left - HW_BOUNDS_OFFSETS_LEFT_PX / 2; - final int y1 = sHwArea.top - HW_BOUNDS_OFFSETS_TOP_PX / 2; + 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; MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); mHandwritingInitiator.onTouchEvent(stylusEvent1); @@ -168,13 +184,13 @@ public class HandwritingInitiatorTest { mHandwritingInitiator.onTouchEvent(stylusEvent2); // Stylus movement within extended HandwritingArea should trigger IMM.startHandwriting once. - verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView); + verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView1); } @Test public void onTouchEvent_startHandwriting_inputConnectionBuiltAfterStylusMove() { - final int x1 = (sHwArea.left + sHwArea.right) / 2; - final int y1 = (sHwArea.top + sHwArea.bottom) / 2; + final int x1 = (sHwArea1.left + sHwArea1.right) / 2; + final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2; MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); mHandwritingInitiator.onTouchEvent(stylusEvent1); @@ -184,15 +200,15 @@ public class HandwritingInitiatorTest { mHandwritingInitiator.onTouchEvent(stylusEvent2); // InputConnection is created after stylus movement. - mHandwritingInitiator.onInputConnectionCreated(mTestView); + mHandwritingInitiator.onInputConnectionCreated(mTestView1); - verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView); + verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView1); } @Test public void onTouchEvent_startHandwriting_inputConnectionBuilt_stylusMoveInExtendedHWArea() { - final int x1 = sHwArea.right + HW_BOUNDS_OFFSETS_RIGHT_PX / 2; - final int y1 = sHwArea.bottom + HW_BOUNDS_OFFSETS_BOTTOM_PX / 2; + final int x1 = sHwArea1.right + HW_BOUNDS_OFFSETS_RIGHT_PX / 2; + final int y1 = sHwArea1.bottom + HW_BOUNDS_OFFSETS_BOTTOM_PX / 2; MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); mHandwritingInitiator.onTouchEvent(stylusEvent1); @@ -202,9 +218,9 @@ public class HandwritingInitiatorTest { mHandwritingInitiator.onTouchEvent(stylusEvent2); // InputConnection is created after stylus movement. - mHandwritingInitiator.onInputConnectionCreated(mTestView); + mHandwritingInitiator.onInputConnectionCreated(mTestView1); - verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView); + verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView1); } @Test @@ -212,11 +228,11 @@ public class HandwritingInitiatorTest { View delegateView = new View(mContext); delegateView.setIsHandwritingDelegate(true); - mTestView.setHandwritingDelegatorCallback( + mTestView1.setHandwritingDelegatorCallback( () -> mHandwritingInitiator.onInputConnectionCreated(delegateView)); - final int x1 = (sHwArea.left + sHwArea.right) / 2; - final int y1 = (sHwArea.top + sHwArea.bottom) / 2; + final int x1 = (sHwArea1.left + sHwArea1.right) / 2; + final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2; MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); mHandwritingInitiator.onTouchEvent(stylusEvent1); @@ -249,12 +265,12 @@ public class HandwritingInitiatorTest { // Stylus movement within HandwritingArea should not trigger IMM.startHandwriting since // the current IME doesn't support handwriting. - verify(mHandwritingInitiator, never()).startHandwriting(mTestView); + verify(mHandwritingInitiator, never()).startHandwriting(mTestView1); } @Test public void onTouchEvent_notStartHandwriting_when_stylusTap_withinHWArea() { - mHandwritingInitiator.onInputConnectionCreated(mTestView); + mHandwritingInitiator.onInputConnectionCreated(mTestView1); final int x1 = 200; final int y1 = 200; MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); @@ -265,12 +281,12 @@ public class HandwritingInitiatorTest { MotionEvent stylusEvent2 = createStylusEvent(ACTION_UP, x2, y2, 0); mHandwritingInitiator.onTouchEvent(stylusEvent2); - verify(mHandwritingInitiator, never()).startHandwriting(mTestView); + verify(mHandwritingInitiator, never()).startHandwriting(mTestView1); } @Test public void onTouchEvent_notStartHandwriting_when_stylusMove_outOfHWArea() { - mHandwritingInitiator.onInputConnectionCreated(mTestView); + mHandwritingInitiator.onInputConnectionCreated(mTestView1); final int x1 = 10; final int y1 = 10; MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); @@ -281,12 +297,12 @@ public class HandwritingInitiatorTest { MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0); mHandwritingInitiator.onTouchEvent(stylusEvent2); - verify(mHandwritingInitiator, never()).startHandwriting(mTestView); + verify(mHandwritingInitiator, never()).startHandwriting(mTestView1); } @Test public void onTouchEvent_notStartHandwriting_when_stylusMove_afterTimeOut() { - mHandwritingInitiator.onInputConnectionCreated(mTestView); + mHandwritingInitiator.onInputConnectionCreated(mTestView1); final int x1 = 10; final int y1 = 10; final long time1 = 10L; @@ -300,13 +316,13 @@ public class HandwritingInitiatorTest { mHandwritingInitiator.onTouchEvent(stylusEvent2); // stylus movement is after TAP_TIMEOUT it shouldn't call startHandwriting. - verify(mHandwritingInitiator, never()).startHandwriting(mTestView); + verify(mHandwritingInitiator, never()).startHandwriting(mTestView1); } @Test public void onTouchEvent_focusView_stylusMoveOnce_withinHWArea() { - final int x1 = (sHwArea.left + sHwArea.right) / 2; - final int y1 = (sHwArea.top + sHwArea.bottom) / 2; + final int x1 = (sHwArea1.left + sHwArea1.right) / 2; + final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2; MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); mHandwritingInitiator.onTouchEvent(stylusEvent1); @@ -317,13 +333,13 @@ public class HandwritingInitiatorTest { mHandwritingInitiator.onTouchEvent(stylusEvent2); // HandwritingInitiator will request focus for the registered view. - verify(mTestView, times(1)).requestFocus(); + verify(mTestView1, times(1)).requestFocus(); } @Test public void onTouchEvent_focusView_stylusMoveOnce_withinExtendedHWArea() { - final int x1 = sHwArea.left - HW_BOUNDS_OFFSETS_LEFT_PX / 2; - final int y1 = sHwArea.top - HW_BOUNDS_OFFSETS_TOP_PX / 2; + final int x1 = sHwArea1.left - HW_BOUNDS_OFFSETS_LEFT_PX / 2; + final int y1 = sHwArea1.top - HW_BOUNDS_OFFSETS_TOP_PX / 2; MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); mHandwritingInitiator.onTouchEvent(stylusEvent1); @@ -334,16 +350,16 @@ public class HandwritingInitiatorTest { mHandwritingInitiator.onTouchEvent(stylusEvent2); // HandwritingInitiator will request focus for the registered view. - verify(mTestView, times(1)).requestFocus(); + verify(mTestView1, times(1)).requestFocus(); } @Test - public void autoHandwriting_whenDisabled_wontStartHW() { - View mockView = createView(sHwArea, false /* autoHandwritingEnabled */, - true /* isStylusHandwritingAvailable */); - mHandwritingInitiator.onInputConnectionCreated(mockView); - final int x1 = (sHwArea.left + sHwArea.right) / 2; - final int y1 = (sHwArea.top + sHwArea.bottom) / 2; + public void onTouchEvent_handwritingAreaOverlapped_initiateForCloserView() { + // The ACTION_DOWN location is within the handwriting bounds of both mTestView1 and + // mTestView2. Because it's closer to mTestView2's handwriting bounds, handwriting is + // initiated for mTestView2. + final int x1 = (sHwArea1.left + sHwArea1.right) / 2; + final int y1 = sHwArea1.bottom + HW_BOUNDS_OFFSETS_BOTTOM_PX - 1; MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); mHandwritingInitiator.onTouchEvent(stylusEvent1); @@ -353,14 +369,57 @@ public class HandwritingInitiatorTest { MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0); mHandwritingInitiator.onTouchEvent(stylusEvent2); - verify(mHandwritingInitiator, never()).startHandwriting(mTestView); + verify(mTestView2, times(1)).requestFocus(); + + mHandwritingInitiator.onInputConnectionCreated(mTestView2); + verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView2); + } + + @Test + public void onTouchEvent_handwritingAreaOverlapped_focusedViewHasPriority() { + // Simulate the case where mTestView1 is focused. + mHandwritingInitiator.onInputConnectionCreated(mTestView1); + // The ACTION_DOWN location is within the handwriting bounds of both mTestView1 and + // mTestView2. Although it's closer to mTestView2's handwriting bounds, handwriting is + // initiated for mTestView1 because it's focused. + final int x1 = (sHwArea1.left + sHwArea1.right) / 2; + final int y1 = sHwArea1.bottom + HW_BOUNDS_OFFSETS_BOTTOM_PX - 1; + MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); + mHandwritingInitiator.onTouchEvent(stylusEvent1); + + final int x2 = x1 + mHandwritingSlop * 2; + final int y2 = y1; + + MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0); + mHandwritingInitiator.onTouchEvent(stylusEvent2); + + verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView1); + } + + @Test + public void autoHandwriting_whenDisabled_wontStartHW() { + View mockView = createView(sHwArea1, false /* autoHandwritingEnabled */, + true /* isStylusHandwritingAvailable */); + mHandwritingInitiator.onInputConnectionCreated(mockView); + final int x1 = (sHwArea1.left + sHwArea1.right) / 2; + final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2; + MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); + mHandwritingInitiator.onTouchEvent(stylusEvent1); + + final int x2 = x1 + mHandwritingSlop * 2; + final int y2 = y1; + + MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0); + mHandwritingInitiator.onTouchEvent(stylusEvent2); + + verify(mHandwritingInitiator, never()).startHandwriting(mTestView1); } @Test public void onInputConnectionCreated() { - mHandwritingInitiator.onInputConnectionCreated(mTestView); + mHandwritingInitiator.onInputConnectionCreated(mTestView1); assertThat(mHandwritingInitiator.mConnectedView).isNotNull(); - assertThat(mHandwritingInitiator.mConnectedView.get()).isEqualTo(mTestView); + assertThat(mHandwritingInitiator.mConnectedView.get()).isEqualTo(mTestView1); } @Test @@ -375,8 +434,8 @@ public class HandwritingInitiatorTest { @Test public void onInputConnectionClosed() { - mHandwritingInitiator.onInputConnectionCreated(mTestView); - mHandwritingInitiator.onInputConnectionClosed(mTestView); + mHandwritingInitiator.onInputConnectionCreated(mTestView1); + mHandwritingInitiator.onInputConnectionClosed(mTestView1); assertThat(mHandwritingInitiator.mConnectedView).isNull(); } @@ -396,12 +455,12 @@ public class HandwritingInitiatorTest { // When IMM restarts input connection, View#onInputConnectionCreatedInternal might be // called before View#onInputConnectionClosedInternal. As a result, we need to handle the // case where "one view "2 InputConnections". - mHandwritingInitiator.onInputConnectionCreated(mTestView); - mHandwritingInitiator.onInputConnectionCreated(mTestView); - mHandwritingInitiator.onInputConnectionClosed(mTestView); + mHandwritingInitiator.onInputConnectionCreated(mTestView1); + mHandwritingInitiator.onInputConnectionCreated(mTestView1); + mHandwritingInitiator.onInputConnectionClosed(mTestView1); assertThat(mHandwritingInitiator.mConnectedView).isNotNull(); - assertThat(mHandwritingInitiator.mConnectedView.get()).isEqualTo(mTestView); + assertThat(mHandwritingInitiator.mConnectedView.get()).isEqualTo(mTestView1); } private MotionEvent createStylusEvent(int action, int x, int y, long eventTime) {