1 /*
<lambda>null2  * Copyright 2022 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.glance.semantics
18 
19 /**
20  * General semantics properties, mainly used for accessibility and testing.
21  *
22  * Each property is intended to be set by the respective SemanticsPropertyReceiver extension instead
23  * of used directly.
24  */
25 object SemanticsProperties {
26     /** @see SemanticsPropertyReceiver.contentDescription */
27     val ContentDescription =
28         SemanticsPropertyKey<List<String>>(
29             name = "ContentDescription",
30             mergePolicy = { parentValue, childValue ->
31                 parentValue?.toMutableList()?.also { it.addAll(childValue) } ?: childValue
32             }
33         )
34 
35     /** @see SemanticsPropertyReceiver.testTag */
36     val TestTag =
37         SemanticsPropertyKey<String>(
38             name = "TestTag",
39             mergePolicy = { parentValue, _ ->
40                 // No merge
41                 parentValue
42             }
43         )
44 }
45 
46 /**
47  * SemanticsPropertyKey is the infrastructure for setting key/value pairs inside semantics block in
48  * a type-safe way. Each key has one particular statically defined value type T.
49  */
50 class SemanticsPropertyKey<T>(
51     /** The name of the property. Should be the same as the constant from shich it is accessed. */
52     val name: String,
childValuenull53     internal val mergePolicy: (T?, T) -> T? = { parentValue, childValue ->
54         parentValue ?: childValue
55     }
56 ) {
mergenull57     fun merge(parentValue: T?, childValue: T): T? {
58         return mergePolicy(parentValue, childValue)
59     }
60 }
61 
62 /**
63  * SemanticsPropertyReceiver is the scope provided by semantics {} blocks, letting you set key/value
64  * pairs primarily via extension functions.
65  */
66 interface SemanticsPropertyReceiver {
setnull67     operator fun <T> set(key: SemanticsPropertyKey<T>, value: T)
68 }
69 
70 /**
71  * Developer-set content description of the semantics node, for use in testing, accessibility and
72  * similar use cases.
73  */
74 var SemanticsPropertyReceiver.contentDescription: String
75     /** Throws [UnsupportedOperationException]. Should not be called. */
76     get() {
77         throw UnsupportedOperationException("You cannot retrieve a semantics property directly")
78     }
79     set(value) {
80         set(SemanticsProperties.ContentDescription, listOf(value))
81     }
82 
83 /**
84  * Test tag attached to this Glance composable node.
85  *
86  * This is a free form String and can be used to find nodes in testing frameworks.
87  */
88 var SemanticsPropertyReceiver.testTag: String
89     /** Throws [UnsupportedOperationException]. Should not be called. */
90     get() {
91         throw UnsupportedOperationException("You cannot retrieve a semantics property directly")
92     }
93     set(value) {
94         set(SemanticsProperties.TestTag, value)
95     }
96 
97 /** Describes the semantics information associated with the owning component. */
98 class SemanticsConfiguration : SemanticsPropertyReceiver {
99     private val props: MutableMap<SemanticsPropertyKey<*>, Any?> = mutableMapOf()
100 
setnull101     override fun <T> set(key: SemanticsPropertyKey<T>, value: T) {
102         props[key] = value
103     }
104 
105     /**
106      * Retrieves the value for the given property, if one has been set, If a value has not been set,
107      * throws [IllegalStateException]
108      */
109     // Unavoidable, guaranteed by [set]
110     @Suppress("UNCHECKED_CAST")
getnull111     operator fun <T> get(key: SemanticsPropertyKey<T>): T {
112         return props.getOrElse(key) {
113             throw java.lang.IllegalStateException("Key not present: $key")
114         } as T
115     }
116 
117     /**
118      * Retrieves the value for the given property, if one has been set, If a value has not been set,
119      * returns the provided default value.
120      */
121     // Unavoidable, guaranteed by [set]
122     @Suppress("UNCHECKED_CAST")
getOrElseNullablenull123     fun <T> getOrElseNullable(key: SemanticsPropertyKey<T>, defaultValue: () -> T?): T? {
124         return props.getOrElse(key, defaultValue) as T?
125     }
126 
127     /**
128      * Retrieves the value for the given property, if one has been set, If a value has not been set,
129      * returns null
130      */
getOrNullnull131     fun <T> getOrNull(key: SemanticsPropertyKey<T>): T? {
132         return getOrElseNullable(key) { null }
133     }
134 }
135