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