Reduce tech debt in InputDispatcher and improve docs

Simplifies timeline related code and improves documentation by stripping
out obsolete references and simplifying public facing text.

Fix: 192053863
Test: ./gradlew compose:ui:ui-test:cC
Change-Id: I16a817aa4fc480580ed316ee407aeba26e9d0de7
Relnote: N/A
diff --git a/compose/ui/ui-test/api/current.txt b/compose/ui/ui-test/api/current.txt
index 4559dc2..260a98d 100644
--- a/compose/ui/ui-test/api/current.txt
+++ b/compose/ui/ui-test/api/current.txt
@@ -169,6 +169,9 @@
     property public abstract boolean isIdleNow;
   }
 
+  public final class InputDispatcherKt {
+  }
+
   public final class KeyInputHelpersKt {
     method public static boolean performKeyPress-S8GO8FU(androidx.compose.ui.test.SemanticsNodeInteraction, android.view.KeyEvent keyEvent);
   }
diff --git a/compose/ui/ui-test/api/public_plus_experimental_current.txt b/compose/ui/ui-test/api/public_plus_experimental_current.txt
index ba65508..bcbd1b9 100644
--- a/compose/ui/ui-test/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui-test/api/public_plus_experimental_current.txt
@@ -180,6 +180,9 @@
     property public abstract boolean isIdleNow;
   }
 
+  public final class InputDispatcherKt {
+  }
+
   @kotlin.RequiresOptIn(message="This is internal API for Compose modules that may change frequently and without warning.") public @interface InternalTestApi {
   }
 
diff --git a/compose/ui/ui-test/api/restricted_current.txt b/compose/ui/ui-test/api/restricted_current.txt
index 4559dc2..260a98d 100644
--- a/compose/ui/ui-test/api/restricted_current.txt
+++ b/compose/ui/ui-test/api/restricted_current.txt
@@ -169,6 +169,9 @@
     property public abstract boolean isIdleNow;
   }
 
+  public final class InputDispatcherKt {
+  }
+
   public final class KeyInputHelpersKt {
     method public static boolean performKeyPress-S8GO8FU(androidx.compose.ui.test.SemanticsNodeInteraction, android.view.KeyEvent keyEvent);
   }
