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 androidx.appsearch.app;
18 
19 import android.annotation.SuppressLint;
20 
21 import androidx.annotation.IntDef;
22 import androidx.annotation.IntRange;
23 import androidx.annotation.NonNull;
24 import androidx.annotation.Nullable;
25 import androidx.annotation.RequiresFeature;
26 import androidx.annotation.RestrictTo;
27 import androidx.appsearch.annotation.CanIgnoreReturnValue;
28 import androidx.appsearch.exceptions.AppSearchException;
29 import androidx.appsearch.flags.FlaggedApi;
30 import androidx.appsearch.flags.Flags;
31 import androidx.collection.ArrayMap;
32 import androidx.collection.ArraySet;
33 import androidx.core.util.ObjectsCompat;
34 import androidx.core.util.Preconditions;
35 
36 import java.lang.annotation.Retention;
37 import java.lang.annotation.RetentionPolicy;
38 import java.util.ArrayList;
39 import java.util.Arrays;
40 import java.util.Collection;
41 import java.util.Collections;
42 import java.util.List;
43 import java.util.Map;
44 import java.util.Set;
45 
46 /**
47  * Encapsulates a request to update the schema of an {@link AppSearchSession} database.
48  *
49  * <p>The schema is composed of a collection of {@link AppSearchSchema} objects, each of which
50  * defines a unique type of data.
51  *
52  * <p>The first call to SetSchemaRequest will set the provided schema and store it within the
53  * {@link AppSearchSession} database.
54  *
55  * <p>Subsequent calls will compare the provided schema to the previously saved schema, to
56  * determine how to treat existing documents.
57  *
58  * <p>The following types of schema modifications are always safe and are made without deleting any
59  * existing documents:
60  * <ul>
61  *     <li>Addition of new {@link AppSearchSchema} types
62  *     <li>Addition of new properties to an existing {@link AppSearchSchema} type
63  *     <li>Changing the cardinality of a property to be less restrictive
64  * </ul>
65  *
66  * <p>The following types of schema changes are not backwards compatible:
67  * <ul>
68  *     <li>Removal of an existing {@link AppSearchSchema} type
69  *     <li>Removal of a property from an existing {@link AppSearchSchema} type
70  *     <li>Changing the data type of an existing property
71  *     <li>Changing the cardinality of a property to be more restrictive
72  * </ul>
73  *
74  * <p>Providing a schema with incompatible changes, will throw an
75  * {@link androidx.appsearch.exceptions.AppSearchException}, with a message describing the
76  * incompatibility. As a result, the previously set schema will remain unchanged.
77  *
78  * <p>Backward incompatible changes can be made by :
79  * <ul>
80  *     <li>setting {@link SetSchemaRequest.Builder#setForceOverride} method to {@code true}.
81  *         This deletes all documents that are incompatible with the new schema. The new schema is
82  *         then saved and persisted to disk.
83  *     <li>Add a {@link Migrator} for each incompatible type and make no deletion. The migrator
84  *         will migrate documents from its old schema version to the new version. Migrated types
85  *         will be set into both {@link SetSchemaResponse#getIncompatibleTypes()} and
86  *         {@link SetSchemaResponse#getMigratedTypes()}. See the migration section below.
87  * </ul>
88  * @see AppSearchSession#setSchemaAsync
89  * @see Migrator
90  */
91 // TODO(b/384721898): Switch to JSpecify annotations
92 @SuppressWarnings("JSpecifyNullness")
93 public final class SetSchemaRequest {
94 
95     /**
96      * List of Android Permission are supported in
97      * {@link SetSchemaRequest.Builder#addRequiredPermissionsForSchemaTypeVisibility}
98      *
99      * @see android.Manifest.permission
100      * @exportToFramework:hide
101      */
102     @IntDef(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     @RequiresFeature(
116             enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
117             name = Features.ADD_PERMISSIONS_AND_GET_VISIBILITY)
118     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
119     public @interface AppSearchSupportedPermission {}
120 
121     /**
122      * The {@link android.Manifest.permission#READ_SMS} AppSearch supported in
123      * {@link SetSchemaRequest.Builder#addRequiredPermissionsForSchemaTypeVisibility}
124      */
125     @RequiresFeature(
126             enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
127             name = Features.ADD_PERMISSIONS_AND_GET_VISIBILITY)
128     public static final int READ_SMS = 1;
129 
130     /**
131      * The {@link android.Manifest.permission#READ_CALENDAR} AppSearch supported in
132      * {@link SetSchemaRequest.Builder#addRequiredPermissionsForSchemaTypeVisibility}
133      */
134     @RequiresFeature(
135             enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
136             name = Features.ADD_PERMISSIONS_AND_GET_VISIBILITY)
137     public static final int READ_CALENDAR = 2;
138 
139     /**
140      * The {@link android.Manifest.permission#READ_CONTACTS} AppSearch supported in
141      * {@link SetSchemaRequest.Builder#addRequiredPermissionsForSchemaTypeVisibility}
142      */
143     @RequiresFeature(
144             enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
145             name = Features.ADD_PERMISSIONS_AND_GET_VISIBILITY)
146     public static final int READ_CONTACTS = 3;
147 
148     /**
149      * The {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} AppSearch supported in
150      * {@link SetSchemaRequest.Builder#addRequiredPermissionsForSchemaTypeVisibility}
151      */
152     @RequiresFeature(
153             enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
154             name = Features.ADD_PERMISSIONS_AND_GET_VISIBILITY)
155     public static final int READ_EXTERNAL_STORAGE = 4;
156 
157     /**
158      * The {@link android.Manifest.permission#READ_HOME_APP_SEARCH_DATA} AppSearch supported in
159      * {@link SetSchemaRequest.Builder#addRequiredPermissionsForSchemaTypeVisibility}
160      */
161     @RequiresFeature(
162             enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
163             name = Features.ADD_PERMISSIONS_AND_GET_VISIBILITY)
164     public static final int READ_HOME_APP_SEARCH_DATA = 5;
165 
166     /**
167      * The {@link android.Manifest.permission#READ_ASSISTANT_APP_SEARCH_DATA} AppSearch supported in
168      * {@link SetSchemaRequest.Builder#addRequiredPermissionsForSchemaTypeVisibility}
169      */
170     @RequiresFeature(
171             enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
172             name = Features.ADD_PERMISSIONS_AND_GET_VISIBILITY)
173     public static final int READ_ASSISTANT_APP_SEARCH_DATA = 6;
174 
175     /**
176      * A schema must have this permission set through {@link
177      * SetSchemaRequest.Builder#addRequiredPermissionsForSchemaTypeVisibility} to be visible to an
178      * {@link EnterpriseGlobalSearchSession}. A call from a regular {@link GlobalSearchSession} will
179      * not count as having this permission.
180      *
181      * @exportToFramework:hide
182      */
183     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
184     public static final int ENTERPRISE_ACCESS = 7;
185 
186     /**
187      * A schema with this permission set through {@link
188      * SetSchemaRequest.Builder#addRequiredPermissionsForSchemaTypeVisibility} requires the caller
189      * to have managed profile contacts access from {@link android.app.admin.DevicePolicyManager} to
190      * be visible. This permission indicates that the protected schema may expose managed profile
191      * data for contacts search.
192      *
193      * @exportToFramework:hide
194      */
195     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
196     public static final int MANAGED_PROFILE_CONTACTS_ACCESS = 8;
197 
198     /**
199      * The AppSearch enumeration corresponding to {@link
200      * android.Manifest.permission#EXECUTE_APP_FUNCTIONS} Android permission that can be used to
201      * guard AppSearch schema type visibility in {@link
202      * SetSchemaRequest.Builder#addRequiredPermissionsForSchemaTypeVisibility}.
203      *
204      * <p>This is internally used by AppFunctions API to store app functions runtime metadata so it
205      * is visible to packages holding {@link android.Manifest.permission#EXECUTE_APP_FUNCTIONS}
206      * permission (currently associated with system assistant apps).
207      *
208      * @exportToFramework:hide
209      */
210     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
211     public static final int EXECUTE_APP_FUNCTIONS = 9;
212 
213     /**
214      * @deprecated The corresponding permission is deprecated. Some documents are already persisted
215      *     with this constant, therefore keeping the constant here for compatibility reasons.
216      *
217      * @exportToFramework:hide
218      */
219     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
220     public static final int EXECUTE_APP_FUNCTIONS_TRUSTED = 10;
221 
222     /**
223      * The {@link android.Manifest.permission#PACKAGE_USAGE_STATS} AppSearch supported in {@link
224      * SetSchemaRequest.Builder#addRequiredPermissionsForSchemaTypeVisibility}
225      *
226      * @exportToFramework:hide
227      */
228     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
229     public static final int PACKAGE_USAGE_STATS = 11;
230 
231     private final Set<AppSearchSchema> mSchemas;
232     private final Set<String> mSchemasNotDisplayedBySystem;
233     private final Map<String, Set<PackageIdentifier>> mSchemasVisibleToPackages;
234     private final Map<String, Set<Set<Integer>>> mSchemasVisibleToPermissions;
235     private final Map<String, PackageIdentifier> mPubliclyVisibleSchemas;
236     private final Map<String, Set<SchemaVisibilityConfig>> mSchemasVisibleToConfigs;
237     private final Map<String, Migrator> mMigrators;
238     private final boolean mForceOverride;
239     private final int mVersion;
240 
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)241     SetSchemaRequest(@NonNull Set<AppSearchSchema> schemas,
242             @NonNull Set<String> schemasNotDisplayedBySystem,
243             @NonNull Map<String, Set<PackageIdentifier>> schemasVisibleToPackages,
244             @NonNull Map<String, Set<Set<Integer>>> schemasVisibleToPermissions,
245             @NonNull Map<String, PackageIdentifier> publiclyVisibleSchemas,
246             @NonNull Map<String, Set<SchemaVisibilityConfig>> schemasVisibleToConfigs,
247             @NonNull Map<String, Migrator> migrators,
248             boolean forceOverride,
249             int version) {
250         mSchemas = Preconditions.checkNotNull(schemas);
251         mSchemasNotDisplayedBySystem = Preconditions.checkNotNull(schemasNotDisplayedBySystem);
252         mSchemasVisibleToPackages = Preconditions.checkNotNull(schemasVisibleToPackages);
253         mSchemasVisibleToPermissions = Preconditions.checkNotNull(schemasVisibleToPermissions);
254         mPubliclyVisibleSchemas = Preconditions.checkNotNull(publiclyVisibleSchemas);
255         mSchemasVisibleToConfigs = Preconditions.checkNotNull(schemasVisibleToConfigs);
256         mMigrators = Preconditions.checkNotNull(migrators);
257         mForceOverride = forceOverride;
258         mVersion = version;
259     }
260 
261     /** Returns the {@link AppSearchSchema} types that are part of this request. */
getSchemas()262     public @NonNull Set<AppSearchSchema> getSchemas() {
263         return Collections.unmodifiableSet(mSchemas);
264     }
265 
266     /**
267      * Returns all the schema types that are opted out of being displayed and visible on any
268      * system UI surface.
269      */
getSchemasNotDisplayedBySystem()270     public @NonNull Set<String> getSchemasNotDisplayedBySystem() {
271         return Collections.unmodifiableSet(mSchemasNotDisplayedBySystem);
272     }
273 
274     /**
275      * Returns a mapping of schema types to the set of packages that have access
276      * to that schema type.
277      *
278      * <p>It’s inefficient to call this method repeatedly.
279      */
getSchemasVisibleToPackages()280     public @NonNull Map<String, Set<PackageIdentifier>> getSchemasVisibleToPackages() {
281         Map<String, Set<PackageIdentifier>> copy = new ArrayMap<>();
282         for (Map.Entry<String, Set<PackageIdentifier>> entry :
283                 mSchemasVisibleToPackages.entrySet()) {
284             copy.put(entry.getKey(), new ArraySet<>(entry.getValue()));
285         }
286         return copy;
287     }
288 
289     /**
290      * Returns a mapping of schema types to the Map of {@link android.Manifest.permission}
291      * combinations that querier must hold to access that schema type.
292      *
293      * <p> The querier could read the {@link GenericDocument} objects under the {@code schemaType}
294      * if they holds ALL required permissions of ANY of the individual value sets.
295      *
296      * <p>For example, if the Map contains {@code {% verbatim %}{{permissionA, PermissionB},
297      * {PermissionC, PermissionD}, {PermissionE}}{% endverbatim %}}.
298      * <ul>
299      *     <li>A querier holds both PermissionA and PermissionB has access.</li>
300      *     <li>A querier holds both PermissionC and PermissionD has access.</li>
301      *     <li>A querier holds only PermissionE has access.</li>
302      *     <li>A querier holds both PermissionA and PermissionE has access.</li>
303      *     <li>A querier holds only PermissionA doesn't have access.</li>
304      *     <li>A querier holds both PermissionA and PermissionC doesn't have access.</li>
305      * </ul>
306      *
307      * <p>It’s inefficient to call this method repeatedly.
308      *
309      * @return The map contains schema type and all combinations of required permission for querier
310      *         to access it. The supported Permission are {@link SetSchemaRequest#READ_SMS},
311      *         {@link SetSchemaRequest#READ_CALENDAR}, {@link SetSchemaRequest#READ_CONTACTS},
312      *         {@link SetSchemaRequest#READ_EXTERNAL_STORAGE},
313      *         {@link SetSchemaRequest#READ_HOME_APP_SEARCH_DATA} and
314      *         {@link SetSchemaRequest#READ_ASSISTANT_APP_SEARCH_DATA}.
315      */
316     // TODO(b/237388235): add enterprise permissions to javadocs after they're unhidden
317     // Annotation is here to suppress lint error. Lint error is erroneous since the method does not
318     // require the caller to hold any permission for the method to function.
319     @SuppressLint("RequiresPermission")
getRequiredPermissionsForSchemaTypeVisibility()320     public @NonNull Map<String, Set<Set<Integer>>> getRequiredPermissionsForSchemaTypeVisibility() {
321         return deepCopy(mSchemasVisibleToPermissions);
322     }
323 
324     /**
325      * Returns a mapping of publicly visible schemas to the {@link PackageIdentifier} specifying
326      * the package the schemas are from.
327      */
328     @FlaggedApi(Flags.FLAG_ENABLE_SET_PUBLICLY_VISIBLE_SCHEMA)
getPubliclyVisibleSchemas()329     public @NonNull Map<String, PackageIdentifier> getPubliclyVisibleSchemas() {
330         return Collections.unmodifiableMap(mPubliclyVisibleSchemas);
331     }
332 
333     /**
334      * Returns a mapping of schema types to the set of {@link SchemaVisibilityConfig} that have
335      * access to that schema type.
336      *
337      * <p>It’s inefficient to call this method repeatedly.
338      * @see SetSchemaRequest.Builder#addSchemaTypeVisibleToConfig
339      */
340     @FlaggedApi(Flags.FLAG_ENABLE_SET_SCHEMA_VISIBLE_TO_CONFIGS)
getSchemasVisibleToConfigs()341     public @NonNull Map<String, Set<SchemaVisibilityConfig>> getSchemasVisibleToConfigs() {
342         Map<String, Set<SchemaVisibilityConfig>> copy = new ArrayMap<>();
343         for (Map.Entry<String, Set<SchemaVisibilityConfig>> entry :
344                 mSchemasVisibleToConfigs.entrySet()) {
345             copy.put(entry.getKey(), new ArraySet<>(entry.getValue()));
346         }
347         return copy;
348     }
349 
350     /**
351      * Returns the map of {@link Migrator}, the key will be the schema type of the
352      * {@link Migrator} associated with.
353      */
getMigrators()354     public @NonNull Map<String, Migrator> getMigrators() {
355         return Collections.unmodifiableMap(mMigrators);
356     }
357 
358     /**
359      * Returns a mapping of {@link AppSearchSchema} types to the set of packages that have access
360      * to that schema type.
361      *
362      * <p>A more efficient version of {@link #getSchemasVisibleToPackages}, but it returns a
363      * modifiable map. This is not meant to be unhidden and should only be used by internal
364      * classes.
365      *
366      * @exportToFramework:hide
367      */
368     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
getSchemasVisibleToPackagesInternal()369     public @NonNull Map<String, Set<PackageIdentifier>> getSchemasVisibleToPackagesInternal() {
370         return mSchemasVisibleToPackages;
371     }
372 
373     /** Returns whether this request will force the schema to be overridden. */
isForceOverride()374     public boolean isForceOverride() {
375         return mForceOverride;
376     }
377 
378     /** Returns the database overall schema version. */
379     @IntRange(from = 1)
getVersion()380     public int getVersion() {
381         return mVersion;
382     }
383 
384     @Override
equals(@ullable Object other)385     public boolean equals(@Nullable Object other) {
386         if (this == other) {
387             return true;
388         }
389         if (!(other instanceof SetSchemaRequest)) {
390             return false;
391         }
392         SetSchemaRequest otherRequest = (SetSchemaRequest) other;
393         return mSchemas.equals(otherRequest.mSchemas)
394                 && mSchemasNotDisplayedBySystem.equals(otherRequest.mSchemasNotDisplayedBySystem)
395                 && mSchemasVisibleToPackages.equals(otherRequest.mSchemasVisibleToPackages)
396                 && mSchemasVisibleToPermissions.equals(otherRequest.mSchemasVisibleToPermissions)
397                 && mPubliclyVisibleSchemas.equals(otherRequest.mPubliclyVisibleSchemas)
398                 && mSchemasVisibleToConfigs.equals(otherRequest.mSchemasVisibleToConfigs)
399                 && mMigrators.equals(otherRequest.mMigrators)
400                 && mForceOverride == otherRequest.mForceOverride
401                 && mVersion == otherRequest.mVersion;
402     }
403 
404     @Override
hashCode()405     public int hashCode() {
406         return ObjectsCompat.hash(mSchemas, mSchemasNotDisplayedBySystem, mSchemasVisibleToPackages,
407         mSchemasVisibleToPermissions, mPubliclyVisibleSchemas, mSchemasVisibleToConfigs, mMigrators,
408                 mForceOverride, mVersion);
409     }
410 
411     /** Builder for {@link SetSchemaRequest} objects. */
412     public static final class Builder {
413         private static final int DEFAULT_VERSION = 1;
414         private ArraySet<AppSearchSchema> mSchemas = new ArraySet<>();
415         private ArraySet<String> mSchemasNotDisplayedBySystem = new ArraySet<>();
416         private ArrayMap<String, Set<PackageIdentifier>> mSchemasVisibleToPackages =
417                 new ArrayMap<>();
418         private ArrayMap<String, Set<Set<Integer>>> mSchemasVisibleToPermissions = new ArrayMap<>();
419         private ArrayMap<String, PackageIdentifier> mPubliclyVisibleSchemas = new ArrayMap<>();
420         private ArrayMap<String, Set<SchemaVisibilityConfig>> mSchemaVisibleToConfigs =
421                 new ArrayMap<>();
422         private ArrayMap<String, Migrator> mMigrators = new ArrayMap<>();
423         private boolean mForceOverride = false;
424         private int mVersion = DEFAULT_VERSION;
425         private boolean mBuilt = false;
426 
427         /** Creates a new {@link SetSchemaRequest.Builder}. */
Builder()428         public Builder() {
429         }
430 
431         /**
432          * Creates a {@link SetSchemaRequest.Builder} from the given {@link SetSchemaRequest}.
433          */
434         @ExperimentalAppSearchApi
435         @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
Builder(@onNull SetSchemaRequest request)436         public Builder(@NonNull SetSchemaRequest request) {
437             mSchemas.addAll(request.mSchemas);
438             mSchemasNotDisplayedBySystem.addAll(request.mSchemasNotDisplayedBySystem);
439             for (Map.Entry<String, Set<PackageIdentifier>> entry
440                     : request.mSchemasVisibleToPackages.entrySet()) {
441                 mSchemasVisibleToPackages.put(entry.getKey(), new ArraySet<>(entry.getValue()));
442             }
443             mSchemasVisibleToPermissions = deepCopy(request.mSchemasVisibleToPermissions);
444             mPubliclyVisibleSchemas.putAll(request.mPubliclyVisibleSchemas);
445             for (Map.Entry<String, Set<SchemaVisibilityConfig>> entry :
446                     request.mSchemasVisibleToConfigs.entrySet()) {
447                 mSchemaVisibleToConfigs.put(entry.getKey(), new ArraySet<>(entry.getValue()));
448             }
449             mMigrators.putAll(request.mMigrators);
450             mForceOverride = request.mForceOverride;
451             mVersion = request.mVersion;
452         }
453 
454         /**
455          * Adds one or more {@link AppSearchSchema} types to the schema.
456          *
457          * <p>An {@link AppSearchSchema} object represents one type of structured data.
458          *
459          * <p>Any documents of these types will be displayed on system UI surfaces by default.
460          */
461         @CanIgnoreReturnValue
addSchemas(@onNull AppSearchSchema... schemas)462         public @NonNull Builder addSchemas(@NonNull AppSearchSchema... schemas) {
463             Preconditions.checkNotNull(schemas);
464             resetIfBuilt();
465             return addSchemas(Arrays.asList(schemas));
466         }
467 
468         /**
469          * Adds a collection of {@link AppSearchSchema} objects to the schema.
470          *
471          * <p>An {@link AppSearchSchema} object represents one type of structured data.
472          */
473         @CanIgnoreReturnValue
addSchemas(@onNull Collection<AppSearchSchema> schemas)474         public @NonNull Builder addSchemas(@NonNull Collection<AppSearchSchema> schemas) {
475             Preconditions.checkNotNull(schemas);
476             resetIfBuilt();
477             mSchemas.addAll(schemas);
478             return this;
479         }
480 
481 // @exportToFramework:startStrip()
482         /**
483          * Adds one or more {@link androidx.appsearch.annotation.Document} annotated classes to the
484          * schema.
485          *
486          * <p>Merged list available from {@link #getSchemas()}.
487          *
488          * @param documentClasses classes annotated with
489          *                        {@link androidx.appsearch.annotation.Document}.
490          * @throws AppSearchException if {@code androidx.appsearch.compiler.AppSearchCompiler}
491          *                            has not generated a schema for the given document classes.
492          */
493         @CanIgnoreReturnValue
494         @SuppressLint("MissingGetterMatchingBuilder")
addDocumentClasses(@onNull Class<?>.... documentClasses)495         public @NonNull Builder addDocumentClasses(@NonNull Class<?>... documentClasses)
496                 throws AppSearchException {
497             Preconditions.checkNotNull(documentClasses);
498             resetIfBuilt();
499             return addDocumentClasses(Arrays.asList(documentClasses));
500         }
501 
502         /**
503          * Adds a collection of {@link androidx.appsearch.annotation.Document} annotated classes to
504          * the schema.
505          *
506          * <p>This will also add all {@link androidx.appsearch.annotation.Document} classes
507          * referenced by the schema via document properties.
508          *
509          * <p>Merged list available from {@link #getSchemas()}.
510          *
511          * @param documentClasses classes annotated with
512          *                        {@link androidx.appsearch.annotation.Document}.
513          * @throws AppSearchException if {@code androidx.appsearch.compiler.AppSearchCompiler}
514          *                            has not generated a schema for the given document classes.
515          */
516         @CanIgnoreReturnValue
517         @SuppressLint("MissingGetterMatchingBuilder")
addDocumentClasses( @onNull Collection<? extends Class<?>> documentClasses)518         public @NonNull Builder addDocumentClasses(
519                 @NonNull Collection<? extends Class<?>> documentClasses) throws AppSearchException {
520             Preconditions.checkNotNull(documentClasses);
521             resetIfBuilt();
522 
523             DocumentClassFactoryRegistry registry = DocumentClassFactoryRegistry.getInstance();
524 
525             List<Class<?>> processedClasses = new ArrayList<>(documentClasses.size());
526             processedClasses.addAll(documentClasses);
527 
528             for (int i = 0; i < processedClasses.size(); i++) {
529                 DocumentClassFactory<?> factory =
530                         registry.getOrCreateFactory(processedClasses.get(i));
531                 for (Class<?> nested: factory.getDependencyDocumentClasses()) {
532                     if (!processedClasses.contains(nested)) {
533                         processedClasses.add(nested);
534                     }
535                 }
536             }
537 
538             List<AppSearchSchema> schemas = new ArrayList<>(processedClasses.size());
539             for (Class<?> documentClass : processedClasses) {
540                 DocumentClassFactory<?> factory =
541                         registry.getOrCreateFactory(documentClass);
542                 schemas.add(factory.getSchema());
543             }
544 
545             return addSchemas(schemas);
546         }
547 // @exportToFramework:endStrip()
548 
549         /**
550          * Clears all {@link AppSearchSchema}s from the list of schemas.
551          */
552         @ExperimentalAppSearchApi
553         @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
554         @CanIgnoreReturnValue
clearSchemas()555         public @NonNull Builder clearSchemas() {
556             resetIfBuilt();
557             mSchemas.clear();
558             return this;
559         }
560 
561         /**
562          * Sets whether or not documents from the provided {@code schemaType} will be displayed
563          * and visible on any system UI surface.
564          *
565          * <p>This setting applies to the provided {@code schemaType} only, and does not persist
566          * across {@link AppSearchSession#setSchemaAsync} calls.
567          *
568          * <p>The default behavior, if this method is not called, is to allow types to be
569          * displayed on system UI surfaces.
570          *
571          * @param schemaType The name of an {@link AppSearchSchema} within the same
572          *                   {@link SetSchemaRequest}, which will be configured.
573          * @param displayed  Whether documents of this type will be displayed on system UI surfaces.
574          */
575         // Merged list available from getSchemasNotDisplayedBySystem
576         @CanIgnoreReturnValue
577         @SuppressLint("MissingGetterMatchingBuilder")
setSchemaTypeDisplayedBySystem( @onNull String schemaType, boolean displayed)578         public @NonNull Builder setSchemaTypeDisplayedBySystem(
579                 @NonNull String schemaType, boolean displayed) {
580             Preconditions.checkNotNull(schemaType);
581             resetIfBuilt();
582             if (displayed) {
583                 mSchemasNotDisplayedBySystem.remove(schemaType);
584             } else {
585                 mSchemasNotDisplayedBySystem.add(schemaType);
586             }
587             return this;
588         }
589 
590         /**
591          * Adds a set of required Android {@link android.Manifest.permission} combination to the
592          * given schema type.
593          *
594          * <p> If the querier holds ALL of the required permissions in this combination, they will
595          * have access to read {@link GenericDocument} objects of the given schema type.
596          *
597          * <p> You can call this method to add multiple permission combinations, and the querier
598          * will have access if they holds ANY of the combinations.
599          *
600          * <p> The supported Permissions are {@link #READ_SMS}, {@link #READ_CALENDAR},
601          * {@link #READ_CONTACTS}, {@link #READ_EXTERNAL_STORAGE},
602          * {@link #READ_HOME_APP_SEARCH_DATA} and {@link #READ_ASSISTANT_APP_SEARCH_DATA}.
603          *
604          * <p> The relationship between permissions added in this method and package visibility
605          * setting {@link #setSchemaTypeVisibilityForPackage} is "OR". The caller could access
606          * the schema if they match ANY requirements. If you want to set "AND" requirements like
607          * a caller must hold required permissions AND it is a specified package, please use
608          * {@link #addSchemaTypeVisibleToConfig}.
609          *
610          * @see android.Manifest.permission#READ_SMS
611          * @see android.Manifest.permission#READ_CALENDAR
612          * @see android.Manifest.permission#READ_CONTACTS
613          * @see android.Manifest.permission#READ_EXTERNAL_STORAGE
614          * @see android.Manifest.permission#READ_HOME_APP_SEARCH_DATA
615          * @see android.Manifest.permission#READ_ASSISTANT_APP_SEARCH_DATA
616          * @param schemaType       The schema type to set visibility on.
617          * @param permissions      A set of required Android permissions the caller need to hold
618          *                         to access {@link GenericDocument} objects that under the given
619          *                         schema.
620          * @throws IllegalArgumentException – if input unsupported permission.
621          */
622         // TODO(b/237388235): add enterprise permissions to javadocs after they're unhidden
623         // Merged list available from getRequiredPermissionsForSchemaTypeVisibility
624         // Annotation is here to suppress lint error. Lint error is erroneous since the method does
625         // not require the caller to hold any permission for the method to function.
626         @CanIgnoreReturnValue
627         @SuppressLint({"MissingGetterMatchingBuilder", "RequiresPermission"})
628         @RequiresFeature(
629                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
630                 name = Features.ADD_PERMISSIONS_AND_GET_VISIBILITY)
addRequiredPermissionsForSchemaTypeVisibility( @onNull String schemaType, @AppSearchSupportedPermission @NonNull Set<Integer> permissions)631         public @NonNull Builder addRequiredPermissionsForSchemaTypeVisibility(
632                 @NonNull String schemaType,
633                 @AppSearchSupportedPermission @NonNull Set<Integer> permissions) {
634             Preconditions.checkNotNull(schemaType);
635             Preconditions.checkNotNull(permissions);
636             for (int permission : permissions) {
637                 Preconditions.checkArgumentInRange(permission, READ_SMS,
638                         PACKAGE_USAGE_STATS, "permission");
639             }
640             resetIfBuilt();
641             Set<Set<Integer>> visibleToPermissions = mSchemasVisibleToPermissions.get(schemaType);
642             if (visibleToPermissions == null) {
643                 visibleToPermissions = new ArraySet<>();
644                 mSchemasVisibleToPermissions.put(schemaType, visibleToPermissions);
645             }
646             visibleToPermissions.add(permissions);
647             return this;
648         }
649 
650         /**  Clears all required permissions combinations for the given schema type.  */
651         @CanIgnoreReturnValue
652         @RequiresFeature(
653                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
654                 name = Features.ADD_PERMISSIONS_AND_GET_VISIBILITY)
clearRequiredPermissionsForSchemaTypeVisibility( @onNull String schemaType)655         public @NonNull Builder clearRequiredPermissionsForSchemaTypeVisibility(
656                 @NonNull String schemaType) {
657             Preconditions.checkNotNull(schemaType);
658             resetIfBuilt();
659             mSchemasVisibleToPermissions.remove(schemaType);
660             return this;
661         }
662 
663         /**
664          * Sets whether or not documents from the provided {@code schemaType} can be read by the
665          * specified package.
666          *
667          * <p>Each package is represented by a {@link PackageIdentifier}, containing a package name
668          * and a byte array of type {@link android.content.pm.PackageManager#CERT_INPUT_SHA256}.
669          *
670          * <p>To opt into one-way data sharing with another application, the developer will need to
671          * explicitly grant the other application’s package name and certificate Read access to its
672          * data.
673          *
674          * <p>For two-way data sharing, both applications need to explicitly grant Read access to
675          * one another.
676          *
677          * <p>By default, data sharing between applications is disabled.
678          *
679          * <p> The relationship between permissions added in this method and package visibility
680          * setting {@link #setSchemaTypeVisibilityForPackage} is "OR". The caller could access
681          * the schema if they match ANY requirements. If you want to set "AND" requirements like
682          * a caller must hold required permissions AND it is a specified package, please use
683          * {@link #addSchemaTypeVisibleToConfig}.
684          *
685          * @param schemaType        The schema type to set visibility on.
686          * @param visible           Whether the {@code schemaType} will be visible or not.
687          * @param packageIdentifier Represents the package that will be granted visibility.
688          */
689         // Merged list available from getSchemasVisibleToPackages
690         @CanIgnoreReturnValue
691         @SuppressLint("MissingGetterMatchingBuilder")
setSchemaTypeVisibilityForPackage( @onNull String schemaType, boolean visible, @NonNull PackageIdentifier packageIdentifier)692         public @NonNull Builder setSchemaTypeVisibilityForPackage(
693                 @NonNull String schemaType,
694                 boolean visible,
695                 @NonNull PackageIdentifier packageIdentifier) {
696             Preconditions.checkNotNull(schemaType);
697             Preconditions.checkNotNull(packageIdentifier);
698             resetIfBuilt();
699 
700             Set<PackageIdentifier> packageIdentifiers = mSchemasVisibleToPackages.get(schemaType);
701             if (visible) {
702                 if (packageIdentifiers == null) {
703                     packageIdentifiers = new ArraySet<>();
704                 }
705                 packageIdentifiers.add(packageIdentifier);
706                 mSchemasVisibleToPackages.put(schemaType, packageIdentifiers);
707             } else {
708                 if (packageIdentifiers == null) {
709                     // Return early since there was nothing set to begin with.
710                     return this;
711                 }
712                 packageIdentifiers.remove(packageIdentifier);
713                 if (packageIdentifiers.isEmpty()) {
714                     // Remove the entire key so that we don't have empty sets as values.
715                     mSchemasVisibleToPackages.remove(schemaType);
716                 }
717             }
718 
719             return this;
720         }
721 
722         /**
723          * Specify that the schema should be publicly available, to packages which already have
724          * visibility to {@code packageIdentifier}. This visibility is determined by the result of
725          * {@link android.content.pm.PackageManager#canPackageQuery}.
726          *
727          * <p> It is possible for the packageIdentifier parameter to be different from the
728          * package performing the indexing. This might happen in the case of an on-device indexer
729          * processing information about various packages. The visibility will be the same
730          * regardless of which package indexes the document, as the visibility is based on the
731          * packageIdentifier parameter.
732          *
733          * <p> If this is called repeatedly with the same schema, the {@link PackageIdentifier} in
734          * the last call will be used as the "from" package for that schema.
735          *
736          * <p> Calling this with packageIdentifier set to null is valid, and will remove public
737          * visibility for the schema.
738          *
739          * @param schema the schema to make publicly accessible.
740          * @param packageIdentifier if an app can see this package via
741          *                          PackageManager#canPackageQuery, it will be able to see the
742          *                          documents of type {@code schema}.
743          */
744         // Merged list available from getPubliclyVisibleSchemas
745         @CanIgnoreReturnValue
746         @SuppressLint("MissingGetterMatchingBuilder")
747         @RequiresFeature(
748                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
749                 name = Features.SET_SCHEMA_REQUEST_SET_PUBLICLY_VISIBLE)
750         @FlaggedApi(Flags.FLAG_ENABLE_SET_PUBLICLY_VISIBLE_SCHEMA)
setPubliclyVisibleSchema(@onNull String schema, @Nullable PackageIdentifier packageIdentifier)751         public @NonNull Builder setPubliclyVisibleSchema(@NonNull String schema,
752                 @Nullable PackageIdentifier packageIdentifier) {
753             Preconditions.checkNotNull(schema);
754             resetIfBuilt();
755 
756             // If the package identifier is null or empty we clear public visibility
757             if (packageIdentifier == null || packageIdentifier.getPackageName().isEmpty()) {
758                 mPubliclyVisibleSchemas.remove(schema);
759                 return this;
760             }
761 
762             mPubliclyVisibleSchemas.put(schema, packageIdentifier);
763             return this;
764         }
765 
766 // @exportToFramework:startStrip()
767         /**
768          * Specify that the documents from the provided
769          * {@link androidx.appsearch.annotation.Document} annotated class should be publicly
770          * available, to packages which already have visibility to {@code packageIdentifier}. This
771          * visibility is determined by the result of
772          * {@link android.content.pm.PackageManager#canPackageQuery}.
773          *
774          * <p> It is possible for the packageIdentifier parameter to be different from the
775          * package performing the indexing. This might happen in the case of an on-device indexer
776          * processing information about various packages. The visibility will be the same
777          * regardless of which package indexes the document, as the visibility is based on the
778          * packageIdentifier parameter.
779          *
780          * <p> If this is called repeatedly with the same
781          * {@link androidx.appsearch.annotation.Document} annotated class, the
782          * {@link PackageIdentifier} in the last call will be used as the "from" package for that
783          * class (or schema).
784          *
785          * <p> Calling this with packageIdentifier set to null is valid, and will remove public
786          * visibility for the class (or schema).
787          *
788          * @param documentClass the {@link androidx.appsearch.annotation.Document} annotated class
789          *                      to make publicly accessible.
790          * @param packageIdentifier if an app can see this package via
791          *                          PackageManager#canPackageQuery, it will be able to see the
792          *                          documents of type {@code documentClass}.
793          * @see SetSchemaRequest.Builder#setPubliclyVisibleSchema
794          */
795         // Merged list available from getPubliclyVisibleSchemas
796         @CanIgnoreReturnValue
797         @SuppressLint("MissingGetterMatchingBuilder")
798         @RequiresFeature(
799                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
800                 name = Features.SET_SCHEMA_REQUEST_SET_PUBLICLY_VISIBLE)
801         @FlaggedApi(Flags.FLAG_ENABLE_SET_PUBLICLY_VISIBLE_SCHEMA)
setPubliclyVisibleDocumentClass(@onNull Class<?> documentClass, @Nullable PackageIdentifier packageIdentifier)802         public @NonNull Builder setPubliclyVisibleDocumentClass(@NonNull Class<?> documentClass,
803                 @Nullable PackageIdentifier packageIdentifier) throws AppSearchException {
804             Preconditions.checkNotNull(documentClass);
805             resetIfBuilt();
806             DocumentClassFactoryRegistry registry = DocumentClassFactoryRegistry.getInstance();
807             DocumentClassFactory<?> factory = registry.getOrCreateFactory(documentClass);
808             return setPubliclyVisibleSchema(factory.getSchemaName(), packageIdentifier);
809         }
810 // @exportToFramework:endStrip()
811 
812         /**
813          * Sets the documents from the provided {@code schemaType} can be read by the caller if they
814          * match the ALL visibility requirements set in {@link SchemaVisibilityConfig}.
815          *
816          * <p> The requirements in a {@link SchemaVisibilityConfig} is "AND" relationship. A
817          * caller must match ALL requirements to access the schema. For example, a caller must hold
818          * required permissions AND it is a specified package.
819          *
820          * <p> You can call this method repeatedly to add multiple {@link SchemaVisibilityConfig}s,
821          * and the querier will have access if they match ANY of the
822          * {@link SchemaVisibilityConfig}.
823          *
824          * @param schemaType              The schema type to set visibility on.
825          * @param schemaVisibilityConfig  The {@link SchemaVisibilityConfig} holds all requirements
826          *                                that a call must to match to access the schema.
827          */
828         // Merged list available from getSchemasVisibleToConfigs
829         @CanIgnoreReturnValue
830         @SuppressLint("MissingGetterMatchingBuilder")
831         @FlaggedApi(Flags.FLAG_ENABLE_SET_SCHEMA_VISIBLE_TO_CONFIGS)
832         @RequiresFeature(
833                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
834                 name = Features.SET_SCHEMA_REQUEST_ADD_SCHEMA_TYPE_VISIBLE_TO_CONFIG)
addSchemaTypeVisibleToConfig(@onNull String schemaType, @NonNull SchemaVisibilityConfig schemaVisibilityConfig)835         public @NonNull Builder addSchemaTypeVisibleToConfig(@NonNull String schemaType,
836                 @NonNull SchemaVisibilityConfig schemaVisibilityConfig) {
837             Preconditions.checkNotNull(schemaType);
838             Preconditions.checkNotNull(schemaVisibilityConfig);
839             resetIfBuilt();
840             Set<SchemaVisibilityConfig> visibleToConfigs = mSchemaVisibleToConfigs.get(schemaType);
841             if (visibleToConfigs == null) {
842                 visibleToConfigs = new ArraySet<>();
843                 mSchemaVisibleToConfigs.put(schemaType, visibleToConfigs);
844             }
845             visibleToConfigs.add(schemaVisibilityConfig);
846             return this;
847         }
848 
849         /**  Clears all visible to {@link SchemaVisibilityConfig} for the given schema type. */
850         @CanIgnoreReturnValue
851         @FlaggedApi(Flags.FLAG_ENABLE_SET_SCHEMA_VISIBLE_TO_CONFIGS)
852         @RequiresFeature(
853                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
854                 name = Features.SET_SCHEMA_REQUEST_ADD_SCHEMA_TYPE_VISIBLE_TO_CONFIG)
clearSchemaTypeVisibleToConfigs(@onNull String schemaType)855         public @NonNull Builder clearSchemaTypeVisibleToConfigs(@NonNull String schemaType) {
856             Preconditions.checkNotNull(schemaType);
857             resetIfBuilt();
858             mSchemaVisibleToConfigs.remove(schemaType);
859             return this;
860         }
861 
862         /**
863          * Sets the {@link Migrator} associated with the given SchemaType.
864          *
865          * <p>The {@link Migrator} migrates all {@link GenericDocument}s under given schema type
866          * from the current version number stored in AppSearch to the final version set via
867          * {@link #setVersion}.
868          *
869          * <p>A {@link Migrator} will be invoked if the current version number stored in
870          * AppSearch is different from the final version set via {@link #setVersion} and
871          * {@link Migrator#shouldMigrate} returns {@code true}.
872          *
873          * <p>The target schema type of the output {@link GenericDocument} of
874          * {@link Migrator#onUpgrade} or {@link Migrator#onDowngrade} must exist in this
875          * {@link SetSchemaRequest}.
876          *
877          * @param schemaType The schema type to set migrator on.
878          * @param migrator   The migrator translates a document from its current version to the
879          *                   final version set via {@link #setVersion}.
880          *
881          * @see SetSchemaRequest.Builder#setVersion
882          * @see SetSchemaRequest.Builder#addSchemas
883          * @see AppSearchSession#setSchemaAsync
884          */
885         @CanIgnoreReturnValue
886         @SuppressLint("MissingGetterMatchingBuilder")        // Getter return plural objects.
setMigrator(@onNull String schemaType, @NonNull Migrator migrator)887         public @NonNull Builder setMigrator(@NonNull String schemaType,
888                 @NonNull Migrator migrator) {
889             Preconditions.checkNotNull(schemaType);
890             Preconditions.checkNotNull(migrator);
891             resetIfBuilt();
892             mMigrators.put(schemaType, migrator);
893             return this;
894         }
895 
896         /**
897          * Sets a Map of {@link Migrator}s.
898          *
899          * <p>The key of the map is the schema type that the {@link Migrator} value applies to.
900          *
901          * <p>The {@link Migrator} migrates all {@link GenericDocument}s under given schema type
902          * from the current version number stored in AppSearch to the final version set via
903          * {@link #setVersion}.
904          *
905          * <p>A {@link Migrator} will be invoked if the current version number stored in
906          * AppSearch is different from the final version set via {@link #setVersion} and
907          * {@link Migrator#shouldMigrate} returns {@code true}.
908          *
909          * <p>The target schema type of the output {@link GenericDocument} of
910          * {@link Migrator#onUpgrade} or {@link Migrator#onDowngrade} must exist in this
911          * {@link SetSchemaRequest}.
912          *
913          * @param migrators  A {@link Map} of migrators that translate a document from its current
914          *                   version to the final version set via {@link #setVersion}. The key of
915          *                   the map is the schema type that the {@link Migrator} value applies to.
916          *
917          * @see SetSchemaRequest.Builder#setVersion
918          * @see SetSchemaRequest.Builder#addSchemas
919          * @see AppSearchSession#setSchemaAsync
920          */
921         @CanIgnoreReturnValue
setMigrators(@onNull Map<String, Migrator> migrators)922         public @NonNull Builder setMigrators(@NonNull Map<String, Migrator> migrators) {
923             Preconditions.checkNotNull(migrators);
924             resetIfBuilt();
925             mMigrators.putAll(migrators);
926             return this;
927         }
928 
929         /**
930          * Clears all {@link Migrator}s.
931          */
932         @ExperimentalAppSearchApi
933         @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
934         @CanIgnoreReturnValue
clearMigrators()935         public @NonNull Builder clearMigrators() {
936             resetIfBuilt();
937             mMigrators.clear();
938             return this;
939         }
940 
941 // @exportToFramework:startStrip()
942 
943         /**
944          * Sets whether or not documents from the provided
945          * {@link androidx.appsearch.annotation.Document} annotated class will be displayed and
946          * visible on any system UI surface.
947          *
948          * <p>This setting applies to the provided {@link androidx.appsearch.annotation.Document}
949          * annotated class only, and does not persist across {@link AppSearchSession#setSchemaAsync}
950          * calls.
951          *
952          * <p>The default behavior, if this method is not called, is to allow types to be
953          * displayed on system UI surfaces.
954          *
955          * <p> Merged list available from {@link #getSchemasNotDisplayedBySystem()}.
956          *
957          * @param documentClass A class annotated with
958          *                      {@link androidx.appsearch.annotation.Document}, the visibility of
959          *                      which will be configured
960          * @param displayed     Whether documents of this type will be displayed on system UI
961          *                      surfaces.
962          * @throws AppSearchException if {@code androidx.appsearch.compiler.AppSearchCompiler}
963          *                            has not generated a schema for the given document class.
964          */
965         @CanIgnoreReturnValue
966         @SuppressLint("MissingGetterMatchingBuilder")
setDocumentClassDisplayedBySystem(@onNull Class<?> documentClass, boolean displayed)967         public @NonNull Builder setDocumentClassDisplayedBySystem(@NonNull Class<?> documentClass,
968                 boolean displayed) throws AppSearchException {
969             Preconditions.checkNotNull(documentClass);
970             resetIfBuilt();
971             DocumentClassFactoryRegistry registry = DocumentClassFactoryRegistry.getInstance();
972             DocumentClassFactory<?> factory = registry.getOrCreateFactory(documentClass);
973             return setSchemaTypeDisplayedBySystem(factory.getSchemaName(), displayed);
974         }
975 
976         /**
977          * Sets whether or not documents from the provided
978          * {@link androidx.appsearch.annotation.Document} annotated class can be read by the
979          * specified package.
980          *
981          * <p>Each package is represented by a {@link PackageIdentifier}, containing a package name
982          * and a byte array of type {@link android.content.pm.PackageManager#CERT_INPUT_SHA256}.
983          *
984          * <p>To opt into one-way data sharing with another application, the developer will need to
985          * explicitly grant the other application’s package name and certificate Read access to its
986          * data.
987          *
988          * <p>For two-way data sharing, both applications need to explicitly grant Read access to
989          * one another.
990          *
991          * <p>By default, app data sharing between applications is disabled.
992          *
993          * <p> The relationship between visible packages added in this method and permission
994          * visibility setting {@link #addRequiredPermissionsForSchemaTypeVisibility} is "OR". The
995          * caller could access the schema if they match ANY requirements. If you want to set
996          * "AND" requirements like a caller must hold required permissions AND it is a specified
997          * package, please use {@link #addSchemaTypeVisibleToConfig}.
998          *
999          * <p>Merged list available from {@link #getSchemasVisibleToPackages()}.
1000          *
1001          * @param documentClass     The {@link androidx.appsearch.annotation.Document} class to set
1002          *                          visibility on.
1003          * @param visible           Whether the {@code documentClass} will be visible or not.
1004          * @param packageIdentifier Represents the package that will be granted visibility.
1005          * @throws AppSearchException if {@code androidx.appsearch.compiler.AppSearchCompiler}
1006          *                            has not generated a schema for the given document class.
1007          */
1008         @CanIgnoreReturnValue
1009         @SuppressLint("MissingGetterMatchingBuilder")
setDocumentClassVisibilityForPackage( @onNull Class<?> documentClass, boolean visible, @NonNull PackageIdentifier packageIdentifier)1010         public @NonNull Builder setDocumentClassVisibilityForPackage(
1011                 @NonNull Class<?> documentClass, boolean visible,
1012                 @NonNull PackageIdentifier packageIdentifier) throws AppSearchException {
1013             Preconditions.checkNotNull(documentClass);
1014             resetIfBuilt();
1015             DocumentClassFactoryRegistry registry = DocumentClassFactoryRegistry.getInstance();
1016             DocumentClassFactory<?> factory = registry.getOrCreateFactory(documentClass);
1017             return setSchemaTypeVisibilityForPackage(factory.getSchemaName(), visible,
1018                     packageIdentifier);
1019         }
1020 
1021         /**
1022          * Adds a set of required Android {@link android.Manifest.permission} combination to the
1023          * given schema type.
1024          *
1025          * <p> If the querier holds ALL of the required permissions in this combination, they will
1026          * have access to read {@link GenericDocument} objects of the given schema type.
1027          *
1028          * <p> You can call this method to add multiple permission combinations, and the querier
1029          * will have access if they holds ANY of the combinations.
1030          *
1031          * <p>The supported Permissions are {@link #READ_SMS}, {@link #READ_CALENDAR},
1032          * {@link #READ_CONTACTS}, {@link #READ_EXTERNAL_STORAGE},
1033          * {@link #READ_HOME_APP_SEARCH_DATA} and {@link #READ_ASSISTANT_APP_SEARCH_DATA}.
1034          *
1035          * <p> The relationship between visible packages added in this method and permission
1036          * visibility setting {@link #addRequiredPermissionsForSchemaTypeVisibility} is "OR". The
1037          * caller could access the schema if they match ANY requirements. If you want to set
1038          * "AND" requirements like a caller must hold required permissions AND it is a specified
1039          * package, please use {@link #addSchemaTypeVisibleToConfig}.
1040          *
1041          * <p>Merged map available from {@link #getRequiredPermissionsForSchemaTypeVisibility()}.
1042          * @see android.Manifest.permission#READ_SMS
1043          * @see android.Manifest.permission#READ_CALENDAR
1044          * @see android.Manifest.permission#READ_CONTACTS
1045          * @see android.Manifest.permission#READ_EXTERNAL_STORAGE
1046          * @see android.Manifest.permission#READ_HOME_APP_SEARCH_DATA
1047          * @see android.Manifest.permission#READ_ASSISTANT_APP_SEARCH_DATA
1048          * @param documentClass    The {@link androidx.appsearch.annotation.Document} class to set
1049          *                         visibility on.
1050          * @param permissions      A set of required Android permissions the caller need to hold
1051          *                         to access {@link GenericDocument} objects that under the given
1052          *                         schema.
1053          * @throws IllegalArgumentException – if input unsupported permission.
1054          */
1055         @CanIgnoreReturnValue
1056         @SuppressLint("MissingGetterMatchingBuilder")
1057         @RequiresFeature(
1058                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
1059                 name = Features.ADD_PERMISSIONS_AND_GET_VISIBILITY)
addRequiredPermissionsForDocumentClassVisibility( @onNull Class<?> documentClass, @AppSearchSupportedPermission @NonNull Set<Integer> permissions)1060         public @NonNull Builder addRequiredPermissionsForDocumentClassVisibility(
1061                 @NonNull Class<?> documentClass,
1062                 @AppSearchSupportedPermission @NonNull Set<Integer> permissions)
1063                 throws AppSearchException {
1064             Preconditions.checkNotNull(documentClass);
1065             resetIfBuilt();
1066             DocumentClassFactoryRegistry registry = DocumentClassFactoryRegistry.getInstance();
1067             DocumentClassFactory<?> factory = registry.getOrCreateFactory(documentClass);
1068             return addRequiredPermissionsForSchemaTypeVisibility(
1069                     factory.getSchemaName(), permissions);
1070         }
1071 
1072         /**  Clears all required permissions combinations for the given schema type.  */
1073         @CanIgnoreReturnValue
1074         @RequiresFeature(
1075                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
1076                 name = Features.ADD_PERMISSIONS_AND_GET_VISIBILITY)
clearRequiredPermissionsForDocumentClassVisibility( @onNull Class<?> documentClass)1077         public @NonNull Builder clearRequiredPermissionsForDocumentClassVisibility(
1078                 @NonNull Class<?> documentClass)
1079                 throws AppSearchException {
1080             Preconditions.checkNotNull(documentClass);
1081             resetIfBuilt();
1082             DocumentClassFactoryRegistry registry = DocumentClassFactoryRegistry.getInstance();
1083             DocumentClassFactory<?> factory = registry.getOrCreateFactory(documentClass);
1084             return clearRequiredPermissionsForSchemaTypeVisibility(factory.getSchemaName());
1085         }
1086 
1087         /**
1088          * Sets the documents from the provided {@code schemaType} can be read by the caller if they
1089          * match the ALL visibility requirements set in {@link SchemaVisibilityConfig}.
1090          *
1091          * <p> The requirements in a {@link SchemaVisibilityConfig} is "AND" relationship. A
1092          * caller must match ALL requirements to access the schema. For example, a caller must hold
1093          * required permissions AND it is a specified package.
1094          *
1095          * <p> You can call this method repeatedly to add multiple {@link SchemaVisibilityConfig}s,
1096          * and the querier will have access if they match ANY of the {@link SchemaVisibilityConfig}.
1097          *
1098          * @param documentClass            A class annotated with
1099          *                                 {@link androidx.appsearch.annotation.Document}, the
1100          *                                 visibility of which will be configured
1101          * @param schemaVisibilityConfig   The {@link SchemaVisibilityConfig} holds all
1102          *                                 requirements that a call must to match to access the
1103          *                                 schema.
1104          */
1105         // Merged list available from getSchemasVisibleToConfigs
1106         @SuppressLint("MissingGetterMatchingBuilder")
1107         @RequiresFeature(
1108                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
1109                 name = Features.SET_SCHEMA_REQUEST_ADD_SCHEMA_TYPE_VISIBLE_TO_CONFIG)
1110         @FlaggedApi(Flags.FLAG_ENABLE_SET_SCHEMA_VISIBLE_TO_CONFIGS)
addDocumentClassVisibleToConfig( @onNull Class<?> documentClass, @NonNull SchemaVisibilityConfig schemaVisibilityConfig)1111         public @NonNull Builder addDocumentClassVisibleToConfig(
1112                 @NonNull Class<?> documentClass,
1113                 @NonNull SchemaVisibilityConfig schemaVisibilityConfig)
1114                 throws AppSearchException {
1115             Preconditions.checkNotNull(documentClass);
1116             resetIfBuilt();
1117             DocumentClassFactoryRegistry registry = DocumentClassFactoryRegistry.getInstance();
1118             DocumentClassFactory<?> factory = registry.getOrCreateFactory(documentClass);
1119             return addSchemaTypeVisibleToConfig(factory.getSchemaName(),
1120                     schemaVisibilityConfig);
1121         }
1122 
1123         /**  Clears all visible to {@link SchemaVisibilityConfig} for the given schema type. */
1124         @RequiresFeature(
1125                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
1126                 name = Features.SET_SCHEMA_REQUEST_ADD_SCHEMA_TYPE_VISIBLE_TO_CONFIG)
1127         @FlaggedApi(Flags.FLAG_ENABLE_SET_SCHEMA_VISIBLE_TO_CONFIGS)
clearDocumentClassVisibleToConfigs( @onNull Class<?> documentClass)1128         public @NonNull Builder clearDocumentClassVisibleToConfigs(
1129                 @NonNull Class<?> documentClass) throws AppSearchException {
1130             Preconditions.checkNotNull(documentClass);
1131             resetIfBuilt();
1132             DocumentClassFactoryRegistry registry = DocumentClassFactoryRegistry.getInstance();
1133             DocumentClassFactory<?> factory = registry.getOrCreateFactory(documentClass);
1134             return clearSchemaTypeVisibleToConfigs(factory.getSchemaName());
1135         }
1136 // @exportToFramework:endStrip()
1137 
1138         /**
1139          * Sets whether or not to override the current schema in the {@link AppSearchSession}
1140          * database.
1141          *
1142          * <p>Call this method whenever backward incompatible changes need to be made by setting
1143          * {@code forceOverride} to {@code true}. As a result, during execution of the setSchema
1144          * operation, all documents that are incompatible with the new schema will be deleted and
1145          * the new schema will be saved and persisted.
1146          *
1147          * <p>By default, this is {@code false}.
1148          */
1149         @CanIgnoreReturnValue
setForceOverride(boolean forceOverride)1150         public @NonNull Builder setForceOverride(boolean forceOverride) {
1151             resetIfBuilt();
1152             mForceOverride = forceOverride;
1153             return this;
1154         }
1155 
1156         /**
1157          * Sets the version number of the overall {@link AppSearchSchema} in the database.
1158          *
1159          * <p>The {@link AppSearchSession} database can only ever hold documents for one version
1160          * at a time.
1161          *
1162          * <p>Setting a version number that is different from the version number currently stored
1163          * in AppSearch will result in AppSearch calling the {@link Migrator}s provided to
1164          * {@link AppSearchSession#setSchemaAsync} to migrate the documents already in AppSearch from
1165          * the previous version to the one set in this request. The version number can be
1166          * updated without any other changes to the set of schemas.
1167          *
1168          * <p>The version number can stay the same, increase, or decrease relative to the current
1169          * version number that is already stored in the {@link AppSearchSession} database.
1170          *
1171          * <p>The version of an empty database will always be 0. You cannot set version to the
1172          * {@link SetSchemaRequest}, if it doesn't contains any {@link AppSearchSchema}.
1173          *
1174          * @param version A positive integer representing the version of the entire set of
1175          *                schemas represents the version of the whole schema in the
1176          *                {@link AppSearchSession} database, default version is 1.
1177          *
1178          * @throws IllegalArgumentException if the version is negative.
1179          *
1180          * @see AppSearchSession#setSchemaAsync
1181          * @see Migrator
1182          * @see SetSchemaRequest.Builder#setMigrator
1183          */
1184         @CanIgnoreReturnValue
setVersion(@ntRangefrom = 1) int version)1185         public @NonNull Builder setVersion(@IntRange(from = 1) int version) {
1186             Preconditions.checkArgument(version >= 1, "Version must be a positive number.");
1187             resetIfBuilt();
1188             mVersion = version;
1189             return this;
1190         }
1191 
1192         /**
1193          * Builds a new {@link SetSchemaRequest} object.
1194          *
1195          * @throws IllegalArgumentException if schema types were referenced, but the
1196          *                                  corresponding {@link AppSearchSchema} type was never
1197          *                                  added.
1198          */
build()1199         public @NonNull SetSchemaRequest build() {
1200             // Verify that any schema types with display or visibility settings refer to a real
1201             // schema.
1202             // Create a copy because we're going to remove from the set for verification purposes.
1203             Set<String> referencedSchemas = new ArraySet<>(mSchemasNotDisplayedBySystem);
1204             referencedSchemas.addAll(mSchemasVisibleToPackages.keySet());
1205             referencedSchemas.addAll(mSchemasVisibleToPermissions.keySet());
1206             referencedSchemas.addAll(mPubliclyVisibleSchemas.keySet());
1207             referencedSchemas.addAll(mSchemaVisibleToConfigs.keySet());
1208 
1209             for (AppSearchSchema schema : mSchemas) {
1210                 referencedSchemas.remove(schema.getSchemaType());
1211             }
1212             if (!referencedSchemas.isEmpty()) {
1213                 // We still have schema types that weren't seen in our mSchemas set. This means
1214                 // there wasn't a corresponding AppSearchSchema.
1215                 throw new IllegalArgumentException(
1216                         "Schema types " + referencedSchemas + " referenced, but were not added.");
1217             }
1218             if (mSchemas.isEmpty() && mVersion != DEFAULT_VERSION) {
1219                 throw new IllegalArgumentException(
1220                         "Cannot set version to the request if schema is empty.");
1221             }
1222             mBuilt = true;
1223             return new SetSchemaRequest(
1224                     mSchemas,
1225                     mSchemasNotDisplayedBySystem,
1226                     mSchemasVisibleToPackages,
1227                     mSchemasVisibleToPermissions,
1228                     mPubliclyVisibleSchemas,
1229                     mSchemaVisibleToConfigs,
1230                     mMigrators,
1231                     mForceOverride,
1232                     mVersion);
1233         }
1234 
resetIfBuilt()1235         private void resetIfBuilt() {
1236             if (mBuilt) {
1237                 ArrayMap<String, Set<PackageIdentifier>> schemasVisibleToPackages =
1238                         new ArrayMap<>(mSchemasVisibleToPackages.size());
1239                 for (Map.Entry<String, Set<PackageIdentifier>> entry
1240                         : mSchemasVisibleToPackages.entrySet()) {
1241                     schemasVisibleToPackages.put(entry.getKey(), new ArraySet<>(entry.getValue()));
1242                 }
1243                 mSchemasVisibleToPackages = schemasVisibleToPackages;
1244 
1245                 mPubliclyVisibleSchemas = new ArrayMap<>(mPubliclyVisibleSchemas);
1246 
1247                 mSchemasVisibleToPermissions = deepCopy(mSchemasVisibleToPermissions);
1248 
1249                 ArrayMap<String, Set<SchemaVisibilityConfig>> schemaVisibleToConfigs =
1250                         new ArrayMap<>(mSchemaVisibleToConfigs.size());
1251                 for (Map.Entry<String, Set<SchemaVisibilityConfig>> entry :
1252                         mSchemaVisibleToConfigs.entrySet()) {
1253                     schemaVisibleToConfigs.put(entry.getKey(), new ArraySet<>(entry.getValue()));
1254                 }
1255                 mSchemaVisibleToConfigs = schemaVisibleToConfigs;
1256 
1257                 mSchemas = new ArraySet<>(mSchemas);
1258                 mSchemasNotDisplayedBySystem = new ArraySet<>(mSchemasNotDisplayedBySystem);
1259                 mMigrators = new ArrayMap<>(mMigrators);
1260                 mBuilt = false;
1261             }
1262         }
1263     }
1264 
deepCopy( @onNull Map<String, Set<Set<Integer>>> original)1265     private static ArrayMap<String, Set<Set<Integer>>> deepCopy(
1266             @NonNull Map<String, Set<Set<Integer>>> original) {
1267         ArrayMap<String, Set<Set<Integer>>> copy = new ArrayMap<>(original.size());
1268         for (Map.Entry<String, Set<Set<Integer>>> entry : original.entrySet()) {
1269             Set<Set<Integer>> valueCopy = new ArraySet<>();
1270             for (Set<Integer> innerValue : entry.getValue()) {
1271                 valueCopy.add(new ArraySet<>(innerValue));
1272             }
1273             copy.put(entry.getKey(), valueCopy);
1274         }
1275         return copy;
1276     }
1277 }
1278