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.IntDef; 20 import android.annotation.IntRange; 21 import android.annotation.NonNull; 22 import android.annotation.SuppressLint; 23 import android.app.appsearch.annotation.CanIgnoreReturnValue; 24 import android.util.ArrayMap; 25 import android.util.ArraySet; 26 27 import com.android.internal.util.Preconditions; 28 29 import java.lang.annotation.Retention; 30 import java.lang.annotation.RetentionPolicy; 31 import java.util.Arrays; 32 import java.util.Collection; 33 import java.util.Collections; 34 import java.util.Map; 35 import java.util.Objects; 36 import java.util.Set; 37 38 /** 39 * Encapsulates a request to update the schema of an {@link AppSearchSession} database. 40 * 41 * <p>The schema is composed of a collection of {@link AppSearchSchema} objects, each of which 42 * defines a unique type of data. 43 * 44 * <p>The first call to SetSchemaRequest will set the provided schema and store it within the {@link 45 * AppSearchSession} database. 46 * 47 * <p>Subsequent calls will compare the provided schema to the previously saved schema, to determine 48 * how to treat existing documents. 49 * 50 * <p>The following types of schema modifications are always safe and are made without deleting any 51 * existing documents: 52 * 53 * <ul> 54 * <li>Addition of new {@link AppSearchSchema} types 55 * <li>Addition of new properties to an existing {@link AppSearchSchema} type 56 * <li>Changing the cardinality of a property to be less restrictive 57 * </ul> 58 * 59 * <p>The following types of schema changes are not backwards compatible: 60 * 61 * <ul> 62 * <li>Removal of an existing {@link AppSearchSchema} type 63 * <li>Removal of a property from an existing {@link AppSearchSchema} type 64 * <li>Changing the data type of an existing property 65 * <li>Changing the cardinality of a property to be more restrictive 66 * </ul> 67 * 68 * <p>Providing a schema with incompatible changes, will throw an {@link 69 * android.app.appsearch.exceptions.AppSearchException}, with a message describing the 70 * incompatibility. As a result, the previously set schema will remain unchanged. 71 * 72 * <p>Backward incompatible changes can be made by : 73 * 74 * <ul> 75 * <li>setting {@link SetSchemaRequest.Builder#setForceOverride} method to {@code true}. This 76 * deletes all documents that are incompatible with the new schema. The new schema is then 77 * saved and persisted to disk. 78 * <li>Add a {@link Migrator} for each incompatible type and make no deletion. The migrator will 79 * migrate documents from its old schema version to the new version. Migrated types will be 80 * set into both {@link SetSchemaResponse#getIncompatibleTypes()} and {@link 81 * SetSchemaResponse#getMigratedTypes()}. See the migration section below. 82 * </ul> 83 * 84 * @see AppSearchSession#setSchema 85 * @see Migrator 86 */ 87 public final class SetSchemaRequest { 88 89 /** 90 * List of Android Permission are supported in {@link 91 * SetSchemaRequest.Builder#addRequiredPermissionsForSchemaTypeVisibility} 92 * 93 * @see android.Manifest.permission 94 * @hide 95 */ 96 @IntDef( 97 value = { 98 READ_SMS, 99 READ_CALENDAR, 100 READ_CONTACTS, 101 READ_EXTERNAL_STORAGE, 102 READ_HOME_APP_SEARCH_DATA, 103 READ_ASSISTANT_APP_SEARCH_DATA, 104 }) 105 @Retention(RetentionPolicy.SOURCE) 106 public @interface AppSearchSupportedPermission {} 107 108 /** 109 * The {@link android.Manifest.permission#READ_SMS} AppSearch supported in {@link 110 * SetSchemaRequest.Builder#addRequiredPermissionsForSchemaTypeVisibility} 111 */ 112 public static final int READ_SMS = 1; 113 114 /** 115 * The {@link android.Manifest.permission#READ_CALENDAR} AppSearch supported in {@link 116 * SetSchemaRequest.Builder#addRequiredPermissionsForSchemaTypeVisibility} 117 */ 118 public static final int READ_CALENDAR = 2; 119 120 /** 121 * The {@link android.Manifest.permission#READ_CONTACTS} AppSearch supported in {@link 122 * SetSchemaRequest.Builder#addRequiredPermissionsForSchemaTypeVisibility} 123 */ 124 public static final int READ_CONTACTS = 3; 125 126 /** 127 * The {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} AppSearch supported in {@link 128 * SetSchemaRequest.Builder#addRequiredPermissionsForSchemaTypeVisibility} 129 */ 130 public static final int READ_EXTERNAL_STORAGE = 4; 131 132 /** 133 * The {@link android.Manifest.permission#READ_HOME_APP_SEARCH_DATA} AppSearch supported in 134 * {@link SetSchemaRequest.Builder#addRequiredPermissionsForSchemaTypeVisibility} 135 */ 136 public static final int READ_HOME_APP_SEARCH_DATA = 5; 137 138 /** 139 * The {@link android.Manifest.permission#READ_ASSISTANT_APP_SEARCH_DATA} AppSearch supported in 140 * {@link SetSchemaRequest.Builder#addRequiredPermissionsForSchemaTypeVisibility} 141 */ 142 public static final int READ_ASSISTANT_APP_SEARCH_DATA = 6; 143 144 private final Set<AppSearchSchema> mSchemas; 145 private final Set<String> mSchemasNotDisplayedBySystem; 146 private final Map<String, Set<PackageIdentifier>> mSchemasVisibleToPackages; 147 private final Map<String, Set<Set<Integer>>> mSchemasVisibleToPermissions; 148 private final Map<String, Migrator> mMigrators; 149 private final boolean mForceOverride; 150 private final int mVersion; 151 SetSchemaRequest( @onNull Set<AppSearchSchema> schemas, @NonNull Set<String> schemasNotDisplayedBySystem, @NonNull Map<String, Set<PackageIdentifier>> schemasVisibleToPackages, @NonNull Map<String, Set<Set<Integer>>> schemasVisibleToPermissions, @NonNull Map<String, Migrator> migrators, boolean forceOverride, int version)152 SetSchemaRequest( 153 @NonNull Set<AppSearchSchema> schemas, 154 @NonNull Set<String> schemasNotDisplayedBySystem, 155 @NonNull Map<String, Set<PackageIdentifier>> schemasVisibleToPackages, 156 @NonNull Map<String, Set<Set<Integer>>> schemasVisibleToPermissions, 157 @NonNull Map<String, Migrator> migrators, 158 boolean forceOverride, 159 int version) { 160 mSchemas = Objects.requireNonNull(schemas); 161 mSchemasNotDisplayedBySystem = Objects.requireNonNull(schemasNotDisplayedBySystem); 162 mSchemasVisibleToPackages = Objects.requireNonNull(schemasVisibleToPackages); 163 mSchemasVisibleToPermissions = Objects.requireNonNull(schemasVisibleToPermissions); 164 mMigrators = Objects.requireNonNull(migrators); 165 mForceOverride = forceOverride; 166 mVersion = version; 167 } 168 169 /** Returns the {@link AppSearchSchema} types that are part of this request. */ 170 @NonNull getSchemas()171 public Set<AppSearchSchema> getSchemas() { 172 return Collections.unmodifiableSet(mSchemas); 173 } 174 175 /** 176 * Returns all the schema types that are opted out of being displayed and visible on any system 177 * UI surface. 178 */ 179 @NonNull getSchemasNotDisplayedBySystem()180 public Set<String> getSchemasNotDisplayedBySystem() { 181 return Collections.unmodifiableSet(mSchemasNotDisplayedBySystem); 182 } 183 184 /** 185 * Returns a mapping of schema types to the set of packages that have access to that schema 186 * type. 187 * 188 * <p>It’s inefficient to call this method repeatedly. 189 */ 190 @NonNull getSchemasVisibleToPackages()191 public Map<String, Set<PackageIdentifier>> getSchemasVisibleToPackages() { 192 Map<String, Set<PackageIdentifier>> copy = new ArrayMap<>(); 193 for (Map.Entry<String, Set<PackageIdentifier>> entry : 194 mSchemasVisibleToPackages.entrySet()) { 195 copy.put(entry.getKey(), new ArraySet<>(entry.getValue())); 196 } 197 return copy; 198 } 199 200 /** 201 * Returns a mapping of schema types to the Map of {@link android.Manifest.permission} 202 * combinations that querier must hold to access that schema type. 203 * 204 * <p>The querier could read the {@link GenericDocument} objects under the {@code schemaType} if 205 * they holds ALL required permissions of ANY of the individual value sets. 206 * 207 * <p>For example, if the Map contains {@code {% verbatim %}{{permissionA, PermissionB}, 208 * {PermissionC, PermissionD}, {PermissionE}}{% endverbatim %}}. 209 * 210 * <ul> 211 * <li>A querier holds both PermissionA and PermissionB has access. 212 * <li>A querier holds both PermissionC and PermissionD has access. 213 * <li>A querier holds only PermissionE has access. 214 * <li>A querier holds both PermissionA and PermissionE has access. 215 * <li>A querier holds only PermissionA doesn't have access. 216 * <li>A querier holds both PermissionA and PermissionC doesn't have access. 217 * </ul> 218 * 219 * <p>It’s inefficient to call this method repeatedly. 220 * 221 * @return The map contains schema type and all combinations of required permission for querier 222 * to access it. The supported Permission are {@link SetSchemaRequest#READ_SMS}, {@link 223 * SetSchemaRequest#READ_CALENDAR}, {@link SetSchemaRequest#READ_CONTACTS}, {@link 224 * SetSchemaRequest#READ_EXTERNAL_STORAGE}, {@link 225 * SetSchemaRequest#READ_HOME_APP_SEARCH_DATA} and {@link 226 * SetSchemaRequest#READ_ASSISTANT_APP_SEARCH_DATA}. 227 */ 228 @NonNull getRequiredPermissionsForSchemaTypeVisibility()229 public Map<String, Set<Set<Integer>>> getRequiredPermissionsForSchemaTypeVisibility() { 230 return deepCopy(mSchemasVisibleToPermissions); 231 } 232 233 /** 234 * Returns the map of {@link Migrator}, the key will be the schema type of the {@link Migrator} 235 * associated with. 236 */ 237 @NonNull getMigrators()238 public Map<String, Migrator> getMigrators() { 239 return Collections.unmodifiableMap(mMigrators); 240 } 241 242 /** 243 * Returns a mapping of {@link AppSearchSchema} types to the set of packages that have access to 244 * that schema type. 245 * 246 * <p>A more efficient version of {@link #getSchemasVisibleToPackages}, but it returns a 247 * modifiable map. This is not meant to be unhidden and should only be used by internal classes. 248 * 249 * @hide 250 */ 251 @NonNull getSchemasVisibleToPackagesInternal()252 public Map<String, Set<PackageIdentifier>> getSchemasVisibleToPackagesInternal() { 253 return mSchemasVisibleToPackages; 254 } 255 256 /** Returns whether this request will force the schema to be overridden. */ isForceOverride()257 public boolean isForceOverride() { 258 return mForceOverride; 259 } 260 261 /** Returns the database overall schema version. */ 262 @IntRange(from = 1) getVersion()263 public int getVersion() { 264 return mVersion; 265 } 266 267 /** Builder for {@link SetSchemaRequest} objects. */ 268 public static final class Builder { 269 private static final int DEFAULT_VERSION = 1; 270 private ArraySet<AppSearchSchema> mSchemas = new ArraySet<>(); 271 private ArraySet<String> mSchemasNotDisplayedBySystem = new ArraySet<>(); 272 private ArrayMap<String, Set<PackageIdentifier>> mSchemasVisibleToPackages = 273 new ArrayMap<>(); 274 private ArrayMap<String, Set<Set<Integer>>> mSchemasVisibleToPermissions = new ArrayMap<>(); 275 private ArrayMap<String, Migrator> mMigrators = new ArrayMap<>(); 276 private boolean mForceOverride = false; 277 private int mVersion = DEFAULT_VERSION; 278 private boolean mBuilt = false; 279 280 /** 281 * Adds one or more {@link AppSearchSchema} types to the schema. 282 * 283 * <p>An {@link AppSearchSchema} object represents one type of structured data. 284 * 285 * <p>Any documents of these types will be displayed on system UI surfaces by default. 286 */ 287 @CanIgnoreReturnValue 288 @NonNull addSchemas(@onNull AppSearchSchema... schemas)289 public Builder addSchemas(@NonNull AppSearchSchema... schemas) { 290 Objects.requireNonNull(schemas); 291 resetIfBuilt(); 292 return addSchemas(Arrays.asList(schemas)); 293 } 294 295 /** 296 * Adds a collection of {@link AppSearchSchema} objects to the schema. 297 * 298 * <p>An {@link AppSearchSchema} object represents one type of structured data. 299 */ 300 @CanIgnoreReturnValue 301 @NonNull addSchemas(@onNull Collection<AppSearchSchema> schemas)302 public Builder addSchemas(@NonNull Collection<AppSearchSchema> schemas) { 303 Objects.requireNonNull(schemas); 304 resetIfBuilt(); 305 mSchemas.addAll(schemas); 306 return this; 307 } 308 309 /** 310 * Sets whether or not documents from the provided {@code schemaType} will be displayed and 311 * visible on any system UI surface. 312 * 313 * <p>This setting applies to the provided {@code schemaType} only, and does not persist 314 * across {@link AppSearchSession#setSchema} calls. 315 * 316 * <p>The default behavior, if this method is not called, is to allow types to be displayed 317 * on system UI surfaces. 318 * 319 * @param schemaType The name of an {@link AppSearchSchema} within the same {@link 320 * SetSchemaRequest}, which will be configured. 321 * @param displayed Whether documents of this type will be displayed on system UI surfaces. 322 */ 323 // Merged list available from getSchemasNotDisplayedBySystem 324 @CanIgnoreReturnValue 325 @SuppressLint("MissingGetterMatchingBuilder") 326 @NonNull setSchemaTypeDisplayedBySystem( @onNull String schemaType, boolean displayed)327 public Builder setSchemaTypeDisplayedBySystem( 328 @NonNull String schemaType, boolean displayed) { 329 Objects.requireNonNull(schemaType); 330 resetIfBuilt(); 331 if (displayed) { 332 mSchemasNotDisplayedBySystem.remove(schemaType); 333 } else { 334 mSchemasNotDisplayedBySystem.add(schemaType); 335 } 336 return this; 337 } 338 339 /** 340 * Adds a set of required Android {@link android.Manifest.permission} combination to the 341 * given schema type. 342 * 343 * <p>If the querier holds ALL of the required permissions in this combination, they will 344 * have access to read {@link GenericDocument} objects of the given schema type. 345 * 346 * <p>You can call this method to add multiple permission combinations, and the querier will 347 * have access if they holds ANY of the combinations. 348 * 349 * <p>The supported Permissions are {@link #READ_SMS}, {@link #READ_CALENDAR}, {@link 350 * #READ_CONTACTS}, {@link #READ_EXTERNAL_STORAGE}, {@link #READ_HOME_APP_SEARCH_DATA} and 351 * {@link #READ_ASSISTANT_APP_SEARCH_DATA}. 352 * 353 * @see android.Manifest.permission#READ_SMS 354 * @see android.Manifest.permission#READ_CALENDAR 355 * @see android.Manifest.permission#READ_CONTACTS 356 * @see android.Manifest.permission#READ_EXTERNAL_STORAGE 357 * @see android.Manifest.permission#READ_HOME_APP_SEARCH_DATA 358 * @see android.Manifest.permission#READ_ASSISTANT_APP_SEARCH_DATA 359 * @param schemaType The schema type to set visibility on. 360 * @param permissions A set of required Android permissions the caller need to hold to 361 * access {@link GenericDocument} objects that under the given schema. 362 * @throws IllegalArgumentException – if input unsupported permission. 363 */ 364 // Merged list available from getRequiredPermissionsForSchemaTypeVisibility 365 @CanIgnoreReturnValue 366 @SuppressLint("MissingGetterMatchingBuilder") 367 @NonNull addRequiredPermissionsForSchemaTypeVisibility( @onNull String schemaType, @AppSearchSupportedPermission @NonNull Set<Integer> permissions)368 public Builder addRequiredPermissionsForSchemaTypeVisibility( 369 @NonNull String schemaType, 370 @AppSearchSupportedPermission @NonNull Set<Integer> permissions) { 371 Objects.requireNonNull(schemaType); 372 Objects.requireNonNull(permissions); 373 for (int permission : permissions) { 374 Preconditions.checkArgumentInRange( 375 permission, READ_SMS, READ_ASSISTANT_APP_SEARCH_DATA, "permission"); 376 } 377 resetIfBuilt(); 378 Set<Set<Integer>> visibleToPermissions = mSchemasVisibleToPermissions.get(schemaType); 379 if (visibleToPermissions == null) { 380 visibleToPermissions = new ArraySet<>(); 381 mSchemasVisibleToPermissions.put(schemaType, visibleToPermissions); 382 } 383 visibleToPermissions.add(permissions); 384 return this; 385 } 386 387 /** Clears all required permissions combinations for the given schema type. */ 388 @NonNull clearRequiredPermissionsForSchemaTypeVisibility(@onNull String schemaType)389 public Builder clearRequiredPermissionsForSchemaTypeVisibility(@NonNull String schemaType) { 390 Objects.requireNonNull(schemaType); 391 resetIfBuilt(); 392 mSchemasVisibleToPermissions.remove(schemaType); 393 return this; 394 } 395 396 /** 397 * Sets whether or not documents from the provided {@code schemaType} can be read by the 398 * specified package. 399 * 400 * <p>Each package is represented by a {@link PackageIdentifier}, containing a package name 401 * and a byte array of type {@link android.content.pm.PackageManager#CERT_INPUT_SHA256}. 402 * 403 * <p>To opt into one-way data sharing with another application, the developer will need to 404 * explicitly grant the other application’s package name and certificate Read access to its 405 * data. 406 * 407 * <p>For two-way data sharing, both applications need to explicitly grant Read access to 408 * one another. 409 * 410 * <p>By default, data sharing between applications is disabled. 411 * 412 * @param schemaType The schema type to set visibility on. 413 * @param visible Whether the {@code schemaType} will be visible or not. 414 * @param packageIdentifier Represents the package that will be granted visibility. 415 */ 416 // Merged list available from getSchemasVisibleToPackages 417 @CanIgnoreReturnValue 418 @SuppressLint("MissingGetterMatchingBuilder") 419 @NonNull setSchemaTypeVisibilityForPackage( @onNull String schemaType, boolean visible, @NonNull PackageIdentifier packageIdentifier)420 public Builder setSchemaTypeVisibilityForPackage( 421 @NonNull String schemaType, 422 boolean visible, 423 @NonNull PackageIdentifier packageIdentifier) { 424 Objects.requireNonNull(schemaType); 425 Objects.requireNonNull(packageIdentifier); 426 resetIfBuilt(); 427 428 Set<PackageIdentifier> packageIdentifiers = mSchemasVisibleToPackages.get(schemaType); 429 if (visible) { 430 if (packageIdentifiers == null) { 431 packageIdentifiers = new ArraySet<>(); 432 } 433 packageIdentifiers.add(packageIdentifier); 434 mSchemasVisibleToPackages.put(schemaType, packageIdentifiers); 435 } else { 436 if (packageIdentifiers == null) { 437 // Return early since there was nothing set to begin with. 438 return this; 439 } 440 packageIdentifiers.remove(packageIdentifier); 441 if (packageIdentifiers.isEmpty()) { 442 // Remove the entire key so that we don't have empty sets as values. 443 mSchemasVisibleToPackages.remove(schemaType); 444 } 445 } 446 447 return this; 448 } 449 450 /** 451 * Sets the {@link Migrator} associated with the given SchemaType. 452 * 453 * <p>The {@link Migrator} migrates all {@link GenericDocument}s under given schema type 454 * from the current version number stored in AppSearch to the final version set via {@link 455 * #setVersion}. 456 * 457 * <p>A {@link Migrator} will be invoked if the current version number stored in AppSearch 458 * is different from the final version set via {@link #setVersion} and {@link 459 * Migrator#shouldMigrate} returns {@code true}. 460 * 461 * <p>The target schema type of the output {@link GenericDocument} of {@link 462 * Migrator#onUpgrade} or {@link Migrator#onDowngrade} must exist in this {@link 463 * SetSchemaRequest}. 464 * 465 * @param schemaType The schema type to set migrator on. 466 * @param migrator The migrator translates a document from its current version to the final 467 * version set via {@link #setVersion}. 468 * @see SetSchemaRequest.Builder#setVersion 469 * @see SetSchemaRequest.Builder#addSchemas 470 * @see AppSearchSession#setSchema 471 */ 472 @CanIgnoreReturnValue 473 @NonNull 474 @SuppressLint("MissingGetterMatchingBuilder") // Getter return plural objects. setMigrator(@onNull String schemaType, @NonNull Migrator migrator)475 public Builder setMigrator(@NonNull String schemaType, @NonNull Migrator migrator) { 476 Objects.requireNonNull(schemaType); 477 Objects.requireNonNull(migrator); 478 resetIfBuilt(); 479 mMigrators.put(schemaType, migrator); 480 return this; 481 } 482 483 /** 484 * Sets a Map of {@link Migrator}s. 485 * 486 * <p>The key of the map is the schema type that the {@link Migrator} value applies to. 487 * 488 * <p>The {@link Migrator} migrates all {@link GenericDocument}s under given schema type 489 * from the current version number stored in AppSearch to the final version set via {@link 490 * #setVersion}. 491 * 492 * <p>A {@link Migrator} will be invoked if the current version number stored in AppSearch 493 * is different from the final version set via {@link #setVersion} and {@link 494 * Migrator#shouldMigrate} returns {@code true}. 495 * 496 * <p>The target schema type of the output {@link GenericDocument} of {@link 497 * Migrator#onUpgrade} or {@link Migrator#onDowngrade} must exist in this {@link 498 * SetSchemaRequest}. 499 * 500 * @param migrators A {@link Map} of migrators that translate a document from its current 501 * version to the final version set via {@link #setVersion}. The key of the map is the 502 * schema type that the {@link Migrator} value applies to. 503 * @see SetSchemaRequest.Builder#setVersion 504 * @see SetSchemaRequest.Builder#addSchemas 505 * @see AppSearchSession#setSchema 506 */ 507 @CanIgnoreReturnValue 508 @NonNull setMigrators(@onNull Map<String, Migrator> migrators)509 public Builder setMigrators(@NonNull Map<String, Migrator> migrators) { 510 Objects.requireNonNull(migrators); 511 resetIfBuilt(); 512 mMigrators.putAll(migrators); 513 return this; 514 } 515 516 /** 517 * Sets whether or not to override the current schema in the {@link AppSearchSession} 518 * database. 519 * 520 * <p>Call this method whenever backward incompatible changes need to be made by setting 521 * {@code forceOverride} to {@code true}. As a result, during execution of the setSchema 522 * operation, all documents that are incompatible with the new schema will be deleted and 523 * the new schema will be saved and persisted. 524 * 525 * <p>By default, this is {@code false}. 526 */ 527 @CanIgnoreReturnValue 528 @NonNull setForceOverride(boolean forceOverride)529 public Builder setForceOverride(boolean forceOverride) { 530 resetIfBuilt(); 531 mForceOverride = forceOverride; 532 return this; 533 } 534 535 /** 536 * Sets the version number of the overall {@link AppSearchSchema} in the database. 537 * 538 * <p>The {@link AppSearchSession} database can only ever hold documents for one version at 539 * a time. 540 * 541 * <p>Setting a version number that is different from the version number currently stored in 542 * AppSearch will result in AppSearch calling the {@link Migrator}s provided to {@link 543 * AppSearchSession#setSchema} to migrate the documents already in AppSearch from the 544 * previous version to the one set in this request. The version number can be updated 545 * without any other changes to the set of schemas. 546 * 547 * <p>The version number can stay the same, increase, or decrease relative to the current 548 * version number that is already stored in the {@link AppSearchSession} database. 549 * 550 * <p>The version of an empty database will always be 0. You cannot set version to the 551 * {@link SetSchemaRequest}, if it doesn't contains any {@link AppSearchSchema}. 552 * 553 * @param version A positive integer representing the version of the entire set of schemas 554 * represents the version of the whole schema in the {@link AppSearchSession} database, 555 * default version is 1. 556 * @throws IllegalArgumentException if the version is negative. 557 * @see AppSearchSession#setSchema 558 * @see Migrator 559 * @see SetSchemaRequest.Builder#setMigrator 560 */ 561 @CanIgnoreReturnValue 562 @NonNull setVersion(@ntRangefrom = 1) int version)563 public Builder setVersion(@IntRange(from = 1) int version) { 564 Preconditions.checkArgument(version >= 1, "Version must be a positive number."); 565 resetIfBuilt(); 566 mVersion = version; 567 return this; 568 } 569 570 /** 571 * Builds a new {@link SetSchemaRequest} object. 572 * 573 * @throws IllegalArgumentException if schema types were referenced, but the corresponding 574 * {@link AppSearchSchema} type was never added. 575 */ 576 @NonNull build()577 public SetSchemaRequest build() { 578 // Verify that any schema types with display or visibility settings refer to a real 579 // schema. 580 // Create a copy because we're going to remove from the set for verification purposes. 581 Set<String> referencedSchemas = new ArraySet<>(mSchemasNotDisplayedBySystem); 582 referencedSchemas.addAll(mSchemasVisibleToPackages.keySet()); 583 referencedSchemas.addAll(mSchemasVisibleToPermissions.keySet()); 584 585 for (AppSearchSchema schema : mSchemas) { 586 referencedSchemas.remove(schema.getSchemaType()); 587 } 588 if (!referencedSchemas.isEmpty()) { 589 // We still have schema types that weren't seen in our mSchemas set. This means 590 // there wasn't a corresponding AppSearchSchema. 591 throw new IllegalArgumentException( 592 "Schema types " + referencedSchemas + " referenced, but were not added."); 593 } 594 if (mSchemas.isEmpty() && mVersion != DEFAULT_VERSION) { 595 throw new IllegalArgumentException( 596 "Cannot set version to the request if schema is empty."); 597 } 598 mBuilt = true; 599 return new SetSchemaRequest( 600 mSchemas, 601 mSchemasNotDisplayedBySystem, 602 mSchemasVisibleToPackages, 603 mSchemasVisibleToPermissions, 604 mMigrators, 605 mForceOverride, 606 mVersion); 607 } 608 resetIfBuilt()609 private void resetIfBuilt() { 610 if (mBuilt) { 611 ArrayMap<String, Set<PackageIdentifier>> schemasVisibleToPackages = 612 new ArrayMap<>(mSchemasVisibleToPackages.size()); 613 for (Map.Entry<String, Set<PackageIdentifier>> entry : 614 mSchemasVisibleToPackages.entrySet()) { 615 schemasVisibleToPackages.put(entry.getKey(), new ArraySet<>(entry.getValue())); 616 } 617 mSchemasVisibleToPackages = schemasVisibleToPackages; 618 619 mSchemasVisibleToPermissions = deepCopy(mSchemasVisibleToPermissions); 620 621 mSchemas = new ArraySet<>(mSchemas); 622 mSchemasNotDisplayedBySystem = new ArraySet<>(mSchemasNotDisplayedBySystem); 623 mMigrators = new ArrayMap<>(mMigrators); 624 mBuilt = false; 625 } 626 } 627 } 628 deepCopy( @onNull Map<String, Set<Set<Integer>>> original)629 static ArrayMap<String, Set<Set<Integer>>> deepCopy( 630 @NonNull Map<String, Set<Set<Integer>>> original) { 631 ArrayMap<String, Set<Set<Integer>>> copy = new ArrayMap<>(original.size()); 632 for (Map.Entry<String, Set<Set<Integer>>> entry : original.entrySet()) { 633 Set<Set<Integer>> valueCopy = new ArraySet<>(); 634 for (Set<Integer> innerValue : entry.getValue()) { 635 valueCopy.add(new ArraySet<>(innerValue)); 636 } 637 copy.put(entry.getKey(), valueCopy); 638 } 639 return copy; 640 } 641 } 642