diff --git a/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/AndroidInputDispatcher.android.kt b/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/AndroidInputDispatcher.android.kt
index 58f0643..d28ba19 100644
--- a/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/AndroidInputDispatcher.android.kt
+++ b/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/AndroidInputDispatcher.android.kt
@@ -35,7 +35,7 @@
     root: RootForTest
 ): InputDispatcher {
     require(root is ViewRootForTest) {
-        "InputDispatcher currently only supports dispatching to ViewRootForTest, not to " +
+        "InputDispatcher only supports dispatching to ViewRootForTest, not to " +
             root::class.java.simpleName
     }
     val view = root.view
@@ -49,12 +49,9 @@
 ) : InputDispatcher(testContext, root) {
 
     private val batchLock = Any()
-    // Batched events are generated just-in-time, given the "lateness" of the dispatching (see
-    // sendAllSynchronous), so enqueue generators rather than instantiated events
     private var batchedEvents = mutableListOf<MotionEvent>()
     private var acceptEvents = true
-    private var firstEventTime = Long.MAX_VALUE
-    private val previousLastEventTime = partialGesture?.lastEventTime
+    private var lastEventTime = currentTime
 
     override val now: Long get() = SystemClock.uptimeMillis()
 
@@ -91,7 +88,7 @@
         val entries = lastPositions.entries.sortedBy { it.key }
         batchMotionEvent(
             downTime,
-            lastEventTime,
+            currentTime,
             action,
             actionIndex,
             List(entries.size) { entries[it].value },
@@ -121,8 +118,8 @@
                     "coordinates=$coordinates" +
                     "), events have already been (or are being) dispatched or disposed"
             }
-            if (firstEventTime == Long.MAX_VALUE) {
-                firstEventTime = eventTime
+            if (lastEventTime == TimeNotSet) {
+                lastEventTime = eventTime
             }
             val positionInScreen = if (root != null) {
                 val array = intArrayOf(0, 0)
@@ -167,13 +164,12 @@
         testContext.testOwner.runOnUiThread {
             checkAndStopAcceptingEvents()
 
-            var lastEventTime = (previousLastEventTime ?: firstEventTime)
+            var currentEventTime = lastEventTime
             batchedEvents.forEach { event ->
                 // Before injecting the next event, pump the clock
                 // by the difference between this and the last event
-                pumpClock(
-                    event.eventTime - lastEventTime.also { lastEventTime = event.eventTime }
-                )
+                pumpClock(event.eventTime - currentEventTime)
+                currentEventTime = event.eventTime
                 sendAndRecycleEvent(event)
             }
         }
diff --git a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/GestureScope.kt b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/GestureScope.kt
index d180bc8..7cf6995 100644
--- a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/GestureScope.kt
+++ b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/GestureScope.kt
@@ -58,24 +58,24 @@
  * The receiver scope for injecting gestures on the [semanticsNode] identified by the
  * corresponding [SemanticsNodeInteraction]. Gestures can be injected by calling methods defined
  * on [GestureScope], such as [click] or [swipe]. The [SemanticsNodeInteraction] can be found by
- * one of the finder methods such as [ComposeTestRule.onNode].
+ * one of the finder methods such as
+ * [ComposeTestRule.onNode][androidx.compose.ui.test.junit4.ComposeTestRule.onNode].
  *
  * The functions in [GestureScope] can roughly be divided into two groups: full gestures and
- * partial gestures. Partial gestures are the ones that send individual touch events: [down],
- * [move], [up] and [cancel]. Full gestures are all the other functions, like [click],
- * [doubleClick], [swipe], etc. See the documentation of [down] for more information about
- * partial gestures. Normally, if you accidentally try to execute a full gesture while in the
- * middle of a partial gesture, an [IllegalStateException] or [IllegalArgumentException] will be
- * thrown. However, you might want to do this on purpose, for testing multi-touch gestures, where
- * one finger might tap the screen while another is making a gesture. In that case, make sure the
- * partial gesture uses a non-default pointer id.
+ * individual touch events. The individual touch events are: [down], [move] and friends, [up]
+ * and [cancel]. Full gestures are all the other functions, like [click], [doubleClick],
+ * [swipe], etc. See the documentation of [down] for more information about individual events. If
+ * you execute a full gesture while in the middle of another gesture, an [IllegalStateException]
+ * or [IllegalArgumentException] can be thrown when the pointerId is unintentionally used for
+ * both gestures. If you want to perform e.g. a click during a partially performed gesture, make
+ * sure they use different pointer ids.
  *
  * Note that all events generated by the gesture methods are batched together and sent as a whole
  * after [performGesture] has executed its code block.
  *
  * Next to the functions, [GestureScope] also exposes several properties that allow you to get
  * [coordinates][Offset] within a node, like the [top left corner][topLeft], its [center], or
- * 20% to the left of the right edge and 10% below the top edge ([percentOffset]).
+ * some percentage of the size ([percentOffset]).
  *
  * Example usage:
  * ```
@@ -107,7 +107,7 @@
     private var _semanticsNode: SemanticsNode? = node
     internal val semanticsNode
         get() = checkNotNull(_semanticsNode) {
-            "Can't query SemanticsNode, (Partial)GestureScope has already been disposed"
+            "Can't query SemanticsNode, GestureScope has already been disposed"
         }
 
     // TODO(b/133217292): Better error: explain which gesture couldn't be performed
@@ -115,7 +115,7 @@
         createInputDispatcher(testContext, checkNotNull(semanticsNode.root))
     internal val inputDispatcher
         get() = checkNotNull(_inputDispatcher) {
-            "Can't send gesture, (Partial)GestureScope has already been disposed"
+            "Can't send gesture, GestureScope has already been disposed"
         }
 
     /**
@@ -675,11 +675,11 @@
  * node. The [position] is in the node's local coordinate system, where (0, 0) is
  * the top left corner of the node.
  *
- * If no pointers are down yet, this will start a new partial gesture. If a partial gesture is
+ * If no pointers are down yet, this will start a new gesture. If a gesture is
  * already in progress, this event is sent with at the same timestamp as the last event. If the
  * given pointer is already down, an [IllegalArgumentException] will be thrown.
  *
- * This gesture is considered _partial_, because the entire gesture can be spread over several
+ * Subsequent events for this or other gestures can be spread out over both this and future
  * invocations of [performGesture]. An entire gesture starts with a [down][down] event,
  * followed by several down, move or up events, and ends with an [up][up] or a
  * [cancel][cancel] event. Movement can be expressed with [moveTo] and [moveBy] to
@@ -694,7 +694,7 @@
  * cancel event will contain the up to date position of all pointers. Move and cancel events will
  * advance the event time by 10 milliseconds.
  *
- * Because partial gestures don't have to be defined all in the same [performGesture] block,
+ * Because gestures don't have to be defined all in the same [performGesture] block,
  * keep in mind that while the gesture is not complete, all code you execute in between
  * blocks that progress the gesture, will be executed while imaginary fingers are actively
  * touching the screen.
@@ -715,7 +715,7 @@
  * [position] is in the node's local coordinate system, where (0, 0) is the top left
  * corner of the node. The default pointer has `pointerId = 0`.
  *
- * If no pointers are down yet, this will start a new partial gesture. If a partial gesture is
+ * If no pointers are down yet, this will start a new gesture. If a gesture is
  * already in progress, this event is sent with at the same timestamp as the last event. If the
  * default pointer is already down, an [IllegalArgumentException] will be thrown.
  *
@@ -839,7 +839,7 @@
 }
 
 /**
- * Sends a cancel event to cancel the current partial gesture. The cancel event contains the
+ * Sends a cancel event to cancel the current gesture. The cancel event contains the
  * current position of all active pointers.
  */
 fun GestureScope.cancel() {
diff --git a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/InputDispatcher.kt b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/InputDispatcher.kt
index 06ac3c0..78b6fb2 100644
--- a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/InputDispatcher.kt
+++ b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/InputDispatcher.kt
@@ -27,6 +27,11 @@
 ): InputDispatcher
 
 /**
+ * Indicates that [InputDispatcher.currentTime] is not set
+ */
+internal const val TimeNotSet = -1L
+
+/**
  * Dispatcher to inject full and partial gestures. An [InputDispatcher] is created at the
  * beginning of [performGesture], and disposed at the end of that method. If there is still a
  * [gesture going on][isGestureInProgress] when the dispatcher is disposed, the state of the
@@ -66,27 +71,18 @@
          */
         var eventPeriodMillis = 10L
             internal set
-
-        /**
-         * Indicates that [nextDownTime] is not set
-         */
-        private const val DownTimeNotSet = -1L
     }
 
     /**
-     * The down time of the next gesture, if a gesture will follow the one that is currently in
-     * progress. If [DownTimeNotSet], the down time will be set to the current time when the
-     * first down event is enqueued for the next gesture. It will only be [DownTimeNotSet] if
-     * this is the first of one or more chained gestures, which is the usual case. A chained
-     * gesture is for example a double click, which consists of a click, a delay and another
-     * click.
+     * The eventTime of the next event. If [TimeNotSet], no gesture has been started yet, and
+     * enqueuing anything other than a down event will fail.
      */
-    protected var nextDownTime = DownTimeNotSet
+    protected var currentTime = TimeNotSet
 
     /**
      * The state of the current gesture in progress. If `null`, no gesture is in progress. This
-     * state contains the current position of all pointer ids, the current time of the event, and
-     * whether or not pointers have moved without having enqueued the corresponding move event.
+     * state contains the current position of all pointer ids and whether or not pointers have
+     * moved without having enqueued the corresponding move event.
      */
     protected var partialGesture: PartialGesture? = null
 
@@ -98,14 +94,14 @@
         get() = partialGesture != null
 
     /**
-     * The current time, in the time scale used by gesture events.
+     * The current wall clock time, in the time scale used by gesture events.
      */
     protected abstract val now: Long
 
     init {
         val state = testContext.states.remove(root)
         if (state?.partialGesture != null) {
-            nextDownTime = state.nextDownTime
+            currentTime = state.currentTime
             partialGesture = state.partialGesture
         }
     }
@@ -114,49 +110,36 @@
         if (root != null) {
             testContext.states[root] =
                 InputDispatcherState(
-                    nextDownTime,
+                    currentTime,
                     partialGesture
                 )
         }
     }
 
     /**
-     * Generates the downTime of the next gesture with the given [durationMillis]. The gesture's
-     * [durationMillis] is necessary to facilitate chaining of gestures: if another gesture is made
-     * after the next one, it will start exactly [durationMillis] after the start of the next
-     * gesture. Always use this method to determine the downTime of the [down event][enqueueDown]
-     * of a gesture.
+     * Returns the time to use for the next downTime. If no event has been enqueued yet, will
+     * return the current wall clock time. Otherwise, will return the
+     * [current eventTime][currentTime].
      *
-     * If the duration is unknown when calling this method, use a duration of zero and update
-     * with [moveNextDownTime] when the duration is known, or use [moveNextDownTime]
-     * incrementally if the gesture unfolds gradually.
+     * Call [increaseEventTime] each time the gesture progresses forward in time to make sure the
+     * current eventTime stays accurate.
      */
-    private fun generateDownTime(durationMillis: Long): Long {
-        val downTime = if (nextDownTime == DownTimeNotSet) {
-            now
+    private fun generateDownTime(): Long {
+        return if (currentTime == TimeNotSet) {
+            now.also { currentTime = it }
         } else {
-            nextDownTime
+            currentTime
         }
-        nextDownTime = downTime + durationMillis
-        return downTime
     }
 
     /**
-     * Moves the start time of the next gesture ahead by the given [durationMillis]. Does not affect
-     * any event time from the current gesture. Use this when the expected duration passed to
-     * [generateDownTime] has changed.
+     * Moves the eventTime for the next event ahead by the given [durationMillis].
      */
-    private fun moveNextDownTime(durationMillis: Long) {
-        generateDownTime(durationMillis)
-    }
-
-    /**
-     * Increases the eventTime with the given [time]. Also pushes the downTime for the next
-     * chained gesture by the same amount to facilitate chaining.
-     */
-    private fun PartialGesture.increaseEventTime(time: Long = eventPeriodMillis) {
-        moveNextDownTime(time)
-        lastEventTime += time
+    private fun increaseEventTime(durationMillis: Long) {
+        check(currentTime != TimeNotSet) {
+            "Can't adjust current event time when no gesture is in progress."
+        }
+        currentTime += durationMillis
     }
 
     /**
@@ -313,15 +296,10 @@
     }
 
     /**
-     * Adds a delay between the end of the last full or current partial gesture of the given
-     * [durationMillis]. Guarantees that the first event time of the next gesture will be exactly
-     * [durationMillis] later then if that gesture would be injected without this delay, provided
-     * that the next gesture is started using the same [InputDispatcher] instance as the one used to
-     * end the last gesture.
-     *
-     * Note: this does not affect the time of the next event for the _current_ partial gesture,
-     * using [enqueueMove], [enqueueUp] and [enqueueCancel], but it will affect the time of the
-     * _next_ gesture (including partial gestures started with [enqueueDown]).
+     * Adds an extra delay of [durationMillis] between the last and the next event. The delay is
+     * added on top of the delay that would already be added between the two events. The normal
+     * delay depends on the type of the next event: for [enqueueMove] and [enqueueCancel] move
+     * the eventTime by 10ms, and all other methods don't move the eventTime.
      *
      * @param durationMillis The duration of the delay. Must be positive
      */
@@ -329,7 +307,7 @@
         require(durationMillis >= 0) {
             "duration of a delay can only be positive, not $durationMillis"
         }
-        moveNextDownTime(durationMillis)
+        increaseEventTime(durationMillis)
     }
 
     /**
@@ -339,8 +317,8 @@
      * is enqueued in this [InputDispatcher] and will be sent when [sendAllSynchronous] is called
      * at the end of [performGesture].
      *
-     * It is possible to mix partial gestures with full gestures (e.g. generate a [click]
-     * [enqueueClick] during a partial gesture), as long as you make sure that the default
+     * It is possible to mix partial gestures with full gestures (e.g. generate a
+     * [click][enqueueClick] during a partial gesture), as long as you make sure that the default
      * pointer id (id=0) is free to be used by the full gesture.
      *
      * A full gesture starts with a down event at some position (with this method) that indicates
@@ -379,7 +357,7 @@
 
         // Start a new gesture, or add the pointerId to the existing gesture
         if (gesture == null) {
-            gesture = PartialGesture(generateDownTime(0), position, pointerId)
+            gesture = PartialGesture(generateDownTime(), position, pointerId)
             partialGesture = gesture
         } else {
             gesture.lastPositions[pointerId] = position
@@ -409,7 +387,7 @@
             "Cannot send MOVE event with a delay of $delay ms"
         }
 
-        gesture.increaseEventTime(delay)
+        increaseEventTime(delay)
         gesture.enqueueMove()
         gesture.hasPointerUpdates = false
     }
@@ -477,7 +455,7 @@
         }
 
         gesture.flushPointerUpdates()
-        gesture.increaseEventTime(delay)
+        increaseEventTime(delay)
 
         // First send the UP event
         gesture.enqueueUp(pointerId)
@@ -512,14 +490,13 @@
             "Cannot send CANCEL event with a delay of $delay ms"
         }
 
-        gesture.increaseEventTime(delay)
+        increaseEventTime(delay)
         gesture.enqueueCancel()
         partialGesture = null
     }
 
     /**
-     * Sends all enqueued events and blocks while they are dispatched. Will suspend before
-     * dispatching an event until [now] is at least that event's timestamp. If an exception is
+     * Sends all enqueued events and blocks while they are dispatched. If an exception is
      * thrown during the process, all events that haven't yet been dispatched will be dropped.
      */
     abstract fun sendAllSynchronous()
@@ -555,14 +532,15 @@
 
 /**
  * The state of the current gesture. Contains the current position of all pointers and the
- * current time of the gesture.
+ * down time (start time) of the gesture. Does not contain the
+ * [current time][InputDispatcher.currentTime], as the current time's lifecycle can span multiple
+ * (chained) gestures.
  *
  * @param downTime The time of the first down event of this gesture
  * @param startPosition The position of the first down event of this gesture
  * @param pointerId The pointer id of the first down event of this gesture
  */
 internal class PartialGesture(val downTime: Long, startPosition: Offset, pointerId: Int) {
-    var lastEventTime: Long = downTime
     val lastPositions = mutableMapOf(Pair(pointerId, startPosition))
     var hasPointerUpdates: Boolean = false
 }
@@ -571,13 +549,14 @@
  * The state of an [InputDispatcher], saved when the [GestureScope] is disposed and restored
  * when the [GestureScope] is recreated.
  *
- * @param nextDownTime The downTime of the start of the next gesture, when chaining gestures.
- * This property will only be restored if an incomplete gesture was in progress when the
- * state of the [InputDispatcher] was saved.
+ * @param currentTime The current event time. Usually this is when the last event was injected,
+ * unless [InputDispatcher.enqueueDelay] has been used after the last event. This property will
+ * only be restored if an incomplete gesture was in progress when the state of the
+ * [InputDispatcher] was saved.
  * @param partialGesture The state of an incomplete gesture. If no gesture was in progress
  * when the state of the [InputDispatcher] was saved, this will be `null`.
  */
 internal data class InputDispatcherState(
-    val nextDownTime: Long,
+    val currentTime: Long,
     val partialGesture: PartialGesture?
 )
diff --git a/compose/ui/ui-test/src/desktopMain/kotlin/androidx/compose/ui/test/DesktopInputDispatcher.desktop.kt b/compose/ui/ui-test/src/desktopMain/kotlin/androidx/compose/ui/test/DesktopInputDispatcher.desktop.kt
index d7941b3..020c3ee 100644
--- a/compose/ui/ui-test/src/desktopMain/kotlin/androidx/compose/ui/test/DesktopInputDispatcher.desktop.kt
+++ b/compose/ui/ui-test/src/desktopMain/kotlin/androidx/compose/ui/test/DesktopInputDispatcher.desktop.kt
@@ -65,7 +65,7 @@
     }
 
     private fun PartialGesture.pointerInputEvent(down: Boolean): List<TestPointerInputEventData> {
-        val time = lastEventTime
+        val time = currentTime
         val offset = lastPositions[lastPositions.keys.sorted()[0]]!!
         val event = listOf(
             TestPointerInputEventData(