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