• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.app.appsearch;
18 
19 import android.annotation.IntRange;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.SuppressLint;
23 import android.app.appsearch.annotation.CanIgnoreReturnValue;
24 import android.os.Bundle;
25 import android.util.ArrayMap;
26 import android.util.ArraySet;
27 
28 import com.android.internal.util.Preconditions;
29 
30 import java.util.ArrayList;
31 import java.util.Collections;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Objects;
35 import java.util.Set;
36 
37 /** The response class of {@link AppSearchSession#getSchema} */
38 public final class GetSchemaResponse {
39     private static final String VERSION_FIELD = "version";
40     private static final String SCHEMAS_FIELD = "schemas";
41     private static final String SCHEMAS_NOT_DISPLAYED_BY_SYSTEM_FIELD =
42             "schemasNotDisplayedBySystem";
43     private static final String SCHEMAS_VISIBLE_TO_PACKAGES_FIELD = "schemasVisibleToPackages";
44     private static final String SCHEMAS_VISIBLE_TO_PERMISSION_FIELD = "schemasVisibleToPermissions";
45     private static final String ALL_REQUIRED_PERMISSION_FIELD = "allRequiredPermission";
46     /**
47      * This Set contains all schemas that are not displayed by the system. All values in the set are
48      * prefixed with the package-database prefix. We do lazy fetch, the object will be created when
49      * you first time fetch it.
50      */
51     @Nullable private Set<String> mSchemasNotDisplayedBySystem;
52     /**
53      * This map contains all schemas and {@link PackageIdentifier} that has access to the schema.
54      * All keys in the map are prefixed with the package-database prefix. We do lazy fetch, the
55      * object will be created when you first time fetch it.
56      */
57     @Nullable private Map<String, Set<PackageIdentifier>> mSchemasVisibleToPackages;
58 
59     /**
60      * This map contains all schemas and Android Permissions combinations that are required to
61      * access the schema. All keys in the map are prefixed with the package-database prefix. We do
62      * lazy fetch, the object will be created when you first time fetch it. The Map is constructed
63      * in ANY-ALL cases. The querier could read the {@link GenericDocument} objects under the {@code
64      * schemaType} if they holds ALL required permissions of ANY combinations. The value set
65      * represents {@link android.app.appsearch.SetSchemaRequest.AppSearchSupportedPermission}.
66      */
67     @Nullable private Map<String, Set<Set<Integer>>> mSchemasVisibleToPermissions;
68 
69     private final Bundle mBundle;
70 
GetSchemaResponse(@onNull Bundle bundle)71     GetSchemaResponse(@NonNull Bundle bundle) {
72         mBundle = Objects.requireNonNull(bundle);
73     }
74 
75     /**
76      * Returns the {@link Bundle} populated by this builder.
77      *
78      * @hide
79      */
80     @NonNull
getBundle()81     public Bundle getBundle() {
82         return mBundle;
83     }
84 
85     /**
86      * Returns the overall database schema version.
87      *
88      * <p>If the database is empty, 0 will be returned.
89      */
90     @IntRange(from = 0)
getVersion()91     public int getVersion() {
92         return mBundle.getInt(VERSION_FIELD);
93     }
94 
95     /**
96      * Return the schemas most recently successfully provided to {@link AppSearchSession#setSchema}.
97      *
98      * <p>It is inefficient to call this method repeatedly.
99      */
100     @NonNull
101     @SuppressWarnings("deprecation")
getSchemas()102     public Set<AppSearchSchema> getSchemas() {
103         ArrayList<Bundle> schemaBundles =
104                 Objects.requireNonNull(mBundle.getParcelableArrayList(SCHEMAS_FIELD));
105         Set<AppSearchSchema> schemas = new ArraySet<>(schemaBundles.size());
106         for (int i = 0; i < schemaBundles.size(); i++) {
107             schemas.add(new AppSearchSchema(schemaBundles.get(i)));
108         }
109         return schemas;
110     }
111 
112     /**
113      * Returns all the schema types that are opted out of being displayed and visible on any system
114      * UI surface.
115      */
116     @NonNull
getSchemaTypesNotDisplayedBySystem()117     public Set<String> getSchemaTypesNotDisplayedBySystem() {
118         checkGetVisibilitySettingSupported();
119         if (mSchemasNotDisplayedBySystem == null) {
120             List<String> schemasNotDisplayedBySystemList =
121                     mBundle.getStringArrayList(SCHEMAS_NOT_DISPLAYED_BY_SYSTEM_FIELD);
122             mSchemasNotDisplayedBySystem =
123                     Collections.unmodifiableSet(new ArraySet<>(schemasNotDisplayedBySystemList));
124         }
125         return mSchemasNotDisplayedBySystem;
126     }
127 
128     /**
129      * Returns a mapping of schema types to the set of packages that have access to that schema
130      * type.
131      */
132     @NonNull
133     @SuppressWarnings("deprecation")
getSchemaTypesVisibleToPackages()134     public Map<String, Set<PackageIdentifier>> getSchemaTypesVisibleToPackages() {
135         checkGetVisibilitySettingSupported();
136         if (mSchemasVisibleToPackages == null) {
137             Bundle schemaVisibleToPackagesBundle =
138                     Objects.requireNonNull(mBundle.getBundle(SCHEMAS_VISIBLE_TO_PACKAGES_FIELD));
139             Map<String, Set<PackageIdentifier>> copy = new ArrayMap<>();
140             for (String key : schemaVisibleToPackagesBundle.keySet()) {
141                 List<Bundle> PackageIdentifierBundles =
142                         Objects.requireNonNull(
143                                 schemaVisibleToPackagesBundle.getParcelableArrayList(key));
144                 Set<PackageIdentifier> packageIdentifiers =
145                         new ArraySet<>(PackageIdentifierBundles.size());
146                 for (int i = 0; i < PackageIdentifierBundles.size(); i++) {
147                     packageIdentifiers.add(new PackageIdentifier(PackageIdentifierBundles.get(i)));
148                 }
149                 copy.put(key, packageIdentifiers);
150             }
151             mSchemasVisibleToPackages = Collections.unmodifiableMap(copy);
152         }
153         return mSchemasVisibleToPackages;
154     }
155 
156     /**
157      * Returns a mapping of schema types to the Map of {@link android.Manifest.permission}
158      * combinations that querier must hold to access that schema type.
159      *
160      * <p>The querier could read the {@link GenericDocument} objects under the {@code schemaType} if
161      * they holds ALL required permissions of ANY of the individual value sets.
162      *
163      * <p>For example, if the Map contains {@code {% verbatim %}{{permissionA, PermissionB}, {
164      * PermissionC, PermissionD}, {PermissionE}}{% endverbatim %}}.
165      *
166      * <ul>
167      *   <li>A querier holds both PermissionA and PermissionB has access.
168      *   <li>A querier holds both PermissionC and PermissionD has access.
169      *   <li>A querier holds only PermissionE has access.
170      *   <li>A querier holds both PermissionA and PermissionE has access.
171      *   <li>A querier holds only PermissionA doesn't have access.
172      *   <li>A querier holds both PermissionA and PermissionC doesn't have access.
173      * </ul>
174      *
175      * @return The map contains schema type and all combinations of required permission for querier
176      *     to access it. The supported Permission are {@link SetSchemaRequest#READ_SMS}, {@link
177      *     SetSchemaRequest#READ_CALENDAR}, {@link SetSchemaRequest#READ_CONTACTS}, {@link
178      *     SetSchemaRequest#READ_EXTERNAL_STORAGE}, {@link
179      *     SetSchemaRequest#READ_HOME_APP_SEARCH_DATA} and {@link
180      *     SetSchemaRequest#READ_ASSISTANT_APP_SEARCH_DATA}.
181      */
182     @NonNull
183     @SuppressWarnings("deprecation")
getRequiredPermissionsForSchemaTypeVisibility()184     public Map<String, Set<Set<Integer>>> getRequiredPermissionsForSchemaTypeVisibility() {
185         checkGetVisibilitySettingSupported();
186         if (mSchemasVisibleToPermissions == null) {
187             Map<String, Set<Set<Integer>>> copy = new ArrayMap<>();
188             Bundle schemaVisibleToPermissionBundle =
189                     Objects.requireNonNull(mBundle.getBundle(SCHEMAS_VISIBLE_TO_PERMISSION_FIELD));
190             for (String key : schemaVisibleToPermissionBundle.keySet()) {
191                 ArrayList<Bundle> allRequiredPermissionsBundle =
192                         schemaVisibleToPermissionBundle.getParcelableArrayList(key);
193                 Set<Set<Integer>> visibleToPermissions = new ArraySet<>();
194                 if (allRequiredPermissionsBundle != null) {
195                     // This should never be null
196                     for (int i = 0; i < allRequiredPermissionsBundle.size(); i++) {
197                         visibleToPermissions.add(
198                                 new ArraySet<>(
199                                         allRequiredPermissionsBundle
200                                                 .get(i)
201                                                 .getIntegerArrayList(
202                                                         ALL_REQUIRED_PERMISSION_FIELD)));
203                     }
204                 }
205                 copy.put(key, visibleToPermissions);
206             }
207             mSchemasVisibleToPermissions = Collections.unmodifiableMap(copy);
208         }
209         return mSchemasVisibleToPermissions;
210     }
211 
checkGetVisibilitySettingSupported()212     private void checkGetVisibilitySettingSupported() {
213         if (!mBundle.containsKey(SCHEMAS_VISIBLE_TO_PACKAGES_FIELD)) {
214             throw new UnsupportedOperationException(
215                     "Get visibility setting is not supported with"
216                             + " this backend/Android API level combination.");
217         }
218     }
219 
220     /** Builder for {@link GetSchemaResponse} objects. */
221     public static final class Builder {
222         private int mVersion = 0;
223         private ArrayList<Bundle> mSchemaBundles = new ArrayList<>();
224         /**
225          * Creates the object when we actually set them. If we never set visibility settings, we
226          * should throw {@link UnsupportedOperationException} in the visibility getters.
227          */
228         @Nullable private ArrayList<String> mSchemasNotDisplayedBySystem;
229 
230         private Bundle mSchemasVisibleToPackages;
231         private Bundle mSchemasVisibleToPermissions;
232         private boolean mBuilt = false;
233 
234         /** Create a {@link Builder} object} */
Builder()235         public Builder() {
236             this(/*getVisibilitySettingSupported=*/ true);
237         }
238 
239         /**
240          * Create a {@link Builder} object}.
241          *
242          * <p>This constructor should only be used in Android API below than T.
243          *
244          * @param getVisibilitySettingSupported whether supported {@link
245          *     Features#ADD_PERMISSIONS_AND_GET_VISIBILITY} by this backend/Android API level.
246          * @hide
247          */
Builder(boolean getVisibilitySettingSupported)248         public Builder(boolean getVisibilitySettingSupported) {
249             if (getVisibilitySettingSupported) {
250                 mSchemasNotDisplayedBySystem = new ArrayList<>();
251                 mSchemasVisibleToPackages = new Bundle();
252                 mSchemasVisibleToPermissions = new Bundle();
253             }
254         }
255 
256         /**
257          * Sets the database overall schema version.
258          *
259          * <p>Default version is 0
260          */
261         @CanIgnoreReturnValue
262         @NonNull
setVersion(@ntRangefrom = 0) int version)263         public Builder setVersion(@IntRange(from = 0) int version) {
264             resetIfBuilt();
265             mVersion = version;
266             return this;
267         }
268 
269         /** Adds one {@link AppSearchSchema} to the schema list. */
270         @CanIgnoreReturnValue
271         @NonNull
addSchema(@onNull AppSearchSchema schema)272         public Builder addSchema(@NonNull AppSearchSchema schema) {
273             Objects.requireNonNull(schema);
274             resetIfBuilt();
275             mSchemaBundles.add(schema.getBundle());
276             return this;
277         }
278 
279         /**
280          * Sets whether or not documents from the provided {@code schemaType} will be displayed and
281          * visible on any system UI surface.
282          *
283          * @param schemaType The name of an {@link AppSearchSchema} within the same {@link
284          *     GetSchemaResponse}, which won't be displayed by system.
285          */
286         // Getter getSchemaTypesNotDisplayedBySystem returns plural objects.
287         @CanIgnoreReturnValue
288         @SuppressLint("MissingGetterMatchingBuilder")
289         @NonNull
addSchemaTypeNotDisplayedBySystem(@onNull String schemaType)290         public Builder addSchemaTypeNotDisplayedBySystem(@NonNull String schemaType) {
291             Objects.requireNonNull(schemaType);
292             resetIfBuilt();
293             if (mSchemasNotDisplayedBySystem == null) {
294                 mSchemasNotDisplayedBySystem = new ArrayList<>();
295             }
296             mSchemasNotDisplayedBySystem.add(schemaType);
297             return this;
298         }
299 
300         /**
301          * Sets whether or not documents from the provided {@code schemaType} can be read by the
302          * specified package.
303          *
304          * <p>Each package is represented by a {@link PackageIdentifier}, containing a package name
305          * and a byte array of type {@link android.content.pm.PackageManager#CERT_INPUT_SHA256}.
306          *
307          * <p>To opt into one-way data sharing with another application, the developer will need to
308          * explicitly grant the other application’s package name and certificate Read access to its
309          * data.
310          *
311          * <p>For two-way data sharing, both applications need to explicitly grant Read access to
312          * one another.
313          *
314          * @param schemaType The schema type to set visibility on.
315          * @param packageIdentifiers Represents the package that has access to the given schema
316          *     type.
317          */
318         // Getter getSchemaTypesVisibleToPackages returns a map contains all schema types.
319         @CanIgnoreReturnValue
320         @SuppressLint("MissingGetterMatchingBuilder")
321         @NonNull
setSchemaTypeVisibleToPackages( @onNull String schemaType, @NonNull Set<PackageIdentifier> packageIdentifiers)322         public Builder setSchemaTypeVisibleToPackages(
323                 @NonNull String schemaType, @NonNull Set<PackageIdentifier> packageIdentifiers) {
324             Objects.requireNonNull(schemaType);
325             Objects.requireNonNull(packageIdentifiers);
326             resetIfBuilt();
327             ArrayList<Bundle> bundles = new ArrayList<>(packageIdentifiers.size());
328             for (PackageIdentifier packageIdentifier : packageIdentifiers) {
329                 bundles.add(packageIdentifier.getBundle());
330             }
331             mSchemasVisibleToPackages.putParcelableArrayList(schemaType, bundles);
332             return this;
333         }
334 
335         /**
336          * Sets a set of required {@link android.Manifest.permission} combinations to the given
337          * schema type.
338          *
339          * <p>The querier could read the {@link GenericDocument} objects under the {@code
340          * schemaType} if they holds ALL required permissions of ANY of the individual value sets.
341          *
342          * <p>For example, if the Map contains {@code {% verbatim %}{{permissionA, PermissionB},
343          * {PermissionC, PermissionD}, {PermissionE}}{% endverbatim %}}.
344          *
345          * <ul>
346          *   <li>A querier holds both PermissionA and PermissionB has access.
347          *   <li>A querier holds both PermissionC and PermissionD has access.
348          *   <li>A querier holds only PermissionE has access.
349          *   <li>A querier holds both PermissionA and PermissionE has access.
350          *   <li>A querier holds only PermissionA doesn't have access.
351          *   <li>A querier holds both PermissionA and PermissionC doesn't have access.
352          * </ul>
353          *
354          * @see android.Manifest.permission#READ_SMS
355          * @see android.Manifest.permission#READ_CALENDAR
356          * @see android.Manifest.permission#READ_CONTACTS
357          * @see android.Manifest.permission#READ_EXTERNAL_STORAGE
358          * @see android.Manifest.permission#READ_HOME_APP_SEARCH_DATA
359          * @see android.Manifest.permission#READ_ASSISTANT_APP_SEARCH_DATA
360          * @param schemaType The schema type to set visibility on.
361          * @param visibleToPermissions The Android permissions that will be required to access the
362          *     given schema.
363          */
364         // Getter getRequiredPermissionsForSchemaTypeVisibility returns a map for all schemaTypes.
365         @CanIgnoreReturnValue
366         @SuppressLint("MissingGetterMatchingBuilder")
367         @NonNull
setRequiredPermissionsForSchemaTypeVisibility( @onNull String schemaType, @SetSchemaRequest.AppSearchSupportedPermission @NonNull Set<Set<Integer>> visibleToPermissions)368         public Builder setRequiredPermissionsForSchemaTypeVisibility(
369                 @NonNull String schemaType,
370                 @SetSchemaRequest.AppSearchSupportedPermission @NonNull
371                         Set<Set<Integer>> visibleToPermissions) {
372             Objects.requireNonNull(schemaType);
373             Objects.requireNonNull(visibleToPermissions);
374             resetIfBuilt();
375             ArrayList<Bundle> visibleToPermissionsBundle = new ArrayList<>();
376             for (Set<Integer> allRequiredPermissions : visibleToPermissions) {
377                 for (int permission : allRequiredPermissions) {
378                     Preconditions.checkArgumentInRange(
379                             permission,
380                             SetSchemaRequest.READ_SMS,
381                             SetSchemaRequest.READ_ASSISTANT_APP_SEARCH_DATA,
382                             "permission");
383                 }
384                 Bundle allRequiredPermissionsBundle = new Bundle();
385                 allRequiredPermissionsBundle.putIntegerArrayList(
386                         ALL_REQUIRED_PERMISSION_FIELD, new ArrayList<>(allRequiredPermissions));
387                 visibleToPermissionsBundle.add(allRequiredPermissionsBundle);
388             }
389             mSchemasVisibleToPermissions.putParcelableArrayList(
390                     schemaType, visibleToPermissionsBundle);
391             return this;
392         }
393 
394         /** Builds a {@link GetSchemaResponse} object. */
395         @NonNull
build()396         public GetSchemaResponse build() {
397             Bundle bundle = new Bundle();
398             bundle.putInt(VERSION_FIELD, mVersion);
399             bundle.putParcelableArrayList(SCHEMAS_FIELD, mSchemaBundles);
400             if (mSchemasNotDisplayedBySystem != null) {
401                 // Only save the visibility fields if it was actually set.
402                 bundle.putStringArrayList(
403                         SCHEMAS_NOT_DISPLAYED_BY_SYSTEM_FIELD, mSchemasNotDisplayedBySystem);
404                 bundle.putBundle(SCHEMAS_VISIBLE_TO_PACKAGES_FIELD, mSchemasVisibleToPackages);
405                 bundle.putBundle(SCHEMAS_VISIBLE_TO_PERMISSION_FIELD, mSchemasVisibleToPermissions);
406             }
407             mBuilt = true;
408             return new GetSchemaResponse(bundle);
409         }
410 
resetIfBuilt()411         private void resetIfBuilt() {
412             if (mBuilt) {
413                 mSchemaBundles = new ArrayList<>(mSchemaBundles);
414                 if (mSchemasNotDisplayedBySystem != null) {
415                     // Only reset the visibility fields if it was actually set.
416                     mSchemasNotDisplayedBySystem = new ArrayList<>(mSchemasNotDisplayedBySystem);
417                     Bundle copyVisibleToPackages = new Bundle();
418                     copyVisibleToPackages.putAll(mSchemasVisibleToPackages);
419                     mSchemasVisibleToPackages = copyVisibleToPackages;
420                     Bundle copyVisibleToPermissions = new Bundle();
421                     copyVisibleToPermissions.putAll(mSchemasVisibleToPermissions);
422                     mSchemasVisibleToPermissions = copyVisibleToPermissions;
423                 }
424                 mBuilt = false;
425             }
426         }
427     }
428 }
429