1 /* 2 * Copyright 2020 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 android.app.appsearch; 18 19 import android.annotation.CurrentTimeMillisLong; 20 import android.annotation.FlaggedApi; 21 import android.annotation.IntRange; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.annotation.SuppressLint; 25 import android.annotation.SystemApi; 26 import android.app.appsearch.annotation.CanIgnoreReturnValue; 27 import android.app.appsearch.safeparcel.GenericDocumentParcel; 28 import android.app.appsearch.safeparcel.PropertyParcel; 29 import android.app.appsearch.util.IndentingStringBuilder; 30 import android.os.Build; 31 import android.os.Parcel; 32 import android.util.Log; 33 34 import com.android.appsearch.flags.Flags; 35 36 import java.lang.reflect.Array; 37 import java.util.ArrayList; 38 import java.util.Arrays; 39 import java.util.Collections; 40 import java.util.List; 41 import java.util.Map; 42 import java.util.Objects; 43 import java.util.Set; 44 45 /** 46 * Represents a document unit. 47 * 48 * <p>Documents contain structured data conforming to their {@link AppSearchSchema} type. Each 49 * document is uniquely identified by a namespace and a String ID within that namespace. 50 * 51 * <p>Documents are constructed by using the {@link GenericDocument.Builder}. 52 * 53 * @see AppSearchSession#put 54 * @see AppSearchSession#getByDocumentId 55 * @see AppSearchSession#search 56 */ 57 // TODO(b/384721898): Switch to JSpecify annotations 58 @SuppressWarnings("JSpecifyNullness") 59 public class GenericDocument { 60 private static final String TAG = "AppSearchGenericDocumen"; 61 62 /** The maximum number of indexed properties a document can have. */ 63 private static final int MAX_INDEXED_PROPERTIES = 16; 64 65 /** 66 * Fixed constant synthetic property for parent types. 67 * 68 * @hide 69 */ 70 public static final String PARENT_TYPES_SYNTHETIC_PROPERTY = "$$__AppSearch__parentTypes"; 71 72 /** 73 * An immutable empty {@link GenericDocument}. 74 * 75 * @hide 76 */ 77 public static final GenericDocument EMPTY = new GenericDocument.Builder<>("", "", "").build(); 78 79 /** 80 * The maximum number of indexed properties a document can have. 81 * 82 * <p>Indexed properties are properties which are strings where the {@link 83 * AppSearchSchema.StringPropertyConfig#getIndexingType} value is anything other than {@link 84 * AppSearchSchema.StringPropertyConfig#INDEXING_TYPE_NONE}, as well as long properties where 85 * the {@link AppSearchSchema.LongPropertyConfig#getIndexingType} value is {@link 86 * AppSearchSchema.LongPropertyConfig#INDEXING_TYPE_RANGE}. 87 */ getMaxIndexedProperties()88 public static int getMaxIndexedProperties() { 89 return MAX_INDEXED_PROPERTIES; 90 } 91 92 /** The class to hold all meta data and properties for this {@link GenericDocument}. */ 93 private final GenericDocumentParcel mDocumentParcel; 94 95 /** 96 * Rebuilds a {@link GenericDocument} from a {@link GenericDocumentParcel}. 97 * 98 * @param documentParcel Packaged {@link GenericDocument} data, such as the result of {@link 99 * #getDocumentParcel()}. 100 * @hide 101 */ 102 @SuppressWarnings("deprecation") GenericDocument(@onNull GenericDocumentParcel documentParcel)103 public GenericDocument(@NonNull GenericDocumentParcel documentParcel) { 104 mDocumentParcel = Objects.requireNonNull(documentParcel); 105 } 106 107 /** 108 * Creates a new {@link GenericDocument} from an existing instance. 109 * 110 * <p>This method should be only used by constructor of a subclass. 111 */ GenericDocument(@onNull GenericDocument document)112 protected GenericDocument(@NonNull GenericDocument document) { 113 this(document.mDocumentParcel); 114 } 115 116 /** 117 * Writes the {@link GenericDocument} to the given {@link Parcel}. 118 * 119 * @param dest The {@link Parcel} to write to. 120 * @param flags The flags to use for parceling. 121 * @hide 122 */ 123 // GenericDocument is an open class that can be extended, whereas parcelable classes must be 124 // final in those methods. Thus, we make this a system api to avoid 3p apps depending on it 125 // and getting confused by the inheritability. 126 @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) 127 @FlaggedApi(Flags.FLAG_ENABLE_GENERIC_DOCUMENT_OVER_IPC) writeToParcel(@onNull Parcel dest, int flags)128 public final void writeToParcel(@NonNull Parcel dest, int flags) { 129 Objects.requireNonNull(dest); 130 dest.writeParcelable(mDocumentParcel, flags); 131 } 132 133 /** 134 * Creates a {@link GenericDocument} from a {@link Parcel}. 135 * 136 * @param parcel The {@link Parcel} to read from. 137 * @hide 138 */ 139 // GenericDocument is an open class that can be extended, whereas parcelable classes must be 140 // final in those methods. Thus, we make this a system api to avoid 3p apps depending on it 141 // and getting confused by the inheritability. 142 @SuppressWarnings("deprecation") 143 @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) 144 @FlaggedApi(Flags.FLAG_ENABLE_GENERIC_DOCUMENT_OVER_IPC) createFromParcel(@onNull Parcel parcel)145 public static @NonNull GenericDocument createFromParcel(@NonNull Parcel parcel) { 146 Objects.requireNonNull(parcel); 147 GenericDocumentParcel documentParcel; 148 149 // Code built in Framework cannot depend on Androidx libraries. Therefore, we must call 150 // Parcel#readParcelable directly. 151 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { 152 documentParcel = 153 parcel.readParcelable( 154 GenericDocumentParcel.class.getClassLoader(), 155 GenericDocumentParcel.class); 156 } else { 157 // The Parcel#readParcelable(ClassLoader, Class) function has a known issue on Android 158 // T. This was fixed on Android U. When on Android T, call the older version of 159 // Parcel#readParcelable. 160 documentParcel = parcel.readParcelable(GenericDocumentParcel.class.getClassLoader()); 161 } 162 return new GenericDocument(documentParcel); 163 } 164 165 /** 166 * Returns the {@link GenericDocumentParcel} holding the values for this {@link 167 * GenericDocument}. 168 * 169 * @hide 170 */ getDocumentParcel()171 public @NonNull GenericDocumentParcel getDocumentParcel() { 172 return mDocumentParcel; 173 } 174 175 /** Returns the unique identifier of the {@link GenericDocument}. */ getId()176 public @NonNull String getId() { 177 return mDocumentParcel.getId(); 178 } 179 180 /** Returns the namespace of the {@link GenericDocument}. */ getNamespace()181 public @NonNull String getNamespace() { 182 return mDocumentParcel.getNamespace(); 183 } 184 185 /** Returns the {@link AppSearchSchema} type of the {@link GenericDocument}. */ getSchemaType()186 public @NonNull String getSchemaType() { 187 return mDocumentParcel.getSchemaType(); 188 } 189 190 /** 191 * Returns the list of parent types of the {@link GenericDocument}'s type. 192 * 193 * <p>It is guaranteed that child types appear before parent types in the list. 194 * 195 * @deprecated Parent types should no longer be set in {@link GenericDocument}. Use {@link 196 * SearchResult.Builder#getParentTypeMap()} instead. 197 * @hide 198 */ 199 @Deprecated getParentTypes()200 public @Nullable List<String> getParentTypes() { 201 List<String> result = mDocumentParcel.getParentTypes(); 202 if (result == null) { 203 return null; 204 } 205 return Collections.unmodifiableList(result); 206 } 207 208 /** 209 * Returns the creation timestamp of the {@link GenericDocument}, in milliseconds. 210 * 211 * <p>The value is in the {@link System#currentTimeMillis} time base. 212 */ 213 @CurrentTimeMillisLong getCreationTimestampMillis()214 public long getCreationTimestampMillis() { 215 return mDocumentParcel.getCreationTimestampMillis(); 216 } 217 218 /** 219 * Returns the TTL (time-to-live) of the {@link GenericDocument}, in milliseconds. 220 * 221 * <p>The TTL is measured against {@link #getCreationTimestampMillis}. At the timestamp of 222 * {@code creationTimestampMillis + ttlMillis}, measured in the {@link System#currentTimeMillis} 223 * time base, the document will be auto-deleted. 224 * 225 * <p>The default value is 0, which means the document is permanent and won't be auto-deleted 226 * until the app is uninstalled or {@link AppSearchSession#remove} is called. 227 */ getTtlMillis()228 public long getTtlMillis() { 229 return mDocumentParcel.getTtlMillis(); 230 } 231 232 /** 233 * Returns the score of the {@link GenericDocument}. 234 * 235 * <p>The score is a query-independent measure of the document's quality, relative to other 236 * {@link GenericDocument} objects of the same {@link AppSearchSchema} type. 237 * 238 * <p>Results may be sorted by score using {@link SearchSpec.Builder#setRankingStrategy}. 239 * Documents with higher scores are considered better than documents with lower scores. 240 * 241 * <p>Any non-negative integer can be used a score. 242 */ getScore()243 public int getScore() { 244 return mDocumentParcel.getScore(); 245 } 246 247 /** Returns the names of all properties defined in this document. */ getPropertyNames()248 public @NonNull Set<String> getPropertyNames() { 249 return Collections.unmodifiableSet(mDocumentParcel.getPropertyNames()); 250 } 251 252 /** 253 * Retrieves the property value with the given path as {@link Object}. 254 * 255 * <p>A path can be a simple property name, such as those returned by {@link #getPropertyNames}. 256 * It may also be a dot-delimited path through the nested document hierarchy, with nested {@link 257 * GenericDocument} properties accessed via {@code '.'} and repeated properties optionally 258 * indexed into via {@code [n]}. 259 * 260 * <p>For example, given the following {@link GenericDocument}: 261 * 262 * <pre> 263 * (Message) { 264 * from: "sender@example.com" 265 * to: [{ 266 * name: "Albert Einstein" 267 * email: "einstein@example.com" 268 * }, { 269 * name: "Marie Curie" 270 * email: "curie@example.com" 271 * }] 272 * tags: ["important", "inbox"] 273 * subject: "Hello" 274 * } 275 * </pre> 276 * 277 * <p>Here are some example paths and their results: 278 * 279 * <ul> 280 * <li>{@code "from"} returns {@code "sender@example.com"} as a {@link String} array with one 281 * element 282 * <li>{@code "to"} returns the two nested documents containing contact information as a 283 * {@link GenericDocument} array with two elements 284 * <li>{@code "to[1]"} returns the second nested document containing Marie Curie's contact 285 * information as a {@link GenericDocument} array with one element 286 * <li>{@code "to[1].email"} returns {@code "curie@example.com"} 287 * <li>{@code "to[100].email"} returns {@code null} as this particular document does not have 288 * that many elements in its {@code "to"} array. 289 * <li>{@code "to.email"} aggregates emails across all nested documents that have them, 290 * returning {@code ["einstein@example.com", "curie@example.com"]} as a {@link String} 291 * array with two elements. 292 * </ul> 293 * 294 * <p>If you know the expected type of the property you are retrieving, it is recommended to use 295 * one of the typed versions of this method instead, such as {@link #getPropertyString} or 296 * {@link #getPropertyStringArray}. 297 * 298 * <p>If the property was assigned as an empty array using one of the {@code 299 * Builder#setProperty} functions, this method will return an empty array. If no such property 300 * exists at all, this method returns {@code null}. 301 * 302 * <p>Note: If the property is an empty {@link GenericDocument}[] or {@code byte[][]}, this 303 * method will return a {@code null} value in versions of Android prior to {@link 304 * android.os.Build.VERSION_CODES#TIRAMISU Android T}. Starting in Android T it will return an 305 * empty array if the property has been set as an empty array, matching the behavior of other 306 * property types. 307 * 308 * @param path The path to look for. 309 * @return The entry with the given path as an object or {@code null} if there is no such path. 310 * The returned object will be one of the following types: {@code String[]}, {@code long[]}, 311 * {@code double[]}, {@code boolean[]}, {@code byte[][]}, {@code GenericDocument[]}. 312 */ getProperty(@onNull String path)313 public @Nullable Object getProperty(@NonNull String path) { 314 Objects.requireNonNull(path); 315 Object rawValue = 316 getRawPropertyFromRawDocument( 317 new PropertyPath(path), 318 /* pathIndex= */ 0, 319 mDocumentParcel.getPropertyMap()); 320 321 // Unpack the raw value into the types the user expects, if required. 322 if (rawValue instanceof GenericDocumentParcel) { 323 // getRawPropertyFromRawDocument may return a document as a bare documentParcel 324 // as a performance optimization for lookups. 325 GenericDocument document = new GenericDocument((GenericDocumentParcel) rawValue); 326 return new GenericDocument[] {document}; 327 } 328 329 if (rawValue instanceof GenericDocumentParcel[]) { 330 // The underlying parcelable of nested GenericDocuments is packed into 331 // a Parcelable array. 332 // We must unpack it into GenericDocument instances. 333 GenericDocumentParcel[] docParcels = (GenericDocumentParcel[]) rawValue; 334 GenericDocument[] documents = new GenericDocument[docParcels.length]; 335 for (int i = 0; i < docParcels.length; i++) { 336 if (docParcels[i] == null) { 337 Log.e(TAG, "The inner parcel is null at " + i + ", for path: " + path); 338 continue; 339 } 340 documents[i] = new GenericDocument(docParcels[i]); 341 } 342 return documents; 343 } 344 345 // Otherwise the raw property is the same as the final property and needs no transformation. 346 return rawValue; 347 } 348 349 /** 350 * Looks up a property path within the given document bundle. 351 * 352 * <p>The return value may be any of GenericDocument's internal repeated storage types 353 * (String[], long[], double[], boolean[], ArrayList<Bundle>, Parcelable[]). 354 * 355 * <p>Usually, this method takes a path and loops over it to get a property from the bundle. But 356 * in the case where we collect documents across repeated nested documents, we need to recurse 357 * back into this method, and so we also keep track of the index into the path. 358 * 359 * @param path the PropertyPath object representing the path 360 * @param pathIndex the index into the path we start at 361 * @param propertyMap the map containing the path we are looking up 362 * @return the raw property 363 */ 364 @SuppressWarnings("deprecation") getRawPropertyFromRawDocument( @onNull PropertyPath path, int pathIndex, @NonNull Map<String, PropertyParcel> propertyMap)365 private static @Nullable Object getRawPropertyFromRawDocument( 366 @NonNull PropertyPath path, 367 int pathIndex, 368 @NonNull Map<String, PropertyParcel> propertyMap) { 369 Objects.requireNonNull(path); 370 Objects.requireNonNull(propertyMap); 371 for (int i = pathIndex; i < path.size(); i++) { 372 PropertyPath.PathSegment segment = path.get(i); 373 Object currentElementValue = propertyMap.get(segment.getPropertyName()); 374 if (currentElementValue == null) { 375 return null; 376 } 377 378 // If the current PathSegment has an index, we now need to update currentElementValue to 379 // contain the value of the indexed property. For example, for a path segment like 380 // "recipients[0]", currentElementValue now contains the value of "recipients" while we 381 // need the value of "recipients[0]". 382 int index = segment.getPropertyIndex(); 383 if (index != PropertyPath.PathSegment.NON_REPEATED_CARDINALITY) { 384 // For properties bundle, now we will only get PropertyParcel as the value. 385 PropertyParcel propertyParcel = (PropertyParcel) currentElementValue; 386 387 // Extract the right array element 388 Object extractedValue = null; 389 if (propertyParcel.getStringValues() != null) { 390 String[] stringValues = propertyParcel.getStringValues(); 391 if (stringValues != null && index < stringValues.length) { 392 extractedValue = Arrays.copyOfRange(stringValues, index, index + 1); 393 } 394 } else if (propertyParcel.getLongValues() != null) { 395 long[] longValues = propertyParcel.getLongValues(); 396 if (longValues != null && index < longValues.length) { 397 extractedValue = Arrays.copyOfRange(longValues, index, index + 1); 398 } 399 } else if (propertyParcel.getDoubleValues() != null) { 400 double[] doubleValues = propertyParcel.getDoubleValues(); 401 if (doubleValues != null && index < doubleValues.length) { 402 extractedValue = Arrays.copyOfRange(doubleValues, index, index + 1); 403 } 404 } else if (propertyParcel.getBooleanValues() != null) { 405 boolean[] booleanValues = propertyParcel.getBooleanValues(); 406 if (booleanValues != null && index < booleanValues.length) { 407 extractedValue = Arrays.copyOfRange(booleanValues, index, index + 1); 408 } 409 } else if (propertyParcel.getBytesValues() != null) { 410 byte[][] bytesValues = propertyParcel.getBytesValues(); 411 if (bytesValues != null && index < bytesValues.length) { 412 extractedValue = Arrays.copyOfRange(bytesValues, index, index + 1); 413 } 414 } else if (propertyParcel.getDocumentValues() != null) { 415 // Special optimization: to avoid creating new singleton arrays for traversing 416 // paths we return the bare document parcel in this particular case. 417 GenericDocumentParcel[] docValues = propertyParcel.getDocumentValues(); 418 if (docValues != null && index < docValues.length) { 419 extractedValue = docValues[index]; 420 } 421 } else if (propertyParcel.getEmbeddingValues() != null) { 422 EmbeddingVector[] embeddingValues = propertyParcel.getEmbeddingValues(); 423 if (embeddingValues != null && index < embeddingValues.length) { 424 extractedValue = Arrays.copyOfRange(embeddingValues, index, index + 1); 425 } 426 } else if (propertyParcel.getBlobHandleValues() != null) { 427 AppSearchBlobHandle[] blobHandlesValues = propertyParcel.getBlobHandleValues(); 428 if (blobHandlesValues != null && index < blobHandlesValues.length) { 429 extractedValue = Arrays.copyOfRange(blobHandlesValues, index, index + 1); 430 } 431 } else { 432 throw new IllegalStateException( 433 "Unsupported value type: " + currentElementValue); 434 } 435 currentElementValue = extractedValue; 436 } 437 438 // at the end of the path, either something like "...foo" or "...foo[1]" 439 if (currentElementValue == null || i == path.size() - 1) { 440 if (currentElementValue != null && currentElementValue instanceof PropertyParcel) { 441 // Unlike previous bundle-based implementation, now each 442 // value is wrapped in PropertyParcel. 443 // Here we need to get and return the actual value for non-repeated fields. 444 currentElementValue = ((PropertyParcel) currentElementValue).getValues(); 445 } 446 return currentElementValue; 447 } 448 449 // currentElementValue is now a GenericDocumentParcel or PropertyParcel, 450 // we can continue down the path. 451 if (currentElementValue instanceof GenericDocumentParcel) { 452 propertyMap = ((GenericDocumentParcel) currentElementValue).getPropertyMap(); 453 } else if (currentElementValue instanceof PropertyParcel 454 && ((PropertyParcel) currentElementValue).getDocumentValues() != null) { 455 GenericDocumentParcel[] docParcels = 456 ((PropertyParcel) currentElementValue).getDocumentValues(); 457 if (docParcels != null && docParcels.length == 1) { 458 propertyMap = docParcels[0].getPropertyMap(); 459 continue; 460 } 461 462 // Slowest path: we're collecting values across repeated nested docs. (Example: 463 // given a path like recipient.name, where recipient is a repeated field, we return 464 // a string array where each recipient's name is an array element). 465 // 466 // Performance note: Suppose that we have a property path "a.b.c" where the "a" 467 // property has N document values and each containing a "b" property with M document 468 // values and each of those containing a "c" property with an int array. 469 // 470 // We'll allocate a new ArrayList for each of the "b" properties, add the M int 471 // arrays from the "c" properties to it and then we'll allocate an int array in 472 // flattenAccumulator before returning that (1 + M allocation per "b" property). 473 // 474 // When we're on the "a" properties, we'll allocate an ArrayList and add the N 475 // flattened int arrays returned from the "b" properties to the list. Then we'll 476 // allocate an int array in flattenAccumulator (1 + N ("b" allocs) allocations per 477 // "a"). // So this implementation could incur 1 + N + NM allocs. 478 // 479 // However, we expect the vast majority of getProperty calls to be either for direct 480 // property names (not paths) or else property paths returned from snippetting, 481 // which always refer to exactly one property value and don't aggregate across 482 // repeated values. The implementation is optimized for these two cases, requiring 483 // no additional allocations. So we've decided that the above performance 484 // characteristics are OK for the less used path. 485 if (docParcels != null) { 486 List<Object> accumulator = new ArrayList<>(docParcels.length); 487 for (GenericDocumentParcel docParcel : docParcels) { 488 // recurse as we need to branch 489 Object value = 490 getRawPropertyFromRawDocument( 491 path, 492 /* pathIndex= */ i + 1, 493 ((GenericDocumentParcel) docParcel).getPropertyMap()); 494 if (value != null) { 495 accumulator.add(value); 496 } 497 } 498 // Break the path traversing loop 499 return flattenAccumulator(accumulator); 500 } 501 } else { 502 Log.e(TAG, "Failed to apply path to document; no nested value found: " + path); 503 return null; 504 } 505 } 506 // Only way to get here is with an empty path list 507 return null; 508 } 509 510 /** 511 * Combines accumulated repeated properties from multiple documents into a single array. 512 * 513 * @param accumulator List containing objects of the following types: {@code String[]}, {@code 514 * long[]}, {@code double[]}, {@code boolean[]}, {@code byte[][]}, or {@code 515 * GenericDocumentParcelable[]}. 516 * @return The result of concatenating each individual list element into a larger array/list of 517 * the same type. 518 */ flattenAccumulator(@onNull List<Object> accumulator)519 private static @Nullable Object flattenAccumulator(@NonNull List<Object> accumulator) { 520 if (accumulator.isEmpty()) { 521 return null; 522 } 523 Object first = accumulator.get(0); 524 if (first instanceof String[]) { 525 int length = 0; 526 for (int i = 0; i < accumulator.size(); i++) { 527 length += ((String[]) accumulator.get(i)).length; 528 } 529 String[] result = new String[length]; 530 int total = 0; 531 for (int i = 0; i < accumulator.size(); i++) { 532 String[] castValue = (String[]) accumulator.get(i); 533 System.arraycopy(castValue, 0, result, total, castValue.length); 534 total += castValue.length; 535 } 536 return result; 537 } 538 if (first instanceof long[]) { 539 int length = 0; 540 for (int i = 0; i < accumulator.size(); i++) { 541 length += ((long[]) accumulator.get(i)).length; 542 } 543 long[] result = new long[length]; 544 int total = 0; 545 for (int i = 0; i < accumulator.size(); i++) { 546 long[] castValue = (long[]) accumulator.get(i); 547 System.arraycopy(castValue, 0, result, total, castValue.length); 548 total += castValue.length; 549 } 550 return result; 551 } 552 if (first instanceof double[]) { 553 int length = 0; 554 for (int i = 0; i < accumulator.size(); i++) { 555 length += ((double[]) accumulator.get(i)).length; 556 } 557 double[] result = new double[length]; 558 int total = 0; 559 for (int i = 0; i < accumulator.size(); i++) { 560 double[] castValue = (double[]) accumulator.get(i); 561 System.arraycopy(castValue, 0, result, total, castValue.length); 562 total += castValue.length; 563 } 564 return result; 565 } 566 if (first instanceof boolean[]) { 567 int length = 0; 568 for (int i = 0; i < accumulator.size(); i++) { 569 length += ((boolean[]) accumulator.get(i)).length; 570 } 571 boolean[] result = new boolean[length]; 572 int total = 0; 573 for (int i = 0; i < accumulator.size(); i++) { 574 boolean[] castValue = (boolean[]) accumulator.get(i); 575 System.arraycopy(castValue, 0, result, total, castValue.length); 576 total += castValue.length; 577 } 578 return result; 579 } 580 if (first instanceof byte[][]) { 581 int length = 0; 582 for (int i = 0; i < accumulator.size(); i++) { 583 length += ((byte[][]) accumulator.get(i)).length; 584 } 585 byte[][] result = new byte[length][]; 586 int total = 0; 587 for (int i = 0; i < accumulator.size(); i++) { 588 byte[][] castValue = (byte[][]) accumulator.get(i); 589 System.arraycopy(castValue, 0, result, total, castValue.length); 590 total += castValue.length; 591 } 592 return result; 593 } 594 if (first instanceof GenericDocumentParcel[]) { 595 int length = 0; 596 for (int i = 0; i < accumulator.size(); i++) { 597 length += ((GenericDocumentParcel[]) accumulator.get(i)).length; 598 } 599 GenericDocumentParcel[] result = new GenericDocumentParcel[length]; 600 int total = 0; 601 for (int i = 0; i < accumulator.size(); i++) { 602 GenericDocumentParcel[] castValue = (GenericDocumentParcel[]) accumulator.get(i); 603 System.arraycopy(castValue, 0, result, total, castValue.length); 604 total += castValue.length; 605 } 606 return result; 607 } 608 throw new IllegalStateException("Unexpected property type: " + first); 609 } 610 611 /** 612 * Retrieves a {@link String} property by path. 613 * 614 * <p>See {@link #getProperty} for a detailed description of the path syntax. 615 * 616 * @param path The path to look for. 617 * @return The first {@link String} associated with the given path or {@code null} if there is 618 * no such value or the value is of a different type. 619 */ getPropertyString(@onNull String path)620 public @Nullable String getPropertyString(@NonNull String path) { 621 Objects.requireNonNull(path); 622 String[] propertyArray = getPropertyStringArray(path); 623 if (propertyArray == null || propertyArray.length == 0) { 624 return null; 625 } 626 warnIfSinglePropertyTooLong("String", path, propertyArray.length); 627 return propertyArray[0]; 628 } 629 630 /** 631 * Retrieves a {@code long} property by path. 632 * 633 * <p>See {@link #getProperty} for a detailed description of the path syntax. 634 * 635 * @param path The path to look for. 636 * @return The first {@code long} associated with the given path or default value {@code 0} if 637 * there is no such value or the value is of a different type. 638 */ getPropertyLong(@onNull String path)639 public long getPropertyLong(@NonNull String path) { 640 Objects.requireNonNull(path); 641 long[] propertyArray = getPropertyLongArray(path); 642 if (propertyArray == null || propertyArray.length == 0) { 643 return 0; 644 } 645 warnIfSinglePropertyTooLong("Long", path, propertyArray.length); 646 return propertyArray[0]; 647 } 648 649 /** 650 * Retrieves a {@code double} property by path. 651 * 652 * <p>See {@link #getProperty} for a detailed description of the path syntax. 653 * 654 * @param path The path to look for. 655 * @return The first {@code double} associated with the given path or default value {@code 0.0} 656 * if there is no such value or the value is of a different type. 657 */ getPropertyDouble(@onNull String path)658 public double getPropertyDouble(@NonNull String path) { 659 Objects.requireNonNull(path); 660 double[] propertyArray = getPropertyDoubleArray(path); 661 if (propertyArray == null || propertyArray.length == 0) { 662 return 0.0; 663 } 664 warnIfSinglePropertyTooLong("Double", path, propertyArray.length); 665 return propertyArray[0]; 666 } 667 668 /** 669 * Retrieves a {@code boolean} property by path. 670 * 671 * <p>See {@link #getProperty} for a detailed description of the path syntax. 672 * 673 * @param path The path to look for. 674 * @return The first {@code boolean} associated with the given path or default value {@code 675 * false} if there is no such value or the value is of a different type. 676 */ getPropertyBoolean(@onNull String path)677 public boolean getPropertyBoolean(@NonNull String path) { 678 Objects.requireNonNull(path); 679 boolean[] propertyArray = getPropertyBooleanArray(path); 680 if (propertyArray == null || propertyArray.length == 0) { 681 return false; 682 } 683 warnIfSinglePropertyTooLong("Boolean", path, propertyArray.length); 684 return propertyArray[0]; 685 } 686 687 /** 688 * Retrieves a {@code byte[]} property by path. 689 * 690 * <p>See {@link #getProperty} for a detailed description of the path syntax. 691 * 692 * @param path The path to look for. 693 * @return The first {@code byte[]} associated with the given path or {@code null} if there is 694 * no such value or the value is of a different type. 695 */ getPropertyBytes(@onNull String path)696 public @Nullable byte[] getPropertyBytes(@NonNull String path) { 697 Objects.requireNonNull(path); 698 byte[][] propertyArray = getPropertyBytesArray(path); 699 if (propertyArray == null || propertyArray.length == 0) { 700 return null; 701 } 702 warnIfSinglePropertyTooLong("ByteArray", path, propertyArray.length); 703 return propertyArray[0]; 704 } 705 706 /** 707 * Retrieves a {@link GenericDocument} property by path. 708 * 709 * <p>See {@link #getProperty} for a detailed description of the path syntax. 710 * 711 * @param path The path to look for. 712 * @return The first {@link GenericDocument} associated with the given path or {@code null} if 713 * there is no such value or the value is of a different type. 714 */ getPropertyDocument(@onNull String path)715 public @Nullable GenericDocument getPropertyDocument(@NonNull String path) { 716 Objects.requireNonNull(path); 717 GenericDocument[] propertyArray = getPropertyDocumentArray(path); 718 if (propertyArray == null || propertyArray.length == 0) { 719 return null; 720 } 721 warnIfSinglePropertyTooLong("Document", path, propertyArray.length); 722 return propertyArray[0]; 723 } 724 725 /** 726 * Retrieves an {@code EmbeddingVector} property by path. 727 * 728 * <p>See {@link #getProperty} for a detailed description of the path syntax. 729 * 730 * @param path The path to look for. 731 * @return The first {@code EmbeddingVector[]} associated with the given path or {@code null} if 732 * there is no such value or the value is of a different type. 733 */ 734 @FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_EMBEDDING_PROPERTY_CONFIG) getPropertyEmbedding(@onNull String path)735 public @Nullable EmbeddingVector getPropertyEmbedding(@NonNull String path) { 736 Objects.requireNonNull(path); 737 EmbeddingVector[] propertyArray = getPropertyEmbeddingArray(path); 738 if (propertyArray == null || propertyArray.length == 0) { 739 return null; 740 } 741 warnIfSinglePropertyTooLong("Embedding", path, propertyArray.length); 742 return propertyArray[0]; 743 } 744 745 /** 746 * Retrieves an {@link AppSearchBlobHandle} property by path. 747 * 748 * <p>See {@link #getProperty} for a detailed description of the path syntax. 749 * 750 * <p>See {@link AppSearchSession#openBlobForRead} for how to use {@link AppSearchBlobHandle} to 751 * retrieve blob data. 752 * 753 * @param path The path to look for. 754 * @return The first {@link AppSearchBlobHandle} associated with the given path or {@code null} 755 * if there is no such value or the value is of a different type. 756 */ 757 @FlaggedApi(Flags.FLAG_ENABLE_BLOB_STORE) getPropertyBlobHandle(@onNull String path)758 public @Nullable AppSearchBlobHandle getPropertyBlobHandle(@NonNull String path) { 759 Objects.requireNonNull(path); 760 AppSearchBlobHandle[] propertyArray = getPropertyBlobHandleArray(path); 761 if (propertyArray == null || propertyArray.length == 0) { 762 return null; 763 } 764 warnIfSinglePropertyTooLong("BlobHandle", path, propertyArray.length); 765 return propertyArray[0]; 766 } 767 768 /** Prints a warning to logcat if the given propertyLength is greater than 1. */ warnIfSinglePropertyTooLong( @onNull String propertyType, @NonNull String path, int propertyLength)769 private static void warnIfSinglePropertyTooLong( 770 @NonNull String propertyType, @NonNull String path, int propertyLength) { 771 if (propertyLength > 1) { 772 Log.w( 773 TAG, 774 "The value for \"" 775 + path 776 + "\" contains " 777 + propertyLength 778 + " elements. Only the first one will be returned from " 779 + "getProperty" 780 + propertyType 781 + "(). Try getProperty" 782 + propertyType 783 + "Array()."); 784 } 785 } 786 787 /** 788 * Retrieves a repeated {@code String} property by path. 789 * 790 * <p>See {@link #getProperty} for a detailed description of the path syntax. 791 * 792 * <p>If the property has not been set via {@link Builder#setPropertyString}, this method 793 * returns {@code null}. 794 * 795 * <p>If it has been set via {@link Builder#setPropertyString} to an empty {@code String[]}, 796 * this method returns an empty {@code String[]}. 797 * 798 * @param path The path to look for. 799 * @return The {@code String[]} associated with the given path, or {@code null} if no value is 800 * set or the value is of a different type. 801 */ getPropertyStringArray(@onNull String path)802 public @Nullable String[] getPropertyStringArray(@NonNull String path) { 803 Objects.requireNonNull(path); 804 Object value = getProperty(path); 805 return safeCastProperty(path, value, String[].class); 806 } 807 808 /** 809 * Retrieves a repeated {@code long[]} property by path. 810 * 811 * <p>See {@link #getProperty} for a detailed description of the path syntax. 812 * 813 * <p>If the property has not been set via {@link Builder#setPropertyLong}, this method returns 814 * {@code null}. 815 * 816 * <p>If it has been set via {@link Builder#setPropertyLong} to an empty {@code long[]}, this 817 * method returns an empty {@code long[]}. 818 * 819 * @param path The path to look for. 820 * @return The {@code long[]} associated with the given path, or {@code null} if no value is set 821 * or the value is of a different type. 822 */ getPropertyLongArray(@onNull String path)823 public @Nullable long[] getPropertyLongArray(@NonNull String path) { 824 Objects.requireNonNull(path); 825 Object value = getProperty(path); 826 return safeCastProperty(path, value, long[].class); 827 } 828 829 /** 830 * Retrieves a repeated {@code double} property by path. 831 * 832 * <p>See {@link #getProperty} for a detailed description of the path syntax. 833 * 834 * <p>If the property has not been set via {@link Builder#setPropertyDouble}, this method 835 * returns {@code null}. 836 * 837 * <p>If it has been set via {@link Builder#setPropertyDouble} to an empty {@code double[]}, 838 * this method returns an empty {@code double[]}. 839 * 840 * @param path The path to look for. 841 * @return The {@code double[]} associated with the given path, or {@code null} if no value is 842 * set or the value is of a different type. 843 */ getPropertyDoubleArray(@onNull String path)844 public @Nullable double[] getPropertyDoubleArray(@NonNull String path) { 845 Objects.requireNonNull(path); 846 Object value = getProperty(path); 847 return safeCastProperty(path, value, double[].class); 848 } 849 850 /** 851 * Retrieves a repeated {@code boolean} property by path. 852 * 853 * <p>See {@link #getProperty} for a detailed description of the path syntax. 854 * 855 * <p>If the property has not been set via {@link Builder#setPropertyBoolean}, this method 856 * returns {@code null}. 857 * 858 * <p>If it has been set via {@link Builder#setPropertyBoolean} to an empty {@code boolean[]}, 859 * this method returns an empty {@code boolean[]}. 860 * 861 * @param path The path to look for. 862 * @return The {@code boolean[]} associated with the given path, or {@code null} if no value is 863 * set or the value is of a different type. 864 */ getPropertyBooleanArray(@onNull String path)865 public @Nullable boolean[] getPropertyBooleanArray(@NonNull String path) { 866 Objects.requireNonNull(path); 867 Object value = getProperty(path); 868 return safeCastProperty(path, value, boolean[].class); 869 } 870 871 /** 872 * Retrieves a {@code byte[][]} property by path. 873 * 874 * <p>See {@link #getProperty} for a detailed description of the path syntax. 875 * 876 * <p>If the property has not been set via {@link Builder#setPropertyBytes}, this method returns 877 * {@code null}. 878 * 879 * <p>If it has been set via {@link Builder#setPropertyBytes} to an empty {@code byte[][]}, this 880 * method returns an empty {@code byte[][]} starting in {@link 881 * android.os.Build.VERSION_CODES#TIRAMISU Android T} and {@code null} in earlier versions of 882 * Android. 883 * 884 * @param path The path to look for. 885 * @return The {@code byte[][]} associated with the given path, or {@code null} if no value is 886 * set or the value is of a different type. 887 */ 888 @SuppressLint("ArrayReturn") getPropertyBytesArray(@onNull String path)889 public @Nullable byte[][] getPropertyBytesArray(@NonNull String path) { 890 Objects.requireNonNull(path); 891 Object value = getProperty(path); 892 return safeCastProperty(path, value, byte[][].class); 893 } 894 895 /** 896 * Retrieves a repeated {@link GenericDocument} property by path. 897 * 898 * <p>See {@link #getProperty} for a detailed description of the path syntax. 899 * 900 * <p>If the property has not been set via {@link Builder#setPropertyDocument}, this method 901 * returns {@code null}. 902 * 903 * <p>If it has been set via {@link Builder#setPropertyDocument} to an empty {@code 904 * GenericDocument[]}, this method returns an empty {@code GenericDocument[]} starting in {@link 905 * android.os.Build.VERSION_CODES#TIRAMISU Android T} and {@code null} in earlier versions of 906 * Android. 907 * 908 * @param path The path to look for. 909 * @return The {@link GenericDocument}[] associated with the given path, or {@code null} if no 910 * value is set or the value is of a different type. 911 */ 912 @SuppressLint("ArrayReturn") getPropertyDocumentArray(@onNull String path)913 public @Nullable GenericDocument[] getPropertyDocumentArray(@NonNull String path) { 914 Objects.requireNonNull(path); 915 Object value = getProperty(path); 916 return safeCastProperty(path, value, GenericDocument[].class); 917 } 918 919 /** 920 * Retrieves a repeated {@code EmbeddingVector[]} property by path. 921 * 922 * <p>See {@link #getProperty} for a detailed description of the path syntax. 923 * 924 * <p>If the property has not been set via {@link Builder#setPropertyEmbedding}, this method 925 * returns {@code null}. 926 * 927 * <p>If it has been set via {@link Builder#setPropertyEmbedding} to an empty {@code 928 * EmbeddingVector[]}, this method returns an empty {@code EmbeddingVector[]}. 929 * 930 * @param path The path to look for. 931 * @return The {@code EmbeddingVector[]} associated with the given path, or {@code null} if no 932 * value is set or the value is of a different type. 933 */ 934 @SuppressLint({"ArrayReturn", "NullableCollection"}) 935 @FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_EMBEDDING_PROPERTY_CONFIG) getPropertyEmbeddingArray(@onNull String path)936 public @Nullable EmbeddingVector[] getPropertyEmbeddingArray(@NonNull String path) { 937 Objects.requireNonNull(path); 938 Object value = getProperty(path); 939 return safeCastProperty(path, value, EmbeddingVector[].class); 940 } 941 942 /** 943 * Retrieves a repeated {@code AppSearchBlobHandle[]} property by path. 944 * 945 * <p>See {@link #getProperty} for a detailed description of the path syntax. 946 * 947 * <p>If the property has not been set via {@link Builder#setPropertyBlobHandle}, this method 948 * returns {@code null}. 949 * 950 * <p>If it has been set via {@link Builder#setPropertyBlobHandle} to an empty {@code 951 * AppSearchBlobHandle[]}, this method returns an empty {@code AppSearchBlobHandle[]}. 952 * 953 * @param path The path to look for. 954 * @return The {@code AppSearchBlobHandle[]} associated with the given path, or {@code null} if 955 * no value is set or the value is of a different type. 956 */ 957 @SuppressLint({"ArrayReturn", "NullableCollection"}) 958 @FlaggedApi(Flags.FLAG_ENABLE_BLOB_STORE) getPropertyBlobHandleArray(@onNull String path)959 public @Nullable AppSearchBlobHandle[] getPropertyBlobHandleArray(@NonNull String path) { 960 Objects.requireNonNull(path); 961 Object value = getProperty(path); 962 return safeCastProperty(path, value, AppSearchBlobHandle[].class); 963 } 964 965 /** 966 * Casts a repeated property to the provided type, logging an error and returning {@code null} 967 * if the cast fails. 968 * 969 * @param path Path to the property within the document. Used for logging. 970 * @param value Value of the property 971 * @param tClass Class to cast the value into 972 */ safeCastProperty( @onNull String path, @Nullable Object value, @NonNull Class<T> tClass)973 private static <T> @Nullable T safeCastProperty( 974 @NonNull String path, @Nullable Object value, @NonNull Class<T> tClass) { 975 if (value == null) { 976 return null; 977 } 978 try { 979 return tClass.cast(value); 980 } catch (ClassCastException e) { 981 Log.w(TAG, "Error casting to requested type for path \"" + path + "\"", e); 982 return null; 983 } 984 } 985 986 @Override equals(@ullable Object other)987 public boolean equals(@Nullable Object other) { 988 if (this == other) { 989 return true; 990 } 991 if (!(other instanceof GenericDocument)) { 992 return false; 993 } 994 GenericDocument otherDocument = (GenericDocument) other; 995 return mDocumentParcel.equals(otherDocument.mDocumentParcel); 996 } 997 998 @Override hashCode()999 public int hashCode() { 1000 return mDocumentParcel.hashCode(); 1001 } 1002 1003 @Override toString()1004 public @NonNull String toString() { 1005 IndentingStringBuilder stringBuilder = new IndentingStringBuilder(); 1006 appendGenericDocumentString(stringBuilder); 1007 return stringBuilder.toString(); 1008 } 1009 1010 /** 1011 * Appends a debug string for the {@link GenericDocument} instance to the given string builder. 1012 * 1013 * @param builder the builder to append to. 1014 */ appendGenericDocumentString(@onNull IndentingStringBuilder builder)1015 void appendGenericDocumentString(@NonNull IndentingStringBuilder builder) { 1016 Objects.requireNonNull(builder); 1017 1018 builder.append("{\n"); 1019 builder.increaseIndentLevel(); 1020 1021 builder.append("namespace: \"").append(getNamespace()).append("\",\n"); 1022 builder.append("id: \"").append(getId()).append("\",\n"); 1023 builder.append("score: ").append(getScore()).append(",\n"); 1024 builder.append("schemaType: \"").append(getSchemaType()).append("\",\n"); 1025 List<String> parentTypes = getParentTypes(); 1026 if (parentTypes != null) { 1027 builder.append("parentTypes: ").append(parentTypes).append("\n"); 1028 } 1029 builder.append("creationTimestampMillis: ") 1030 .append(getCreationTimestampMillis()) 1031 .append(",\n"); 1032 builder.append("timeToLiveMillis: ").append(getTtlMillis()).append(",\n"); 1033 1034 builder.append("properties: {\n"); 1035 1036 String[] sortedProperties = getPropertyNames().toArray(new String[0]); 1037 Arrays.sort(sortedProperties); 1038 1039 for (int i = 0; i < sortedProperties.length; i++) { 1040 Object property = Objects.requireNonNull(getProperty(sortedProperties[i])); 1041 builder.increaseIndentLevel(); 1042 appendPropertyString(sortedProperties[i], property, builder); 1043 if (i != sortedProperties.length - 1) { 1044 builder.append(",\n"); 1045 } 1046 builder.decreaseIndentLevel(); 1047 } 1048 1049 builder.append("\n"); 1050 builder.append("}"); 1051 1052 builder.decreaseIndentLevel(); 1053 builder.append("\n"); 1054 builder.append("}"); 1055 } 1056 1057 /** 1058 * Appends a debug string for the given document property to the given string builder. 1059 * 1060 * @param propertyName name of property to create string for. 1061 * @param property property object to create string for. 1062 * @param builder the builder to append to. 1063 */ appendPropertyString( @onNull String propertyName, @NonNull Object property, @NonNull IndentingStringBuilder builder)1064 private void appendPropertyString( 1065 @NonNull String propertyName, 1066 @NonNull Object property, 1067 @NonNull IndentingStringBuilder builder) { 1068 Objects.requireNonNull(propertyName); 1069 Objects.requireNonNull(property); 1070 Objects.requireNonNull(builder); 1071 1072 builder.append("\"").append(propertyName).append("\": ["); 1073 if (property instanceof GenericDocument[]) { 1074 GenericDocument[] documentValues = (GenericDocument[]) property; 1075 for (int i = 0; i < documentValues.length; ++i) { 1076 builder.append("\n"); 1077 builder.increaseIndentLevel(); 1078 documentValues[i].appendGenericDocumentString(builder); 1079 if (i != documentValues.length - 1) { 1080 builder.append(","); 1081 } 1082 builder.append("\n"); 1083 builder.decreaseIndentLevel(); 1084 } 1085 } else { 1086 int propertyArrLength = Array.getLength(property); 1087 for (int i = 0; i < propertyArrLength; i++) { 1088 Object propertyElement = Array.get(property, i); 1089 if (propertyElement instanceof String) { 1090 builder.append("\"").append((String) propertyElement).append("\""); 1091 } else if (propertyElement instanceof byte[]) { 1092 builder.append(Arrays.toString((byte[]) propertyElement)); 1093 } else if (propertyElement != null) { 1094 builder.append(propertyElement.toString()); 1095 } 1096 if (i != propertyArrLength - 1) { 1097 builder.append(", "); 1098 } 1099 } 1100 } 1101 builder.append("]"); 1102 } 1103 1104 /** 1105 * The builder class for {@link GenericDocument}. 1106 * 1107 * @param <BuilderType> Type of subclass who extends this. 1108 */ 1109 // This builder is specifically designed to be extended by classes deriving from 1110 // GenericDocument. 1111 @SuppressLint("StaticFinalBuilder") 1112 @SuppressWarnings("rawtypes") 1113 public static class Builder<BuilderType extends Builder> { 1114 private final GenericDocumentParcel.Builder mDocumentParcelBuilder; 1115 private final BuilderType mBuilderTypeInstance; 1116 1117 /** 1118 * Creates a new {@link GenericDocument.Builder}. 1119 * 1120 * <p>Document IDs are unique within a namespace. 1121 * 1122 * <p>The number of namespaces per app should be kept small for efficiency reasons. 1123 * 1124 * @param namespace the namespace to set for the {@link GenericDocument}. 1125 * @param id the unique identifier for the {@link GenericDocument} in its namespace. 1126 * @param schemaType the {@link AppSearchSchema} type of the {@link GenericDocument}. The 1127 * provided {@code schemaType} must be defined using {@link AppSearchSession#setSchema} 1128 * prior to inserting a document of this {@code schemaType} into the AppSearch index 1129 * using {@link AppSearchSession#put}. Otherwise, the document will be rejected by 1130 * {@link AppSearchSession#put} with result code {@link 1131 * AppSearchResult#RESULT_NOT_FOUND}. 1132 */ 1133 @SuppressWarnings("unchecked") Builder(@onNull String namespace, @NonNull String id, @NonNull String schemaType)1134 public Builder(@NonNull String namespace, @NonNull String id, @NonNull String schemaType) { 1135 Objects.requireNonNull(namespace); 1136 Objects.requireNonNull(id); 1137 Objects.requireNonNull(schemaType); 1138 1139 mBuilderTypeInstance = (BuilderType) this; 1140 mDocumentParcelBuilder = new GenericDocumentParcel.Builder(namespace, id, schemaType); 1141 } 1142 1143 /** 1144 * Creates a new {@link GenericDocument.Builder} from the given {@link 1145 * GenericDocumentParcel.Builder}. 1146 * 1147 * <p>The bundle is NOT copied. 1148 */ 1149 @SuppressWarnings("unchecked") Builder(@onNull GenericDocumentParcel.Builder documentParcelBuilder)1150 Builder(@NonNull GenericDocumentParcel.Builder documentParcelBuilder) { 1151 mDocumentParcelBuilder = Objects.requireNonNull(documentParcelBuilder); 1152 mBuilderTypeInstance = (BuilderType) this; 1153 } 1154 1155 /** 1156 * Creates a new {@link GenericDocument.Builder} from the given GenericDocument. 1157 * 1158 * <p>The GenericDocument is deep copied, that is, it changes to a new GenericDocument 1159 * returned by this function and will NOT affect the original GenericDocument. 1160 */ 1161 @FlaggedApi(Flags.FLAG_ENABLE_GENERIC_DOCUMENT_COPY_CONSTRUCTOR) Builder(@onNull GenericDocument document)1162 public Builder(@NonNull GenericDocument document) { 1163 this(new GenericDocumentParcel.Builder(document.mDocumentParcel)); 1164 } 1165 1166 /** 1167 * Sets the app-defined namespace this document resides in, changing the value provided in 1168 * the constructor. No special values are reserved or understood by the infrastructure. 1169 * 1170 * <p>Document IDs are unique within a namespace. 1171 * 1172 * <p>The number of namespaces per app should be kept small for efficiency reasons. 1173 */ 1174 @FlaggedApi(Flags.FLAG_ENABLE_GENERIC_DOCUMENT_BUILDER_HIDDEN_METHODS) 1175 @CanIgnoreReturnValue setNamespace(@onNull String namespace)1176 public @NonNull BuilderType setNamespace(@NonNull String namespace) { 1177 Objects.requireNonNull(namespace); 1178 mDocumentParcelBuilder.setNamespace(namespace); 1179 return mBuilderTypeInstance; 1180 } 1181 1182 /** 1183 * Sets the ID of this document, changing the value provided in the constructor. No special 1184 * values are reserved or understood by the infrastructure. 1185 * 1186 * <p>Document IDs are unique within the combination of package, database, and namespace. 1187 * 1188 * <p>Setting a document with a duplicate id will overwrite the original document with the 1189 * new document, enforcing uniqueness within the above constraint. 1190 */ 1191 @FlaggedApi(Flags.FLAG_ENABLE_GENERIC_DOCUMENT_BUILDER_HIDDEN_METHODS) 1192 @CanIgnoreReturnValue setId(@onNull String id)1193 public @NonNull BuilderType setId(@NonNull String id) { 1194 Objects.requireNonNull(id); 1195 mDocumentParcelBuilder.setId(id); 1196 return mBuilderTypeInstance; 1197 } 1198 1199 /** 1200 * Sets the schema type of this document, changing the value provided in the constructor. 1201 * 1202 * <p>To successfully index a document, the schema type must match the name of an {@link 1203 * AppSearchSchema} object previously provided to {@link AppSearchSession#setSchema}. 1204 */ 1205 @FlaggedApi(Flags.FLAG_ENABLE_GENERIC_DOCUMENT_BUILDER_HIDDEN_METHODS) 1206 @CanIgnoreReturnValue setSchemaType(@onNull String schemaType)1207 public @NonNull BuilderType setSchemaType(@NonNull String schemaType) { 1208 Objects.requireNonNull(schemaType); 1209 mDocumentParcelBuilder.setSchemaType(schemaType); 1210 return mBuilderTypeInstance; 1211 } 1212 1213 /** 1214 * Sets the list of parent types of the {@link GenericDocument}'s type. 1215 * 1216 * <p>Child types must appear before parent types in the list. 1217 * 1218 * @deprecated Parent types should no longer be set in {@link GenericDocument}. Use {@link 1219 * SearchResult.Builder#setParentTypeMap(Map)} instead. 1220 * @hide 1221 */ 1222 @CanIgnoreReturnValue 1223 @Deprecated setParentTypes(@ullable List<String> parentTypes)1224 public @NonNull BuilderType setParentTypes(@Nullable List<String> parentTypes) { 1225 mDocumentParcelBuilder.setParentTypes(parentTypes); 1226 return mBuilderTypeInstance; 1227 } 1228 1229 /** 1230 * Sets the score of the {@link GenericDocument}. 1231 * 1232 * <p>The score is a query-independent measure of the document's quality, relative to other 1233 * {@link GenericDocument} objects of the same {@link AppSearchSchema} type. 1234 * 1235 * <p>Results may be sorted by score using {@link SearchSpec.Builder#setRankingStrategy}. 1236 * Documents with higher scores are considered better than documents with lower scores. 1237 * 1238 * <p>Any non-negative integer can be used a score. By default, scores are set to 0. 1239 * 1240 * @param score any non-negative {@code int} representing the document's score. 1241 * @throws IllegalArgumentException if the score is negative. 1242 */ 1243 @CanIgnoreReturnValue setScore( @ntRangefrom = 0, to = Integer.MAX_VALUE) int score)1244 public @NonNull BuilderType setScore( 1245 @IntRange(from = 0, to = Integer.MAX_VALUE) int score) { 1246 if (score < 0) { 1247 throw new IllegalArgumentException("Document score cannot be negative."); 1248 } 1249 mDocumentParcelBuilder.setScore(score); 1250 return mBuilderTypeInstance; 1251 } 1252 1253 /** 1254 * Sets the creation timestamp of the {@link GenericDocument}, in milliseconds. 1255 * 1256 * <p>This should be set using a value obtained from the {@link System#currentTimeMillis} 1257 * time base. 1258 * 1259 * <p>If this method is not called, this will be set to the time the object is built. 1260 * 1261 * @param creationTimestampMillis a creation timestamp in milliseconds. 1262 */ 1263 @CanIgnoreReturnValue setCreationTimestampMillis( @urrentTimeMillisLong long creationTimestampMillis)1264 public @NonNull BuilderType setCreationTimestampMillis( 1265 @CurrentTimeMillisLong long creationTimestampMillis) { 1266 mDocumentParcelBuilder.setCreationTimestampMillis(creationTimestampMillis); 1267 return mBuilderTypeInstance; 1268 } 1269 1270 /** 1271 * Sets the TTL (time-to-live) of the {@link GenericDocument}, in milliseconds. 1272 * 1273 * <p>The TTL is measured against {@link #getCreationTimestampMillis}. At the timestamp of 1274 * {@code creationTimestampMillis + ttlMillis}, measured in the {@link 1275 * System#currentTimeMillis} time base, the document will be auto-deleted. 1276 * 1277 * <p>The default value is 0, which means the document is permanent and won't be 1278 * auto-deleted until the app is uninstalled or {@link AppSearchSession#remove} is called. 1279 * 1280 * @param ttlMillis a non-negative duration in milliseconds. 1281 * @throws IllegalArgumentException if ttlMillis is negative. 1282 */ 1283 @CanIgnoreReturnValue setTtlMillis(long ttlMillis)1284 public @NonNull BuilderType setTtlMillis(long ttlMillis) { 1285 if (ttlMillis < 0) { 1286 throw new IllegalArgumentException("Document ttlMillis cannot be negative."); 1287 } 1288 mDocumentParcelBuilder.setTtlMillis(ttlMillis); 1289 return mBuilderTypeInstance; 1290 } 1291 1292 /** 1293 * Sets one or multiple {@code String} values for a property, replacing its previous values. 1294 * 1295 * @param name the name associated with the {@code values}. Must match the name for this 1296 * property as given in {@link AppSearchSchema.PropertyConfig#getName}. 1297 * @param values the {@code String} values of the property. 1298 * @throws IllegalArgumentException if no values are provided, or if a passed in {@code 1299 * String} is {@code null} or "". 1300 */ 1301 @CanIgnoreReturnValue setPropertyString( @onNull String name, @NonNull String... values)1302 public @NonNull BuilderType setPropertyString( 1303 @NonNull String name, @NonNull String... values) { 1304 Objects.requireNonNull(name); 1305 Objects.requireNonNull(values); 1306 validatePropertyName(name); 1307 for (int i = 0; i < values.length; i++) { 1308 if (values[i] == null) { 1309 throw new IllegalArgumentException("The String at " + i + " is null."); 1310 } 1311 } 1312 mDocumentParcelBuilder.putInPropertyMap(name, values); 1313 return mBuilderTypeInstance; 1314 } 1315 1316 /** 1317 * Sets one or multiple {@code boolean} values for a property, replacing its previous 1318 * values. 1319 * 1320 * @param name the name associated with the {@code values}. Must match the name for this 1321 * property as given in {@link AppSearchSchema.PropertyConfig#getName}. 1322 * @param values the {@code boolean} values of the property. 1323 * @throws IllegalArgumentException if the name is empty or {@code null}. 1324 */ 1325 @CanIgnoreReturnValue setPropertyBoolean( @onNull String name, @NonNull boolean... values)1326 public @NonNull BuilderType setPropertyBoolean( 1327 @NonNull String name, @NonNull boolean... values) { 1328 Objects.requireNonNull(name); 1329 Objects.requireNonNull(values); 1330 validatePropertyName(name); 1331 mDocumentParcelBuilder.putInPropertyMap(name, values); 1332 return mBuilderTypeInstance; 1333 } 1334 1335 /** 1336 * Sets one or multiple {@code long} values for a property, replacing its previous values. 1337 * 1338 * @param name the name associated with the {@code values}. Must match the name for this 1339 * property as given in {@link AppSearchSchema.PropertyConfig#getName}. 1340 * @param values the {@code long} values of the property. 1341 * @throws IllegalArgumentException if the name is empty or {@code null}. 1342 */ 1343 @CanIgnoreReturnValue setPropertyLong(@onNull String name, @NonNull long... values)1344 public @NonNull BuilderType setPropertyLong(@NonNull String name, @NonNull long... values) { 1345 Objects.requireNonNull(name); 1346 Objects.requireNonNull(values); 1347 validatePropertyName(name); 1348 mDocumentParcelBuilder.putInPropertyMap(name, values); 1349 return mBuilderTypeInstance; 1350 } 1351 1352 /** 1353 * Sets one or multiple {@code double} values for a property, replacing its previous values. 1354 * 1355 * @param name the name associated with the {@code values}. Must match the name for this 1356 * property as given in {@link AppSearchSchema.PropertyConfig#getName}. 1357 * @param values the {@code double} values of the property. 1358 * @throws IllegalArgumentException if the name is empty or {@code null}. 1359 */ 1360 @CanIgnoreReturnValue setPropertyDouble( @onNull String name, @NonNull double... values)1361 public @NonNull BuilderType setPropertyDouble( 1362 @NonNull String name, @NonNull double... values) { 1363 Objects.requireNonNull(name); 1364 Objects.requireNonNull(values); 1365 validatePropertyName(name); 1366 mDocumentParcelBuilder.putInPropertyMap(name, values); 1367 return mBuilderTypeInstance; 1368 } 1369 1370 /** 1371 * Sets one or multiple {@code byte[]} for a property, replacing its previous values. 1372 * 1373 * <p>For large byte data and lazy retrieval, see {@link #setPropertyBlobHandle}. 1374 * 1375 * @param name the name associated with the {@code values}. Must match the name for this 1376 * property as given in {@link AppSearchSchema.PropertyConfig#getName}. 1377 * @param values the {@code byte[]} of the property. 1378 * @throws IllegalArgumentException if no values are provided, or if a passed in {@code 1379 * byte[]} is {@code null}, or if name is empty. 1380 */ 1381 @CanIgnoreReturnValue setPropertyBytes( @onNull String name, @NonNull byte[]... values)1382 public @NonNull BuilderType setPropertyBytes( 1383 @NonNull String name, @NonNull byte[]... values) { 1384 Objects.requireNonNull(name); 1385 Objects.requireNonNull(values); 1386 validatePropertyName(name); 1387 for (int i = 0; i < values.length; i++) { 1388 if (values[i] == null) { 1389 throw new IllegalArgumentException("The byte[] at " + i + " is null."); 1390 } 1391 } 1392 mDocumentParcelBuilder.putInPropertyMap(name, values); 1393 return mBuilderTypeInstance; 1394 } 1395 1396 /** 1397 * Sets one or multiple {@link GenericDocument} values for a property, replacing its 1398 * previous values. 1399 * 1400 * @param name the name associated with the {@code values}. Must match the name for this 1401 * property as given in {@link AppSearchSchema.PropertyConfig#getName}. 1402 * @param values the {@link GenericDocument} values of the property. 1403 * @throws IllegalArgumentException if no values are provided, or if a passed in {@link 1404 * GenericDocument} is {@code null}, or if name is empty. 1405 */ 1406 @CanIgnoreReturnValue setPropertyDocument( @onNull String name, @NonNull GenericDocument... values)1407 public @NonNull BuilderType setPropertyDocument( 1408 @NonNull String name, @NonNull GenericDocument... values) { 1409 Objects.requireNonNull(name); 1410 Objects.requireNonNull(values); 1411 validatePropertyName(name); 1412 GenericDocumentParcel[] documentParcels = new GenericDocumentParcel[values.length]; 1413 for (int i = 0; i < values.length; i++) { 1414 if (values[i] == null) { 1415 throw new IllegalArgumentException("The document at " + i + " is null."); 1416 } 1417 documentParcels[i] = values[i].getDocumentParcel(); 1418 } 1419 mDocumentParcelBuilder.putInPropertyMap(name, documentParcels); 1420 return mBuilderTypeInstance; 1421 } 1422 1423 /** 1424 * Sets one or multiple {@code EmbeddingVector} values for a property, replacing its 1425 * previous values. 1426 * 1427 * @param name the name associated with the {@code values}. Must match the name for this 1428 * property as given in {@link AppSearchSchema.PropertyConfig#getName}. 1429 * @param values the {@code EmbeddingVector} values of the property. 1430 * @throws IllegalArgumentException if the name is empty or {@code null}. 1431 */ 1432 @CanIgnoreReturnValue 1433 @FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_EMBEDDING_PROPERTY_CONFIG) setPropertyEmbedding( @onNull String name, @NonNull EmbeddingVector... values)1434 public @NonNull BuilderType setPropertyEmbedding( 1435 @NonNull String name, @NonNull EmbeddingVector... values) { 1436 Objects.requireNonNull(name); 1437 Objects.requireNonNull(values); 1438 validatePropertyName(name); 1439 for (int i = 0; i < values.length; i++) { 1440 if (values[i] == null) { 1441 throw new IllegalArgumentException("The EmbeddingVector at " + i + " is null."); 1442 } 1443 } 1444 mDocumentParcelBuilder.putInPropertyMap(name, values); 1445 return mBuilderTypeInstance; 1446 } 1447 1448 /** 1449 * Sets one or multiple {@link AppSearchBlobHandle} values for a property, replacing its 1450 * previous values. 1451 * 1452 * <p>{@link AppSearchBlobHandle} is a pointer to a blob of data. 1453 * 1454 * <p>Store large byte via the {@link android.os.ParcelFileDescriptor} returned from {@link 1455 * AppSearchSession#openBlobForWrite}. Once the blob data is committed via {@link 1456 * AppSearchSession#commitBlob}, the blob is retrievable via {@link 1457 * AppSearchSession#openBlobForRead}. 1458 * 1459 * @param name the name associated with the {@code values}. Must match the name for this 1460 * property as given in {@link AppSearchSchema.PropertyConfig#getName}. 1461 * @param values the {@link AppSearchBlobHandle} values of the property. 1462 * @throws IllegalArgumentException if the name is empty or {@code null}. 1463 */ 1464 @CanIgnoreReturnValue 1465 @FlaggedApi(Flags.FLAG_ENABLE_BLOB_STORE) setPropertyBlobHandle( @onNull String name, @NonNull AppSearchBlobHandle... values)1466 public @NonNull BuilderType setPropertyBlobHandle( 1467 @NonNull String name, @NonNull AppSearchBlobHandle... values) { 1468 Objects.requireNonNull(name); 1469 Objects.requireNonNull(values); 1470 validatePropertyName(name); 1471 for (int i = 0; i < values.length; i++) { 1472 if (values[i] == null) { 1473 throw new IllegalArgumentException("The BlobHandle at " + i + " is null."); 1474 } 1475 } 1476 mDocumentParcelBuilder.putInPropertyMap(name, values); 1477 return mBuilderTypeInstance; 1478 } 1479 1480 /** 1481 * Clears the value for the property with the given name. 1482 * 1483 * <p>Note that this method does not support property paths. 1484 * 1485 * <p>You should check for the existence of the property in {@link #getPropertyNames} if you 1486 * need to make sure the property being cleared actually exists. 1487 * 1488 * <p>If the string passed is an invalid or nonexistent property, no error message or 1489 * behavior will be observed. 1490 * 1491 * @param name The name of the property to clear. 1492 */ 1493 @FlaggedApi(Flags.FLAG_ENABLE_GENERIC_DOCUMENT_BUILDER_HIDDEN_METHODS) 1494 @CanIgnoreReturnValue clearProperty(@onNull String name)1495 public @NonNull BuilderType clearProperty(@NonNull String name) { 1496 Objects.requireNonNull(name); 1497 mDocumentParcelBuilder.clearProperty(name); 1498 return mBuilderTypeInstance; 1499 } 1500 1501 /** Builds the {@link GenericDocument} object. */ build()1502 public @NonNull GenericDocument build() { 1503 return new GenericDocument(mDocumentParcelBuilder.build()); 1504 } 1505 1506 /** Method to ensure property names are not blank */ validatePropertyName(@onNull String name)1507 private void validatePropertyName(@NonNull String name) { 1508 if (name.isEmpty()) { 1509 throw new IllegalArgumentException("Property name cannot be blank."); 1510 } 1511 } 1512 } 1513 } 1514