[go: nahoru, domu]

Implement cancel for the remainder of
the gesture detectors.

Bug: 146581189
Test: ./gradlew ui:ui-framework:test
Test: ./gradlew ui:ui-core:test
Test: ./gradlew ui:ui-framework:connectedCheck
Test: ./gradlew ui:ui-core:connectedCheck

Change-Id: I349f477ad8112046e916e23d00f8ea2ad850ed11
diff --git a/ui/ui-framework/src/androidTest/java/androidx/ui/core/gesture/LongPressDragGestureDetectorTest.kt b/ui/ui-framework/src/androidTest/java/androidx/ui/core/gesture/LongPressDragGestureDetectorTest.kt
index 8335953..cc272a8 100644
--- a/ui/ui-framework/src/androidTest/java/androidx/ui/core/gesture/LongPressDragGestureDetectorTest.kt
+++ b/ui/ui-framework/src/androidTest/java/androidx/ui/core/gesture/LongPressDragGestureDetectorTest.kt
@@ -18,11 +18,17 @@
 import android.view.MotionEvent
 import android.view.View
 import android.view.ViewGroup
+import androidx.test.filters.LargeTest
 import androidx.test.rule.ActivityTestRule
+import androidx.ui.core.Layout
+import androidx.ui.core.PxPosition
 import androidx.ui.core.ipx
 import androidx.ui.core.setContent
 import androidx.ui.framework.test.TestActivity
 import com.nhaarman.mockitokotlin2.any
+import com.nhaarman.mockitokotlin2.inOrder
+import com.nhaarman.mockitokotlin2.spy
+import com.nhaarman.mockitokotlin2.times
 import com.nhaarman.mockitokotlin2.verify
 import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions
 import org.junit.Assert.assertTrue
@@ -33,12 +39,10 @@
 import org.junit.runners.JUnit4
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.TimeUnit
-import androidx.ui.core.PxPosition
-import com.nhaarman.mockitokotlin2.reset
-import com.nhaarman.mockitokotlin2.spy
-import com.nhaarman.mockitokotlin2.times
-import androidx.test.filters.LargeTest
-import androidx.ui.core.Layout
+
+// TODO(shepshapard): Figure out how to test composite gesture detectors better.  There should be
+//  more tests, but hopefully they can also be easier to write then what is currently available.
+//  Should possibly wait till we have host side testing that does not require the Android runtime.
 
 @LargeTest
 @RunWith(JUnit4::class)
@@ -78,8 +82,6 @@
         assertTrue(setupLatch.await(1000, TimeUnit.SECONDS))
     }
 
-    // Tests that verify conditions under which nothing will be called.
-
     @Test
     fun ui_downMoveUpBeforeLongPressTimeout_noCallbacksCalled() {
 
@@ -116,10 +118,44 @@
         verifyNoMoreInteractions(longPressDragObserver)
     }
 
-    // Tests that verify conditions under which onLongPress will only be called.
+    @Test
+    fun ui_downMoveCancelBeforeLongPressTimeout_noCallbacksCalled() {
+
+        val down = MotionEvent(
+            0,
+            MotionEvent.ACTION_DOWN,
+            1,
+            0,
+            arrayOf(PointerProperties(0)),
+            arrayOf(PointerCoords(50f, 50f))
+        )
+        val move = MotionEvent(
+            0,
+            MotionEvent.ACTION_MOVE,
+            1,
+            0,
+            arrayOf(PointerProperties(0)),
+            arrayOf(PointerCoords(51f, 50f))
+        )
+        val cancel = MotionEvent(
+            0,
+            MotionEvent.ACTION_CANCEL,
+            1,
+            0,
+            arrayOf(PointerProperties(0)),
+            arrayOf(PointerCoords(51f, 50f))
+        )
+        activityTestRule.runOnUiThreadIR {
+            view.dispatchTouchEvent(down)
+            view.dispatchTouchEvent(move)
+            view.dispatchTouchEvent(cancel)
+        }
+
+        verifyNoMoreInteractions(longPressDragObserver)
+    }
 
     @Test
