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.NonNull;
20 import android.annotation.Nullable;
21 import android.app.appsearch.safeparcel.AbstractSafeParcelable;
22 import android.app.appsearch.safeparcel.SafeParcelable;
23 import android.os.Parcel;
24 import android.os.Parcelable;
25 import android.util.ArraySet;
26 
27 import java.util.Arrays;
28 import java.util.Objects;
29 import java.util.Set;
30 
31 /**
32  * The config class that holds all required permissions for a caller need to hold to access the
33  * schema which the outer {@link SchemaVisibilityConfig} represents.
34  *
35  * @hide
36  */
37 @SafeParcelable.Class(creator = "VisibilityPermissionConfigCreator")
38 public final class VisibilityPermissionConfig extends AbstractSafeParcelable {
39     @NonNull
40     public static final Parcelable.Creator<VisibilityPermissionConfig> CREATOR =
41             new VisibilityPermissionConfigCreator();
42 
43     /**
44      * The Schema type for documents that hold AppSearch's metadata, such as visibility settings.
45      */
46     public static final String SCHEMA_TYPE = "VisibilityPermissionType";
47 
48     /** Property that holds the required permissions to access the schema. */
49     public static final String ALL_REQUIRED_PERMISSIONS_PROPERTY = "allRequiredPermissions";
50 
51     /**
52      * Schema for the VisibilityStore's documents.
53      *
54      * <p>NOTE: If you update this, also update schema version number in
55      * VisibilityToDocumentConverter
56      */
57     public static final AppSearchSchema SCHEMA =
58             new AppSearchSchema.Builder(SCHEMA_TYPE)
59                     .addProperty(
60                             new AppSearchSchema.LongPropertyConfig.Builder(
61                                             ALL_REQUIRED_PERMISSIONS_PROPERTY)
62                                     .setCardinality(
63                                             AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
64                                     .build())
65                     .build();
66 
67     @Nullable
68     @Field(id = 1)
69     final int[] mAllRequiredPermissions;
70 
71     @Nullable
72     // We still need to convert this class to a GenericDocument until we completely treat it
73     // differently in AppSearchImpl.
74     // TODO(b/298118943) Remove this once internally we don't use GenericDocument to store
75     //  visibility information.
76     private GenericDocument mGenericDocument;
77 
78     @Nullable private Integer mHashCode;
79 
80     @Constructor
VisibilityPermissionConfig(@aramid = 1) @ullable int[] allRequiredPermissions)81     VisibilityPermissionConfig(@Param(id = 1) @Nullable int[] allRequiredPermissions) {
82         mAllRequiredPermissions = allRequiredPermissions;
83     }
84 
85     /**
86      * Sets a set of Android Permissions that caller must hold to access the schema that the outer
87      * {@link SchemaVisibilityConfig} represents.
88      */
VisibilityPermissionConfig(@onNull Set<Integer> allRequiredPermissions)89     public VisibilityPermissionConfig(@NonNull Set<Integer> allRequiredPermissions) {
90         mAllRequiredPermissions = toInts(Objects.requireNonNull(allRequiredPermissions));
91     }
92 
93     /**
94      * Returns an array of Android Permissions that caller mush hold to access the schema that the
95      * outer {@link SchemaVisibilityConfig} represents.
96      */
97     @Nullable
getAllRequiredPermissions()98     public Set<Integer> getAllRequiredPermissions() {
99         return toIntegerSet(mAllRequiredPermissions);
100     }
101 
102     @NonNull
toInts(@onNull Set<Integer> properties)103     private static int[] toInts(@NonNull Set<Integer> properties) {
104         int[] outputs = new int[properties.size()];
105         int i = 0;
106         for (int property : properties) {
107             outputs[i++] = property;
108         }
109         return outputs;
110     }
111 
112     @Nullable
toIntegerSet(@ullable int[] properties)113     private static Set<Integer> toIntegerSet(@Nullable int[] properties) {
114         if (properties == null) {
115             return null;
116         }
117         Set<Integer> outputs = new ArraySet<>(properties.length);
118         for (int property : properties) {
119             outputs.add(property);
120         }
121         return outputs;
122     }
123 
124     /**
125      * Generates a {@link GenericDocument} from the current class.
126      *
127      * <p>This conversion is needed until we don't treat Visibility related documents as {@link
128      * GenericDocument}s internally.
129      */
130     @NonNull
toGenericDocument()131     public GenericDocument toGenericDocument() {
132         if (mGenericDocument == null) {
133             // This is used as a nested document, we do not need a namespace or id.
134             GenericDocument.Builder<?> builder =
135                     new GenericDocument.Builder<>(/* namespace= */ "", /* id= */ "", SCHEMA_TYPE);
136 
137             if (mAllRequiredPermissions != null) {
138                 // GenericDocument only supports long, so int[] needs to be converted to
139                 // long[] here.
140                 long[] longs = new long[mAllRequiredPermissions.length];
141                 for (int i = 0; i < mAllRequiredPermissions.length; ++i) {
142                     longs[i] = mAllRequiredPermissions[i];
143                 }
144                 builder.setPropertyLong(ALL_REQUIRED_PERMISSIONS_PROPERTY, longs);
145             }
146 
147             // The creationTimestamp doesn't matter for Visibility documents.
148             // But to make tests pass, we set it 0 so two GenericDocuments generated from
149             // the same VisibilityPermissionConfig can be same.
150             builder.setCreationTimestampMillis(0L);
151 
152             mGenericDocument = builder.build();
153         }
154         return mGenericDocument;
155     }
156 
157     @Override
hashCode()158     public int hashCode() {
159         if (mHashCode == null) {
160             mHashCode = Arrays.hashCode(mAllRequiredPermissions);
161         }
162         return mHashCode;
163     }
164 
165     @Override
equals(@ullable Object other)166     public boolean equals(@Nullable Object other) {
167         if (this == other) {
168             return true;
169         }
170         if (!(other instanceof VisibilityPermissionConfig)) {
171             return false;
172         }
173         VisibilityPermissionConfig otherVisibilityPermissionConfig =
174                 (VisibilityPermissionConfig) other;
175         return Arrays.equals(
176                 mAllRequiredPermissions, otherVisibilityPermissionConfig.mAllRequiredPermissions);
177     }
178 
179     @Override
writeToParcel(@onNull Parcel dest, int flags)180     public void writeToParcel(@NonNull Parcel dest, int flags) {
181         VisibilityPermissionConfigCreator.writeToParcel(this, dest, flags);
182     }
183 }
184