Add new gesture detector method for looping over gestures

Fixes: 251260206

Relnote: "A new method, awaitEachGesture(), for gesture detectors
was added. It operates similar to forEachGesture(), but the loop
over gestures operates entirely within the AwaitPointerEventScope
so events can't be lost between iterations.

forEachGesture() has been deprecated in favor of awaitEachGesture()
because it allows events to be lost between gestures."

Test: new tests
Change-Id: Iffc3fb8cf53d0e5eb9b529c023b3e2d29003e86f
diff --git a/compose/foundation/foundation/api/current.txt b/compose/foundation/foundation/api/current.txt
index 4fc8b33..d7dd537 100644
--- a/compose/foundation/foundation/api/current.txt
+++ b/compose/foundation/foundation/api/current.txt
@@ -203,7 +203,8 @@
   }
 
   public final class ForEachGestureKt {
-    method public static suspend Object? forEachGesture(androidx.compose.ui.input.pointer.PointerInputScope, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public static suspend Object? awaitEachGesture(androidx.compose.ui.input.pointer.PointerInputScope, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.AwaitPointerEventScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method @Deprecated public static suspend Object? forEachGesture(androidx.compose.ui.input.pointer.PointerInputScope, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
   }
 
   public final class GestureCancellationException extends java.util.concurrent.CancellationException {
diff --git a/compose/foundation/foundation/api/public_plus_experimental_current.txt b/compose/foundation/foundation/api/public_plus_experimental_current.txt
index b514e7d..d23f77a 100644
--- a/compose/foundation/foundation/api/public_plus_experimental_current.txt
+++ b/compose/foundation/foundation/api/public_plus_experimental_current.txt
@@ -254,7 +254,8 @@
   }
 
   public final class ForEachGestureKt {
-    method public static suspend Object? forEachGesture(androidx.compose.ui.input.pointer.PointerInputScope, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public static suspend Object? awaitEachGesture(androidx.compose.ui.input.pointer.PointerInputScope, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.AwaitPointerEventScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method @Deprecated public static suspend Object? forEachGesture(androidx.compose.ui.input.pointer.PointerInputScope, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
   }
 
   public final class GestureCancellationException extends java.util.concurrent.CancellationException {
diff --git a/compose/foundation/foundation/api/restricted_current.txt b/compose/foundation/foundation/api/restricted_current.txt
index 4fc8b33..d7dd537 100644
--- a/compose/foundation/foundation/api/restricted_current.txt
+++ b/compose/foundation/foundation/api/restricted_current.txt
@@ -203,7 +203,8 @@
   }
 
   public final class ForEachGestureKt {
-    method public static suspend Object? forEachGesture(androidx.compose.ui.input.pointer.PointerInputScope, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public static suspend Object? awaitEachGesture(androidx.compose.ui.input.pointer.PointerInputScope, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.AwaitPointerEventScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method @Deprecated public static suspend Object? forEachGesture(androidx.compose.ui.input.pointer.PointerInputScope, kotlin.jvm.functions.Function2<? super androidx.compose.ui.input.pointer.PointerInputScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
   }
 
   public final class GestureCancellationException extends java.util.concurrent.CancellationException {
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/SuspendingGesturesDemo.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/SuspendingGesturesDemo.kt
index 786b6ed..183a7d9 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/SuspendingGesturesDemo.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/SuspendingGesturesDemo.kt
@@ -21,11 +21,11 @@
 import androidx.compose.foundation.background
 import androidx.compose.foundation.border
 import androidx.compose.foundation.gestures.detectDragGestures
+import androidx.compose.foundation.gestures.awaitEachGesture
 import androidx.compose.foundation.gestures.detectHorizontalDragGestures
 import androidx.compose.foundation.gestures.detectTapGestures
 import androidx.compose.foundation.gestures.detectTransformGestures
 import androidx.compose.foundation.gestures.detectVerticalDragGestures
-import androidx.compose.foundation.gestures.forEachGesture
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
@@ -461,19 +461,17 @@
     var pointerType by remember { mutableStateOf<PointerType?>(null) }
     Box(
         Modifier.pointerInput(Unit) {
-            forEachGesture {
-                awaitPointerEventScope {
-                    val pointer = awaitPointerEvent().changes.first()
-                    pointerType = pointer.type
-                    do {
-                        val event = awaitPointerEvent()
-                    } while (event.changes.first().pressed)
-                    pointerType = null
-                }
+            awaitEachGesture {
+                val pointer = awaitPointerEvent().changes.first()
+                pointerType = pointer.type
+                do {
+                    val event = awaitPointerEvent()
+                } while (event.changes.first().pressed)
+                pointerType = null
             }
         }
     ) {
         Text("Touch or click the area to see what type of input it is.")
         Text("PointerType: ${pointerType ?: ""}", Modifier.align(Alignment.BottomStart))
     }
-}
\ No newline at end of file
+}
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/DragGestureDetectorSamples.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/DragGestureDetectorSamples.kt
index 4ea4c6a..ed0b6f4 100644
--- a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/DragGestureDetectorSamples.kt
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/DragGestureDetectorSamples.kt
@@ -27,10 +27,10 @@
 import androidx.compose.foundation.gestures.awaitVerticalTouchSlopOrCancellation
 import androidx.compose.foundation.gestures.detectDragGestures
 import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress
+import androidx.compose.foundation.gestures.awaitEachGesture
 import androidx.compose.foundation.gestures.detectHorizontalDragGestures
 import androidx.compose.foundation.gestures.detectVerticalDragGestures
 import androidx.compose.foundation.gestures.drag
-import androidx.compose.foundation.gestures.forEachGesture
 import androidx.compose.foundation.gestures.horizontalDrag
 import androidx.compose.foundation.gestures.verticalDrag
 import androidx.compose.foundation.layout.Box
@@ -74,26 +74,24 @@
                 .width(50.dp)
                 .background(Color.Blue)
                 .pointerInput(Unit) {
-                    forEachGesture {
-                        awaitPointerEventScope {
-                            val down = awaitFirstDown()
-                            var change =
-                                awaitHorizontalTouchSlopOrCancellation(down.id) { change, over ->
-                                    val originalX = offsetX.value
-                                    val newValue =
-                                        (originalX + over).coerceIn(0f, width - 50.dp.toPx())
-                                    change.consume()
-                                    offsetX.value = newValue
-                                }
-                            while (change != null && change.pressed) {
-                                change = awaitHorizontalDragOrCancellation(change.id)
-                                if (change != null && change.pressed) {
-                                    val originalX = offsetX.value
-                                    val newValue = (originalX + change.positionChange().x)
-                                        .coerceIn(0f, width - 50.dp.toPx())
-                                    change.consume()
-                                    offsetX.value = newValue
-                                }
+                    awaitEachGesture {
+                        val down = awaitFirstDown()
+                        var change =
+                            awaitHorizontalTouchSlopOrCancellation(down.id) { change, over ->
+                                val originalX = offsetX.value
+                                val newValue =
+                                    (originalX + over).coerceIn(0f, width - 50.dp.toPx())
+                                change.consume()
+                                offsetX.value = newValue
+                            }
+                        while (change != null && change.pressed) {
+                            change = awaitHorizontalDragOrCancellation(change.id)
+                            if (change != null && change.pressed) {
+                                val originalX = offsetX.value
+                                val newValue = (originalX + change.positionChange().x)
+                                    .coerceIn(0f, width - 50.dp.toPx())
+                                change.consume()
+                                offsetX.value = newValue
                             }
                         }
                     }
@@ -118,25 +116,23 @@
                 .width(50.dp)
                 .background(Color.Blue)
                 .pointerInput(Unit) {
-                    forEachGesture {
-                        awaitPointerEventScope {
-                            val down = awaitFirstDown()
-                            val change =
-                                awaitHorizontalTouchSlopOrCancellation(down.id) { change, over ->
-                                    val originalX = offsetX.value
-                                    val newValue =
-                                        (originalX + over).coerceIn(0f, width - 50.dp.toPx())
-                                    change.consume()
-                                    offsetX.value = newValue
-                                }
-                            if (change != null) {
-                                horizontalDrag(change.id) {
-                                    val originalX = offsetX.value
-                                    val newValue = (originalX + it.positionChange().x)
-                                        .coerceIn(0f, width - 50.dp.toPx())
-                                    it.consume()
-                                    offsetX.value = newValue
-                                }
+                    awaitEachGesture {
+                        val down = awaitFirstDown()
+                        val change =
+                            awaitHorizontalTouchSlopOrCancellation(down.id) { change, over ->
+                                val originalX = offsetX.value
+                                val newValue =
+                                    (originalX + over).coerceIn(0f, width - 50.dp.toPx())
+                                change.consume()
+                                offsetX.value = newValue
+                            }
+                        if (change != null) {
+                            horizontalDrag(change.id) {
+                                val originalX = offsetX.value
+                                val newValue = (originalX + it.positionChange().x)
+                                    .coerceIn(0f, width - 50.dp.toPx())
+                                it.consume()
+                                offsetX.value = newValue
                             }
                         }
                     }
@@ -187,26 +183,24 @@
                 .height(50.dp)
                 .background(Color.Blue)
                 .pointerInput(Unit) {
-                    forEachGesture {
-                        awaitPointerEventScope {
-                            val down = awaitFirstDown()
-                            var change =
-                                awaitVerticalTouchSlopOrCancellation(down.id) { change, over ->
-                                    val originalY = offsetY.value
-                                    val newValue = (originalY + over)
-                                        .coerceIn(0f, height - 50.dp.toPx())
-                                    change.consume()
-                                    offsetY.value = newValue
-                                }
-                            while (change != null && change.pressed) {
-                                change = awaitVerticalDragOrCancellation(change.id)
-                                if (change != null && change.pressed) {
-                                    val originalY = offsetY.value
-                                    val newValue = (originalY + change.positionChange().y)
-                                        .coerceIn(0f, height - 50.dp.toPx())
-                                    change.consume()
-                                    offsetY.value = newValue
-                                }
+                    awaitEachGesture {
+                        val down = awaitFirstDown()
+                        var change =
+                            awaitVerticalTouchSlopOrCancellation(down.id) { change, over ->
+                                val originalY = offsetY.value
+                                val newValue = (originalY + over)
+                                    .coerceIn(0f, height - 50.dp.toPx())
+                                change.consume()
+                                offsetY.value = newValue
+                            }
+                        while (change != null && change.pressed) {
+                            change = awaitVerticalDragOrCancellation(change.id)
+                            if (change != null && change.pressed) {
+                                val originalY = offsetY.value
+                                val newValue = (originalY + change.positionChange().y)
+                                    .coerceIn(0f, height - 50.dp.toPx())
+                                change.consume()
+                                offsetY.value = newValue
                             }
                         }
                     }
@@ -231,25 +225,23 @@
                 .height(50.dp)
                 .background(Color.Blue)
                 .pointerInput(Unit) {
-                    forEachGesture {
-                        awaitPointerEventScope {
-                            val down = awaitFirstDown()
-                            val change =
-                                awaitVerticalTouchSlopOrCancellation(down.id) { change, over ->
-                                    val originalY = offsetY.value
-                                    val newValue = (originalY + over)
-                                        .coerceIn(0f, height - 50.dp.toPx())
-                                    change.consume()
-                                    offsetY.value = newValue
-                                }
-                            if (change != null) {
-                                verticalDrag(change.id) {
-                                    val originalY = offsetY.value
-                                    val newValue = (originalY + it.positionChange().y)
-                                        .coerceIn(0f, height - 50.dp.toPx())
-                                    it.consume()
-                                    offsetY.value = newValue
-                                }
+                    awaitEachGesture {
+                        val down = awaitFirstDown()
+                        val change =
+                            awaitVerticalTouchSlopOrCancellation(down.id) { change, over ->
+                                val originalY = offsetY.value
+                                val newValue = (originalY + over)
+                                    .coerceIn(0f, height - 50.dp.toPx())
+                                change.consume()
+                                offsetY.value = newValue
+                            }
+                        if (change != null) {
+                            verticalDrag(change.id) {
+                                val originalY = offsetY.value
+                                val newValue = (originalY + it.positionChange().y)
+                                    .coerceIn(0f, height - 50.dp.toPx())
+                                it.consume()
+                                offsetY.value = newValue
                             }
                         }
                     }
@@ -299,12 +291,24 @@
                 .size(50.dp)
                 .background(Color.Blue)
                 .pointerInput(Unit) {
-                    forEachGesture {
-                        awaitPointerEventScope {
-                            val down = awaitFirstDown()
-                            var change = awaitTouchSlopOrCancellation(down.id) { change, over ->
+                    awaitEachGesture {
+                        val down = awaitFirstDown()
+                        var change = awaitTouchSlopOrCancellation(down.id) { change, over ->
+                            val original = Offset(offsetX.value, offsetY.value)
+                            val summed = original + over
+                            val newValue = Offset(
+                                x = summed.x.coerceIn(0f, size.width - 50.dp.toPx()),
+                                y = summed.y.coerceIn(0f, size.height - 50.dp.toPx())
+                            )
+                            change.consume()
+                            offsetX.value = newValue.x
+                            offsetY.value = newValue.y
+                        }
+                        while (change != null && change.pressed) {
+                            change = awaitDragOrCancellation(change.id)
+                            if (change != null && change.pressed) {
                                 val original = Offset(offsetX.value, offsetY.value)
-                                val summed = original + over
+                                val summed = original + change.positionChange()
                                 val newValue = Offset(
                                     x = summed.x.coerceIn(0f, size.width - 50.dp.toPx()),
                                     y = summed.y.coerceIn(0f, size.height - 50.dp.toPx())
@@ -313,20 +317,6 @@
                                 offsetX.value = newValue.x
                                 offsetY.value = newValue.y
                             }
-                            while (change != null && change.pressed) {
-                                change = awaitDragOrCancellation(change.id)
-                                if (change != null && change.pressed) {
-                                    val original = Offset(offsetX.value, offsetY.value)
-                                    val summed = original + change.positionChange()
-                                    val newValue = Offset(
-                                        x = summed.x.coerceIn(0f, size.width - 50.dp.toPx()),
-                                        y = summed.y.coerceIn(0f, size.height - 50.dp.toPx())
-                                    )
-                                    change.consume()
-                                    offsetX.value = newValue.x
-                                    offsetY.value = newValue.y
-                                }
-                            }
                         }
                     }
                 }
@@ -349,33 +339,31 @@
                 .size(50.dp)
                 .background(Color.Blue)
                 .pointerInput(Unit) {
-                    forEachGesture {
-                        awaitPointerEventScope {
-                            val down = awaitFirstDown()
-                            val change = awaitTouchSlopOrCancellation(down.id) { change, over ->
+                    awaitEachGesture {
+                        val down = awaitFirstDown()
+                        val change = awaitTouchSlopOrCancellation(down.id) { change, over ->
+                            val original = Offset(offsetX.value, offsetY.value)
+                            val summed = original + over
+                            val newValue = Offset(
+                                x = summed.x.coerceIn(0f, size.width - 50.dp.toPx()),
+                                y = summed.y.coerceIn(0f, size.height - 50.dp.toPx())
+                            )
+                            change.consume()
+                            offsetX.value = newValue.x
+                            offsetY.value = newValue.y
+                        }
+                        if (change != null) {
+                            drag(change.id) {
                                 val original = Offset(offsetX.value, offsetY.value)
-                                val summed = original + over
+                                val summed = original + it.positionChange()
                                 val newValue = Offset(
                                     x = summed.x.coerceIn(0f, size.width - 50.dp.toPx()),
                                     y = summed.y.coerceIn(0f, size.height - 50.dp.toPx())
                                 )
-                                change.consume()
+                                it.consume()
                                 offsetX.value = newValue.x
                                 offsetY.value = newValue.y
                             }
-                            if (change != null) {
-                                drag(change.id) {
-                                    val original = Offset(offsetX.value, offsetY.value)
-                                    val summed = original + it.positionChange()
-                                    val newValue = Offset(
-                                        x = summed.x.coerceIn(0f, size.width - 50.dp.toPx()),
-                                        y = summed.y.coerceIn(0f, size.height - 50.dp.toPx())
-                                    )
-                                    it.consume()
-                                    offsetX.value = newValue.x
-                                    offsetY.value = newValue.y
-                                }
-                            }
                         }
                     }
                 }
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/TapGestureSamples.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/TapGestureSamples.kt
index 2e759f3..9255c67 100644
--- a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/TapGestureSamples.kt
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/TapGestureSamples.kt
@@ -22,7 +22,7 @@
 import androidx.compose.foundation.border
 import androidx.compose.foundation.gestures.awaitFirstDown
 import androidx.compose.foundation.gestures.awaitLongPressOrCancellation
-import androidx.compose.foundation.gestures.forEachGesture
+import androidx.compose.foundation.gestures.awaitEachGesture
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxSize
@@ -56,12 +56,10 @@
                 .wrapContentSize(Alignment.Center)
                 .size(192.dp)
                 .pointerInput(Unit) {
-                    forEachGesture {
-                        awaitPointerEventScope {
-                            val down = awaitFirstDown(requireUnconsumed = false)
-                            awaitLongPressOrCancellation(down.id)?.let {
-                                count++
-                            }
+                    awaitEachGesture {
+                        val down = awaitFirstDown(requireUnconsumed = false)
+                        awaitLongPressOrCancellation(down.id)?.let {
+                            count++
                         }
                     }
                 }
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/TransformGestureSamples.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/TransformGestureSamples.kt
index 279d7ea..d4940a6 100644
--- a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/TransformGestureSamples.kt
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/TransformGestureSamples.kt
@@ -24,8 +24,8 @@
 import androidx.compose.foundation.gestures.calculateRotation
 import androidx.compose.foundation.gestures.calculateZoom
 import androidx.compose.foundation.gestures.detectTransformGestures
-import androidx.compose.foundation.gestures.forEachGesture
 import androidx.compose.foundation.gestures.awaitFirstDown
+import androidx.compose.foundation.gestures.awaitEachGesture
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.offset
@@ -113,15 +113,13 @@
             .graphicsLayer(rotationZ = angle)
             .background(Color.Blue)
             .pointerInput(Unit) {
-                forEachGesture {
-                    awaitPointerEventScope {
-                        awaitFirstDown()
-                        do {
-                            val event = awaitPointerEvent()
-                            val rotation = event.calculateRotation()
-                            angle += rotation
-                        } while (event.changes.any { it.pressed })
-                    }
+                awaitEachGesture {
+                    awaitFirstDown()
+                    do {
+                        val event = awaitPointerEvent()
+                        val rotation = event.calculateRotation()
+                        angle += rotation
+                    } while (event.changes.any { it.pressed })
                 }
             }
             .fillMaxSize()
@@ -137,14 +135,12 @@
             .graphicsLayer(scaleX = zoom, scaleY = zoom)
             .background(Color.Blue)
             .pointerInput(Unit) {
-                forEachGesture {
-                    awaitPointerEventScope {
-                        awaitFirstDown()
-                        do {
-                            val event = awaitPointerEvent()
-                            zoom *= event.calculateZoom()
-                        } while (event.changes.any { it.pressed })
-                    }
+                awaitEachGesture {
+                    awaitFirstDown()
+                    do {
+                        val event = awaitPointerEvent()
+                        zoom *= event.calculateZoom()
+                    } while (event.changes.any { it.pressed })
                 }
             }
             .fillMaxSize()
@@ -162,16 +158,14 @@
             .graphicsLayer()
             .background(Color.Blue)
             .pointerInput(Unit) {
-                forEachGesture {
-                    awaitPointerEventScope {
-                        awaitFirstDown()
-                        do {
-                            val event = awaitPointerEvent()
-                            val offset = event.calculatePan()
-                            offsetX.value += offset.x
-                            offsetY.value += offset.y
-                        } while (event.changes.any { it.pressed })
-                    }
+                awaitEachGesture {
+                    awaitFirstDown()
+                    do {
+                        val event = awaitPointerEvent()
+                        val offset = event.calculatePan()
+                        offsetX.value += offset.x
+                        offsetY.value += offset.y
+                    } while (event.changes.any { it.pressed })
                 }
             }
             .fillMaxSize()
@@ -190,23 +184,21 @@
                 drawCircle(Color.Blue, centroidSize, center = position)
             }
             .pointerInput(Unit) {
-                forEachGesture {
-                    awaitPointerEventScope {
-                        awaitFirstDown().also {
-                            position = it.position
-                        }
-                        do {
-                            val event = awaitPointerEvent()
-                            val size = event.calculateCentroidSize()
-                            if (size != 0f) {
-                                centroidSize = event.calculateCentroidSize()
-                            }
-                            val centroid = event.calculateCentroid()
-                            if (centroid != Offset.Unspecified) {
-                                position = centroid
-                            }
-                        } while (event.changes.any { it.pressed })
+                awaitEachGesture {
+                    awaitFirstDown().also {
+                        position = it.position
                     }
+                    do {
+                        val event = awaitPointerEvent()
+                        val size = event.calculateCentroidSize()
+                        if (size != 0f) {
+                            centroidSize = event.calculateCentroidSize()
+                        }
+                        val centroid = event.calculateCentroid()
+                        if (centroid != Offset.Unspecified) {
+                            position = centroid
+                        }
+                    } while (event.changes.any { it.pressed })
                 }
             }
             .fillMaxSize()
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/gesture/AwaitEachGestureTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/gesture/AwaitEachGestureTest.kt
new file mode 100644
index 0000000..ef7040b
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/gesture/AwaitEachGestureTest.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.gesture
+
+import androidx.compose.foundation.gestures.awaitEachGesture
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.size
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.pointer.PointerEventType
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.coroutineScope
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class AwaitEachGestureTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun awaitEachGestureInternalCancellation() {
+        val inputLatch = CountDownLatch(1)
+        rule.setContent {
+            Box(
+                Modifier.pointerInput(Unit) {
+                    try {
+                        var count = 0
+                        coroutineScope {
+                            awaitEachGesture {
+                                when (count++) {
+                                    0 -> Unit // continue
+                                    1 -> throw CancellationException("internal exception")
+                                    else -> {
+                                        // detectGestures will loop infinitely with nothing in the
+                                        // middle so wait for cancellation
+                                        cancel("really canceled")
+                                    }
+                                }
+                            }
+                        }
+                    } catch (cancellationException: CancellationException) {
+                        assertWithMessage("The internal exception shouldn't cancel detectGestures")
+                            .that(cancellationException.message)
+                            .isEqualTo("really canceled")
+                    }
+                    inputLatch.countDown()
+                }.size(10.dp)
+            )
+        }
+        rule.waitForIdle()
+        assertThat(inputLatch.await(1, TimeUnit.SECONDS)).isTrue()
+    }
+
+    @Test
+    fun awaitEachGestureLoops() {
+        val events = mutableListOf<PointerEventType>()
+        val tag = "input rect"
+        rule.setContent {
+            Box(
+                Modifier.fillMaxSize()
+                    .testTag(tag)
+                    .pointerInput(Unit) {
+                        awaitEachGesture {
+                            val event = awaitPointerEvent()
+                            events += event.type
+                        }
+                    }
+            )
+        }
+
+        rule.onNodeWithTag(tag).performTouchInput {
+            down(Offset.Zero)
+            moveBy(Offset(10f, 10f))
+            up()
+            down(Offset(3f, 3f))
+            moveBy(Offset(10f, 10f))
+            moveBy(Offset(1f, 1f))
+            up()
+        }
+        assertThat(events).hasSize(2)
+        assertThat(events).containsExactly(PointerEventType.Press, PointerEventType.Press)
+    }
+}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/gesture/AwaitTouchEventTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/gesture/AwaitTouchEventTest.kt
index 52d0430..591e968 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/gesture/AwaitTouchEventTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/gesture/AwaitTouchEventTest.kt
@@ -18,7 +18,7 @@
 
 import androidx.compose.foundation.gestures.awaitFirstDown
 import androidx.compose.foundation.gestures.awaitLongPressOrCancellation
-import androidx.compose.foundation.gestures.forEachGesture
+import androidx.compose.foundation.gestures.awaitEachGesture
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.text.BasicText
 import androidx.compose.ui.Modifier
@@ -73,12 +73,10 @@
                     modifier = Modifier
                         .testTag("myLongPress")
                         .pointerInput(Unit) {
-                            forEachGesture {
-                                awaitPointerEventScope {
-                                    val down = awaitFirstDown(requireUnconsumed = false)
-                                    awaitLongPressOrCancellation(down.id)?.let {
-                                        counter++
-                                    }
+                            awaitEachGesture {
+                                val down = awaitFirstDown(requireUnconsumed = false)
+                                awaitLongPressOrCancellation(down.id)?.let {
+                                    counter++
                                 }
                             }
                         }
@@ -111,12 +109,10 @@
                     modifier = Modifier
                         .testTag("myLongPress")
                         .pointerInput(Unit) {
-                            forEachGesture {
-                                awaitPointerEventScope {
-                                    val down = awaitFirstDown(requireUnconsumed = false)
-                                    awaitLongPressOrCancellation(down.id)?.let {
-                                        counter++
-                                    }
+                            awaitEachGesture {
+                                val down = awaitFirstDown(requireUnconsumed = false)
+                                awaitLongPressOrCancellation(down.id)?.let {
+                                    counter++
                                 }
                             }
                         }
@@ -149,12 +145,10 @@
                     modifier = Modifier
                         .testTag("myLongPress")
                         .pointerInput(Unit) {
-                            forEachGesture {
-                                awaitPointerEventScope {
-                                    val down = awaitFirstDown(requireUnconsumed = false)
-                                    awaitLongPressOrCancellation(down.id)?.let {
-                                        counter++
-                                    }
+                            awaitEachGesture {
+                                val down = awaitFirstDown(requireUnconsumed = false)
+                                awaitLongPressOrCancellation(down.id)?.let {
+                                    counter++
                                 }
                             }
                         }
@@ -197,12 +191,10 @@
                     modifier = Modifier
                         .testTag("myLongPress")
                         .pointerInput(Unit) {
-                            forEachGesture {
-                                awaitPointerEventScope {
-                                    val down = awaitFirstDown(requireUnconsumed = false)
-                                    awaitLongPressOrCancellation(down.id)?.let {
-                                        counter++
-                                    }
+                            awaitEachGesture {
+                                val down = awaitFirstDown(requireUnconsumed = false)
+                                awaitLongPressOrCancellation(down.id)?.let {
+                                    counter++
                                 }
                             }
                         }
@@ -245,12 +237,10 @@
                     modifier = Modifier
                         .testTag("myLongPress")
                         .pointerInput(Unit) {
-                            forEachGesture {
-                                awaitPointerEventScope {
-                                    val down = awaitFirstDown(requireUnconsumed = false)
-                                    awaitLongPressOrCancellation(down.id)?.let {
-                                        counter++
-                                    }
+                            awaitEachGesture {
+                                val down = awaitFirstDown(requireUnconsumed = false)
+                                awaitLongPressOrCancellation(down.id)?.let {
+                                    counter++
                                 }
                             }
                         }
@@ -301,12 +291,10 @@
                     modifier = Modifier
                         .testTag("myLongPress")
                         .pointerInput(Unit) {
-                            forEachGesture {
-                                awaitPointerEventScope {
-                                    val down = awaitFirstDown(requireUnconsumed = false)
-                                    awaitLongPressOrCancellation(down.id)?.let {
-                                        counter++
-                                    }
+                            awaitEachGesture {
+                                val down = awaitFirstDown(requireUnconsumed = false)
+                                awaitLongPressOrCancellation(down.id)?.let {
+                                    counter++
                                 }
                             }
                         }
@@ -358,12 +346,10 @@
                     modifier = Modifier
                         .testTag("myLongPress")
                         .pointerInput(Unit) {
-                            forEachGesture {
-                                awaitPointerEventScope {
-                                    val down = awaitFirstDown(requireUnconsumed = false)
-                                    awaitLongPressOrCancellation(down.id)?.let {
-                                        counter++
-                                    }
+                            awaitEachGesture {
+                                val down = awaitFirstDown(requireUnconsumed = false)
+                                awaitLongPressOrCancellation(down.id)?.let {
+                                    counter++
                                 }
                             }
                         }
@@ -406,13 +392,11 @@
                 modifier = Modifier
                     .testTag("MyLongPressParent")
                     .pointerInput(Unit) {
-                        forEachGesture() {
-                            awaitPointerEventScope {
-                                while (true) {
-                                    val event = awaitPointerEvent(PointerEventPass.Final)
-                                    if (event.type == PointerEventType.Move) {
-                                        event.changes[0].consume()
-                                    }
+                        awaitEachGesture {
+                            while (true) {
+                                val event = awaitPointerEvent(PointerEventPass.Final)
+                                if (event.type == PointerEventType.Move) {
+                                    event.changes[0].consume()
                                 }
                             }
                         }
@@ -423,12 +407,10 @@
                     modifier = Modifier
                         .testTag("myLongPress")
                         .pointerInput(Unit) {
-                            forEachGesture {
-                                awaitPointerEventScope {
-                                    val down = awaitFirstDown(requireUnconsumed = false)
-                                    awaitLongPressOrCancellation(down.id)?.let {
-                                        counter++
-                                    }
+                            awaitEachGesture {
+                                val down = awaitFirstDown(requireUnconsumed = false)
+                                awaitLongPressOrCancellation(down.id)?.let {
+                                    counter++
                                 }
                             }
                         }
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/gesture/ForEachGestureTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/gesture/ForEachGestureTest.kt
index 123be5e..2fcb00d 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/gesture/ForEachGestureTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/gesture/ForEachGestureTest.kt
@@ -38,6 +38,7 @@
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.launch
 
+@Suppress("DEPRECATION")
 @MediumTest
 @RunWith(AndroidJUnit4::class)
 class ForEachGestureTest {
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/AndroidOverscroll.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/AndroidOverscroll.kt
index cf212ef..32047cf 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/AndroidOverscroll.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/AndroidOverscroll.kt
@@ -25,7 +25,7 @@
 import androidx.compose.foundation.EdgeEffectCompat.onPullDistanceCompat
 import androidx.compose.foundation.EdgeEffectCompat.onReleaseWithOppositeDelta
 import androidx.compose.foundation.gestures.awaitFirstDown
-import androidx.compose.foundation.gestures.forEachGesture
+import androidx.compose.foundation.gestures.awaitEachGesture
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.neverEqualPolicy
@@ -319,27 +319,25 @@
     override val effectModifier: Modifier = Modifier
         .then(StretchOverscrollNonClippingLayer)
         .pointerInput(Unit) {
-            forEachGesture {
-                awaitPointerEventScope {
-                    val down = awaitFirstDown(requireUnconsumed = false)
-                    pointerId = down.id
-                    pointerPosition = down.position
-                    do {
-                        val pressedChanges = awaitPointerEvent().changes.fastFilter { it.pressed }
-                        // If the same ID we are already tracking is down, use that. Otherwise, use
-                        // the next down, to move the overscroll to the next pointer.
-                        val change = pressedChanges
-                            .fastFirstOrNull { it.id == pointerId } ?: pressedChanges.firstOrNull()
-                        if (change != null) {
-                            // Update the id if we are now tracking a new down
-                            pointerId = change.id
-                            pointerPosition = change.position
-                        }
-                    } while (pressedChanges.isNotEmpty())
-                    pointerId = null
-                    // Explicitly not resetting the pointer position until the next down, so we
-                    // don't change any existing effects
-                }
+            awaitEachGesture {
+                val down = awaitFirstDown(requireUnconsumed = false)
+                pointerId = down.id
+                pointerPosition = down.position
+                do {
+                    val pressedChanges = awaitPointerEvent().changes.fastFilter { it.pressed }
+                    // If the same ID we are already tracking is down, use that. Otherwise, use
+                    // the next down, to move the overscroll to the next pointer.
+                    val change = pressedChanges
+                        .fastFirstOrNull { it.id == pointerId } ?: pressedChanges.firstOrNull()
+                    if (change != null) {
+                        // Update the id if we are now tracking a new down
+                        pointerId = change.id
+                        pointerPosition = change.position
+                    }
+                } while (pressedChanges.isNotEmpty())
+                pointerId = null
+                // Explicitly not resetting the pointer position until the next down, so we
+                // don't change any existing effects
             }
         }
         .onSizeChanged(onNewSize)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/DragGestureDetector.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/DragGestureDetector.kt
index fb26f90..7aed630 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/DragGestureDetector.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/DragGestureDetector.kt
@@ -172,34 +172,32 @@
     onDragCancel: () -> Unit = { },
     onDrag: (change: PointerInputChange, dragAmount: Offset) -> Unit
 ) {
-    forEachGesture {
-        awaitPointerEventScope {
-            val down = awaitFirstDown(requireUnconsumed = false)
-            var drag: PointerInputChange?
-            var overSlop = Offset.Zero
-            do {
-                drag = awaitPointerSlopOrCancellation(
-                    down.id,
-                    down.type,
-                    triggerOnMainAxisSlop = false
-                ) { change, over ->
-                    change.consume()
-                    overSlop = over
+    awaitEachGesture {
+        val down = awaitFirstDown(requireUnconsumed = false)
+        var drag: PointerInputChange?
+        var overSlop = Offset.Zero
+        do {
+            drag = awaitPointerSlopOrCancellation(
+                down.id,
+                down.type,
+                triggerOnMainAxisSlop = false
+            ) { change, over ->
+                change.consume()
+                overSlop = over
+            }
+        } while (drag != null && !drag.isConsumed)
+        if (drag != null) {
+            onDragStart.invoke(drag.position)
+            onDrag(drag, overSlop)
+            if (
+                !drag(drag.id) {
+                    onDrag(it, it.positionChange())
+                    it.consume()
                 }
-            } while (drag != null && !drag.isConsumed)
-            if (drag != null) {
-                onDragStart.invoke(drag.position)
-                onDrag(drag, overSlop)
-                if (
-                    !drag(drag.id) {
-                        onDrag(it, it.positionChange())
-                        it.consume()
-                    }
-                ) {
-                    onDragCancel()
-                } else {
-                    onDragEnd()
-                }
+            ) {
+                onDragCancel()
+            } else {
+                onDragEnd()
             }
         }
     }
@@ -232,28 +230,26 @@
     onDragCancel: () -> Unit = { },
     onDrag: (change: PointerInputChange, dragAmount: Offset) -> Unit
 ) {
-    forEachGesture {
+    awaitEachGesture {
         try {
-            awaitPointerEventScope {
-                val down = awaitFirstDown(requireUnconsumed = false)
-                val drag = awaitLongPressOrCancellation(down.id)
-                if (drag != null) {
-                    onDragStart.invoke(drag.position)
+            val down = awaitFirstDown(requireUnconsumed = false)
+            val drag = awaitLongPressOrCancellation(down.id)
+            if (drag != null) {
+                onDragStart.invoke(drag.position)
 
-                    if (
-                        drag(drag.id) {
-                            onDrag(it, it.positionChange())
-                            it.consume()
-                        }
-                    ) {
-                        // consume up if we quit drag gracefully with the up
-                        currentEvent.changes.fastForEach {
-                            if (it.changedToUp()) it.consume()
-                        }
-                        onDragEnd()
-                    } else {
-                        onDragCancel()
+                if (
+                    drag(drag.id) {
+                        onDrag(it, it.positionChange())
+                        it.consume()
                     }
+                ) {
+                    // consume up if we quit drag gracefully with the up
+                    currentEvent.changes.fastForEach {
+                        if (it.changedToUp()) it.consume()
+                    }
+                    onDragEnd()
+                } else {
+                    onDragCancel()
                 }
             }
         } catch (c: CancellationException) {
@@ -391,27 +387,25 @@
     onDragCancel: () -> Unit = { },
     onVerticalDrag: (change: PointerInputChange, dragAmount: Float) -> Unit
 ) {
-    forEachGesture {
-        awaitPointerEventScope {
-            val down = awaitFirstDown(requireUnconsumed = false)
-            var overSlop = 0f
-            val drag = awaitVerticalPointerSlopOrCancellation(down.id, down.type) { change, over ->
-                change.consume()
-                overSlop = over
-            }
-            if (drag != null) {
-                onDragStart.invoke(drag.position)
-                onVerticalDrag.invoke(drag, overSlop)
-                if (
-                    verticalDrag(drag.id) {
-                        onVerticalDrag(it, it.positionChange().y)
-                        it.consume()
-                    }
-                ) {
-                    onDragEnd()
-                } else {
-                    onDragCancel()
+    awaitEachGesture {
+        val down = awaitFirstDown(requireUnconsumed = false)
+        var overSlop = 0f
+        val drag = awaitVerticalPointerSlopOrCancellation(down.id, down.type) { change, over ->
+            change.consume()
+            overSlop = over
+        }
+        if (drag != null) {
+            onDragStart.invoke(drag.position)
+            onVerticalDrag.invoke(drag, overSlop)
+            if (
+                verticalDrag(drag.id) {
+                    onVerticalDrag(it, it.positionChange().y)
+                    it.consume()
                 }
+            ) {
+                onDragEnd()
+            } else {
+                onDragCancel()
             }
         }
     }
@@ -541,30 +535,28 @@
     onDragCancel: () -> Unit = { },
     onHorizontalDrag: (change: PointerInputChange, dragAmount: Float) -> Unit
 ) {
-    forEachGesture {
-        awaitPointerEventScope {
-            val down = awaitFirstDown(requireUnconsumed = false)
-            var overSlop = 0f
-            val drag = awaitHorizontalPointerSlopOrCancellation(
-                down.id,
-                down.type
-            ) { change, over ->
-                change.consume()
-                overSlop = over
-            }
-            if (drag != null) {
-                onDragStart.invoke(drag.position)
-                onHorizontalDrag(drag, overSlop)
-                if (
-                    horizontalDrag(drag.id) {
-                        onHorizontalDrag(it, it.positionChange().x)
-                        it.consume()
-                    }
-                ) {
-                    onDragEnd()
-                } else {
-                    onDragCancel()
+    awaitEachGesture {
+        val down = awaitFirstDown(requireUnconsumed = false)
+        var overSlop = 0f
+        val drag = awaitHorizontalPointerSlopOrCancellation(
+            down.id,
+            down.type
+        ) { change, over ->
+            change.consume()
+            overSlop = over
+        }
+        if (drag != null) {
+            onDragStart.invoke(drag.position)
+            onHorizontalDrag(drag, overSlop)
+            if (
+                horizontalDrag(drag.id) {
+                    onHorizontalDrag(it, it.positionChange().x)
+                    it.consume()
                 }
+            ) {
+                onDragEnd()
+            } else {
+                onDragCancel()
             }
         }
     }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/ForEachGesture.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/ForEachGesture.kt
index c13e405..4901cab 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/ForEachGesture.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/ForEachGesture.kt
@@ -33,7 +33,14 @@
  * Repeatedly calls [block] to handle gestures. If there is a [CancellationException],
  * it will wait until all pointers are raised before another gesture is detected, or it
  * exits if [isActive] is `false`.
+ *
+ * [awaitEachGesture] does the same thing without the possibility of missing events between
+ * gestures, but also lacks the ability to call arbitrary suspending functions within [block].
  */
+@Deprecated(
+    message = "Use awaitEachGesture instead. forEachGesture() can drop events between gestures.",
+    replaceWith = ReplaceWith("awaitEachGesture(block)")
+)
 suspend fun PointerInputScope.forEachGesture(block: suspend PointerInputScope.() -> Unit) {
     val currentContext = currentCoroutineContext()
     while (currentContext.isActive) {
@@ -79,4 +86,36 @@
             val events = awaitPointerEvent(PointerEventPass.Final)
         } while (events.changes.fastAny { it.pressed })
     }
+}
+
+/**
+ * Repeatedly calls [block] to handle gestures. If there is a [CancellationException],
+ * it will wait until all pointers are raised before another gesture is detected, or it
+ * exits if [isActive] is `false`.
+ *
+ * [block] is run within [PointerInputScope.awaitPointerEventScope] and will loop entirely
+ * within the [AwaitPointerEventScope] so events will not be lost between gestures.
+ */
+suspend fun PointerInputScope.awaitEachGesture(block: suspend AwaitPointerEventScope.() -> Unit) {
+    val currentContext = currentCoroutineContext()
+    awaitPointerEventScope {
+        while (currentContext.isActive) {
+            try {
+                block()
+
+                // Wait for all pointers to be up. Gestures start when a finger goes down.
+                awaitAllPointersUp()
+            } catch (e: CancellationException) {
+                if (currentContext.isActive) {
+                    // The current gesture was canceled. Wait for all fingers to be "up" before
+                    // looping again.
+                    awaitAllPointersUp()
+                } else {
+                    // detectGesture was cancelled externally. Rethrow the cancellation exception to
+                    // propagate it upwards.
+                    throw e
+                }
+            }
+        }
+    }
 }
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/TapGestureDetector.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/TapGestureDetector.kt
index b0158ed..13695ce 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/TapGestureDetector.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/TapGestureDetector.kt
@@ -96,73 +96,87 @@
     // cancel/up events as we're only require down events
     val pressScope = PressGestureScopeImpl(this@detectTapGestures)
 
-    forEachGesture {
-        awaitPointerEventScope {
-            val down = awaitFirstDown()
-            down.consume()
+    awaitEachGesture {
+        val down = awaitFirstDown()
+        down.consume()
+        launch {
             pressScope.reset()
-            if (onPress !== NoPressGesture) launch {
-                pressScope.onPress(down.position)
+        }
+        if (onPress !== NoPressGesture) launch {
+            pressScope.onPress(down.position)
+        }
+        val longPressTimeout = onLongPress?.let {
+            viewConfiguration.longPressTimeoutMillis
+        } ?: (Long.MAX_VALUE / 2)
+        var upOrCancel: PointerInputChange? = null
+        try {
+            // wait for first tap up or long press
+            upOrCancel = withTimeout(longPressTimeout) {
+                waitForUpOrCancellation()
             }
-            val longPressTimeout = onLongPress?.let {
-                viewConfiguration.longPressTimeoutMillis
-            } ?: (Long.MAX_VALUE / 2)
-            var upOrCancel: PointerInputChange? = null
-            try {
-                // wait for first tap up or long press
-                upOrCancel = withTimeout(longPressTimeout) {
-                    waitForUpOrCancellation()
-                }
-                if (upOrCancel == null) {
+            if (upOrCancel == null) {
+                launch {
                     pressScope.cancel() // tap-up was canceled
-                } else {
-                    upOrCancel.consume()
+                }
+            } else {
+                upOrCancel.consume()
+                launch {
                     pressScope.release()
                 }
-            } catch (_: PointerEventTimeoutCancellationException) {
-                onLongPress?.invoke(down.position)
-                consumeUntilUp()
+            }
+        } catch (_: PointerEventTimeoutCancellationException) {
+            onLongPress?.invoke(down.position)
+            consumeUntilUp()
+            launch {
                 pressScope.release()
             }
+        }
 
-            if (upOrCancel != null) {
-                // tap was successful.
-                if (onDoubleTap == null) {
-                    onTap?.invoke(upOrCancel.position) // no need to check for double-tap.
+        if (upOrCancel != null) {
+            // tap was successful.
+            if (onDoubleTap == null) {
+                onTap?.invoke(upOrCancel.position) // no need to check for double-tap.
+            } else {
+                // check for second tap
+                val secondDown = awaitSecondDown(upOrCancel)
+
+                if (secondDown == null) {
+                    onTap?.invoke(upOrCancel.position) // no valid second tap started
                 } else {
-                    // check for second tap
-                    val secondDown = awaitSecondDown(upOrCancel)
-
-                    if (secondDown == null) {
-                        onTap?.invoke(upOrCancel.position) // no valid second tap started
-                    } else {
-                        // Second tap down detected
+                    // Second tap down detected
+                    launch {
                         pressScope.reset()
-                        if (onPress !== NoPressGesture) {
-                            launch { pressScope.onPress(secondDown.position) }
-                        }
+                    }
+                    if (onPress !== NoPressGesture) {
+                        launch { pressScope.onPress(secondDown.position) }
+                    }
 
-                        try {
-                            // Might have a long second press as the second tap
-                            withTimeout(longPressTimeout) {
-                                val secondUp = waitForUpOrCancellation()
-                                if (secondUp != null) {
-                                    secondUp.consume()
+                    try {
+                        // Might have a long second press as the second tap
+                        withTimeout(longPressTimeout) {
+                            val secondUp = waitForUpOrCancellation()
+                            if (secondUp != null) {
+                                secondUp.consume()
+                                launch {
                                     pressScope.release()
-                                    onDoubleTap(secondUp.position)
-                                } else {
-                                    pressScope.cancel()
-                                    onTap?.invoke(upOrCancel.position)
                                 }
+                                onDoubleTap(secondUp.position)
+                            } else {
+                                launch {
+                                    pressScope.cancel()
+                                }
+                                onTap?.invoke(upOrCancel.position)
                             }
-                        } catch (e: PointerEventTimeoutCancellationException) {
-                            // The first tap was valid, but the second tap is a long press.
-                            // notify for the first tap
-                            onTap?.invoke(upOrCancel.position)
+                        }
+                    } catch (e: PointerEventTimeoutCancellationException) {
+                        // The first tap was valid, but the second tap is a long press.
+                        // notify for the first tap
+                        onTap?.invoke(upOrCancel.position)
 
-                            // notify for the long press
-                            onLongPress?.invoke(secondDown.position)
-                            consumeUntilUp()
+                        // notify for the long press
+                        onLongPress?.invoke(secondDown.position)
+                        consumeUntilUp()
+                        launch {
                             pressScope.release()
                         }
                     }
@@ -214,25 +228,31 @@
     onTap: ((Offset) -> Unit)? = null
 ) {
     val pressScope = PressGestureScopeImpl(this)
-    forEachGesture {
-        coroutineScope {
-            pressScope.reset()
-            awaitPointerEventScope {
+    coroutineScope {
+        awaitEachGesture {
+            launch {
+                pressScope.reset()
+            }
 
-                val down = awaitFirstDown().also { it.consume() }
+            val down = awaitFirstDown().also { it.consume() }
 
-                if (onPress !== NoPressGesture) {
-                    launch { pressScope.onPress(down.position) }
+            if (onPress !== NoPressGesture) {
+                launch {
+                    pressScope.onPress(down.position)
                 }
+            }
 
-                val up = waitForUpOrCancellation()
-                if (up == null) {
+            val up = waitForUpOrCancellation()
+            if (up == null) {
+                launch {
                     pressScope.cancel() // tap-up was canceled
-                } else {
-                    up.consume()
-                    pressScope.release()
-                    onTap?.invoke(up.position)
                 }
+            } else {
+                up.consume()
+                launch {
+                    pressScope.release()
+                }
+                onTap?.invoke(up.position)
             }
         }
     }
@@ -322,8 +342,8 @@
     /**
      * Called when a new gesture has started.
      */
-    fun reset() {
-        mutex.tryLock() // If tryAwaitRelease wasn't called, this will be unlocked.
+    suspend fun reset() {
+        mutex.lock()
         isReleased = false
         isCanceled = false
     }
@@ -337,7 +357,8 @@
     override suspend fun tryAwaitRelease(): Boolean {
         if (!isReleased && !isCanceled) {
             mutex.lock()
+            mutex.unlock()
         }
         return isReleased
     }
-}
\ No newline at end of file
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/TransformGestureDetector.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/TransformGestureDetector.kt
index 4213b98..8d8c460 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/TransformGestureDetector.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/TransformGestureDetector.kt
@@ -48,61 +48,59 @@
     panZoomLock: Boolean = false,
     onGesture: (centroid: Offset, pan: Offset, zoom: Float, rotation: Float) -> Unit
 ) {
-    forEachGesture {
-        awaitPointerEventScope {
-            var rotation = 0f
-            var zoom = 1f
-            var pan = Offset.Zero
-            var pastTouchSlop = false
-            val touchSlop = viewConfiguration.touchSlop
-            var lockedToPanZoom = false
+    awaitEachGesture {
+        var rotation = 0f
+        var zoom = 1f
+        var pan = Offset.Zero
+        var pastTouchSlop = false
+        val touchSlop = viewConfiguration.touchSlop
+        var lockedToPanZoom = false
 
-            awaitFirstDown(requireUnconsumed = false)
-            do {
-                val event = awaitPointerEvent()
-                val canceled = event.changes.fastAny { it.isConsumed }
-                if (!canceled) {
-                    val zoomChange = event.calculateZoom()
-                    val rotationChange = event.calculateRotation()
-                    val panChange = event.calculatePan()
+        awaitFirstDown(requireUnconsumed = false)
+        do {
+            val event = awaitPointerEvent()
+            val canceled = event.changes.fastAny { it.isConsumed }
+            if (!canceled) {
+                val zoomChange = event.calculateZoom()
+                val rotationChange = event.calculateRotation()
+                val panChange = event.calculatePan()
 
-                    if (!pastTouchSlop) {
-                        zoom *= zoomChange
-                        rotation += rotationChange
-                        pan += panChange
+                if (!pastTouchSlop) {
+                    zoom *= zoomChange
+                    rotation += rotationChange
+                    pan += panChange
 
-                        val centroidSize = event.calculateCentroidSize(useCurrent = false)
-                        val zoomMotion = abs(1 - zoom) * centroidSize
-                        val rotationMotion = abs(rotation * PI.toFloat() * centroidSize / 180f)
-                        val panMotion = pan.getDistance()
+                    val centroidSize = event.calculateCentroidSize(useCurrent = false)
+                    val zoomMotion = abs(1 - zoom) * centroidSize
+                    val rotationMotion = abs(rotation * PI.toFloat() * centroidSize / 180f)
+                    val panMotion = pan.getDistance()
 
-                        if (zoomMotion > touchSlop ||
-                            rotationMotion > touchSlop ||
-                            panMotion > touchSlop
-                        ) {
-                            pastTouchSlop = true
-                            lockedToPanZoom = panZoomLock && rotationMotion < touchSlop
-                        }
+                    if (zoomMotion > touchSlop ||
+                        rotationMotion > touchSlop ||
+                        panMotion > touchSlop
+                    ) {
+                        pastTouchSlop = true
+                        lockedToPanZoom = panZoomLock && rotationMotion < touchSlop
                     }
+                }
 
-                    if (pastTouchSlop) {
-                        val centroid = event.calculateCentroid(useCurrent = false)
-                        val effectiveRotation = if (lockedToPanZoom) 0f else rotationChange
-                        if (effectiveRotation != 0f ||
-                            zoomChange != 1f ||
-                            panChange != Offset.Zero
-                        ) {
-                            onGesture(centroid, panChange, zoomChange, effectiveRotation)
-                        }
-                        event.changes.fastForEach {
-                            if (it.positionChanged()) {
-                                it.consume()
-                            }
+                if (pastTouchSlop) {
+                    val centroid = event.calculateCentroid(useCurrent = false)
+                    val effectiveRotation = if (lockedToPanZoom) 0f else rotationChange
+                    if (effectiveRotation != 0f ||
+                        zoomChange != 1f ||
+                        panChange != Offset.Zero
+                    ) {
+                        onGesture(centroid, panChange, zoomChange, effectiveRotation)
+                    }
+                    event.changes.fastForEach {
+                        if (it.positionChanged()) {
+                            it.consume()
                         }
                     }
                 }
-            } while (!canceled && event.changes.fastAny { it.pressed })
-        }
+            }
+        } while (!canceled && event.changes.fastAny { it.pressed })
     }
 }
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Transformable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Transformable.kt
index 117fc97..397ee29 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Transformable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Transformable.kt
@@ -68,6 +68,14 @@
         val updatePanZoomLock = rememberUpdatedState(lockRotationOnZoomPan)
         val block: suspend PointerInputScope.() -> Unit = remember {
             {
+                /**
+                 * This cannot be converted to awaitEachGesture() because
+                 * [TransformableState.transform] is a suspend function. Unfortunately, this means
+                 * that events can be lost in the middle of a gesture.
+                 *
+                 * TODO(b/251826790) Convert to awaitEachGesture()
+                 */
+                @Suppress("DEPRECATION")
                 forEachGesture {
                     detectZoom(updatePanZoomLock, updatedState)
                 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/LongPressTextDragObserver.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/LongPressTextDragObserver.kt
index 72b63e9..49c2cb8 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/LongPressTextDragObserver.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/LongPressTextDragObserver.kt
@@ -19,7 +19,7 @@
 import androidx.compose.foundation.gestures.awaitFirstDown
 import androidx.compose.foundation.gestures.detectDragGestures
 import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress
-import androidx.compose.foundation.gestures.forEachGesture
+import androidx.compose.foundation.gestures.awaitEachGesture
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.input.pointer.PointerInputScope
 import androidx.compose.ui.util.fastAny
@@ -94,16 +94,14 @@
 private suspend fun PointerInputScope.detectPreDragGesturesWithObserver(
     observer: TextDragObserver
 ) {
-    forEachGesture {
-        awaitPointerEventScope {
-            val down = awaitFirstDown()
-            observer.onDown(down.position)
-            // Wait for that pointer to come up.
-            do {
-                val event = awaitPointerEvent()
-            } while (event.changes.fastAny { it.id == down.id && it.pressed })
-            observer.onUp()
-        }
+    awaitEachGesture {
+        val down = awaitFirstDown()
+        observer.onDown(down.position)
+        // Wait for that pointer to come up.
+        do {
+            val event = awaitPointerEvent()
+        } while (event.changes.fastAny { it.id == down.id && it.pressed })
+        observer.onUp()
     }
 }
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt
index 7538250..87053fb 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt
@@ -20,7 +20,7 @@
 
 import androidx.compose.foundation.fastFold
 import androidx.compose.foundation.focusable
-import androidx.compose.foundation.gestures.forEachGesture
+import androidx.compose.foundation.gestures.awaitEachGesture
 import androidx.compose.foundation.gestures.waitForUpOrCancellation
 import androidx.compose.foundation.text.Handle
 import androidx.compose.foundation.text.TextDragObserver
@@ -54,7 +54,6 @@
 import kotlin.math.absoluteValue
 import kotlin.math.max
 import kotlin.math.min
-import kotlinx.coroutines.coroutineScope
 
 /**
  * A bridge class between user interaction to the text composables for text selection.
@@ -615,13 +614,9 @@
      * Detect tap without consuming the up event.
      */
     private suspend fun PointerInputScope.detectNonConsumingTap(onTap: (Offset) -> Unit) {
-        forEachGesture {
-            coroutineScope {
-                awaitPointerEventScope {
-                    waitForUpOrCancellation()?.let {
-                        onTap(it.position)
-                    }
-                }
+        awaitEachGesture {
+            waitForUpOrCancellation()?.let {
+                onTap(it.position)
             }
         }
     }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextSelectionMouseDetector.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextSelectionMouseDetector.kt
index 4db3b64..c154770 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextSelectionMouseDetector.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextSelectionMouseDetector.kt
@@ -16,8 +16,8 @@
 
 package androidx.compose.foundation.text.selection
 
+import androidx.compose.foundation.gestures.awaitEachGesture
 import androidx.compose.foundation.gestures.drag
-import androidx.compose.foundation.gestures.forEachGesture
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.input.pointer.AwaitPointerEventScope
 import androidx.compose.ui.input.pointer.PointerEvent
@@ -83,36 +83,34 @@
 internal suspend fun PointerInputScope.mouseSelectionDetector(
     observer: MouseSelectionObserver
 ) {
-    forEachGesture {
-        awaitPointerEventScope {
-            val clicksCounter = ClicksCounter(viewConfiguration)
-            while (true) {
-                val down = awaitMouseEventDown()
-                clicksCounter.update(down)
-                val downChange = down.changes[0]
-                if (down.isShiftPressed) {
-                    val started = observer.onExtend(downChange.position)
-                    if (started) {
-                        downChange.consume()
-                        drag(downChange.id) {
-                            if (observer.onExtendDrag(it.position)) {
-                                it.consume()
-                            }
+    awaitEachGesture {
+        val clicksCounter = ClicksCounter(viewConfiguration)
+        while (true) {
+            val down = awaitMouseEventDown()
+            clicksCounter.update(down)
+            val downChange = down.changes[0]
+            if (down.isShiftPressed) {
+                val started = observer.onExtend(downChange.position)
+                if (started) {
+                    downChange.consume()
+                    drag(downChange.id) {
+                        if (observer.onExtendDrag(it.position)) {
+                            it.consume()
                         }
                     }
-                } else {
-                    val selectionMode = when (clicksCounter.clicks) {
-                        1 -> SelectionAdjustment.None
-                        2 -> SelectionAdjustment.Word
-                        else -> SelectionAdjustment.Paragraph
-                    }
-                    val started = observer.onStart(downChange.position, selectionMode)
-                    if (started) {
-                        downChange.consume()
-                        drag(downChange.id) {
-                            if (observer.onDrag(it.position, selectionMode)) {
-                                it.consume()
-                            }
+                }
+            } else {
+                val selectionMode = when (clicksCounter.clicks) {
+                    1 -> SelectionAdjustment.None
+                    2 -> SelectionAdjustment.Word
+                    else -> SelectionAdjustment.Paragraph
+                }
+                val started = observer.onStart(downChange.position, selectionMode)
+                if (started) {
+                    downChange.consume()
+                    drag(downChange.id) {
+                        if (observer.onDrag(it.position, selectionMode)) {
+                            it.consume()
                         }
                     }
                 }
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Clickable.desktop.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Clickable.desktop.kt
index f03d71f..6d663a8 100644
--- a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Clickable.desktop.kt
+++ b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Clickable.desktop.kt
@@ -16,7 +16,7 @@
 
 package androidx.compose.foundation
 
-import androidx.compose.foundation.gestures.forEachGesture
+import androidx.compose.foundation.gestures.awaitEachGesture
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.interaction.PressInteraction
 import androidx.compose.runtime.Composable
@@ -51,7 +51,6 @@
 import androidx.compose.ui.unit.toOffset
 import androidx.compose.ui.util.fastAll
 import java.awt.event.KeyEvent.VK_ENTER
-import kotlinx.coroutines.coroutineScope
 
 @Composable
 internal actual fun isComposeRootInScrollableContainer(): () -> Boolean = { false }
@@ -146,20 +145,15 @@
 internal suspend fun PointerInputScope.detectTapWithContext(
     onTap: ((PointerEvent, PointerEvent) -> Unit)? = null
 ) {
-    forEachGesture {
-        coroutineScope {
-            awaitPointerEventScope {
+    awaitEachGesture {
+        val down = awaitEventFirstDown().also {
+            it.changes.forEach { it.consume() }
+        }
 
-                val down = awaitEventFirstDown().also {
-                    it.changes.forEach { it.consume() }
-                }
-
-                val up = waitForFirstInboundUp()
-                if (up != null) {
-                    up.changes.forEach { it.consume() }
-                    onTap?.invoke(down, up)
-                }
-            }
+        val up = waitForFirstInboundUp()
+        if (up != null) {
+            up.changes.forEach { it.consume() }
+            onTap?.invoke(down, up)
         }
     }
 }
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/ContextMenuProvider.desktop.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/ContextMenuProvider.desktop.kt
index 6d6102f..2a7fa89 100644
--- a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/ContextMenuProvider.desktop.kt
+++ b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/ContextMenuProvider.desktop.kt
@@ -16,7 +16,7 @@
 
 package androidx.compose.foundation
 
-import androidx.compose.foundation.gestures.forEachGesture
+import androidx.compose.foundation.gestures.awaitEachGesture
 import androidx.compose.foundation.layout.Box
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
@@ -107,14 +107,12 @@
         enabled && state.status == ContextMenuState.Status.Closed
     ) {
         this.pointerInput(state) {
-            forEachGesture {
-                awaitPointerEventScope {
-                    val event = awaitEventFirstDown()
-                    if (event.buttons.isSecondaryPressed) {
-                        event.changes.forEach { it.consume() }
-                        state.status =
-                            ContextMenuState.Status.Open(Rect(event.changes[0].position, 0f))
-                    }
+            awaitEachGesture {
+                val event = awaitEventFirstDown()
+                if (event.buttons.isSecondaryPressed) {
+                    event.changes.forEach { it.consume() }
+                    state.status =
+                        ContextMenuState.Status.Open(Rect(event.changes[0].position, 0f))
                 }
             }
         }
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Scrollbar.desktop.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Scrollbar.desktop.kt
index 1b7aa2a..77c5476 100644
--- a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Scrollbar.desktop.kt
+++ b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Scrollbar.desktop.kt
@@ -21,7 +21,7 @@
 import androidx.compose.foundation.gestures.awaitFirstDown
 import androidx.compose.foundation.gestures.detectTapAndPress
 import androidx.compose.foundation.gestures.drag
-import androidx.compose.foundation.gestures.forEachGesture
+import androidx.compose.foundation.gestures.awaitEachGesture
 import androidx.compose.foundation.gestures.scrollBy
 import androidx.compose.foundation.interaction.DragInteraction
 import androidx.compose.foundation.interaction.MutableInteractionSource
@@ -275,25 +275,23 @@
     val currentOnDelta by rememberUpdatedState(onDelta)
     val currentOnFinished by rememberUpdatedState(onFinished)
     pointerInput(Unit) {
-        forEachGesture {
-            awaitPointerEventScope {
-                val down = awaitFirstDown(requireUnconsumed = false)
-                val interaction = DragInteraction.Start()
-                currentInteractionSource.tryEmit(interaction)
-                currentDraggedInteraction.value = interaction
-                val isSuccess = drag(down.id) { change ->
-                    currentOnDelta.invoke(change.positionChange())
-                    change.consume()
-                }
-                val finishInteraction = if (isSuccess) {
-                    DragInteraction.Stop(interaction)
-                } else {
-                    DragInteraction.Cancel(interaction)
-                }
-                currentInteractionSource.tryEmit(finishInteraction)
-                currentDraggedInteraction.value = null
-                currentOnFinished.invoke()
+        awaitEachGesture {
+            val down = awaitFirstDown(requireUnconsumed = false)
+            val interaction = DragInteraction.Start()
+            currentInteractionSource.tryEmit(interaction)
+            currentDraggedInteraction.value = interaction
+            val isSuccess = drag(down.id) { change ->
+                currentOnDelta.invoke(change.positionChange())
+                change.consume()
             }
+            val finishInteraction = if (isSuccess) {
+                DragInteraction.Stop(interaction)
+            } else {
+                DragInteraction.Cancel(interaction)
+            }
+            currentInteractionSource.tryEmit(finishInteraction)
+            currentDraggedInteraction.value = null
+            currentOnFinished.invoke()
         }
     }
 }
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/window/WindowDraggableArea.desktop.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/window/WindowDraggableArea.desktop.kt
index 36eeaa8..b285b1c 100644
--- a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/window/WindowDraggableArea.desktop.kt
+++ b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/window/WindowDraggableArea.desktop.kt
@@ -17,7 +17,7 @@
 package androidx.compose.foundation.window
 
 import androidx.compose.foundation.gestures.awaitFirstDown
-import androidx.compose.foundation.gestures.forEachGesture
+import androidx.compose.foundation.gestures.awaitEachGesture
 import androidx.compose.foundation.layout.Box
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
@@ -46,11 +46,9 @@
 
     Box(
         modifier = modifier.pointerInput(Unit) {
-            forEachGesture {
-                awaitPointerEventScope {
-                    awaitFirstDown()
-                    handler.register()
-                }
+            awaitEachGesture {
+                awaitFirstDown()
+                handler.register()
             }
         }
     ) {
diff --git a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/gestures/DragGestureDetectorTest.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/gestures/DragGestureDetectorTest.kt
index 30db9bd..9479a6c 100644
--- a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/gestures/DragGestureDetectorTest.kt
+++ b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/gestures/DragGestureDetectorTest.kt
@@ -133,99 +133,93 @@
     }
 
     private val AwaitVerticalDragUtil = SuspendingGestureTestUtil(width = 100, height = 100) {
-        forEachGesture {
-            awaitPointerEventScope {
-                val down = awaitFirstDown()
-                val slopChange = awaitVerticalTouchSlopOrCancellation(down.id) { change, overSlop ->
-                    if (change.positionChange().y > 0f || !consumePositiveOnly) {
-                        dragged = true
-                        dragDistance = overSlop
+        awaitEachGesture {
+            val down = awaitFirstDown()
+            val slopChange = awaitVerticalTouchSlopOrCancellation(down.id) { change, overSlop ->
+                if (change.positionChange().y > 0f || !consumePositiveOnly) {
+                    dragged = true
+                    dragDistance = overSlop
+                    change.consume()
+                }
+            }
+            if (slopChange != null || sloppyDetector) {
+                gestureStarted = true
+                var pointer = if (sloppyDetector) down.id else slopChange!!.id
+                do {
+                    val change = awaitVerticalDragOrCancellation(pointer)
+                    if (change == null) {
+                        gestureCanceled = true
+                    } else {
+                        dragDistance += change.positionChange().y
                         change.consume()
-                    }
-                }
-                if (slopChange != null || sloppyDetector) {
-                    gestureStarted = true
-                    var pointer = if (sloppyDetector) down.id else slopChange!!.id
-                    do {
-                        val change = awaitVerticalDragOrCancellation(pointer)
-                        if (change == null) {
-                            gestureCanceled = true
-                        } else {
-                            dragDistance += change.positionChange().y
-                            change.consume()
-                            if (change.changedToUpIgnoreConsumed()) {
-                                gestureEnded = true
-                            }
-                            pointer = change.id
+                        if (change.changedToUpIgnoreConsumed()) {
+                            gestureEnded = true
                         }
-                    } while (!gestureEnded && !gestureCanceled)
-                }
+                        pointer = change.id
+                    }
+                } while (!gestureEnded && !gestureCanceled)
             }
         }
     }
 
     private val AwaitHorizontalDragUtil = SuspendingGestureTestUtil(width = 100, height = 100) {
-        forEachGesture {
-            awaitPointerEventScope {
-                val down = awaitFirstDown()
-                val slopChange =
-                    awaitHorizontalTouchSlopOrCancellation(down.id) { change, overSlop ->
-                        if (change.positionChange().x > 0f || !consumePositiveOnly) {
-                            dragged = true
-                            dragDistance = overSlop
-                            change.consume()
-                        }
+        awaitEachGesture {
+            val down = awaitFirstDown()
+            val slopChange =
+                awaitHorizontalTouchSlopOrCancellation(down.id) { change, overSlop ->
+                    if (change.positionChange().x > 0f || !consumePositiveOnly) {
+                        dragged = true
+                        dragDistance = overSlop
+                        change.consume()
                     }
-                if (slopChange != null || sloppyDetector) {
-                    gestureStarted = true
-                    var pointer = if (sloppyDetector) down.id else slopChange!!.id
-                    do {
-                        val change = awaitHorizontalDragOrCancellation(pointer)
-                        if (change == null) {
-                            gestureCanceled = true
-                        } else {
-                            dragDistance += change.positionChange().x
-                            change.consume()
-                            if (change.changedToUpIgnoreConsumed()) {
-                                gestureEnded = true
-                            }
-                            pointer = change.id
-                        }
-                    } while (!gestureEnded && !gestureCanceled)
                 }
+            if (slopChange != null || sloppyDetector) {
+                gestureStarted = true
+                var pointer = if (sloppyDetector) down.id else slopChange!!.id
+                do {
+                    val change = awaitHorizontalDragOrCancellation(pointer)
+                    if (change == null) {
+                        gestureCanceled = true
+                    } else {
+                        dragDistance += change.positionChange().x
+                        change.consume()
+                        if (change.changedToUpIgnoreConsumed()) {
+                            gestureEnded = true
+                        }
+                        pointer = change.id
+                    }
+                } while (!gestureEnded && !gestureCanceled)
             }
         }
     }
 
     private val AwaitDragUtil = SuspendingGestureTestUtil(width = 100, height = 100) {
-        forEachGesture {
-            awaitPointerEventScope {
-                val down = awaitFirstDown()
-                val slopChange = awaitTouchSlopOrCancellation(down.id) { change, overSlop ->
-                    val positionChange = change.positionChange()
-                    if (positionChange.x > 0f || positionChange.y > 0f || !consumePositiveOnly) {
-                        dragged = true
-                        dragDistance = overSlop.getDistance()
+        awaitEachGesture {
+            val down = awaitFirstDown()
+            val slopChange = awaitTouchSlopOrCancellation(down.id) { change, overSlop ->
+                val positionChange = change.positionChange()
+                if (positionChange.x > 0f || positionChange.y > 0f || !consumePositiveOnly) {
+                    dragged = true
+                    dragDistance = overSlop.getDistance()
+                    change.consume()
+                }
+            }
+            if (slopChange != null || sloppyDetector) {
+                gestureStarted = true
+                var pointer = if (sloppyDetector) down.id else slopChange!!.id
+                do {
+                    val change = awaitDragOrCancellation(pointer)
+                    if (change == null) {
+                        gestureCanceled = true
+                    } else {
+                        dragDistance += change.positionChange().getDistance()
                         change.consume()
-                    }
-                }
-                if (slopChange != null || sloppyDetector) {
-                    gestureStarted = true
-                    var pointer = if (sloppyDetector) down.id else slopChange!!.id
-                    do {
-                        val change = awaitDragOrCancellation(pointer)
-                        if (change == null) {
-                            gestureCanceled = true
-                        } else {
-                            dragDistance += change.positionChange().getDistance()
-                            change.consume()
-                            if (change.changedToUpIgnoreConsumed()) {
-                                gestureEnded = true
-                            }
-                            pointer = change.id
+                        if (change.changedToUpIgnoreConsumed()) {
+                            gestureEnded = true
                         }
-                    } while (!gestureEnded && !gestureCanceled)
-                }
+                        pointer = change.id
+                    }
+                } while (!gestureEnded && !gestureCanceled)
             }
         }
     }
diff --git a/compose/material/material/build.gradle b/compose/material/material/build.gradle
index fe57f44..8b59f0e 100644
--- a/compose/material/material/build.gradle
+++ b/compose/material/material/build.gradle
@@ -33,7 +33,7 @@
          * corresponding block below
          */
         api("androidx.compose.animation:animation-core:1.2.1")
-        api("androidx.compose.foundation:foundation:1.2.1")
+        api(project(":compose:foundation:foundation"))
         api(project(":compose:material:material-icons-core"))
         api(project(":compose:material:material-ripple"))
         api("androidx.compose.runtime:runtime:1.2.1")
diff --git a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/ColorPickerDemo.kt b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/ColorPickerDemo.kt
index e878f9a..4aff510 100644
--- a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/ColorPickerDemo.kt
+++ b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/ColorPickerDemo.kt
@@ -25,8 +25,8 @@
 import androidx.compose.foundation.Image
 import androidx.compose.foundation.background
 import androidx.compose.foundation.gestures.awaitFirstDown
+import androidx.compose.foundation.gestures.awaitEachGesture
 import androidx.compose.foundation.gestures.drag
-import androidx.compose.foundation.gestures.forEachGesture
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.BoxWithConstraints
 import androidx.compose.foundation.layout.Column
@@ -112,17 +112,15 @@
                 }
             }
 
-            forEachGesture {
-                awaitPointerEventScope {
-                    val down = awaitFirstDown()
-                    hasInput = true
-                    updateColorWheel(down.position)
-                    drag(down.id) { change ->
-                        change.consume()
-                        updateColorWheel(change.position)
-                    }
-                    hasInput = false
+            awaitEachGesture {
+                val down = awaitFirstDown()
+                hasInput = true
+                updateColorWheel(down.position)
+                drag(down.id) { change ->
+                    change.consume()
+                    updateColorWheel(change.position)
                 }
+                hasInput = false
             }
         }
 
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/SurfaceTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/SurfaceTest.kt
index e4301df..99ffbe5 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/SurfaceTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/SurfaceTest.kt
@@ -17,7 +17,7 @@
 package androidx.compose.material
 
 import android.os.Build
-import androidx.compose.foundation.gestures.forEachGesture
+import androidx.compose.foundation.gestures.awaitEachGesture
 import androidx.compose.foundation.interaction.Interaction
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.interaction.PressInteraction
@@ -594,13 +594,11 @@
                             .fillMaxSize()
                             .testTag("clickable")
                             .pointerInput(Unit) {
-                                forEachGesture {
-                                    awaitPointerEventScope {
-                                        hitTested.value = true
-                                        val event = awaitPointerEvent(PointerEventPass.Final)
-                                        Truth.assertThat(event.changes[0].isConsumed)
-                                            .isFalse()
-                                    }
+                                awaitEachGesture {
+                                    hitTested.value = true
+                                    val event = awaitPointerEvent(PointerEventPass.Final)
+                                    Truth.assertThat(event.changes[0].isConsumed)
+                                        .isFalse()
                                 }
                             }
                     )
diff --git a/compose/material/material/src/androidMain/kotlin/androidx/compose/material/ExposedDropdownMenu.kt b/compose/material/material/src/androidMain/kotlin/androidx/compose/material/ExposedDropdownMenu.kt
index 20b95ff..3214402 100644
--- a/compose/material/material/src/androidMain/kotlin/androidx/compose/material/ExposedDropdownMenu.kt
+++ b/compose/material/material/src/androidMain/kotlin/androidx/compose/material/ExposedDropdownMenu.kt
@@ -21,7 +21,7 @@
 import androidx.compose.animation.animateColorAsState
 import androidx.compose.animation.core.MutableTransitionState
 import androidx.compose.animation.core.tween
-import androidx.compose.foundation.gestures.forEachGesture
+import androidx.compose.foundation.gestures.awaitEachGesture
 import androidx.compose.foundation.interaction.InteractionSource
 import androidx.compose.foundation.interaction.collectIsFocusedAsState
 import androidx.compose.foundation.layout.Box
@@ -63,7 +63,6 @@
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.unit.DpOffset
 import androidx.compose.ui.util.fastAll
-import kotlinx.coroutines.coroutineScope
 import kotlin.math.max
 
 /**
@@ -513,18 +512,14 @@
     onExpandedChange: () -> Unit,
     menuLabel: String
 ) = pointerInput(Unit) {
-    forEachGesture {
-        coroutineScope {
-            awaitPointerEventScope {
-                var event: PointerEvent
-                do {
-                    event = awaitPointerEvent(PointerEventPass.Initial)
-                } while (
-                    !event.changes.fastAll { it.changedToUp() }
-                )
-                onExpandedChange.invoke()
-            }
-        }
+    awaitEachGesture {
+        var event: PointerEvent
+        do {
+            event = awaitPointerEvent(PointerEventPass.Initial)
+        } while (
+            !event.changes.fastAll { it.changedToUp() }
+        )
+        onExpandedChange.invoke()
     }
 }.semantics {
     contentDescription = menuLabel // this should be a localised string
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Slider.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Slider.kt
index 580ab3a..7c76824 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Slider.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Slider.kt
@@ -30,7 +30,7 @@
 import androidx.compose.foundation.gestures.awaitFirstDown
 import androidx.compose.foundation.gestures.detectTapGestures
 import androidx.compose.foundation.gestures.draggable
-import androidx.compose.foundation.gestures.forEachGesture
+import androidx.compose.foundation.gestures.awaitEachGesture
 import androidx.compose.foundation.gestures.horizontalDrag
 import androidx.compose.foundation.hoverable
 import androidx.compose.foundation.indication
@@ -980,56 +980,54 @@
                 onDrag
             )
             coroutineScope {
-                forEachGesture {
-                    awaitPointerEventScope {
-                        val event = awaitFirstDown(requireUnconsumed = false)
-                        val interaction = DragInteraction.Start()
-                        var posX = if (isRtl) maxPx - event.position.x else event.position.x
-                        val compare = rangeSliderLogic.compareOffsets(posX)
-                        var draggingStart = if (compare != 0) {
-                            compare < 0
+                awaitEachGesture {
+                    val event = awaitFirstDown(requireUnconsumed = false)
+                    val interaction = DragInteraction.Start()
+                    var posX = if (isRtl) maxPx - event.position.x else event.position.x
+                    val compare = rangeSliderLogic.compareOffsets(posX)
+                    var draggingStart = if (compare != 0) {
+                        compare < 0
+                    } else {
+                        rawOffsetStart.value > posX
+                    }
+
+                    awaitSlop(event.id, event.type)?.let {
+                        val slop = viewConfiguration.pointerSlop(event.type)
+                        val shouldUpdateCapturedThumb = abs(rawOffsetEnd.value - posX) < slop &&
+                            abs(rawOffsetStart.value - posX) < slop
+                        if (shouldUpdateCapturedThumb) {
+                            val dir = it.second
+                            draggingStart = if (isRtl) dir >= 0f else dir < 0f
+                            posX += it.first.positionChange().x
+                        }
+                    }
+
+                    rangeSliderLogic.captureThumb(
+                        draggingStart,
+                        posX,
+                        interaction,
+                        this@coroutineScope
+                    )
+
+                    val finishInteraction = try {
+                        val success = horizontalDrag(pointerId = event.id) {
+                            val deltaX = it.positionChange().x
+                            onDrag.value.invoke(draggingStart, if (isRtl) -deltaX else deltaX)
+                        }
+                        if (success) {
+                            DragInteraction.Stop(interaction)
                         } else {
-                            rawOffsetStart.value > posX
-                        }
-
-                        awaitSlop(event.id, event.type)?.let {
-                            val slop = viewConfiguration.pointerSlop(event.type)
-                            val shouldUpdateCapturedThumb = abs(rawOffsetEnd.value - posX) < slop &&
-                                abs(rawOffsetStart.value - posX) < slop
-                            if (shouldUpdateCapturedThumb) {
-                                val dir = it.second
-                                draggingStart = if (isRtl) dir >= 0f else dir < 0f
-                                posX += it.first.positionChange().x
-                            }
-                        }
-
-                        rangeSliderLogic.captureThumb(
-                            draggingStart,
-                            posX,
-                            interaction,
-                            this@coroutineScope
-                        )
-
-                        val finishInteraction = try {
-                            val success = horizontalDrag(pointerId = event.id) {
-                                val deltaX = it.positionChange().x
-                                onDrag.value.invoke(draggingStart, if (isRtl) -deltaX else deltaX)
-                            }
-                            if (success) {
-                                DragInteraction.Stop(interaction)
-                            } else {
-                                DragInteraction.Cancel(interaction)
-                            }
-                        } catch (e: CancellationException) {
                             DragInteraction.Cancel(interaction)
                         }
+                    } catch (e: CancellationException) {
+                        DragInteraction.Cancel(interaction)
+                    }
 
-                        gestureEndAction.value.invoke(draggingStart)
-                        launch {
-                            rangeSliderLogic
-                                .activeInteraction(draggingStart)
-                                .emit(finishInteraction)
-                        }
+                    gestureEndAction.value.invoke(draggingStart)
+                    launch {
+                        rangeSliderLogic
+                            .activeInteraction(draggingStart)
+                            .emit(finishInteraction)
                     }
                 }
             }
diff --git a/compose/material3/material3/build.gradle b/compose/material3/material3/build.gradle
index be01d8b..c479be1 100644
--- a/compose/material3/material3/build.gradle
+++ b/compose/material3/material3/build.gradle
@@ -39,7 +39,7 @@
         implementation("androidx.compose.animation:animation-core:1.3.0-rc01")
         implementation("androidx.compose.foundation:foundation-layout:1.3.0-rc01")
         implementation("androidx.compose.ui:ui-util:1.3.0-rc01")
-        api("androidx.compose.foundation:foundation:1.3.0-rc01")
+        api(project(":compose:foundation:foundation"))
         api("androidx.compose.material:material-icons-core:1.3.0-rc01")
         api("androidx.compose.material:material-ripple:1.3.0-rc01")
         api("androidx.compose.runtime:runtime:1.3.0-rc01")
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/SurfaceTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/SurfaceTest.kt
index 0882f40..cbb0705 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/SurfaceTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/SurfaceTest.kt
@@ -18,7 +18,7 @@
 
 import android.os.Build
 import androidx.compose.foundation.clickable
-import androidx.compose.foundation.gestures.forEachGesture
+import androidx.compose.foundation.gestures.awaitEachGesture
 import androidx.compose.foundation.interaction.Interaction
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.interaction.PressInteraction
@@ -707,13 +707,11 @@
                             .fillMaxSize()
                             .testTag("clickable")
                             .pointerInput(Unit) {
-                                forEachGesture {
-                                    awaitPointerEventScope {
-                                        hitTested.value = true
-                                        val event = awaitPointerEvent(PointerEventPass.Final)
-                                        Truth.assertThat(event.changes[0].isConsumed)
-                                            .isFalse()
-                                    }
+                                awaitEachGesture {
+                                    hitTested.value = true
+                                    val event = awaitPointerEvent(PointerEventPass.Final)
+                                    Truth.assertThat(event.changes[0].isConsumed)
+                                        .isFalse()
                                 }
                             }
                     )
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ExposedDropdownMenu.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ExposedDropdownMenu.kt
index e270215..3fa4084 100644
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ExposedDropdownMenu.kt
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ExposedDropdownMenu.kt
@@ -20,7 +20,7 @@
 import android.view.View
 import android.view.ViewTreeObserver
 import androidx.compose.animation.core.MutableTransitionState
-import androidx.compose.foundation.gestures.forEachGesture
+import androidx.compose.foundation.gestures.awaitEachGesture
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.ColumnScope
 import androidx.compose.foundation.layout.PaddingValues
@@ -66,7 +66,6 @@
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.util.fastAll
 import kotlin.math.max
-import kotlinx.coroutines.coroutineScope
 
 /**
  * <a href="https://m3.material.io/components/menus/overview" class="external" target="_blank">Material Design Exposed Dropdown Menu</a>.
@@ -527,18 +526,14 @@
     expandedDescription: String = getString(Strings.MenuExpanded),
     collapsedDescription: String = getString(Strings.MenuCollapsed),
 ) = pointerInput(Unit) {
-    forEachGesture {
-        coroutineScope {
-            awaitPointerEventScope {
-                var event: PointerEvent
-                do {
-                    event = awaitPointerEvent(PointerEventPass.Initial)
-                } while (
-                    !event.changes.fastAll { it.changedToUp() }
-                )
-                onExpandedChange()
-            }
-        }
+    awaitEachGesture {
+        var event: PointerEvent
+        do {
+            event = awaitPointerEvent(PointerEventPass.Initial)
+        } while (
+            !event.changes.fastAll { it.changedToUp() }
+        )
+        onExpandedChange()
     }
 }.semantics {
     stateDescription = if (expanded) expandedDescription else collapsedDescription
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Slider.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Slider.kt
index c9f2656..5eb67f3 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Slider.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Slider.kt
@@ -30,7 +30,7 @@
 import androidx.compose.foundation.gestures.awaitFirstDown
 import androidx.compose.foundation.gestures.detectTapGestures
 import androidx.compose.foundation.gestures.draggable
-import androidx.compose.foundation.gestures.forEachGesture
+import androidx.compose.foundation.gestures.awaitEachGesture
 import androidx.compose.foundation.gestures.horizontalDrag
 import androidx.compose.foundation.hoverable
 import androidx.compose.foundation.indication
@@ -1200,56 +1200,54 @@
                 onDrag
             )
             coroutineScope {
-                forEachGesture {
-                    awaitPointerEventScope {
-                        val event = awaitFirstDown(requireUnconsumed = false)
-                        val interaction = DragInteraction.Start()
-                        var posX = if (isRtl) maxPx - event.position.x else event.position.x
-                        val compare = rangeSliderLogic.compareOffsets(posX)
-                        var draggingStart = if (compare != 0) {
-                            compare < 0
+                awaitEachGesture {
+                    val event = awaitFirstDown(requireUnconsumed = false)
+                    val interaction = DragInteraction.Start()
+                    var posX = if (isRtl) maxPx - event.position.x else event.position.x
+                    val compare = rangeSliderLogic.compareOffsets(posX)
+                    var draggingStart = if (compare != 0) {
+                        compare < 0
+                    } else {
+                        rawOffsetStart.value > posX
+                    }
+
+                    awaitSlop(event.id, event.type)?.let {
+                        val slop = viewConfiguration.pointerSlop(event.type)
+                        val shouldUpdateCapturedThumb = abs(rawOffsetEnd.value - posX) < slop &&
+                            abs(rawOffsetStart.value - posX) < slop
+                        if (shouldUpdateCapturedThumb) {
+                            val dir = it.second
+                            draggingStart = if (isRtl) dir >= 0f else dir < 0f
+                            posX += it.first.positionChange().x
+                        }
+                    }
+
+                    rangeSliderLogic.captureThumb(
+                        draggingStart,
+                        posX,
+                        interaction,
+                        this@coroutineScope
+                    )
+
+                    val finishInteraction = try {
+                        val success = horizontalDrag(pointerId = event.id) {
+                            val deltaX = it.positionChange().x
+                            onDrag.value.invoke(draggingStart, if (isRtl) -deltaX else deltaX)
+                        }
+                        if (success) {
+                            DragInteraction.Stop(interaction)
                         } else {
-                            rawOffsetStart.value > posX
-                        }
-
-                        awaitSlop(event.id, event.type)?.let {
-                            val slop = viewConfiguration.pointerSlop(event.type)
-                            val shouldUpdateCapturedThumb = abs(rawOffsetEnd.value - posX) < slop &&
-                                abs(rawOffsetStart.value - posX) < slop
-                            if (shouldUpdateCapturedThumb) {
-                                val dir = it.second
-                                draggingStart = if (isRtl) dir >= 0f else dir < 0f
-                                posX += it.first.positionChange().x
-                            }
-                        }
-
-                        rangeSliderLogic.captureThumb(
-                            draggingStart,
-                            posX,
-                            interaction,
-                            this@coroutineScope
-                        )
-
-                        val finishInteraction = try {
-                            val success = horizontalDrag(pointerId = event.id) {
-                                val deltaX = it.positionChange().x
-                                onDrag.value.invoke(draggingStart, if (isRtl) -deltaX else deltaX)
-                            }
-                            if (success) {
-                                DragInteraction.Stop(interaction)
-                            } else {
-                                DragInteraction.Cancel(interaction)
-                            }
-                        } catch (e: CancellationException) {
                             DragInteraction.Cancel(interaction)
                         }
+                    } catch (e: CancellationException) {
+                        DragInteraction.Cancel(interaction)
+                    }
 
-                        gestureEndAction.value.invoke(draggingStart)
-                        launch {
-                            rangeSliderLogic
-                                .activeInteraction(draggingStart)
-                                .emit(finishInteraction)
-                        }
+                    gestureEndAction.value.invoke(draggingStart)
+                    launch {
+                        rangeSliderLogic
+                            .activeInteraction(draggingStart)
+                            .emit(finishInteraction)
                     }
                 }
             }
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/gestures/ButtonMetaStateDemo.kt b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/gestures/ButtonMetaStateDemo.kt
index 1e5c90c..3740ea8 100644
--- a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/gestures/ButtonMetaStateDemo.kt
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/gestures/ButtonMetaStateDemo.kt
@@ -17,7 +17,7 @@
 package androidx.compose.ui.demos.gestures
 
 import androidx.compose.foundation.background
-import androidx.compose.foundation.gestures.forEachGesture
+import androidx.compose.foundation.gestures.awaitEachGesture
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxWidth
@@ -71,45 +71,43 @@
 
     Column(
         Modifier.pointerInput(Unit) {
-            forEachGesture {
-                awaitPointerEventScope {
-                    do {
-                        val event = awaitPointerEvent()
-                        val metaState = event.keyboardModifiers
-                        control = metaState.isCtrlPressed
-                        alt = metaState.isAltPressed
-                        shift = metaState.isShiftPressed
-                        meta = metaState.isMetaPressed
-                        sym = metaState.isSymPressed
-                        function = metaState.isFunctionPressed
-                        numLock = metaState.isNumLockOn
-                        scrollLock = metaState.isScrollLockOn
-                        capsLock = metaState.isCapsLockOn
+            awaitEachGesture {
+                do {
+                    val event = awaitPointerEvent()
+                    val metaState = event.keyboardModifiers
+                    control = metaState.isCtrlPressed
+                    alt = metaState.isAltPressed
+                    shift = metaState.isShiftPressed
+                    meta = metaState.isMetaPressed
+                    sym = metaState.isSymPressed
+                    function = metaState.isFunctionPressed
+                    numLock = metaState.isNumLockOn
+                    scrollLock = metaState.isScrollLockOn
+                    capsLock = metaState.isCapsLockOn
 
-                        val buttons = event.buttons
-                        primary = buttons.isPrimaryPressed
-                        secondary = buttons.isSecondaryPressed
-                        tertiary = buttons.isTertiaryPressed
-                        back = buttons.isBackPressed
-                        forward = buttons.isForwardPressed
-                    } while (event.changes.any { it.pressed })
-                    // In the future, hover events should work also, but it isn't
-                    // implemented yet.
-                    control = false
-                    alt = false
-                    shift = false
-                    meta = false
-                    sym = false
-                    function = false
-                    numLock = false
-                    scrollLock = false
-                    capsLock = false
-                    primary = false
-                    secondary = false
-                    tertiary = false
-                    back = false
-                    forward = false
-                }
+                    val buttons = event.buttons
+                    primary = buttons.isPrimaryPressed
+                    secondary = buttons.isSecondaryPressed
+                    tertiary = buttons.isTertiaryPressed
+                    back = buttons.isBackPressed
+                    forward = buttons.isForwardPressed
+                } while (event.changes.any { it.pressed })
+                // In the future, hover events should work also, but it isn't
+                // implemented yet.
+                control = false
+                alt = false
+                shift = false
+                meta = false
+                sym = false
+                function = false
+                numLock = false
+                scrollLock = false
+                capsLock = false
+                primary = false
+                secondary = false
+                tertiary = false
+                back = false
+                forward = false
             }
         }
     ) {
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/gestures/DragSlopExceededGestureFilterDemo.kt b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/gestures/DragSlopExceededGestureFilterDemo.kt
index 76e1e3f..4ffbac4 100644
--- a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/gestures/DragSlopExceededGestureFilterDemo.kt
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/gestures/DragSlopExceededGestureFilterDemo.kt
@@ -19,7 +19,7 @@
 import androidx.compose.foundation.background
 import androidx.compose.foundation.gestures.awaitFirstDown
 import androidx.compose.foundation.gestures.awaitTouchSlopOrCancellation
-import androidx.compose.foundation.gestures.forEachGesture
+import androidx.compose.foundation.gestures.awaitEachGesture
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxSize
@@ -64,13 +64,11 @@
                 .wrapContentSize(Alignment.Center)
                 .size(192.dp)
                 .pointerInput(Unit) {
-                    forEachGesture {
-                        awaitPointerEventScope {
-                            val down = awaitFirstDown(requireUnconsumed = false)
-                            awaitTouchSlopOrCancellation(down.id) { change, _ ->
-                                alternativeColor.value = !alternativeColor.value
-                                change.consume()
-                            }
+                    awaitEachGesture {
+                        val down = awaitFirstDown(requireUnconsumed = false)
+                        awaitTouchSlopOrCancellation(down.id) { change, _ ->
+                            alternativeColor.value = !alternativeColor.value
+                            change.consume()
                         }
                     }
                 }
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/gestures/RawDragGestureDetectorDemo.kt b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/gestures/RawDragGestureDetectorDemo.kt
index e48488c..004e2b7 100644
--- a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/gestures/RawDragGestureDetectorDemo.kt
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/gestures/RawDragGestureDetectorDemo.kt
@@ -18,8 +18,8 @@
 
 import androidx.compose.foundation.background
 import androidx.compose.foundation.gestures.awaitFirstDown
+import androidx.compose.foundation.gestures.awaitEachGesture
 import androidx.compose.foundation.gestures.drag
-import androidx.compose.foundation.gestures.forEachGesture
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxSize
@@ -57,13 +57,11 @@
                 .offset(offsetX, offsetY)
                 .size(192.dp)
                 .pointerInput(Unit) {
-                    forEachGesture {
-                        awaitPointerEventScope {
-                            val down = awaitFirstDown(requireUnconsumed = false)
-                            drag(down.id) { change ->
-                                offset.value += change.positionChange()
-                                change.consume()
-                            }
+                    awaitEachGesture {
+                        val down = awaitFirstDown(requireUnconsumed = false)
+                        drag(down.id) { change ->
+                            offset.value += change.positionChange()
+                            change.consume()
                         }
                     }
                 }
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/PositionInWindowTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/PositionInWindowTest.kt
index faf7cf4..5952258 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/PositionInWindowTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/PositionInWindowTest.kt
@@ -20,8 +20,8 @@
 import androidx.activity.ComponentActivity
 import androidx.compose.foundation.background
 import androidx.compose.foundation.gestures.awaitFirstDown
+import androidx.compose.foundation.gestures.awaitEachGesture
 import androidx.compose.foundation.gestures.drag
-import androidx.compose.foundation.gestures.forEachGesture
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.requiredSize
@@ -246,15 +246,13 @@
                             .background(Color.Black)
                             .testTag(smallBoxTag)
                             .pointerInput(Unit) {
-                                forEachGesture {
-                                    awaitPointerEventScope {
-                                        val down = awaitFirstDown()
-                                        var previous = down.position
-                                        drag(down.id) {
-                                            it.consume()
-                                            offset += it.position - previous
-                                            previous = it.position
-                                        }
+                                awaitEachGesture {
+                                    val down = awaitFirstDown()
+                                    var previous = down.position
+                                    drag(down.id) {
+                                        it.consume()
+                                        offset += it.position - previous
+                                        previous = it.position
                                     }
                                 }
                             }