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