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