• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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&lt;Bundle&gt;, 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