-    fun ui_downWaitForLongPress_onLongPressCalled() {
+    fun ui_downWaitForLongPress_onlyOnLongPressCalled() {
 
         val down = MotionEvent(
             0,
@@ -137,49 +173,9 @@
         verifyNoMoreInteractions(longPressDragObserver)
     }
 
-    // Tests that verify conditions under which onDragStart and onDrag will be called.
-
     @Test
-    fun ui_downWaitForLongPressMove_onDragStartAndOnDragCalled() {
+    fun ui_downWaitForLongPressMove_callbacksCorrect() {
 
-        // Arrange.
-        val down = MotionEvent(
-            0,
-            MotionEvent.ACTION_DOWN,
-            1,
-            0,
-            arrayOf(PointerProperties(0)),
-            arrayOf(PointerCoords(50f, 50f))
-        )
-        waitForLongPress {
-            view.dispatchTouchEvent(down)
-        }
-        reset(longPressDragObserver)
-
-        // Act.
-        val move = MotionEvent(
-            0,
-            MotionEvent.ACTION_MOVE,
-            1,
-            0,
-            arrayOf(PointerProperties(0)),
-            arrayOf(PointerCoords(51f, 50f))
-        )
-        view.dispatchTouchEvent(move)
-
-        // Assert.
-        verify(longPressDragObserver).onDragStart()
-        // Twice because DragGestureDetector dispatches onDrag during 2 passes.
-        verify(longPressDragObserver, times(2)).onDrag(any())
-        verifyNoMoreInteractions(longPressDragObserver)
-    }
-
-    // Tests that verify conditions under which onStop will be called.
-
-    @Test
-    fun ui_downWaitForLongPressMoveUp_onDragStopCalled() {
-
-        // Arrange.
         val down = MotionEvent(
             0,
             MotionEvent.ACTION_DOWN,
@@ -200,28 +196,19 @@
             arrayOf(PointerCoords(51f, 50f))
         )
         view.dispatchTouchEvent(move)
-        reset(longPressDragObserver)
 
-        // Act.
-        val up = MotionEvent(
-            0,
-            MotionEvent.ACTION_UP,
-            1,
-            0,
-            arrayOf(PointerProperties(0)),
-            arrayOf(PointerCoords(51f, 50f))
-        )
-        view.dispatchTouchEvent(up)
-
-        // Assert.
-        verify(longPressDragObserver).onStop(any())
+        inOrder(longPressDragObserver) {
+            verify(longPressDragObserver).onLongPress(any())
+            verify(longPressDragObserver).onDragStart()
+            // Twice because DragGestureDetector dispatches onDrag during 2 passes.
+            verify(longPressDragObserver, times(2)).onDrag(any())
+        }
         verifyNoMoreInteractions(longPressDragObserver)
     }
 
     @Test
-    fun ui_downWaitForLongPressUp_onDragStopCalled() {
+    fun ui_downWaitForLongPressUp_callbacksCorrect() {
 
-        // Arrange.
         val down = MotionEvent(
             0,
             MotionEvent.ACTION_DOWN,
@@ -233,9 +220,6 @@
         waitForLongPress {
             view.dispatchTouchEvent(down)
         }
-        reset(longPressDragObserver)
-
-        // Act.
         val up = MotionEvent(
             0,
             MotionEvent.ACTION_UP,
@@ -247,7 +231,127 @@
         view.dispatchTouchEvent(up)
 
         // Assert.
-        verify(longPressDragObserver).onStop(any())
+        inOrder(longPressDragObserver) {
+            verify(longPressDragObserver).onLongPress(any())
+            verify(longPressDragObserver).onStop(any())
+        }
+        verifyNoMoreInteractions(longPressDragObserver)
+    }
+
+    @Test
+    fun ui_downWaitForLongPressMoveUp_callbacksCorrect() {
+
+        val down = MotionEvent(
+            0,
+            MotionEvent.ACTION_DOWN,
+            1,
+            0,
+            arrayOf(PointerProperties(0)),
+            arrayOf(PointerCoords(50f, 50f))
+        )
+        waitForLongPress {
+            view.dispatchTouchEvent(down)
+        }
+        val move = MotionEvent(
+            0,
+            MotionEvent.ACTION_MOVE,
+            1,
+            0,
+            arrayOf(PointerProperties(0)),
+            arrayOf(PointerCoords(51f, 50f))
+        )
+        view.dispatchTouchEvent(move)
+        val up = MotionEvent(
+            0,
+            MotionEvent.ACTION_UP,
+            1,
+            0,
+            arrayOf(PointerProperties(0)),
+            arrayOf(PointerCoords(51f, 50f))
+        )
+        view.dispatchTouchEvent(up)
+
+        inOrder(longPressDragObserver) {
+            verify(longPressDragObserver).onLongPress(any())
+            verify(longPressDragObserver).onDragStart()
+            // Twice because DragGestureDetector dispatches onDrag during 2 passes.
+            verify(longPressDragObserver, times(2)).onDrag(any())
+            verify(longPressDragObserver).onStop(any())
+        }
+        verifyNoMoreInteractions(longPressDragObserver)
+    }
+
+    @Test
+    fun ui_downWaitForLongPressCancel_callbacksCorrect() {
+
+        val down = MotionEvent(
+            0,
+            MotionEvent.ACTION_DOWN,
+            1,
+            0,
+            arrayOf(PointerProperties(0)),
+            arrayOf(PointerCoords(50f, 50f))
+        )
+        waitForLongPress {
+            view.dispatchTouchEvent(down)
+        }
+        val cancel = MotionEvent(
+            0,
+            MotionEvent.ACTION_CANCEL,
+            1,
+            0,
+            arrayOf(PointerProperties(0)),
+            arrayOf(PointerCoords(50f, 50f))
+        )
+        view.dispatchTouchEvent(cancel)
+
+        inOrder(longPressDragObserver) {
+            verify(longPressDragObserver).onLongPress(any())
+            verify(longPressDragObserver).onCancel()
+        }
+        verifyNoMoreInteractions(longPressDragObserver)
+    }
+
+    @Test
+    fun ui_downWaitForLongPressMoveCancel_callbacksCorrect() {
+
+        val down = MotionEvent(
+            0,
+            MotionEvent.ACTION_DOWN,
+            1,
+            0,
+            arrayOf(PointerProperties(0)),
+            arrayOf(PointerCoords(50f, 50f))
+        )
+        waitForLongPress {
+            view.dispatchTouchEvent(down)
+        }
+        val move = MotionEvent(
+            0,
+            MotionEvent.ACTION_MOVE,
+            1,
+            0,
+            arrayOf(PointerProperties(0)),
+            arrayOf(PointerCoords(51f, 50f))
+        )
+        view.dispatchTouchEvent(move)
+        val cancel = MotionEvent(
+            0,
+            MotionEvent.ACTION_CANCEL,
+            1,
+            0,
+            arrayOf(PointerProperties(0)),
+            arrayOf(PointerCoords(51f, 50f))
+        )
+        view.dispatchTouchEvent(cancel)
+
+        inOrder(longPressDragObserver) {
+            verify(longPressDragObserver).onLongPress(any())
+            verify(longPressDragObserver).onDragStart()
+            // Twice because DragGestureDetector dispatches onDrag during 2 passes.
+            verify(longPressDragObserver, times(2)).onDrag(any())
+            verify(longPressDragObserver).onCancel()
+        }
         verifyNoMoreInteractions(longPressDragObserver)
     }
 
@@ -264,11 +368,11 @@
         onLongPress()
     }
 
-    override fun onDragStart() { }
+    override fun onDragStart() {}
 
     override fun onDrag(dragDistance: PxPosition): PxPosition {
         return super.onDrag(dragDistance)
     }
 
-    override fun onStop(velocity: PxPosition) { }
+    override fun onStop(velocity: PxPosition) {}
 }
\ No newline at end of file
diff --git a/ui/ui-framework/src/androidTest/java/androidx/ui/core/gesture/PressGestureDetectorTest.kt b/ui/ui-framework/src/androidTest/java/androidx/ui/core/gesture/PressGestureDetectorTest.kt
index aeb2396..0bd52ae 100644
--- a/ui/ui-framework/src/androidTest/java/androidx/ui/core/gesture/PressGestureDetectorTest.kt
+++ b/ui/ui-framework/src/androidTest/java/androidx/ui/core/gesture/PressGestureDetectorTest.kt
@@ -166,6 +166,36 @@
         verify(onRelease, never()).invoke()
     }
 
+    // Verify when onCancel should be called.
+
+    @Test
+    fun ui_downCancel_onCancelCalled() {
+        compose()
+
+        val down = MotionEvent(
+            0,
+            MotionEvent.ACTION_DOWN,
+            1,
+            0,
+            arrayOf(PointerProperties(0)),
+            arrayOf(PointerCoords(50f, 50f))
+        )
+        val cancel = MotionEvent(
+            10,
+            MotionEvent.ACTION_CANCEL,
+            1,
+            0,
+            arrayOf(PointerProperties(0)),
+            arrayOf(PointerCoords(50f, 50f))
+        )
+        activityTestRule.runOnUiThreadIR {
+            view.dispatchTouchEvent(down)
+            view.dispatchTouchEvent(cancel)
+        }
+
+        verify(onCancel).invoke()
+    }
+
     private fun compose(enabled: Boolean? = null) {
         val setupLatch = CountDownLatch(2)
         activityTestRule.runOnUiThreadIR {
diff --git a/ui/ui-framework/src/androidTest/java/androidx/ui/core/gesture/ScaleGestureDetectorTest.kt b/ui/ui-framework/src/androidTest/java/androidx/ui/core/gesture/ScaleGestureDetectorTest.kt
index 65b0025..0fd04b3 100644
--- a/ui/ui-framework/src/androidTest/java/androidx/ui/core/gesture/ScaleGestureDetectorTest.kt
+++ b/ui/ui-framework/src/androidTest/java/androidx/ui/core/gesture/ScaleGestureDetectorTest.kt
@@ -141,7 +141,7 @@
     }
 
     @Test
-    fun ui_pointerMovementBeyondTouchSlop_callbacksCalled() {
+    fun ui_pointerMovementBeyondTouchSlop_correctCallbacksInOrder() {
 
         val touchSlopFloat = touchSlop.value.toFloat()
 
@@ -219,6 +219,75 @@
             verify().onStop()
         }
     }
+
+    // Verify when onCancel should be called.
+
+    @Test
+    fun ui_downMoveBeyondSlopCancel_correctCallbacksInOrder() {
+
+        val touchSlopFloat = touchSlop.value.toFloat()
+
+        val down1 = MotionEvent(
+            0,
+            MotionEvent.ACTION_DOWN,
+            1,
+            0,
+            arrayOf(PointerProperties(0)),
+            arrayOf(PointerCoords(touchSlopFloat, 50f))
+        )
+        val down2 = MotionEvent(
+            10,
+            MotionEvent.ACTION_POINTER_DOWN,
+            2,
+            1,
+            arrayOf(PointerProperties(0), PointerProperties(1)),
+            arrayOf(
+                PointerCoords(touchSlopFloat, 50f),
+                PointerCoords(touchSlopFloat * 3, 50f)
+            )
+        )
+        val move = MotionEvent(
+            20,
+            MotionEvent.ACTION_MOVE,
+            2,
+            0,
+            arrayOf(
+                PointerProperties(0),
+                PointerProperties(1)
+            ),
+            arrayOf(
+                PointerCoords(-touchSlopFloat * 2, 50f),
+                PointerCoords(touchSlopFloat * 6, 50f)
+            )
+        )
+        val cancel = MotionEvent(
+            30,
+            MotionEvent.ACTION_CANCEL,
+            2,
+            0,
+            arrayOf(
+                PointerProperties(0),
+                PointerProperties(1)
+            ),
+            arrayOf(
+                PointerCoords(-touchSlopFloat * 2, 50f),
+                PointerCoords(touchSlopFloat * 6, 50f)
+            )
+        )
+
+        activityTestRule.runOnUiThreadIR {
+            view.dispatchTouchEvent(down1)
+            view.dispatchTouchEvent(down2)
+            view.dispatchTouchEvent(move)
+            view.dispatchTouchEvent(cancel)
+        }
+
+        scaleObserver.inOrder {
+            verify().onStart()
+            verify().onScale(4f)
+            verify().onCancel()
+        }
+    }
 }
 
 @Suppress("RedundantOverride")
diff --git a/ui/ui-framework/src/androidTest/java/androidx/ui/core/gesture/TouchSlopDragGestureDetectorTest.kt b/ui/ui-framework/src/androidTest/java/androidx/ui/core/gesture/TouchSlopDragGestureDetectorTest.kt
index e46f8bb..151bea9 100644
--- a/ui/ui-framework/src/androidTest/java/androidx/ui/core/gesture/TouchSlopDragGestureDetectorTest.kt
+++ b/ui/ui-framework/src/androidTest/java/androidx/ui/core/gesture/TouchSlopDragGestureDetectorTest.kt
@@ -93,7 +93,7 @@
     }
 
     @Test
-    fun ui_pointerMovementBeyondTouchSlop_callbacksCalled() {
+    fun ui_pointerDownMovementBeyondTouchSlopUp_correctCallbacksInOrder() {
         setup(false)
 
         val touchSlopFloat = touchSlop.value.toFloat()
@@ -140,6 +140,53 @@
     }
 
     @Test
+    fun ui_pointerDownMovementBeyondTouchSlopCancel_correctCallbacksInOrder() {
+        setup(false)
+
+        val touchSlopFloat = touchSlop.value.toFloat()
+
+        val down = MotionEvent(
+            0,
+            MotionEvent.ACTION_DOWN,
+            1,
+            0,
+            arrayOf(PointerProperties(0)),
+            arrayOf(PointerCoords(50f, 50f))
+        )
+        val move = MotionEvent(
+            20,
+            MotionEvent.ACTION_MOVE,
+            1,
+            0,
+            arrayOf(PointerProperties(0)),
+            arrayOf(PointerCoords(50f + touchSlopFloat + 1, 50f))
+        )
+        val cancel = MotionEvent(
+            30,
+            MotionEvent.ACTION_CANCEL,
+            1,
+            0,
+            arrayOf(PointerProperties(0)),
+            arrayOf(PointerCoords(50f + touchSlopFloat, 50f))
+        )
+
+        activityTestRule.runOnUiThreadIR {
+            view.dispatchTouchEvent(down)
+            view.dispatchTouchEvent(move)
+            view.dispatchTouchEvent(cancel)
+        }
+
+        dragObserver.inOrder {
+            verify().onStart(PxPosition(50.ipx, 50.ipx))
+            // Twice because RawDragGestureDetector calls the callback on both postUp and postDown
+            // and nothing consumes the drag distance.
+            verify(dragObserver, times(2))
+                .onDrag(PxPosition(Px(touchSlopFloat + 1), 0.px))
+            verify().onCancel()
+        }
+    }
+
+    @Test
     fun ui_startDragImmediatelyTrueDown_callbacksCalled() {
         setup(true)
 
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/PointerInputWrapper.kt b/ui/ui-framework/src/main/java/androidx/ui/core/PointerInputWrapper.kt
index b22141a..ff8223a 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/PointerInputWrapper.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/PointerInputWrapper.kt
@@ -20,8 +20,8 @@
 // TODO: Rename
 @Composable
 inline fun PointerInputWrapper(
-    noinline pointerInputHandler: PointerInputHandler = { event, _, _ -> event },
-    noinline cancelHandler: () -> Unit = {},
+    noinline pointerInputHandler: PointerInputHandler,
+    noinline cancelHandler: () -> Unit,
     crossinline children: @Composable() () -> Unit
 ) {
     // Hide the internals of PointerInputNode
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/DoubleTapGestureDetector.kt b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/DoubleTapGestureDetector.kt
index 83366a0..e86317d 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/DoubleTapGestureDetector.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/DoubleTapGestureDetector.kt
@@ -16,19 +16,18 @@
 
 package androidx.ui.core.gesture
 
-import androidx.ui.core.PointerEventPass
-import androidx.ui.core.PointerInputChange
-import androidx.ui.core.changedToDown
 import androidx.compose.Composable
 import androidx.compose.ambient
 import androidx.compose.remember
-import androidx.ui.core.changedToUp
 import androidx.ui.core.CoroutineContextAmbient
 import androidx.ui.core.IntPxSize
+import androidx.ui.core.PointerEventPass
+import androidx.ui.core.PointerInputChange
 import androidx.ui.core.PointerInputWrapper
 import androidx.ui.core.PxPosition
 import androidx.ui.core.anyPositionChangeConsumed
-import androidx.ui.core.changedToUpIgnoreConsumed
+import androidx.ui.core.changedToDown
+import androidx.ui.core.changedToUp
 import androidx.ui.core.consumeDownChange
 import androidx.ui.core.gesture.util.anyPointersInBounds
 import androidx.ui.temputils.delay
@@ -40,7 +39,8 @@
 // TODO(b/138754591): The behavior of this gesture detector needs to be finalized.
 // TODO(b/139020678): Probably has shared functionality with other press based detectors.
 /**
- * Responds to pointers going up, down within a small duration, and then up again.
+ * Responds to pointers going down and up (tap) and then down and up again (another tap)
+ * with minimal gap of time between the first up and the second down.
  *
  * Note: This is a temporary implementation to unblock dependents.  Once the underlying API that
  * allows double tap to temporarily block tap from firing is complete, this gesture detector will
@@ -55,11 +55,14 @@
     children: @Composable() () -> Unit
 ) {
     val coroutineContext = ambient(CoroutineContextAmbient)
-    val recognizer =
-        remember { DoubleTapGestureRecognizer(coroutineContext) }
+    val recognizer = remember { DoubleTapGestureRecognizer(coroutineContext) }
     recognizer.>
 
-    PointerInputWrapper(pointerInputHandler = recognizer.pointerInputHandler, children = children)
+    PointerInputWrapper(
+        pointerInputHandler = recognizer.pointerInputHandler,
+        cancelHandler = recognizer.cancelHandler,
+        children = children
+    )
 }
 
 internal class DoubleTapGestureRecognizer(
@@ -68,7 +71,7 @@
     lateinit var onDoubleTap: (PxPosition) -> Unit
 
     private enum class State {
-        Idle, Down, Up, SecondDown, Cancelled
+        Idle, Down, Up, SecondDown
     }
 
     var doubleTapTimeout = DoubleTapTimeout
@@ -95,24 +98,26 @@
                     changesToReturn = changesToReturn.map { it.consumeDownChange() }
                     state = State.Idle
                     onDoubleTap.invoke(changes[0].previous.position!!)
-                } else if (state == State.Cancelled &&
-                    changesToReturn.all { it.changedToUpIgnoreConsumed() }
-                ) {
-                    state = State.Idle
                 } else if ((state == State.Down || state == State.SecondDown) &&
-                    !changesToReturn.anyPointersInBounds(bounds)) {
+                    !changesToReturn.anyPointersInBounds(bounds)
+                ) {
                     // If we are in one of the down states, and none of pointers are in our bounds,
                     // then we should cancel and wait till we can be Idle again.
-                    state = State.Cancelled
+                    state = State.Idle
                 }
             }
 
             if (pass == PointerEventPass.PostDown &&
                 changesToReturn.any { it.anyPositionChangeConsumed() }
             ) {
-                state = State.Cancelled
+                state = State.Idle
             }
 
             changesToReturn
         }
+
+    var cancelHandler = {
+        job?.cancel()
+        state = State.Idle
+    }
 }
\ No newline at end of file
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/LongPressDragGestureDetector.kt b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/LongPressDragGestureDetector.kt
index 1b36cd9..415139b 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/LongPressDragGestureDetector.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/LongPressDragGestureDetector.kt
@@ -18,12 +18,12 @@
 
 import androidx.compose.Composable
 import androidx.compose.remember
-import androidx.ui.core.PxPosition
 import androidx.ui.core.IntPxSize
 import androidx.ui.core.PointerEventPass
 import androidx.ui.core.PointerInputChange
 import androidx.ui.core.PointerInputHandler
 import androidx.ui.core.PointerInputWrapper
+import androidx.ui.core.PxPosition
 import androidx.ui.core.changedToUpIgnoreConsumed
 
 interface LongPressDragObserver {
@@ -64,12 +64,13 @@
      *
      * When overridden, return the amount of the [dragDistance] that has been consumed.
      *
-     * Called after [onDragStart] and for every subsequent pointer movement, as long as the movement was enough to
-     * constitute a drag (the average movement on the x or y axis is not equal to 0).  This may be called
-     * synchronously, immediately afterward [onDragStart], as a result of the same pointer input event.
+     * Called after [onDragStart] and for every subsequent pointer movement, as long as the movement
+     * was enough to constitute a drag (the average movement on the x or y axis is not equal to
+     * 0).  This may be called synchronously, immediately afterward [onDragStart], as a result of
+     * the same pointer input event.
      *
-     * Note: This will be called multiple times for a single pointer input event and the values provided in each call
-     * should be considered unique.
+     * Note: This will be called multiple times for a single pointer input event and the values
+     * provided in each call should be considered unique.
      *
      * @param dragDistance The distance that has been dragged.  Reflects the average drag distance
      * of all pointers.
@@ -79,21 +80,35 @@
     /**
      * Override to be notified when a drag has stopped.
      *
-     * This is called once all pointers have stopped interacting with this DragGestureDetector and [onLongPress] was
-     * previously called.
+     * This is called once all pointers have stopped interacting with this DragGestureDetector and
+     * [onLongPress] was previously called.
      */
     fun onStop(velocity: PxPosition) {}
+
+    /**
+     * Override to be notified when the drag has been cancelled.
+     *
+     * This is called if [onLongPress] has ben called and then a cancellation event has occurs
+     * (for example, due to the gesture detector being removed from the tree) before [onStop] is
+     * called.
+     */
+    fun onCancel() {}
 }
 
 /**
- * This gesture detector detects dragging in any direction, but only after a long press has first occurred.
+ * This gesture detector detects dragging in any direction, but only after a long press has first
+ * occurred.
  *
  * Dragging begins once a long press has occurred and then dragging occurs.  When long press occurs,
- * [LongPressDragObserver.onLongPress] is called. Once dragging has occurred, [LongPressDragObserver.onDragStart]
- * will be called. [LongPressDragObserver.onDrag] is then continuously called whenever pointer movement results
- * in a drag have moved. [LongPressDragObserver.onStop] is called when the dragging ends due to all of
- * the pointers no longer interacting with the LongPressDragGestureDetector (for example, the last
- * finger has been lifted off of the LongPressDragGestureDetector).
+ * [LongPressDragObserver.onLongPress] is called. Once dragging has occurred,
+ * [LongPressDragObserver.onDragStart] will be called. [LongPressDragObserver.onDrag] is then
+ * continuously called whenever pointer movement results in a drag. The gesture will end
+ * with either a call to [LongPressDragObserver.onStop] or [LongPressDragObserver.onCancel]. Either
+ * will be called after [LongPressDragObserver.onLongPress] is called.
+ * [LongPressDragObserver.onStop] is called when the the gesture ends due to all of the pointers
+ * no longer interacting with the LongPressDragGestureDetector (for example, the last finger has
+ * been lifted off of the LongPressDragGestureDetector). [LongPressDragObserver.onCancel] is
+ * called in response to a system cancellation event.
  *
  * When multiple pointers are touching the detector, the drag distance is taken as the average of
  * all of the pointers.
@@ -110,7 +125,7 @@
     glue.longPressDragObserver = longPressDragObserver
 
     RawDragGestureDetector(glue.dragObserver, glue::dragEnabled) {
-        PointerInputWrapper(glue.pointerInputHandler) {
+        PointerInputWrapper(glue.pointerInputHandler, glue.cancelHandler) {
             LongPressGestureDetector(glue.onLongPress, children)
         }
     }
@@ -143,8 +158,16 @@
                 dragStarted = false
                 longPressDragObserver.onStop(velocity)
             }
+
+            override fun onCancel() {
+                dragEnabled = false
+                dragStarted = false
+                longPressDragObserver.onCancel()
+            }
         }
 
+    // This handler ensures that onStop will be called after long press happened, but before
+    // dragging actually has begun.
     val pointerInputHandler =
         { changes: List<PointerInputChange>, pass: PointerEventPass, _: IntPxSize ->
             if (pass == PointerEventPass.PostUp &&
@@ -158,6 +181,15 @@
             changes
         }
 
+    // This handler ensures that onCancel is called if onLongPress was previously called but
+    // dragging has not yet started.
+    val cancelHandler = {
+        if (dragEnabled && !dragStarted) {
+            dragEnabled = false
+            longPressDragObserver.onCancel()
+        }
+    }
+
     val  pxPosition: PxPosition ->
         dragEnabled = true
         longPressDragObserver.onLongPress(pxPosition)
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/LongPressGestureDetector.kt b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/LongPressGestureDetector.kt
index 22e8944..f990732 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/LongPressGestureDetector.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/LongPressGestureDetector.kt
@@ -55,7 +55,11 @@
         remember { LongPressGestureRecognizer(coroutineContext) }
     recognizer.>
 
-    PointerInputWrapper(pointerInputHandler = recognizer.pointerInputHandler, children = children)
+    PointerInputWrapper(
+        pointerInputHandler = recognizer.pointerInputHandler,
+        cancelHandler = recognizer.cancelHandler,
+        children = children
+    )
 }
 
 internal class LongPressGestureRecognizer(
@@ -101,16 +105,16 @@
                     state = State.Primed
                 } else if (state != State.Idle && changes.all { it.changedToUpIgnoreConsumed() }) {
                     // If we have started and all of the changes changed to up, we are stopping.
-                    reset()
+                    cancelHandler()
                 } else if (!changesToReturn.anyPointersInBounds(bounds)) {
                     // If none of the pointers are in bounds of our bounds, we should reset and wait
                     // till all pointers are changing to down to "prime" again.
-                    reset()
+                    cancelHandler()
                 }
 
                 if (state == State.Primed) {
-                    // If we are primed, for all down pointers, keep track of their current positions, and for all
-                    // other pointers, remove their tracked information.
+                    // If we are primed, for all down pointers, keep track of their current
+                    // positions, and for all other pointers, remove their tracked information.
                     changes.forEach {
                         if (it.current.down) {
                             pointerPositions[it.id] = it.current.position!!
@@ -128,13 +132,13 @@
                 // If we are primed, reset so we don't fire.
                 // If we are fired, reset to idle so we don't block up events that still fire after
                 // dragging (like flinging).
-                reset()
+                cancelHandler()
             }
 
             changesToReturn
         }
 
-    private fun reset() {
+    val cancelHandler = {
         job?.cancel()
         state = State.Idle
     }
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/PressReleasedGestureDetector.kt b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/PressReleasedGestureDetector.kt
index cded35a..3fc5b82 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/PressReleasedGestureDetector.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/PressReleasedGestureDetector.kt
@@ -60,7 +60,11 @@
     recognizer.consumeDownOnStart = consumeDownOnStart
     recognizer.setEnabled(enabled)
 
-    PointerInputWrapper(pointerInputHandler = recognizer.pointerInputHandler, children = children)
+    PointerInputWrapper(
+        pointerInputHandler = recognizer.pointerInputHandler,
+        cancelHandler = recognizer.cancelHandler,
+        children = children
+    )
 }
 
 internal class PressReleaseGestureRecognizer {
@@ -108,7 +112,7 @@
                 } else if (!internalChanges.anyPointersInBounds(bounds)) {
                     // If none of the pointers are in bounds of our bounds, we should reset and wait
                     // till all pointers are changing to down.
-                    active = false
+                    cancelHandler()
                 }
 
                 if (active && consumeDownOnStart) {
@@ -122,9 +126,13 @@
             ) {
                 // On the final pass, if we have started and any of the changes had consumed
                 // position changes, we cancel.
-                active = false
+                cancelHandler()
             }
 
             internalChanges
         }
+
+    val cancelHandler = {
+        active = false
+    }
 }
\ No newline at end of file
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/RawDragGestureDetector.kt b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/RawDragGestureDetector.kt
index 46739d8..7a4b165 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/RawDragGestureDetector.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/RawDragGestureDetector.kt
@@ -74,6 +74,15 @@
      * Only called if the last call between [onStart] and [onStop] was [onStart].
      */
     fun onStop(velocity: PxPosition) {}
+
+    /**
+     * Override to be notified when the drag has been cancelled.
+     *
+     * This is called if [onStart] has ben called and then a cancellation event has occurs
+     * (for example, due to the gesture detector being removed from the tree) before [onStop] is
+     * called.
+     */
+    fun onCancel() {}
 }
 
 // TODO(shepshapard): Convert to functional component with effects once effects are ready.
@@ -93,9 +102,12 @@
  * Dragging begins when the a single pointer has moved and either [canStartDragging] is null or
  * returns true.  When dragging begins, [DragObserver.onStart] is called.  [DragObserver.onDrag] is
  * then continuously called whenever the average movement of all pointers has movement along the x
- * or y axis.  [DragObserver.onStop] is called when the dragging ends due to all of the pointers no
- * longer interacting with the DragGestureDetector (for example, the last pointer has been lifted
- * off of the DragGestureDetector).
+ * or y axis.  The gesture ends with either a call to [DragObserver.onStop] or
+ * [DragObserver.onCancel], only after [DragObserver.onStart] is called. [DragObserver.onStop] is
+ * called when the dragging ends due to all of the pointers no longer interacting with the
+ * DragGestureDetector (for example, the last pointer has been lifted off of the
+ * DragGestureDetector). [DragObserver.onCancel] is called when the dragging ends due to a system
+ * cancellation event.
  *
  * When multiple pointers are touching the detector, the drag distance is taken as the average of
  * all of the pointers.
@@ -120,7 +132,11 @@
     recognizer.dragObserver = dragObserver
     recognizer.canStartDragging = canStartDragging
 
-    PointerInputWrapper(pointerInputHandler = recognizer.pointerInputHandler, children = children)
+    PointerInputWrapper(
+        pointerInputHandler = recognizer.pointerInputHandler,
+        cancelHandler = recognizer.cancelHandler,
+        children = children
+    )
 }
 
 internal class RawDragGestureRecognizer {
@@ -288,6 +304,15 @@
 
             changesToReturn
         }
+
+    val cancelHandler = {
+        downPositions.clear()
+        velocityTrackers.clear()
+        if (started) {
+            started = false
+            dragObserver.onCancel()
+        }
+    }
 }
 
 private fun Iterable<PxPosition>.averagePosition(): PxPosition {
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/RawPressStartGestureDetector.kt b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/RawPressStartGestureDetector.kt
index 104b39b..e444f55 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/RawPressStartGestureDetector.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/RawPressStartGestureDetector.kt
@@ -80,14 +80,6 @@
             var internalChanges = changes
 
             if (pass == executionPass) {
-
-                println("testtest, pressStartGestureDetector, internalChanges.size: " +
-                        "${internalChanges.size}")
-                println("testtest, pressStartGestureDetector, internalChanges, down: " +
-                        "${internalChanges.first().changedToDown()}")
-                println("testtest, pressStartGestureDetector, internalChanges, up: " +
-                        "${internalChanges.first().changedToUp()}")
-
                 if (enabled && internalChanges.all { it.changedToDown() }) {
                     // If we have not yet started and all of the changes changed to down, we are
                     // starting.
@@ -112,7 +104,6 @@
     }
 
     fun setEnabled(enabled: Boolean) {
-        println("testtest, setEnabled: $enabled")
         this.enabled = enabled
         // Whenever we are disabled, we can just go ahead and become inactive (which is the state we
         // should be in if we are to pretend that we aren't in the hierarchy.
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/RawScaleGestureDetector.kt b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/RawScaleGestureDetector.kt
index 1c98579..1407108 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/RawScaleGestureDetector.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/RawScaleGestureDetector.kt
@@ -80,6 +80,15 @@
      * Only called after [onStart] and one or more calls to [onScale]
      */
     fun onStop() {}
+
+    /**
+     * Override to be notified when the scale has been cancelled.
+     *
+     * This is called if [onStart] has ben called and then a cancellation event has occurs
+     * (for example, due to the gesture detector being removed from the tree) before [onStop] is
+     * called.
+     */
+    fun onCancel() {}
 }
 
 /**
@@ -98,7 +107,10 @@
  * [canStartScaling] is null or returns true.  When scaling begins, [RawScaleObserver.onStart] is
  * called followed immediately by a call to [RawScaleObserver.onScale].
  * [RawScaleObserver.onScale] is then continuously called whenever the movement of all pointers
- * denotes scaling. [RawScaleObserver.onStop] is called when no pointers remain.
+ * denotes scaling. The gesture stops with either a call to [RawScaleObserver.onStop] or
+ * [RawScaleObserver.onCancel], either of which will only be called if [RawScaleObserver.onStart]
+ * was previously called. [RawScaleObserver.onStop] is called when no pointers remain.
+ * [RawScaleObserver.onCancel] is called due to a system cancellation event.
  *
  * @param scaleObserver The callback interface to report all events related to scaling.
  * @param canStartScaling If set, before scaling is started ([RawScaleObserver.onStart] is called),
@@ -115,7 +127,11 @@
     recognizer.scaleObserver = scaleObserver
     recognizer.canStartScaling = canStartScaling
 
-    PointerInputWrapper(pointerInputHandler = recognizer.pointerInputHandler, children = children)
+    PointerInputWrapper(
+        pointerInputHandler = recognizer.pointerInputHandler,
+        cancelHandler = recognizer.cancelHandler,
+        children = children
+    )
 }
 
 internal class RawScaleGestureRecognizer {
@@ -213,4 +229,11 @@
 
             changesToReturn
         }
+
+    val cancelHandler = {
+        if (active) {
+            scaleObserver.onCancel()
+            active = false
+        }
+    }
 }
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/ScaleGestureDetector.kt b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/ScaleGestureDetector.kt
index 46a7a93..cbf33c2 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/ScaleGestureDetector.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/ScaleGestureDetector.kt
@@ -68,6 +68,15 @@
      * @see onScale
      */
     fun onStop() {}
+
+    /**
+     * Override to be notified when the scale has been cancelled.
+     *
+     * This is called if [onStart] has ben called and then a cancellation event has occurs
+     * (for example, due to the gesture detector being removed from the tree) before [onStop] is
+     * called.
+     */
+    fun onCancel() {}
 }
 
 /**
@@ -78,10 +87,12 @@
  *
  * Scaling begins when the average distance between a set of pointers changes and at least 1 pointer
  * moves beyond the touch slop distance (currently defined by [TouchSlop]).  When scaling begins,
- * [RawScaleObserver.onStart] is called followed immediately by a call to
- * [RawScaleObserver.onScale].  [RawScaleObserver.onScale] is then continuously called whenever the
- * movement of pointers denotes scaling. [RawScaleObserver.onStop] is called when no pointers
- * remain.
+ * [ScaleObserver.onStart] is called followed immediately by a call to [ScaleObserver.onScale].
+ * [ScaleObserver.onScale] is then continuously called whenever the movement of pointers denotes
+ * scaling. The gesture stops with either a call to [RawScaleObserver.onStop] or
+ * [RawScaleObserver.onCancel], either of which will only be called if [RawScaleObserver.onStart]
+ * was previously called. [RawScaleObserver.onStop] is called when no pointers remain.
+ * [RawScaleObserver.onCancel] is called due to a system cancellation event.
  *
  * This gesture detector is similar to [RawScaleGestureDetector] except that it is made for more
  * standard use cases where touch slop should likely be respected and no "nested scaling" is
@@ -126,8 +137,13 @@
             }
 
             override fun onStop() {
-                scaleObserver.onStop()
                 scaleEnabled = false
+                scaleObserver.onStop()
+            }
+
+            override fun onCancel() {
+                scaleEnabled = false
+                scaleObserver.onCancel()
             }
         }
 }
\ No newline at end of file
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/ScaleSlopExceededGestureDetector.kt b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/ScaleSlopExceededGestureDetector.kt
index 058fabc..1f34491 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/ScaleSlopExceededGestureDetector.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/ScaleSlopExceededGestureDetector.kt
@@ -57,7 +57,11 @@
     // TODO(b/129784010): Consider also allowing onStart, onScale, and onEnd to be set individually.
     recognizer.>
 
-    PointerInputWrapper(pointerInputHandler = recognizer.pointerInputHandler, children = children)
+    PointerInputWrapper(
+        pointerInputHandler = recognizer.pointerInputHandler,
+        cancelHandler = recognizer.cancelHandler,
+        children = children
+    )
 }
 
 /**
@@ -107,4 +111,9 @@
 
             changes
         }
+
+    val cancelHandler = {
+        passedSlop = false
+        scaleDiffTotal = 0f
+    }
 }
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/TouchSlopDragGestureDetector.kt b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/TouchSlopDragGestureDetector.kt
index 5794ca7..98432d3 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/TouchSlopDragGestureDetector.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/TouchSlopDragGestureDetector.kt
@@ -30,9 +30,12 @@
  * surpassed in a supported direction (see [DragObserver.onDrag]).  When dragging begins in this
  * manner, [DragObserver.onStart] is called, followed immediately by a call to
  * [DragObserver.onDrag]. [DragObserver.onDrag] is then continuously called whenever pointers
- * have moved. [DragObserver.onStop] is called when the dragging ends due to all of the pointers
- * no longer interacting with the DragGestureDetector (for example, the last finger has been lifted
- * off of the DragGestureDetector).
+ * have moved. The gesture ends with either a call to [DragObserver.onStop] or
+ * [DragObserver.onCancel], only after [DragObserver.onStart] is called. [DragObserver.onStop] is
+ * called when the dragging ends due to all of the pointers no longer interacting with the
+ * DragGestureDetector (for example, the last pointer has been lifted off of the
+ * DragGestureDetector). [DragObserver.onCancel] is called when the dragging ends due to a system
+ * cancellation event.
  *
  * If [startDragImmediately] is set to true, dragging will begin as soon as soon as a pointer comes
  * in contact with it, effectively ignoring touch slop and blocking any descendants from reacting
@@ -61,6 +64,9 @@
     val glue = remember { TouchSlopDragGestureDetectorGlue() }
     glue.touchSlopDragObserver = dragObserver
 
+    // TODO(b/146427920): There is a gap here where RawPressStartGestureDetector can cause a call to
+    //  DragObserver.onStart but if the pointer doesn't move and releases, (or if cancel is called)
+    //  The appropriate callbacks to DragObserver will not be called.
     RawDragGestureDetector(glue.rawDragObserver, glue::enabledOrStarted) {
         TouchSlopExceededGestureDetector(glue::enableDrag, canDrag) {
             RawPressStartGestureDetector(
@@ -107,9 +113,15 @@
             }
 
             override fun onStop(velocity: PxPosition) {
-                touchSlopDragObserver.onStop(velocity)
                 started = false
                 enabled = false
+                touchSlopDragObserver.onStop(velocity)
+            }
+
+            override fun onCancel() {
+                started = false
+                enabled = false
+                touchSlopDragObserver.onCancel()
             }
         }
 }
\ No newline at end of file
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/TouchSlopExceededGestureDetector.kt b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/TouchSlopExceededGestureDetector.kt
index 96d5cee..e3bf68c 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/gesture/TouchSlopExceededGestureDetector.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/gesture/TouchSlopExceededGestureDetector.kt
@@ -54,7 +54,11 @@
     recognizer.canDrag = canDrag
     recognizer.>
 
-    PointerInputWrapper(pointerInputHandler = recognizer.pointerInputHandler, children = children)
+    PointerInputWrapper(
+        pointerInputHandler = recognizer.pointerInputHandler,
+        cancelHandler = recognizer.cancelHandler,
+        children = children
+    )
 }
 
 // TODO(shepshapard): Shouldn't touchSlop be Px and not IntPx? What if the density bucket of the
@@ -177,6 +181,11 @@
             changes
         }
 
+    val cancelHandler = {
+        pointerTrackers.clear()
+        passedSlop = false
+    }
+
     internal data class PointerTrackingData(
         var dxUnderSlop: Float = 0f,
         var dyUnderSlop: Float = 0f,
diff --git a/ui/ui-framework/src/test/java/androidx/ui/core/gesture/DoubleTapGestureDetectorTest.kt b/ui/ui-framework/src/test/java/androidx/ui/core/gesture/DoubleTapGestureDetectorTest.kt
index b27697b..722af49 100644
--- a/ui/ui-framework/src/test/java/androidx/ui/core/gesture/DoubleTapGestureDetectorTest.kt
+++ b/ui/ui-framework/src/test/java/androidx/ui/core/gesture/DoubleTapGestureDetectorTest.kt
@@ -31,6 +31,7 @@
 import com.nhaarman.mockitokotlin2.any
 import com.nhaarman.mockitokotlin2.mock
 import com.nhaarman.mockitokotlin2.never
+import com.nhaarman.mockitokotlin2.times
 import com.nhaarman.mockitokotlin2.verify
 import org.junit.Before
 import org.junit.Test
@@ -63,7 +64,7 @@
 
     @Test
     fun pointerInputHandler_down_onDoubleTapNotCalled() {
-        mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(down()))
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(down())
         verify(onDoubleTap, never()).invoke(any())
     }
 
@@ -480,6 +481,27 @@
         verify(onDoubleTap).invoke(any())
     }
 
+    // This test verifies that the 2nd down causes the double tap time out timer to stop such that
+    // the second wait doesn't cause the gesture detector to reset to an idle state.
+    @Test
+    fun pointerInputHandler_downUpWaitHalfDownWaitHalfUp_onDoubleTapCalled() {
+        val down1 = down(duration = 0.milliseconds)
+        val up1 = down1.up(duration = 1.milliseconds)
+        val wait1 = 50L
+        val down2 = down(duration = 51.milliseconds)
+        val wait2 = 50L
+        val up2 = down2.up(duration = 101.milliseconds)
+
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(down1)
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(up1)
+        testContext.advanceTimeBy(wait1, TimeUnit.MILLISECONDS)
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(down2)
+        testContext.advanceTimeBy(wait2, TimeUnit.MILLISECONDS)
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(up2)
+
+        verify(onDoubleTap).invoke(any())
+    }
+
     // Tests that verify correctness of PxPosition value passed to onDoubleTap
 
     @Test
@@ -539,13 +561,13 @@
         verify(onDoubleTap).invoke(PxPosition((7).px, (11).px))
     }
 
-    // Tests that verify that consumption behavior
+    // Tests that verify correct consumption behavior
 
     @Test
     fun pointerInputHandler_down_downNotConsumed() {
         val down = down()
         val result = mRecognizer.pointerInputHandler.invokeOverAllPasses(down)
-        assertThat(result[0].consumed.downChange).isFalse()
+        assertThat(result.consumed.downChange).isFalse()
     }
 
     @Test
@@ -554,7 +576,7 @@
         val up = down.up(1.milliseconds)
         mRecognizer.pointerInputHandler.invokeOverAllPasses(down)
         val result = mRecognizer.pointerInputHandler.invokeOverAllPasses(up)
-        assertThat(result[0].consumed.downChange).isFalse()
+        assertThat(result.consumed.downChange).isFalse()
     }
 
     @Test
@@ -568,7 +590,7 @@
         testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
         val result = mRecognizer.pointerInputHandler.invokeOverAllPasses(down2)
 
-        assertThat(result[0].consumed.downChange).isFalse()
+        assertThat(result.consumed.downChange).isFalse()
     }
 
     @Test
@@ -584,7 +606,7 @@
         mRecognizer.pointerInputHandler.invokeOverAllPasses(down2)
         val result = mRecognizer.pointerInputHandler.invokeOverAllPasses(up2)
 
-        assertThat(result[0].consumed.downChange).isFalse()
+        assertThat(result.consumed.downChange).isFalse()
     }
 
     @Test
@@ -600,6 +622,178 @@
         mRecognizer.pointerInputHandler.invokeOverAllPasses(down2)
         val result = mRecognizer.pointerInputHandler.invokeOverAllPasses(up2)
 
-        assertThat(result[0].consumed.downChange).isTrue()
+        assertThat(result.consumed.downChange).isTrue()
+    }
+
+    // Tests that verify correct cancellation behavior
+
+    @Test
+    fun cancelHandler_downUpCancelWaitDownUp_onDoubleTapNotCalled() {
+        val down1 = down(duration = 100.milliseconds)
+        val up1 = down1.up(duration = 101.milliseconds)
+        val down2 = down(duration = 200.milliseconds)
+        val up2 = down2.up(duration = 201.milliseconds)
+
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(down1)
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(up1)
+        mRecognizer.cancelHandler()
+        testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(down2)
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(up2)
+
+        verify(onDoubleTap, never()).invoke(any())
+    }
+
+    @Test
+    fun cancelHandler_downUpWaitCancelDownUp_onDoubleTapNotCalled() {
+        val down1 = down(duration = 100.milliseconds)
+        val up1 = down1.up(duration = 101.milliseconds)
+        val down2 = down(duration = 200.milliseconds)
+        val up2 = down2.up(duration = 201.milliseconds)
+
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(down1)
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(up1)
+        testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
+        mRecognizer.cancelHandler()
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(down2)
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(up2)
+
+        verify(onDoubleTap, never()).invoke(any())
+    }
+
+    @Test
+    fun cancelHandler_cancelDownUpDownUp_onDoubleTapCalledOnce() {
+        val down1 = down(duration = 100.milliseconds)
+        val up1 = down1.up(duration = 101.milliseconds)
+        val down2 = down(duration = 200.milliseconds)
+        val up2 = down2.up(duration = 201.milliseconds)
+
+        mRecognizer.cancelHandler()
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(down1)
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(up1)
+        testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(down2)
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(up2)
+
+        verify(onDoubleTap).invoke(any())
+    }
+
+    @Test
+    fun cancelHandler_downCancelDownUpDownUp_onDoubleTapCalledOnce() {
+        val down0 = down(duration = 99.milliseconds)
+        val down1 = down(duration = 100.milliseconds)
+        val up1 = down1.up(duration = 101.milliseconds)
+        val down2 = down(duration = 200.milliseconds)
+        val up2 = down2.up(duration = 201.milliseconds)
+
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(down0)
+        mRecognizer.cancelHandler()
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(down1)
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(up1)
+        testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(down2)
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(up2)
+
+        verify(onDoubleTap).invoke(any())
+    }
+
+    @Test
+    fun cancelHandler_downUpCancelDownUpDownUp_onDoubleTapCalledOnce() {
+        val down0 = down(duration = 98.milliseconds)
+        val up0 = down0.up(duration = 99.milliseconds)
+        val down1 = down(duration = 100.milliseconds)
+        val up1 = down1.up(duration = 101.milliseconds)
+        val down2 = down(duration = 200.milliseconds)
+        val up2 = down2.up(duration = 201.milliseconds)
+
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(down0)
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(up0)
+        mRecognizer.cancelHandler()
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(down1)
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(up1)
+        testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(down2)
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(up2)
+
+        verify(onDoubleTap).invoke(any())
+    }
+
+    @Test
+    fun cancelHandler_downUpDownCancelDownUpDownUp_onDoubleTapCalledOnce() {
+        val down0 = down(duration = 97.milliseconds)
+        val up0 = down0.up(duration = 98.milliseconds)
+        val down1 = down(duration = 99.milliseconds)
+        val down2 = down(duration = 100.milliseconds)
+        val up2 = down2.up(duration = 101.milliseconds)
+        val down3 = down(duration = 200.milliseconds)
+        val up3 = down3.up(duration = 201.milliseconds)
+
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(down0)
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(up0)
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(down1)
+        mRecognizer.cancelHandler()
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(down2)
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(up2)
+        testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(down3)
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(up3)
+
+        verify(onDoubleTap).invoke(any())
+    }
+
+    @Test
+    fun cancelHandler_downUpDownUpCancelDownUpDownUp_onDoubleTapCalledTwice() {
+        val down0 = down(duration = 0.milliseconds)
+        val up0 = down0.up(duration = 1.milliseconds)
+        val down1 = down(duration = 100.milliseconds)
+        val up1 = down1.up(duration = 101.milliseconds)
+
+        val down2 = down(duration = 200.milliseconds)
+        val up2 = down2.up(duration = 201.milliseconds)
+        val down3 = down(duration = 300.milliseconds)
+        val up3 = down3.up(duration = 301.milliseconds)
+
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(down0)
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(up0)
+        testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(down1)
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(up1)
+        mRecognizer.cancelHandler()
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(down2)
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(up2)
+        testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(down3)
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(up3)
+
+        verify(onDoubleTap, times(2)).invoke(any())
+    }
+
+    // This test verifies that the cancel event causes the double tap timer to be reset.  If it does
+    // not cause it to be reset, then when delay1 is dispatched, the DoubleTapGestureDetector will
+    // be forced back into the IDLE state, preventing the double tap that follows cancel from
+    // firing.
+    @Test
+    fun cancelHandler_downUpWaitCancelDownWaitUpDownUp_onDoubleTapCalledOnce() {
+        val down0 = down(duration = 0.milliseconds)
+        val up0 = down0.up(duration = 1.milliseconds)
+        val delay0 = 50L
+        // Cancel happens here
+        val down1 = down(duration = 51.milliseconds)
+        val delay1 = 50L
+        val up1 = down1.up(duration = 101.milliseconds)
+        val down2 = down(duration = 102.milliseconds)
+        val up2 = down2.up(duration = 103.milliseconds)
+
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(down0)
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(up0)
+        testContext.advanceTimeBy(delay0, TimeUnit.MILLISECONDS)
+        mRecognizer.cancelHandler()
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(down1)
+        testContext.advanceTimeBy(delay1, TimeUnit.MILLISECONDS)
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(up1)
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(down2)
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(up2)
+
+        verify(onDoubleTap).invoke(any())
     }
 }
\ No newline at end of file
diff --git a/ui/ui-framework/src/test/java/androidx/ui/core/gesture/LongPressGestureDetectorTest.kt b/ui/ui-framework/src/test/java/androidx/ui/core/gesture/LongPressGestureDetectorTest.kt
index 788d6f3..2d79970 100644
--- a/ui/ui-framework/src/test/java/androidx/ui/core/gesture/LongPressGestureDetectorTest.kt
+++ b/ui/ui-framework/src/test/java/androidx/ui/core/gesture/LongPressGestureDetectorTest.kt
@@ -61,13 +61,13 @@
 
     @Test
     fun pointerInputHandler_down_onLongPressNotCalled() {
-        mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(down()))
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(down())
         verify(onLongPress, never()).invoke(any())
     }
 
     @Test
     fun pointerInputHandler_downWithinTimeout_onLongPressNotCalled() {
-        mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(down()))
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(down())
         testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
         verify(onLongPress, never()).invoke(any())
     }
@@ -77,9 +77,9 @@
         val down = down(0)
         val move = down.moveBy(50.milliseconds, 1f, 1f).consume(1f, 0f)
 
-        mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(down))
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(down)
         testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
-        mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(move))
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(move)
         testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
 
         verify(onLongPress, never()).invoke(any())
@@ -92,9 +92,9 @@
         val move0 = down0.moveBy(50.milliseconds, 1f, 1f).consume(1f, 0f)
         val move1 = down0.moveBy(50.milliseconds, 0f, 0f)
 
-        mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(down0, down1))
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(down0, down1)
         testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
-        mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(move0, move1))
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(move0, move1)
         testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
 
         verify(onLongPress, never()).invoke(any())
@@ -105,9 +105,9 @@
         val down = down(0)
         val up = down.up(50.milliseconds).consumeDownChange()
 
-        mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(down))
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(down)
         testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
-        mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(up))
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(up)
         testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
 
         verify(onLongPress, never()).invoke(any())
@@ -118,9 +118,9 @@
         val down = down(0)
         val up = down.up(50.milliseconds)
 
-        mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(down))
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(down)
         testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
-        mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(up))
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(up)
         testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
 
         verify(onLongPress, never()).invoke(any())
@@ -139,25 +139,13 @@
 
         // Act
 
-        mRecognizer.pointerInputHandler.invokeOverAllPasses(
-            listOf(
-                down0
-            )
-        )
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(down0)
 
         testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
-        mRecognizer.pointerInputHandler.invokeOverAllPasses(
-            listOf(
-                up0
-            )
-        )
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(up0)
 
         testContext.advanceTimeBy(1, TimeUnit.MILLISECONDS)
-        mRecognizer.pointerInputHandler.invokeOverAllPasses(
-            listOf(
-                down1
-            )
-        )
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(down1)
 
         testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
 
@@ -181,14 +169,14 @@
 
     @Test
     fun pointerInputHandler_downBeyondTimeout_onLongPressCalled() {
-        mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(down()))
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(down())
         testContext.advanceTimeBy(100, TimeUnit.MILLISECONDS)
         verify(onLongPress).invoke(any())
     }
 
     @Test
     fun pointerInputHandler_2DownBeyondTimeout_onLongPressCalled() {
-        mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(down(0), down(1)))
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(down(0), down(1))
         testContext.advanceTimeBy(100, TimeUnit.MILLISECONDS)
         verify(onLongPress).invoke(any())
     }
@@ -226,23 +214,17 @@
         // Act
 
         mRecognizer.pointerInputHandler.invokeOverAllPasses(
-            listOf(
-                down0
-            )
+            down0
         )
 
         testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
         mRecognizer.pointerInputHandler.invokeOverAllPasses(
-            listOf(
-                move0, down1
-            )
+            move0, down1
         )
 
         testContext.advanceTimeBy(25, TimeUnit.MILLISECONDS)
         mRecognizer.pointerInputHandler.invokeOverAllPasses(
-            listOf(
-                up0, move1
-            )
+            up0, move1
         )
 
         testContext.advanceTimeBy(25, TimeUnit.MILLISECONDS)
@@ -257,9 +239,9 @@
         val down = down(0)
         val move = down.moveBy(50.milliseconds, 1f, 1f)
 
-        mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(down))
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(down)
         testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
-        mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(move))
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(move)
         testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
 
         verify(onLongPress).invoke(any())
@@ -271,7 +253,7 @@
     fun pointerInputHandler_down_onLongPressCalledWithDownPosition() {
         val down = down(0, x = 13f, y = 17f)
 
-        mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(down))
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(down)
         testContext.advanceTimeBy(100, TimeUnit.MILLISECONDS)
 
         verify(onLongPress).invoke(PxPosition(13.px, 17.px))
@@ -282,9 +264,9 @@
         val down = down(0, x = 13f, y = 17f)
         val move = down.moveTo(50.milliseconds, 7f, 5f)
 
-        mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(down))
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(down)
         testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
-        mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(move))
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(move)
         testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
 
         verify(onLongPress).invoke(PxPosition((7).px, 5.px))
@@ -297,9 +279,9 @@
         val move0 = down0.moveBy(50.milliseconds, 0f, 0f)
         val down1 = down(1, 50.milliseconds, 11f, 19f)
 
-        mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(down0))
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(down0)
         testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
-        mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(move0, down1))
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(move0, down1)
         testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
 
         verify(onLongPress).invoke(PxPosition(13.px, 17.px))
@@ -315,11 +297,11 @@
         val up0 = move0.up(75.milliseconds)
         val move1 = down1.moveBy(25.milliseconds, 0f, 0f)
 
-        mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(down0))
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(down0)
         testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
-        mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(move0, down1))
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(move0, down1)
         testContext.advanceTimeBy(25, TimeUnit.MILLISECONDS)
-        mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(up0, move1))
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(up0, move1)
         testContext.advanceTimeBy(25, TimeUnit.MILLISECONDS)
 
         verify(onLongPress).invoke(PxPosition(11.px, 19.px))
@@ -332,9 +314,9 @@
         val move0 = down0.moveTo(50.milliseconds, 27f, 29f)
         val down1 = down(1, 50.milliseconds, 11f, 19f)
 
-        mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(down0))
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(down0)
         testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
-        mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(move0, down1))
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(move0, down1)
         testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
 
         verify(onLongPress).invoke(PxPosition(27.px, 29.px))
@@ -350,11 +332,11 @@
         val up0 = move0.up(50.milliseconds)
         val move1 = down1.moveTo(50.milliseconds, 27f, 23f)
 
-        mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(down0))
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(down0)
         testContext.advanceTimeBy(25, TimeUnit.MILLISECONDS)
-        mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(move0, down1))
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(move0, down1)
         testContext.advanceTimeBy(25, TimeUnit.MILLISECONDS)
-        mRecognizer.pointerInputHandler.invokeOverAllPasses(listOf(up0, move1))
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(up0, move1)
         testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
 
         verify(onLongPress).invoke(PxPosition(27.px, 23.px))
@@ -366,11 +348,9 @@
     fun pointerInputHandler_1Down_notConsumed() {
         val down0 = down(0)
         val result = mRecognizer.pointerInputHandler.invokeOverAllPasses(
-            listOf(
                 down0
-            )
         )
-        assertThat(result[0].consumed.downChange).isFalse()
+        assertThat(result.consumed.downChange).isFalse()
     }
 
     @Test
@@ -380,9 +360,7 @@
 
         val down0 = down(0, duration = 0.milliseconds)
         mRecognizer.pointerInputHandler.invokeOverAllPasses(
-            listOf(
                 down0
-            )
         )
 
         // Act
@@ -391,9 +369,7 @@
         val move0 = down0.moveTo(10.milliseconds, 0f, 0f)
         val down1 = down(0, duration = 10.milliseconds)
         val result = mRecognizer.pointerInputHandler.invokeOverAllPasses(
-            listOf(
                 move0, down1
-            )
         )
 
         // Assert
@@ -409,9 +385,7 @@
 
         val down0 = down(0, duration = 0.milliseconds)
         mRecognizer.pointerInputHandler.invokeOverAllPasses(
-            listOf(
                 down0
-            )
         )
 
         // Act
@@ -419,14 +393,12 @@
         testContext.advanceTimeBy(50, TimeUnit.MILLISECONDS)
         val up0 = down0.up(50.milliseconds)
         val result = mRecognizer.pointerInputHandler.invokeOverAllPasses(
-            listOf(
                 up0
-            )
         )
 
         // Assert
 
-        assertThat(result[0].consumed.downChange).isFalse()
+        assertThat(result.consumed.downChange).isFalse()
     }
 
     @Test
@@ -436,9 +408,7 @@
 
         val down0 = down(0, duration = 0.milliseconds)
         mRecognizer.pointerInputHandler.invokeOverAllPasses(
-            listOf(
                 down0
-            )
         )
 
         // Act
@@ -475,6 +445,29 @@
 
         // Assert
 
-        assertThat(result[0].consumed.downChange).isFalse()
+        assertThat(result.consumed.downChange).isFalse()
     }
+
+    // Tests that verify correct behavior around cancellation.
+
+    @Test
+    fun cancelHandler_downCancelBeyondTimeout_onLongPressNotCalled() {
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(down())
+        mRecognizer.cancelHandler()
+        testContext.advanceTimeBy(100, TimeUnit.MILLISECONDS)
+
+        verify(onLongPress, never()).invoke(any())
+    }
+
+    @Test
+    fun cancelHandler_downAlmostTimeoutCancelTimeout_onLongPressNotCalled() {
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(down())
+        testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
+        mRecognizer.cancelHandler()
+        testContext.advanceTimeBy(1, TimeUnit.MILLISECONDS)
+
+        verify(onLongPress, never()).invoke(any())
+    }
+
+    // cancelHandler_downCancelDownTimeExpires_onLongPressCalledOnce
 }
\ No newline at end of file
diff --git a/ui/ui-framework/src/test/java/androidx/ui/core/gesture/PressIndicatorGestureDetectorTest.kt b/ui/ui-framework/src/test/java/androidx/ui/core/gesture/PressIndicatorGestureDetectorTest.kt
index 9fd5fb9..1956d7c 100644
--- a/ui/ui-framework/src/test/java/androidx/ui/core/gesture/PressIndicatorGestureDetectorTest.kt
+++ b/ui/ui-framework/src/test/java/androidx/ui/core/gesture/PressIndicatorGestureDetectorTest.kt
@@ -61,14 +61,14 @@
 
     @Test
     fun pointerInputHandler_downConsumed_onStartNotCalled() {
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(down().consumeDownChange()))
+        recognizer.pointerInputHandler.invokeOverAllPasses(down().consumeDownChange())
         verify(recognizer.onStart!!, never()).invoke(any())
     }
 
     @Test
     fun pointerInputHandler_disabledDown_onStartNotCalled() {
         recognizer.setEnabled(false)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(down().consumeDownChange()))
+        recognizer.pointerInputHandler.invokeOverAllPasses(down().consumeDownChange())
         verify(recognizer.onStart!!, never()).invoke(any())
     }
 
@@ -76,17 +76,17 @@
 
     @Test
     fun pointerInputHandler_down_onStartCalledOnce() {
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(down()))
+        recognizer.pointerInputHandler.invokeOverAllPasses(down())
         verify(recognizer.onStart!!).invoke(any())
     }
 
     @Test
     fun pointerInputHandler_downDown_onStartCalledOnce() {
         var pointer0 = down(0)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer0))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer0)
         pointer0 = pointer0.moveTo(1.milliseconds)
         val pointer1 = down(1, 1.milliseconds)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer0, pointer1))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer0, pointer1)
 
         verify(recognizer.onStart!!).invoke(any())
     }
@@ -95,13 +95,13 @@
     fun pointerInputHandler_2Down1Up1Down_onStartCalledOnce() {
         var pointer0 = down(0)
         var pointer1 = down(1)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer0, pointer1))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer0, pointer1)
         pointer0 = pointer0.up(100.milliseconds)
         pointer1 = pointer1.moveTo(100.milliseconds)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer0, pointer1))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer0, pointer1)
         pointer0 = down(0, duration = 200.milliseconds)
         pointer1 = pointer1.moveTo(200.milliseconds)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer0, pointer1))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer0, pointer1)
 
         verify(recognizer.onStart!!).invoke(any())
     }
@@ -125,11 +125,11 @@
     @Test
     fun pointerInputHandler_downMoveConsumedUp_onStopNotCalled() {
         var pointer = down()
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
         pointer = pointer.moveTo(100.milliseconds, 5f).consume(1f)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
         pointer = pointer.up(200.milliseconds)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
 
         verify(recognizer.onStop!!, never()).invoke()
     }
@@ -137,9 +137,9 @@
     @Test
     fun pointerInputHandler_downConsumedUp_onStopNotCalled() {
         var pointer = down().consumeDownChange()
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
         pointer = pointer.up(100.milliseconds)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
 
         verify(recognizer.onStop!!, never()).invoke()
     }
@@ -148,10 +148,10 @@
     fun pointerInputHandler_2DownUp_onStopNotCalled() {
         var pointer0 = down(0)
         var pointer1 = down(1)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer0, pointer1))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer0, pointer1)
         pointer0 = pointer0.moveTo(100.milliseconds)
         pointer1 = pointer1.up(100.milliseconds)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer0, pointer1))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer0, pointer1)
 
         verify(recognizer.onStop!!, never()).invoke()
     }
@@ -159,10 +159,10 @@
     @Test
     fun pointerInputHandler_downDisabledUp_onStopNotCalled() {
         var pointer = down()
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
         recognizer.setEnabled(false)
         pointer = pointer.up(100.milliseconds)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
 
         verify(recognizer.onStop!!, never()).invoke()
     }
@@ -170,11 +170,11 @@
     @Test
     fun pointerInputHandler_downDisabledEnabledUp_onStopNotCalled() {
         var pointer = down()
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
         recognizer.setEnabled(false)
         recognizer.setEnabled(true)
         pointer = pointer.up(100.milliseconds)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
 
         verify(recognizer.onStop!!, never()).invoke()
     }
@@ -184,9 +184,9 @@
     @Test
     fun pointerInputHandler_downUp_onStopCalledOnce() {
         var pointer = down()
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
         pointer = pointer.up(100.milliseconds)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
 
         verify(recognizer.onStop!!).invoke()
     }
@@ -194,9 +194,9 @@
     @Test
     fun pointerInputHandler_downUpConsumed_onStopCalledOnce() {
         var pointer = down()
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
         pointer = pointer.up(100.milliseconds).consumeDownChange()
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
 
         verify(recognizer.onStop!!).invoke()
     }
@@ -204,11 +204,11 @@
     @Test
     fun pointerInputHandler_downMoveUp_onStopCalledOnce() {
         var pointer = down()
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
         pointer = pointer.moveTo(100.milliseconds, 5f)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
         pointer = pointer.up(200.milliseconds)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
 
         verify(recognizer.onStop!!).invoke()
     }
@@ -217,10 +217,10 @@
     fun pointerInputHandler_2Down2Up_onStopCalledOnce() {
         var pointer1 = down(0)
         var pointer2 = down(1)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer1, pointer2))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer1, pointer2)
         pointer1 = pointer1.up(100.milliseconds)
         pointer2 = pointer2.up(100.milliseconds)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer1, pointer2))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer1, pointer2)
 
         verify(recognizer.onStop!!).invoke()
     }
@@ -230,9 +230,9 @@
     @Test
     fun pointerInputHandler_downConsumedMoveConsumed_onCancelNotCalled() {
         var pointer = down().consumeDownChange()
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
         pointer = pointer.moveBy(100.milliseconds, 5f).consume(1f)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
 
         verify(recognizer.onCancel!!, never()).invoke()
     }
@@ -240,9 +240,9 @@
     @Test
     fun pointerInputHandler_downUp_onCancelNotCalled() {
         var pointer = down()
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
         pointer = pointer.up(100.milliseconds)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
 
         verify(recognizer.onCancel!!, never()).invoke()
     }
@@ -250,11 +250,11 @@
     @Test
     fun pointerInputHandler_downMoveUp_onCancelNotCalled() {
         var pointer = down()
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
         pointer = pointer.moveTo(100.milliseconds, 5f)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
         pointer = pointer.up(100.milliseconds)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
 
         verify(recognizer.onCancel!!, never()).invoke()
     }
@@ -264,11 +264,11 @@
         var pointer0 = down(0, x = 0f, y = 0f)
         var pointer1 = down(0, x = 4f, y = 4f)
         recognizer.pointerInputHandler
-            .invokeOverAllPasses(listOf(pointer0, pointer1), IntPxSize(5.ipx, 5.ipx))
+            .invokeOverAllPasses(pointer0, pointer1, size = IntPxSize(5.ipx, 5.ipx))
         pointer0 = pointer0.moveTo(100.milliseconds, 0f, 0f)
         pointer1 = pointer1.moveTo(100.milliseconds, 5f, 4f)
         recognizer.pointerInputHandler
-            .invokeOverAllPasses(listOf(pointer0, pointer1), IntPxSize(5.ipx, 5.ipx))
+            .invokeOverAllPasses(pointer0, pointer1, size = IntPxSize(5.ipx, 5.ipx))
 
         verify(recognizer.onCancel!!, never()).invoke()
     }
@@ -287,9 +287,9 @@
     @Test
     fun pointerInputHandler_downMoveConsumed_onCancelCalledOnce() {
         var pointer = down()
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
         pointer = pointer.moveBy(100.milliseconds, 5f).consume(1f)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
 
         verify(recognizer.onCancel!!).invoke()
     }
@@ -297,11 +297,11 @@
     @Test
     fun pointerInputHandler_downMoveConsumedMoveConsumed_onCancelCalledOnce() {
         var pointer = down()
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
         pointer = pointer.moveBy(100.milliseconds, 5f).consume(1f)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
         pointer = pointer.moveBy(100.milliseconds, 5f).consume(1f)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
 
         verify(recognizer.onCancel!!).invoke()
     }
@@ -310,10 +310,10 @@
     fun pointerInputHandler_2Down2MoveConsumed_onCancelCalledOnce() {
         var pointer0 = down(0)
         var pointer1 = down(1)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer0, pointer1))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer0, pointer1)
         pointer0 = pointer0.moveBy(100.milliseconds, 5f).consume(1f)
         pointer1 = pointer1.moveBy(100.milliseconds, 5f).consume(1f)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer0, pointer1))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer0, pointer1)
 
         verify(recognizer.onCancel!!).invoke()
     }
@@ -321,16 +321,16 @@
     @Test
     fun pointerInputHandler_2Down1MoveConsumedTheOtherMoveConsume_onCancelCalledOnce() {
         var pointer0 = down(0)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer0))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer0)
         pointer0 = pointer0.moveTo(100.milliseconds)
         var pointer1 = down(1, duration = 100.milliseconds)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer0, pointer1))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer0, pointer1)
         pointer0 = pointer0.moveBy(100L.milliseconds, 5f).consume(5f)
         pointer1 = pointer1.moveBy(100L.milliseconds)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer0, pointer1))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer0, pointer1)
         pointer0 = pointer0.moveBy(100L.milliseconds)
         pointer1 = pointer1.moveBy(100L.milliseconds, 5f).consume(5f)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer0, pointer1))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer0, pointer1)
 
         verify(recognizer.onCancel!!).invoke()
     }
@@ -388,11 +388,11 @@
         var pointer0 = down(0, x = 0f, y = 4f)
         var pointer1 = down(1, x = 4f, y = 0f)
         recognizer.pointerInputHandler
-            .invokeOverAllPasses(listOf(pointer0, pointer1), IntPxSize(5.ipx, 5.ipx))
+            .invokeOverAllPasses(pointer0, pointer1, size = IntPxSize(5.ipx, 5.ipx))
         pointer0 = pointer0.moveTo(100.milliseconds, 0f, 5f)
         pointer1 = pointer1.moveTo(100.milliseconds, 5f, 0f)
         recognizer.pointerInputHandler
-            .invokeOverAllPasses(listOf(pointer0, pointer1), IntPxSize(5.ipx, 5.ipx))
+            .invokeOverAllPasses(pointer0, pointer1, size = IntPxSize(5.ipx, 5.ipx))
 
         verify(recognizer.onCancel!!).invoke()
     }
@@ -435,7 +435,7 @@
     @Test
     fun pointerInputHandler_downDisabled_onCancelCalledOnce() {
         val pointer = down()
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
         recognizer.setEnabled(false)
 
         verify(recognizer.onCancel!!).invoke()
@@ -445,7 +445,7 @@
 
     @Test
     fun pointerInputHandler_down_downPositionIsCorrect() {
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(down(x = 13f, y = 17f)))
+        recognizer.pointerInputHandler.invokeOverAllPasses(down(x = 13f, y = 17f))
         verify(recognizer.onStart!!).invoke(PxPosition(13.px, 17f.px))
     }
 
@@ -455,11 +455,11 @@
     fun pointerInputHandler_down_downChangeConsumedDuringPostUp() {
         var pointer = down()
         pointer = recognizer.pointerInputHandler.invokeOverPasses(
-            listOf(pointer),
+            pointer,
             PointerEventPass.InitialDown,
             PointerEventPass.PreUp,
             PointerEventPass.PreDown
-        ).first()
+        )
         assertThat(pointer.consumed.downChange, `is`(false))
 
         pointer = recognizer.pointerInputHandler.invoke(
@@ -474,7 +474,7 @@
     fun pointerInputHandler_disabledDown_noDownChangeConsumed() {
         recognizer.setEnabled(false)
         var pointer = down()
-        pointer = recognizer.pointerInputHandler.invokeOverPasses(listOf(pointer)).first()
+        pointer = recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
         assertThat(pointer.consumed.downChange, `is`(false))
     }
 
@@ -489,7 +489,7 @@
 
     @Test
     fun cancelHandler_downConsumedCancel_noCallbacksCalled() {
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(down().consumeDownChange()))
+        recognizer.pointerInputHandler.invokeOverAllPasses(down().consumeDownChange())
         recognizer.cancelHandler.invoke()
 
         verifyNoMoreInteractions(recognizer.onStart, recognizer.onStop, recognizer.onCancel)
@@ -497,7 +497,7 @@
 
     @Test
     fun cancelHandler_downCancel_justStartAndCancelCalledInOrderOnce() {
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(down()))
+        recognizer.pointerInputHandler.invokeOverAllPasses(down())
         recognizer.cancelHandler.invoke()
 
         inOrder(recognizer.onStart!!, recognizer.onCancel!!) {
@@ -510,9 +510,9 @@
     @Test
     fun cancelHandler_downUpCancel_justStartAndStopCalledInOrderOnce() {
         var pointer = down()
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
         pointer = pointer.up(100.milliseconds)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
         recognizer.cancelHandler.invoke()
 
         inOrder(recognizer.onStart!!, recognizer.onStop!!) {
@@ -525,9 +525,9 @@
     @Test
     fun cancelHandler_downMoveCancel_justStartAndCancelCalledInOrderOnce() {
         var pointer = down()
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
         pointer = pointer.moveTo(50.milliseconds, 1f)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
         recognizer.cancelHandler.invoke()
 
         inOrder(recognizer.onStart!!, recognizer.onCancel!!) {
@@ -540,9 +540,9 @@
     @Test
     fun cancelHandler_downMoveConsumedCancel_justStartAndCancelCalledInOrderOnce() {
         var pointer = down()
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
         pointer = pointer.moveTo(50.milliseconds, 1f).consume(1f)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
         recognizer.cancelHandler.invoke()
 
         inOrder(recognizer.onStart!!, recognizer.onCancel!!) {
@@ -555,10 +555,10 @@
     @Test
     fun cancelHandler_downThenCancelThenDown_justStartCancelStartCalledInOrderOnce() {
         var pointer = down()
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
         recognizer.cancelHandler.invoke()
         pointer = down()
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
 
         inOrder(recognizer.onStart!!, recognizer.onCancel!!) {
             verify(recognizer.onStart!!).invoke(any())
diff --git a/ui/ui-framework/src/test/java/androidx/ui/core/gesture/PressReleasedGestureDetectorTest.kt b/ui/ui-framework/src/test/java/androidx/ui/core/gesture/PressReleasedGestureDetectorTest.kt
index f4002c1..8beec57 100644
--- a/ui/ui-framework/src/test/java/androidx/ui/core/gesture/PressReleasedGestureDetectorTest.kt
+++ b/ui/ui-framework/src/test/java/androidx/ui/core/gesture/PressReleasedGestureDetectorTest.kt
@@ -52,16 +52,16 @@
 
     @Test
     fun pointerInputHandler_down_onReleaseNotCalled() {
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(down()))
+        recognizer.pointerInputHandler.invokeOverAllPasses(down())
         verify(recognizer.onRelease!!, never()).invoke()
     }
 
     @Test
     fun pointerInputHandler_downConsumedUp_onReleaseNotCalled() {
         var pointer = down().consumeDownChange()
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
         pointer = pointer.up(100.milliseconds)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
 
         verify(recognizer.onRelease!!, never()).invoke()
     }
@@ -69,11 +69,11 @@
     @Test
     fun pointerInputHandler_downMoveConsumedUp_onReleaseNotCalled() {
         var pointer = down()
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
         pointer = pointer.moveTo(100.milliseconds, 5f).consume(5f)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
         pointer = pointer.up(200.milliseconds)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
 
         verify(recognizer.onRelease!!, never()).invoke()
     }
@@ -81,9 +81,9 @@
     @Test
     fun pointerInputHandler_downUpConsumed_onReleaseNotCalled() {
         var pointer = down().consumeDownChange()
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
         pointer = pointer.up(100.milliseconds).consumeDownChange()
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
 
         verify(recognizer.onRelease!!, never()).invoke()
     }
@@ -140,9 +140,9 @@
     fun pointerInputHandler_disabledDownUp_onReleaseNotCalled() {
         recognizer.setEnabled(false)
         var pointer = down()
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
         pointer = pointer.up(100.milliseconds)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
 
         verify(recognizer.onRelease!!, never()).invoke()
     }
@@ -151,10 +151,10 @@
     fun pointerInputHandler_disabledDownEnabledUp_onReleaseNotCalled() {
         recognizer.setEnabled(false)
         var pointer = down()
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
         recognizer.setEnabled(true)
         pointer = pointer.up(100.milliseconds)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
 
         verify(recognizer.onRelease!!, never()).invoke()
     }
@@ -162,10 +162,10 @@
     @Test
     fun pointerInputHandler_downDisabledUp_onReleaseNotCalled() {
         var pointer = down()
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
         recognizer.setEnabled(false)
         pointer = pointer.up(100.milliseconds)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
 
         verify(recognizer.onRelease!!, never()).invoke()
     }
@@ -175,9 +175,9 @@
     @Test
     fun pointerInputHandler_downUp_onReleaseCalledOnce() {
         var pointer = down()
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
         pointer = pointer.up(100.milliseconds)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
 
         verify(recognizer.onRelease!!).invoke()
     }
@@ -185,11 +185,11 @@
     @Test
     fun pointerInputHandler_downMoveUp_onReleaseCalledOnce() {
         var pointer = down()
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
         pointer = pointer.moveTo(100.milliseconds, 5f)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
         pointer = pointer.up(200.milliseconds)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
 
         verify(recognizer.onRelease!!).invoke()
     }
@@ -213,10 +213,10 @@
     @Test
     fun pointerInputHandler_downEnabledUp_onReleaseCalled() {
         var pointer = down()
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
         recognizer.setEnabled(true)
         pointer = pointer.up(100.milliseconds)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
 
         verify(recognizer.onRelease!!).invoke()
     }
@@ -226,23 +226,23 @@
     @Test
     fun pointerInputHandler_consumeDownOnStartIsFalse_downChangeNotConsumed() {
         recognizer.consumeDownOnStart = false
-        val pointerEventChange = recognizer.pointerInputHandler.invokeOverAllPasses(listOf(down()))
-        assertThat(pointerEventChange.first().consumed.downChange, `is`(false))
+        val pointerEventChange = recognizer.pointerInputHandler.invokeOverAllPasses(down())
+        assertThat(pointerEventChange.consumed.downChange, `is`(false))
     }
 
     @Test
     fun pointerInputHandler_disabledAndConsumeDownOnStartIsDefault_downChangeNotConsumed() {
         recognizer.setEnabled(false)
-        val pointerEventChange = recognizer.pointerInputHandler.invokeOverAllPasses(listOf(down()))
-        assertThat(pointerEventChange.first().consumed.downChange, `is`(false))
+        val pointerEventChange = recognizer.pointerInputHandler.invokeOverAllPasses(down())
+        assertThat(pointerEventChange.consumed.downChange, `is`(false))
     }
 
     // Verification for when the down change should be consumed.
 
     @Test
     fun pointerInputHandler_consumeDownOnStartIsDefault_downChangeConsumed() {
-        val pointerEventChange = recognizer.pointerInputHandler.invokeOverAllPasses(listOf(down()))
-        assertThat(pointerEventChange.first().consumed.downChange, `is`(true))
+        val pointerEventChange = recognizer.pointerInputHandler.invokeOverAllPasses(down())
+        assertThat(pointerEventChange.consumed.downChange, `is`(true))
     }
 
     // Verification for when the up change should not be consumed.
@@ -251,34 +251,34 @@
     fun pointerInputHandler_disabledDownUp_upChangeNotConsumed() {
         recognizer.setEnabled(false)
         var pointer = down()
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
         pointer = pointer.up(100.milliseconds)
         val pointerEventChange = recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
 
-        assertThat(pointerEventChange.first().consumed.downChange, `is`(false))
+        assertThat(pointerEventChange.consumed.downChange, `is`(false))
     }
 
     @Test
     fun pointerInputHandler_disabledDownEnabledUp_upChangeNotConsumed() {
         recognizer.setEnabled(false)
         var pointer = down()
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
         recognizer.setEnabled(true)
         pointer = pointer.up(100.milliseconds)
-        val pointerEventChange = recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        val pointerEventChange = recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
 
-        assertThat(pointerEventChange.first().consumed.downChange, `is`(false))
+        assertThat(pointerEventChange.consumed.downChange, `is`(false))
     }
 
     @Test
     fun pointerInputHandler_downDisabledUp_upChangeNotConsumed() {
         var pointer = down()
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
         recognizer.setEnabled(false)
         pointer = pointer.up(100.milliseconds)
-        val pointerEventChange = recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        val pointerEventChange = recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
 
-        assertThat(pointerEventChange.first().consumed.downChange, `is`(false))
+        assertThat(pointerEventChange.consumed.downChange, `is`(false))
     }
 
     @Test
@@ -291,7 +291,7 @@
         val result =
             recognizer.pointerInputHandler.invokeOverAllPasses(pointer, IntPxSize(1.ipx, 1.ipx))
 
-        assertThat(result.first().consumed.downChange, `is`(false))
+        assertThat(result.consumed.downChange, `is`(false))
     }
 
     @Test
@@ -304,7 +304,7 @@
         val result =
             recognizer.pointerInputHandler.invokeOverAllPasses(pointer, IntPxSize(1.ipx, 1.ipx))
 
-        assertThat(result.first().consumed.downChange, `is`(false))
+        assertThat(result.consumed.downChange, `is`(false))
     }
 
     @Test
@@ -317,7 +317,7 @@
         val result =
             recognizer.pointerInputHandler.invokeOverAllPasses(pointer, IntPxSize(1.ipx, 1.ipx))
 
-        assertThat(result.first().consumed.downChange, `is`(false))
+        assertThat(result.consumed.downChange, `is`(false))
     }
 
     @Test
@@ -331,7 +331,7 @@
         val result =
             recognizer.pointerInputHandler.invokeOverAllPasses(pointer, IntPxSize(1.ipx, 1.ipx))
 
-        assertThat(result.first().consumed.downChange, `is`(false))
+        assertThat(result.consumed.downChange, `is`(false))
     }
 
     // Verification for when the up change should be consumed.
@@ -339,51 +339,64 @@
     @Test
     fun pointerInputHandler_upChangeConsumed() {
         var pointer = down()
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
         pointer = pointer.up(100.milliseconds)
-        val pointerEventChange = recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
-        assertThat(pointerEventChange.first().consumed.downChange, `is`(true))
+        val pointerEventChange = recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
+        assertThat(pointerEventChange.consumed.downChange, `is`(true))
     }
 
     // Verification for during what pass the changes are consumed.
 
     @Test
     fun pointerInputHandler_downChangeConsumedDuringPostUp() {
-        var pointerEventChange = listOf(down())
+        var pointerEventChange = down()
         pointerEventChange = recognizer.pointerInputHandler.invokeOverPasses(
             pointerEventChange,
             PointerEventPass.InitialDown,
             PointerEventPass.PreUp,
             PointerEventPass.PreDown
         )
-        assertThat(pointerEventChange.first().consumed.downChange, `is`(false))
+        assertThat(pointerEventChange.consumed.downChange, `is`(false))
 
-        pointerEventChange = recognizer.pointerInputHandler.invoke(
+        pointerEventChange = recognizer.pointerInputHandler.invokeOverPasses(
             pointerEventChange,
             PointerEventPass.PostUp,
             IntPxSize(0.ipx, 0.ipx)
         )
-        assertThat(pointerEventChange.first().consumed.downChange, `is`(true))
+        assertThat(pointerEventChange.consumed.downChange, `is`(true))
     }
 
     @Test
     fun pointerInputHandler_upChangeConsumedDuringPostUp() {
         val pointer = down()
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer))
-        var pointerEventChange = listOf(pointer.up(100.milliseconds))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
+        var pointerEventChange = pointer.up(100.milliseconds)
         pointerEventChange = recognizer.pointerInputHandler.invokeOverPasses(
             pointerEventChange,
             PointerEventPass.InitialDown,
             PointerEventPass.PreUp,
             PointerEventPass.PreDown
         )
-        assertThat(pointerEventChange.first().consumed.downChange, `is`(false))
+        assertThat(pointerEventChange.consumed.downChange, `is`(false))
 
-        pointerEventChange = recognizer.pointerInputHandler.invoke(
+        pointerEventChange = recognizer.pointerInputHandler.invokeOverPasses(
             pointerEventChange,
             PointerEventPass.PostUp,
             IntPxSize(0.ipx, 0.ipx)
         )
-        assertThat(pointerEventChange.first().consumed.downChange, `is`(true))
+        assertThat(pointerEventChange.consumed.downChange, `is`(true))
+    }
+
+    // Verification of correct cancellation behavior.
+
+    @Test
+    fun cancelationHandler_downCancelUp_onReleaseNotCalled() {
+        var pointer = down()
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
+        recognizer.cancelHandler()
+        pointer = pointer.up(100.milliseconds)
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
+
+        verify(recognizer.onRelease!!, never()).invoke()
     }
 }
\ No newline at end of file
diff --git a/ui/ui-framework/src/test/java/androidx/ui/core/gesture/RawDragGestureDetectorTest.kt b/ui/ui-framework/src/test/java/androidx/ui/core/gesture/RawDragGestureDetectorTest.kt
index 2bcf220..baf55bf 100644
--- a/ui/ui-framework/src/test/java/androidx/ui/core/gesture/RawDragGestureDetectorTest.kt
+++ b/ui/ui-framework/src/test/java/androidx/ui/core/gesture/RawDragGestureDetectorTest.kt
@@ -123,8 +123,8 @@
 
         // Arrange
 
-        val pointers = arrayListOf(down(0), down(1), down(2))
-        recognizer.pointerInputHandler.invokeOverAllPasses(pointers)
+        val pointers = arrayOf(down(0), down(1), down(2))
+        recognizer.pointerInputHandler.invokeOverAllPasses(*pointers)
         dragStartBlocked = false
 
         // Act
@@ -148,7 +148,7 @@
                 0f,
                 2f
             )
-        recognizer.pointerInputHandler.invokeOverAllPasses(pointers)
+        recognizer.pointerInputHandler.invokeOverAllPasses(*pointers)
 
         // Assert
         assertThat(log.filter { it.methodName == "onStart" }).isEmpty()
@@ -433,7 +433,7 @@
     @Test
     fun pointerInputHandler_down_downNotConsumed() {
         val result = recognizer.pointerInputHandler.invokeOverAllPasses(down())
-        assertThat(result.first().consumed.downChange).isFalse()
+        assertThat(result.consumed.downChange).isFalse()
     }
 
     @Test
@@ -448,7 +448,7 @@
         )
         dragObserver.dragConsume = PxPosition(7.ipx, (-11).ipx)
         var result = recognizer.pointerInputHandler.invokeOverPasses(
-            listOf(change),
+            change,
             PointerEventPass.InitialDown,
             PointerEventPass.PreUp,
             PointerEventPass.PreDown,
@@ -463,7 +463,7 @@
             PointerEventPass.PostUp
         )
 
-        assertThat(result.first().anyPositionChangeConsumed()).isFalse()
+        assertThat(result.anyPositionChangeConsumed()).isFalse()
     }
 
     @Test
@@ -481,7 +481,7 @@
         )
         val result = recognizer.pointerInputHandler.invokeOverAllPasses(change)
 
-        assertThat(result.first().anyPositionChangeConsumed()).isFalse()
+        assertThat(result.anyPositionChangeConsumed()).isFalse()
     }
 
     @Test
@@ -499,7 +499,7 @@
         )
         val result = recognizer.pointerInputHandler.invokeOverAllPasses(change)
 
-        assertThat(result.first().anyPositionChangeConsumed()).isFalse()
+        assertThat(result.anyPositionChangeConsumed()).isFalse()
     }
 
     @Test
@@ -515,7 +515,7 @@
         )
         dragObserver.dragConsume = PxPosition(7.ipx, (-11).ipx)
         var result = recognizer.pointerInputHandler.invokeOverPasses(
-            listOf(change),
+            change,
             PointerEventPass.InitialDown,
             PointerEventPass.PreUp,
             PointerEventPass.PreDown,
@@ -527,8 +527,8 @@
             PointerEventPass.PostDown
         )
 
-        assertThat(result.first().consumed.positionChange.x.value).isEqualTo(7f)
-        assertThat(result.first().consumed.positionChange.y.value).isEqualTo(-11f)
+        assertThat(result.consumed.positionChange.x.value).isEqualTo(7f)
+        assertThat(result.consumed.positionChange.y.value).isEqualTo(-11f)
     }
 
     @Test
@@ -546,7 +546,7 @@
         change = change.up(20.milliseconds)
         val result = recognizer.pointerInputHandler.invokeOverAllPasses(change)
 
-        assertThat(result.first().consumed.downChange).isTrue()
+        assertThat(result.consumed.downChange).isTrue()
     }
 
     @Test
@@ -583,6 +583,107 @@
             .isEqualTo(PxPosition(3.px, 4.px))
     }
 
+    // Tests that verify when onCancel should not be called.
+
+    @Test
+    fun cancelHandler_downCancel_onCancelNotCalled() {
+        val down = down()
+        recognizer.pointerInputHandler.invokeOverAllPasses(down)
+        dragStartBlocked = false
+        recognizer.cancelHandler()
+
+        assertThat(log.filter { it.methodName == "onCancel" }).isEmpty()
+    }
+
+    @Test
+    fun cancelHandler_blockedDownMoveCancel_onCancelNotCalled() {
+        val down = down()
+        recognizer.pointerInputHandler.invokeOverAllPasses(down)
+        dragStartBlocked = true
+        val move = down.moveBy(1.milliseconds, 1f, 0f)
+        recognizer.pointerInputHandler.invokeOverAllPasses(move)
+        recognizer.cancelHandler()
+
+        assertThat(log.filter { it.methodName == "onCancel" }).isEmpty()
+    }
+
+    // Tests that verify when onCancel should be called.
+
+    @Test
+    fun cancelHandler_downMoveCancel_onCancelCalledOnce() {
+        val down = down()
+        recognizer.pointerInputHandler.invokeOverAllPasses(down)
+        dragStartBlocked = false
+        val move = down.moveBy(1.milliseconds, 1f, 0f)
+        recognizer.pointerInputHandler.invokeOverAllPasses(move)
+        recognizer.cancelHandler()
+
+        assertThat(log.filter { it.methodName == "onCancel" }).hasSize(1)
+    }
+
+    // Tests that cancel behavior is correct.
+
+    @Test
+    fun cancelHandler_downCancelDownMove_startPositionIsSecondDown() {
+        val down1 = down(x = 3f, y = 5f)
+        recognizer.pointerInputHandler.invokeOverAllPasses(down1)
+        dragStartBlocked = false
+        recognizer.cancelHandler()
+
+        val down2 = down(x = 7f, y = 11f)
+        recognizer.pointerInputHandler.invokeOverAllPasses(down2)
+
+        val move = down2.moveBy(Duration(milliseconds = 10), 1f, 0f)
+        recognizer.pointerInputHandler.invokeOverAllPasses(move)
+
+        assertThat(log.first { it.methodName == "onStart" }.pxPosition)
+            .isEqualTo(PxPosition(7.px, 11.px))
+    }
+
+    @Test
+    fun cancelHandler_downMoveCancelDownMoveUp_flingIgnoresMoveBeforeCancel() {
+
+        // Act.
+
+        // Down, move, cancel.
+        var change = down(id = 0, duration = 0.milliseconds)
+        recognizer.pointerInputHandler.invokeOverAllPasses(change)
+        dragStartBlocked = false
+        repeat(11) {
+            change = change.moveBy(
+                10.milliseconds,
+                -1f,
+                -1f
+            )
+            recognizer.pointerInputHandler.invokeOverAllPasses(change)
+        }
+        recognizer.cancelHandler()
+
+        // Down, Move, Up
+        change = down(id = 1, duration = 200.milliseconds)
+        recognizer.pointerInputHandler.invokeOverAllPasses(change)
+        dragStartBlocked = false
+        repeat(11) {
+            change = change.moveBy(
+                10.milliseconds,
+                1f,
+                1f
+            )
+            recognizer.pointerInputHandler.invokeOverAllPasses(change)
+        }
+        change = change.up(310.milliseconds)
+        recognizer.pointerInputHandler.invokeOverAllPasses(change)
+
+        // Assert.
+
+        // Fling velocity should only take account of the second Down, Move, Up.
+        val loggedStops = log.filter { it.methodName == "onStop" }
+        assertThat(loggedStops).hasSize(1)
+        val velocity = loggedStops[0].pxPosition!!
+        assertThat(velocity.x.value).isWithin(.01f).of(100f)
+        assertThat(velocity.y.value).isWithin(.01f).of(100f)
+    }
+
     data class LogItem(
         val methodName: String,
         val pxPosition: PxPosition? = null
@@ -600,17 +701,17 @@
 
         override fun onDrag(dragDistance: PxPosition): PxPosition {
             log.add(LogItem("onDrag", pxPosition = dragDistance))
-            val internalDragConsume = dragConsume
-            if (internalDragConsume == null) {
-                return super.onDrag(dragDistance)
-            } else {
-                return internalDragConsume
-            }
+            return dragConsume ?: super.onDrag(dragDistance)
         }
 
         override fun onStop(velocity: PxPosition) {
             log.add(LogItem("onStop", pxPosition = velocity))
             super.onStop(velocity)
         }
+
+        override fun onCancel() {
+            log.add(LogItem("onCancel", pxPosition = null))
+            super.onCancel()
+        }
     }
 }
\ No newline at end of file
diff --git a/ui/ui-framework/src/test/java/androidx/ui/core/gesture/RawPressStartGestureDetectorTest.kt b/ui/ui-framework/src/test/java/androidx/ui/core/gesture/RawPressStartGestureDetectorTest.kt
index 6d98063..d5021b7 100644
--- a/ui/ui-framework/src/test/java/androidx/ui/core/gesture/RawPressStartGestureDetectorTest.kt
+++ b/ui/ui-framework/src/test/java/androidx/ui/core/gesture/RawPressStartGestureDetectorTest.kt
@@ -57,7 +57,7 @@
 
     @Test
     fun pointerInputHandler_downConsumed_onPressStartNotCalled() {
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(down().consumeDownChange()))
+        recognizer.pointerInputHandler.invokeOverAllPasses(down().consumeDownChange())
         verify(recognizer.onPressStart, never()).invoke(any())
     }
 
@@ -67,14 +67,14 @@
         recognizer.pointerInputHandler.invokeOverAllPasses(pointer1)
         pointer1 = pointer1.moveBy(10.milliseconds)
         val pointer2 = down(duration = 10.milliseconds)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer1, pointer2))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer1, pointer2)
         verify(recognizer.onPressStart, never()).invoke(any())
     }
 
     @Test
     fun pointerInputHandler_disabledDown_onPressStartNotCalled() {
         recognizer.setEnabled(false)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(down()))
+        recognizer.pointerInputHandler.invokeOverAllPasses(down())
         verify(recognizer.onPressStart, never()).invoke(any())
     }
 
@@ -87,7 +87,7 @@
         recognizer.setEnabled(true)
         pointer1 = pointer1.moveBy(10.milliseconds)
         val pointer2 = down(duration = 10.milliseconds)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer1, pointer2))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer1, pointer2)
         verify(recognizer.onPressStart, never()).invoke(any())
     }
 
@@ -95,17 +95,17 @@
 
     @Test
     fun pointerInputHandler_down_onPressStartCalledOnce() {
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(down()))
+        recognizer.pointerInputHandler.invokeOverAllPasses(down())
         verify(recognizer.onPressStart).invoke(any())
     }
 
     @Test
     fun pointerInputHandler_downDown_onPressStartCalledOnce() {
         var pointer0 = down(0)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer0))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer0)
         pointer0 = pointer0.moveTo(1.milliseconds)
         val pointer1 = down(1, 1.milliseconds)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer0, pointer1))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer0, pointer1)
 
         verify(recognizer.onPressStart).invoke(any())
     }
@@ -114,13 +114,13 @@
     fun pointerInputHandler_2Down1Up1Down_onPressStartCalledOnce() {
         var pointer0 = down(0)
         var pointer1 = down(1)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer0, pointer1))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer0, pointer1)
         pointer0 = pointer0.up(100.milliseconds)
         pointer1 = pointer1.moveTo(100.milliseconds)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer0, pointer1))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer0, pointer1)
         pointer0 = down(0, duration = 200.milliseconds)
         pointer1 = pointer1.moveTo(200.milliseconds)
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(pointer0, pointer1))
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer0, pointer1)
 
         verify(recognizer.onPressStart).invoke(any())
     }
@@ -143,7 +143,7 @@
 
     @Test
     fun pointerInputHandler_down_downPositionIsCorrect() {
-        recognizer.pointerInputHandler.invokeOverAllPasses(listOf(down(x = 13f, y = 17f)))
+        recognizer.pointerInputHandler.invokeOverAllPasses(down(x = 13f, y = 17f))
         verify(recognizer.onPressStart).invoke(PxPosition(13.px, 17f.px))
     }
 
@@ -153,7 +153,7 @@
     fun pointerInputHandler_disabledDown_noDownChangeConsumed() {
         recognizer.setEnabled(false)
         var pointer = down()
-        pointer = recognizer.pointerInputHandler.invokeOverPasses(listOf(pointer)).first()
+        pointer = recognizer.pointerInputHandler.invokeOverAllPasses(pointer)
         assertThat(pointer.consumed.downChange, `is`(false))
     }
 
@@ -182,9 +182,9 @@
         recognizer.setExecutionPass(PointerEventPass.InitialDown)
 
         val pointer = recognizer.pointerInputHandler.invokeOverPasses(
-            listOf(down()),
+            down(),
             PointerEventPass.InitialDown
-        ).first()
+        )
 
         verify(recognizer.onPressStart).invoke(any())
         assertThat(pointer.consumed.downChange, `is`(true))
@@ -195,17 +195,17 @@
         recognizer.setExecutionPass(PointerEventPass.PreUp)
 
         var pointer = recognizer.pointerInputHandler.invokeOverPasses(
-            listOf(down()),
+            down(),
             PointerEventPass.InitialDown
-        ).first()
+        )
 
         verify(recognizer.onPressStart, never()).invoke(any())
         assertThat(pointer.consumed.downChange, `is`(false))
 
         pointer = recognizer.pointerInputHandler.invokeOverPasses(
-            listOf(down()),
+            down(),
             PointerEventPass.PreUp
-        ).first()
+        )
 
         verify(recognizer.onPressStart).invoke(any())
         assertThat(pointer.consumed.downChange, `is`(true))
@@ -216,18 +216,18 @@
         recognizer.setExecutionPass(PointerEventPass.PreDown)
 
         var pointer = recognizer.pointerInputHandler.invokeOverPasses(
-            listOf(down()),
+            down(),
             PointerEventPass.InitialDown,
             PointerEventPass.PreUp
-        ).first()
+        )
 
         verify(recognizer.onPressStart, never()).invoke(any())
         assertThat(pointer.consumed.downChange, `is`(false))
 
         pointer = recognizer.pointerInputHandler.invokeOverPasses(
-            listOf(down()),
+            down(),
             PointerEventPass.PreDown
-        ).first()
+        )
 
         verify(recognizer.onPressStart).invoke(any())
         assertThat(pointer.consumed.downChange, `is`(true))
@@ -238,19 +238,19 @@
         recognizer.setExecutionPass(PointerEventPass.PostUp)
 
         var pointer = recognizer.pointerInputHandler.invokeOverPasses(
-            listOf(down()),
+            down(),
             PointerEventPass.InitialDown,
             PointerEventPass.PreUp,
             PointerEventPass.PreDown
-        ).first()
+        )
 
         verify(recognizer.onPressStart, never()).invoke(any())
         assertThat(pointer.consumed.downChange, `is`(false))
 
         pointer = recognizer.pointerInputHandler.invokeOverPasses(
-            listOf(down()),
+            down(),
             PointerEventPass.PostUp
-        ).first()
+        )
 
         verify(recognizer.onPressStart).invoke(any())
         assertThat(pointer.consumed.downChange, `is`(true))
@@ -261,22 +261,42 @@
         recognizer.setExecutionPass(PointerEventPass.PostDown)
 
         var pointer = recognizer.pointerInputHandler.invokeOverPasses(
-            listOf(down()),
+            down(),
             PointerEventPass.InitialDown,
             PointerEventPass.PreUp,
             PointerEventPass.PreDown,
             PointerEventPass.PostUp
-        ).first()
+        )
 
         verify(recognizer.onPressStart, never()).invoke(any())
         assertThat(pointer.consumed.downChange, `is`(false))
 
         pointer = recognizer.pointerInputHandler.invokeOverPasses(
-            listOf(down()),
+            down(),
             PointerEventPass.PostDown
-        ).first()
+        )
 
         verify(recognizer.onPressStart).invoke(any())
         assertThat(pointer.consumed.downChange, `is`(true))
     }
+
+    // Verification of correct cancellation behavior.
+
+    // The purpose of this test is hard to understand, but it proves that the cancel event sets the
+    // state of the gesture detector to inactive such that when a new stream of events starts,
+    // and the 1st down is already consumed, the gesture detector won't consume the 2nd down.
+    @Test
+    fun cancelHandler_downCancelDownConsumedDown_thirdDownNotConsumed() {
+        recognizer.pointerInputHandler
+            .invokeOverAllPasses(down(id = 0, duration = 0.milliseconds))
+        recognizer.cancelHandler()
+        var pointer1 = down(id = 1, duration = 10.milliseconds).consumeDownChange()
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer1)
+        pointer1 = pointer1.moveTo(20.milliseconds, 0f, 0f)
+        val pointer2 = down(id = 2, duration = 20.milliseconds)
+        val results = recognizer.pointerInputHandler.invokeOverAllPasses(pointer1, pointer2)
+
+        assertThat(results[0].consumed.downChange, `is`(false))
+        assertThat(results[1].consumed.downChange, `is`(false))
+    }
 }
\ No newline at end of file
diff --git a/ui/ui-framework/src/test/java/androidx/ui/core/gesture/RawScaleGestureDetectorTest.kt b/ui/ui-framework/src/test/java/androidx/ui/core/gesture/RawScaleGestureDetectorTest.kt
index 8dc7821..be535e3 100644
--- a/ui/ui-framework/src/test/java/androidx/ui/core/gesture/RawScaleGestureDetectorTest.kt
+++ b/ui/ui-framework/src/test/java/androidx/ui/core/gesture/RawScaleGestureDetectorTest.kt
@@ -581,7 +581,7 @@
     @Test
     fun pointerInputHandler_1down_downNotConsumed() {
         val result = recognizer.pointerInputHandler.invokeOverAllPasses(down())
-        assertThat(result.first().consumed.downChange).isFalse()
+        assertThat(result.consumed.downChange).isFalse()
     }
 
     @Test
@@ -650,13 +650,16 @@
         scaleObserver.resultingScaleChange = 2f
         var result = recognizer.pointerInputHandler.invokeOverPasses(
             listOf(change1, change2),
-            PointerEventPass.InitialDown,
-            PointerEventPass.PreUp,
-            PointerEventPass.PreDown,
-            PointerEventPass.PostUp
+            listOf(
+                PointerEventPass.InitialDown,
+                PointerEventPass.PreUp,
+                PointerEventPass.PreDown,
+                PointerEventPass.PostUp
+            )
         )
         scaleObserver.resultingScaleChange = 1f
-        result = recognizer.pointerInputHandler.invokeOverPasses(result, PointerEventPass.PostDown)
+        result = recognizer.pointerInputHandler
+            .invokeOverPasses(result, listOf(PointerEventPass.PostDown))
 
         // Assert
 
@@ -681,13 +684,16 @@
         scaleObserver.resultingScaleChange = 2f
         var result = recognizer.pointerInputHandler.invokeOverPasses(
             listOf(change1, change2),
-            PointerEventPass.InitialDown,
-            PointerEventPass.PreUp,
-            PointerEventPass.PreDown,
-            PointerEventPass.PostUp
+            listOf(
+                PointerEventPass.InitialDown,
+                PointerEventPass.PreUp,
+                PointerEventPass.PreDown,
+                PointerEventPass.PostUp
+            )
         )
         scaleObserver.resultingScaleChange = 1f
-        result = recognizer.pointerInputHandler.invokeOverPasses(result, PointerEventPass.PostDown)
+        result = recognizer.pointerInputHandler
+            .invokeOverPasses(result, listOf(PointerEventPass.PostDown))
 
         // Assert
 
@@ -730,13 +736,16 @@
         scaleObserver.resultingScaleChange = .75f
         var result = recognizer.pointerInputHandler.invokeOverPasses(
             listOf(change1, change2),
-            PointerEventPass.InitialDown,
-            PointerEventPass.PreUp,
-            PointerEventPass.PreDown,
-            PointerEventPass.PostUp
+            listOf(
+                PointerEventPass.InitialDown,
+                PointerEventPass.PreUp,
+                PointerEventPass.PreDown,
+                PointerEventPass.PostUp
+            )
         )
         scaleObserver.resultingScaleChange = 1f
-        result = recognizer.pointerInputHandler.invokeOverPasses(result, PointerEventPass.PostDown)
+        result = recognizer.pointerInputHandler
+            .invokeOverPasses(result, listOf(PointerEventPass.PostDown))
 
         // Assert
 
@@ -761,13 +770,16 @@
         scaleObserver.resultingScaleChange = .75f
         var result = recognizer.pointerInputHandler.invokeOverPasses(
             listOf(change1, change2),
-            PointerEventPass.InitialDown,
-            PointerEventPass.PreUp,
-            PointerEventPass.PreDown,
-            PointerEventPass.PostUp
+            listOf(
+                PointerEventPass.InitialDown,
+                PointerEventPass.PreUp,
+                PointerEventPass.PreDown,
+                PointerEventPass.PostUp
+            )
         )
         scaleObserver.resultingScaleChange = 1f
-        result = recognizer.pointerInputHandler.invokeOverPasses(result, PointerEventPass.PostDown)
+        result = recognizer.pointerInputHandler
+            .invokeOverPasses(result, listOf(PointerEventPass.PostDown))
 
         // Assert
 
@@ -808,13 +820,18 @@
         scaleObserver.resultingScaleChange = 2f
         var result = recognizer.pointerInputHandler.invokeOverPasses(
             listOf(change1, change2),
-            PointerEventPass.InitialDown,
-            PointerEventPass.PreUp,
-            PointerEventPass.PreDown,
-            PointerEventPass.PostUp
+            listOf(
+                PointerEventPass.InitialDown,
+                PointerEventPass.PreUp,
+                PointerEventPass.PreDown,
+                PointerEventPass.PostUp
+            )
         )
         scaleObserver.resultingScaleChange = 1f
-        result = recognizer.pointerInputHandler.invokeOverPasses(result, PointerEventPass.PostDown)
+        result = recognizer.pointerInputHandler
+            .invokeOverPasses(
+                result, listOf(PointerEventPass.PostDown)
+            )
 
         // Assert
 
@@ -839,13 +856,16 @@
         scaleObserver.resultingScaleChange = 2f
         var result = recognizer.pointerInputHandler.invokeOverPasses(
             listOf(change1, change2),
-            PointerEventPass.InitialDown,
-            PointerEventPass.PreUp,
-            PointerEventPass.PreDown,
-            PointerEventPass.PostUp
+            listOf(
+                PointerEventPass.InitialDown,
+                PointerEventPass.PreUp,
+                PointerEventPass.PreDown,
+                PointerEventPass.PostUp
+            )
         )
         scaleObserver.resultingScaleChange = 1f
-        result = recognizer.pointerInputHandler.invokeOverPasses(result, PointerEventPass.PostDown)
+        result = recognizer.pointerInputHandler
+            .invokeOverPasses(result, listOf(PointerEventPass.PostDown))
 
         // Assert
 
@@ -870,13 +890,16 @@
         scaleObserver.resultingScaleChange = .75f
         var result = recognizer.pointerInputHandler.invokeOverPasses(
             listOf(change1, change2),
-            PointerEventPass.InitialDown,
-            PointerEventPass.PreUp,
-            PointerEventPass.PreDown,
-            PointerEventPass.PostUp
+            listOf(
+                PointerEventPass.InitialDown,
+                PointerEventPass.PreUp,
+                PointerEventPass.PreDown,
+                PointerEventPass.PostUp
+            )
         )
         scaleObserver.resultingScaleChange = 1f
-        result = recognizer.pointerInputHandler.invokeOverPasses(result, PointerEventPass.PostDown)
+        result = recognizer.pointerInputHandler
+            .invokeOverPasses(result, listOf(PointerEventPass.PostDown))
 
         // Assert
 
@@ -901,13 +924,16 @@
         scaleObserver.resultingScaleChange = .75f
         var result = recognizer.pointerInputHandler.invokeOverPasses(
             listOf(change1, change2),
-            PointerEventPass.InitialDown,
-            PointerEventPass.PreUp,
-            PointerEventPass.PreDown,
-            PointerEventPass.PostUp
+            listOf(
+                PointerEventPass.InitialDown,
+                PointerEventPass.PreUp,
+                PointerEventPass.PreDown,
+                PointerEventPass.PostUp
+            )
         )
         scaleObserver.resultingScaleChange = 1f
-        result = recognizer.pointerInputHandler.invokeOverPasses(result, PointerEventPass.PostDown)
+        result = recognizer.pointerInputHandler
+            .invokeOverPasses(result, listOf(PointerEventPass.PostDown))
 
         // Assert
 
@@ -932,13 +958,16 @@
         scaleObserver.resultingScaleChange = 2f
         var result = recognizer.pointerInputHandler.invokeOverPasses(
             listOf(change1, change2),
-            PointerEventPass.InitialDown,
-            PointerEventPass.PreUp,
-            PointerEventPass.PreDown,
-            PointerEventPass.PostUp
+            listOf(
+                PointerEventPass.InitialDown,
+                PointerEventPass.PreUp,
+                PointerEventPass.PreDown,
+                PointerEventPass.PostUp
+            )
         )
         scaleObserver.resultingScaleChange = 1f
-        result = recognizer.pointerInputHandler.invokeOverPasses(result, PointerEventPass.PostDown)
+        result = recognizer.pointerInputHandler
+            .invokeOverPasses(result, listOf(PointerEventPass.PostDown))
 
         // Assert
 
@@ -963,13 +992,16 @@
         scaleObserver.resultingScaleChange = .75f
         var result = recognizer.pointerInputHandler.invokeOverPasses(
             listOf(change1, change2),
-            PointerEventPass.InitialDown,
-            PointerEventPass.PreUp,
-            PointerEventPass.PreDown,
-            PointerEventPass.PostUp
+            listOf(
+                PointerEventPass.InitialDown,
+                PointerEventPass.PreUp,
+                PointerEventPass.PreDown,
+                PointerEventPass.PostUp
+            )
         )
         scaleObserver.resultingScaleChange = 1f
-        result = recognizer.pointerInputHandler.invokeOverPasses(result, PointerEventPass.PostDown)
+        result = recognizer.pointerInputHandler
+            .invokeOverPasses(result, listOf(PointerEventPass.PostDown))
 
         // Assert
 
@@ -1043,6 +1075,48 @@
         assertThat(result.count { it.consumed.downChange }).isEqualTo(2)
     }
 
+    // Tests that verify when onCancel should not be called.
+
+    @Test
+    fun cancelHandler_downCancel_onCancelNotCalled() {
+        val down = down()
+        recognizer.pointerInputHandler.invokeOverAllPasses(down)
+        scaleStartBlocked = false
+        recognizer.cancelHandler()
+
+        assertThat(log.filter { it.methodName == "onCancel" }).isEmpty()
+    }
+
+    @Test
+    fun cancelHandler_blockedDownMoveCancel_onCancelNotCalled() {
+        var pointer1 = down(0, x = 1f, y = 1f)
+        var pointer2 = down(0, x = 2f, y = 2f)
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer1, pointer2)
+        scaleStartBlocked = true
+        pointer1 = pointer1.moveBy(10.milliseconds, dx = 1f, dy = 0f)
+        pointer2 = pointer2.moveBy(10.milliseconds, dx = 0f, dy = 0f)
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer1, pointer2)
+        recognizer.cancelHandler()
+
+        assertThat(log.filter { it.methodName == "onCancel" }).isEmpty()
+    }
+
+    // Tests that verify when onCancel should be called.
+
+    @Test
+    fun cancelHandler_downMoveCancel_onCancelCalledOnce() {
+        var pointer1 = down(0, x = 1f, y = 1f)
+        var pointer2 = down(0, x = 2f, y = 2f)
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer1, pointer2)
+        scaleStartBlocked = false
+        pointer1 = pointer1.moveBy(10.milliseconds, dx = 1f, dy = 0f)
+        pointer2 = pointer2.moveBy(10.milliseconds, dx = 0f, dy = 0f)
+        recognizer.pointerInputHandler.invokeOverAllPasses(pointer1, pointer2)
+        recognizer.cancelHandler()
+
+        assertThat(log.count { it.methodName == "onCancel" }).isEqualTo(1)
+    }
+
     data class LogItem(
         val methodName: String,
         val percentageChanged: Float? = null
@@ -1067,5 +1141,10 @@
             log.add(LogItem("onStop"))
             super.onStop()
         }
+
+        override fun onCancel() {
+            log.add(LogItem("onCancel"))
+            super.onCancel()
+        }
     }
 }
\ No newline at end of file
diff --git a/ui/ui-framework/src/test/java/androidx/ui/core/gesture/ScaleSlopExceededGestureDetectorTest.kt b/ui/ui-framework/src/test/java/androidx/ui/core/gesture/ScaleSlopExceededGestureDetectorTest.kt
index 2f9409f..5eea6ba 100644
--- a/ui/ui-framework/src/test/java/androidx/ui/core/gesture/ScaleSlopExceededGestureDetectorTest.kt
+++ b/ui/ui-framework/src/test/java/androidx/ui/core/gesture/ScaleSlopExceededGestureDetectorTest.kt
@@ -655,6 +655,100 @@
         assertThat(onScaleSlopExceededCount).isEqualTo(1)
     }
 
+    // Tests that verify correct cancelling behavior.
+
+    @Test
+    fun cancelHandler_scaleHalfCancelScaleOtherHalf_onScaleSlopExceededNotCalled() {
+
+        // Arrange
+
+        var pointer1 = down(0, 0.milliseconds, 0f, 0f)
+        var pointer2 = down(1, 0L.milliseconds, 1f, 0f)
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(pointer1, pointer2)
+
+        pointer1 = pointer1.moveTo(
+            10.milliseconds,
+            0f,
+            0f
+        )
+        pointer2 = pointer2.moveTo(
+            10.milliseconds,
+            6f,
+            0f
+        )
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(pointer1, pointer2)
+
+        // Act
+
+        mRecognizer.cancelHandler()
+
+        pointer1 = down(0, 0.milliseconds, 0f, 0f)
+        pointer2 = down(1, 0L.milliseconds, 1f, 0f)
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(pointer1, pointer2)
+
+        pointer1 = pointer1.moveTo(
+            10.milliseconds,
+            0f,
+            0f
+        )
+        pointer2 = pointer2.moveTo(
+            10.milliseconds,
+            6.01f,
+            0f
+        )
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(pointer1, pointer2)
+
+        // Assert
+
+        assertThat(onScaleSlopExceededCount).isEqualTo(0)
+    }
+
+    @Test
+    fun cancelHandler_scalePastCancelScalePast_onScaleSlopExceededCalledTwice() {
+
+        // Arrange
+
+        var pointer1 = down(0, 0.milliseconds, 0f, 0f)
+        var pointer2 = down(1, 0L.milliseconds, 1f, 0f)
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(pointer1, pointer2)
+
+        pointer1 = pointer1.moveTo(
+            10.milliseconds,
+            0f,
+            0f
+        )
+        pointer2 = pointer2.moveTo(
+            10.milliseconds,
+            11.01f,
+            0f
+        )
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(pointer1, pointer2)
+
+        // Act
+
+        mRecognizer.cancelHandler()
+
+        pointer1 = down(0, 0.milliseconds, 0f, 0f)
+        pointer2 = down(1, 0L.milliseconds, 1f, 0f)
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(pointer1, pointer2)
+
+        pointer1 = pointer1.moveTo(
+            10.milliseconds,
+            0f,
+            0f
+        )
+        pointer2 = pointer2.moveTo(
+            10.milliseconds,
+            11.01f,
+            0f
+        )
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(pointer1, pointer2)
+
+        // Assert
+
+        assertThat(onScaleSlopExceededCount).isEqualTo(2)
+    }
+
     private fun onPointerInputChanges_2Pointers(
         x1s: Float,
         y1s: Float,
diff --git a/ui/ui-framework/src/test/java/androidx/ui/core/gesture/TouchSlopExceededGestureDetectorTest.kt b/ui/ui-framework/src/test/java/androidx/ui/core/gesture/TouchSlopExceededGestureDetectorTest.kt
index f28da04..16c5d71 100644
--- a/ui/ui-framework/src/test/java/androidx/ui/core/gesture/TouchSlopExceededGestureDetectorTest.kt
+++ b/ui/ui-framework/src/test/java/androidx/ui/core/gesture/TouchSlopExceededGestureDetectorTest.kt
@@ -311,14 +311,16 @@
         val move = down().moveBy(10.milliseconds, TestTouchSlop.toFloat(), 0f)
         mRecognizer.pointerInputHandler.invokeOverPasses(
             listOf(move),
-            PointerEventPass.InitialDown,
-            PointerEventPass.PreUp,
-            PointerEventPass.PreDown,
-            PointerEventPass.PostUp
+            listOf(
+                PointerEventPass.InitialDown,
+                PointerEventPass.PreUp,
+                PointerEventPass.PreDown,
+                PointerEventPass.PostUp
+            )
         )
         val move2 = move.consume(dx = (TestTouchSlop * 2f + 1))
         mRecognizer.pointerInputHandler.invokeOverPasses(
-            listOf(move2),
+            move2,
             PointerEventPass.PostDown
         )
 
@@ -514,8 +516,8 @@
 
         val beyondTouchSlop = (TestTouchSlop + 1).toFloat()
 
-        val pointers = arrayListOf(down(0), down(1), down(2))
-        mRecognizer.pointerInputHandler.invokeOverAllPasses(pointers)
+        val pointers = arrayOf(down(0), down(1), down(2))
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(*pointers)
 
         // Act
 
@@ -538,7 +540,7 @@
                 0f,
                 beyondTouchSlop * 2
             )
-        mRecognizer.pointerInputHandler.invokeOverAllPasses(pointers)
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(*pointers)
 
         // Assert
 
@@ -552,8 +554,8 @@
 
         val beyondTouchSlop = (TestTouchSlop + 1).toFloat()
 
-        val pointers = arrayListOf(down(0), down(1), down(2), down(3), down(4))
-        mRecognizer.pointerInputHandler.invokeOverAllPasses(pointers)
+        val pointers = arrayOf(down(0), down(1), down(2), down(3), down(4))
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(*pointers)
 
         // Act
 
@@ -571,7 +573,7 @@
                 beyondTouchSlop * -1,
                 0f
             )
-        mRecognizer.pointerInputHandler.invokeOverAllPasses(pointers)
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(*pointers)
 
         // Assert
 
@@ -609,10 +611,12 @@
         val move = down().moveBy(10.milliseconds, 0f, 0f).consume(dx = TestTouchSlop + 1f)
         mRecognizer.pointerInputHandler.invokeOverPasses(
             listOf(move),
-            PointerEventPass.InitialDown,
-            PointerEventPass.PreUp,
-            PointerEventPass.PreDown,
-            PointerEventPass.PostUp
+            listOf(
+                PointerEventPass.InitialDown,
+                PointerEventPass.PreUp,
+                PointerEventPass.PreDown,
+                PointerEventPass.PostUp
+            )
         )
 
         // Assert
@@ -629,15 +633,17 @@
         val move = down().moveBy(10.milliseconds, 0f, 0f)
         mRecognizer.pointerInputHandler.invokeOverPasses(
             listOf(move),
-            PointerEventPass.InitialDown,
-            PointerEventPass.PreUp,
-            PointerEventPass.PreDown,
-            PointerEventPass.PostUp
+            listOf(
+                PointerEventPass.InitialDown,
+                PointerEventPass.PreUp,
+                PointerEventPass.PreDown,
+                PointerEventPass.PostUp
+            )
         )
 
         val moveConsumed = move.consume(dx = TestTouchSlop + 1f)
         mRecognizer.pointerInputHandler.invokeOverPasses(
-            listOf(moveConsumed),
+            moveConsumed,
             PointerEventPass.PostDown
         )
 
@@ -658,15 +664,17 @@
         val move = down().moveBy(10.milliseconds, halfSlop.toFloat(), 0f)
         mRecognizer.pointerInputHandler.invokeOverPasses(
             listOf(move),
-            PointerEventPass.InitialDown,
-            PointerEventPass.PreUp,
-            PointerEventPass.PreDown,
-            PointerEventPass.PostUp
+            listOf(
+                PointerEventPass.InitialDown,
+                PointerEventPass.PreUp,
+                PointerEventPass.PreDown,
+                PointerEventPass.PostUp
+            )
         )
 
         val moveConsumed = move.consume(dx = -restOfSlopAndBeyond.toFloat())
         mRecognizer.pointerInputHandler.invokeOverPasses(
-            listOf(moveConsumed),
+            moveConsumed,
             PointerEventPass.PostDown
         )
 
@@ -684,10 +692,12 @@
         val move = down().moveBy(10.milliseconds, TestTouchSlop + 1f, 0f)
         mRecognizer.pointerInputHandler.invokeOverPasses(
             listOf(move),
-            PointerEventPass.InitialDown,
-            PointerEventPass.PreUp,
-            PointerEventPass.PreDown,
-            PointerEventPass.PostUp
+            listOf(
+                PointerEventPass.InitialDown,
+                PointerEventPass.PreUp,
+                PointerEventPass.PreDown,
+                PointerEventPass.PostUp
+            )
         )
 
         // Assert
@@ -704,9 +714,9 @@
 
         // Assert
 
-        assertThat(result[0].consumed.downChange).isFalse()
-        assertThat(result[0].consumed.positionChange.x).isEqualTo(0.px)
-        assertThat(result[0].consumed.positionChange.y).isEqualTo(0.px)
+        assertThat(result.consumed.downChange).isFalse()
+        assertThat(result.consumed.positionChange.x).isEqualTo(0.px)
+        assertThat(result.consumed.positionChange.y).isEqualTo(0.px)
     }
 
     @Test
@@ -720,9 +730,9 @@
 
         // Assert
 
-        assertThat(result[0].consumed.downChange).isFalse()
-        assertThat(result[0].consumed.positionChange.x).isEqualTo(0.px)
-        assertThat(result[0].consumed.positionChange.y).isEqualTo(0.px)
+        assertThat(result.consumed.downChange).isFalse()
+        assertThat(result.consumed.positionChange.x).isEqualTo(0.px)
+        assertThat(result.consumed.positionChange.y).isEqualTo(0.px)
     }
 
     @Test
@@ -739,9 +749,9 @@
 
         // Assert
 
-        assertThat(result[0].consumed.downChange).isFalse()
-        assertThat(result[0].consumed.positionChange.x).isEqualTo(0.px)
-        assertThat(result[0].consumed.positionChange.y).isEqualTo(0.px)
+        assertThat(result.consumed.downChange).isFalse()
+        assertThat(result.consumed.positionChange.x).isEqualTo(0.px)
+        assertThat(result.consumed.positionChange.y).isEqualTo(0.px)
     }
 
     @Test
@@ -755,9 +765,9 @@
 
         // Assert
 
-        assertThat(result[0].consumed.downChange).isFalse()
-        assertThat(result[0].consumed.positionChange.x).isEqualTo(0.px)
-        assertThat(result[0].consumed.positionChange.y).isEqualTo(0.px)
+        assertThat(result.consumed.downChange).isFalse()
+        assertThat(result.consumed.positionChange.x).isEqualTo(0.px)
+        assertThat(result.consumed.positionChange.y).isEqualTo(0.px)
     }
 
     @Test
@@ -774,9 +784,9 @@
 
         // Assert
 
-        assertThat(result[0].consumed.downChange).isFalse()
-        assertThat(result[0].consumed.positionChange.x).isEqualTo(0.px)
-        assertThat(result[0].consumed.positionChange.y).isEqualTo(0.px)
+        assertThat(result.consumed.downChange).isFalse()
+        assertThat(result.consumed.positionChange.x).isEqualTo(0.px)
+        assertThat(result.consumed.positionChange.y).isEqualTo(0.px)
     }
 
     // Verification that TouchSlopExceededGestureDetector resets after up correctly.
@@ -797,4 +807,74 @@
 
         assertThat(onTouchSlopExceededCallCount).isEqualTo(2)
     }
+
+    // Verification that cancellation behavior is correct.
+
+    @Test
+    fun cancelHandler_halfSlopCancelOtherHalfOfSlop_onTouchSlopExceededNotCalled() {
+
+        // Arrange
+
+        var pointer = down(0, 0.milliseconds, 0f, 0f)
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(pointer)
+
+        pointer = pointer.moveTo(
+            10.milliseconds,
+            5f,
+            0f
+        )
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(pointer)
+
+        // Act
+
+        mRecognizer.cancelHandler()
+
+        pointer = down(0, 0.milliseconds, 0f, 0f)
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(pointer)
+
+        pointer = pointer.moveTo(
+            10.milliseconds,
+            5.01f,
+            0f
+        )
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(pointer)
+
+        // Assert
+
+        assertThat(onTouchSlopExceededCallCount).isEqualTo(0)
+    }
+
+    @Test
+    fun cancelHandler_pastSlopCancelPastSlop_onScaleSlopExceededCalledTwice() {
+
+        // Arrange
+
+        var pointer = down(0, 0.milliseconds, 0f, 0f)
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(pointer)
+
+        pointer = pointer.moveTo(
+            10.milliseconds,
+            10.01f,
+            0f
+        )
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(pointer)
+
+        // Act
+
+        mRecognizer.cancelHandler()
+
+        pointer = down(0, 0.milliseconds, 0f, 0f)
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(pointer)
+
+        pointer = pointer.moveTo(
+            10.milliseconds,
+            10.01f,
+            0f
+        )
+        mRecognizer.pointerInputHandler.invokeOverAllPasses(pointer)
+
+        // Assert
+
+        assertThat(onTouchSlopExceededCallCount).isEqualTo(2)
+    }
 }
\ No newline at end of file