Use InputEventAssigner to assign input to frame

When a frame is determined to be caused by an input event, it will be
labeled with a specific input event id. The newly added entity,
InputEventAssigner, is responsible for determining which input
event we should use.

For regular ACTION_MOVE events, we will take the latest input event as
the event that has caused the frame. For the initial gesture
(ACTION_DOWN), we will use the first event (the ACTION_DOWN itself) to
figure out the down latency. This will allow us to split up 'down' and
'scroll' latencies.

Bug: 169866723
Test: looked at the data printed locally to logcat
Test: atest InputTests
Change-Id: I8513a36960e5d652d07655d1267e758d0c59ced7
diff --git a/core/java/android/view/InputEventAssigner.java b/core/java/android/view/InputEventAssigner.java
new file mode 100644
index 0000000..c159a12
--- /dev/null
+++ b/core/java/android/view/InputEventAssigner.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static android.os.IInputConstants.INVALID_INPUT_EVENT_ID;
+import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
+
+/**
+ * Process input events and assign input event id to a specific frame.
+ *
+ * The assigned input event id is determined by where the current gesture is relative to the vsync.
+ * In the middle of the gesture (we already processed some input events, and already received at
+ * least 1 vsync), the latest InputEvent is assigned to the next frame.
+ * If a gesture just started, then the ACTION_DOWN event will be assigned to the next frame.
+ *
+ * Consider the following sequence:
+ * DOWN -> VSYNC 1 -> MOVE 1 -> MOVE 2 -> VSYNC 2.
+ *
+ * For VSYNC 1, we will assign the "DOWN" input event.
+ * For VSYNC 2, we will assign the "MOVE 2" input event.
+ *
+ * Consider another sequence:
+ * DOWN -> MOVE 1 -> MOVE 2 -> VSYNC 1 -> MOVE 3 -> VSYNC 2.
+ *
+ * For VSYNC 1, we will still assign the "DOWN" input event. That means that "MOVE 1" and "MOVE 2"
+ * events are not attributed to any frame.
+ * For VSYNC 2, the "MOVE 3" input event will be assigned.
+ *
+ * @hide
+ */
+public class InputEventAssigner {
+    private static final String TAG = "InputEventAssigner";
+    private boolean mHasUnprocessedDown = false;
+    private int mEventId = INVALID_INPUT_EVENT_ID;
+
+    /**
+     * Notify InputEventAssigner that the Choreographer callback has been processed. This will reset
+     * the 'down' state to assign the latest input event to the current frame.
+     */
+    public void onChoreographerCallback() {
+        // Mark completion of this frame. Use newest input event from now on.
+        mHasUnprocessedDown = false;
+    }
+
+    /**
+     * Process the provided input event to determine which event id to assign to the current frame.
+     * @param event the input event currently being processed
+     * @return the id of the input event to use for the current frame
+     */
+    public int processEvent(InputEvent event) {
+        if (event instanceof KeyEvent) {
+            // We will not do any special handling for key events
+            return event.getId();
+        }
+
+        if (event instanceof MotionEvent) {
+            MotionEvent motionEvent = (MotionEvent) event;
+            final int action = motionEvent.getActionMasked();
+
+            if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
+                mHasUnprocessedDown = false;
+            }
+            if (motionEvent.isFromSource(SOURCE_TOUCHSCREEN) && action == MotionEvent.ACTION_DOWN) {
+                mHasUnprocessedDown = true;
+                mEventId = event.getId();
+                // This will remain 'true' even if we receive a MOVE event, as long as choreographer
+                // hasn't invoked the 'CALLBACK_INPUT' callback.
+            }
+            // Don't update the event id if we haven't processed DOWN yet.
+            if (!mHasUnprocessedDown) {
+                mEventId = event.getId();
+            }
+            return mEventId;
+        }
+
+        throw new IllegalArgumentException("Received unexpected " + event);
+    }
+}
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index d67439c..6801c27 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -3589,6 +3589,7 @@
             msg.append(", deviceId=").append(getDeviceId());
             msg.append(", source=0x").append(Integer.toHexString(getSource()));
             msg.append(", displayId=").append(getDisplayId());
+            msg.append(", eventId=").append(getId());
         }
         msg.append(" }");
         return msg.toString();
diff --git a/core/java/android/view/ViewFrameInfo.java b/core/java/android/view/ViewFrameInfo.java
index d4aaa61..36bf532 100644
--- a/core/java/android/view/ViewFrameInfo.java
+++ b/core/java/android/view/ViewFrameInfo.java
@@ -17,6 +17,7 @@
 package android.view;
 
 import android.graphics.FrameInfo;
