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