CropCutoutCornersKey Property
val CropCutoutCornersKey: SemanticsPropertyKey<List<Offset>>
Semantics key publishing where CropPicture’s corner handles are currently drawn, so that a UI test can aim a gesture at one.
The crop surface positions its handles from the picture, the outline and the current zoom, none of which a test can reasonably recompute. Without this key a test can see that the crop screen is displayed but cannot find a handle to drag, which makes crop editing effectively untestable. Read the key from the node’s semantics, convert as described below, and drive an ordinary drag.
Finding the node. The key is published by a node inside the crop surface, not by the node a caller tags. CropPicture applies the modifier it is given to its own root, while the handles are drawn by a descendant, so looking the key up on a tagged node finds nothing. Match on the key itself and search the unmerged tree, as the sample below does.
Coordinate space. The values are pixel coordinates local to the node that publishes them — not window coordinates, not screen coordinates, and not the picture’s own pixel grid. Zoom and pan are already applied, so a value read here is where the handle is right now. How much converting is needed depends entirely on what consumes the result:
- Compose touch injection on this same node — no conversion at all.
performTouchInputtakes coordinates local to the node it is invoked on, so a published corner is already in the right space. Adding an offset here introduces a spurious displacement and can move the gesture off the handle. - A consumer that works in window coordinates — add the publishing node’s
positionInWindow. - Injected events outside Compose, sent through
UiAutomationoradb shell input motionevent— add the publishing node’spositionInWindowand then the window’s own origin on screen (decorView.getLocationOnScreen), because those events are dispatched in screen coordinates. Omitting the window origin still lands correctly while the window happens to start at the top-left of the display, and misses everywhere else.
Order. The corners are published in the same order the outline stores them, so index 0 is the same corner across reads, and the order is stable as the outline is edited. Treat the order as positional identity, not as a compass direction: a rotated or reflected page can place index 0 at any visual corner.
When there is no outline. The list is empty. CropPicture renders no handles when it is given a null cutout, and the key reports that faithfully rather than reporting stale positions. A test should treat empty as “nothing to drag”, not as a failure to publish.
This key is intended for instrumentation and UI testing. It is inert in production: nothing in the SDK reads it, and it takes no part in gesture handling or drawing.
@get:Rule
val composeRule = createAndroidComposeRule<MyActivity>()
// Match on the key: it is published inside the crop surface, not by the node you tagged.
val surface = composeRule
.onNode(SemanticsMatcher.keyIsDefined(CropCutoutCornersKey), useUnmergedTree = true)
val corner = surface.fetchSemanticsNode().config[CropCutoutCornersKey].first()
// Dragging with Compose, on this same node: the published value is already correct.
surface.performTouchInput { swipe(corner, corner + Offset(40f, 40f)) }
// Dragging with an injected event instead: convert all the way to screen coordinates.
val node = surface.fetchSemanticsNode()
val origin = IntArray(2)
.also { composeRule.activity.window.decorView.getLocationOnScreen(it) }
val inWindow = corner + node.positionInWindow
val onScreen = Offset(origin[0] + inWindow.x, origin[1] + inWindow.y)