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