---
title: "IndirectPointerInjectionScope"
description: "The receiver scope of the indirect pointer input injection lambda from performIndirectPointerInput."
type: "interface"
lastmod: "2026-05-08T01:17:01.206733Z"
---
## API Reference

> Source set: Common

```kotlin
@JvmDefaultWithCompatibility
interface IndirectPointerInjectionScope : Density
```

The receiver scope of the indirect pointer input injection lambda from
[performIndirectPointerInput](/jetpack-compose/androidx.compose.ui/ui-test/functions/performIndirectPointerInput).

Indirect pointer input events are dispatched through the focused tree, and components will only
receive these events if they are focused, or an ancestor of a focused item. Therefore, this API
requires an active focus state.

An indirect pointer gesture (just like a regular touch gesture) starts with a [down](/jetpack-compose/androidx.compose.ui/ui-test/functions/down) event,
followed by a sequence of [move](/jetpack-compose/androidx.compose.ui/ui-test/functions/move) events and finally an [up](/jetpack-compose/androidx.compose.ui/ui-test/functions/up) event, optionally combined with more
sets of [down](/jetpack-compose/androidx.compose.ui/ui-test/functions/down) and [up](/jetpack-compose/androidx.compose.ui/ui-test/functions/up) events for multitouch gestures.

