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