• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2023 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.safeparcel;
18 
19 import android.annotation.CurrentTimeMillisLong;
20 import android.annotation.SuppressLint;
21 import android.app.appsearch.AppSearchBlobHandle;
22 import android.app.appsearch.AppSearchSchema;
23 import android.app.appsearch.AppSearchSession;
24 import android.app.appsearch.EmbeddingVector;
25 import android.app.appsearch.GenericDocument;
26 import android.app.appsearch.annotation.CanIgnoreReturnValue;
27 import android.os.Parcel;
28 import android.os.Parcelable;
29 import android.util.ArrayMap;
30 
31 import org.jspecify.annotations.NonNull;
32 import org.jspecify.annotations.Nullable;
33 
34 import java.util.ArrayList;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.Objects;
38 import java.util.Set;
39 
40 /**
41  * Holds data for a {@link GenericDocument}.
42  *
43  * @hide
44  */
45 @SafeParcelable.Class(creator = "GenericDocumentParcelCreator")
46 // This won't be used to send data over binder, and we have to use Parcelable for code sync purpose.
47 @SuppressLint("BanParcelableUsage")
48 public final class GenericDocumentParcel extends AbstractSafeParcelable implements Parcelable {
49     public static final Parcelable.@NonNull Creator<GenericDocumentParcel> CREATOR =
50             new GenericDocumentParcelCreator();
51 
52     /** The default score of document. */
53     private static final int DEFAULT_SCORE = 0;
54 
55     /** The default time-to-live in millisecond of a document, which is infinity. */
56     private static final long DEFAULT_TTL_MILLIS = 0L;
57 
58     /** Default but invalid value for {@code mCreationTimestampMillis}. */
59     private static final long INVALID_CREATION_TIMESTAMP_MILLIS = -1L;
60 
61     @Field(id = 1, getter = "getNamespace")
62     private final @NonNull String mNamespace;
63 
64     @Field(id = 2, getter = "getId")
65     private final @NonNull String mId;
66 
67     @Field(id = 3, getter = "getSchemaType")
68     private final @NonNull String mSchemaType;
69 
70     @Field(id = 4, getter = "getCreationTimestampMillis")
71     private final long mCreationTimestampMillis;
72 
73     @Field(id = 5, getter = "getTtlMillis")
74     private final long mTtlMillis;
75 
76     @Field(id = 6, getter = "getScore")
77     private final int mScore;
78 
79     /**
80      * Contains all properties in {@link GenericDocument} in a list.
81      *
82      * <p>Unfortunately SafeParcelable doesn't support map type so we have to use a list here.
83      */
84     @Field(id = 7, getter = "getProperties")
85     private final @NonNull List<PropertyParcel> mProperties;
86 
87     /** Contains all parent properties for this {@link GenericDocument} in a list. */
88     @Field(id = 8, getter = "getParentTypes")
89     private final @Nullable List<String> mParentTypes;
90 
91     /**
92      * Contains all properties in {@link GenericDocument} to support getting properties via name
93      *
94      * <p>This map is created for quick looking up property by name.
95      */
96     private final @NonNull Map<String, PropertyParcel> mPropertyMap;
97 
98     private @Nullable Integer mHashCode;
99 
100     /**
101      * The constructor taking the property list, and create map internally from this list.
102      *
103      * <p>This will be used in createFromParcel, so creating the property map can not be avoided in
104      * this constructor.
105      */
106     @Constructor
GenericDocumentParcel( @aramid = 1) @onNull String namespace, @Param(id = 2) @NonNull String id, @Param(id = 3) @NonNull String schemaType, @Param(id = 4) long creationTimestampMillis, @Param(id = 5) long ttlMillis, @Param(id = 6) int score, @Param(id = 7) @NonNull List<PropertyParcel> properties, @Param(id = 8) @Nullable List<String> parentTypes)107     GenericDocumentParcel(
108             @Param(id = 1) @NonNull String namespace,
109             @Param(id = 2) @NonNull String id,
110             @Param(id = 3) @NonNull String schemaType,
111             @Param(id = 4) long creationTimestampMillis,
112             @Param(id = 5) long ttlMillis,
113             @Param(id = 6) int score,
114             @Param(id = 7) @NonNull List<PropertyParcel> properties,
115             @Param(id = 8) @Nullable List<String> parentTypes) {
116         this(
117                 namespace,
118                 id,
119                 schemaType,
120                 creationTimestampMillis,
121                 ttlMillis,
122                 score,
123                 properties,
124                 createPropertyMapFromPropertyArray(properties),
125                 parentTypes);
126     }
127 
128     /**
129      * A constructor taking both property list and property map.
130      *
131      * <p>Caller needs to make sure property list and property map matches(map is generated from
132      * list, or list generated from map).
133      */
GenericDocumentParcel( @onNull String namespace, @NonNull String id, @NonNull String schemaType, long creationTimestampMillis, long ttlMillis, int score, @NonNull List<PropertyParcel> properties, @NonNull Map<String, PropertyParcel> propertyMap, @Nullable List<String> parentTypes)134     GenericDocumentParcel(
135             @NonNull String namespace,
136             @NonNull String id,
137             @NonNull String schemaType,
138             long creationTimestampMillis,
139             long ttlMillis,
140             int score,
141             @NonNull List<PropertyParcel> properties,
142             @NonNull Map<String, PropertyParcel> propertyMap,
143             @Nullable List<String> parentTypes) {
144         mNamespace = Objects.requireNonNull(namespace);
145         mId = Objects.requireNonNull(id);
146         mSchemaType = Objects.requireNonNull(schemaType);
147         mCreationTimestampMillis = creationTimestampMillis;
148         mTtlMillis = ttlMillis;
149         mScore = score;
150         mProperties = Objects.requireNonNull(properties);
151         mPropertyMap = Objects.requireNonNull(propertyMap);
152         mParentTypes = parentTypes;
153     }
154 
155     /** Returns the {@link GenericDocumentParcel} object from the given {@link GenericDocument}. */
fromGenericDocument( @onNull GenericDocument genericDocument)156     public static @NonNull GenericDocumentParcel fromGenericDocument(
157             @NonNull GenericDocument genericDocument) {
158         Objects.requireNonNull(genericDocument);
159         return genericDocument.getDocumentParcel();
160     }
161 
createPropertyMapFromPropertyArray( @onNull List<PropertyParcel> properties)162     private static Map<String, PropertyParcel> createPropertyMapFromPropertyArray(
163             @NonNull List<PropertyParcel> properties) {
164         Objects.requireNonNull(properties);
165         Map<String, PropertyParcel> propertyMap = new ArrayMap<>(properties.size());
166         for (int i = 0; i < properties.size(); ++i) {
167             PropertyParcel property = properties.get(i);
168             propertyMap.put(property.getPropertyName(), property);
169         }
170         return propertyMap;
171     }
172 
173     /** Returns the unique identifier of the {@link GenericDocument}. */
getId()174     public @NonNull String getId() {
175         return mId;
176     }
177 
178     /** Returns the namespace of the {@link GenericDocument}. */
getNamespace()179     public @NonNull String getNamespace() {
180         return mNamespace;
181     }
182 
183     /** Returns the {@link AppSearchSchema} type of the {@link GenericDocument}. */
getSchemaType()184     public @NonNull String getSchemaType() {
185         return mSchemaType;
186     }
187 
188     /** Returns the creation timestamp of the {@link GenericDocument}, in milliseconds. */
189     @CurrentTimeMillisLong
getCreationTimestampMillis()190     public long getCreationTimestampMillis() {
191         return mCreationTimestampMillis;
192     }
193 
194     /** Returns the TTL (time-to-live) of the {@link GenericDocument}, in milliseconds. */
getTtlMillis()195     public long getTtlMillis() {
196         return mTtlMillis;
197     }
198 
199     /** Returns the score of the {@link GenericDocument}. */
getScore()200     public int getScore() {
201         return mScore;
202     }
203 
204     /** Returns the names of all properties defined in this document. */
getPropertyNames()205     public @NonNull Set<String> getPropertyNames() {
206         return mPropertyMap.keySet();
207     }
208 
209     /** Returns all the properties the document has. */
getProperties()210     public @NonNull List<PropertyParcel> getProperties() {
211         return mProperties;
212     }
213 
214     /** Returns the property map the document has. */
getPropertyMap()215     public @NonNull Map<String, PropertyParcel> getPropertyMap() {
216         return mPropertyMap;
217     }
218 
219     /** Returns the list of parent types for the {@link GenericDocument}. */
getParentTypes()220     public @Nullable List<String> getParentTypes() {
221         return mParentTypes;
222     }
223 
224     @Override
equals(@ullable Object other)225     public boolean equals(@Nullable Object other) {
226         if (this == other) {
227             return true;
228         }
229         if (!(other instanceof GenericDocumentParcel)) {
230             return false;
231         }
232         GenericDocumentParcel otherDocument = (GenericDocumentParcel) other;
233         return mNamespace.equals(otherDocument.mNamespace)
234                 && mId.equals(otherDocument.mId)
235                 && mSchemaType.equals(otherDocument.mSchemaType)
236                 && mTtlMillis == otherDocument.mTtlMillis
237                 && mCreationTimestampMillis == otherDocument.mCreationTimestampMillis
238                 && mScore == otherDocument.mScore
239                 && Objects.equals(mProperties, otherDocument.mProperties)
240                 && Objects.equals(mPropertyMap, otherDocument.mPropertyMap)
241                 && Objects.equals(mParentTypes, otherDocument.mParentTypes);
242     }
243 
244     @Override
hashCode()245     public int hashCode() {
246         if (mHashCode == null) {
247             mHashCode =
248                     Objects.hash(
249                             mNamespace,
250                             mId,
251                             mSchemaType,
252                             mTtlMillis,
253                             mScore,
254                             mCreationTimestampMillis,
255                             Objects.hashCode(mProperties),
256                             Objects.hashCode(mPropertyMap),
257                             Objects.hashCode(mParentTypes));
258         }
259         return mHashCode;
260     }
261 
262     @Override
writeToParcel(@onNull Parcel dest, int flags)263     public void writeToParcel(@NonNull Parcel dest, int flags) {
264         GenericDocumentParcelCreator.writeToParcel(this, dest, flags);
265     }
266 
267     /** The builder class for {@link GenericDocumentParcel}. */
268     public static final class Builder {
269         private String mNamespace;
270         private String mId;
271         private String mSchemaType;
272         private long mCreationTimestampMillis;
273         private long mTtlMillis;
274         private int mScore;
275         private Map<String, PropertyParcel> mPropertyMap;
276         private @Nullable List<String> mParentTypes;
277 
278         /**
279          * Creates a new {@link GenericDocumentParcel.Builder}.
280          *
281          * <p>Document IDs are unique within a namespace.
282          *
283          * <p>The number of namespaces per app should be kept small for efficiency reasons.
284          */
Builder(@onNull String namespace, @NonNull String id, @NonNull String schemaType)285         public Builder(@NonNull String namespace, @NonNull String id, @NonNull String schemaType) {
286             mNamespace = Objects.requireNonNull(namespace);
287             mId = Objects.requireNonNull(id);
288             mSchemaType = Objects.requireNonNull(schemaType);
289             mCreationTimestampMillis = INVALID_CREATION_TIMESTAMP_MILLIS;
290             mTtlMillis = DEFAULT_TTL_MILLIS;
291             mScore = DEFAULT_SCORE;
292             mPropertyMap = new ArrayMap<>();
293         }
294 
295         /**
296          * Creates a new {@link GenericDocumentParcel.Builder} from the given {@link
297          * GenericDocumentParcel}.
298          */
Builder(@onNull GenericDocumentParcel documentSafeParcel)299         public Builder(@NonNull GenericDocumentParcel documentSafeParcel) {
300             Objects.requireNonNull(documentSafeParcel);
301 
302             mNamespace = documentSafeParcel.mNamespace;
303             mId = documentSafeParcel.mId;
304             mSchemaType = documentSafeParcel.mSchemaType;
305             mCreationTimestampMillis = documentSafeParcel.mCreationTimestampMillis;
306             mTtlMillis = documentSafeParcel.mTtlMillis;
307             mScore = documentSafeParcel.mScore;
308 
309             // Create a shallow copy of the map so we won't change the original one.
310             Map<String, PropertyParcel> propertyMap = documentSafeParcel.mPropertyMap;
311             mPropertyMap = new ArrayMap<>(propertyMap.size());
312             for (PropertyParcel value : propertyMap.values()) {
313                 mPropertyMap.put(value.getPropertyName(), value);
314             }
315 
316             // We don't need to create a shallow copy here, as in the setter for ParentTypes we
317             // will create a new list anyway.
318             mParentTypes = documentSafeParcel.mParentTypes;
319         }
320 
321         /**
322          * Sets the app-defined namespace this document resides in, changing the value provided in
323          * the constructor. No special values are reserved or understood by the infrastructure.
324          *
325          * <p>Document IDs are unique within a namespace.
326          *
327          * <p>The number of namespaces per app should be kept small for efficiency reasons.
328          */
329         @CanIgnoreReturnValue
setNamespace(@onNull String namespace)330         public @NonNull Builder setNamespace(@NonNull String namespace) {
331             Objects.requireNonNull(namespace);
332             mNamespace = namespace;
333             return this;
334         }
335 
336         /**
337          * Sets the ID of this document, changing the value provided in the constructor. No special
338          * values are reserved or understood by the infrastructure.
339          *
340          * <p>Document IDs are unique within a namespace.
341          */
342         @CanIgnoreReturnValue
setId(@onNull String id)343         public @NonNull Builder setId(@NonNull String id) {
344             Objects.requireNonNull(id);
345             mId = id;
346             return this;
347         }
348 
349         /**
350          * Sets the schema type of this document, changing the value provided in the constructor.
351          *
352          * <p>To successfully index a document, the schema type must match the name of an {@link
353          * AppSearchSchema} object previously provided to {@link AppSearchSession#setSchema}.
354          */
355         @CanIgnoreReturnValue
setSchemaType(@onNull String schemaType)356         public @NonNull Builder setSchemaType(@NonNull String schemaType) {
357             Objects.requireNonNull(schemaType);
358             mSchemaType = schemaType;
359             return this;
360         }
361 
362         /** Sets the score of the parent {@link GenericDocument}. */
363         @CanIgnoreReturnValue
setScore(int score)364         public @NonNull Builder setScore(int score) {
365             mScore = score;
366             return this;
367         }
368 
369         /**
370          * Sets the creation timestamp of the {@link GenericDocument}, in milliseconds.
371          *
372          * <p>This should be set using a value obtained from the {@link System#currentTimeMillis}
373          * time base.
374          *
375          * <p>If this method is not called, this will be set to the time the object is built.
376          *
377          * @param creationTimestampMillis a creation timestamp in milliseconds.
378          */
379         @CanIgnoreReturnValue
setCreationTimestampMillis( @urrentTimeMillisLong long creationTimestampMillis)380         public @NonNull Builder setCreationTimestampMillis(
381                 @CurrentTimeMillisLong long creationTimestampMillis) {
382             mCreationTimestampMillis = creationTimestampMillis;
383             return this;
384         }
385 
386         /**
387          * Sets the TTL (time-to-live) of the {@link GenericDocument}, in milliseconds.
388          *
389          * <p>The TTL is measured against {@link #getCreationTimestampMillis}. At the timestamp of
390          * {@code creationTimestampMillis + ttlMillis}, measured in the {@link
391          * System#currentTimeMillis} time base, the document will be auto-deleted.
392          *
393          * <p>The default value is 0, which means the document is permanent and won't be
394          * auto-deleted until the app is uninstalled or {@link AppSearchSession#remove} is called.
395          *
396          * @param ttlMillis a non-negative duration in milliseconds.
397          * @throws IllegalArgumentException if ttlMillis is negative.
398          */
399         @CanIgnoreReturnValue
setTtlMillis(long ttlMillis)400         public @NonNull Builder setTtlMillis(long ttlMillis) {
401             if (ttlMillis < 0) {
402                 throw new IllegalArgumentException("Document ttlMillis cannot be negative.");
403             }
404             mTtlMillis = ttlMillis;
405             return this;
406         }
407 
408         /**
409          * Sets the list of parent types of the {@link GenericDocument}'s type.
410          *
411          * <p>Child types must appear before parent types in the list.
412          */
413         @CanIgnoreReturnValue
setParentTypes(@ullable List<String> parentTypes)414         public @NonNull Builder setParentTypes(@Nullable List<String> parentTypes) {
415             if (parentTypes == null) {
416                 mParentTypes = null;
417             } else {
418                 mParentTypes = new ArrayList<>(parentTypes);
419             }
420             return this;
421         }
422 
423         /**
424          * Clears the value for the property with the given name.
425          *
426          * <p>Note that this method does not support property paths.
427          *
428          * @param name The name of the property to clear.
429          */
430         @CanIgnoreReturnValue
clearProperty(@onNull String name)431         public @NonNull Builder clearProperty(@NonNull String name) {
432             Objects.requireNonNull(name);
433             mPropertyMap.remove(name);
434             return this;
435         }
436 
437         /** Puts an array of {@link String} in the property map. */
438         @CanIgnoreReturnValue
putInPropertyMap(@onNull String name, String @NonNull [] values)439         public @NonNull Builder putInPropertyMap(@NonNull String name, String @NonNull [] values)
440                 throws IllegalArgumentException {
441             putInPropertyMap(
442                     name, new PropertyParcel.Builder(name).setStringValues(values).build());
443             return this;
444         }
445 
446         /** Puts an array of boolean in the property map. */
447         @CanIgnoreReturnValue
putInPropertyMap(@onNull String name, boolean @NonNull [] values)448         public @NonNull Builder putInPropertyMap(@NonNull String name, boolean @NonNull [] values) {
449             putInPropertyMap(
450                     name, new PropertyParcel.Builder(name).setBooleanValues(values).build());
451             return this;
452         }
453 
454         /** Puts an array of double in the property map. */
455         @CanIgnoreReturnValue
putInPropertyMap(@onNull String name, double @NonNull [] values)456         public @NonNull Builder putInPropertyMap(@NonNull String name, double @NonNull [] values) {
457             putInPropertyMap(
458                     name, new PropertyParcel.Builder(name).setDoubleValues(values).build());
459             return this;
460         }
461 
462         /** Puts an array of long in the property map. */
463         @CanIgnoreReturnValue
putInPropertyMap(@onNull String name, long @NonNull [] values)464         public @NonNull Builder putInPropertyMap(@NonNull String name, long @NonNull [] values) {
465             putInPropertyMap(name, new PropertyParcel.Builder(name).setLongValues(values).build());
466             return this;
467         }
468 
469         /** Converts and saves a byte[][] into {@link #mProperties}. */
470         @CanIgnoreReturnValue
putInPropertyMap(@onNull String name, byte @NonNull [][] values)471         public @NonNull Builder putInPropertyMap(@NonNull String name, byte @NonNull [][] values) {
472             putInPropertyMap(name, new PropertyParcel.Builder(name).setBytesValues(values).build());
473             return this;
474         }
475 
476         /** Puts an array of {@link GenericDocumentParcel} in the property map. */
477         @CanIgnoreReturnValue
putInPropertyMap( @onNull String name, GenericDocumentParcel @NonNull [] values)478         public @NonNull Builder putInPropertyMap(
479                 @NonNull String name, GenericDocumentParcel @NonNull [] values) {
480             putInPropertyMap(
481                     name, new PropertyParcel.Builder(name).setDocumentValues(values).build());
482             return this;
483         }
484 
485         /** Puts an array of {@link EmbeddingVector} in the property map. */
486         @CanIgnoreReturnValue
putInPropertyMap( @onNull String name, EmbeddingVector @NonNull [] values)487         public @NonNull Builder putInPropertyMap(
488                 @NonNull String name, EmbeddingVector @NonNull [] values) {
489             putInPropertyMap(
490                     name, new PropertyParcel.Builder(name).setEmbeddingValues(values).build());
491             return this;
492         }
493 
494         /** Puts an array of {@link AppSearchBlobHandle} in the property map. */
495         @CanIgnoreReturnValue
putInPropertyMap( @onNull String name, AppSearchBlobHandle @NonNull [] values)496         public @NonNull Builder putInPropertyMap(
497                 @NonNull String name, AppSearchBlobHandle @NonNull [] values) {
498             Objects.requireNonNull(values);
499             putInPropertyMap(
500                     name, new PropertyParcel.Builder(name).setBlobHandleValues(values).build());
501             return this;
502         }
503 
504         /** Directly puts a {@link PropertyParcel} in the property map. */
505         @CanIgnoreReturnValue
putInPropertyMap( @onNull String name, @NonNull PropertyParcel value)506         public @NonNull Builder putInPropertyMap(
507                 @NonNull String name, @NonNull PropertyParcel value) {
508             Objects.requireNonNull(value);
509             mPropertyMap.put(name, value);
510             return this;
511         }
512 
513         /** Builds the {@link GenericDocument} object. */
build()514         public @NonNull GenericDocumentParcel build() {
515             // Set current timestamp for creation timestamp by default.
516             if (mCreationTimestampMillis == INVALID_CREATION_TIMESTAMP_MILLIS) {
517                 mCreationTimestampMillis = System.currentTimeMillis();
518             }
519             return new GenericDocumentParcel(
520                     mNamespace,
521                     mId,
522                     mSchemaType,
523                     mCreationTimestampMillis,
524                     mTtlMillis,
525                     mScore,
526                     new ArrayList<>(mPropertyMap.values()),
527                     mParentTypes);
528         }
529     }
530 }
531