1 /* <lambda>null2 * 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 18 19 import android.app.PendingIntent 20 import android.app.appsearch.GenericDocument 21 import android.net.Uri 22 import android.os.Build 23 import android.os.Bundle 24 import android.util.Log 25 import androidx.annotation.RequiresApi 26 import androidx.annotation.RestrictTo 27 import androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP 28 import androidx.annotation.VisibleForTesting 29 import androidx.appfunctions.internal.AppFunctionSerializableFactory 30 import androidx.appfunctions.internal.Constants.APP_FUNCTIONS_TAG 31 import androidx.appfunctions.metadata.AppFunctionComponentsMetadata 32 import androidx.appfunctions.metadata.AppFunctionObjectTypeMetadata 33 import androidx.appfunctions.metadata.AppFunctionParameterMetadata 34 import java.time.LocalDateTime 35 36 /** 37 * A data class to contain information to be communicated between AppFunctions apps and agents. 38 * 39 * This class can be logically viewed as a mapping from [String] keys to properties of various 40 * supported types, or arrays of supported types. 41 * 42 * When trying to retrieve an associated value, [AppFunctionData] would validate the request against 43 * the predefined metadata specification provided from [Builder]. 44 * 45 * For example, 46 * ``` 47 * fun callCreateNoteFunction( 48 * metadata: AppFunctionMetadata, 49 * request: ExecuteAppFunctionRequest, 50 * ) { 51 * val response = appFunctionManager.executeAppFunction(request) 52 * 53 * if (metadata.response.valueType is AppFunctionObjectTypeMetadata) { 54 * val returnData = response.returnValue.getAppFunctionData( 55 * ExecuteAppFunctionResponse.Success.PROPERTY_RETURN_VALUE 56 * ) 57 * val title = returnData.getString("title") 58 * // Throws an error if "owner" doesn't exist according to the metadata 59 * val owner = returnData.getString("owner") 60 * // Throws an error if "content" is String. 61 * val content = returnData.getInt("content") 62 * } 63 * } 64 * ``` 65 */ 66 @RequiresApi(Build.VERSION_CODES.TIRAMISU) 67 public class AppFunctionData 68 internal constructor( 69 // TODO: Make it non-null once the constructor that takes qualifiedName has removed 70 internal val spec: AppFunctionDataSpec?, 71 @get:VisibleForTesting 72 @get:RestrictTo(LIBRARY_GROUP) 73 public val genericDocument: GenericDocument, 74 internal val extras: Bundle 75 ) { 76 77 // TODO: Remove this constructor 78 @RestrictTo(LIBRARY_GROUP) 79 public constructor( 80 genericDocument: GenericDocument, 81 extras: Bundle, 82 ) : this(null, genericDocument, extras) 83 84 /** Qualified name of the underlying object */ 85 public val qualifiedName: String 86 get() = genericDocument.schemaType 87 88 /** 89 * Returns the ID of the underlying [GenericDocument]. Only use this for handling legacy schema. 90 */ 91 @get:RestrictTo(LIBRARY_GROUP) public val id: String = genericDocument.id 92 93 /** 94 * Checks if [AppFunctionData] has an associated value with the specified [key]. 95 * 96 * @param key The key to checks for. 97 * @return True if there is an associated value. Otherwise, false. 98 * @throws IllegalArgumentException If there is no metadata related to [key]. 99 */ 100 public fun containsKey(key: String): Boolean { 101 if (spec != null && !spec.containsMetadata(key)) { 102 throw IllegalArgumentException("There is no metadata associated with $key") 103 } 104 return genericDocument.getProperty(key) != null || extras.containsKey(key) 105 } 106 107 /** 108 * Retrieves a [Boolean] value associated with the specified [key]. 109 * 110 * @param key The key to retrieve the value for. 111 * @return The value associated with the [key]. It would return a default value false if the 112 * associated value is not found. 113 * @throws IllegalArgumentException if the [key] is not allowed or the value type is incorrect 114 * according to the metadata specification. 115 */ 116 public fun getBoolean(key: String): Boolean { 117 return getBoolean(key, DEFAULT_BOOLEAN) 118 } 119 120 /** 121 * Retrieves a [Boolean] value associated with the specified [key], or returns [defaultValue] if 122 * the associated value is not found. 123 * 124 * @param key The key to retrieve the value for. 125 * @param defaultValue The default value if the associated value is not found. 126 * @return The value associated with the [key], or the [defaultValue] if the associated value is 127 * not required and it is not found. 128 * @throws IllegalArgumentException if the [key] is not allowed or the value type is incorrect 129 * according to the metadata specification. 130 */ 131 public fun getBoolean(key: String, defaultValue: Boolean): Boolean { 132 return getBooleanOrNull(key) ?: defaultValue 133 } 134 135 /** 136 * Retrieves a [Boolean] value associated with the specified [key]. 137 * 138 * @param key The key to retrieve the value for. 139 * @return The value associated with the [key], or null if the associated value is not found. 140 * @throws IllegalArgumentException if the [key] is not allowed or the value type is incorrect 141 * according to the metadata specification. 142 */ 143 @RestrictTo(LIBRARY_GROUP) 144 public fun getBooleanOrNull(key: String): Boolean? { 145 val array = unsafeGetProperty(key, BooleanArray::class.java) 146 val booleanValue = 147 if (array == null || array.isEmpty()) { 148 null 149 } else { 150 array[0] 151 } 152 spec?.validateReadRequest( 153 key, 154 Boolean::class.java, 155 isCollection = false, 156 ) 157 return booleanValue 158 } 159 160 /** 161 * Retrieves a [Float] value associated with the specified [key]. 162 * 163 * @param key The key to retrieve the value for. 164 * @return The value associated with the [key]. It would return a default value 0.0 if the 165 * associated value is not found. 166 * @throws IllegalArgumentException if the [key] is not allowed or the value type is incorrect 167 * according to the metadata specification. 168 */ 169 public fun getFloat(key: String): Float { 170 return getFloat(key, DEFAULT_FLOAT) 171 } 172 173 /** 174 * Retrieves a [Float] value associated with the specified [key], or returns [defaultValue] if 175 * the associated value is not found. 176 * 177 * @param key The key to retrieve the value for. 178 * @param defaultValue The default value if the associated value is not found. 179 * @return The value associated with the [key], or the [defaultValue] if the associated is not 180 * found. 181 * @throws IllegalArgumentException if the [key] is not allowed or the value type is incorrect 182 * according to the metadata specification. 183 */ 184 public fun getFloat(key: String, defaultValue: Float): Float { 185 return getFloatOrNull(key) ?: defaultValue 186 } 187 188 /** 189 * Retrieves a [Float] value associated with the specified [key]. 190 * 191 * @param key The key to retrieve the value for. 192 * @return The value associated with the [key], or null if the associated value is not found. 193 * @throws IllegalArgumentException if the [key] is not allowed or the value type is incorrect 194 * according to the metadata specification. 195 */ 196 @RestrictTo(LIBRARY_GROUP) 197 public fun getFloatOrNull(key: String): Float? { 198 val array = unsafeGetProperty(key, DoubleArray::class.java) 199 val doubleValue = 200 if (array == null || array.isEmpty()) { 201 null 202 } else { 203 array[0] 204 } 205 spec?.validateReadRequest( 206 key, 207 Float::class.java, 208 isCollection = false, 209 ) 210 if (doubleValue != null && !isDoubleWithinFloatRange(doubleValue)) { 211 // This should never happen because the setters forbid such value to exist in the 212 // first place. 213 throw IllegalStateException( 214 "The value associated with $key is not within the range of Float" 215 ) 216 } 217 return doubleValue?.toFloat() 218 } 219 220 /** 221 * Retrieves a [Double] value associated with the specified [key]. 222 * 223 * @param key The key to retrieve the value for. 224 * @return The value associated with the [key]. It would return a default value 0.0 if the 225 * associated value is not found. 226 * @throws IllegalArgumentException if the [key] is not allowed or the value type is incorrect 227 * according to the metadata specification. 228 */ 229 public fun getDouble(key: String): Double { 230 return getDouble(key, DEFAULT_DOUBLE) 231 } 232 233 /** 234 * Retrieves a [Double] value associated with the specified [key], or returns [defaultValue] if 235 * the associated value is not found. 236 * 237 * @param key The key to retrieve the value for. 238 * @param defaultValue The default value if the associated value is not found. 239 * @return The value associated with the [key], or the [defaultValue] if the associated value is 240 * not found. 241 * @throws IllegalArgumentException if the [key] is not allowed or the value type is incorrect 242 * according to the metadata specification. 243 */ 244 public fun getDouble(key: String, defaultValue: Double): Double { 245 return getDoubleOrNull(key) ?: defaultValue 246 } 247 248 /** 249 * Retrieves a [Double] value associated with the specified [key]. 250 * 251 * @param key The key to retrieve the value for. 252 * @return The value associated with the [key], or null if the associated value is not found. 253 * @throws IllegalArgumentException if the [key] is not allowed or the value type is incorrect 254 * according to the metadata specification. 255 */ 256 @RestrictTo(LIBRARY_GROUP) 257 public fun getDoubleOrNull(key: String): Double? { 258 val array = unsafeGetProperty(key, DoubleArray::class.java) 259 val doubleValue = 260 if (array == null || array.isEmpty()) { 261 null 262 } else { 263 array[0] 264 } 265 spec?.validateReadRequest( 266 key, 267 Double::class.java, 268 isCollection = false, 269 ) 270 return doubleValue 271 } 272 273 /** 274 * Retrieves an [Int] value associated with the specified [key]. 275 * 276 * @param key The key to retrieve the value for. 277 * @return The value associated with the [key]. It would return a default value 0L if the 278 * associated value is not found. 279 * @throws IllegalArgumentException if the [key] is not allowed or the value type is incorrect 280 * according to the metadata specification. 281 */ 282 public fun getInt(key: String): Int { 283 return getInt(key, DEFAULT_INT) 284 } 285 286 /** 287 * Retrieves an [Int] value associated with the specified [key], or returns [defaultValue] if 288 * the associated value is not found. 289 * 290 * @param key The key to retrieve the value for. 291 * @param defaultValue The default value if the associated value is not found. 292 * @return The value associated with the [key], or the [defaultValue] if the associated value is 293 * not found. 294 * @throws IllegalArgumentException if the [key] is not allowed or the value type is incorrect 295 * according to the metadata specification. 296 */ 297 public fun getInt(key: String, defaultValue: Int): Int { 298 return getIntOrNull(key) ?: defaultValue 299 } 300 301 /** 302 * Retrieves an [Int] value associated with the specified [key]. 303 * 304 * @param key The key to retrieve the value for. 305 * @return The value associated with the [key], or null if the associated value is not found. 306 * @throws IllegalArgumentException if the [key] is not allowed or the value type is incorrect 307 * according to the metadata specification. 308 */ 309 @RestrictTo(LIBRARY_GROUP) 310 public fun getIntOrNull(key: String): Int? { 311 val array = unsafeGetProperty(key, LongArray::class.java) 312 val longValue = 313 if (array == null || array.isEmpty()) { 314 null 315 } else { 316 array[0] 317 } 318 spec?.validateReadRequest( 319 key, 320 Int::class.java, 321 isCollection = false, 322 ) 323 if (longValue != null && !isLongWithinLongRange(longValue)) { 324 // This should never happen because the setters forbid such value to exist in the 325 // first place. 326 throw IllegalStateException( 327 "The value associated with $key is not within the range of Int" 328 ) 329 } 330 return longValue?.toInt() 331 } 332 333 /** 334 * Retrieves a [Long] value associated with the specified [key]. 335 * 336 * @param key The key to retrieve the value for. 337 * @return The value associated with the [key]. It would return a default value 0L if the 338 * associated value is not found. 339 * @throws IllegalArgumentException if the [key] is not allowed or the value type is incorrect 340 * according to the metadata specification. 341 */ 342 public fun getLong(key: String): Long { 343 return getLong(key, DEFAULT_LONG) 344 } 345 346 /** 347 * Retrieves a [Long] value associated with the specified [key], or returns [defaultValue] if 348 * the associated value is not found. 349 * 350 * @param key The key to retrieve the value for. 351 * @param defaultValue The default value if the associated value is not found. 352 * @return The value associated with the [key], or the [defaultValue] if the associated value is 353 * not found. 354 * @throws IllegalArgumentException if the [key] is not allowed or the value type is incorrect 355 * according to the metadata specification. 356 */ 357 public fun getLong(key: String, defaultValue: Long): Long { 358 return getLongOrNull(key) ?: defaultValue 359 } 360 361 /** 362 * Retrieves a [Long] value associated with the specified [key]. 363 * 364 * @param key The key to retrieve the value for. 365 * @return The value associated with the [key], or null if the associated value is not found. 366 * @throws IllegalArgumentException if the [key] is not allowed or the value type is incorrect 367 * according to the metadata specification. 368 */ 369 @RestrictTo(LIBRARY_GROUP) 370 public fun getLongOrNull(key: String): Long? { 371 val array = unsafeGetProperty(key, LongArray::class.java) 372 val longValue = 373 if (array == null || array.isEmpty()) { 374 null 375 } else { 376 array[0] 377 } 378 spec?.validateReadRequest( 379 key, 380 Long::class.java, 381 isCollection = false, 382 ) 383 return longValue 384 } 385 386 /** 387 * Retrieves a [String] value associated with the specified [key]. 388 * 389 * @param key The key to retrieve the value for. 390 * @return The value associated with the [key], or null if the associated value is not found. 391 * @throws IllegalArgumentException if the [key] is not allowed or the value type is incorrect 392 * according to the metadata specification. 393 */ 394 public fun getString(key: String): String? { 395 return getStringOrNull(key) 396 } 397 398 /** 399 * Retrieves a [String] value associated with the specified [key], or returns null if the 400 * associated value is not found. 401 * 402 * This method is used internally by the [AppFunctionSerializableFactory] to retrieve the 403 * underlying string value. 404 * 405 * @param key The key to retrieve the value for. 406 * @return The value associated with the [key], or null if the associated value is not found. 407 * @throws IllegalArgumentException if the [key] is not allowed or the value type is incorrect 408 * according to the metadata specification. 409 */ 410 @RestrictTo(LIBRARY_GROUP) 411 public fun getStringOrNull(key: String): String? { 412 val array = unsafeGetProperty(key, Array<String>::class.java) 413 val stringValue = 414 if (array == null || array.isEmpty()) { 415 null 416 } else { 417 array[0] 418 } 419 spec?.validateReadRequest( 420 key, 421 String::class.java, 422 isCollection = false, 423 ) 424 return stringValue 425 } 426 427 /** 428 * Retrieves an [AppFunctionData] value associated with the specified [key]. 429 * 430 * @param key The key to retrieve the value for. 431 * @return The value associated with the [key], or null if the associated value is not found. 432 * @throws IllegalArgumentException if the [key] is not allowed or the value type is incorrect 433 * according to the metadata specification. 434 */ 435 public fun getAppFunctionData( 436 key: String, 437 ): AppFunctionData? { 438 val array = unsafeGetProperty(key, Array<GenericDocument>::class.java) 439 val dataValue = 440 if (array == null || array.isEmpty()) { 441 null 442 } else { 443 AppFunctionData( 444 spec?.getPropertyObjectSpec(key), 445 array[0], 446 extras.getBundle(extrasKey(key)) ?: Bundle.EMPTY 447 ) 448 } 449 spec?.validateReadRequest( 450 key, 451 AppFunctionData::class.java, 452 isCollection = false, 453 ) 454 return dataValue 455 } 456 457 /** 458 * Retrieves a [PendingIntent] value associated with the specified [key]. 459 * 460 * @param key The key to retrieve the value for. 461 * @return The value associated with the [key], or null if the associated value is not found. 462 * @throws IllegalArgumentException if the [key] is not allowed or the value type is incorrect 463 * according to the metadata specification. 464 */ 465 public fun getPendingIntent(key: String): PendingIntent? { 466 return getPendingIntentOrNull(key) 467 } 468 469 /** 470 * Retrieves a [PendingIntent] value associated with the specified [key], or returns null if the 471 * associated value is not found. 472 * 473 * This method is used internally by the [AppFunctionSerializableFactory] to retrieve the 474 * underlying PendingIntent value. 475 * 476 * @param key The key to retrieve the value for. 477 * @return The value associated with the [key], or null if the associated value is not found. 478 * @throws IllegalArgumentException if the [key] is not allowed or the value type is incorrect 479 * according to the metadata specification. 480 */ 481 @RestrictTo(LIBRARY_GROUP) 482 public fun getPendingIntentOrNull(key: String): PendingIntent? { 483 spec?.validateReadRequest(key, PendingIntent::class.java, isCollection = false) 484 return extras.getParcelable(key, PendingIntent::class.java) 485 } 486 487 /** 488 * Retrieves a [BooleanArray] value associated with the specified [key]. 489 * 490 * @param key The key to retrieve the value for. 491 * @return The value associated with the [key]. Or null if the associated value is not found. 492 * @throws IllegalArgumentException if the [key] is not allowed or the value type is incorrect 493 * according to the metadata specification. 494 */ 495 public fun getBooleanArray(key: String): BooleanArray? { 496 val booleanArrayValue = unsafeGetProperty(key, BooleanArray::class.java) 497 spec?.validateReadRequest( 498 key, 499 Boolean::class.java, 500 isCollection = true, 501 ) 502 return booleanArrayValue 503 } 504 505 /** 506 * Retrieves a [FloatArray] value associated with the specified [key]. 507 * 508 * @param key The key to retrieve the value for. 509 * @return The value associated with the [key]. Or null if the associated value is not found. 510 * @throws IllegalArgumentException if the [key] is not allowed or the value type is incorrect 511 * according to the metadata specification. 512 */ 513 public fun getFloatArray(key: String): FloatArray? { 514 val doubleArrayValue = unsafeGetProperty(key, DoubleArray::class.java) 515 spec?.validateReadRequest( 516 key, 517 Float::class.java, 518 isCollection = true, 519 ) 520 return doubleArrayValue 521 ?.map { doubleValue -> 522 if (!isDoubleWithinFloatRange(doubleValue)) { 523 // This should never happen because the setters forbid such value to exist in 524 // the first place. 525 throw IllegalStateException( 526 "One of the value associated with $key is not within the range of Float" 527 ) 528 } 529 doubleValue.toFloat() 530 } 531 ?.toFloatArray() 532 } 533 534 /** 535 * Retrieves a [DoubleArray] value associated with the specified [key]. 536 * 537 * @param key The key to retrieve the value for. 538 * @return The value associated with the [key]. Or null if the associated value is not found. 539 * @throws IllegalArgumentException if the [key] is not allowed or the value type is incorrect 540 * according to the metadata specification. 541 */ 542 public fun getDoubleArray(key: String): DoubleArray? { 543 val doubleArrayValue = unsafeGetProperty(key, DoubleArray::class.java) 544 spec?.validateReadRequest( 545 key, 546 Double::class.java, 547 isCollection = true, 548 ) 549 return doubleArrayValue 550 } 551 552 /** 553 * Retrieves an [IntArray] value associated with the specified [key]. 554 * 555 * @param key The key to retrieve the value for. 556 * @return The value associated with the [key]. Or null if the associated value is not found. 557 * @throws IllegalArgumentException if the [key] is not allowed or the value type is incorrect 558 * according to the metadata specification. 559 */ 560 public fun getIntArray(key: String): IntArray? { 561 val longArrayValue = unsafeGetProperty(key, LongArray::class.java) 562 spec?.validateReadRequest( 563 key, 564 Int::class.java, 565 isCollection = true, 566 ) 567 return longArrayValue 568 ?.map { longValue -> 569 if (!isLongWithinLongRange(longValue)) { 570 // This should never happen because the setters forbid such value to exist in 571 // the first place. 572 throw IllegalStateException( 573 "One of the value associated with $key is not within the range of Int" 574 ) 575 } 576 longValue.toInt() 577 } 578 ?.toIntArray() 579 } 580 581 /** 582 * Retrieves a [LongArray] value associated with the specified [key]. 583 * 584 * @param key The key to retrieve the value for. 585 * @return The value associated with the [key]. Or null if the associated value is not found. 586 * @throws IllegalArgumentException if the [key] is not allowed or the value type is incorrect 587 * according to the metadata specification. 588 */ 589 public fun getLongArray(key: String): LongArray? { 590 val longArrayValue = unsafeGetProperty(key, LongArray::class.java) 591 spec?.validateReadRequest( 592 key, 593 Long::class.java, 594 isCollection = true, 595 ) 596 return longArrayValue 597 } 598 599 /** 600 * Retrieves a [ByteArray] value associated with the specified [key]. 601 * 602 * @param key The key to retrieve the value for. 603 * @return The value associated with the [key]. Or null if the associated value is not found. 604 * @throws IllegalArgumentException if the [key] is not allowed or the value type is incorrect 605 * according to the metadata specification. 606 */ 607 public fun getByteArray(key: String): ByteArray? { 608 val byteArrayValue = unsafeGetProperty(key, Array<ByteArray>::class.java) 609 spec?.validateReadRequest( 610 key, 611 Byte::class.java, 612 isCollection = true, 613 ) 614 return if (byteArrayValue == null || byteArrayValue.isEmpty()) { 615 null 616 } else { 617 byteArrayValue[0] 618 } 619 } 620 621 /** 622 * Retrieves a [List] of [String] value associated with the specified [key]. 623 * 624 * @param key The key to retrieve the value for. 625 * @return The value associated with the [key]. Or null if the associated value is not found. 626 * @throws IllegalArgumentException if the [key] is not allowed or the value type is incorrect 627 * according to the metadata specification. 628 */ 629 @Suppress("NullableCollection") 630 public fun getStringList(key: String): List<String>? { 631 val stringArrayValue = unsafeGetProperty(key, Array<String>::class.java) 632 spec?.validateReadRequest( 633 key, 634 String::class.java, 635 isCollection = true, 636 ) 637 return stringArrayValue?.asList() 638 } 639 640 /** 641 * Retrieves a [List] of [AppFunctionData] value associated with the specified [key]. 642 * 643 * @param key The key to retrieve the value for. 644 * @return The value associated with the [key]. Or null if the associated value is not found. 645 * @throws IllegalArgumentException if the [key] is not allowed or the value type is incorrect 646 * according to the metadata specification. 647 */ 648 @Suppress("NullableCollection") 649 public fun getAppFunctionDataList( 650 key: String, 651 ): List<AppFunctionData>? { 652 val propertySpec = spec?.getPropertyObjectSpec(key) 653 val dataArrayValue = 654 unsafeGetProperty(key, Array<GenericDocument>::class.java)?.mapIndexed { index, element 655 -> 656 AppFunctionData( 657 propertySpec, 658 element, 659 extras.getBundle(extrasKey(key, index)) ?: Bundle.EMPTY 660 ) 661 } 662 spec?.validateReadRequest( 663 key, 664 AppFunctionData::class.java, 665 isCollection = true, 666 ) 667 return dataArrayValue 668 } 669 670 /** 671 * Retrieves a [List] of [PendingIntent] value associated with the specified [key]. 672 * 673 * @param key The key to retrieve the value for. 674 * @return The value associated with the [key]. Or null if the associated value is not found. 675 * @throws IllegalArgumentException if the [key] is not allowed or the value type is incorrect 676 * according to the metadata specification. 677 */ 678 @Suppress("NullableCollection") 679 public fun getPendingIntentList(key: String): List<PendingIntent>? { 680 spec?.validateReadRequest(key, PendingIntent::class.java, isCollection = true) 681 return extras.getParcelableArrayList(key, PendingIntent::class.java) 682 } 683 684 /** 685 * Retrieves a generic value of type [T] associated with the specified [key]. 686 * 687 * @param T The type of the associated value. 688 * @param key The key to retrieve the value for. 689 * @param valueClass The class of the type [T]. 690 * @return The value associated with the [key]. Or null if the associated value is not found. 691 * @throws IllegalArgumentException if the [key] is not allowed or the value type is incorrect 692 * according to the metadata specification. 693 */ 694 @RestrictTo(LIBRARY_GROUP) 695 public fun <T> getGenericField(key: String, valueClass: Class<T>): T { 696 @Suppress("UNCHECKED_CAST") 697 return when (valueClass) { 698 Int::class.java -> getIntOrNull(key) as T 699 Long::class.java -> getLongOrNull(key) as T 700 Float::class.java -> getFloatOrNull(key) as T 701 Double::class.java -> getDoubleOrNull(key) as T 702 Boolean::class.java -> getBooleanOrNull(key) as T 703 String::class.java -> getString(key) as T 704 PendingIntent::class.java -> getPendingIntent(key) as T 705 IntArray::class.java -> getIntArray(key) as T 706 LongArray::class.java -> getLongArray(key) as T 707 FloatArray::class.java -> getFloatArray(key) as T 708 DoubleArray::class.java -> getDoubleArray(key) as T 709 BooleanArray::class.java -> getBooleanArray(key) as T 710 ByteArray::class.java -> getByteArray(key) as T 711 // TODO(b/408385427): Handle deserialization in generic factory 712 else -> getAppFunctionData(key)?.deserialize(valueClass as Class<Any>) as T 713 } 714 } 715 716 /** 717 * Retrieves a [List] of generic value of type [T] associated with the specified [key]. 718 * 719 * @param I The [T]'s item type. 720 * @param T The type of the associated value. 721 * @param key The key to retrieve the value for. 722 * @param itemValueClass The class of the type [T]. 723 * @return The list value associated with the [key]. Or null if the associated value is not 724 * found. 725 * @throws IllegalArgumentException if the [key] is not allowed or the value type is incorrect 726 * according to the metadata specification. 727 */ 728 @RestrictTo(LIBRARY_GROUP) 729 public fun <I, T : List<I>?> getGenericListField(key: String, itemValueClass: Class<I>): T { 730 @Suppress("UNCHECKED_CAST") 731 return when (itemValueClass) { 732 String::class.java -> getStringList(key) as T 733 PendingIntent::class.java -> getPendingIntentList(key) as T 734 // TODO(b/408385427): Handle deserialization in generic factory 735 else -> 736 getAppFunctionDataList(key)?.map { it.deserialize(itemValueClass as Class<Any>) } 737 as T 738 } 739 } 740 741 override fun toString(): String { 742 // TODO(b/391419368): Improve output to avoid reference to underlying GenericDocument 743 return "AppFunctionData(genericDocument=$genericDocument, extras=$extras)" 744 } 745 746 private fun isDoubleWithinFloatRange(doubleValue: Double): Boolean { 747 if (doubleValue.isInfinite() || doubleValue.isNaN()) { 748 // Float also has NaN and Infinity representation 749 return true 750 } 751 if (doubleValue > Float.MAX_VALUE || doubleValue < -Float.MAX_VALUE) { 752 // The double value is not within the range of a Float. 753 return false 754 } 755 return true 756 } 757 758 private fun isLongWithinLongRange(longValue: Long): Boolean { 759 return longValue >= Int.MIN_VALUE && longValue <= Int.MAX_VALUE 760 } 761 762 /** 763 * Deserializes the [AppFunctionData] to an [AppFunctionSerializable] instance. 764 * 765 * @param serializableClass The AppFunctionSerializable class. 766 * @return The instance of [serializableClass]. 767 * @throws IllegalArgumentException If unable to deserialize the [AppFunctionData] to an 768 * instance of [serializableClass]. 769 * @see [AppFunctionSerializable] 770 */ 771 public fun <T : Any> deserialize(serializableClass: Class<T>): T { 772 return try { 773 val factory = getSerializableFactory(serializableClass) 774 factory.fromAppFunctionData(this) 775 } catch (e: Exception) { 776 Log.d( 777 APP_FUNCTIONS_TAG, 778 "Something went wrong while deserialize $this to $serializableClass", 779 e 780 ) 781 throw IllegalArgumentException( 782 "Unable to deserialize $serializableClass. Is the class annotated with @AppFunctionSerializable?" 783 ) 784 } 785 } 786 787 /** 788 * Deserializes the [AppFunctionData] to an [AppFunctionSerializable] instance identified by 789 * [qualifiedName]. 790 * 791 * @param qualifiedName The qualifiedName of the AppFunctionSerializable class. 792 * @return The instance of the class identified by [qualifiedName]. 793 * @throws IllegalArgumentException If unable to deserialize the [AppFunctionData] to an 794 * instance of the class identified by [qualifiedName]. 795 * @see [AppFunctionSerializable] 796 */ 797 @RestrictTo(LIBRARY_GROUP) 798 public fun <T : Any> deserialize(qualifiedName: String): T { 799 return deserialize<T>(getSerializableClass(qualifiedName)) 800 } 801 802 private fun <T : Any> unsafeGetProperty(key: String, arrayClass: Class<T>): T? { 803 return try { 804 val value = genericDocument.getProperty(key) 805 if (value != null) { 806 arrayClass.cast(value) 807 } else { 808 null 809 } 810 } catch (e: ClassCastException) { 811 throw IllegalArgumentException( 812 "Found the property under [$key] but data type does not match with the request.", 813 e, 814 ) 815 } 816 } 817 818 /** 819 * Builder for constructing [AppFunctionData] 820 * 821 * For example, to write an [AppFunctionData] for calling an AppFunction 822 * 823 * ``` 824 * fun callCreateNoteFunction(metadata: AppFunctionMetadata) { 825 * val appFunctionData = AppFunctionData.Builder( 826 * metadata.parameters, 827 * metadata.components, 828 * ).apply { 829 * setString("title", "Note Title") 830 * // If the function doesn't accept "owner" as parameter, it would throw an error 831 * setString("owner", "Me") 832 * // If the function actually expects "content" as String, it would throw an error 833 * setInt("content", 100) 834 * } 835 * .build() 836 * } 837 * ``` 838 */ 839 public class Builder { 840 841 // TODO(b/399823985): Remove this once the constructor that takes qualifiedName has removed 842 private val qualifiedName: String 843 // TODO(b/399823985): Make it non-null once the constructor that takes qualifiedName has 844 // removed 845 private val spec: AppFunctionDataSpec? 846 private var genericDocumentBuilder: GenericDocument.Builder<*> 847 private val extrasBuilder = Bundle() 848 849 // TODO(b/399823985): Clean up the usage without providing metadata. 850 /** 851 * @param id: Only set this when creating a document for the legacy schema. In the legacy 852 * schema, ID is stored as [GenericDocument.id]. In Jetpack, ID is just a normal property. 853 */ 854 @RestrictTo(LIBRARY_GROUP) 855 public constructor(qualifiedName: String, id: String = "") { 856 this.qualifiedName = qualifiedName 857 spec = null 858 genericDocumentBuilder = 859 GenericDocument.Builder<GenericDocument.Builder<*>>("", id, qualifiedName) 860 } 861 862 /** 863 * Constructs a [Builder] to create input data for an AppFunction execution call. 864 * 865 * This constructor is used when you need to write data that will be passed as input when 866 * executing an AppFunction. The [parameterMetadataList] defines the expected input 867 * parameters for that function. 868 * 869 * @param parameterMetadataList List of [AppFunctionParameterMetadata] defining the input 870 * parameters. 871 * @param componentMetadata [AppFunctionComponentsMetadata] that has the shared data type. 872 */ 873 public constructor( 874 parameterMetadataList: List<AppFunctionParameterMetadata>, 875 componentMetadata: AppFunctionComponentsMetadata, 876 ) : this(AppFunctionDataSpec.create(parameterMetadataList, componentMetadata)) 877 878 /** 879 * Constructs a [Builder] to create [AppFunctionData] representing an object. 880 * 881 * This constructor is used when you need to create [AppFunctionData] that represents an 882 * object used as either function parameters or return values, as defined by an 883 * [AppFunctionObjectTypeMetadata]. This metadata specifies the properties and their types 884 * for the object. 885 * 886 * @param objectTypeMetadata [AppFunctionObjectTypeMetadata] defining the object structure. 887 * @param componentMetadata [AppFunctionComponentsMetadata] that has the shared data type. 888 */ 889 public constructor( 890 objectTypeMetadata: AppFunctionObjectTypeMetadata, 891 componentMetadata: AppFunctionComponentsMetadata, 892 ) : this(AppFunctionDataSpec.create(objectTypeMetadata, componentMetadata)) 893 894 private constructor(spec: AppFunctionDataSpec) { 895 this.spec = spec 896 this.qualifiedName = spec.objectQualifiedName 897 genericDocumentBuilder = 898 GenericDocument.Builder<GenericDocument.Builder<*>>( 899 "", 900 "", 901 spec.objectQualifiedName 902 ) 903 } 904 905 /** 906 * Sets a [Boolean] value for the given [key]. 907 * 908 * @param key The key to set the [Boolean] value for. 909 * @param value The [Boolean] value to set. 910 * @throws IllegalArgumentException if the [key] is not allowed or the [value] does not 911 * match the metadata specification associated with the [key]. 912 */ 913 public fun setBoolean(key: String, value: Boolean): Builder { 914 spec?.validateWriteRequest(key, Boolean::class.java, isCollection = false) 915 genericDocumentBuilder.setPropertyBoolean(key, value) 916 return this 917 } 918 919 /** 920 * Sets a [Float] value for the given [key]. 921 * 922 * @param key The key to set the [Float] value for. 923 * @param value The [Float] value to set. 924 * @throws IllegalArgumentException if the [key] is not allowed or the [value] does not 925 * match the metadata specification associated with the [key]. 926 */ 927 public fun setFloat(key: String, value: Float): Builder { 928 spec?.validateWriteRequest(key, Float::class.java, isCollection = false) 929 genericDocumentBuilder.setPropertyDouble(key, value.toDouble()) 930 return this 931 } 932 933 /** 934 * Sets a [Double] value for the given [key]. 935 * 936 * @param key The key to set the [Double] value for. 937 * @param value The [Double] value to set. 938 * @throws IllegalArgumentException if the [key] is not allowed or the [value] does not 939 * match the metadata specification associated with the [key]. 940 */ 941 public fun setDouble(key: String, value: Double): Builder { 942 spec?.validateWriteRequest(key, Double::class.java, isCollection = false) 943 genericDocumentBuilder.setPropertyDouble(key, value) 944 return this 945 } 946 947 /** 948 * Sets an [Int] value for the given [key]. 949 * 950 * @param key The key to set the [Int] value for. 951 * @param value The [Int] value to set. 952 * @throws IllegalArgumentException if the [key] is not allowed or the [value] does not 953 * match the metadata specification associated with the [key]. 954 */ 955 public fun setInt(key: String, value: Int): Builder { 956 spec?.validateWriteRequest(key, Int::class.java, isCollection = false) 957 genericDocumentBuilder.setPropertyLong(key, value.toLong()) 958 return this 959 } 960 961 /** 962 * Sets a [Long] value for the given [key]. 963 * 964 * @param key The key to set the [Long] value for. 965 * @param value The [Long] value to set. 966 * @throws IllegalArgumentException if the [key] is not allowed or the [value] does not 967 * match the metadata specification associated with the [key]. 968 */ 969 public fun setLong(key: String, value: Long): Builder { 970 spec?.validateWriteRequest(key, Long::class.java, isCollection = false) 971 genericDocumentBuilder.setPropertyLong(key, value) 972 return this 973 } 974 975 /** 976 * Sets a [String] value for the given [key]. 977 * 978 * @param key The key to set the [String] value for. 979 * @param value The [String] value to set. 980 * @throws IllegalArgumentException if the [key] is not allowed or the [value] does not 981 * match the metadata specification associated with the [key]. 982 */ 983 public fun setString(key: String, value: String): Builder { 984 spec?.validateWriteRequest(key, String::class.java, isCollection = false) 985 genericDocumentBuilder.setPropertyString(key, value) 986 return this 987 } 988 989 /** 990 * Sets an [AppFunctionData] value for the given [key]. 991 * 992 * @param key The key to set the [AppFunctionData] value for. 993 * @param value The [AppFunctionData] value to set. 994 * @throws IllegalArgumentException if the [key] is not allowed or the [value] does not 995 * match the metadata specification associated with the [key]. 996 */ 997 public fun setAppFunctionData(key: String, value: AppFunctionData): Builder { 998 spec?.validateWriteRequest(key, AppFunctionData::class.java, isCollection = false) 999 spec?.getPropertyObjectSpec(key)?.validateDataSpecMatches(value) 1000 1001 genericDocumentBuilder.setPropertyDocument(key, value.genericDocument) 1002 if (!value.extras.isEmpty()) { 1003 extrasBuilder.putBundle(extrasKey(key), value.extras) 1004 } 1005 return this 1006 } 1007 1008 /** 1009 * Sets a [PendingIntent] value for the given [key]. 1010 * 1011 * @param key The key to set the [AppFunctionData] value for. 1012 * @param value The [AppFunctionData] value to set. 1013 * @throws IllegalArgumentException if the [key] is not allowed or the [value] does not 1014 * match the metadata specification associated with the [key]. 1015 */ 1016 public fun setPendingIntent(key: String, value: PendingIntent): Builder { 1017 spec?.validateWriteRequest(key, PendingIntent::class.java, isCollection = false) 1018 extrasBuilder.putParcelable(key, value) 1019 return this 1020 } 1021 1022 /** 1023 * Sets a [BooleanArray] value for the given [key]. 1024 * 1025 * @param key The key to set the [BooleanArray] value for. 1026 * @param value The [BooleanArray] value to set. 1027 * @throws IllegalArgumentException if the [key] is not allowed or the [value] does not 1028 * match the metadata specification associated with the [key]. 1029 */ 1030 public fun setBooleanArray(key: String, value: BooleanArray): Builder { 1031 spec?.validateWriteRequest(key, Boolean::class.java, isCollection = true) 1032 genericDocumentBuilder.setPropertyBoolean(key, *value) 1033 return this 1034 } 1035 1036 /** 1037 * Sets a [FloatArray] value for the given [key]. 1038 * 1039 * @param key The key to set the [DoubleArray] value for. 1040 * @param value The [FloatArray] value to set. 1041 * @throws IllegalArgumentException if the [key] is not allowed or the [value] does not 1042 * match the metadata specification associated with the [key]. 1043 */ 1044 public fun setFloatArray(key: String, value: FloatArray): Builder { 1045 spec?.validateWriteRequest(key, Float::class.java, isCollection = true) 1046 genericDocumentBuilder.setPropertyDouble( 1047 key, 1048 *(value.asList().map { it.toDouble() }.toDoubleArray()) 1049 ) 1050 return this 1051 } 1052 1053 /** 1054 * Sets a [DoubleArray] value for the given [key]. 1055 * 1056 * @param key The key to set the [DoubleArray] value for. 1057 * @param value The [DoubleArray] value to set. 1058 * @throws IllegalArgumentException if the [key] is not allowed or the [value] does not 1059 * match the metadata specification associated with the [key]. 1060 */ 1061 public fun setDoubleArray(key: String, value: DoubleArray): Builder { 1062 spec?.validateWriteRequest(key, Double::class.java, isCollection = true) 1063 genericDocumentBuilder.setPropertyDouble(key, *value) 1064 return this 1065 } 1066 1067 /** 1068 * Sets an [IntArray] value for the given [key]. 1069 * 1070 * @param key The key to set the [IntArray] value for. 1071 * @param value The [IntArray] value to set. 1072 * @throws IllegalArgumentException if the [key] is not allowed or the [value] does not 1073 * match the metadata specification associated with the [key]. 1074 */ 1075 public fun setIntArray(key: String, value: IntArray): Builder { 1076 spec?.validateWriteRequest(key, Int::class.java, isCollection = true) 1077 genericDocumentBuilder.setPropertyLong( 1078 key, 1079 *(value.asList().map { it.toLong() }.toLongArray()) 1080 ) 1081 return this 1082 } 1083 1084 /** 1085 * Sets a [LongArray] value for the given [key]. 1086 * 1087 * @param key The key to set the [LongArray] value for. 1088 * @param value The [LongArray] value to set. 1089 * @throws IllegalArgumentException if the [key] is not allowed or the [value] does not 1090 * match the metadata specification associated with the [key]. 1091 */ 1092 public fun setLongArray(key: String, value: LongArray): Builder { 1093 spec?.validateWriteRequest(key, Long::class.java, isCollection = true) 1094 genericDocumentBuilder.setPropertyLong(key, *value) 1095 return this 1096 } 1097 1098 /** 1099 * Sets a [ByteArray] value for the given [key]. 1100 * 1101 * @param key The key to set the [ByteArray] value for. 1102 * @param value The [ByteArray] value to set. 1103 * @throws IllegalArgumentException if the [key] is not allowed or the [value] does not 1104 * match the metadata specification associated with the [key]. 1105 */ 1106 public fun setByteArray(key: String, value: ByteArray): Builder { 1107 spec?.validateWriteRequest(key, Byte::class.java, isCollection = true) 1108 genericDocumentBuilder.setPropertyBytes(key, value) 1109 return this 1110 } 1111 1112 /** 1113 * Sets a [List] of [String] value for the given [key]. 1114 * 1115 * @param key The key to set the [List] of [String] value for. 1116 * @param value The [List] of [String] value to set. 1117 * @throws IllegalArgumentException if the [key] is not allowed or the [value] does not 1118 * match the metadata specification associated with the [key]. 1119 */ 1120 public fun setStringList(key: String, value: List<String>): Builder { 1121 spec?.validateWriteRequest(key, String()::class.java, isCollection = true) 1122 genericDocumentBuilder.setPropertyString(key, *value.toTypedArray()) 1123 return this 1124 } 1125 1126 /** 1127 * Sets a [List] of [AppFunctionData] value for the given [key]. 1128 * 1129 * @param key The key to set the [List] of [AppFunctionData] value for. 1130 * @param value The [List] of [AppFunctionData] value to set. 1131 * @throws IllegalArgumentException if the [key] is not allowed or the [value] does not 1132 * match the metadata specification associated with the [key]. 1133 */ 1134 public fun setAppFunctionDataList(key: String, value: List<AppFunctionData>): Builder { 1135 spec?.validateWriteRequest(key, AppFunctionData::class.java, isCollection = true) 1136 genericDocumentBuilder.setPropertyDocument( 1137 key, 1138 *value.map { it.genericDocument }.toTypedArray(), 1139 ) 1140 value.forEachIndexed { index, element -> 1141 spec?.getPropertyObjectSpec(key)?.validateDataSpecMatches(element) 1142 if (!element.extras.isEmpty()) { 1143 extrasBuilder.putBundle(extrasKey(key, index), element.extras) 1144 } 1145 } 1146 return this 1147 } 1148 1149 /** 1150 * Sets a [List] of [PendingIntent] value for the given [key]. 1151 * 1152 * @param key The key to set the [List] of [AppFunctionData] value for. 1153 * @param value The [List] of [AppFunctionData] value to set. 1154 * @throws IllegalArgumentException if the [key] is not allowed or the [value] does not 1155 * match the metadata specification associated with the [key]. 1156 */ 1157 public fun setPendingIntentList(key: String, value: List<PendingIntent>): Builder { 1158 spec?.validateWriteRequest(key, PendingIntent::class.java, isCollection = true) 1159 extrasBuilder.putParcelableArrayList(key, ArrayList<PendingIntent>(value)) 1160 return this 1161 } 1162 1163 /** 1164 * Sets a generic type [value] of class [valueClass] for the given [key]. 1165 * 1166 * @param T The type [value]. 1167 * @param key The key to set the generic type [value] for. 1168 * @param value The generic type value to set. 1169 * @param valueClass The class of type [T]. 1170 * @throws IllegalArgumentException if the [key] is not allowed or the [value] does not 1171 * match the metadata specification associated with the [key]. 1172 */ 1173 @RestrictTo(LIBRARY_GROUP) 1174 public fun <T : Any?> setGenericField( 1175 key: String, 1176 value: T, 1177 valueClass: Class<T> 1178 ): Builder { 1179 if (value == null) { 1180 return this 1181 } 1182 @Suppress("UNCHECKED_CAST") 1183 return when (valueClass) { 1184 Int::class.java -> setInt(key, value as Int) 1185 Long::class.java -> setLong(key, value as Long) 1186 Float::class.java -> setFloat(key, value as Float) 1187 Double::class.java -> setDouble(key, value as Double) 1188 Boolean::class.java -> setBoolean(key, value as Boolean) 1189 String::class.java -> setString(key, value as String) 1190 PendingIntent::class.java -> setPendingIntent(key, value as PendingIntent) 1191 IntArray::class.java -> setIntArray(key, value as IntArray) 1192 LongArray::class.java -> setLongArray(key, value as LongArray) 1193 FloatArray::class.java -> setFloatArray(key, value as FloatArray) 1194 DoubleArray::class.java -> setDoubleArray(key, value as DoubleArray) 1195 BooleanArray::class.java -> setBooleanArray(key, value as BooleanArray) 1196 ByteArray::class.java -> setByteArray(key, value as ByteArray) 1197 // TODO(b/408385427): Handle serialization in generic factory 1198 else -> setAppFunctionData(key, serialize(value, valueClass as Class<Any>)) 1199 } 1200 } 1201 1202 /** 1203 * Sets a list generic type [value] of class [itemValueClass] for the given [key]. 1204 * 1205 * @param I The [value]'s item type. 1206 * @param T The type [value]. 1207 * @param key The key to set the generic type [value] for. 1208 * @param value The generic type value to set. 1209 * @param itemValueClass The [value]'s item class. 1210 * @throws IllegalArgumentException if the [key] is not allowed or the [value] does not 1211 * match the metadata specification associated with the [key]. 1212 */ 1213 @RestrictTo(LIBRARY_GROUP) 1214 public fun <I, T : List<*>?> setGenericListField( 1215 key: String, 1216 value: T, 1217 itemValueClass: Class<I> 1218 ): Builder { 1219 if (value == null) { 1220 return this 1221 } 1222 @Suppress("UNCHECKED_CAST") 1223 return when (itemValueClass) { 1224 String::class.java -> setStringList(key, value as List<String>) 1225 PendingIntent::class.java -> setPendingIntentList(key, value as List<PendingIntent>) 1226 // TODO(b/408385427): Handle serialization in generic factory 1227 else -> 1228 setAppFunctionDataList( 1229 key, 1230 value.map { serialize(it as Any, itemValueClass as Class<Any>) } 1231 ) 1232 } 1233 } 1234 1235 /** Builds [AppFunctionData] */ 1236 public fun build(): AppFunctionData { 1237 // TODO(b/399823985): validate required fields. 1238 return AppFunctionData(spec, genericDocumentBuilder.build(), extrasBuilder) 1239 } 1240 } 1241 1242 public companion object { 1243 private const val DEFAULT_BOOLEAN: Boolean = false 1244 private const val DEFAULT_FLOAT: Float = 0F 1245 private const val DEFAULT_DOUBLE: Double = 0.0 1246 private const val DEFAULT_INT: Int = 0 1247 private const val DEFAULT_LONG: Long = 0L 1248 1249 private fun extrasKey(key: String) = "property/$key" 1250 1251 private fun extrasKey(key: String, index: Int) = "property/$key[$index]" 1252 1253 // TODO(b/399823985): Codegen the mapping table to prevent using reflection 1254 private fun <T : Any> getSerializableClass(qualifiedName: String): Class<T> { 1255 return try { 1256 @Suppress("UNCHECKED_CAST") 1257 Class.forName(qualifiedName) as Class<T> 1258 } catch (e: Exception) { 1259 Log.d(APP_FUNCTIONS_TAG, "Unable to find serializable class $qualifiedName", e) 1260 throw IllegalArgumentException("Unable to find serializable class $qualifiedName") 1261 } 1262 } 1263 1264 // TODO(b/399823985): Codegen the mapping table to prevent using reflection 1265 private fun <T : Any> getSerializableFactory( 1266 serializableClass: Class<T> 1267 ): AppFunctionSerializableFactory<T> { 1268 val packageName = getPackageName(serializableClass) 1269 val serializableSimpleName = serializableClass.simpleName 1270 1271 val factorySimpleName = "${'$'}${serializableSimpleName}Factory" 1272 val factoryClassName = "${packageName}.${factorySimpleName}" 1273 1274 return try { 1275 val factoryClass = Class.forName(factoryClassName) 1276 @Suppress("UNCHECKED_CAST") 1277 factoryClass.getDeclaredConstructor().newInstance() 1278 as AppFunctionSerializableFactory<T> 1279 } catch (e: Exception) { 1280 Log.d( 1281 APP_FUNCTIONS_TAG, 1282 "Unable to create AppFunctionSerializableFactory for $serializableClass", 1283 e 1284 ) 1285 throw IllegalArgumentException( 1286 "Unable to create AppFunctionSerializableFactory for $serializableClass" 1287 ) 1288 } 1289 } 1290 1291 private fun getPackageName(serializableClass: Class<*>): String { 1292 val setOfProxyTypes = setOf(LocalDateTime::class.simpleName, Uri::class.simpleName) 1293 val serializableProxyPackageName = "androidx.appfunctions.internal.serializableproxies" 1294 if (setOfProxyTypes.contains(serializableClass.simpleName)) { 1295 return serializableProxyPackageName 1296 } 1297 1298 return serializableClass.packageName 1299 } 1300 1301 /** 1302 * Serializes [serializable] to an [AppFunctionData]. 1303 * 1304 * @param serializable The instance of [serializableClass]. 1305 * @param serializableClass The class of [serializable]. 1306 * @return [AppFunctionData] with properties from [serializable]. 1307 * @throws IllegalArgumentException If unable to serialize [serializable] to an 1308 * [AppFunctionData]. 1309 * @see [AppFunctionSerializable] 1310 */ 1311 @JvmStatic 1312 public fun <T : Any> serialize( 1313 serializable: T, 1314 serializableClass: Class<T> 1315 ): AppFunctionData { 1316 return try { 1317 val factory = getSerializableFactory(serializableClass) 1318 factory.toAppFunctionData(serializable) 1319 } catch (e: Exception) { 1320 Log.d( 1321 APP_FUNCTIONS_TAG, 1322 "Something went wrong while serialize $serializable of class $serializableClass", 1323 e 1324 ) 1325 throw IllegalArgumentException( 1326 "Unable to serialize $serializableClass. Is the class annotated with @AppFunctionSerializable?" 1327 ) 1328 } 1329 } 1330 1331 /** 1332 * Serializes [serializable] to an [AppFunctionData]. 1333 * 1334 * @param serializable The instance of [qualifiedName]. 1335 * @param qualifiedName The qualified name of the class [serializable]. 1336 * @return [AppFunctionData] with properties from [serializable]. 1337 * @throws IllegalArgumentException If unable to serialize [serializable] to an 1338 * [AppFunctionData]. 1339 * @see [AppFunctionSerializable] 1340 */ 1341 @RestrictTo(LIBRARY_GROUP) 1342 public fun <T : Any> serialize(serializable: T, qualifiedName: String): AppFunctionData { 1343 return serialize(serializable, getSerializableClass<T>(qualifiedName)) 1344 } 1345 1346 /** Represents an empty [AppFunctionData]. */ 1347 @JvmField 1348 public val EMPTY: AppFunctionData = 1349 AppFunctionData( 1350 GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "").build(), 1351 Bundle.EMPTY, 1352 ) 1353 } 1354 } 1355