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 android.app.appsearch;
18 
19 import android.annotation.FlaggedApi;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.app.appsearch.annotation.CanIgnoreReturnValue;
23 import android.app.appsearch.safeparcel.AbstractSafeParcelable;
24 import android.app.appsearch.safeparcel.PackageIdentifierParcel;
25 import android.app.appsearch.safeparcel.SafeParcelable;
26 import android.os.Parcel;
27 import android.os.Parcelable;
28 import android.util.ArraySet;
29 
30 import com.android.appsearch.flags.Flags;
31 
32 import java.util.ArrayList;
33 import java.util.List;
34 import java.util.Objects;
35 import java.util.Set;
36 
37 /**
38  * A class to hold a all necessary Visibility information corresponding to the same schema. This
39  * pattern allows for easier association of these documents.
40  *
41  * <p>This does not correspond to any schema, the properties held in this class are kept in two
42  * separate schemas, VisibilityConfig and PublicAclOverlay.
43  */
44 @FlaggedApi(Flags.FLAG_ENABLE_SET_SCHEMA_VISIBLE_TO_CONFIGS)
45 @SafeParcelable.Class(creator = "VisibilityConfigCreator")
46 @SuppressWarnings("HiddenSuperclass")
47 public final class SchemaVisibilityConfig extends AbstractSafeParcelable {
48     @NonNull
49     public static final Parcelable.Creator<SchemaVisibilityConfig> CREATOR =
50             new VisibilityConfigCreator();
51 
52     @NonNull
53     @Field(id = 1)
54     final List<PackageIdentifierParcel> mAllowedPackages;
55 
56     @NonNull
57     @Field(id = 2)
58     final List<VisibilityPermissionConfig> mRequiredPermissions;
59 
60     @Nullable
61     @Field(id = 3)
62     final PackageIdentifierParcel mPubliclyVisibleTargetPackage;
63 
64     @Nullable private Integer mHashCode;
65     @Nullable private List<PackageIdentifier> mAllowedPackagesCached;
66     @Nullable private Set<Set<Integer>> mRequiredPermissionsCached;
67 
68     @Constructor
SchemaVisibilityConfig( @aramid = 1) @onNull List<PackageIdentifierParcel> allowedPackages, @Param(id = 2) @NonNull List<VisibilityPermissionConfig> requiredPermissions, @Param(id = 3) @Nullable PackageIdentifierParcel publiclyVisibleTargetPackage)69     SchemaVisibilityConfig(
70             @Param(id = 1) @NonNull List<PackageIdentifierParcel> allowedPackages,
71             @Param(id = 2) @NonNull List<VisibilityPermissionConfig> requiredPermissions,
72             @Param(id = 3) @Nullable PackageIdentifierParcel publiclyVisibleTargetPackage) {
73         mAllowedPackages = Objects.requireNonNull(allowedPackages);
74         mRequiredPermissions = Objects.requireNonNull(requiredPermissions);
75         mPubliclyVisibleTargetPackage = publiclyVisibleTargetPackage;
76     }
77 
78     /** Returns a list of {@link PackageIdentifier}s of packages that can access this schema. */
79     @NonNull
getAllowedPackages()80     public List<PackageIdentifier> getAllowedPackages() {
81         if (mAllowedPackagesCached == null) {
82             mAllowedPackagesCached = new ArrayList<>(mAllowedPackages.size());
83             for (int i = 0; i < mAllowedPackages.size(); i++) {
84                 mAllowedPackagesCached.add(new PackageIdentifier(mAllowedPackages.get(i)));
85             }
86         }
87         return mAllowedPackagesCached;
88     }
89 
90     /**
91      * Returns an array of Integers representing Android Permissions that the caller must hold to
92      * access the schema this {@link SchemaVisibilityConfig} represents.
93      *
94      * @see SetSchemaRequest.Builder#addRequiredPermissionsForSchemaTypeVisibility(String, Set)
95      */
96     @NonNull
getRequiredPermissions()97     public Set<Set<Integer>> getRequiredPermissions() {
98         if (mRequiredPermissionsCached == null) {
99             mRequiredPermissionsCached = new ArraySet<>(mRequiredPermissions.size());
100             for (int i = 0; i < mRequiredPermissions.size(); i++) {
101                 VisibilityPermissionConfig permissionConfig = mRequiredPermissions.get(i);
102                 Set<Integer> requiredPermissions = permissionConfig.getAllRequiredPermissions();
103                 if (mRequiredPermissionsCached != null && requiredPermissions != null) {
104                     mRequiredPermissionsCached.add(requiredPermissions);
105                 }
106             }
107         }
108         // Added for nullness checker as it is @Nullable, we initialize it above if it is null.
109         return Objects.requireNonNull(mRequiredPermissionsCached);
110     }
111 
112     /**
113      * Returns the {@link PackageIdentifier} of the package that will be used as the target package
114      * in a call to {@link android.content.pm.PackageManager#canPackageQuery} to determine which
115      * packages can access this publicly visible schema. Returns null if the schema is not publicly
116      * visible.
117      */
118     @Nullable
getPubliclyVisibleTargetPackage()119     public PackageIdentifier getPubliclyVisibleTargetPackage() {
120         if (mPubliclyVisibleTargetPackage == null) {
121             return null;
122         }
123         return new PackageIdentifier(mPubliclyVisibleTargetPackage);
124     }
125 
126     @Override
writeToParcel(@onNull Parcel dest, int flags)127     public void writeToParcel(@NonNull Parcel dest, int flags) {
128         VisibilityConfigCreator.writeToParcel(this, dest, flags);
129     }
130 
131     @Override
equals(@ullable Object o)132     public boolean equals(@Nullable Object o) {
133         if (this == o) {
134             return true;
135         }
136         if (o == null) {
137             return false;
138         }
139         if (!(o instanceof SchemaVisibilityConfig)) {
140             return false;
141         }
142         SchemaVisibilityConfig that = (SchemaVisibilityConfig) o;
143         return Objects.equals(mAllowedPackages, that.mAllowedPackages)
144                 && Objects.equals(mRequiredPermissions, that.mRequiredPermissions)
145                 && Objects.equals(
146                         mPubliclyVisibleTargetPackage, that.mPubliclyVisibleTargetPackage);
147     }
148 
149     @Override
hashCode()150     public int hashCode() {
151         if (mHashCode == null) {
152             mHashCode =
153                     Objects.hash(
154                             mAllowedPackages, mRequiredPermissions, mPubliclyVisibleTargetPackage);
155         }
156         return mHashCode;
157     }
158 
159     /** The builder class of {@link SchemaVisibilityConfig}. */
160     @FlaggedApi(Flags.FLAG_ENABLE_SET_SCHEMA_VISIBLE_TO_CONFIGS)
161     public static final class Builder {
162         private List<PackageIdentifierParcel> mAllowedPackages = new ArrayList<>();
163         private List<VisibilityPermissionConfig> mRequiredPermissions = new ArrayList<>();
164         @Nullable private PackageIdentifierParcel mPubliclyVisibleTargetPackage;
165         private boolean mBuilt;
166 
167         /** Creates a {@link Builder} for a {@link SchemaVisibilityConfig}. */
Builder()168         public Builder() {}
169 
170         /**
171          * Creates a {@link Builder} copying the values from an existing {@link
172          * SchemaVisibilityConfig}.
173          *
174          * @hide
175          */
Builder(@onNull SchemaVisibilityConfig schemaVisibilityConfig)176         public Builder(@NonNull SchemaVisibilityConfig schemaVisibilityConfig) {
177             Objects.requireNonNull(schemaVisibilityConfig);
178             mAllowedPackages = new ArrayList<>(schemaVisibilityConfig.mAllowedPackages);
179             mRequiredPermissions = new ArrayList<>(schemaVisibilityConfig.mRequiredPermissions);
180             mPubliclyVisibleTargetPackage = schemaVisibilityConfig.mPubliclyVisibleTargetPackage;
181         }
182 
183         /** Add {@link PackageIdentifier} of packages which has access to this schema. */
184         @CanIgnoreReturnValue
185         @NonNull
addAllowedPackage(@onNull PackageIdentifier packageIdentifier)186         public Builder addAllowedPackage(@NonNull PackageIdentifier packageIdentifier) {
187             Objects.requireNonNull(packageIdentifier);
188             resetIfBuilt();
189             mAllowedPackages.add(packageIdentifier.getPackageIdentifierParcel());
190             return this;
191         }
192 
193         /** Clears the list of packages which have access to this schema. */
194         @CanIgnoreReturnValue
195         @NonNull
clearAllowedPackages()196         public Builder clearAllowedPackages() {
197             resetIfBuilt();
198             mAllowedPackages.clear();
199             return this;
200         }
201 
202         /**
203          * Adds a set of required Android {@link android.Manifest.permission} combination a package
204          * needs to hold to access the schema this {@link SchemaVisibilityConfig} represents.
205          *
206          * <p>If the querier holds ALL of the required permissions in this combination, they will
207          * have access to read {@link GenericDocument} objects of the given schema type.
208          *
209          * <p>You can call this method repeatedly to add multiple permission combinations, and the
210          * querier will have access if they holds ANY of the combinations.
211          *
212          * <p>Merged Set available from {@link #getRequiredPermissions()}.
213          *
214          * @see SetSchemaRequest.Builder#addRequiredPermissionsForSchemaTypeVisibility for supported
215          *     Permissions.
216          */
217         @SuppressWarnings("RequiresPermission") // No permission required to call this method
218         @CanIgnoreReturnValue
219         @NonNull
addRequiredPermissions(@onNull Set<Integer> visibleToPermissions)220         public Builder addRequiredPermissions(@NonNull Set<Integer> visibleToPermissions) {
221             Objects.requireNonNull(visibleToPermissions);
222             resetIfBuilt();
223             mRequiredPermissions.add(new VisibilityPermissionConfig(visibleToPermissions));
224             return this;
225         }
226 
227         /**
228          * Clears all required permissions combinations set to this {@link SchemaVisibilityConfig}.
229          */
230         @CanIgnoreReturnValue
231         @NonNull
clearRequiredPermissions()232         public Builder clearRequiredPermissions() {
233             resetIfBuilt();
234             mRequiredPermissions.clear();
235             return this;
236         }
237 
238         /**
239          * Specify that this schema should be publicly available, to the same packages that have
240          * visibility to the package passed as a parameter. This visibility is determined by the
241          * result of {@link android.content.pm.PackageManager#canPackageQuery}.
242          *
243          * <p>It is possible for the packageIdentifier parameter to be different from the package
244          * performing the indexing. This might happen in the case of an on-device indexer processing
245          * information about various packages. The visibility will be the same regardless of which
246          * package indexes the document, as the visibility is based on the packageIdentifier
247          * parameter.
248          *
249          * <p>Calling this with packageIdentifier set to null is valid, and will remove public
250          * visibility for the schema.
251          *
252          * @param packageIdentifier the {@link PackageIdentifier} of the package that will be used
253          *     as the target package in a call to {@link
254          *     android.content.pm.PackageManager#canPackageQuery} to determine which packages can
255          *     access this publicly visible schema.
256          */
257         @CanIgnoreReturnValue
258         @NonNull
setPubliclyVisibleTargetPackage( @ullable PackageIdentifier packageIdentifier)259         public Builder setPubliclyVisibleTargetPackage(
260                 @Nullable PackageIdentifier packageIdentifier) {
261             resetIfBuilt();
262             if (packageIdentifier == null) {
263                 mPubliclyVisibleTargetPackage = null;
264             } else {
265                 mPubliclyVisibleTargetPackage = packageIdentifier.getPackageIdentifierParcel();
266             }
267             return this;
268         }
269 
resetIfBuilt()270         private void resetIfBuilt() {
271             if (mBuilt) {
272                 mAllowedPackages = new ArrayList<>(mAllowedPackages);
273                 mRequiredPermissions = new ArrayList<>(mRequiredPermissions);
274                 mBuilt = false;
275             }
276         }
277 
278         /** Build a {@link SchemaVisibilityConfig} */
279         @NonNull
build()280         public SchemaVisibilityConfig build() {
281             mBuilt = true;
282             return new SchemaVisibilityConfig(
283                     mAllowedPackages, mRequiredPermissions, mPubliclyVisibleTargetPackage);
284         }
285     }
286 }
287