Ensure handwriting session can only be started from inside app bounds

Handwriting should only be started if there is an ongoing stylus gesture
targeted at the focused app. If there is no ongoing stylus gesture on
the focused app, the startStylusHandwriting request will fail.

Update the StylusHandwritingTests for these new requirements.

Bug: 226081811
Test: atest StylusHandwritingTest
Test: manual with HandwritingIme test app
Change-Id: I9cc202a932cc7b79b4c0b0703ba9a3a92465b588
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/StylusHandwritingTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/StylusHandwritingTest.java
index 1398785..84a9285 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/StylusHandwritingTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/StylusHandwritingTest.java
@@ -106,6 +106,39 @@
     }
 
     @Test
+    public void testHandwritingDoesNotStartWhenNoStylusDown() throws Exception {
+        final InputMethodManager imm = mContext.getSystemService(InputMethodManager.class);
+        try (MockImeSession imeSession = MockImeSession.create(
+                InstrumentationRegistry.getInstrumentation().getContext(),
+                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                new ImeSettings.Builder())) {
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            final String marker = getTestMarker();
+            final EditText editText = launchTestActivity(marker);
+
+            expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+            notExpectEvent(
+                    stream,
+                    editorMatcher("onStartInputView", marker),
+                    NOT_EXPECT_TIMEOUT);
+
+            imm.startStylusHandwriting(editText);
+
+            // Handwriting should not start
+            notExpectEvent(
+                    stream,
+                    editorMatcher("onStartStylusHandwriting", marker),
+                    NOT_EXPECT_TIMEOUT);
+
+            // Verify Stylus Handwriting window is not shown
+            assertFalse(expectCommand(
+                    stream, imeSession.callGetStylusHandwritingWindowVisibility(), TIMEOUT)
+                    .getReturnBooleanValue());
+        }
+    }
+
+    @Test
     public void testHandwritingStartAndFinish() throws Exception {
         final InputMethodManager imm = mContext.getSystemService(InputMethodManager.class);
         try (MockImeSession imeSession = MockImeSession.create(
@@ -117,6 +150,11 @@
             final String marker = getTestMarker();
             final EditText editText = launchTestActivity(marker);
 
+            // Touch down with a stylus
+            final int x = 10;
+            final int y = 10;
+            TestUtils.injectStylusDownEvent(editText, x, y);
+
             expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
             notExpectEvent(
                     stream,
@@ -144,6 +182,9 @@
                     stream, imeSession.callGetStylusHandwritingWindowVisibility(), TIMEOUT)
                             .getReturnBooleanValue());
 
+            // Release the stylus pointer
+            TestUtils.injectStylusUpEvent(editText, x, y);
+
             // Verify calling finishStylusHandwriting() calls onFinishStylusHandwriting().
             imeSession.callFinishStylusHandwriting();
             expectEvent(
@@ -186,6 +227,12 @@
             final String marker = getTestMarker();
             final EditText editText = launchTestActivity(marker);
 
+            final List<MotionEvent> injectedEvents = new ArrayList<>();
+            // Touch down with a stylus
+            final int startX = 10;
+            final int startY = 10;
+            injectedEvents.add(TestUtils.injectStylusDownEvent(editText, startX, startY));
+
             expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
             notExpectEvent(
                     stream,
@@ -212,10 +259,11 @@
                         TIMEOUT).getReturnBooleanValue());
             }
 
-            // inject Stylus touch events to screen
-            List<MotionEvent> injectedEvents =
-                    TestUtils.getStylusMoveEventsOnDisplay(0 /* displayId */);
-            TestUtils.injectAll(injectedEvents);
+            final int endX = startX + 500;
+            final int endY = startY + 500;
+            injectedEvents.addAll(
+                    TestUtils.injectStylusMoveEvents(editText, startX, startY, endX, endY, 10));
+            injectedEvents.add(TestUtils.injectStylusUpEvent(editText, endX, endY));
 
             expectEvent(
                     stream, event -> "onStylusMotionEvent".equals(event.getEventName()), TIMEOUT);
diff --git a/tests/inputmethod/util/src/android/view/inputmethod/cts/util/TestUtils.java b/tests/inputmethod/util/src/android/view/inputmethod/cts/util/TestUtils.java
index d5abfce..54a12ee 100644
--- a/tests/inputmethod/util/src/android/view/inputmethod/cts/util/TestUtils.java
+++ b/tests/inputmethod/util/src/android/view/inputmethod/cts/util/TestUtils.java
@@ -204,8 +204,9 @@
      * @param view  view whose coordinates are used to compute the event location.
      * @param x the x coordinates of the stylus event in the view's location coordinates.
      * @param y the y coordinates of the stylus event in the view's location coordinates.
+     * @return the injected MotionEvent.
      */
-    public static void injectStylusDownEvent(@NonNull View view, int x, int y) {
+    public static MotionEvent injectStylusDownEvent(@NonNull View view, int x, int y) {
         int[] xy = new int[2];
         view.getLocationOnScreen(xy);
         x += xy[0];
@@ -213,8 +214,10 @@
 
         // Inject stylus ACTION_DOWN
         long downTime = SystemClock.uptimeMillis();
-        MotionEvent downEvent = getMotionEvent(downTime, downTime, MotionEvent.ACTION_DOWN, x, y);
+        final MotionEvent downEvent =
+                getMotionEvent(downTime, downTime, MotionEvent.ACTION_DOWN, x, y);
         injectMotionEvent(downEvent, true /* sync */);
+        return downEvent;
     }
 
     /**
@@ -222,8 +225,9 @@
      * @param view  view whose coordinates are used to compute the event location.
      * @param x the x coordinates of the stylus event in the view's location coordinates.
      * @param y the y coordinates of the stylus event in the view's location coordinates.
+     * @return the injected MotionEvent.
      */
-    public static void injectStylusUpEvent(@NonNull View view, int x, int y) {
+    public static MotionEvent injectStylusUpEvent(@NonNull View view, int x, int y) {
         int[] xy = new int[2];
         view.getLocationOnScreen(xy);
         x += xy[0];
@@ -231,8 +235,9 @@
 
         // Inject stylus ACTION_DOWN
         long downTime = SystemClock.uptimeMillis();
-        MotionEvent downEvent = getMotionEvent(downTime, downTime, MotionEvent.ACTION_UP, x, y);
-        injectMotionEvent(downEvent, true /* sync */);
+        final MotionEvent upEvent = getMotionEvent(downTime, downTime, MotionEvent.ACTION_UP, x, y);
+        injectMotionEvent(upEvent, true /* sync */);
+        return upEvent;
     }
 
     /**
@@ -244,15 +249,17 @@
      * @param endX the end x coordinates of the stylus event in the view's local coordinates.
      * @param endY the end y coordinates of the stylus event in the view's local coordinates.
      * @param number the number of the motion events injected to the view.
+     * @return the injected MotionEvents.
      */
-    public static void injectStylusMoveEvents(@NonNull View view, int startX, int startY,
-            int endX, int endY, int number) {
+    public static List<MotionEvent> injectStylusMoveEvents(@NonNull View view, int startX,
+            int startY, int endX, int endY, int number) {
         int[] xy = new int[2];
         view.getLocationOnScreen(xy);
 
         final float incrementX = ((float) (endX - startX)) / (number - 1);
         final float incrementY = ((float) (endY - startY)) / (number - 1);
 
+        final List<MotionEvent> injectedEvents = new ArrayList<>(number);
         // Inject stylus ACTION_MOVE
         for (int i = 0; i < number; i++) {
             long time = SystemClock.uptimeMillis();
@@ -261,7 +268,9 @@
             final MotionEvent moveEvent =
                     getMotionEvent(time, time, MotionEvent.ACTION_MOVE, x, y);
             injectMotionEvent(moveEvent, true /* sync */);
+            injectedEvents.add(moveEvent);
         }
+        return injectedEvents;
     }
 
     /**
@@ -283,30 +292,6 @@
 
     }
 
-    public static List<MotionEvent> getStylusMoveEventsOnDisplay(int displayId) {
-        // TODO(b/210039666): use screen center.
-        // TODO(b/210039666): try combining with #injectStylusEvents().
-        int x = 500;
-        int y = 500;
-        List<MotionEvent> events = new ArrayList<>();
-        final long downTime = SystemClock.uptimeMillis();
-        events.add(getMotionEvent(downTime, downTime, MotionEvent.ACTION_DOWN, x, y, displayId));
-
-        int j = 0;
-        for (int i = 0; i < 10; i++) {
-            final long moveTime = SystemClock.uptimeMillis();
-            j = i * 5;
-            events.add(getMotionEvent(
-                    moveTime, moveTime, MotionEvent.ACTION_MOVE, x + j, y + j, displayId));
-        }
-
-        final long upTime = SystemClock.uptimeMillis();
-        events.add(getMotionEvent(
-                upTime, upTime, MotionEvent.ACTION_UP, x + j, y + j, displayId));
-
-        return events;
-    }
-
     private static MotionEvent getMotionEvent(long downTime, long eventTime, int action,
             float x, float y) {
         return getMotionEvent(downTime, eventTime, action, (int) x, (int) y, 0);