1 /*
<lambda>null2 * Copyright 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package androidx.compose.ui.semantics
18
19 import androidx.compose.runtime.Immutable
20 import androidx.compose.ui.autofill.ContentDataType
21 import androidx.compose.ui.autofill.ContentType
22 import androidx.compose.ui.geometry.Offset
23 import androidx.compose.ui.state.ToggleableState
24 import androidx.compose.ui.text.AnnotatedString
25 import androidx.compose.ui.text.TextLayoutResult
26 import androidx.compose.ui.text.TextRange
27 import androidx.compose.ui.text.input.ImeAction
28 import kotlin.reflect.KProperty
29
30 /**
31 * General semantics properties, mainly used for accessibility and testing.
32 *
33 * Each of these is intended to be set by the respective SemanticsPropertyReceiver extension instead
34 * of used directly.
35 */
36 /*@VisibleForTesting*/
37 object SemanticsProperties {
38 /** @see SemanticsPropertyReceiver.contentDescription */
39 val ContentDescription =
40 AccessibilityKey<List<String>>(
41 name = "ContentDescription",
42 mergePolicy = { parentValue, childValue ->
43 parentValue?.toMutableList()?.also { it.addAll(childValue) } ?: childValue
44 }
45 )
46
47 /** @see SemanticsPropertyReceiver.stateDescription */
48 val StateDescription = AccessibilityKey<String>("StateDescription")
49
50 /** @see SemanticsPropertyReceiver.progressBarRangeInfo */
51 val ProgressBarRangeInfo = AccessibilityKey<ProgressBarRangeInfo>("ProgressBarRangeInfo")
52
53 /** @see SemanticsPropertyReceiver.paneTitle */
54 val PaneTitle =
55 AccessibilityKey<String>(
56 name = "PaneTitle",
57 mergePolicy = { _, _ ->
58 throw IllegalStateException(
59 "merge function called on unmergeable property PaneTitle."
60 )
61 }
62 )
63
64 /** @see SemanticsPropertyReceiver.selectableGroup */
65 val SelectableGroup = AccessibilityKey<Unit>("SelectableGroup")
66
67 /** @see SemanticsPropertyReceiver.collectionInfo */
68 val CollectionInfo = AccessibilityKey<CollectionInfo>("CollectionInfo")
69
70 /** @see SemanticsPropertyReceiver.collectionItemInfo */
71 val CollectionItemInfo = AccessibilityKey<CollectionItemInfo>("CollectionItemInfo")
72
73 /** @see SemanticsPropertyReceiver.heading */
74 val Heading = AccessibilityKey<Unit>("Heading")
75
76 /** @see SemanticsPropertyReceiver.disabled */
77 val Disabled = AccessibilityKey<Unit>("Disabled")
78
79 /** @see SemanticsPropertyReceiver.liveRegion */
80 val LiveRegion = AccessibilityKey<LiveRegionMode>("LiveRegion")
81
82 /** @see SemanticsPropertyReceiver.focused */
83 val Focused = AccessibilityKey<Boolean>("Focused")
84
85 /** @see SemanticsPropertyReceiver.isContainer */
86 @Deprecated(
87 "Use `isTraversalGroup` instead.",
88 replaceWith = ReplaceWith("IsTraversalGroup"),
89 )
90 // TODO(mnuzen): `isContainer` should not need to be an accessibility key after a new
91 // pruning API is added. See b/347038246 for more details.
92 val IsContainer = AccessibilityKey<Boolean>("IsContainer")
93
94 /** @see SemanticsPropertyReceiver.isTraversalGroup */
95 val IsTraversalGroup = SemanticsPropertyKey<Boolean>("IsTraversalGroup")
96
97 /** @see SemanticsPropertyReceiver.invisibleToUser */
98 @Deprecated(
99 "Use `hideFromAccessibility` instead.",
100 replaceWith = ReplaceWith("HideFromAccessibility")
101 )
102 @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
103 // Retain for binary compatibility with aosp/3341487 in 1.7
104 val InvisibleToUser =
105 SemanticsPropertyKey<Unit>(
106 name = "InvisibleToUser",
107 mergePolicy = { parentValue, _ -> parentValue }
108 )
109
110 /** @see SemanticsPropertyReceiver.hideFromAccessibility */
111 val HideFromAccessibility =
112 SemanticsPropertyKey<Unit>(
113 name = "HideFromAccessibility",
114 mergePolicy = { parentValue, _ -> parentValue }
115 )
116
117 /** @see SemanticsPropertyReceiver.contentType */
118 val ContentType =
119 SemanticsPropertyKey<ContentType>(
120 name = "ContentType",
121 mergePolicy = { parentValue, _ ->
122 // Never merge autofill types
123 parentValue
124 }
125 )
126
127 /** @see SemanticsPropertyReceiver.contentDataType */
128 val ContentDataType =
129 SemanticsPropertyKey<ContentDataType>(
130 name = "ContentDataType",
131 mergePolicy = { parentValue, _ ->
132 // Never merge autofill data types
133 parentValue
134 }
135 )
136
137 /** @see SemanticsPropertyReceiver.traversalIndex */
138 val TraversalIndex =
139 SemanticsPropertyKey<Float>(
140 name = "TraversalIndex",
141 mergePolicy = { parentValue, _ ->
142 // Never merge traversal indices
143 parentValue
144 }
145 )
146
147 /** @see SemanticsPropertyReceiver.horizontalScrollAxisRange */
148 val HorizontalScrollAxisRange = AccessibilityKey<ScrollAxisRange>("HorizontalScrollAxisRange")
149
150 /** @see SemanticsPropertyReceiver.verticalScrollAxisRange */
151 val VerticalScrollAxisRange = AccessibilityKey<ScrollAxisRange>("VerticalScrollAxisRange")
152
153 /** @see SemanticsPropertyReceiver.popup */
154 val IsPopup =
155 AccessibilityKey<Unit>(
156 name = "IsPopup",
157 mergePolicy = { _, _ ->
158 throw IllegalStateException(
159 "merge function called on unmergeable property IsPopup. " +
160 "A popup should not be a child of a clickable/focusable node."
161 )
162 }
163 )
164
165 /** @see SemanticsPropertyReceiver.dialog */
166 val IsDialog =
167 AccessibilityKey<Unit>(
168 name = "IsDialog",
169 mergePolicy = { _, _ ->
170 throw IllegalStateException(
171 "merge function called on unmergeable property IsDialog. " +
172 "A dialog should not be a child of a clickable/focusable node."
173 )
174 }
175 )
176
177 /**
178 * The type of user interface element. Accessibility services might use this to describe the
179 * element or do customizations. Most roles can be automatically resolved by the semantics
180 * properties of this element. But some elements with subtle differences need an exact role. If
181 * an exact role is not listed in [Role], this property should not be set and the framework will
182 * automatically resolve it.
183 *
184 * @see SemanticsPropertyReceiver.role
185 */
186 val Role = AccessibilityKey<Role>("Role") { parentValue, _ -> parentValue }
187
188 /** @see SemanticsPropertyReceiver.testTag */
189 val TestTag =
190 SemanticsPropertyKey<String>(
191 name = "TestTag",
192 isImportantForAccessibility = false,
193 mergePolicy = { parentValue, _ ->
194 // Never merge TestTags, to avoid leaking internal test tags to parents.
195 parentValue
196 }
197 )
198
199 /**
200 * Marks a link within a text node (a link is represented by a
201 * [androidx.compose.ui.text.LinkAnnotation]) for identification during automated testing. This
202 * property is for internal use only and not intended for general use by developers.
203 */
204 val LinkTestMarker =
205 SemanticsPropertyKey<Unit>(
206 name = "LinkTestMarker",
207 isImportantForAccessibility = false,
208 mergePolicy = { parentValue, _ -> parentValue }
209 )
210
211 /** @see SemanticsPropertyReceiver.text */
212 val Text =
213 AccessibilityKey<List<AnnotatedString>>(
214 name = "Text",
215 mergePolicy = { parentValue, childValue ->
216 parentValue?.toMutableList()?.also { it.addAll(childValue) } ?: childValue
217 }
218 )
219
220 /** @see SemanticsPropertyReceiver.textSubstitution */
221 val TextSubstitution = SemanticsPropertyKey<AnnotatedString>(name = "TextSubstitution")
222
223 /** @see SemanticsPropertyReceiver.isShowingTextSubstitution */
224 val IsShowingTextSubstitution = SemanticsPropertyKey<Boolean>("IsShowingTextSubstitution")
225
226 /** @see SemanticsPropertyReceiver.inputText */
227 val InputText = AccessibilityKey<AnnotatedString>(name = "InputText")
228
229 /** @see SemanticsPropertyReceiver.editableText */
230 val EditableText = AccessibilityKey<AnnotatedString>(name = "EditableText")
231
232 /** @see SemanticsPropertyReceiver.textSelectionRange */
233 val TextSelectionRange = AccessibilityKey<TextRange>("TextSelectionRange")
234
235 /** @see SemanticsPropertyReceiver.onImeAction */
236 val ImeAction = AccessibilityKey<ImeAction>("ImeAction")
237
238 /** @see SemanticsPropertyReceiver.selected */
239 val Selected = AccessibilityKey<Boolean>("Selected")
240
241 /** @see SemanticsPropertyReceiver.toggleableState */
242 val ToggleableState = AccessibilityKey<ToggleableState>("ToggleableState")
243
244 /** @see SemanticsPropertyReceiver.password */
245 val Password = AccessibilityKey<Unit>("Password")
246
247 /** @see SemanticsPropertyReceiver.error */
248 val Error = AccessibilityKey<String>("Error")
249
250 /** @see SemanticsPropertyReceiver.indexForKey */
251 val IndexForKey = SemanticsPropertyKey<(Any) -> Int>("IndexForKey")
252
253 /** @see SemanticsPropertyReceiver.isEditable */
254 val IsEditable = SemanticsPropertyKey<Boolean>("IsEditable")
255
256 /** @see SemanticsPropertyReceiver.maxTextLength */
257 val MaxTextLength = SemanticsPropertyKey<Int>("MaxTextLength")
258 }
259
260 /**
261 * Ths object defines keys of the actions which can be set in semantics and performed on the
262 * semantics node.
263 *
264 * Each of these is intended to be set by the respective SemanticsPropertyReceiver extension instead
265 * of used directly.
266 */
267 /*@VisibleForTesting*/
268 object SemanticsActions {
269 /** @see SemanticsPropertyReceiver.getTextLayoutResult */
270 val GetTextLayoutResult =
271 ActionPropertyKey<(MutableList<TextLayoutResult>) -> Boolean>("GetTextLayoutResult")
272
273 /** @see SemanticsPropertyReceiver.onClick */
274 val OnClick = ActionPropertyKey<() -> Boolean>("OnClick")
275
276 /** @see SemanticsPropertyReceiver.onLongClick */
277 val OnLongClick = ActionPropertyKey<() -> Boolean>("OnLongClick")
278
279 /** @see SemanticsPropertyReceiver.scrollBy */
280 val ScrollBy = ActionPropertyKey<(x: Float, y: Float) -> Boolean>("ScrollBy")
281
282 /** @see SemanticsPropertyReceiver.scrollByOffset */
283 val ScrollByOffset = SemanticsPropertyKey<suspend (offset: Offset) -> Offset>("ScrollByOffset")
284
285 /** @see SemanticsPropertyReceiver.scrollToIndex */
286 val ScrollToIndex = ActionPropertyKey<(Int) -> Boolean>("ScrollToIndex")
287
288 /** @see SemanticsPropertyReceiver.onAutofillText */
289 val OnAutofillText = ActionPropertyKey<(AnnotatedString) -> Boolean>("OnAutofillText")
290
291 /** @see SemanticsPropertyReceiver.setProgress */
292 val SetProgress = ActionPropertyKey<(progress: Float) -> Boolean>("SetProgress")
293
294 /** @see SemanticsPropertyReceiver.setSelection */
295 val SetSelection = ActionPropertyKey<(Int, Int, Boolean) -> Boolean>("SetSelection")
296
297 /** @see SemanticsPropertyReceiver.setText */
298 val SetText = ActionPropertyKey<(AnnotatedString) -> Boolean>("SetText")
299
300 /** @see SemanticsPropertyReceiver.setTextSubstitution */
301 val SetTextSubstitution = ActionPropertyKey<(AnnotatedString) -> Boolean>("SetTextSubstitution")
302
303 /** @see SemanticsPropertyReceiver.showTextSubstitution */
304 val ShowTextSubstitution = ActionPropertyKey<(Boolean) -> Boolean>("ShowTextSubstitution")
305
306 /** @see SemanticsPropertyReceiver.clearTextSubstitution */
307 val ClearTextSubstitution = ActionPropertyKey<() -> Boolean>("ClearTextSubstitution")
308
309 /** @see SemanticsPropertyReceiver.insertTextAtCursor */
310 val InsertTextAtCursor = ActionPropertyKey<(AnnotatedString) -> Boolean>("InsertTextAtCursor")
311
312 /** @see SemanticsPropertyReceiver.onImeAction */
313 val OnImeAction = ActionPropertyKey<() -> Boolean>("PerformImeAction")
314
315 // b/322269946
316 @Suppress("unused")
317 @Deprecated(
318 message = "Use `SemanticsActions.OnImeAction` instead.",
319 replaceWith =
320 ReplaceWith(
321 "OnImeAction",
322 "androidx.compose.ui.semantics.SemanticsActions.OnImeAction",
323 ),
324 level = DeprecationLevel.ERROR,
325 )
326 val PerformImeAction = ActionPropertyKey<() -> Boolean>("PerformImeAction")
327
328 /** @see SemanticsPropertyReceiver.copyText */
329 val CopyText = ActionPropertyKey<() -> Boolean>("CopyText")
330
331 /** @see SemanticsPropertyReceiver.cutText */
332 val CutText = ActionPropertyKey<() -> Boolean>("CutText")
333
334 /** @see SemanticsPropertyReceiver.pasteText */
335 val PasteText = ActionPropertyKey<() -> Boolean>("PasteText")
336
337 /** @see SemanticsPropertyReceiver.expand */
338 val Expand = ActionPropertyKey<() -> Boolean>("Expand")
339
340 /** @see SemanticsPropertyReceiver.collapse */
341 val Collapse = ActionPropertyKey<() -> Boolean>("Collapse")
342
343 /** @see SemanticsPropertyReceiver.dismiss */
344 val Dismiss = ActionPropertyKey<() -> Boolean>("Dismiss")
345
346 /** @see SemanticsPropertyReceiver.requestFocus */
347 val RequestFocus = ActionPropertyKey<() -> Boolean>("RequestFocus")
348
349 /** @see SemanticsPropertyReceiver.customActions */
350 val CustomActions = AccessibilityKey<List<CustomAccessibilityAction>>("CustomActions")
351
352 /** @see SemanticsPropertyReceiver.pageUp */
353 val PageUp = ActionPropertyKey<() -> Boolean>("PageUp")
354
355 /** @see SemanticsPropertyReceiver.pageLeft */
356 val PageLeft = ActionPropertyKey<() -> Boolean>("PageLeft")
357
358 /** @see SemanticsPropertyReceiver.pageDown */
359 val PageDown = ActionPropertyKey<() -> Boolean>("PageDown")
360
361 /** @see SemanticsPropertyReceiver.pageRight */
362 val PageRight = ActionPropertyKey<() -> Boolean>("PageRight")
363
364 /** @see SemanticsPropertyReceiver.getScrollViewportLength */
365 val GetScrollViewportLength =
366 ActionPropertyKey<(MutableList<Float>) -> Boolean>("GetScrollViewportLength")
367 }
368
369 /**
370 * SemanticsPropertyKey is the infrastructure for setting key/value pairs inside semantics blocks in
371 * a type-safe way. Each key has one particular statically defined value type T.
372 */
373 class SemanticsPropertyKey<T>(
374 /** The name of the property. Should be the same as the constant from which it is accessed. */
375 val name: String,
childValuenull376 internal val mergePolicy: (T?, T) -> T? = { parentValue, childValue ->
377 parentValue ?: childValue
378 }
379 ) {
380 /**
381 * Whether this type of property provides information relevant to accessibility services.
382 *
383 * Most built-in semantics properties are relevant to accessibility, but a very common exception
384 * is testTag. Nodes with only a testTag still need to be included in the AccessibilityNodeInfo
385 * tree because UIAutomator tests rely on that, but we mark them `isImportantForAccessibility =
386 * false` on the AccessibilityNodeInfo to inform accessibility services that they are best
387 * ignored.
388 *
389 * The default value is false and it is not exposed as a public API. That's because it is
390 * impossible in the first place for `SemanticsPropertyKey`s defined outside the UI package to
391 * be relevant to accessibility, because for each accessibility-relevant SemanticsProperty type
392 * to get plumbed into the AccessibilityNodeInfo, the private `createNodeInfo` implementation
393 * must also have a line of code.
394 */
395 internal var isImportantForAccessibility = false
396 private set
397
398 internal constructor(
399 name: String,
400 isImportantForAccessibility: Boolean,
401 ) : this(name) {
402 this.isImportantForAccessibility = isImportantForAccessibility
403 }
404
405 internal constructor(
406 name: String,
407 isImportantForAccessibility: Boolean,
408 mergePolicy: (T?, T) -> T?
409 ) : this(name, mergePolicy) {
410 this.isImportantForAccessibility = isImportantForAccessibility
411 }
412
413 /**
414 * Method implementing the semantics merge policy of a particular key.
415 *
416 * When mergeDescendants is set on a semantics node, then this function will called for each
417 * descendant node of a given key in depth-first-search order. The parent value accumulates the
418 * result of merging the values seen so far, similar to reduce().
419 *
420 * The default implementation returns the parent value if one exists, otherwise uses the child
421 * element. This means by default, a SemanticsNode with mergeDescendants = true winds up with
422 * the first value found for each key in its subtree in depth-first-search order.
423 */
mergenull424 fun merge(parentValue: T?, childValue: T): T? {
425 return mergePolicy(parentValue, childValue)
426 }
427
428 /** Throws [UnsupportedOperationException]. Should not be called. */
429 // TODO(KT-6519): Remove this getter
430 // TODO(KT-32770): Cannot deprecate this either as the getter is considered called by "by"
getValuenull431 final operator fun getValue(thisRef: SemanticsPropertyReceiver, property: KProperty<*>): T {
432 return throwSemanticsGetNotSupported()
433 }
434
setValuenull435 final operator fun setValue(
436 thisRef: SemanticsPropertyReceiver,
437 property: KProperty<*>,
438 value: T
439 ) {
440 thisRef[this] = value
441 }
442
toStringnull443 override fun toString(): String {
444 return "AccessibilityKey: $name"
445 }
446 }
447
throwSemanticsGetNotSupportednull448 private fun <T> throwSemanticsGetNotSupported(): T {
449 throw UnsupportedOperationException(
450 "You cannot retrieve a semantics property directly - " +
451 "use one of the SemanticsConfiguration.getOr* methods instead"
452 )
453 }
454
455 @Suppress("NOTHING_TO_INLINE")
456 // inline to avoid different static initialization order on different targets.
457 // See https://youtrack.jetbrains.com/issue/KT-65040 for more information.
AccessibilityKeynull458 internal inline fun <T> AccessibilityKey(name: String) =
459 SemanticsPropertyKey<T>(name = name, isImportantForAccessibility = true)
460
461 @Suppress("NOTHING_TO_INLINE")
462 // inline to avoid different static initialization order on different targets
463 // See https://youtrack.jetbrains.com/issue/KT-65040 for more information.
464 internal inline fun <T> AccessibilityKey(name: String, noinline mergePolicy: (T?, T) -> T?) =
465 SemanticsPropertyKey(name = name, isImportantForAccessibility = true, mergePolicy = mergePolicy)
466
467 /**
468 * Standard accessibility action.
469 *
470 * @param label The description of this action
471 * @param action The function to invoke when this action is performed. The function should return a
472 * boolean result indicating whether the action is successfully handled. For example, a scroll
473 * forward action should return false if the widget is not enabled or has reached the end of the
474 * list. If multiple semantics blocks with the same AccessibilityAction are provided, the
475 * resulting AccessibilityAction's label/action will be the label/action of the outermost modifier
476 * with this key and nonnull label/action, or null if no nonnull label/action is found.
477 */
478 class AccessibilityAction<T : Function<Boolean>>(val label: String?, val action: T?) {
479 override fun equals(other: Any?): Boolean {
480 if (this === other) return true
481 if (other !is AccessibilityAction<*>) return false
482
483 if (label != other.label) return false
484 if (action != other.action) return false
485
486 return true
487 }
488
489 override fun hashCode(): Int {
490 var result = label?.hashCode() ?: 0
491 result = 31 * result + action.hashCode()
492 return result
493 }
494
495 override fun toString(): String {
496 return "AccessibilityAction(label=$label, action=$action)"
497 }
498 }
499
500 @Suppress("NOTHING_TO_INLINE")
501 // inline to break static initialization cycle issue
ActionPropertyKeynull502 private inline fun <T : Function<Boolean>> ActionPropertyKey(name: String) =
503 AccessibilityKey<AccessibilityAction<T>>(
504 name = name,
505 mergePolicy = { parentValue, childValue ->
506 AccessibilityAction(
507 parentValue?.label ?: childValue.label,
508 parentValue?.action ?: childValue.action
509 )
510 }
511 )
512
513 /**
514 * Custom accessibility action.
515 *
516 * @param label The description of this action
517 * @param action The function to invoke when this action is performed. The function should have no
518 * arguments and return a boolean result indicating whether the action is successfully handled.
519 */
520 class CustomAccessibilityAction(val label: String, val action: () -> Boolean) {
equalsnull521 override fun equals(other: Any?): Boolean {
522 if (this === other) return true
523 if (other !is CustomAccessibilityAction) return false
524
525 if (label != other.label) return false
526 if (action !== other.action) return false
527
528 return true
529 }
530
hashCodenull531 override fun hashCode(): Int {
532 var result = label.hashCode()
533 result = 31 * result + action.hashCode()
534 return result
535 }
536
toStringnull537 override fun toString(): String {
538 return "CustomAccessibilityAction(label=$label, action=$action)"
539 }
540 }
541
542 /**
543 * Accessibility range information, to represent the status of a progress bar or seekable progress
544 * bar.
545 *
546 * @param current current value in the range. Must not be NaN.
547 * @param range range of this node
548 * @param steps if greater than `0`, specifies the number of discrete values, evenly distributed
549 * between across the whole value range. If `0`, any value from the range specified can be chosen.
550 * Cannot be less than `0`.
551 */
552 class ProgressBarRangeInfo(
553 val current: Float,
554 val range: ClosedFloatingPointRange<Float>,
555 /*@IntRange(from = 0)*/
556 val steps: Int = 0
557 ) {
558 init {
<lambda>null559 require(!current.isNaN()) { "current must not be NaN" }
560 }
561
562 companion object {
563 /** Accessibility range information to present indeterminate progress bar */
564 val Indeterminate = ProgressBarRangeInfo(0f, 0f..0f)
565 }
566
equalsnull567 override fun equals(other: Any?): Boolean {
568 if (this === other) return true
569 if (other !is ProgressBarRangeInfo) return false
570
571 if (current != other.current) return false
572 if (range != other.range) return false
573 if (steps != other.steps) return false
574
575 return true
576 }
577
hashCodenull578 override fun hashCode(): Int {
579 var result = current.hashCode()
580 result = 31 * result + range.hashCode()
581 result = 31 * result + steps
582 return result
583 }
584
toStringnull585 override fun toString(): String {
586 return "ProgressBarRangeInfo(current=$current, range=$range, steps=$steps)"
587 }
588 }
589
590 /**
591 * Information about the collection.
592 *
593 * A collection of items has [rowCount] rows and [columnCount] columns. For example, a vertical list
594 * is a collection with one column, as many rows as the list items that are important for
595 * accessibility; A table is a collection with several rows and several columns.
596 *
597 * @param rowCount the number of rows in the collection, or -1 if unknown
598 * @param columnCount the number of columns in the collection, or -1 if unknown
599 */
600 class CollectionInfo(val rowCount: Int, val columnCount: Int)
601
602 /**
603 * Information about the item of a collection.
604 *
605 * A collection item is contained in a collection, it starts at a given [rowIndex] and [columnIndex]
606 * in the collection, and spans one or more rows and columns. For example, a header of two related
607 * table columns starts at the first row and the first column, spans one row and two columns.
608 *
609 * @param rowIndex the index of the row at which item is located
610 * @param rowSpan the number of rows the item spans
611 * @param columnIndex the index of the column at which item is located
612 * @param columnSpan the number of columns the item spans
613 */
614 class CollectionItemInfo(
615 val rowIndex: Int,
616 val rowSpan: Int,
617 val columnIndex: Int,
618 val columnSpan: Int
619 )
620
621 /**
622 * The scroll state of one axis if this node is scrollable.
623 *
624 * @param value current 0-based scroll position value (either in pixels, or lazy-item count)
625 * @param maxValue maximum bound for [value], or [Float.POSITIVE_INFINITY] if still unknown
626 * @param reverseScrolling for horizontal scroll, when this is `true`, 0 [value] will mean right,
627 * when`false`, 0 [value] will mean left. For vertical scroll, when this is `true`, 0 [value] will
628 * mean bottom, when `false`, 0 [value] will mean top
629 */
630 class ScrollAxisRange(
631 val value: () -> Float,
632 val maxValue: () -> Float,
633 val reverseScrolling: Boolean = false
634 ) {
toStringnull635 override fun toString(): String =
636 "ScrollAxisRange(value=${value()}, maxValue=${maxValue()}, " +
637 "reverseScrolling=$reverseScrolling)"
638 }
639
640 /**
641 * The type of user interface element. Accessibility services might use this to describe the element
642 * or do customizations. Most roles can be automatically resolved by the semantics properties of
643 * this element. But some elements with subtle differences need an exact role. If an exact role is
644 * not listed, [SemanticsPropertyReceiver.role] should not be set and the framework will
645 * automatically resolve it.
646 */
647 @Immutable
648 @kotlin.jvm.JvmInline
649 value class Role private constructor(@Suppress("unused") private val value: Int) {
650 companion object {
651 /**
652 * This element is a button control. Associated semantics properties for accessibility:
653 * [SemanticsProperties.Disabled], [SemanticsActions.OnClick]
654 */
655 val Button = Role(0)
656
657 /**
658 * This element is a Checkbox which is a component that represents two states (checked /
659 * unchecked). Associated semantics properties for accessibility:
660 * [SemanticsProperties.Disabled], [SemanticsProperties.StateDescription],
661 * [SemanticsActions.OnClick]
662 */
663 val Checkbox = Role(1)
664
665 /**
666 * This element is a Switch which is a two state toggleable component that provides on/off
667 * like options. Associated semantics properties for accessibility:
668 * [SemanticsProperties.Disabled], [SemanticsProperties.StateDescription],
669 * [SemanticsActions.OnClick]
670 */
671 val Switch = Role(2)
672
673 /**
674 * This element is a RadioButton which is a component to represent two states, selected and
675 * not selected. Associated semantics properties for accessibility:
676 * [SemanticsProperties.Disabled], [SemanticsProperties.StateDescription],
677 * [SemanticsActions.OnClick]
678 */
679 val RadioButton = Role(3)
680
681 /**
682 * This element is a Tab which represents a single page of content using a text label and/or
683 * icon. A Tab also has two states: selected and not selected. Associated semantics
684 * properties for accessibility: [SemanticsProperties.Disabled],
685 * [SemanticsProperties.StateDescription], [SemanticsActions.OnClick]
686 */
687 val Tab = Role(4)
688
689 /**
690 * This element is an image. Associated semantics properties for accessibility:
691 * [SemanticsProperties.ContentDescription]
692 */
693 val Image = Role(5)
694
695 /**
696 * This element is associated with a drop down menu. Associated semantics properties for
697 * accessibility: [SemanticsActions.OnClick]
698 */
699 val DropdownList = Role(6)
700
701 /**
702 * This element is a value picker. It should support the following accessibility actions to
703 * enable selection of the next and previous values:
704 *
705 * [android.view.accessibility.AccessibilityNodeInfo.ACTION_SCROLL_FORWARD]: Select the next
706 * value.
707 *
708 * [android.view.accessibility.AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD]: Select the
709 * previous value.
710 *
711 * These actions allow accessibility services to interact with this node programmatically on
712 * behalf of users, facilitating navigation within sets of selectable values.
713 */
714 val ValuePicker = Role(7)
715
716 /**
717 * This element is a Carousel. This means that even if Pager actions are added, this element
718 * will behave like a regular List collection.
719 *
720 * Associated semantics properties for Pager accessibility actions:
721 * [SemanticsActions.PageUp],[SemanticsActions.PageDown],[SemanticsActions.PageLeft],
722 * [SemanticsActions.PageRight]
723 */
724 val Carousel = Role(8)
725 }
726
727 override fun toString() =
728 when (this) {
729 Button -> "Button"
730 Checkbox -> "Checkbox"
731 Switch -> "Switch"
732 RadioButton -> "RadioButton"
733 Tab -> "Tab"
734 Image -> "Image"
735 DropdownList -> "DropdownList"
736 ValuePicker -> "Picker"
737 Carousel -> "Carousel"
738 else -> "Unknown"
739 }
740 }
741
742 /**
743 * The mode of live region. Live region indicates to accessibility services they should
744 * automatically notify the user about changes to the node's content description or text, or to the
745 * content descriptions or text of the node's children (where applicable).
746 */
747 @Immutable
748 @kotlin.jvm.JvmInline
749 value class LiveRegionMode private constructor(@Suppress("unused") private val value: Int) {
750 companion object {
751 /**
752 * Live region mode specifying that accessibility services should announce changes to this
753 * node.
754 */
755 val Polite = LiveRegionMode(0)
756
757 /**
758 * Live region mode specifying that accessibility services should interrupt ongoing speech
759 * to immediately announce changes to this node.
760 */
761 val Assertive = LiveRegionMode(1)
762 }
763
toStringnull764 override fun toString() =
765 when (this) {
766 Polite -> "Polite"
767 Assertive -> "Assertive"
768 else -> "Unknown"
769 }
770 }
771
772 /**
773 * SemanticsPropertyReceiver is the scope provided by semantics {} blocks, letting you set key/value
774 * pairs primarily via extension functions.
775 */
776 interface SemanticsPropertyReceiver {
setnull777 operator fun <T> set(key: SemanticsPropertyKey<T>, value: T)
778 }
779
780 /**
781 * Developer-set content description of the semantics node.
782 *
783 * If this is not set, accessibility services will present the [text][SemanticsProperties.Text] of
784 * this node as the content.
785 *
786 * This typically should not be set directly by applications, because some screen readers will cease
787 * presenting other relevant information when this property is present. This is intended to be used
788 * via Foundation components which are inherently intractable to automatically describe, such as
789 * Image, Icon, and Canvas.
790 */
791 var SemanticsPropertyReceiver.contentDescription: String
792 get() = throwSemanticsGetNotSupported()
793 set(value) {
794 set(SemanticsProperties.ContentDescription, listOf(value))
795 }
796
797 /**
798 * Developer-set state description of the semantics node.
799 *
800 * For example: on/off. If this not set, accessibility services will derive the state from other
801 * semantics properties, like [ProgressBarRangeInfo], but it is not guaranteed and the format will
802 * be decided by accessibility services.
803 */
804 var SemanticsPropertyReceiver.stateDescription by SemanticsProperties.StateDescription
805
806 /**
807 * The semantics represents a range of possible values with a current value. For example, when used
808 * on a slider control, this will allow screen readers to communicate the slider's state.
809 */
810 var SemanticsPropertyReceiver.progressBarRangeInfo by SemanticsProperties.ProgressBarRangeInfo
811
812 /**
813 * The node is marked as heading for accessibility.
814 *
815 * @see SemanticsProperties.Heading
816 */
SemanticsPropertyReceivernull817 fun SemanticsPropertyReceiver.heading() {
818 this[SemanticsProperties.Heading] = Unit
819 }
820
821 /**
822 * Accessibility-friendly title for a screen's pane. For accessibility purposes, a pane is a
823 * visually distinct portion of a window, such as the contents of a open drawer. In order for
824 * accessibility services to understand a pane's window-like behavior, you should give descriptive
825 * titles to your app's panes. Accessibility services can then provide more granular information to
826 * users when a pane's appearance or content changes.
827 *
828 * @see SemanticsProperties.PaneTitle
829 */
830 var SemanticsPropertyReceiver.paneTitle by SemanticsProperties.PaneTitle
831
832 /**
833 * Whether this semantics node is disabled. Note that proper [SemanticsActions] should still be
834 * added when this property is set.
835 *
836 * @see SemanticsProperties.Disabled
837 */
SemanticsPropertyReceivernull838 fun SemanticsPropertyReceiver.disabled() {
839 this[SemanticsProperties.Disabled] = Unit
840 }
841
842 /**
843 * This node is marked as live region for accessibility. This indicates to accessibility services
844 * they should automatically notify the user about changes to the node's content description or
845 * text, or to the content descriptions or text of the node's children (where applicable). It should
846 * be used with caution, especially with assertive mode which immediately stops the current audio
847 * and the user does not hear the rest of the content. An example of proper use is a Snackbar which
848 * is marked as [LiveRegionMode.Polite].
849 *
850 * @see SemanticsProperties.LiveRegion
851 * @see LiveRegionMode
852 */
853 var SemanticsPropertyReceiver.liveRegion by SemanticsProperties.LiveRegion
854
855 /**
856 * Whether this semantics node is focused. The presence of this property indicates this node is
857 * focusable
858 *
859 * @see SemanticsProperties.Focused
860 */
861 var SemanticsPropertyReceiver.focused by SemanticsProperties.Focused
862
863 /**
864 * Whether this semantics node is a container. This is defined as a node whose function is to serve
865 * as a boundary or border in organizing its children.
866 *
867 * @see SemanticsProperties.IsContainer
868 */
869 @Deprecated(
870 "Use `isTraversalGroup` instead.",
871 replaceWith = ReplaceWith("isTraversalGroup"),
872 )
873 @Suppress("DEPRECATION")
874 var SemanticsPropertyReceiver.isContainer by SemanticsProperties.IsContainer
875
876 /**
877 * Whether this semantics node is a traversal group.
878 *
879 * See https://developer.android.com/develop/ui/compose/accessibility/traversal
880 *
881 * @see SemanticsProperties.IsTraversalGroup
882 */
883 var SemanticsPropertyReceiver.isTraversalGroup by SemanticsProperties.IsTraversalGroup
884
885 /**
886 * Whether this node is specially known to be invisible to the user.
887 *
888 * For example, if the node is currently occluded by a dark semitransparent pane above it, then for
889 * all practical purposes the node is invisible to the user, but the system cannot automatically
890 * determine that. To make the screen reader linear navigation skip over this type of invisible
891 * node, this property can be set.
892 *
893 * If looking for a way to hide semantics of small items from screen readers because they're
894 * redundant with semantics of their parent, consider [SemanticsModifier.clearAndSetSemantics]
895 * instead.
896 */
897 @Deprecated(
898 "Use `hideFromAccessibility()` instead.",
899 replaceWith = ReplaceWith("hideFromAccessibility()"),
900 )
901 @Suppress("DEPRECATION")
902 // Retain for binary compatibility with aosp/3341487 in 1.7
SemanticsPropertyReceivernull903 fun SemanticsPropertyReceiver.invisibleToUser() {
904 this[SemanticsProperties.InvisibleToUser] = Unit
905 }
906
907 /**
908 * If present, this node is considered hidden from accessibility services.
909 *
910 * For example, if the node is currently occluded by a dark semitransparent pane above it, then for
911 * all practical purposes the node should not be announced to the user. Since the system cannot
912 * automatically determine that, this property can be set to make the screen reader linear
913 * navigation skip over this type of node.
914 *
915 * If looking for a way to clear semantics of small items from the UI tree completely because they
916 * are redundant with semantics of their parent, consider [SemanticsModifier.clearAndSetSemantics]
917 * instead.
918 */
SemanticsPropertyReceivernull919 fun SemanticsPropertyReceiver.hideFromAccessibility() {
920 this[SemanticsProperties.HideFromAccessibility] = Unit
921 }
922
923 /**
924 * Content field type information.
925 *
926 * This API can be used to indicate to Autofill services what _kind of field_ is associated with
927 * this node. Not to be confused with the _data type_ to be entered into the field.
928 *
929 * @see SemanticsProperties.ContentType
930 */
931 var SemanticsPropertyReceiver.contentType by SemanticsProperties.ContentType
932
933 /**
934 * Content data type information.
935 *
936 * This API can be used to indicate to Autofill services what _kind of data_ is meant to be
937 * suggested for this field. Not to be confused with the _type_ of the field.
938 *
939 * @see SemanticsProperties.ContentType
940 */
941 var SemanticsPropertyReceiver.contentDataType by SemanticsProperties.ContentDataType
942
943 /**
944 * A value to manually control screenreader traversal order.
945 *
946 * This API can be used to customize TalkBack traversal order. When the `traversalIndex` property is
947 * set on a traversalGroup or on a screenreader-focusable node, then the sorting algorithm will
948 * prioritize nodes with smaller `traversalIndex`s earlier. The default traversalIndex value is
949 * zero, and traversalIndices are compared at a peer level.
950 *
951 * For example,` traversalIndex = -1f` can be used to force a top bar to be ordered earlier, and
952 * `traversalIndex = 1f` to make a bottom bar ordered last, in the edge cases where this does not
953 * happen by default. As another example, if you need to reorder two Buttons within a Row, then you
954 * can set `isTraversalGroup = true` on the Row, and set `traversalIndex` on one of the Buttons.
955 *
956 * Note that if `traversalIndex` seems to have no effect, be sure to set `isTraversalGroup = true`
957 * as well.
958 */
959 var SemanticsPropertyReceiver.traversalIndex by SemanticsProperties.TraversalIndex
960
961 /** The horizontal scroll state of this node if this node is scrollable. */
962 var SemanticsPropertyReceiver.horizontalScrollAxisRange by
963 SemanticsProperties.HorizontalScrollAxisRange
964
965 /** The vertical scroll state of this node if this node is scrollable. */
966 var SemanticsPropertyReceiver.verticalScrollAxisRange by SemanticsProperties.VerticalScrollAxisRange
967
968 /**
969 * Whether this semantics node represents a Popup. Not to be confused with if this node is _part of_
970 * a Popup.
971 */
popupnull972 fun SemanticsPropertyReceiver.popup() {
973 this[SemanticsProperties.IsPopup] = Unit
974 }
975
976 /**
977 * Whether this element is a Dialog. Not to be confused with if this element is _part of_ a Dialog.
978 */
dialognull979 fun SemanticsPropertyReceiver.dialog() {
980 this[SemanticsProperties.IsDialog] = Unit
981 }
982
983 /**
984 * The type of user interface element. Accessibility services might use this to describe the element
985 * or do customizations. Most roles can be automatically resolved by the semantics properties of
986 * this element. But some elements with subtle differences need an exact role. If an exact role is
987 * not listed in [Role], this property should not be set and the framework will automatically
988 * resolve it.
989 */
990 var SemanticsPropertyReceiver.role by SemanticsProperties.Role
991
992 /**
993 * Test tag attached to this semantics node.
994 *
995 * This can be used to find nodes in testing frameworks:
996 * - In Compose's built-in unit test framework, use with
997 * [onNodeWithTag][androidx.compose.ui.test.onNodeWithTag].
998 * - For newer AccessibilityNodeInfo-based integration test frameworks, it can be matched in the
999 * extras with key "androidx.compose.ui.semantics.testTag"
1000 * - For legacy AccessibilityNodeInfo-based integration tests, it's optionally exposed as the
1001 * resource id if [testTagsAsResourceId] is true (for matching with 'By.res' in UIAutomator).
1002 */
1003 var SemanticsPropertyReceiver.testTag by SemanticsProperties.TestTag
1004
1005 /**
1006 * Text of the semantics node. It must be real text instead of developer-set content description.
1007 *
1008 * @see SemanticsPropertyReceiver.editableText
1009 */
1010 var SemanticsPropertyReceiver.text: AnnotatedString
1011 get() = throwSemanticsGetNotSupported()
1012 set(value) {
1013 set(SemanticsProperties.Text, listOf(value))
1014 }
1015
1016 /**
1017 * Text substitution of the semantics node. This property is only available after calling
1018 * [SemanticsActions.SetTextSubstitution].
1019 */
1020 var SemanticsPropertyReceiver.textSubstitution by SemanticsProperties.TextSubstitution
1021
1022 /**
1023 * Whether this element is showing the text substitution. This property is only available after
1024 * calling [SemanticsActions.SetTextSubstitution].
1025 */
1026 var SemanticsPropertyReceiver.isShowingTextSubstitution by
1027 SemanticsProperties.IsShowingTextSubstitution
1028
1029 /**
1030 * The raw value of the text field after input transformations have been applied.
1031 *
1032 * This is an actual user input of the fields, e.g. a real password, after any input transformations
1033 * that might change or reject that input have been applied. This value is not affected by visual
1034 * transformations.
1035 */
1036 var SemanticsPropertyReceiver.inputText by SemanticsProperties.InputText
1037
1038 /**
1039 * A visual value of the text field after output transformations that change the visual
1040 * representation of the field's state have been applied.
1041 *
1042 * This is the value displayed to the user, for example "*******" in a password field.
1043 */
1044 var SemanticsPropertyReceiver.editableText by SemanticsProperties.EditableText
1045
1046 /** Text selection range for the text field. */
1047 var SemanticsPropertyReceiver.textSelectionRange by SemanticsProperties.TextSelectionRange
1048
1049 /**
1050 * Contains the IME action provided by the node.
1051 *
1052 * For example, "go to next form field" or "submit".
1053 *
1054 * A node that specifies an action should also specify a callback to perform the action via
1055 * [onImeAction].
1056 */
1057 @Deprecated("Pass the ImeAction to onImeAction instead.")
1058 @get:Deprecated("Pass the ImeAction to onImeAction instead.")
1059 @set:Deprecated("Pass the ImeAction to onImeAction instead.")
1060 var SemanticsPropertyReceiver.imeAction by SemanticsProperties.ImeAction
1061
1062 /**
1063 * Whether this element is selected (out of a list of possible selections).
1064 *
1065 * The presence of this property indicates that the element is selectable.
1066 */
1067 var SemanticsPropertyReceiver.selected by SemanticsProperties.Selected
1068
1069 /**
1070 * This semantics marks node as a collection and provides the required information.
1071 *
1072 * @see collectionItemInfo
1073 */
1074 var SemanticsPropertyReceiver.collectionInfo by SemanticsProperties.CollectionInfo
1075
1076 /**
1077 * This semantics marks node as an items of a collection and provides the required information.
1078 *
1079 * If you mark items of a collection, you should also be marking the collection with
1080 * [collectionInfo].
1081 */
1082 var SemanticsPropertyReceiver.collectionItemInfo by SemanticsProperties.CollectionItemInfo
1083
1084 /**
1085 * The state of a toggleable component.
1086 *
1087 * The presence of this property indicates that the element is toggleable.
1088 */
1089 var SemanticsPropertyReceiver.toggleableState by SemanticsProperties.ToggleableState
1090
1091 /** Whether this semantics node is editable, e.g. an editable text field. */
1092 var SemanticsPropertyReceiver.isEditable by SemanticsProperties.IsEditable
1093
1094 /** The node is marked as a password. */
passwordnull1095 fun SemanticsPropertyReceiver.password() {
1096 this[SemanticsProperties.Password] = Unit
1097 }
1098
1099 /**
1100 * Mark semantics node that contains invalid input or error.
1101 *
1102 * @param [description] a localized description explaining an error to the accessibility user
1103 */
SemanticsPropertyReceivernull1104 fun SemanticsPropertyReceiver.error(description: String) {
1105 this[SemanticsProperties.Error] = description
1106 }
1107
1108 /**
1109 * The index of an item identified by a given key. The key is usually defined during the creation of
1110 * the container. If the key did not match any of the items' keys, the [mapping] must return -1.
1111 */
SemanticsPropertyReceivernull1112 fun SemanticsPropertyReceiver.indexForKey(mapping: (Any) -> Int) {
1113 this[SemanticsProperties.IndexForKey] = mapping
1114 }
1115
1116 /**
1117 * Limits the number of characters that can be entered, e.g. in an editable text field. By default
1118 * this value is -1, signifying there is no maximum text length limit.
1119 */
1120 var SemanticsPropertyReceiver.maxTextLength by SemanticsProperties.MaxTextLength
1121
1122 /**
1123 * The node is marked as a collection of horizontally or vertically stacked selectable elements.
1124 *
1125 * Unlike [collectionInfo] which marks a collection of any elements and asks developer to provide
1126 * all the required information like number of elements etc., this semantics will populate the
1127 * number of selectable elements automatically. Note that if you use this semantics with lazy
1128 * collections, it won't get the number of elements in the collection.
1129 *
1130 * @see SemanticsPropertyReceiver.selected
1131 */
SemanticsPropertyReceivernull1132 fun SemanticsPropertyReceiver.selectableGroup() {
1133 this[SemanticsProperties.SelectableGroup] = Unit
1134 }
1135
1136 /** Custom actions which are defined by app developers. */
1137 var SemanticsPropertyReceiver.customActions by SemanticsActions.CustomActions
1138
1139 /**
1140 * Action to get a Text/TextField node's [TextLayoutResult]. The result is the first element of
1141 * layout (the argument of the AccessibilityAction).
1142 *
1143 * @param label Optional label for this action.
1144 * @param action Action to be performed when the [SemanticsActions.GetTextLayoutResult] is called.
1145 */
SemanticsPropertyReceivernull1146 fun SemanticsPropertyReceiver.getTextLayoutResult(
1147 label: String? = null,
1148 action: ((MutableList<TextLayoutResult>) -> Boolean)?
1149 ) {
1150 this[SemanticsActions.GetTextLayoutResult] = AccessibilityAction(label, action)
1151 }
1152
1153 /**
1154 * Action to be performed when the node is clicked (single-tapped).
1155 *
1156 * @param label Optional label for this action.
1157 * @param action Action to be performed when the [SemanticsActions.OnClick] is called.
1158 */
onClicknull1159 fun SemanticsPropertyReceiver.onClick(label: String? = null, action: (() -> Boolean)?) {
1160 this[SemanticsActions.OnClick] = AccessibilityAction(label, action)
1161 }
1162
1163 /**
1164 * Action to be performed when the node is long clicked (long-pressed).
1165 *
1166 * @param label Optional label for this action.
1167 * @param action Action to be performed when the [SemanticsActions.OnLongClick] is called.
1168 */
onLongClicknull1169 fun SemanticsPropertyReceiver.onLongClick(label: String? = null, action: (() -> Boolean)?) {
1170 this[SemanticsActions.OnLongClick] = AccessibilityAction(label, action)
1171 }
1172
1173 /**
1174 * Action to asynchronously scroll by a specified amount.
1175 *
1176 * [scrollByOffset] should be preferred in most cases, since it is synchronous and returns the
1177 * amount of scroll that was actually consumed.
1178 *
1179 * Expected to be used in conjunction with [verticalScrollAxisRange]/[horizontalScrollAxisRange].
1180 *
1181 * @param label Optional label for this action.
1182 * @param action Action to be performed when [SemanticsActions.ScrollBy] is called.
1183 */
scrollBynull1184 fun SemanticsPropertyReceiver.scrollBy(
1185 label: String? = null,
1186 action: ((x: Float, y: Float) -> Boolean)?
1187 ) {
1188 this[SemanticsActions.ScrollBy] = AccessibilityAction(label, action)
1189 }
1190
1191 /**
1192 * Action to scroll by a specified amount and return how much of the offset was actually consumed.
1193 * E.g. if the node can't scroll at all in the given direction, [Offset.Zero] should be returned.
1194 * The action should not return until the scroll operation has finished.
1195 *
1196 * Expected to be used in conjunction with [verticalScrollAxisRange]/[horizontalScrollAxisRange].
1197 *
1198 * Unlike [scrollBy], this action is synchronous, and returns the amount of scroll consumed.
1199 *
1200 * @param action Action to be performed when [SemanticsActions.ScrollByOffset] is called.
1201 */
SemanticsPropertyReceivernull1202 fun SemanticsPropertyReceiver.scrollByOffset(action: suspend (offset: Offset) -> Offset) {
1203 this[SemanticsActions.ScrollByOffset] = action
1204 }
1205
1206 /**
1207 * Action to scroll a container to the index of one of its items.
1208 *
1209 * The [action] should throw an [IllegalArgumentException] if the index is out of bounds.
1210 */
scrollToIndexnull1211 fun SemanticsPropertyReceiver.scrollToIndex(label: String? = null, action: (Int) -> Boolean) {
1212 this[SemanticsActions.ScrollToIndex] = AccessibilityAction(label, action)
1213 }
1214
1215 /**
1216 * Action to autofill a TextField.
1217 *
1218 * Expected to be used in conjunction with [contentType] and [contentDataType] properties.
1219 *
1220 * @param label Optional label for this action.
1221 * @param action Action to be performed when the [SemanticsActions.OnAutofillText] is called.
1222 */
SemanticsPropertyReceivernull1223 fun SemanticsPropertyReceiver.onAutofillText(
1224 label: String? = null,
1225 action: ((AnnotatedString) -> Boolean)?
1226 ) {
1227 this[SemanticsActions.OnAutofillText] = AccessibilityAction(label, action)
1228 }
1229
1230 /**
1231 * Action to set the current value of the progress bar.
1232 *
1233 * Expected to be used in conjunction with progressBarRangeInfo.
1234 *
1235 * @param label Optional label for this action.
1236 * @param action Action to be performed when the [SemanticsActions.SetProgress] is called.
1237 */
SemanticsPropertyReceivernull1238 fun SemanticsPropertyReceiver.setProgress(label: String? = null, action: ((Float) -> Boolean)?) {
1239 this[SemanticsActions.SetProgress] = AccessibilityAction(label, action)
1240 }
1241
1242 /**
1243 * Action to set the text contents of this node.
1244 *
1245 * Expected to be used on editable text fields.
1246 *
1247 * @param label Optional label for this action.
1248 * @param action Action to be performed when [SemanticsActions.SetText] is called.
1249 */
SemanticsPropertyReceivernull1250 fun SemanticsPropertyReceiver.setText(
1251 label: String? = null,
1252 action: ((AnnotatedString) -> Boolean)?
1253 ) {
1254 this[SemanticsActions.SetText] = AccessibilityAction(label, action)
1255 }
1256
1257 /**
1258 * Action to set the text substitution of this node.
1259 *
1260 * Expected to be used on non-editable text.
1261 *
1262 * Note, this action doesn't show the text substitution. Please call
1263 * [SemanticsPropertyReceiver.showTextSubstitution] to show the text substitution.
1264 *
1265 * @param label Optional label for this action.
1266 * @param action Action to be performed when [SemanticsActions.SetTextSubstitution] is called.
1267 */
setTextSubstitutionnull1268 fun SemanticsPropertyReceiver.setTextSubstitution(
1269 label: String? = null,
1270 action: ((AnnotatedString) -> Boolean)?
1271 ) {
1272 this[SemanticsActions.SetTextSubstitution] = AccessibilityAction(label, action)
1273 }
1274
1275 /**
1276 * Action to show or hide the text substitution of this node.
1277 *
1278 * Expected to be used on non-editable text.
1279 *
1280 * Note, this action only takes effect when the node has the text substitution.
1281 *
1282 * @param label Optional label for this action.
1283 * @param action Action to be performed when [SemanticsActions.ShowTextSubstitution] is called.
1284 */
SemanticsPropertyReceivernull1285 fun SemanticsPropertyReceiver.showTextSubstitution(
1286 label: String? = null,
1287 action: ((Boolean) -> Boolean)?
1288 ) {
1289 this[SemanticsActions.ShowTextSubstitution] = AccessibilityAction(label, action)
1290 }
1291
1292 /**
1293 * Action to clear the text substitution of this node.
1294 *
1295 * Expected to be used on non-editable text.
1296 *
1297 * @param label Optional label for this action.
1298 * @param action Action to be performed when [SemanticsActions.ClearTextSubstitution] is called.
1299 */
SemanticsPropertyReceivernull1300 fun SemanticsPropertyReceiver.clearTextSubstitution(
1301 label: String? = null,
1302 action: (() -> Boolean)?
1303 ) {
1304 this[SemanticsActions.ClearTextSubstitution] = AccessibilityAction(label, action)
1305 }
1306
1307 /**
1308 * Action to insert text into this node at the current cursor position, or replacing the selection
1309 * if text is selected.
1310 *
1311 * Expected to be used on editable text fields.
1312 *
1313 * @param label Optional label for this action.
1314 * @param action Action to be performed when [SemanticsActions.InsertTextAtCursor] is called.
1315 */
SemanticsPropertyReceivernull1316 fun SemanticsPropertyReceiver.insertTextAtCursor(
1317 label: String? = null,
1318 action: ((AnnotatedString) -> Boolean)?
1319 ) {
1320 this[SemanticsActions.InsertTextAtCursor] = AccessibilityAction(label, action)
1321 }
1322
1323 /**
1324 * Action to invoke the IME action handler configured on the node, as well as specify the type of
1325 * IME action provided by the node.
1326 *
1327 * Expected to be used on editable text fields.
1328 *
1329 * @param imeActionType The IME type, such as [ImeAction.Next] or [ImeAction.Search]
1330 * @param label Optional label for this action.
1331 * @param action Action to be performed when [SemanticsActions.OnImeAction] is called.
1332 * @see SemanticsProperties.ImeAction
1333 * @see SemanticsActions.OnImeAction
1334 */
SemanticsPropertyReceivernull1335 fun SemanticsPropertyReceiver.onImeAction(
1336 imeActionType: ImeAction,
1337 label: String? = null,
1338 action: (() -> Boolean)?
1339 ) {
1340 this[SemanticsProperties.ImeAction] = imeActionType
1341 this[SemanticsActions.OnImeAction] = AccessibilityAction(label, action)
1342 }
1343
1344 // b/322269946
1345 @Suppress("unused")
1346 @Deprecated(
1347 message = "Use `SemanticsPropertyReceiver.onImeAction` instead.",
1348 replaceWith =
1349 ReplaceWith(
1350 "onImeAction(imeActionType = ImeAction.Default, label = label, action = action)",
1351 "androidx.compose.ui.semantics.onImeAction",
1352 "androidx.compose.ui.text.input.ImeAction",
1353 ),
1354 level = DeprecationLevel.ERROR,
1355 )
SemanticsPropertyReceivernull1356 fun SemanticsPropertyReceiver.performImeAction(label: String? = null, action: (() -> Boolean)?) {
1357 this[SemanticsActions.OnImeAction] = AccessibilityAction(label, action)
1358 }
1359
1360 /**
1361 * Action to set text selection by character index range.
1362 *
1363 * If this action is provided, the selection data must be provided using [textSelectionRange].
1364 *
1365 * @param label Optional label for this action.
1366 * @param action Action to be performed when the [SemanticsActions.SetSelection] is called. The
1367 * parameters to the action are: `startIndex`, `endIndex`, and whether the indices are relative to
1368 * the original text or the transformed text (when a `VisualTransformation` is applied).
1369 */
setSelectionnull1370 fun SemanticsPropertyReceiver.setSelection(
1371 label: String? = null,
1372 action: ((startIndex: Int, endIndex: Int, relativeToOriginalText: Boolean) -> Boolean)?
1373 ) {
1374 this[SemanticsActions.SetSelection] = AccessibilityAction(label, action)
1375 }
1376
1377 /**
1378 * Action to copy the text to the clipboard.
1379 *
1380 * @param label Optional label for this action.
1381 * @param action Action to be performed when the [SemanticsActions.CopyText] is called.
1382 */
SemanticsPropertyReceivernull1383 fun SemanticsPropertyReceiver.copyText(label: String? = null, action: (() -> Boolean)?) {
1384 this[SemanticsActions.CopyText] = AccessibilityAction(label, action)
1385 }
1386
1387 /**
1388 * Action to cut the text and copy it to the clipboard.
1389 *
1390 * @param label Optional label for this action.
1391 * @param action Action to be performed when the [SemanticsActions.CutText] is called.
1392 */
SemanticsPropertyReceivernull1393 fun SemanticsPropertyReceiver.cutText(label: String? = null, action: (() -> Boolean)?) {
1394 this[SemanticsActions.CutText] = AccessibilityAction(label, action)
1395 }
1396
1397 /**
1398 * This function adds the [SemanticsActions.PasteText] to the [SemanticsPropertyReceiver]. Use it to
1399 * indicate that element is open for accepting paste data from the clipboard. There is no need to
1400 * check if the clipboard data available as this is done by the framework. For this action to be
1401 * triggered, the element must also have the [SemanticsProperties.Focused] property set.
1402 *
1403 * @param label Optional label for this action.
1404 * @param action Action to be performed when the [SemanticsActions.PasteText] is called.
1405 * @see focused
1406 */
SemanticsPropertyReceivernull1407 fun SemanticsPropertyReceiver.pasteText(label: String? = null, action: (() -> Boolean)?) {
1408 this[SemanticsActions.PasteText] = AccessibilityAction(label, action)
1409 }
1410
1411 /**
1412 * Action to expand an expandable node.
1413 *
1414 * @param label Optional label for this action.
1415 * @param action Action to be performed when the [SemanticsActions.Expand] is called.
1416 */
expandnull1417 fun SemanticsPropertyReceiver.expand(label: String? = null, action: (() -> Boolean)?) {
1418 this[SemanticsActions.Expand] = AccessibilityAction(label, action)
1419 }
1420
1421 /**
1422 * Action to collapse an expandable node.
1423 *
1424 * @param label Optional label for this action.
1425 * @param action Action to be performed when the [SemanticsActions.Collapse] is called.
1426 */
SemanticsPropertyReceivernull1427 fun SemanticsPropertyReceiver.collapse(label: String? = null, action: (() -> Boolean)?) {
1428 this[SemanticsActions.Collapse] = AccessibilityAction(label, action)
1429 }
1430
1431 /**
1432 * Action to dismiss a dismissible node.
1433 *
1434 * @param label Optional label for this action.
1435 * @param action Action to be performed when the [SemanticsActions.Dismiss] is called.
1436 */
SemanticsPropertyReceivernull1437 fun SemanticsPropertyReceiver.dismiss(label: String? = null, action: (() -> Boolean)?) {
1438 this[SemanticsActions.Dismiss] = AccessibilityAction(label, action)
1439 }
1440
1441 /**
1442 * Action that gives input focus to this node.
1443 *
1444 * @param label Optional label for this action.
1445 * @param action Action to be performed when the [SemanticsActions.RequestFocus] is called.
1446 */
requestFocusnull1447 fun SemanticsPropertyReceiver.requestFocus(label: String? = null, action: (() -> Boolean)?) {
1448 this[SemanticsActions.RequestFocus] = AccessibilityAction(label, action)
1449 }
1450
1451 /**
1452 * Action to page up.
1453 *
1454 * Using [Role.Carousel] will prevent this action from being sent to accessibility services.
1455 *
1456 * @param label Optional label for this action.
1457 * @param action Action to be performed when the [SemanticsActions.PageUp] is called.
1458 * @see [Role.Carousel] for more information.
1459 */
SemanticsPropertyReceivernull1460 fun SemanticsPropertyReceiver.pageUp(label: String? = null, action: (() -> Boolean)?) {
1461 this[SemanticsActions.PageUp] = AccessibilityAction(label, action)
1462 }
1463
1464 /**
1465 * Action to page down.
1466 *
1467 * Using [Role.Carousel] will prevent this action from being sent to accessibility services.
1468 *
1469 * @param label Optional label for this action.
1470 * @param action Action to be performed when the [SemanticsActions.PageDown] is called.
1471 * @see [Role.Carousel] for more information.
1472 */
pageDownnull1473 fun SemanticsPropertyReceiver.pageDown(label: String? = null, action: (() -> Boolean)?) {
1474 this[SemanticsActions.PageDown] = AccessibilityAction(label, action)
1475 }
1476
1477 /**
1478 * Action to page left.
1479 *
1480 * Using [Role.Carousel] will prevent this action from being sent to accessibility services.
1481 *
1482 * @param label Optional label for this action.
1483 * @param action Action to be performed when the [SemanticsActions.PageLeft] is called.
1484 * @see [Role.Carousel] for more information.
1485 */
pageLeftnull1486 fun SemanticsPropertyReceiver.pageLeft(label: String? = null, action: (() -> Boolean)?) {
1487 this[SemanticsActions.PageLeft] = AccessibilityAction(label, action)
1488 }
1489
1490 /**
1491 * Action to page right.
1492 *
1493 * Using [Role.Carousel] will prevent this action from being sent to accessibility services.
1494 *
1495 * @param label Optional label for this action.
1496 * @param action Action to be performed when the [SemanticsActions.PageRight] is called.
1497 * @see [Role.Carousel] for more information.
1498 */
SemanticsPropertyReceivernull1499 fun SemanticsPropertyReceiver.pageRight(label: String? = null, action: (() -> Boolean)?) {
1500 this[SemanticsActions.PageRight] = AccessibilityAction(label, action)
1501 }
1502
1503 /**
1504 * Action to get a scrollable's active view port amount for scrolling actions.
1505 *
1506 * @param label Optional label for this action.
1507 * @param action Action to be performed when the [SemanticsActions.GetScrollViewportLength] is
1508 * called.
1509 */
SemanticsPropertyReceivernull1510 fun SemanticsPropertyReceiver.getScrollViewportLength(
1511 label: String? = null,
1512 action: (() -> Float?)
1513 ) {
1514 this[SemanticsActions.GetScrollViewportLength] =
1515 AccessibilityAction(label) {
1516 val viewport = action.invoke()
1517 if (viewport == null) {
1518 false
1519 } else {
1520 it.add(viewport)
1521 true
1522 }
1523 }
1524 }
1525