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