1 /*
2  * Copyright 2023 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.os.Parcel;
20 import android.os.Parcelable;
21 
22 import androidx.annotation.OptIn;
23 import androidx.annotation.RestrictTo;
24 import androidx.appsearch.annotation.CanIgnoreReturnValue;
25 import androidx.appsearch.flags.FlaggedApi;
26 import androidx.appsearch.flags.Flags;
27 import androidx.appsearch.safeparcel.AbstractSafeParcelable;
28 import androidx.appsearch.safeparcel.SafeParcelable;
29 import androidx.appsearch.safeparcel.stub.StubCreators.InternalVisibilityConfigCreator;
30 import androidx.collection.ArraySet;
31 
32 import org.jspecify.annotations.NonNull;
33 import org.jspecify.annotations.Nullable;
34 
35 import java.util.ArrayList;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.Objects;
39 import java.util.Set;
40 
41 /**
42  * An expanded version of {@link SchemaVisibilityConfig} which includes fields for internal use by
43  * AppSearch.
44  *
45  * @exportToFramework:hide
46  */
47 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
48 @SafeParcelable.Class(creator = "InternalVisibilityConfigCreator")
49 public final class InternalVisibilityConfig extends AbstractSafeParcelable {
50     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
51     public static final Parcelable.@NonNull Creator<InternalVisibilityConfig> CREATOR =
52             new InternalVisibilityConfigCreator();
53 
54     /**
55      * Build the List of {@link InternalVisibilityConfig}s from given {@link SetSchemaRequest}.
56      */
toInternalVisibilityConfigs( @onNull SetSchemaRequest setSchemaRequest)57     public static @NonNull List<InternalVisibilityConfig> toInternalVisibilityConfigs(
58             @NonNull SetSchemaRequest setSchemaRequest) {
59         Set<AppSearchSchema> searchSchemas = setSchemaRequest.getSchemas();
60         Set<String> schemasNotDisplayedBySystem = setSchemaRequest.getSchemasNotDisplayedBySystem();
61         Map<String, Set<PackageIdentifier>> schemasVisibleToPackages =
62                 setSchemaRequest.getSchemasVisibleToPackages();
63         Map<String, Set<Set<Integer>>> schemasVisibleToPermissions =
64                 setSchemaRequest.getRequiredPermissionsForSchemaTypeVisibility();
65         Map<String, PackageIdentifier> publiclyVisibleSchemas =
66                 setSchemaRequest.getPubliclyVisibleSchemas();
67         Map<String, Set<SchemaVisibilityConfig>> schemasVisibleToConfigs =
68                 setSchemaRequest.getSchemasVisibleToConfigs();
69 
70         List<InternalVisibilityConfig> result = new ArrayList<>(searchSchemas.size());
71         for (AppSearchSchema searchSchema : searchSchemas) {
72             String schemaType = searchSchema.getSchemaType();
73             InternalVisibilityConfig.Builder builder =
74                     new InternalVisibilityConfig.Builder(schemaType)
75                             .setNotDisplayedBySystem(
76                                     schemasNotDisplayedBySystem.contains(schemaType));
77 
78             Set<PackageIdentifier> visibleToPackages = schemasVisibleToPackages.get(schemaType);
79             if (visibleToPackages != null) {
80                 for (PackageIdentifier packageIdentifier : visibleToPackages) {
81                     builder.addVisibleToPackage(packageIdentifier);
82                 }
83             }
84 
85             Set<Set<Integer>> visibleToPermissionSets = schemasVisibleToPermissions.get(schemaType);
86             if (visibleToPermissionSets != null) {
87                 for (Set<Integer> visibleToPermissions : visibleToPermissionSets) {
88                     builder.addVisibleToPermissions(visibleToPermissions);
89                 }
90             }
91 
92             PackageIdentifier publiclyVisibleTargetPackage = publiclyVisibleSchemas.get(schemaType);
93             if (publiclyVisibleTargetPackage != null) {
94                 builder.setPubliclyVisibleTargetPackage(publiclyVisibleTargetPackage);
95             }
96 
97             Set<SchemaVisibilityConfig> visibleToConfigs = schemasVisibleToConfigs.get(schemaType);
98             if (visibleToConfigs != null) {
99                 for (SchemaVisibilityConfig schemaVisibilityConfig : visibleToConfigs) {
100                     builder.addVisibleToConfig(schemaVisibilityConfig);
101                 }
102             }
103 
104             result.add(builder.build());
105         }
106         return result;
107     }
108 
109     /**
110      * Build the List of {@link InternalVisibilityConfig}s from given
111      * {@link SetBlobVisibilityRequest}.
112      */
113     @OptIn(markerClass = ExperimentalAppSearchApi.class)
toInternalVisibilityConfigs( @onNull SetBlobVisibilityRequest setBlobVisibilityRequest)114     public static @NonNull List<InternalVisibilityConfig> toInternalVisibilityConfigs(
115             @NonNull SetBlobVisibilityRequest setBlobVisibilityRequest) {
116 
117         Set<String> blobNamespacesNotDisplayedBySystem =
118                 setBlobVisibilityRequest.getNamespacesNotDisplayedBySystem();
119         Map<String, Set<SchemaVisibilityConfig>> blobNamespacesVisibleToConfigs =
120                 setBlobVisibilityRequest.getNamespacesVisibleToConfigs();
121 
122         Set<String> allBlobNamespaces = new ArraySet<>(blobNamespacesNotDisplayedBySystem);
123         allBlobNamespaces.addAll(blobNamespacesVisibleToConfigs.keySet());
124 
125         List<InternalVisibilityConfig> result = new ArrayList<>();
126         for (String namespace : allBlobNamespaces) {
127             InternalVisibilityConfig.Builder builder =
128                     new InternalVisibilityConfig.Builder(namespace)
129                             .setNotDisplayedBySystem(
130                                     blobNamespacesNotDisplayedBySystem.contains(namespace));
131 
132             Set<SchemaVisibilityConfig> visibleToConfigs =
133                     blobNamespacesVisibleToConfigs.get(namespace);
134             if (visibleToConfigs != null) {
135                 for (SchemaVisibilityConfig schemaVisibilityConfig : visibleToConfigs) {
136                     builder.addVisibleToConfig(schemaVisibilityConfig);
137                 }
138             }
139 
140             result.add(builder.build());
141         }
142         return result;
143     }
144 
145     @Field(id = 1, getter = "getSchemaType")
146     private final @NonNull String mSchemaType;
147 
148     @Field(id = 2, getter = "isNotDisplayedBySystem")
149     private final boolean mIsNotDisplayedBySystem;
150 
151     /** The public visibility settings available in VisibilityConfig. */
152     @Field(id = 3, getter = "getVisibilityConfig")
153     private final @NonNull SchemaVisibilityConfig mVisibilityConfig;
154 
155     /** Extended visibility settings from {@link SetSchemaRequest#getSchemasVisibleToConfigs()} */
156     @Field(id = 4)
157     final @NonNull List<SchemaVisibilityConfig> mVisibleToConfigs;
158 
159     @Constructor
InternalVisibilityConfig( @aramid = 1) @onNull String schemaType, @Param(id = 2) boolean isNotDisplayedBySystem, @Param(id = 3) @NonNull SchemaVisibilityConfig schemaVisibilityConfig, @Param(id = 4) @NonNull List<SchemaVisibilityConfig> visibleToConfigs)160     InternalVisibilityConfig(
161             @Param(id = 1) @NonNull String schemaType,
162             @Param(id = 2) boolean isNotDisplayedBySystem,
163             @Param(id = 3) @NonNull SchemaVisibilityConfig schemaVisibilityConfig,
164             @Param(id = 4) @NonNull List<SchemaVisibilityConfig> visibleToConfigs) {
165         mIsNotDisplayedBySystem = isNotDisplayedBySystem;
166         mSchemaType = Objects.requireNonNull(schemaType);
167         mVisibilityConfig = Objects.requireNonNull(schemaVisibilityConfig);
168         mVisibleToConfigs = Objects.requireNonNull(visibleToConfigs);
169     }
170 
171     /**
172      * Gets the schemaType for this VisibilityConfig.
173      *
174      * <p>This is being used as the document id when we convert a {@link InternalVisibilityConfig}
175      * to a {@link GenericDocument}.
176      */
getSchemaType()177     public @NonNull String getSchemaType() {
178         return mSchemaType;
179     }
180 
181     /** Returns whether this schema is visible to the system. */
isNotDisplayedBySystem()182     public boolean isNotDisplayedBySystem() {
183         return mIsNotDisplayedBySystem;
184     }
185 
186     /**
187      * Returns the visibility settings stored in the public {@link SchemaVisibilityConfig} object.
188      */
getVisibilityConfig()189     public @NonNull SchemaVisibilityConfig getVisibilityConfig() {
190         return mVisibilityConfig;
191     }
192 
193     /**
194      * Returns required {@link SchemaVisibilityConfig} sets for a caller need to match to access the
195      * schema this {@link InternalVisibilityConfig} represents.
196      */
getVisibleToConfigs()197     public @NonNull Set<SchemaVisibilityConfig> getVisibleToConfigs() {
198         return new ArraySet<>(mVisibleToConfigs);
199     }
200 
201     @Override
writeToParcel(@onNull Parcel dest, int flags)202     public void writeToParcel(@NonNull Parcel dest, int flags) {
203         InternalVisibilityConfigCreator.writeToParcel(this, dest, flags);
204     }
205 
206     @Override
equals(@ullable Object o)207     public boolean equals(@Nullable Object o) {
208         if (this == o) {
209             return true;
210         }
211         if (o == null) {
212             return false;
213         }
214         if (!(o instanceof InternalVisibilityConfig)) {
215             return false;
216         }
217         InternalVisibilityConfig that = (InternalVisibilityConfig) o;
218         return mIsNotDisplayedBySystem == that.mIsNotDisplayedBySystem
219                 && Objects.equals(mSchemaType, that.mSchemaType)
220                 && Objects.equals(mVisibilityConfig, that.mVisibilityConfig)
221                 && Objects.equals(mVisibleToConfigs, that.mVisibleToConfigs);
222     }
223 
224     @Override
hashCode()225     public int hashCode() {
226         return Objects.hash(mIsNotDisplayedBySystem, mSchemaType, mVisibilityConfig,
227                 mVisibleToConfigs);
228     }
229 
230     /** The builder class of {@link InternalVisibilityConfig}. */
231     @FlaggedApi(Flags.FLAG_ENABLE_SET_SCHEMA_VISIBLE_TO_CONFIGS)
232     public static final class Builder {
233         private String mSchemaType;
234         private boolean mIsNotDisplayedBySystem;
235         private SchemaVisibilityConfig.Builder mVisibilityConfigBuilder;
236         private List<SchemaVisibilityConfig> mVisibleToConfigs = new ArrayList<>();
237         private boolean mBuilt;
238 
239         /**
240          * Creates a {@link Builder} for a {@link InternalVisibilityConfig}.
241          *
242          * @param schemaType The SchemaType of the {@link AppSearchSchema} that this {@link
243          *                   InternalVisibilityConfig} represents. The package and database prefix
244          *                   will be added in server side. We are using prefixed schema type to be
245          *                   the final id of this {@link InternalVisibilityConfig}. This will be
246          *                   used as as an AppSearch id.
247          * @see GenericDocument#getId
248          */
Builder(@onNull String schemaType)249         public Builder(@NonNull String schemaType) {
250             mSchemaType = Objects.requireNonNull(schemaType);
251             mVisibilityConfigBuilder = new SchemaVisibilityConfig.Builder();
252         }
253 
254         /** Creates a {@link Builder} from an existing {@link InternalVisibilityConfig} */
Builder(@onNull InternalVisibilityConfig internalVisibilityConfig)255         public Builder(@NonNull InternalVisibilityConfig internalVisibilityConfig) {
256             Objects.requireNonNull(internalVisibilityConfig);
257             mSchemaType = internalVisibilityConfig.mSchemaType;
258             mIsNotDisplayedBySystem = internalVisibilityConfig.mIsNotDisplayedBySystem;
259             mVisibilityConfigBuilder = new SchemaVisibilityConfig.Builder(
260                     internalVisibilityConfig.getVisibilityConfig());
261             mVisibleToConfigs = internalVisibilityConfig.mVisibleToConfigs;
262         }
263 
264         /** Sets schemaType, which will be as the id when converting to {@link GenericDocument}. */
265         @CanIgnoreReturnValue
setSchemaType(@onNull String schemaType)266         public @NonNull Builder setSchemaType(@NonNull String schemaType) {
267             resetIfBuilt();
268             mSchemaType = Objects.requireNonNull(schemaType);
269             return this;
270         }
271 
272         /**
273          * Resets all values contained in the VisibilityConfig with the values from the given
274          * VisibiltiyConfig.
275          */
276         @CanIgnoreReturnValue
setVisibilityConfig( @onNull SchemaVisibilityConfig schemaVisibilityConfig)277         public @NonNull Builder setVisibilityConfig(
278                 @NonNull SchemaVisibilityConfig schemaVisibilityConfig) {
279             resetIfBuilt();
280             mVisibilityConfigBuilder = new SchemaVisibilityConfig.Builder(schemaVisibilityConfig);
281             return this;
282         }
283 
284         /**
285          * Sets whether this schema has opted out of platform surfacing.
286          */
287         @CanIgnoreReturnValue
setNotDisplayedBySystem(boolean notDisplayedBySystem)288         public @NonNull Builder setNotDisplayedBySystem(boolean notDisplayedBySystem) {
289             resetIfBuilt();
290             mIsNotDisplayedBySystem = notDisplayedBySystem;
291             return this;
292         }
293 
294         /**
295          * Add {@link PackageIdentifier} of packages which has access to this schema.
296          *
297          * @see SchemaVisibilityConfig.Builder#addAllowedPackage
298          */
299         @CanIgnoreReturnValue
addVisibleToPackage(@onNull PackageIdentifier packageIdentifier)300         public @NonNull Builder addVisibleToPackage(@NonNull PackageIdentifier packageIdentifier) {
301             resetIfBuilt();
302             mVisibilityConfigBuilder.addAllowedPackage(packageIdentifier);
303             return this;
304         }
305 
306         /**
307          * Clears the list of packages which have access to this schema.
308          *
309          * @see SchemaVisibilityConfig.Builder#clearAllowedPackages
310          */
311         @CanIgnoreReturnValue
clearVisibleToPackages()312         public @NonNull Builder clearVisibleToPackages() {
313             resetIfBuilt();
314             mVisibilityConfigBuilder.clearAllowedPackages();
315             return this;
316         }
317 
318         /**
319          * Adds a set of required Android {@link android.Manifest.permission} combination a package
320          * needs to hold to access the schema.
321          *
322          * @see SchemaVisibilityConfig.Builder#addRequiredPermissions
323          */
324         @CanIgnoreReturnValue
addVisibleToPermissions( @onNull Set<Integer> visibleToPermissions)325         public @NonNull Builder addVisibleToPermissions(
326                 @NonNull Set<Integer> visibleToPermissions) {
327             resetIfBuilt();
328             mVisibilityConfigBuilder.addRequiredPermissions(visibleToPermissions);
329             return this;
330         }
331 
332         /**
333          * Clears all required permissions combinations set to this {@link SchemaVisibilityConfig}.
334          *
335          * @see SchemaVisibilityConfig.Builder#clearRequiredPermissions
336          */
337         @CanIgnoreReturnValue
clearVisibleToPermissions()338         public @NonNull Builder clearVisibleToPermissions() {
339             resetIfBuilt();
340             mVisibilityConfigBuilder.clearRequiredPermissions();
341             return this;
342         }
343 
344         /**
345          * Specify that this schema should be publicly available, to the same packages that have
346          * visibility to the package passed as a parameter. This visibility is determined by the
347          * result of {@link android.content.pm.PackageManager#canPackageQuery}.
348          *
349          * @see SchemaVisibilityConfig.Builder#setPubliclyVisibleTargetPackage
350          */
351         @CanIgnoreReturnValue
setPubliclyVisibleTargetPackage( @ullable PackageIdentifier packageIdentifier)352         public @NonNull Builder setPubliclyVisibleTargetPackage(
353                 @Nullable PackageIdentifier packageIdentifier) {
354             resetIfBuilt();
355             mVisibilityConfigBuilder.setPubliclyVisibleTargetPackage(packageIdentifier);
356             return this;
357         }
358 
359         /**
360          * Add the {@link SchemaVisibilityConfig} for a caller need to match to access the schema
361          * this {@link InternalVisibilityConfig} represents.
362          *
363          * <p> You can call this method repeatedly to add multiple {@link SchemaVisibilityConfig},
364          * and the querier will have access if they match ANY of the
365          * {@link SchemaVisibilityConfig}.
366          *
367          * @param schemaVisibilityConfig The {@link SchemaVisibilityConfig} hold all requirements
368          *                               that a call must match to access the schema.
369          */
370         @CanIgnoreReturnValue
addVisibleToConfig( @onNull SchemaVisibilityConfig schemaVisibilityConfig)371         public @NonNull Builder addVisibleToConfig(
372                 @NonNull SchemaVisibilityConfig schemaVisibilityConfig) {
373             Objects.requireNonNull(schemaVisibilityConfig);
374             resetIfBuilt();
375             mVisibleToConfigs.add(schemaVisibilityConfig);
376             return this;
377         }
378 
379         /** Clears the set of {@link SchemaVisibilityConfig} which have access to this schema. */
380         @CanIgnoreReturnValue
clearVisibleToConfig()381         public @NonNull Builder clearVisibleToConfig() {
382             resetIfBuilt();
383             mVisibleToConfigs.clear();
384             return this;
385         }
386 
resetIfBuilt()387         private void resetIfBuilt() {
388             if (mBuilt) {
389                 mVisibleToConfigs = new ArrayList<>(mVisibleToConfigs);
390                 mBuilt = false;
391             }
392         }
393 
394         /** Build a {@link InternalVisibilityConfig} */
build()395         public @NonNull InternalVisibilityConfig build() {
396             mBuilt = true;
397             return new InternalVisibilityConfig(
398                     mSchemaType,
399                     mIsNotDisplayedBySystem,
400                     mVisibilityConfigBuilder.build(),
401                     mVisibleToConfigs);
402         }
403     }
404 }
405