1 /*
2  * Copyright 2024 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.localstorage.visibilitystore;
18 
19 import android.util.Log;
20 
21 import androidx.annotation.RestrictTo;
22 import androidx.appsearch.app.AppSearchSchema;
23 import androidx.appsearch.app.GenericDocument;
24 import androidx.appsearch.app.InternalVisibilityConfig;
25 import androidx.appsearch.app.PackageIdentifier;
26 import androidx.appsearch.app.SchemaVisibilityConfig;
27 import androidx.appsearch.app.VisibilityPermissionConfig;
28 import androidx.collection.ArraySet;
29 
30 import com.google.android.appsearch.proto.AndroidVOverlayProto;
31 import com.google.android.appsearch.proto.PackageIdentifierProto;
32 import com.google.android.appsearch.proto.VisibilityConfigProto;
33 import com.google.android.appsearch.proto.VisibleToPermissionProto;
34 import com.google.android.icing.protobuf.ByteString;
35 import com.google.android.icing.protobuf.InvalidProtocolBufferException;
36 
37 import org.jspecify.annotations.NonNull;
38 import org.jspecify.annotations.Nullable;
39 
40 import java.util.List;
41 import java.util.Objects;
42 import java.util.Set;
43 
44 /**
45  * Utilities for working with {@link VisibilityChecker} and {@link VisibilityStore}.
46  * @exportToFramework:hide
47  */
48 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
49 public class VisibilityToDocumentConverter {
50     private static final String TAG = "AppSearchVisibilityToDo";
51 
52     /**
53      * The Schema type for documents that hold AppSearch's metadata, such as visibility settings.
54      */
55     public static final String VISIBILITY_DOCUMENT_SCHEMA_TYPE = "VisibilityType";
56     /** Namespace of documents that contain visibility settings */
57     public static final String VISIBILITY_DOCUMENT_NAMESPACE = "";
58 
59     /**
60      * The Schema type for the Android V visibility setting overlay documents, that allow for
61      * additional visibility settings.
62      */
63     public static final String ANDROID_V_OVERLAY_SCHEMA_TYPE = "AndroidVOverlayType";
64     /** Namespace of documents that contain Android V visibility setting overlay documents */
65     public static final String ANDROID_V_OVERLAY_NAMESPACE = "androidVOverlay";
66     /** Property that holds the serialized {@link AndroidVOverlayProto}. */
67     public static final String VISIBILITY_PROTO_SERIALIZE_PROPERTY =
68             "visibilityProtoSerializeProperty";
69 
70     /**
71      * Property that holds the list of platform-hidden schemas, as part of the visibility settings.
72      */
73     private static final String NOT_DISPLAYED_BY_SYSTEM_PROPERTY = "notPlatformSurfaceable";
74 
75     /** Property that holds the package name that can access a schema. */
76     private static final String VISIBLE_TO_PACKAGE_IDENTIFIER_PROPERTY = "packageName";
77 
78     /** Property that holds the SHA 256 certificate of the app that can access a schema. */
79     private static final String VISIBLE_TO_PACKAGE_SHA_256_CERT_PROPERTY = "sha256Cert";
80 
81     /** Property that holds the required permissions to access the schema. */
82     private static final String PERMISSION_PROPERTY = "permission";
83 
84     // The initial schema version, one VisibilityConfig contains all visibility information for
85     // whole package.
86     public static final int SCHEMA_VERSION_DOC_PER_PACKAGE = 0;
87 
88     // One VisibilityConfig contains visibility information for a single schema.
89     public static final int SCHEMA_VERSION_DOC_PER_SCHEMA = 1;
90 
91     // One VisibilityConfig contains visibility information for a single schema. The permission
92     // visibility information is stored in a document property VisibilityPermissionConfig of the
93     // outer doc.
94     public static final int SCHEMA_VERSION_NESTED_PERMISSION_SCHEMA = 2;
95 
96     public static final int SCHEMA_VERSION_LATEST = SCHEMA_VERSION_NESTED_PERMISSION_SCHEMA;
97 
98     // The initial schema version, the overlay schema contains public acl and visible to config
99     // properties.
100     public static final int OVERLAY_SCHEMA_VERSION_PUBLIC_ACL_VISIBLE_TO_CONFIG = 0;
101 
102     // The overlay schema only contains a proto property contains all visibility setting.
103     public static final int OVERLAY_SCHEMA_VERSION_ALL_IN_PROTO = 1;
104 
105     // The version number of schema saved in Android V overlay database.
106     public static final int ANDROID_V_OVERLAY_SCHEMA_VERSION_LATEST =
107             OVERLAY_SCHEMA_VERSION_ALL_IN_PROTO;
108 
109     /**
110      * Schema for the VisibilityStore's documents.
111      *
112      * <p>NOTE: If you update this, also update {@link #SCHEMA_VERSION_LATEST}.
113      */
114     public static final AppSearchSchema VISIBILITY_DOCUMENT_SCHEMA =
115             new AppSearchSchema.Builder(VISIBILITY_DOCUMENT_SCHEMA_TYPE)
116                     .addProperty(new AppSearchSchema.BooleanPropertyConfig.Builder(
117                             NOT_DISPLAYED_BY_SYSTEM_PROPERTY)
118                             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
119                             .build())
120                     .addProperty(new AppSearchSchema.StringPropertyConfig.Builder(
121                             VISIBLE_TO_PACKAGE_IDENTIFIER_PROPERTY)
122                             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
123                             .build())
124                     .addProperty(new AppSearchSchema.BytesPropertyConfig.Builder(
125                             VISIBLE_TO_PACKAGE_SHA_256_CERT_PROPERTY)
126                             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
127                             .build())
128                     .addProperty(new AppSearchSchema.DocumentPropertyConfig.Builder(
129                             PERMISSION_PROPERTY, VisibilityPermissionConfig.SCHEMA_TYPE)
130                             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
131                             .build())
132                     .build();
133 
134     /**  Schema for the VisibilityStore's Android V visibility setting overlay. */
135     public static final AppSearchSchema ANDROID_V_OVERLAY_SCHEMA =
136             new AppSearchSchema.Builder(ANDROID_V_OVERLAY_SCHEMA_TYPE)
137                     .addProperty(new AppSearchSchema.BytesPropertyConfig.Builder(
138                             VISIBILITY_PROTO_SERIALIZE_PROPERTY)
139                             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
140                             .build())
141                     .build();
142     /**
143      * The Deprecated schemas and properties that we need to remove from visibility database.
144      * TODO(b/321326441) remove this method when we no longer to migrate devices in this state.
145      */
146     static final AppSearchSchema DEPRECATED_PUBLIC_ACL_OVERLAY_SCHEMA =
147             new AppSearchSchema.Builder("PublicAclOverlayType")
148                     .addProperty(new AppSearchSchema.StringPropertyConfig.Builder(
149                             "publiclyVisibleTargetPackage")
150                             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
151                             .build())
152                     .addProperty(new AppSearchSchema.BytesPropertyConfig.Builder(
153                             "publiclyVisibleTargetPackageSha256Cert")
154                             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
155                             .build())
156                     .build();
157 
158     /**
159      * Constructs a {@link InternalVisibilityConfig} from two {@link GenericDocument}s.
160      *
161      * <p>This constructor is still needed until we don't treat Visibility related documents as
162      * {@link GenericDocument}s internally.
163      *
164      * @param visibilityDocument       a {@link GenericDocument} holding visibility properties
165      *                                 in {@link #VISIBILITY_DOCUMENT_SCHEMA}
166      * @param androidVOverlayDocument  a {@link GenericDocument} holding visibility properties
167      *                                 in {@link #ANDROID_V_OVERLAY_SCHEMA}
168      */
createInternalVisibilityConfig( @onNull GenericDocument visibilityDocument, @Nullable GenericDocument androidVOverlayDocument)169     public static @NonNull InternalVisibilityConfig createInternalVisibilityConfig(
170             @NonNull GenericDocument visibilityDocument,
171             @Nullable GenericDocument androidVOverlayDocument) {
172         Objects.requireNonNull(visibilityDocument);
173 
174         // Parse visibility proto if required
175         AndroidVOverlayProto androidVOverlayProto = null;
176         if (androidVOverlayDocument != null) {
177             try {
178                 byte[] androidVOverlayProtoBytes = androidVOverlayDocument.getPropertyBytes(
179                         VISIBILITY_PROTO_SERIALIZE_PROPERTY);
180                 if (androidVOverlayProtoBytes != null) {
181                     androidVOverlayProto = AndroidVOverlayProto.parseFrom(
182                             androidVOverlayProtoBytes);
183                 }
184             } catch (InvalidProtocolBufferException e) {
185                 Log.e(TAG, "Get an invalid android V visibility overlay proto.", e);
186             }
187         }
188 
189         // Handle all visibility settings other than visibleToConfigs
190         SchemaVisibilityConfig schemaVisibilityConfig = createVisibilityConfig(
191                 visibilityDocument, androidVOverlayProto);
192 
193         // Handle visibleToConfigs
194         String schemaType = visibilityDocument.getId();
195         InternalVisibilityConfig.Builder builder = new InternalVisibilityConfig.Builder(schemaType)
196                 .setNotDisplayedBySystem(visibilityDocument
197                         .getPropertyBoolean(NOT_DISPLAYED_BY_SYSTEM_PROPERTY))
198                 .setVisibilityConfig(schemaVisibilityConfig);
199         if (androidVOverlayProto != null) {
200             List<VisibilityConfigProto> visibleToConfigProtoList =
201                     androidVOverlayProto.getVisibleToConfigsList();
202             for (int i = 0; i < visibleToConfigProtoList.size(); i++) {
203                 SchemaVisibilityConfig visibleToConfig =
204                         convertVisibilityConfigFromProto(visibleToConfigProtoList.get(i));
205                 builder.addVisibleToConfig(visibleToConfig);
206             }
207         }
208 
209         return builder.build();
210     }
211 
212     /**
213      * Constructs a {@link SchemaVisibilityConfig} from a {@link GenericDocument} containing legacy
214      * visibility settings, and an {@link AndroidVOverlayProto} containing extended visibility
215      * settings.
216      *
217      * <p>This constructor is still needed until we don't treat Visibility related documents as
218      * {@link GenericDocument}s internally.
219      *
220      * @param visibilityDocument   a {@link GenericDocument} holding all visibility properties
221      *                             other than publiclyVisibleTargetPackage.
222      * @param androidVOverlayProto the proto containing post-V visibility settings
223      */
createVisibilityConfig( @onNull GenericDocument visibilityDocument, @Nullable AndroidVOverlayProto androidVOverlayProto)224     private static @NonNull SchemaVisibilityConfig createVisibilityConfig(
225             @NonNull GenericDocument visibilityDocument,
226             @Nullable AndroidVOverlayProto androidVOverlayProto) {
227         Objects.requireNonNull(visibilityDocument);
228 
229         // Pre-V visibility settings come from visibilityDocument
230         SchemaVisibilityConfig.Builder builder = new SchemaVisibilityConfig.Builder();
231 
232         String[] visibleToPackageNames =
233                 visibilityDocument.getPropertyStringArray(VISIBLE_TO_PACKAGE_IDENTIFIER_PROPERTY);
234         byte[][] visibleToPackageShaCerts =
235                 visibilityDocument.getPropertyBytesArray(VISIBLE_TO_PACKAGE_SHA_256_CERT_PROPERTY);
236         if (visibleToPackageNames != null && visibleToPackageShaCerts != null) {
237             for (int i = 0; i < visibleToPackageNames.length; i++) {
238                 builder.addAllowedPackage(
239                         new PackageIdentifier(
240                                 visibleToPackageNames[i], visibleToPackageShaCerts[i]));
241             }
242         }
243 
244         GenericDocument[] visibleToPermissionDocs =
245                 visibilityDocument.getPropertyDocumentArray(PERMISSION_PROPERTY);
246         if (visibleToPermissionDocs != null) {
247             for (int i = 0; i < visibleToPermissionDocs.length; ++i) {
248                 long[] visibleToPermissionLongs = visibleToPermissionDocs[i].getPropertyLongArray(
249                         VisibilityPermissionConfig.ALL_REQUIRED_PERMISSIONS_PROPERTY);
250                 if (visibleToPermissionLongs != null) {
251                     Set<Integer> allRequiredPermissions =
252                             new ArraySet<>(visibleToPermissionLongs.length);
253                     for (int j = 0; j < visibleToPermissionLongs.length; j++) {
254                         allRequiredPermissions.add((int) visibleToPermissionLongs[j]);
255                     }
256                     builder.addRequiredPermissions(allRequiredPermissions);
257                 }
258             }
259         }
260 
261         // Post-V visibility settings come from androidVOverlayProto
262         if (androidVOverlayProto != null) {
263             SchemaVisibilityConfig androidVOverlayConfig =
264                     convertVisibilityConfigFromProto(
265                             androidVOverlayProto.getVisibilityConfig());
266             builder.setPubliclyVisibleTargetPackage(
267                     androidVOverlayConfig.getPubliclyVisibleTargetPackage());
268         }
269 
270         return builder.build();
271     }
272 
273     /**  Convert {@link VisibilityConfigProto} into {@link SchemaVisibilityConfig}.     */
convertVisibilityConfigFromProto( @onNull VisibilityConfigProto proto)274     public static @NonNull SchemaVisibilityConfig convertVisibilityConfigFromProto(
275             @NonNull VisibilityConfigProto proto) {
276         SchemaVisibilityConfig.Builder builder = new SchemaVisibilityConfig.Builder();
277 
278         List<PackageIdentifierProto> visibleToPackageProtoList = proto.getVisibleToPackagesList();
279         for (int i = 0; i < visibleToPackageProtoList.size(); i++) {
280             PackageIdentifierProto visibleToPackage = proto.getVisibleToPackages(i);
281             builder.addAllowedPackage(convertPackageIdentifierFromProto(visibleToPackage));
282         }
283 
284         List<VisibleToPermissionProto> visibleToPermissionProtoList =
285                 proto.getVisibleToPermissionsList();
286         for (int i = 0; i < visibleToPermissionProtoList.size(); i++) {
287             VisibleToPermissionProto visibleToPermissionProto = visibleToPermissionProtoList.get(i);
288             Set<Integer> visibleToPermissions =
289                     new ArraySet<>(visibleToPermissionProto.getPermissionsList());
290             builder.addRequiredPermissions(visibleToPermissions);
291         }
292 
293         if (proto.hasPubliclyVisibleTargetPackage()) {
294             PackageIdentifierProto publiclyVisibleTargetPackage =
295                     proto.getPubliclyVisibleTargetPackage();
296             builder.setPubliclyVisibleTargetPackage(
297                     convertPackageIdentifierFromProto(publiclyVisibleTargetPackage));
298         }
299 
300         return builder.build();
301     }
302 
303     /**  Convert {@link SchemaVisibilityConfig} into {@link VisibilityConfigProto}.     */
convertSchemaVisibilityConfigToProto( @onNull SchemaVisibilityConfig schemaVisibilityConfig)304     public static @NonNull VisibilityConfigProto convertSchemaVisibilityConfigToProto(
305             @NonNull SchemaVisibilityConfig schemaVisibilityConfig) {
306         VisibilityConfigProto.Builder builder = VisibilityConfigProto.newBuilder();
307 
308         List<PackageIdentifier> visibleToPackages = schemaVisibilityConfig.getAllowedPackages();
309         for (int i = 0; i < visibleToPackages.size(); i++) {
310             PackageIdentifier visibleToPackage = visibleToPackages.get(i);
311             builder.addVisibleToPackages(convertPackageIdentifierToProto(visibleToPackage));
312         }
313 
314         Set<Set<Integer>> visibleToPermissions = schemaVisibilityConfig.getRequiredPermissions();
315         if (!visibleToPermissions.isEmpty()) {
316             for (Set<Integer> allRequiredPermissions : visibleToPermissions) {
317                 builder.addVisibleToPermissions(
318                         VisibleToPermissionProto.newBuilder()
319                                 .addAllPermissions(allRequiredPermissions));
320             }
321         }
322 
323         PackageIdentifier publicAclPackage =
324                 schemaVisibilityConfig.getPubliclyVisibleTargetPackage();
325         if (publicAclPackage != null) {
326             builder.setPubliclyVisibleTargetPackage(
327                     convertPackageIdentifierToProto(publicAclPackage));
328         }
329 
330         return builder.build();
331     }
332 
333     /**
334      * Returns the {@link GenericDocument} for the visibility schema.
335      *
336      * @param config the configuration to populate into the document
337      */
createVisibilityDocument( @onNull InternalVisibilityConfig config)338     public static @NonNull GenericDocument createVisibilityDocument(
339             @NonNull InternalVisibilityConfig config) {
340         GenericDocument.Builder<?> builder = new GenericDocument.Builder<>(
341                 VISIBILITY_DOCUMENT_NAMESPACE,
342                 config.getSchemaType(), // We are using the prefixedSchemaType to be the id
343                 VISIBILITY_DOCUMENT_SCHEMA_TYPE);
344         builder.setPropertyBoolean(NOT_DISPLAYED_BY_SYSTEM_PROPERTY,
345                 config.isNotDisplayedBySystem());
346         SchemaVisibilityConfig schemaVisibilityConfig = config.getVisibilityConfig();
347         List<PackageIdentifier> visibleToPackages = schemaVisibilityConfig.getAllowedPackages();
348         String[] visibleToPackageNames = new String[visibleToPackages.size()];
349         byte[][] visibleToPackageSha256Certs = new byte[visibleToPackages.size()][32];
350         for (int i = 0; i < visibleToPackages.size(); i++) {
351             visibleToPackageNames[i] = visibleToPackages.get(i).getPackageName();
352             visibleToPackageSha256Certs[i] = visibleToPackages.get(i).getSha256Certificate();
353         }
354         builder.setPropertyString(VISIBLE_TO_PACKAGE_IDENTIFIER_PROPERTY, visibleToPackageNames);
355         builder.setPropertyBytes(VISIBLE_TO_PACKAGE_SHA_256_CERT_PROPERTY,
356                 visibleToPackageSha256Certs);
357 
358         // Generate an array of GenericDocument for VisibilityPermissionConfig.
359         Set<Set<Integer>> visibleToPermissions = schemaVisibilityConfig.getRequiredPermissions();
360         if (!visibleToPermissions.isEmpty()) {
361             GenericDocument[] permissionGenericDocs =
362                     new GenericDocument[visibleToPermissions.size()];
363             int i = 0;
364             for (Set<Integer> allRequiredPermissions : visibleToPermissions) {
365                 VisibilityPermissionConfig permissionDocument =
366                         new VisibilityPermissionConfig(allRequiredPermissions);
367                 permissionGenericDocs[i++] = permissionDocument.toGenericDocument();
368             }
369             builder.setPropertyDocument(PERMISSION_PROPERTY, permissionGenericDocs);
370         }
371 
372         // The creationTimestamp doesn't matter for Visibility documents.
373         // But to make tests pass, we set it 0 so two GenericDocuments generated from
374         // the same VisibilityConfig can be same.
375         builder.setCreationTimestampMillis(0L);
376 
377         return builder.build();
378     }
379 
380     /**
381      * Returns the {@link GenericDocument} for the Android V overlay schema if it is provided,
382      * null otherwise.
383      */
createAndroidVOverlay( @onNull InternalVisibilityConfig internalVisibilityConfig)384     public static @Nullable GenericDocument createAndroidVOverlay(
385             @NonNull InternalVisibilityConfig internalVisibilityConfig) {
386         PackageIdentifier publiclyVisibleTargetPackage =
387                 internalVisibilityConfig.getVisibilityConfig().getPubliclyVisibleTargetPackage();
388         Set<SchemaVisibilityConfig> visibleToConfigs =
389                 internalVisibilityConfig.getVisibleToConfigs();
390         if (publiclyVisibleTargetPackage == null && visibleToConfigs.isEmpty()) {
391             // This config doesn't contains any Android V overlay settings
392             return null;
393         }
394 
395         VisibilityConfigProto.Builder visibilityConfigProtoBuilder =
396                 VisibilityConfigProto.newBuilder();
397         // Set publicAcl
398         if (publiclyVisibleTargetPackage != null) {
399             visibilityConfigProtoBuilder.setPubliclyVisibleTargetPackage(
400                     convertPackageIdentifierToProto(publiclyVisibleTargetPackage));
401         }
402 
403         // Set visibleToConfigs
404         AndroidVOverlayProto.Builder androidVOverlayProtoBuilder =
405                 AndroidVOverlayProto.newBuilder().setVisibilityConfig(visibilityConfigProtoBuilder);
406         if (!visibleToConfigs.isEmpty()) {
407             for (SchemaVisibilityConfig visibleToConfig : visibleToConfigs) {
408                 VisibilityConfigProto visibleToConfigProto =
409                         convertSchemaVisibilityConfigToProto(visibleToConfig);
410                 androidVOverlayProtoBuilder.addVisibleToConfigs(visibleToConfigProto);
411             }
412         }
413 
414         GenericDocument.Builder<?> androidVOverlayBuilder = new GenericDocument.Builder<>(
415                 ANDROID_V_OVERLAY_NAMESPACE,
416                 internalVisibilityConfig.getSchemaType(),
417                 ANDROID_V_OVERLAY_SCHEMA_TYPE)
418                 .setPropertyBytes(
419                         VISIBILITY_PROTO_SERIALIZE_PROPERTY,
420                         androidVOverlayProtoBuilder.build().toByteArray());
421 
422         // The creationTimestamp doesn't matter for Visibility documents.
423         // But to make tests pass, we set it 0 so two GenericDocuments generated from
424         // the same VisibilityConfig can be same.
425         androidVOverlayBuilder.setCreationTimestampMillis(0L);
426 
427         return androidVOverlayBuilder.build();
428     }
429 
convertPackageIdentifierToProto( @onNull PackageIdentifier packageIdentifier)430     private static @NonNull PackageIdentifierProto convertPackageIdentifierToProto(
431             @NonNull PackageIdentifier packageIdentifier) {
432         return PackageIdentifierProto.newBuilder()
433                 .setPackageName(packageIdentifier.getPackageName())
434                 .setPackageSha256Cert(ByteString.copyFrom(packageIdentifier.getSha256Certificate()))
435                 .build();
436     }
437 
convertPackageIdentifierFromProto( @onNull PackageIdentifierProto packageIdentifierProto)438     private static @NonNull PackageIdentifier convertPackageIdentifierFromProto(
439             @NonNull PackageIdentifierProto packageIdentifierProto) {
440         return new PackageIdentifier(
441                 packageIdentifierProto.getPackageName(),
442                 packageIdentifierProto.getPackageSha256Cert().toByteArray());
443     }
444 
VisibilityToDocumentConverter()445     private VisibilityToDocumentConverter() {}
446 }
447