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