1 /*
2 * Copyright 2025 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.appfunctions.internal
18
19 import android.os.Build
20 import android.util.Log
21 import androidx.annotation.RequiresApi
22 import androidx.appfunctions.AppFunctionData
23 import androidx.appfunctions.AppFunctionInvalidArgumentException
24 import androidx.appfunctions.internal.Constants.APP_FUNCTIONS_TAG
25 import androidx.appfunctions.metadata.AppFunctionArrayTypeMetadata
26 import androidx.appfunctions.metadata.AppFunctionObjectTypeMetadata
27 import androidx.appfunctions.metadata.AppFunctionObjectTypeMetadata.Companion.TYPE as TYPE_OBJECT
28 import androidx.appfunctions.metadata.AppFunctionParameterMetadata
29 import androidx.appfunctions.metadata.AppFunctionPrimitiveTypeMetadata
30 import androidx.appfunctions.metadata.AppFunctionPrimitiveTypeMetadata.Companion.TYPE_BOOLEAN
31 import androidx.appfunctions.metadata.AppFunctionPrimitiveTypeMetadata.Companion.TYPE_BYTES
32 import androidx.appfunctions.metadata.AppFunctionPrimitiveTypeMetadata.Companion.TYPE_DOUBLE
33 import androidx.appfunctions.metadata.AppFunctionPrimitiveTypeMetadata.Companion.TYPE_FLOAT
34 import androidx.appfunctions.metadata.AppFunctionPrimitiveTypeMetadata.Companion.TYPE_INT
35 import androidx.appfunctions.metadata.AppFunctionPrimitiveTypeMetadata.Companion.TYPE_LONG
36 import androidx.appfunctions.metadata.AppFunctionPrimitiveTypeMetadata.Companion.TYPE_PENDING_INTENT
37 import androidx.appfunctions.metadata.AppFunctionPrimitiveTypeMetadata.Companion.TYPE_STRING
38 import androidx.appfunctions.metadata.AppFunctionReferenceTypeMetadata
39
40 /**
41 * Gets the parameter value from [AppFunctionData] based on [parameterMetadata].
42 *
43 * @throws [androidx.appfunctions.AppFunctionInvalidArgumentException] if the parameter in
44 * [AppFunctionData] is not valid according to [parameterMetadata].
45 */
46 @RequiresApi(Build.VERSION_CODES.TIRAMISU)
unsafeGetParameterValuenull47 internal fun AppFunctionData.unsafeGetParameterValue(
48 parameterMetadata: AppFunctionParameterMetadata,
49 ): Any? =
50 try {
51 val value =
52 when (val castDataType = parameterMetadata.dataType) {
53 is AppFunctionPrimitiveTypeMetadata -> {
54 unsafeGetSingleProperty(parameterMetadata.name, castDataType.type)
55 }
56 is AppFunctionObjectTypeMetadata -> {
57 unsafeGetSingleProperty(
58 parameterMetadata.name,
59 TYPE_OBJECT,
60 castDataType.qualifiedName
61 )
62 }
63 is AppFunctionArrayTypeMetadata -> {
64 getArrayTypeParameterValue(parameterMetadata.name, castDataType)
65 }
66 is AppFunctionReferenceTypeMetadata -> {
67 unsafeGetSingleProperty(
68 parameterMetadata.name,
69 TYPE_OBJECT,
70 castDataType.referenceDataType
71 )
72 }
73 else ->
74 throw IllegalStateException(
75 "Unknown DataTypeMetadata: ${castDataType::class.java}"
76 )
77 }
78 if (value == null) {
79 require(!parameterMetadata.isRequired) {
80 Log.d(APP_FUNCTIONS_TAG, "Parameter ${parameterMetadata.name} is required")
81 "Parameter ${parameterMetadata.name} is required"
82 }
83 }
84 value
85 } catch (e: IllegalArgumentException) {
86 Log.d(
87 APP_FUNCTIONS_TAG,
88 "Parameter ${parameterMetadata.name} should be the type of ${parameterMetadata.dataType}",
89 e
90 )
91 throw AppFunctionInvalidArgumentException(
92 "Parameter ${parameterMetadata.name} should be the type of ${parameterMetadata.dataType}"
93 )
94 }
95
96 @RequiresApi(Build.VERSION_CODES.TIRAMISU)
getArrayTypeParameterValuenull97 private fun AppFunctionData.getArrayTypeParameterValue(
98 key: String,
99 arrayDataTypeMetadata: AppFunctionArrayTypeMetadata
100 ): Any? {
101 val itemType = arrayDataTypeMetadata.itemType
102 return when (itemType) {
103 is AppFunctionPrimitiveTypeMetadata -> {
104 unsafeGetCollectionProperty(key, itemType.type)
105 }
106 is AppFunctionObjectTypeMetadata -> {
107 unsafeGetCollectionProperty(key, TYPE_OBJECT, itemType.qualifiedName)
108 }
109 is AppFunctionReferenceTypeMetadata -> {
110 unsafeGetCollectionProperty(key, TYPE_OBJECT, itemType.referenceDataType)
111 }
112 else ->
113 throw IllegalStateException("Unknown item DataTypeMetadata: ${itemType::class.java}")
114 }
115 }
116
117 @RequiresApi(Build.VERSION_CODES.TIRAMISU)
AppFunctionDatanull118 private fun AppFunctionData.unsafeGetSingleProperty(
119 key: String,
120 type: Int,
121 objectQualifiedName: String? = null,
122 ): Any? {
123 return when (type) {
124 TYPE_INT -> {
125 getIntOrNull(key)
126 }
127 TYPE_LONG -> {
128 getLongOrNull(key)
129 }
130 TYPE_FLOAT -> {
131 getFloatOrNull(key)
132 }
133 TYPE_DOUBLE -> {
134 getDoubleOrNull(key)
135 }
136 TYPE_BOOLEAN -> {
137 getBooleanOrNull(key)
138 }
139 TYPE_BYTES -> {
140 throw IllegalStateException("Type of a single byte is not supported")
141 }
142 TYPE_STRING -> {
143 getString(key)
144 }
145 TYPE_PENDING_INTENT -> {
146 getPendingIntent(key)
147 }
148 TYPE_OBJECT -> {
149 getAppFunctionData(key)?.deserialize(checkNotNull(objectQualifiedName))
150 }
151 else -> throw IllegalStateException("Unknown data type $type")
152 }
153 }
154
155 @RequiresApi(Build.VERSION_CODES.TIRAMISU)
unsafeGetCollectionPropertynull156 private fun AppFunctionData.unsafeGetCollectionProperty(
157 key: String,
158 type: Int,
159 objectQualifiedName: String? = null,
160 ): Any? {
161 return when (type) {
162 TYPE_INT -> {
163 getIntArray(key)
164 }
165 TYPE_LONG -> {
166 getLongArray(key)
167 }
168 TYPE_FLOAT -> {
169 getFloatArray(key)
170 }
171 TYPE_DOUBLE -> {
172 getDoubleArray(key)
173 }
174 TYPE_BOOLEAN -> {
175 getBooleanArray(key)
176 }
177 TYPE_BYTES -> {
178 getByteArray(key)
179 }
180 TYPE_STRING -> {
181 getStringList(key)
182 }
183 TYPE_PENDING_INTENT -> {
184 getPendingIntentList(key)
185 }
186 TYPE_OBJECT -> {
187 getAppFunctionDataList(key)?.map {
188 it.deserialize<Any>(checkNotNull(objectQualifiedName))
189 }
190 }
191 else -> throw IllegalStateException("Unknown data type $type")
192 }
193 }
194