1 /* 2 * Copyright 2022 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 com.android.server.appsearch.external.localstorage.visibilitystore; 18 19 import android.annotation.NonNull; 20 import android.app.appsearch.AppSearchResult; 21 import android.app.appsearch.AppSearchSchema; 22 import android.app.appsearch.GenericDocument; 23 import android.app.appsearch.GetSchemaResponse; 24 import android.app.appsearch.PackageIdentifier; 25 import android.app.appsearch.exceptions.AppSearchException; 26 import android.util.ArrayMap; 27 28 import com.android.internal.annotations.VisibleForTesting; 29 import com.android.server.appsearch.external.localstorage.AppSearchImpl; 30 import com.android.server.appsearch.external.localstorage.util.PrefixUtil; 31 32 import java.util.ArrayList; 33 import java.util.Collections; 34 import java.util.List; 35 import java.util.Map; 36 import java.util.Objects; 37 import java.util.Set; 38 39 /** 40 * The helper class to store Visibility Document information of version 0 and handle the upgrade to 41 * version 1. 42 * 43 * @hide 44 */ 45 public class VisibilityStoreMigrationHelperFromV0 { VisibilityStoreMigrationHelperFromV0()46 private VisibilityStoreMigrationHelperFromV0() {} 47 48 /** Prefix to add to all visibility document ids. IcingSearchEngine doesn't allow empty ids. */ 49 private static final String DEPRECATED_ID_PREFIX = "uri:"; 50 51 /** Schema type for documents that hold AppSearch's metadata, e.g. visibility settings */ 52 @VisibleForTesting static final String DEPRECATED_VISIBILITY_SCHEMA_TYPE = "VisibilityType"; 53 54 /** 55 * Property that holds the list of platform-hidden schemas, as part of the visibility settings. 56 */ 57 @VisibleForTesting 58 static final String DEPRECATED_NOT_DISPLAYED_BY_SYSTEM_PROPERTY = "notPlatformSurfaceable"; 59 60 /** Property that holds nested documents of package accessible schemas. */ 61 @VisibleForTesting 62 static final String DEPRECATED_VISIBLE_TO_PACKAGES_PROPERTY = "packageAccessible"; 63 64 /** 65 * Property that holds the list of platform-hidden schemas, as part of the visibility settings. 66 */ 67 @VisibleForTesting static final String DEPRECATED_PACKAGE_SCHEMA_TYPE = "PackageAccessibleType"; 68 69 /** Property that holds the prefixed schema type that is accessible by some package. */ 70 @VisibleForTesting 71 static final String DEPRECATED_ACCESSIBLE_SCHEMA_PROPERTY = "accessibleSchema"; 72 73 /** Property that holds the package name that can access a schema. */ 74 @VisibleForTesting static final String DEPRECATED_PACKAGE_NAME_PROPERTY = "packageName"; 75 76 /** Property that holds the SHA 256 certificate of the app that can access a schema. */ 77 @VisibleForTesting static final String DEPRECATED_SHA_256_CERT_PROPERTY = "sha256Cert"; 78 79 // The visibility schema of version 0. 80 // --------------------------------------------------------------------------------------------- 81 // Schema of DEPRECATED_VISIBILITY_SCHEMA_TYPE: 82 // new AppSearchSchema.Builder( 83 // DEPRECATED_VISIBILITY_SCHEMA_TYPE) 84 // .addProperty(new AppSearchSchema.StringPropertyConfig.Builder( 85 // DEPRECATED_NOT_DISPLAYED_BY_SYSTEM_PROPERTY) 86 // .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED) 87 // .build()) 88 // .addProperty(new AppSearchSchema.DocumentPropertyConfig.Builder( 89 // DEPRECATED_VISIBLE_TO_PACKAGES_PROPERTY, 90 // DEPRECATED_PACKAGE_SCHEMA_TYPE) 91 // .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED) 92 // .build()) 93 // .build(); 94 // Schema of DEPRECATED_PACKAGE_SCHEMA_TYPE: 95 // new AppSearchSchema.Builder(DEPRECATED_PACKAGE_SCHEMA_TYPE) 96 // .addProperty(new AppSearchSchema.StringPropertyConfig.Builder( 97 // DEPRECATED_PACKAGE_NAME_PROPERTY) 98 // .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) 99 // .build()) 100 // .addProperty(new AppSearchSchema.BytesPropertyConfig.Builder( 101 // DEPRECATED_SHA_256_CERT_PROPERTY) 102 // .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) 103 // .build()) 104 // .addProperty(new AppSearchSchema.StringPropertyConfig.Builder( 105 // DEPRECATED_ACCESSIBLE_SCHEMA_PROPERTY) 106 // .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) 107 // .build()) 108 // .build(); 109 // --------------------------------------------------------------------------------------------- 110 111 /** Returns whether the given schema type is deprecated. */ isDeprecatedType(@onNull String schemaType)112 static boolean isDeprecatedType(@NonNull String schemaType) { 113 return schemaType.equals(DEPRECATED_VISIBILITY_SCHEMA_TYPE) 114 || schemaType.equals(DEPRECATED_PACKAGE_SCHEMA_TYPE); 115 } 116 117 /** 118 * Adds a prefix to create a deprecated visibility document's id. 119 * 120 * @param packageName Package to which the visibility doc refers. 121 * @param databaseName Database to which the visibility doc refers. 122 * @return deprecated visibility document's id. 123 */ 124 @NonNull getDeprecatedVisibilityDocumentId( @onNull String packageName, @NonNull String databaseName)125 static String getDeprecatedVisibilityDocumentId( 126 @NonNull String packageName, @NonNull String databaseName) { 127 return DEPRECATED_ID_PREFIX + PrefixUtil.createPrefix(packageName, databaseName); 128 } 129 130 /** Reads all stored deprecated Visibility Document in version 0 from icing. */ getVisibilityDocumentsInVersion0( @onNull GetSchemaResponse getSchemaResponse, @NonNull AppSearchImpl appSearchImpl)131 static List<GenericDocument> getVisibilityDocumentsInVersion0( 132 @NonNull GetSchemaResponse getSchemaResponse, @NonNull AppSearchImpl appSearchImpl) 133 throws AppSearchException { 134 if (!hasDeprecatedType(getSchemaResponse)) { 135 return new ArrayList<>(); 136 } 137 Map<String, Set<String>> packageToDatabases = appSearchImpl.getPackageToDatabases(); 138 List<GenericDocument> deprecatedDocuments = new ArrayList<>(packageToDatabases.size()); 139 for (Map.Entry<String, Set<String>> entry : packageToDatabases.entrySet()) { 140 String packageName = entry.getKey(); 141 if (packageName.equals(VisibilityStore.VISIBILITY_PACKAGE_NAME)) { 142 continue; // Our own package. Skip. 143 } 144 for (String databaseName : entry.getValue()) { 145 try { 146 // Note: We use the other clients' prefixed names as ids 147 deprecatedDocuments.add( 148 appSearchImpl.getDocument( 149 VisibilityStore.VISIBILITY_PACKAGE_NAME, 150 VisibilityStore.VISIBILITY_DATABASE_NAME, 151 VisibilityToDocumentConverter.VISIBILITY_DOCUMENT_NAMESPACE, 152 getDeprecatedVisibilityDocumentId(packageName, databaseName), 153 /* typePropertyPaths= */ Collections.emptyMap())); 154 } catch (AppSearchException e) { 155 if (e.getResultCode() == AppSearchResult.RESULT_NOT_FOUND) { 156 // TODO(b/172068212): This indicates some desync error. We were expecting a 157 // document, but didn't find one. Should probably reset AppSearch instead 158 // of ignoring it. 159 continue; 160 } 161 // Otherwise, this is some other error we should pass up. 162 throw e; 163 } 164 } 165 } 166 return deprecatedDocuments; 167 } 168 169 /** 170 * Converts the given list of deprecated Visibility Documents into a Map of {@code 171 * <PrefixedSchemaType, VisibilityDocument.Builder of the latest version>}. 172 * 173 * @param visibilityDocumentV0s The deprecated Visibility Document we found. 174 */ 175 @NonNull toVisibilityDocumentV1( @onNull List<GenericDocument> visibilityDocumentV0s)176 static List<VisibilityDocumentV1> toVisibilityDocumentV1( 177 @NonNull List<GenericDocument> visibilityDocumentV0s) { 178 Map<String, VisibilityDocumentV1.Builder> documentBuilderMap = new ArrayMap<>(); 179 180 // Set all visibility information into documentBuilderMap 181 for (int i = 0; i < visibilityDocumentV0s.size(); i++) { 182 GenericDocument visibilityDocumentV0 = visibilityDocumentV0s.get(i); 183 184 // Read not displayed by system property field. 185 String[] notDisplayedBySystemSchemas = 186 visibilityDocumentV0.getPropertyStringArray( 187 DEPRECATED_NOT_DISPLAYED_BY_SYSTEM_PROPERTY); 188 if (notDisplayedBySystemSchemas != null) { 189 for (String notDisplayedBySystemSchema : notDisplayedBySystemSchemas) { 190 // SetSchemaRequest.Builder.build() make sure all schemas that has visibility 191 // setting must present in the requests. 192 VisibilityDocumentV1.Builder visibilityBuilder = 193 getOrCreateBuilder(documentBuilderMap, notDisplayedBySystemSchema); 194 visibilityBuilder.setNotDisplayedBySystem(true); 195 } 196 } 197 198 // Read visible to packages field. 199 GenericDocument[] deprecatedPackageDocuments = 200 visibilityDocumentV0.getPropertyDocumentArray( 201 DEPRECATED_VISIBLE_TO_PACKAGES_PROPERTY); 202 if (deprecatedPackageDocuments != null) { 203 for (GenericDocument deprecatedPackageDocument : deprecatedPackageDocuments) { 204 String prefixedSchemaType = 205 Objects.requireNonNull( 206 deprecatedPackageDocument.getPropertyString( 207 DEPRECATED_ACCESSIBLE_SCHEMA_PROPERTY)); 208 VisibilityDocumentV1.Builder visibilityBuilder = 209 getOrCreateBuilder(documentBuilderMap, prefixedSchemaType); 210 String packageName = 211 Objects.requireNonNull( 212 deprecatedPackageDocument.getPropertyString( 213 DEPRECATED_PACKAGE_NAME_PROPERTY)); 214 byte[] sha256Cert = 215 Objects.requireNonNull( 216 deprecatedPackageDocument.getPropertyBytes( 217 DEPRECATED_SHA_256_CERT_PROPERTY)); 218 visibilityBuilder.addVisibleToPackage( 219 new PackageIdentifier(packageName, sha256Cert)); 220 } 221 } 222 } 223 List<VisibilityDocumentV1> visibilityDocumentsV1 = 224 new ArrayList<>(documentBuilderMap.size()); 225 for (Map.Entry<String, VisibilityDocumentV1.Builder> entry : 226 documentBuilderMap.entrySet()) { 227 visibilityDocumentsV1.add(entry.getValue().build()); 228 } 229 return visibilityDocumentsV1; 230 } 231 232 /** 233 * Return whether the database maybe has the oldest version of deprecated schema. 234 * 235 * <p>Since the current version number is 0, it is possible that the database is just empty and 236 * it return 0 as the default version number. So we need to check if the deprecated document 237 * presents to trigger the migration. 238 */ hasDeprecatedType(@onNull GetSchemaResponse getSchemaResponse)239 private static boolean hasDeprecatedType(@NonNull GetSchemaResponse getSchemaResponse) { 240 for (AppSearchSchema schema : getSchemaResponse.getSchemas()) { 241 if (VisibilityStoreMigrationHelperFromV0.isDeprecatedType(schema.getSchemaType())) { 242 // Found deprecated type, we need to migrate visibility Document. And it's 243 // not possible for us to find the latest visibility schema. 244 return true; 245 } 246 } 247 return false; 248 } 249 250 @NonNull getOrCreateBuilder( @onNull Map<String, VisibilityDocumentV1.Builder> documentBuilderMap, @NonNull String schemaType)251 private static VisibilityDocumentV1.Builder getOrCreateBuilder( 252 @NonNull Map<String, VisibilityDocumentV1.Builder> documentBuilderMap, 253 @NonNull String schemaType) { 254 VisibilityDocumentV1.Builder builder = documentBuilderMap.get(schemaType); 255 if (builder == null) { 256 builder = new VisibilityDocumentV1.Builder(/* id= */ schemaType); 257 documentBuilderMap.put(schemaType, builder); 258 } 259 return builder; 260 } 261 } 262