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