+import android.os.IInputConstants;
 
 /**
  * The timing information of events taking place in ViewRootImpl
@@ -24,32 +25,14 @@
  */
 public class ViewFrameInfo {
     public long drawStart;
-    public long oldestInputEventTime; // the time of the oldest input event consumed for this frame
-    public long newestInputEventTime; // the time of the newest input event consumed for this frame
+
+
     // Various flags set to provide extra metadata about the current frame. See flag definitions
     // inside FrameInfo.
     // @see android.graphics.FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED
     public long flags;
 
-    /**
-     * Update the oldest event time.
-     * @param eventTime the time of the input event
-     */
-    public void updateOldestInputEvent(long eventTime) {
-        if (oldestInputEventTime == 0 || eventTime < oldestInputEventTime) {
-            oldestInputEventTime = eventTime;
-        }
-    }
-
-    /**
-     * Update the newest event time.
-     * @param eventTime the time of the input event
-     */
-    public void updateNewestInputEvent(long eventTime) {
-        if (newestInputEventTime == 0 || eventTime > newestInputEventTime) {
-            newestInputEventTime = eventTime;
-        }
-    }
+    private int mInputEventId;
 
     /**
      * Populate the missing fields using the data from ViewFrameInfo
@@ -58,8 +41,7 @@
     public void populateFrameInfo(FrameInfo frameInfo) {
         frameInfo.frameInfo[FrameInfo.FLAGS] |= flags;
         frameInfo.frameInfo[FrameInfo.DRAW_START] = drawStart;
-        // TODO(b/169866723): Use InputEventAssigner
-        frameInfo.frameInfo[FrameInfo.INPUT_EVENT_ID] = newestInputEventTime;
+        frameInfo.frameInfo[FrameInfo.INPUT_EVENT_ID] = mInputEventId;
     }
 
     /**
@@ -67,8 +49,7 @@
      */
     public void reset() {
         drawStart = 0;
-        oldestInputEventTime = 0;
-        newestInputEventTime = 0;
+        mInputEventId = IInputConstants.INVALID_INPUT_EVENT_ID;
         flags = 0;
     }
 
@@ -78,4 +59,12 @@
     public void markDrawStart() {
         drawStart = System.nanoTime();
     }
+
+    /**
+     * Assign the value for input event id
+     * @param eventId the id of the input event
+     */
+    public void setInputEvent(int eventId) {
+        mInputEventId = eventId;
+    }
 }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index f8e65bd..390e3ae 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -457,6 +457,7 @@
     FallbackEventHandler mFallbackEventHandler;
     final Choreographer mChoreographer;
     protected final ViewFrameInfo mViewFrameInfo = new ViewFrameInfo();