Most methods accept a pointerId to specify which pointer (finger) the event applies to. Movement
can be expressed absolutely with [moveTo](/jetpack-compose/androidx.compose.ui/ui-test/functions/moveTo) and [updatePointerTo](#updatepointerto), or relative to the current
pointer position with [moveBy](/jetpack-compose/androidx.compose.ui/ui-test/functions/moveBy) and [updatePointerBy](#updatepointerby). The `moveTo/By` methods enqueue an event
immediately, while the `updatePointerTo/By` methods don't. This allows you to update the position
of multiple pointers in a single [move](/jetpack-compose/androidx.compose.ui/ui-test/functions/move) event for multitouch gestures. Indirect pointer input
gestures can be canceled with [cancel](/jetpack-compose/androidx.compose.ui/ui-test/functions/cancel). All events, regardless the method used, will always
contain the current position of _all_ pointers.

The entire event injection state is shared between all `perform.*Input` methods, meaning you can
continue an unfinished Indirect pointer input gesture in a subsequent invocation of
[performIndirectPointerInput](/jetpack-compose/androidx.compose.ui/ui-test/functions/performIndirectPointerInput) or [performMultiModalInput](/jetpack-compose/androidx.compose.ui/ui-test/functions/performMultiModalInput).

All events sent by these methods are batched together and sent as a whole after
[performIndirectPointerInput](/jetpack-compose/androidx.compose.ui/ui-test/functions/performIndirectPointerInput) has executed its code block. Because gestures don't have to be
defined all in the same [performIndirectPointerInput](/jetpack-compose/androidx.compose.ui/ui-test/functions/performIndirectPointerInput) block, keep in mind that while the gesture
is not complete, all code you execute in between these blocks will be executed while imaginary
fingers are actively touching the indirect pointer input device. Remember, indirect pointer
events do NOT correlate to the screen, so those finger locations won't map to a screen x and y.
The x and y coordinates are instead mapped to the specific input device being used during the
interaction.

The events sent as part of the same batch will not be interrupted by recomposition. However, if a
gesture spans multiple [performIndirectPointerInput](/jetpack-compose/androidx.compose.ui/ui-test/functions/performIndirectPointerInput) blocks it is important to remember that
recomposition, layout and drawing could take place during the gesture, which may lead to events
being injected into a moving target.

This scope also provides general capabilities such as advancing event time and accessing the
system [ViewConfiguration](/jetpack-compose/androidx.compose.ui/ui/interfaces/ViewConfiguration). It also implements [Density](/jetpack-compose/androidx.compose.ui/ui-unit/interfaces/Density) to facilitate conversion between pixels
and density-independent pixels.

For injection methods that require specific node information (e.g., [performTouchInput](/jetpack-compose/androidx.compose.ui/ui-test/functions/performTouchInput),
[performMouseInput](/jetpack-compose/androidx.compose.ui/ui-test/functions/performMouseInput)), use [InjectionScope](/jetpack-compose/androidx.compose.ui/ui-test/interfaces/InjectionScope).

Example of performing an indirect pointer click:

Example of performing an indirect pointer swipe:

## Properties

### eventPeriodMillis

> Source set: Common

```kotlin
val eventPeriodMillis
```

The default time between two successive events.

### inputDeviceSize

> Source set: Common

```kotlin
val inputDeviceSize: IntSize
```

The dimensions of the external indirect pointer input device that provide the boundaries for
indirect input. If you go outside these dimensions, the tests will throw an exception. Note:
This is not related to the screen coordinates.

### indirectPointerEventPrimaryDirectionalMotionAxis

> Source set: Common

```kotlin
val indirectPointerEventPrimaryDirectionalMotionAxis:
    IndirectPointerEventPrimaryDirectionalMotionAxis
```

The primary axis for motion from an [IndirectPointerEvent](/jetpack-compose/androidx.compose.ui/ui/interfaces/IndirectPointerEvent). Indirect input devices (such as
touchpads) that do not move a cursor on screen may define a primary axis for motion (such as
scrolling). This facilitates the translation of a 2D input gesture into a 1D scroll on the
screen. For example, an input device might be wide horizontally but narrow vertically. In
such a case, it would designate X as its primary axis of motion. This means horizontal
scrolling on the input device would cause a horizontal list to scroll horizontally, and a
vertical list to scroll vertically - even though the direction of motion on the input device
is horizontal in both cases.

### viewConfiguration

> Source set: Common

```kotlin
val viewConfiguration: ViewConfiguration
```

The [ViewConfiguration](/jetpack-compose/androidx.compose.ui/ui/interfaces/ViewConfiguration) in use by the
[SemanticsNode](/jetpack-compose/androidx.compose.ui/ui/classes/SemanticsNode) from the
[SemanticsNodeInteraction](/jetpack-compose/androidx.compose.ui/ui-test/classes/SemanticsNodeInteraction) on which the input injection method is called.

## Functions

### advanceEventTime

```kotlin
fun advanceEventTime(durationMillis: Long = eventPeriodMillis)
```

Adds the given `durationMillis` to the current event time, delaying the next event by that
time.

### currentPosition

```kotlin
fun currentPosition(pointerId: Int = 0): Offset?
```

Returns the current position of the given [pointerId](/jetpack-compose/androidx.compose.ui/ui/classes/PointerId). The default [pointerId](/jetpack-compose/androidx.compose.ui/ui/classes/PointerId) is 0. The
position is returned in the coordinate system of the device sending the input (see
[inputDeviceSize](#inputdevicesize)). It is NOT related to the screen location.

### down

```kotlin
fun down(pointerId: Int, position: Offset)
```

Sends a down event for the pointer with the given [pointerId](/jetpack-compose/androidx.compose.ui/ui/classes/PointerId) at `position` on the external
indirect pointer input device. The `position` is NOT in the node's local coordinate system
(see [inputDeviceSize](#inputdevicesize)).

If no pointers are down yet, this will start a new Indirect pointer input gesture. If a
gesture is already in progress (that is, there are other pointer ids that are down), this
event is sent at the same timestamp as the last event. You cannot call down with a pointer id
that is already down.

#### Parameters

| | |
| --- | --- |
| pointerId | The id of the pointer, can be any number not yet in use by another pointer |
| position | The position of the down event, in the input device's coordinate system. |

### down

```kotlin
fun down(position: Offset)
```

Sends a down event for the default pointer at `position` on the indirect pointer input device
sending the input. The `position` is NOT in the node's local coordinate system (see
[inputDeviceSize](#inputdevicesize)).

If no pointers are down yet, this will start a new Indirect pointer input gesture. If a
gesture is already in progress, this event is sent at the same timestamp as the last event.
If the given pointer is already down, @throws `IllegalArgumentException`.

#### Parameters

| | |
| --- | --- |
| position | The position of the down event, in the input device's coordinate system. |

### moveTo

```kotlin
fun moveTo(pointerId: Int, position: Offset, delayMillis: Long = eventPeriodMillis)
```

Sends a move event `delayMillis` after the last sent event on nodes in the focus path, with
the position of the pointer with the given [pointerId](/jetpack-compose/androidx.compose.ui/ui/classes/PointerId) updated to `position`. The `position`
is NOT in the node's local coordinate system (see [inputDeviceSize](#inputdevicesize)).

If the pointer is not yet down, @throws `IllegalArgumentException`.

#### Parameters

| | |
| --- | --- |
| pointerId | The id of the pointer to move, as supplied in [down](/jetpack-compose/androidx.compose.ui/ui-test/functions/down) |
| position | The new position of the pointer, in the indirect pointer input device's coordinate system |
| delayMillis | The time between the last sent event and this event. [eventPeriodMillis](#eventperiodmillis) by default. |

### moveTo

```kotlin
fun moveTo(position: Offset, delayMillis: Long = eventPeriodMillis)
```

Sends a move event `delayMillis` after the last sent event on nodes in the focus path, with
the position of the default pointer updated to `position`. The `position` is NOT in the
node's local coordinate system (see [inputDeviceSize](#inputdevicesize)).

If the default pointer is not yet down, @throws `IllegalArgumentException`.

#### Parameters

| | |
| --- | --- |
| position | The new position of the pointer, in the indirect pointer input device's coordinate system |
| delayMillis | The time between the last sent event and this event. [eventPeriodMillis](#eventperiodmillis) by default. |

### updatePointerTo

```kotlin
fun updatePointerTo(pointerId: Int, position: Offset)
```

Updates the position of the pointer with the given [pointerId](/jetpack-compose/androidx.compose.ui/ui/classes/PointerId) to the given `position` within
the indirect pointer input device's bounds, but does not send a move event. The move event
can be sent with [move](/jetpack-compose/androidx.compose.ui/ui-test/functions/move). The `position` is NOT in the node's local coordinate system (see
[inputDeviceSize](#inputdevicesize)).

If the pointer is not yet down, @throws `IllegalArgumentException`.

#### Parameters

| | |
| --- | --- |
| pointerId | The id of the pointer to move, as supplied in [down](/jetpack-compose/androidx.compose.ui/ui-test/functions/down) |
| position | The new position of the pointer, in the indirect pointer input device's coordinate system |

### updatePointerTo

```kotlin
fun updatePointerTo(position: Offset)
```

Updates the position of the default pointer (`pointerId = 0`) to the given `position` within
the indirect pointer input device's bounds, but does not send a move event. The move event
can be sent with [move](/jetpack-compose/androidx.compose.ui/ui-test/functions/move). The `position` is NOT in the node's local coordinate system (see
[inputDeviceSize](#inputdevicesize)).

If the pointer is not yet down, @throws `IllegalArgumentException`.

#### Parameters

| | |
| --- | --- |
| position | The new position of the pointer, in the indirect pointer input device's coordinate system |

### moveBy

```kotlin
fun moveBy(pointerId: Int, delta: Offset, delayMillis: Long = eventPeriodMillis)
```

Sends a move event `delayMillis` after the last sent event on nodes in the focus path, with
the position of the pointer with the given [pointerId](/jetpack-compose/androidx.compose.ui/ui/classes/PointerId) moved by the given `delta`.

If the pointer is not yet down, @throws `IllegalArgumentException`.

#### Parameters

| | |
| --- | --- |
| pointerId | The id of the pointer to move, as supplied in [down](/jetpack-compose/androidx.compose.ui/ui-test/functions/down) |
| delta | The position for this move event, relative to the current position of the pointer. For example, `delta = Offset(10.px, -10.px) will add 10.px to the pointer's x-position, and subtract 10.px from the pointer's y-position. |
| delayMillis | The time between the last sent event and this event. [eventPeriodMillis](#eventperiodmillis) by default. |

### moveBy

```kotlin
fun moveBy(delta: Offset, delayMillis: Long = eventPeriodMillis)
```

Sends a move event `delayMillis` after the last sent event on nodes in the focus path, with
the position of the default pointer moved by the given `delta`. The default pointer has
`pointerId = 0`.

If the pointer is not yet down, @throws `IllegalArgumentException`.

#### Parameters

| | |
| --- | --- |
| delta | The position for this move event, relative to the current position of the pointer. For example, `delta = Offset(10.px, -10.px) will add 10.px to the pointer's x-position, and subtract 10.px from the pointer's y-position. |
| delayMillis | The time between the last sent event and this event. [eventPeriodMillis](#eventperiodmillis) by default. |

### updatePointerBy

```kotlin
fun updatePointerBy(pointerId: Int, delta: Offset)
```

Updates the position of the pointer with the given [pointerId](/jetpack-compose/androidx.compose.ui/ui/classes/PointerId) by the given `delta`, but does
not send a move event. The move event can be sent with [move](/jetpack-compose/androidx.compose.ui/ui-test/functions/move).

If the pointer is not yet down, @throws `IllegalArgumentException`.

#### Parameters

| | |
| --- | --- |
| pointerId | The id of the pointer to move, as supplied in [down](/jetpack-compose/androidx.compose.ui/ui-test/functions/down) |
| delta | The position for this move event, relative to the last sent position of the pointer. For example, `delta = Offset(10.px, -10.px) will add 10.px to the pointer's x-position, and subtract 10.px from the pointer's y-position. |

### move

```kotlin
fun move(delayMillis: Long = eventPeriodMillis)
```

Sends a move event `delayMillis` after the last sent event without updating any of the
pointer positions. This can be useful when batching movement of multiple pointers together,
which can be done with [updatePointerTo](#updatepointerto) and [updatePointerBy](#updatepointerby).

#### Parameters

| | |
| --- | --- |
| delayMillis | The time between the last sent event and this event. [eventPeriodMillis](#eventperiodmillis) by default. |

### moveWithHistoryMultiPointer

```kotlin
fun moveWithHistoryMultiPointer(
        relativeHistoricalTimes: List<Long>,
        historicalCoordinates: List<List<Offset>>,
        delayMillis: Long = eventPeriodMillis,
    )
```

Sends a move event `delayMillis` after the last sent event without updating any of the
pointer positions, while adding the `historicalCoordinates` at the `relativeHistoricalTimes`
to the move event. This corresponds to the scenario where an external touchpad generates
Indirect pointer input events quicker than can be dispatched and batches them together.

#### Parameters

| | |
| --- | --- |
| relativeHistoricalTimes | Time of each historical event, as a millisecond relative to the time the actual event is sent. For example, -10L means 10ms earlier. |
| historicalCoordinates | Coordinates of each historical event, in the same coordinate space as [moveTo](/jetpack-compose/androidx.compose.ui/ui-test/functions/moveTo). The outer list must have the same size as the number of pointers in the event, and each inner list must have the same size as `relativeHistoricalTimes`. The `i`th pointer is assigned the `i`th history, with the pointers sorted on ascending pointerId. |
| delayMillis | The time between the last sent event and this event. [eventPeriodMillis](#eventperiodmillis) by default. |

### moveWithHistory

```kotlin
fun moveWithHistory(
        relativeHistoricalTimes: List<Long>,
        historicalCoordinates: List<Offset>,
        delayMillis: Long = eventPeriodMillis,
    ) =
        moveWithHistoryMultiPointer(
            relativeHistoricalTimes,
            listOf(historicalCoordinates),
            delayMillis,
        )
```

Sends a move event `delayMillis` after the last sent event without updating any of the
pointer positions, while adding the `historicalCoordinates` at the `relativeHistoricalTimes`
to the move event. This corresponds to the scenario where the external device generates
Indirect pointer input events quicker than can be dispatched and batches them together.

This overload is a convenience method for the common case where the gesture only has one
pointer.

#### Parameters

| | |
| --- | --- |
| relativeHistoricalTimes | Time of each historical event, as a millisecond relative to the time the actual event is sent. For example, -10L means 10ms earlier. |
| historicalCoordinates | Coordinates of each historical event, in the same coordinate space as [moveTo](/jetpack-compose/androidx.compose.ui/ui-test/functions/moveTo). The list must have the same size as `relativeHistoricalTimes`. |
| delayMillis | The time between the last sent event and this event. [eventPeriodMillis](#eventperiodmillis) by default. |

### up

```kotlin
fun up(pointerId: Int = 0)
```

Sends an up event for the pointer with the given [pointerId](/jetpack-compose/androidx.compose.ui/ui/classes/PointerId), or the default pointer if
[pointerId](/jetpack-compose/androidx.compose.ui/ui/classes/PointerId) is omitted, on nodes in the focus path.

#### Parameters

| | |
| --- | --- |
| pointerId | The id of the pointer to liftup, as supplied in [down](/jetpack-compose/androidx.compose.ui/ui-test/functions/down) |

### cancel

```kotlin
fun cancel(delayMillis: Long = eventPeriodMillis)
```

Sends a cancel event `delayMillis` after the last sent event to cancel the current gesture.
The cancel event contains the current position of all active pointers.

#### Parameters

| | |
| --- | --- |
| delayMillis | The time between the last sent event and this event. [eventPeriodMillis](#eventperiodmillis) by default. |

## Code Examples

### indirectPointerInputAssertDuringClick
```kotlin
fun indirectPointerInputAssertDuringClick() {
    // Ensure the node is within the focus path (otherwise, you won't get the event).
    composeTestRule.onNodeWithTag("myComponent").requestFocus()
    composeTestRule.performIndirectPointerInput(
        indirectPointerEventPrimaryDirectionalMotionAxis =
            IndirectPointerEventPrimaryDirectionalMotionAxis.X,
        // Horizontal trackpad
        inputDeviceSize = IntSize(width = 5000, height = 1000),
    ) {
        down(position = Offset(x = inputDeviceCenterX, y = inputDeviceCenterY))
    }
    // Assert some pressed state is visible
    composeTestRule.performIndirectPointerInput(
        indirectPointerEventPrimaryDirectionalMotionAxis =
            IndirectPointerEventPrimaryDirectionalMotionAxis.X,
        // Horizontal trackpad
        inputDeviceSize = IntSize(width = 5000, height = 1000),
    ) {
        up()
    }
}
```

### indirectPointerInputClick
```kotlin
// Click options:
fun indirectPointerInputClick() {
    // Ensure the node is within the focus path (otherwise, you won't get the event).
    composeTestRule.onNodeWithTag("myComponent").requestFocus()
    composeTestRule.performIndirectPointerInput(
        indirectPointerEventPrimaryDirectionalMotionAxis =
            IndirectPointerEventPrimaryDirectionalMotionAxis.X,
        // Horizontal trackpad
        inputDeviceSize = IntSize(width = 5000, height = 1000),
    ) {
        click()
    }
}
```

### indirectPointerInputSwipeRight
```kotlin
// Swipe options
fun indirectPointerInputSwipeRight() {
    // Ensure your node is within the focus path (otherwise, you won't get the event).
    composeTestRule.onNodeWithTag("myComponent").requestFocus()
    composeTestRule.performIndirectPointerInput(
        indirectPointerEventPrimaryDirectionalMotionAxis =
            IndirectPointerEventPrimaryDirectionalMotionAxis.X,
        // Horizontal trackpad
        inputDeviceSize = IntSize(width = 5000, height = 1000),
    ) {
        swipeRight(startX = inputDeviceLeft, endX = inputDeviceRight)
    }
}
```
