1 /*
2  * Copyright 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.appsearch.app;
18 
19 import android.annotation.SuppressLint;
20 import android.os.Parcel;
21 import android.os.Parcelable;
22 
23 import androidx.annotation.IntRange;
24 import androidx.annotation.NonNull;
25 import androidx.annotation.Nullable;
26 import androidx.annotation.RequiresFeature;
27 import androidx.annotation.RestrictTo;
28 import androidx.appsearch.annotation.CanIgnoreReturnValue;
29 import androidx.appsearch.flags.FlaggedApi;
30 import androidx.appsearch.flags.Flags;
31 import androidx.appsearch.safeparcel.AbstractSafeParcelable;
32 import androidx.appsearch.safeparcel.SafeParcelable;
33 import androidx.appsearch.safeparcel.stub.StubCreators.GetSchemaResponseCreator;
34 import androidx.collection.ArrayMap;
35 import androidx.collection.ArraySet;
36 import androidx.core.util.Preconditions;
37 
38 import java.util.ArrayList;
39 import java.util.Collections;
40 import java.util.List;
41 import java.util.Map;
42 import java.util.Set;
43 
44 /** The response class of {@link AppSearchSession#getSchemaAsync} */
45 @SafeParcelable.Class(creator = "GetSchemaResponseCreator")
46 // TODO(b/384721898): Switch to JSpecify annotations
47 @SuppressWarnings({"HiddenSuperclass", "JSpecifyNullness"})
48 public final class GetSchemaResponse extends AbstractSafeParcelable {
49     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
50     @FlaggedApi(Flags.FLAG_ENABLE_SAFE_PARCELABLE_2)
51     public static final @NonNull Parcelable.Creator<GetSchemaResponse> CREATOR =
52             new GetSchemaResponseCreator();
53 
54     @Field(id = 1, getter = "getVersion")
55     private final int mVersion;
56 
57     @Field(id = 2)
58     final List<AppSearchSchema> mSchemas;
59 
60     /**
61      * List of VisibilityConfigs for the current schema. May be {@code null} if retrieving the
62      * visibility settings is not possible on the current backend.
63      */
64     @Field(id = 3)
65     final @Nullable List<InternalVisibilityConfig> mVisibilityConfigs;
66 
67     /**
68      * This set contains all schemas most recently successfully provided to
69      * {@link AppSearchSession#setSchemaAsync}. We do lazy fetch, the object will be created when
70      * you first time fetch it.
71      */
72     private @Nullable Set<AppSearchSchema> mSchemasCached;
73 
74     /**
75      * This Set contains all schemas that are not displayed by the system. All values in the set are
76      * prefixed with the package-database prefix. We do lazy fetch, the object will be created
77      * when you first time fetch it.
78      */
79     private @Nullable Set<String> mSchemasNotDisplayedBySystemCached;
80 
81     /**
82      * This map contains all schemas and {@link PackageIdentifier} that has access to the schema.
83      * All keys in the map are prefixed with the package-database prefix. We do lazy fetch, the
84      * object will be created when you first time fetch it.
85      */
86     private @Nullable Map<String, Set<PackageIdentifier>> mSchemasVisibleToPackagesCached;
87 
88     /**
89      * This map contains all schemas and Android Permissions combinations that are required to
90      * access the schema. All keys in the map are prefixed with the package-database prefix. We
91      * do lazy fetch, the object will be created when you first time fetch it.
92      * The Map is constructed in ANY-ALL cases. The querier could read the {@link GenericDocument}
93      * objects under the {@code schemaType} if they hold ALL required permissions of ANY
94      * combinations.
95      * @see SetSchemaRequest.Builder#addRequiredPermissionsForSchemaTypeVisibility(String, Set)
96      */
97     private @Nullable Map<String, Set<Set<Integer>>> mSchemasVisibleToPermissionsCached;
98 
99     /**
100      * This map contains all publicly visible schemas and the {@link PackageIdentifier} specifying
101      * the package that the schemas are from.
102      */
103     private @Nullable Map<String, PackageIdentifier> mPubliclyVisibleSchemasCached;
104 
105     /**
106      * This map contains all {@link SchemaVisibilityConfig}s that has access to the schema.
107      * All keys in the map are prefixed with the package-database prefix. We do lazy fetch, the
108      * object will be created when you first time fetch it.
109      */
110     private @Nullable Map<String, Set<SchemaVisibilityConfig>> mSchemasVisibleToConfigsCached;
111 
112     @Constructor
GetSchemaResponse( @aramid = 1) int version, @Param(id = 2) @NonNull List<AppSearchSchema> schemas, @Param(id = 3) @Nullable List<InternalVisibilityConfig> visibilityConfigs)113     GetSchemaResponse(
114             @Param(id = 1) int version,
115             @Param(id = 2) @NonNull List<AppSearchSchema> schemas,
116             @Param(id = 3) @Nullable List<InternalVisibilityConfig> visibilityConfigs) {
117         mVersion = version;
118         mSchemas = Preconditions.checkNotNull(schemas);
119         mVisibilityConfigs = visibilityConfigs;
120     }
121 
122     /**
123      * Returns the overall database schema version.
124      *
125      * <p>If the database is empty, 0 will be returned.
126      */
127     @IntRange(from = 0)
getVersion()128     public int getVersion() {
129         return mVersion;
130     }
131 
132     /**
133      * Return the schemas most recently successfully provided to
134      * {@link AppSearchSession#setSchemaAsync}.
135      */
getSchemas()136     public @NonNull Set<AppSearchSchema> getSchemas() {
137         if (mSchemasCached == null) {
138             mSchemasCached = Collections.unmodifiableSet(new ArraySet<>(mSchemas));
139         }
140         return mSchemasCached;
141     }
142 
143     /**
144      * Returns all the schema types that are opted out of being displayed and visible on any
145      * system UI surface.
146      * <!--@exportToFramework:ifJetpack()-->
147      * @throws UnsupportedOperationException if {@link Builder#setVisibilitySettingSupported} was
148      * called with false.
149      * <!--@exportToFramework:else()-->
150      */
151     @RequiresFeature(
152             enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
153             name = Features.ADD_PERMISSIONS_AND_GET_VISIBILITY)
getSchemaTypesNotDisplayedBySystem()154     public @NonNull Set<String> getSchemaTypesNotDisplayedBySystem() {
155         List<InternalVisibilityConfig> visibilityConfigs = getVisibilityConfigsOrThrow();
156         if (mSchemasNotDisplayedBySystemCached == null) {
157             Set<String> copy = new ArraySet<>();
158             for (int i = 0; i < visibilityConfigs.size(); i++) {
159                 if (visibilityConfigs.get(i).isNotDisplayedBySystem()) {
160                     copy.add(visibilityConfigs.get(i).getSchemaType());
161                 }
162             }
163             mSchemasNotDisplayedBySystemCached = Collections.unmodifiableSet(copy);
164         }
165         return mSchemasNotDisplayedBySystemCached;
166     }
167 
168     /**
169      * Returns a mapping of schema types to the set of packages that have access
170      * to that schema type.
171      * <!--@exportToFramework:ifJetpack()-->
172      * @throws UnsupportedOperationException if {@link Builder#setVisibilitySettingSupported} was
173      * called with false.
174      * <!--@exportToFramework:else()-->
175      */
176     @RequiresFeature(
177             enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
178             name = Features.ADD_PERMISSIONS_AND_GET_VISIBILITY)
getSchemaTypesVisibleToPackages()179     public @NonNull Map<String, Set<PackageIdentifier>> getSchemaTypesVisibleToPackages() {
180         List<InternalVisibilityConfig> visibilityConfigs = getVisibilityConfigsOrThrow();
181         if (mSchemasVisibleToPackagesCached == null) {
182             Map<String, Set<PackageIdentifier>> copy = new ArrayMap<>();
183             for (int i = 0; i < visibilityConfigs.size(); i++) {
184                 InternalVisibilityConfig visibilityConfig = visibilityConfigs.get(i);
185                 List<PackageIdentifier> visibleToPackages =
186                         visibilityConfig.getVisibilityConfig().getAllowedPackages();
187                 if (!visibleToPackages.isEmpty()) {
188                     copy.put(
189                             visibilityConfig.getSchemaType(),
190                             Collections.unmodifiableSet(new ArraySet<>(visibleToPackages)));
191                 }
192             }
193             mSchemasVisibleToPackagesCached = Collections.unmodifiableMap(copy);
194         }
195         return mSchemasVisibleToPackagesCached;
196     }
197 
198     /**
199      * Returns a mapping of schema types to the set of {@link android.Manifest.permission}
200      * combination sets that querier must hold to access that schema type.
201      *
202      * <p> The querier could read the {@link GenericDocument} objects under the {@code schemaType}
203      * if they holds ALL required permissions of ANY of the individual value sets.
204      *
205      * <p>For example, if the Map contains {@code {% verbatim %}{{permissionA, PermissionB},
206      * { PermissionC, PermissionD}, {PermissionE}}{% endverbatim %}}.
207      * <ul>
208      *     <li>A querier holding both PermissionA and PermissionB has access.</li>
209      *     <li>A querier holding both PermissionC and PermissionD has access.</li>
210      *     <li>A querier holding only PermissionE has access.</li>
211      *     <li>A querier holding both PermissionA and PermissionE has access.</li>
212      *     <li>A querier holding only PermissionA doesn't have access.</li>
213      *     <li>A querier holding only PermissionA and PermissionC doesn't have access.</li>
214      * </ul>
215      *
216      * @return The map contains schema type and all combinations of required permission for querier
217      *         to access it. The supported Permission are {@link SetSchemaRequest#READ_SMS},
218      *         {@link SetSchemaRequest#READ_CALENDAR}, {@link SetSchemaRequest#READ_CONTACTS},
219      *         {@link SetSchemaRequest#READ_EXTERNAL_STORAGE},
220      *         {@link SetSchemaRequest#READ_HOME_APP_SEARCH_DATA} and
221      *         {@link SetSchemaRequest#READ_ASSISTANT_APP_SEARCH_DATA}.
222      * <!--@exportToFramework:ifJetpack()-->
223      * @throws UnsupportedOperationException if {@link Builder#setVisibilitySettingSupported} was
224      * called with false.
225      * <!--@exportToFramework:else()-->
226      */
227     // TODO(b/237388235): add enterprise permissions to javadocs after they're unhidden
228     // Annotation is here to suppress lint error. Lint error is erroneous since the method does not
229     // require the caller to hold any permission for the method to function.
230     @SuppressLint("RequiresPermission")
231     @RequiresFeature(
232             enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
233             name = Features.ADD_PERMISSIONS_AND_GET_VISIBILITY)
getRequiredPermissionsForSchemaTypeVisibility()234     public @NonNull Map<String, Set<Set<Integer>>> getRequiredPermissionsForSchemaTypeVisibility() {
235         List<InternalVisibilityConfig> visibilityConfigs = getVisibilityConfigsOrThrow();
236         if (mSchemasVisibleToPermissionsCached == null) {
237             Map<String, Set<Set<Integer>>> copy = new ArrayMap<>();
238             for (int i = 0; i < visibilityConfigs.size(); i++) {
239                 InternalVisibilityConfig visibilityConfig = visibilityConfigs.get(i);
240                 Set<Set<Integer>> visibleToPermissions =
241                         visibilityConfig.getVisibilityConfig().getRequiredPermissions();
242                 if (!visibleToPermissions.isEmpty()) {
243                     copy.put(
244                             visibilityConfig.getSchemaType(),
245                             Collections.unmodifiableSet(visibleToPermissions));
246                 }
247             }
248             mSchemasVisibleToPermissionsCached = Collections.unmodifiableMap(copy);
249         }
250         return mSchemasVisibleToPermissionsCached;
251     }
252 
253     /**
254      * Returns a mapping of publicly visible schemas to the {@link PackageIdentifier} specifying
255      * the package the schemas are from.
256      *
257      * <p> If no schemas have been set as publicly visible, an empty set will be returned.
258      * <!--@exportToFramework:ifJetpack()-->
259      * @throws UnsupportedOperationException if {@link Builder#setVisibilitySettingSupported} was
260      * called with false.
261      * <!--@exportToFramework:else()-->
262      */
263     @FlaggedApi(Flags.FLAG_ENABLE_SET_PUBLICLY_VISIBLE_SCHEMA)
264     @RequiresFeature(
265             enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
266             name = Features.ADD_PERMISSIONS_AND_GET_VISIBILITY)
getPubliclyVisibleSchemas()267     public @NonNull Map<String, PackageIdentifier> getPubliclyVisibleSchemas() {
268         List<InternalVisibilityConfig> visibilityConfigs = getVisibilityConfigsOrThrow();
269         if (mPubliclyVisibleSchemasCached == null) {
270             Map<String, PackageIdentifier> copy = new ArrayMap<>();
271             for (int i = 0; i < visibilityConfigs.size(); i++) {
272                 InternalVisibilityConfig visibilityConfig = visibilityConfigs.get(i);
273                 PackageIdentifier publiclyVisibleTargetPackage =
274                         visibilityConfig.getVisibilityConfig().getPubliclyVisibleTargetPackage();
275                 if (publiclyVisibleTargetPackage != null) {
276                     copy.put(visibilityConfig.getSchemaType(), publiclyVisibleTargetPackage);
277                 }
278             }
279             mPubliclyVisibleSchemasCached = Collections.unmodifiableMap(copy);
280         }
281         return mPubliclyVisibleSchemasCached;
282     }
283 
284     /**
285      * Returns a mapping of schema types to the set of {@link SchemaVisibilityConfig} that have
286      * access to that schema type.
287      *
288      * @see SetSchemaRequest.Builder#addSchemaTypeVisibleToConfig
289      */
290     @FlaggedApi(Flags.FLAG_ENABLE_SET_SCHEMA_VISIBLE_TO_CONFIGS)
291     @RequiresFeature(
292             enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
293             name = Features.ADD_PERMISSIONS_AND_GET_VISIBILITY)
getSchemaTypesVisibleToConfigs()294     public @NonNull Map<String, Set<SchemaVisibilityConfig>> getSchemaTypesVisibleToConfigs() {
295         List<InternalVisibilityConfig> visibilityConfigs = getVisibilityConfigsOrThrow();
296         if (mSchemasVisibleToConfigsCached == null) {
297             Map<String, Set<SchemaVisibilityConfig>> copy = new ArrayMap<>();
298             for (int i = 0; i < visibilityConfigs.size(); i++) {
299                 InternalVisibilityConfig visibilityConfig = visibilityConfigs.get(i);
300                 Set<SchemaVisibilityConfig> nestedVisibilityConfigs =
301                         visibilityConfig.getVisibleToConfigs();
302                 if (!nestedVisibilityConfigs.isEmpty()) {
303                     copy.put(visibilityConfig.getSchemaType(),
304                             Collections.unmodifiableSet(nestedVisibilityConfigs));
305                 }
306             }
307             mSchemasVisibleToConfigsCached = Collections.unmodifiableMap(copy);
308         }
309         return mSchemasVisibleToConfigsCached;
310     }
311 
getVisibilityConfigsOrThrow()312     private @NonNull List<InternalVisibilityConfig> getVisibilityConfigsOrThrow() {
313         List<InternalVisibilityConfig> visibilityConfigs = mVisibilityConfigs;
314         if (visibilityConfigs == null) {
315             throw new UnsupportedOperationException("Get visibility setting is not supported with "
316                     + "this backend/Android API level combination.");
317         }
318         return visibilityConfigs;
319     }
320 
321     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
322     @FlaggedApi(Flags.FLAG_ENABLE_SAFE_PARCELABLE_2)
323     @Override
writeToParcel(@onNull Parcel dest, int flags)324     public void writeToParcel(@NonNull Parcel dest, int flags) {
325         GetSchemaResponseCreator.writeToParcel(this, dest, flags);
326     }
327 
328     /** Builder for {@link GetSchemaResponse} objects. */
329     public static final class Builder {
330         private int mVersion = 0;
331         private ArrayList<AppSearchSchema> mSchemas = new ArrayList<>();
332         /**
333          * Creates the object when we actually set them. If we never set visibility settings, we
334          * should throw {@link UnsupportedOperationException} in the visibility getters.
335          */
336         private @Nullable Map<String, InternalVisibilityConfig.Builder> mVisibilityConfigBuilders;
337         private boolean mBuilt = false;
338 
339         /** Creates a new {@link Builder} */
Builder()340         public Builder() {
341             setVisibilitySettingSupported(true);
342         }
343 
344         /** Creates a new {@link Builder} from the given {@link GetSchemaResponse}. */
345         @ExperimentalAppSearchApi
346         @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
Builder(@onNull GetSchemaResponse getSchemaResponse)347         public Builder(@NonNull GetSchemaResponse getSchemaResponse) {
348             setVisibilitySettingSupported(true);
349             mVersion = getSchemaResponse.mVersion;
350             mSchemas.addAll(getSchemaResponse.mSchemas);
351             if (getSchemaResponse.mVisibilityConfigs != null) {
352                 int count = getSchemaResponse.mVisibilityConfigs.size();
353                 for (int i = 0; i < count; i++) {
354                     InternalVisibilityConfig config = getSchemaResponse.mVisibilityConfigs.get(i);
355                     mVisibilityConfigBuilders.put(config.getSchemaType(),
356                             new InternalVisibilityConfig.Builder(config));
357                 }
358             }
359         }
360 
361         /**
362          * Sets the database overall schema version.
363          *
364          * <p>Default version is 0
365          */
366         @CanIgnoreReturnValue
setVersion(@ntRangefrom = 0) int version)367         public @NonNull Builder setVersion(@IntRange(from = 0) int version) {
368             Preconditions.checkArgument(version >= 0, "Version must be a non-negative number.");
369             resetIfBuilt();
370             mVersion = version;
371             return this;
372         }
373 
374         /** Adds one {@link AppSearchSchema} to the schema list. */
375         @CanIgnoreReturnValue
addSchema(@onNull AppSearchSchema schema)376         public @NonNull Builder addSchema(@NonNull AppSearchSchema schema) {
377             Preconditions.checkNotNull(schema);
378             resetIfBuilt();
379             mSchemas.add(schema);
380             return this;
381         }
382 
383         /** Clears all {@link AppSearchSchema}s from the list of schemas. */
384         @ExperimentalAppSearchApi
385         @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
386         @CanIgnoreReturnValue
clearSchemas()387         public @NonNull Builder clearSchemas() {
388             resetIfBuilt();
389             mSchemas.clear();
390             return this;
391         }
392 
393         /**
394          * Sets whether or not documents from the provided {@code schemaType} will be displayed
395          * and visible on any system UI surface.
396          *
397          * @param schemaType The name of an {@link AppSearchSchema} within the same
398          *                   {@link GetSchemaResponse}, which won't be displayed by system.
399          */
400         // Getter getSchemaTypesNotDisplayedBySystem returns plural objects.
401         @CanIgnoreReturnValue
402         @SuppressLint("MissingGetterMatchingBuilder")
addSchemaTypeNotDisplayedBySystem(@onNull String schemaType)403         public @NonNull Builder addSchemaTypeNotDisplayedBySystem(@NonNull String schemaType) {
404             Preconditions.checkNotNull(schemaType);
405             resetIfBuilt();
406             InternalVisibilityConfig.Builder visibilityConfigBuilder =
407                     getOrCreateVisibilityConfigBuilder(schemaType);
408             visibilityConfigBuilder.setNotDisplayedBySystem(true);
409             return this;
410         }
411 
412         /**
413          * Clears the visibility setting for the given schema type that prevents the schema from
414          * being displayed and visible on any system UI surface.
415          *
416          * @see Builder#addSchemaTypeNotDisplayedBySystem
417          */
418         @ExperimentalAppSearchApi
419         @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
420         @CanIgnoreReturnValue
clearSchemaTypeNotDisplayedBySystem(@onNull String schemaType)421         public @NonNull Builder clearSchemaTypeNotDisplayedBySystem(@NonNull String schemaType) {
422             Preconditions.checkNotNull(schemaType);
423             resetIfBuilt();
424             InternalVisibilityConfig.Builder visibilityConfigBuilder =
425                     getVisibilityConfigBuilder(schemaType);
426             if (visibilityConfigBuilder != null) {
427                 visibilityConfigBuilder.setNotDisplayedBySystem(false);
428             }
429             return this;
430         }
431 
432         /**
433          * Sets whether or not documents from the provided {@code schemaType} can be read by the
434          * specified package.
435          *
436          * <p>Each package is represented by a {@link PackageIdentifier}, containing a package name
437          * and a byte array of type {@link android.content.pm.PackageManager#CERT_INPUT_SHA256}.
438          *
439          * <p>To opt into one-way data sharing with another application, the developer will need to
440          * explicitly grant the other application’s package name and certificate Read access to its
441          * data.
442          *
443          * <p>For two-way data sharing, both applications need to explicitly grant Read access to
444          * one another.
445          *
446          * @param schemaType               The schema type to set visibility on.
447          * @param packageIdentifiers       Represents the package that has access to the given
448          *                                 schema type.
449          */
450         // Getter getSchemaTypesVisibleToPackages returns a map contains all schema types.
451         @CanIgnoreReturnValue
452         @SuppressLint("MissingGetterMatchingBuilder")
setSchemaTypeVisibleToPackages( @onNull String schemaType, @NonNull Set<PackageIdentifier> packageIdentifiers)453         public @NonNull Builder setSchemaTypeVisibleToPackages(
454                 @NonNull String schemaType,
455                 @NonNull Set<PackageIdentifier> packageIdentifiers) {
456             Preconditions.checkNotNull(schemaType);
457             Preconditions.checkNotNull(packageIdentifiers);
458             resetIfBuilt();
459             InternalVisibilityConfig.Builder visibilityConfigBuilder =
460                     getOrCreateVisibilityConfigBuilder(schemaType);
461             for (PackageIdentifier packageIdentifier : packageIdentifiers) {
462                 visibilityConfigBuilder.addVisibleToPackage(packageIdentifier);
463             }
464             return this;
465         }
466 
467         /**
468          * Clears the set of packages that can read the given schema type.
469          *
470          * @see Builder#setSchemaTypeVisibleToPackages
471          */
472         @ExperimentalAppSearchApi
473         @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
474         @CanIgnoreReturnValue
clearSchemaTypeVisibleToPackages(@onNull String schemaType)475         public @NonNull Builder clearSchemaTypeVisibleToPackages(@NonNull String schemaType) {
476             Preconditions.checkNotNull(schemaType);
477             resetIfBuilt();
478             InternalVisibilityConfig.Builder visibilityConfigBuilder =
479                     getVisibilityConfigBuilder(schemaType);
480             if (visibilityConfigBuilder != null) {
481                 visibilityConfigBuilder.clearVisibleToPackages();
482             }
483             return this;
484         }
485 
486         /**
487          * Sets a set of required {@link android.Manifest.permission} combinations to the given
488          * schema type.
489          *
490          * <p> The querier could read the {@link GenericDocument} objects under the
491          * {@code schemaType} if they holds ALL required permissions of ANY of the individual value
492          * sets.
493          *
494          * <p>For example, if the Map contains {@code {% verbatim %}{{permissionA, PermissionB},
495          * {PermissionC, PermissionD}, {PermissionE}}{% endverbatim %}}.
496          * <ul>
497          *     <li>A querier holds both PermissionA and PermissionB has access.</li>
498          *     <li>A querier holds both PermissionC and PermissionD has access.</li>
499          *     <li>A querier holds only PermissionE has access.</li>
500          *     <li>A querier holds both PermissionA and PermissionE has access.</li>
501          *     <li>A querier holds only PermissionA doesn't have access.</li>
502          *     <li>A querier holds both PermissionA and PermissionC doesn't have access.</li>
503          * </ul>
504          *
505          * @param schemaType              The schema type to set visibility on.
506          * @param visibleToPermissionSets The Sets of Android permissions that will be required to
507          *                                access the given schema.
508          * @see android.Manifest.permission#READ_SMS
509          * @see android.Manifest.permission#READ_CALENDAR
510          * @see android.Manifest.permission#READ_CONTACTS
511          * @see android.Manifest.permission#READ_EXTERNAL_STORAGE
512          * @see android.Manifest.permission#READ_HOME_APP_SEARCH_DATA
513          * @see android.Manifest.permission#READ_ASSISTANT_APP_SEARCH_DATA
514          */
515         // TODO(b/237388235): add enterprise permissions to javadocs after they're unhidden
516         // Getter getRequiredPermissionsForSchemaTypeVisibility returns a map for all schemaTypes.
517         // To use this API doesn't require permissions.
518         @CanIgnoreReturnValue
519         @SuppressLint({"MissingGetterMatchingBuilder", "RequiresPermission"})
520         // @SetSchemaRequest is an IntDef annotation applied to Set<Set<Integer>>.
521         @SuppressWarnings("SupportAnnotationUsage")
setRequiredPermissionsForSchemaTypeVisibility( @onNull String schemaType, @SetSchemaRequest.AppSearchSupportedPermission @NonNull Set<Set<Integer>> visibleToPermissionSets)522         public @NonNull Builder setRequiredPermissionsForSchemaTypeVisibility(
523                 @NonNull String schemaType,
524                 @SetSchemaRequest.AppSearchSupportedPermission
525                 @NonNull Set<Set<Integer>> visibleToPermissionSets) {
526             Preconditions.checkNotNull(schemaType);
527             Preconditions.checkNotNull(visibleToPermissionSets);
528             resetIfBuilt();
529             InternalVisibilityConfig.Builder visibilityConfigBuilder =
530                     getOrCreateVisibilityConfigBuilder(schemaType);
531             for (Set<Integer> visibleToPermissions : visibleToPermissionSets) {
532                 visibilityConfigBuilder.addVisibleToPermissions(visibleToPermissions);
533             }
534             return this;
535         }
536 
537         /**
538          * Clears the set of required {@link android.Manifest.permission} combinations to read the
539          * given schema type.
540          *
541          * @see Builder#setRequiredPermissionsForSchemaTypeVisibility
542          */
543         // To use this API doesn't require permissions.
544         @ExperimentalAppSearchApi
545         @SuppressLint("RequiresPermission")
546         @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
547         @CanIgnoreReturnValue
clearRequiredPermissionsForSchemaTypeVisibility( @onNull String schemaType)548         public @NonNull Builder clearRequiredPermissionsForSchemaTypeVisibility(
549                 @NonNull String schemaType) {
550             Preconditions.checkNotNull(schemaType);
551             resetIfBuilt();
552             InternalVisibilityConfig.Builder visibilityConfigBuilder =
553                     getVisibilityConfigBuilder(schemaType);
554             if (visibilityConfigBuilder != null) {
555                 visibilityConfigBuilder.clearVisibleToPermissions();
556             }
557             return this;
558         }
559 
560         /**
561          * Specify that the schema should be publicly available, to packages which already have
562          * visibility to {@code packageIdentifier}.
563          *
564          * @param schemaType the schema to make publicly accessible.
565          * @param packageIdentifier the package from which the document schema is from.
566          * @see SetSchemaRequest.Builder#setPubliclyVisibleSchema
567          */
568         // Merged list available from getPubliclyVisibleSchemas
569         @CanIgnoreReturnValue
570         @SuppressLint("MissingGetterMatchingBuilder")
571         @FlaggedApi(Flags.FLAG_ENABLE_SET_PUBLICLY_VISIBLE_SCHEMA)
setPubliclyVisibleSchema( @onNull String schemaType, @NonNull PackageIdentifier packageIdentifier)572         public @NonNull Builder setPubliclyVisibleSchema(
573                 @NonNull String schemaType, @NonNull PackageIdentifier packageIdentifier) {
574             Preconditions.checkNotNull(schemaType);
575             Preconditions.checkNotNull(packageIdentifier);
576             resetIfBuilt();
577             InternalVisibilityConfig.Builder visibilityConfigBuilder =
578                     getOrCreateVisibilityConfigBuilder(schemaType);
579             visibilityConfigBuilder.setPubliclyVisibleTargetPackage(packageIdentifier);
580             return this;
581         }
582 
583         /**
584          * Clears the visibility setting that specifies that the given schema type should be
585          * publicly available to packages which already have visibility to a specified package.
586          *
587          * @see Builder#setPubliclyVisibleSchema
588          */
589         @ExperimentalAppSearchApi
590         @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
591         @CanIgnoreReturnValue
clearPubliclyVisibleSchema(@onNull String schemaType)592         public @NonNull Builder clearPubliclyVisibleSchema(@NonNull String schemaType) {
593             Preconditions.checkNotNull(schemaType);
594             resetIfBuilt();
595             InternalVisibilityConfig.Builder visibilityConfigBuilder =
596                     getVisibilityConfigBuilder(schemaType);
597             if (visibilityConfigBuilder != null) {
598                 visibilityConfigBuilder.setPubliclyVisibleTargetPackage(null);
599             }
600             return this;
601         }
602 
603         /**
604          * Sets the documents from the provided {@code schemaType} can be read by the caller if they
605          * match the ALL visibility requirements set in {@link SchemaVisibilityConfig}.
606          *
607          * <p> The requirements in a {@link SchemaVisibilityConfig} is "AND" relationship. A
608          * caller must match ALL requirements to access the schema. For example, a caller must hold
609          * required permissions AND it is a specified package.
610          *
611          * <p> The querier could have access if they match ALL requirements in ANY of the given
612          * {@link SchemaVisibilityConfig}s
613          *
614          * <p>For example, if the Set contains {@code {% verbatim %}{{PackageA and Permission1},
615          * {PackageB and Permission2}}{% endverbatim %}}.
616          * <ul>
617          *     <li>A querier from packageA could read if they holds Permission1.</li>
618          *     <li>A querier from packageA could NOT read if they only holds Permission2 instead of
619          *     Permission1.</li>
620          *     <li>A querier from packageB could read if they holds Permission2.</li>
621          *     <li>A querier from packageC could never read.</li>
622          *     <li>A querier holds both PermissionA and PermissionE has access.</li>
623          * </ul>
624          *
625          * @param schemaType         The schema type to set visibility on.
626          * @param visibleToConfigs   The {@link SchemaVisibilityConfig}s hold all requirements that
627          *                           a call must to match to access the schema.
628          */
629         // Merged map available from getSchemasVisibleToConfigs
630         @CanIgnoreReturnValue
631         @SuppressLint("MissingGetterMatchingBuilder")
632         @FlaggedApi(Flags.FLAG_ENABLE_SET_SCHEMA_VISIBLE_TO_CONFIGS)
setSchemaTypeVisibleToConfigs(@onNull String schemaType, @NonNull Set<SchemaVisibilityConfig> visibleToConfigs)633         public @NonNull Builder setSchemaTypeVisibleToConfigs(@NonNull String schemaType,
634                 @NonNull Set<SchemaVisibilityConfig> visibleToConfigs) {
635             Preconditions.checkNotNull(schemaType);
636             Preconditions.checkNotNull(visibleToConfigs);
637             resetIfBuilt();
638             InternalVisibilityConfig.Builder visibilityConfigBuilder =
639                     getOrCreateVisibilityConfigBuilder(schemaType);
640             for (SchemaVisibilityConfig visibleToConfig : visibleToConfigs) {
641                 visibilityConfigBuilder.addVisibleToConfig(visibleToConfig);
642             }
643             return this;
644         }
645 
646         /**
647          * Clears the {@link SchemaVisibilityConfig}s for the given schema type which allow
648          * visibility to the schema if the caller matches ALL visibility requirements of ANY
649          * {@link SchemaVisibilityConfig}.
650          *
651          * @see Builder#setSchemaTypeVisibleToConfigs
652          */
653         @ExperimentalAppSearchApi
654         @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
655         @CanIgnoreReturnValue
clearSchemaTypeVisibleToConfigs(@onNull String schemaType)656         public @NonNull Builder clearSchemaTypeVisibleToConfigs(@NonNull String schemaType) {
657             Preconditions.checkNotNull(schemaType);
658             resetIfBuilt();
659             InternalVisibilityConfig.Builder visibilityConfigBuilder =
660                     getVisibilityConfigBuilder(schemaType);
661             if (visibilityConfigBuilder != null) {
662                 visibilityConfigBuilder.clearVisibleToConfig();
663             }
664             return this;
665         }
666 
667         /**
668          * Method to set visibility setting. If this is called with false,
669          * {@link #getRequiredPermissionsForSchemaTypeVisibility()},
670          * {@link #getSchemaTypesNotDisplayedBySystem()}}, and
671          * {@link #getSchemaTypesVisibleToPackages()} calls will throw an
672          * {@link UnsupportedOperationException}. If called with true, visibility information for
673          * all schemas will be cleared.
674          *
675          * @param visibilitySettingSupported whether supported
676          * {@link Features#ADD_PERMISSIONS_AND_GET_VISIBILITY} by this
677          *                                      backend/Android API level.
678          * @exportToFramework:hide
679          */
680          // Visibility setting is determined by SDK version, so it won't be needed in framework
681         @CanIgnoreReturnValue
682         @SuppressLint("MissingGetterMatchingBuilder")
setVisibilitySettingSupported(boolean visibilitySettingSupported)683         public @NonNull Builder setVisibilitySettingSupported(boolean visibilitySettingSupported) {
684             if (visibilitySettingSupported) {
685                 mVisibilityConfigBuilders = new ArrayMap<>();
686             } else {
687                 mVisibilityConfigBuilders = null;
688             }
689             return this;
690         }
691 
692         /** Builds a {@link GetSchemaResponse} object. */
build()693         public @NonNull GetSchemaResponse build() {
694             List<InternalVisibilityConfig> visibilityConfigs = null;
695             if (mVisibilityConfigBuilders != null) {
696                 visibilityConfigs = new ArrayList<>();
697                 for (InternalVisibilityConfig.Builder builder :
698                         mVisibilityConfigBuilders.values()) {
699                     visibilityConfigs.add(builder.build());
700                 }
701             }
702             mBuilt = true;
703             return new GetSchemaResponse(mVersion, mSchemas, visibilityConfigs);
704         }
705 
getOrCreateVisibilityConfigBuilder( @onNull String schemaType)706         private @NonNull InternalVisibilityConfig.Builder getOrCreateVisibilityConfigBuilder(
707                 @NonNull String schemaType) {
708             if (mVisibilityConfigBuilders == null) {
709                 throw new IllegalStateException("GetSchemaResponse is not configured with"
710                         + "visibility setting support");
711             }
712             InternalVisibilityConfig.Builder builder = mVisibilityConfigBuilders.get(schemaType);
713             if (builder == null) {
714                 builder = new InternalVisibilityConfig.Builder(schemaType);
715                 mVisibilityConfigBuilders.put(schemaType, builder);
716             }
717             return builder;
718         }
719 
getVisibilityConfigBuilder( @onNull String schemaType)720         private @Nullable InternalVisibilityConfig.Builder getVisibilityConfigBuilder(
721                 @NonNull String schemaType) {
722             if (mVisibilityConfigBuilders == null) {
723                 throw new IllegalStateException("GetSchemaResponse is not configured with"
724                         + "visibility setting support");
725             }
726             return mVisibilityConfigBuilders.get(schemaType);
727         }
728 
resetIfBuilt()729         private void resetIfBuilt() {
730             if (mBuilt) {
731                 // No need to copy mVisibilityConfigBuilders -- it gets copied during build().
732                 mSchemas = new ArrayList<>(mSchemas);
733                 mBuilt = false;
734             }
735         }
736     }
737 }
738