+    private final InputEventAssigner mInputEventAssigner = new InputEventAssigner();
 
     /**
      * Update the Choreographer's FrameInfo object with the timing information for the current
@@ -8352,16 +8353,7 @@
             Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,
                     mPendingInputEventCount);
 
-            long eventTime = q.mEvent.getEventTimeNano();
-            long oldestEventTime = eventTime;
-            if (q.mEvent instanceof MotionEvent) {
-                MotionEvent me = (MotionEvent)q.mEvent;
-                if (me.getHistorySize() > 0) {
-                    oldestEventTime = me.getHistoricalEventTimeNano(0);
-                }
-            }
-            mViewFrameInfo.updateOldestInputEvent(oldestEventTime);
-            mViewFrameInfo.updateNewestInputEvent(eventTime);
+            mViewFrameInfo.setInputEvent(mInputEventAssigner.processEvent(q.mEvent));
 
             deliverInputEvent(q);
         }
@@ -8497,6 +8489,11 @@
             consumedBatches = false;
         }
         doProcessInputEvents();
+        if (consumedBatches) {
+            // Must be done after we processed the input events, to mark the completion of the frame
+            // from the input point of view
+            mInputEventAssigner.onChoreographerCallback();
+        }
         return consumedBatches;
     }
 
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 77ceda9..d663c52 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -421,7 +421,6 @@
                 "libstatspull",
                 "libstatssocket",
                 "libpdfium",
-                "libbinder_ndk",
             ],
             static_libs: [
                 "libgif",
diff --git a/libs/hwui/FrameInfo.h b/libs/hwui/FrameInfo.h
index 912d04c5..e9b2f4a 100644
--- a/libs/hwui/FrameInfo.h
+++ b/libs/hwui/FrameInfo.h
@@ -80,6 +80,10 @@
     explicit UiFrameInfoBuilder(int64_t* buffer) : mBuffer(buffer) {
         memset(mBuffer, 0, UI_THREAD_FRAME_INFO_SIZE * sizeof(int64_t));
         set(FrameInfoIndex::FrameTimelineVsyncId) = INVALID_VSYNC_ID;
+        // The struct is zeroed by memset above. That also sets FrameInfoIndex::InputEventId to
+        // equal android::os::IInputConstants::INVALID_INPUT_EVENT_ID == 0.
+        // Therefore, we can skip setting the value for InputEventId here. If the value for
+        // INVALID_INPUT_EVENT_ID changes, this code would have to be updated, as well.
         set(FrameInfoIndex::FrameDeadline) = std::numeric_limits<int64_t>::max();
     }
 
diff --git a/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt b/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt
new file mode 100644
index 0000000..2e985fb
--- /dev/null
+++ b/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.input
+
+import android.view.InputDevice.SOURCE_MOUSE
+import android.view.InputDevice.SOURCE_TOUCHSCREEN
+import android.view.InputEventAssigner
+import android.view.KeyEvent
+import android.view.MotionEvent
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+/**
+ * Create a MotionEvent with the provided action, eventTime, and source
+ */
+fun createMotionEvent(action: Int, eventTime: Long, source: Int): MotionEvent {
+    val downTime: Long = 10
+    val x = 1f
+    val y = 2f
+    val pressure = 3f
+    val size = 1f
+    val metaState = 0
+    val xPrecision = 0f
+    val yPrecision = 0f
+    val deviceId = 1
+    val edgeFlags = 0
+    val displayId = 0
+    return MotionEvent.obtain(downTime, eventTime, action, x, y, pressure, size, metaState,
+            xPrecision, yPrecision, deviceId, edgeFlags, source, displayId)
+}
+
+fun createKeyEvent(action: Int, eventTime: Long): KeyEvent {
+    val code = KeyEvent.KEYCODE_A
+    val repeat = 0
+    return KeyEvent(eventTime, eventTime, action, code, repeat)
+}
+
+class InputEventAssignerTest {
+    companion object {
+        private const val TAG = "InputEventAssignerTest"
+    }
+
+    /**
+     * A single MOVE event should be assigned to the next available frame.
+     */
+    @Test
+    fun testTouchGesture() {
+        val assigner = InputEventAssigner()
+        val event = createMotionEvent(MotionEvent.ACTION_MOVE, 10, SOURCE_TOUCHSCREEN)
+        val eventId = assigner.processEvent(event)
+        assertEquals(event.id, eventId)
+    }
+
+    /**
+     * DOWN event should be used until a vsync comes in. After vsync, the latest event should be
+     * produced.
+     */
+    @Test
+    fun testTouchDownWithMove() {
+        val assigner = InputEventAssigner()
+        val down = createMotionEvent(MotionEvent.ACTION_DOWN, 10, SOURCE_TOUCHSCREEN)
+        val move1 = createMotionEvent(MotionEvent.ACTION_MOVE, 12, SOURCE_TOUCHSCREEN)
+        val move2 = createMotionEvent(MotionEvent.ACTION_MOVE, 13, SOURCE_TOUCHSCREEN)
+        val move3 = createMotionEvent(MotionEvent.ACTION_MOVE, 14, SOURCE_TOUCHSCREEN)
+        val move4 = createMotionEvent(MotionEvent.ACTION_MOVE, 15, SOURCE_TOUCHSCREEN)
+        var eventId = assigner.processEvent(down)
+        assertEquals(down.id, eventId)
+        eventId = assigner.processEvent(move1)
+        assertEquals(down.id, eventId)
+        eventId = assigner.processEvent(move2)
+        // Even though we already had 2 move events, there was no choreographer callback yet.
+        // Therefore, we should still get the id of the down event
+        assertEquals(down.id, eventId)
+
+        // Now send CALLBACK_INPUT to the assigner. It should provide the latest motion event
+        assigner.onChoreographerCallback()
+        eventId = assigner.processEvent(move3)
+        assertEquals(move3.id, eventId)
+        eventId = assigner.processEvent(move4)
+        assertEquals(move4.id, eventId)
+    }
+
+    /**
+     * Similar to the above test, but with SOURCE_MOUSE. Since we don't have down latency
+     * concept for non-touchscreens, the latest input event will be used.
+     */
+    @Test
+    fun testMouseDownWithMove() {
+        val assigner = InputEventAssigner()
+        val down = createMotionEvent(MotionEvent.ACTION_DOWN, 10, SOURCE_MOUSE)
+        val move1 = createMotionEvent(MotionEvent.ACTION_MOVE, 12, SOURCE_MOUSE)
+        var eventId = assigner.processEvent(down)
+        assertEquals(down.id, eventId)
+        eventId = assigner.processEvent(move1)
+        assertEquals(move1.id, eventId)
+    }
+
+    /**
+     * KeyEvents are processed immediately, so the latest event should be returned.
+     */
+    @Test
+    fun testKeyEvent() {
+        val assigner = InputEventAssigner()
+        val down = createKeyEvent(KeyEvent.ACTION_DOWN, 20)
+        var eventId = assigner.processEvent(down)
+        assertEquals(down.id, eventId)
+        val up = createKeyEvent(KeyEvent.ACTION_UP, 21)
+        eventId = assigner.processEvent(up)
+        // DOWN is only sticky for Motions, not for keys
+        assertEquals(up.id, eventId)
+        assigner.onChoreographerCallback()
+        val down2 = createKeyEvent(KeyEvent.ACTION_DOWN, 22)
+        eventId = assigner.processEvent(down2)
+        assertEquals(down2.id, eventId)
+    }
+}
diff --git a/tests/Input/src/com/android/test/input/ViewFrameInfoTest.kt b/tests/Input/src/com/android/test/input/ViewFrameInfoTest.kt
index c19e5cc..c01d32b 100644
--- a/tests/Input/src/com/android/test/input/ViewFrameInfoTest.kt
+++ b/tests/Input/src/com/android/test/input/ViewFrameInfoTest.kt
@@ -17,6 +17,7 @@
 package com.android.test.input
 
 import android.graphics.FrameInfo
