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