+import android.os.IInputConstants.INVALID_INPUT_EVENT_ID
 import android.os.SystemClock
 import android.view.ViewFrameInfo
 import com.google.common.truth.Truth.assertThat
@@ -33,8 +34,7 @@
     @Before
     fun setUp() {
         mViewFrameInfo.reset()
-        mViewFrameInfo.updateOldestInputEvent(10)
-        mViewFrameInfo.updateNewestInputEvent(20)
+        mViewFrameInfo.setInputEvent(139)
         mViewFrameInfo.flags = mViewFrameInfo.flags or FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED
         mTimeStarted = SystemClock.uptimeNanos()
         mViewFrameInfo.markDrawStart()
@@ -43,8 +43,6 @@
     @Test
     fun testPopulateFields() {
         assertThat(mViewFrameInfo.drawStart).isGreaterThan(mTimeStarted)
-        assertThat(mViewFrameInfo.oldestInputEventTime).isEqualTo(10)
-        assertThat(mViewFrameInfo.newestInputEventTime).isEqualTo(20)
         assertThat(mViewFrameInfo.flags).isEqualTo(FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED)
     }
 
@@ -53,8 +51,6 @@
         mViewFrameInfo.reset()
         // Ensure that the original object is reset correctly
         assertThat(mViewFrameInfo.drawStart).isEqualTo(0)
-        assertThat(mViewFrameInfo.oldestInputEventTime).isEqualTo(0)
-        assertThat(mViewFrameInfo.newestInputEventTime).isEqualTo(0)
         assertThat(mViewFrameInfo.flags).isEqualTo(0)
     }
 
@@ -62,12 +58,13 @@
     fun testUpdateFrameInfoFromViewFrameInfo() {
         val frameInfo = FrameInfo()
         // By default, all values should be zero
-        // TODO(b/169866723): Use InputEventAssigner and assert INPUT_EVENT_ID
+        assertThat(frameInfo.frameInfo[FrameInfo.INPUT_EVENT_ID]).isEqualTo(INVALID_INPUT_EVENT_ID)
         assertThat(frameInfo.frameInfo[FrameInfo.FLAGS]).isEqualTo(0)
         assertThat(frameInfo.frameInfo[FrameInfo.DRAW_START]).isEqualTo(0)
 
         // The values inside FrameInfo should match those from ViewFrameInfo after we update them
         mViewFrameInfo.populateFrameInfo(frameInfo)
+        assertThat(frameInfo.frameInfo[FrameInfo.INPUT_EVENT_ID]).isEqualTo(139)
         assertThat(frameInfo.frameInfo[FrameInfo.FLAGS]).isEqualTo(
                 FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED)
         assertThat(frameInfo.frameInfo[FrameInfo.DRAW_START]).isGreaterThan(mTimeStarted)