1 /* 2 * Copyright 2021 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 package androidx.appsearch.localstorage.visibilitystore; 17 18 import static androidx.appsearch.app.AppSearchResult.RESULT_NOT_FOUND; 19 import static androidx.appsearch.localstorage.visibilitystore.VisibilityToDocumentConverter.ANDROID_V_OVERLAY_NAMESPACE; 20 import static androidx.appsearch.localstorage.visibilitystore.VisibilityToDocumentConverter.VISIBILITY_DOCUMENT_NAMESPACE; 21 22 import android.util.Log; 23 24 import androidx.annotation.RestrictTo; 25 import androidx.appsearch.app.AppSearchResult; 26 import androidx.appsearch.app.AppSearchSchema; 27 import androidx.appsearch.app.GenericDocument; 28 import androidx.appsearch.app.GetSchemaResponse; 29 import androidx.appsearch.app.InternalSetSchemaResponse; 30 import androidx.appsearch.app.InternalVisibilityConfig; 31 import androidx.appsearch.app.SearchResult; 32 import androidx.appsearch.app.SearchResultPage; 33 import androidx.appsearch.app.SearchSpec; 34 import androidx.appsearch.app.VisibilityPermissionConfig; 35 import androidx.appsearch.checker.initialization.qual.UnderInitialization; 36 import androidx.appsearch.checker.initialization.qual.UnknownInitialization; 37 import androidx.appsearch.checker.nullness.qual.RequiresNonNull; 38 import androidx.appsearch.exceptions.AppSearchException; 39 import androidx.appsearch.flags.Flags; 40 import androidx.appsearch.localstorage.AppSearchImpl; 41 import androidx.appsearch.localstorage.util.PrefixUtil; 42 import androidx.appsearch.util.LogUtil; 43 import androidx.collection.ArrayMap; 44 import androidx.core.util.Preconditions; 45 46 import org.jspecify.annotations.NonNull; 47 import org.jspecify.annotations.Nullable; 48 49 import java.util.ArrayList; 50 import java.util.Arrays; 51 import java.util.Collections; 52 import java.util.List; 53 import java.util.Map; 54 import java.util.Set; 55 56 /** 57 * Stores all visibility settings for all databases that AppSearchImpl knows about. 58 * Persists the visibility settings and reloads them on initialization. 59 * 60 * <p>The VisibilityStore creates a {@link InternalVisibilityConfig} for each schema. This config 61 * holds the visibility settings that apply to that schema. The VisibilityStore also creates a 62 * schema and documents for these {@link InternalVisibilityConfig} and has its own 63 * package and database so that its data doesn't interfere with any clients' data. It persists 64 * the document and schema through AppSearchImpl. 65 * 66 * <p>These visibility settings won't be used in AppSearch Jetpack, we only store them for clients 67 * to look up. 68 * 69 * @exportToFramework:hide 70 */ 71 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 72 public class VisibilityStore { 73 private static final String TAG = "AppSearchVisibilityStor"; 74 /** 75 * These cannot have any of the special characters used by AppSearchImpl (e.g. {@code 76 * AppSearchImpl#PACKAGE_DELIMITER} or {@code AppSearchImpl#DATABASE_DELIMITER}. 77 */ 78 public static final String VISIBILITY_PACKAGE_NAME = "VS#Pkg"; 79 80 public static final String DOCUMENT_VISIBILITY_DATABASE_NAME = "VS#Db"; 81 public static final String DOCUMENT_ANDROID_V_OVERLAY_DATABASE_NAME = "VS#AndroidVDb"; 82 83 public static final String BLOB_VISIBILITY_DATABASE_NAME = "VSBlob#Db"; 84 public static final String BLOB_ANDROID_V_OVERLAY_DATABASE_NAME = "VSBlob#AndroidVDb"; 85 public static final int QUERY_RESULT_COUNT_PER_PAGE = 100; 86 87 /** 88 * Map of PrefixedSchemaType to InternalVisibilityConfig stores visibility information for each 89 * schema type. 90 */ 91 private final Map<String, InternalVisibilityConfig> mVisibilityConfigMap = new ArrayMap<>(); 92 private final AppSearchImpl mAppSearchImpl; 93 private final String mDatabaseName; 94 private final String mAndroidVOverlayDatabaseName; 95 96 /** Create a {@link VisibilityStore} instance to store document visibility settings. */ createDocumentVisibilityStore( @onNull AppSearchImpl appSearchImpl)97 public static @NonNull VisibilityStore createDocumentVisibilityStore( 98 @NonNull AppSearchImpl appSearchImpl) throws AppSearchException { 99 List<String> cachedSchemaTypes = appSearchImpl.getAllPrefixedSchemaTypes(); 100 return new VisibilityStore(appSearchImpl, DOCUMENT_VISIBILITY_DATABASE_NAME, 101 DOCUMENT_ANDROID_V_OVERLAY_DATABASE_NAME, cachedSchemaTypes); 102 } 103 104 /** Create a {@link VisibilityStore} instance to store blob visibility settings. */ createBlobVisibilityStore( @onNull AppSearchImpl appSearchImpl)105 public static @NonNull VisibilityStore createBlobVisibilityStore( 106 @NonNull AppSearchImpl appSearchImpl) throws AppSearchException { 107 List<String> cachedBlobNamespaces = appSearchImpl.getAllPrefixedBlobNamespaces(); 108 return new VisibilityStore(appSearchImpl, BLOB_VISIBILITY_DATABASE_NAME, 109 BLOB_ANDROID_V_OVERLAY_DATABASE_NAME, cachedBlobNamespaces); 110 } 111 112 /** 113 * Create a {@link VisibilityStore} instance to store visibility settings for given database. 114 * 115 * <p> We have 2 types of {@link VisibilityStore}, will base on the given database names to 116 * create the specific {@link VisibilityStore}. 117 * 118 * <p> To create a {@link VisibilityStore} to store document visibility settings, use 119 * {@link #DOCUMENT_VISIBILITY_DATABASE_NAME} and 120 * {@link #DOCUMENT_ANDROID_V_OVERLAY_DATABASE_NAME}. 121 * 122 * <p> To create a {@link VisibilityStore} to store blob visibility settings, use 123 * {@link #BLOB_VISIBILITY_DATABASE_NAME} and {@link #BLOB_ANDROID_V_OVERLAY_DATABASE_NAME}. 124 * 125 * @param appSearchImpl The {@link AppSearchImpl} instance to use to store 126 * visibility settings. 127 * @param databaseName The database name to store visibility settings. 128 * @param androidVOverlayDatabaseName The database name to store Android V overlay visibility 129 * settings. 130 * @param allVisibilityDocumentIds The list of all visibility document ids stored in the 131 * given database. 132 * @throws AppSearchException On internal error. 133 */ VisibilityStore(@onNull AppSearchImpl appSearchImpl, @NonNull String databaseName, @NonNull String androidVOverlayDatabaseName, @NonNull List<String> allVisibilityDocumentIds)134 private VisibilityStore(@NonNull AppSearchImpl appSearchImpl, @NonNull String databaseName, 135 @NonNull String androidVOverlayDatabaseName, 136 @NonNull List<String> allVisibilityDocumentIds) 137 throws AppSearchException { 138 mAppSearchImpl = Preconditions.checkNotNull(appSearchImpl); 139 mDatabaseName = Preconditions.checkNotNull(databaseName); 140 mAndroidVOverlayDatabaseName = Preconditions.checkNotNull(androidVOverlayDatabaseName); 141 142 GetSchemaResponse getSchemaResponse = mAppSearchImpl.getSchema( 143 VISIBILITY_PACKAGE_NAME, 144 mDatabaseName, 145 new CallerAccess(/*callingPackageName=*/VISIBILITY_PACKAGE_NAME)); 146 List<VisibilityDocumentV1> visibilityDocumentsV1s = null; 147 switch (getSchemaResponse.getVersion()) { 148 case VisibilityToDocumentConverter.SCHEMA_VERSION_DOC_PER_PACKAGE: 149 // TODO (b/202194495) add VisibilityDocument in version 0 back instead of using 150 // GenericDocument. 151 List<GenericDocument> visibilityDocumentsV0s = 152 VisibilityStoreMigrationHelperFromV0.getVisibilityDocumentsInVersion0( 153 getSchemaResponse, mAppSearchImpl); 154 visibilityDocumentsV1s = VisibilityStoreMigrationHelperFromV0 155 .toVisibilityDocumentV1(visibilityDocumentsV0s); 156 // fall through 157 case VisibilityToDocumentConverter.SCHEMA_VERSION_DOC_PER_SCHEMA: 158 if (visibilityDocumentsV1s == null) { 159 // We need to read VisibilityDocument in Version 1 from AppSearch instead of 160 // taking from the above step. 161 visibilityDocumentsV1s = 162 VisibilityStoreMigrationHelperFromV1.getVisibilityDocumentsInVersion1( 163 mAppSearchImpl); 164 } 165 setLatestSchemaAndDocuments(VisibilityStoreMigrationHelperFromV1 166 .toVisibilityDocumentsV2(visibilityDocumentsV1s)); 167 break; 168 case VisibilityToDocumentConverter.SCHEMA_VERSION_LATEST: 169 verifyOrSetLatestVisibilitySchema(getSchemaResponse); 170 // Check the version for visibility overlay database. 171 migrateVisibilityOverlayDatabase(); 172 // Now we have the latest schema, load visibility config map. 173 loadVisibilityConfigMap(allVisibilityDocumentIds); 174 break; 175 default: 176 // We must did something wrong. 177 throw new AppSearchException(AppSearchResult.RESULT_INTERNAL_ERROR, 178 "Found unsupported visibility version: " + getSchemaResponse.getVersion()); 179 } 180 } 181 182 /** 183 * Sets visibility settings for the given {@link InternalVisibilityConfig}s. Any previous 184 * {@link InternalVisibilityConfig}s with same prefixed schema type will be overwritten. 185 * 186 * @param prefixedVisibilityConfigs List of prefixed {@link InternalVisibilityConfig}s which 187 * contains schema type's visibility information. 188 * @throws AppSearchException on AppSearchImpl error. 189 */ 190 @SuppressWarnings("deprecation") setVisibility(@onNull List<InternalVisibilityConfig> prefixedVisibilityConfigs)191 public void setVisibility(@NonNull List<InternalVisibilityConfig> prefixedVisibilityConfigs) 192 throws AppSearchException { 193 Preconditions.checkNotNull(prefixedVisibilityConfigs); 194 // Save new setting. 195 for (int i = 0; i < prefixedVisibilityConfigs.size(); i++) { 196 // put VisibilityConfig to AppSearchImpl and mVisibilityConfigMap. If there is a 197 // VisibilityConfig with same prefixed schema exists, it will be replaced by new 198 // VisibilityConfig in both AppSearch and memory look up map. 199 InternalVisibilityConfig prefixedVisibilityConfig = prefixedVisibilityConfigs.get(i); 200 InternalVisibilityConfig oldVisibilityConfig = 201 mVisibilityConfigMap.get(prefixedVisibilityConfig.getSchemaType()); 202 // TODO(b/394875109) switch to use batchPut 203 mAppSearchImpl.putDocument( 204 VISIBILITY_PACKAGE_NAME, 205 mDatabaseName, 206 VisibilityToDocumentConverter.createVisibilityDocument( 207 prefixedVisibilityConfig), 208 /*sendChangeNotifications=*/ false, 209 /*logger=*/ null); 210 211 // Put the android V visibility overlay document to AppSearchImpl. 212 GenericDocument androidVOverlay = 213 VisibilityToDocumentConverter.createAndroidVOverlay(prefixedVisibilityConfig); 214 if (androidVOverlay != null) { 215 // TODO(b/394875109) switch to use batchPut 216 mAppSearchImpl.putDocument( 217 VISIBILITY_PACKAGE_NAME, 218 mAndroidVOverlayDatabaseName, 219 androidVOverlay, 220 /*sendChangeNotifications=*/ false, 221 /*logger=*/ null); 222 } else if (isConfigContainsAndroidVOverlay(oldVisibilityConfig)) { 223 // We need to make sure to remove the VisibilityOverlay on disk as the current 224 // VisibilityConfig does not have a VisibilityOverlay. 225 // For performance improvement, we should only make the remove call if the old 226 // VisibilityConfig contains the overlay settings. 227 try { 228 mAppSearchImpl.remove(VISIBILITY_PACKAGE_NAME, 229 mAndroidVOverlayDatabaseName, 230 ANDROID_V_OVERLAY_NAMESPACE, 231 prefixedVisibilityConfig.getSchemaType(), 232 /*removeStatsBuilder=*/null); 233 } catch (AppSearchException e) { 234 // If it already doesn't exist, that is fine 235 if (e.getResultCode() != RESULT_NOT_FOUND) { 236 throw e; 237 } 238 } 239 } 240 241 // Put the VisibilityConfig to memory look up map. 242 mVisibilityConfigMap.put(prefixedVisibilityConfig.getSchemaType(), 243 prefixedVisibilityConfig); 244 } 245 // Now that the visibility document has been written. Persist the newly written data. 246 mAppSearchImpl.persistToDisk(mAppSearchImpl.getConfig().getLightweightPersistType()); 247 } 248 249 /** 250 * Remove the visibility setting for the given prefixed schema type from both AppSearch and 251 * memory look up map. 252 */ removeVisibility(@onNull Set<String> prefixedSchemaTypes)253 public void removeVisibility(@NonNull Set<String> prefixedSchemaTypes) 254 throws AppSearchException { 255 for (String prefixedSchemaType : prefixedSchemaTypes) { 256 if (mVisibilityConfigMap.remove(prefixedSchemaType) != null) { 257 // The deleted schema is not all-default setting, we need to remove its 258 // VisibilityDocument from Icing. 259 try { 260 mAppSearchImpl.remove(VISIBILITY_PACKAGE_NAME, mDatabaseName, 261 VISIBILITY_DOCUMENT_NAMESPACE, 262 prefixedSchemaType, 263 /*removeStatsBuilder=*/null); 264 } catch (AppSearchException e) { 265 if (e.getResultCode() == RESULT_NOT_FOUND) { 266 // We are trying to remove this visibility setting, so it's weird but seems 267 // to be fine if we cannot find it. 268 Log.e(TAG, "Cannot find visibility document for " + prefixedSchemaType 269 + " to remove."); 270 } else { 271 throw e; 272 } 273 } 274 275 try { 276 mAppSearchImpl.remove(VISIBILITY_PACKAGE_NAME, 277 mAndroidVOverlayDatabaseName, 278 ANDROID_V_OVERLAY_NAMESPACE, 279 prefixedSchemaType, 280 /*removeStatsBuilder=*/null); 281 } catch (AppSearchException e) { 282 if (e.getResultCode() == RESULT_NOT_FOUND) { 283 // It's possible no overlay was set, so this this is fine. 284 if (LogUtil.DEBUG) { 285 Log.d(TAG, "Cannot find Android V overlay document for " 286 + prefixedSchemaType + " to remove."); 287 } 288 } else { 289 throw e; 290 } 291 } 292 } 293 } 294 } 295 296 /** Gets the {@link InternalVisibilityConfig} for the given prefixed schema type. */ getVisibility(@onNull String prefixedSchemaType)297 public @Nullable InternalVisibilityConfig getVisibility(@NonNull String prefixedSchemaType) { 298 return mVisibilityConfigMap.get(prefixedSchemaType); 299 } 300 301 /** 302 * Loads all stored latest {@link InternalVisibilityConfig} from Icing, and put them into 303 * {@link #mVisibilityConfigMap}. 304 * 305 * @param allVisibilityDocumentIds all of document ids that we should have visibility settings 306 * stored in this database. The Id should be either 307 * prefixedSchemaType for document visibility settings or 308 * prefixedBlobNamespace for blob visibility settings. 309 */ 310 @RequiresNonNull("mAppSearchImpl") loadVisibilityConfigMap(@nderInitialization VisibilityStore this, @NonNull List<String> allVisibilityDocumentIds)311 private void loadVisibilityConfigMap(@UnderInitialization VisibilityStore this, 312 @NonNull List<String> allVisibilityDocumentIds) 313 throws AppSearchException { 314 // Populate visibility settings set 315 if (Flags.enableQueryVisibilityDocuments()) { 316 // query all overlay document first and convert to id->doc map. 317 List<GenericDocument> androidVOverlayDocuments = queryVisibilityDocument( 318 mAndroidVOverlayDatabaseName, ANDROID_V_OVERLAY_NAMESPACE); 319 Map<String, GenericDocument> androidVOverlapMap = new ArrayMap<>( 320 androidVOverlayDocuments.size()); 321 for (int i = 0; i < androidVOverlayDocuments.size(); i++) { 322 GenericDocument androidVOverlayDocument = androidVOverlayDocuments.get(i); 323 androidVOverlapMap.put(androidVOverlayDocument.getId(), androidVOverlayDocument); 324 } 325 326 // It's ok to have null overlay document, and we should never have overlay document that 327 // doesn't have main visibility document. 328 List<GenericDocument> visibilityDocuments = queryVisibilityDocument( 329 mDatabaseName, VISIBILITY_DOCUMENT_NAMESPACE); 330 for (int i = 0; i < visibilityDocuments.size(); i++) { 331 GenericDocument visibilityDocument = visibilityDocuments.get(i); 332 GenericDocument visibilityAndroidVOverlay = 333 androidVOverlapMap.get(visibilityDocument.getId()); 334 mVisibilityConfigMap.put( 335 visibilityDocument.getId(), 336 VisibilityToDocumentConverter.createInternalVisibilityConfig( 337 visibilityDocument, visibilityAndroidVOverlay)); 338 } 339 } else { 340 for (int i = 0; i < allVisibilityDocumentIds.size(); i++) { 341 String visibilityDocumentId = allVisibilityDocumentIds.get(i); 342 String packageName = PrefixUtil.getPackageName(visibilityDocumentId); 343 if (packageName.equals(VISIBILITY_PACKAGE_NAME)) { 344 continue; // Our own package. Skip. 345 } 346 347 GenericDocument visibilityDocument; 348 GenericDocument visibilityAndroidVOverlay = null; 349 try { 350 // Note: We use the other clients' prefixed schema type as ids 351 visibilityDocument = mAppSearchImpl.getDocument( 352 VISIBILITY_PACKAGE_NAME, 353 mDatabaseName, 354 VISIBILITY_DOCUMENT_NAMESPACE, 355 /*id=*/ visibilityDocumentId, 356 /*typePropertyPaths=*/ Collections.emptyMap()); 357 } catch (AppSearchException e) { 358 if (e.getResultCode() == RESULT_NOT_FOUND) { 359 // The schema has all default setting and we won't have a VisibilityDocument 360 // for it. 361 continue; 362 } 363 // Otherwise, this is some other error we should pass up. 364 throw e; 365 } 366 367 try { 368 visibilityAndroidVOverlay = mAppSearchImpl.getDocument( 369 VISIBILITY_PACKAGE_NAME, 370 mAndroidVOverlayDatabaseName, 371 ANDROID_V_OVERLAY_NAMESPACE, 372 /*id=*/ visibilityDocumentId, 373 /*typePropertyPaths=*/ Collections.emptyMap()); 374 } catch (AppSearchException e) { 375 if (e.getResultCode() != RESULT_NOT_FOUND) { 376 // This is some other error we should pass up. 377 throw e; 378 } 379 // Otherwise we continue inserting into visibility document map as the overlay 380 // map can be null 381 } 382 383 mVisibilityConfigMap.put( 384 visibilityDocumentId, 385 VisibilityToDocumentConverter.createInternalVisibilityConfig( 386 visibilityDocument, visibilityAndroidVOverlay)); 387 } 388 } 389 } 390 391 @NonNull queryVisibilityDocument( @onNull String databaseName, @NonNull String namespace)392 private List<GenericDocument> queryVisibilityDocument( 393 @NonNull String databaseName, @NonNull String namespace) throws AppSearchException { 394 SearchSpec searchSpec = new SearchSpec.Builder() 395 .addFilterNamespaces(namespace) 396 .setResultCountPerPage(QUERY_RESULT_COUNT_PER_PAGE) 397 .build(); 398 SearchResultPage searchResultPage = mAppSearchImpl.query( 399 VISIBILITY_PACKAGE_NAME, 400 databaseName, 401 /*queryExpression=*/ "", 402 searchSpec, 403 /*logger=*/null); 404 List<GenericDocument> visibilityDocuments = new ArrayList<>(); 405 List<SearchResult> searchResults = searchResultPage.getResults(); 406 while (!searchResults.isEmpty()) { 407 for (int i = 0; i < searchResults.size(); i++) { 408 visibilityDocuments.add(searchResults.get(i).getGenericDocument()); 409 } 410 searchResultPage = mAppSearchImpl.getNextPage( 411 VISIBILITY_PACKAGE_NAME, 412 searchResultPage.getNextPageToken(), 413 /*statsBuilder=*/null); 414 searchResults = searchResultPage.getResults(); 415 } 416 return visibilityDocuments; 417 } 418 419 /** 420 * Set the latest version of {@link InternalVisibilityConfig} and its schema to AppSearch. 421 */ 422 @RequiresNonNull("mAppSearchImpl") 423 @SuppressWarnings("deprecation") setLatestSchemaAndDocuments( @nderInitialization VisibilityStore this, @NonNull List<InternalVisibilityConfig> migratedDocuments)424 private void setLatestSchemaAndDocuments( 425 @UnderInitialization VisibilityStore this, 426 @NonNull List<InternalVisibilityConfig> migratedDocuments) 427 throws AppSearchException { 428 // The latest schema type doesn't exist yet. Add it. Set forceOverride true to 429 // delete old schema. 430 InternalSetSchemaResponse internalSetSchemaResponse = mAppSearchImpl.setSchema( 431 VISIBILITY_PACKAGE_NAME, 432 mDatabaseName, 433 Arrays.asList(VisibilityToDocumentConverter.VISIBILITY_DOCUMENT_SCHEMA, 434 VisibilityPermissionConfig.SCHEMA), 435 /*visibilityConfigs=*/ Collections.emptyList(), 436 /*forceOverride=*/ true, 437 /*version=*/ VisibilityToDocumentConverter.SCHEMA_VERSION_LATEST, 438 /*setSchemaStatsBuilder=*/ null); 439 if (!internalSetSchemaResponse.isSuccess()) { 440 // Impossible case, we just set forceOverride to be true, we should never 441 // fail in incompatible changes. 442 throw new AppSearchException(AppSearchResult.RESULT_INTERNAL_ERROR, 443 internalSetSchemaResponse.getErrorMessage()); 444 } 445 InternalSetSchemaResponse internalSetAndroidVOverlaySchemaResponse = 446 mAppSearchImpl.setSchema( 447 VISIBILITY_PACKAGE_NAME, 448 mAndroidVOverlayDatabaseName, 449 Collections.singletonList( 450 VisibilityToDocumentConverter.ANDROID_V_OVERLAY_SCHEMA), 451 /*visibilityConfigs=*/ Collections.emptyList(), 452 /*forceOverride=*/ true, 453 /*version=*/ VisibilityToDocumentConverter 454 .ANDROID_V_OVERLAY_SCHEMA_VERSION_LATEST, 455 /*setSchemaStatsBuilder=*/ null); 456 if (!internalSetAndroidVOverlaySchemaResponse.isSuccess()) { 457 // Impossible case, we just set forceOverride to be true, we should never 458 // fail in incompatible changes. 459 throw new AppSearchException(AppSearchResult.RESULT_INTERNAL_ERROR, 460 internalSetAndroidVOverlaySchemaResponse.getErrorMessage()); 461 } 462 for (int i = 0; i < migratedDocuments.size(); i++) { 463 InternalVisibilityConfig migratedConfig = migratedDocuments.get(i); 464 mVisibilityConfigMap.put(migratedConfig.getSchemaType(), migratedConfig); 465 // TODO(b/394875109) switch to use batchPut 466 mAppSearchImpl.putDocument( 467 VISIBILITY_PACKAGE_NAME, 468 mDatabaseName, 469 VisibilityToDocumentConverter.createVisibilityDocument(migratedConfig), 470 /*sendChangeNotifications=*/ false, 471 /*logger=*/ null); 472 } 473 } 474 475 /** 476 * Check and migrate visibility schemas in {@link #mAndroidVOverlayDatabaseName} to 477 * {@link VisibilityToDocumentConverter#ANDROID_V_OVERLAY_SCHEMA_VERSION_LATEST}. 478 */ 479 @RequiresNonNull("mAppSearchImpl") migrateVisibilityOverlayDatabase(@nderInitialization VisibilityStore this)480 private void migrateVisibilityOverlayDatabase(@UnderInitialization VisibilityStore this) 481 throws AppSearchException { 482 GetSchemaResponse getSchemaResponse = mAppSearchImpl.getSchema( 483 VISIBILITY_PACKAGE_NAME, 484 mAndroidVOverlayDatabaseName, 485 new CallerAccess(/*callingPackageName=*/VISIBILITY_PACKAGE_NAME)); 486 switch (getSchemaResponse.getVersion()) { 487 case VisibilityToDocumentConverter.OVERLAY_SCHEMA_VERSION_PUBLIC_ACL_VISIBLE_TO_CONFIG: 488 // Force override to next version. This version hasn't released to any public 489 // version. There shouldn't have any public device in this state, so we don't 490 // actually need to migrate any document. 491 InternalSetSchemaResponse internalSetSchemaResponse = mAppSearchImpl.setSchema( 492 VISIBILITY_PACKAGE_NAME, 493 mAndroidVOverlayDatabaseName, 494 Collections.singletonList( 495 VisibilityToDocumentConverter.ANDROID_V_OVERLAY_SCHEMA), 496 /*visibilityConfigs=*/ Collections.emptyList(), 497 /*forceOverride=*/ true, // force update to nest version. 498 VisibilityToDocumentConverter.ANDROID_V_OVERLAY_SCHEMA_VERSION_LATEST, 499 /*setSchemaStatsBuilder=*/ null); 500 if (!internalSetSchemaResponse.isSuccess()) { 501 // Impossible case, we just set forceOverride to be true, we should never 502 // fail in incompatible changes. 503 throw new AppSearchException(AppSearchResult.RESULT_INTERNAL_ERROR, 504 internalSetSchemaResponse.getErrorMessage()); 505 } 506 break; 507 case VisibilityToDocumentConverter.OVERLAY_SCHEMA_VERSION_ALL_IN_PROTO: 508 verifyOrSetLatestVisibilityOverlaySchema(getSchemaResponse); 509 break; 510 default: 511 // We must did something wrong. 512 throw new AppSearchException(AppSearchResult.RESULT_INTERNAL_ERROR, 513 "Found unsupported visibility version: " + getSchemaResponse.getVersion()); 514 } 515 } 516 517 /** 518 * Verify the existing visibility schema, set the latest visibility schema if it's missing. 519 */ 520 @RequiresNonNull("mAppSearchImpl") verifyOrSetLatestVisibilitySchema( @nderInitialization VisibilityStore this, @NonNull GetSchemaResponse getSchemaResponse)521 private void verifyOrSetLatestVisibilitySchema( 522 @UnderInitialization VisibilityStore this, @NonNull GetSchemaResponse getSchemaResponse) 523 throws AppSearchException { 524 // We cannot change the schema version past 2 as detecting version "3" would hit the 525 // default block and throw an AppSearchException. This is why we added 526 // VisibilityOverlay. 527 528 // Check Visibility schema first. 529 Set<AppSearchSchema> existingVisibilitySchema = getSchemaResponse.getSchemas(); 530 // Force to override visibility schema if it contains DEPRECATED_PUBLIC_ACL_OVERLAY_SCHEMA. 531 // The DEPRECATED_PUBLIC_ACL_OVERLAY_SCHEMA was added to VISIBILITY_DATABASE_NAME and 532 // removed to ANDROID_V_OVERLAY_DATABASE_NAME. We need to force update the schema to 533 // migrate devices that have already store public acl schema. 534 // TODO(b/321326441) remove this method when we no longer to migrate devices in this state. 535 if (existingVisibilitySchema.contains( 536 VisibilityToDocumentConverter.VISIBILITY_DOCUMENT_SCHEMA) 537 && existingVisibilitySchema.contains(VisibilityPermissionConfig.SCHEMA) 538 && existingVisibilitySchema.contains( 539 VisibilityToDocumentConverter.DEPRECATED_PUBLIC_ACL_OVERLAY_SCHEMA)) { 540 InternalSetSchemaResponse internalSetSchemaResponse = mAppSearchImpl.setSchema( 541 VISIBILITY_PACKAGE_NAME, 542 mDatabaseName, 543 Arrays.asList(VisibilityToDocumentConverter.VISIBILITY_DOCUMENT_SCHEMA, 544 VisibilityPermissionConfig.SCHEMA), 545 /*visibilityConfigs=*/ Collections.emptyList(), 546 /*forceOverride=*/ true, 547 /*version=*/ VisibilityToDocumentConverter.SCHEMA_VERSION_LATEST, 548 /*setSchemaStatsBuilder=*/ null); 549 if (!internalSetSchemaResponse.isSuccess()) { 550 throw new AppSearchException(AppSearchResult.RESULT_INTERNAL_ERROR, 551 "Fail to force override deprecated visibility schema with public acl."); 552 } 553 } else if (!(existingVisibilitySchema.contains( 554 VisibilityToDocumentConverter.VISIBILITY_DOCUMENT_SCHEMA) 555 && existingVisibilitySchema.contains(VisibilityPermissionConfig.SCHEMA))) { 556 // We must have a broken schema. Reset it to the latest version. 557 // Do NOT set forceOverride to be true here, see comment below. 558 InternalSetSchemaResponse internalSetSchemaResponse = mAppSearchImpl.setSchema( 559 VISIBILITY_PACKAGE_NAME, 560 mDatabaseName, 561 Arrays.asList(VisibilityToDocumentConverter.VISIBILITY_DOCUMENT_SCHEMA, 562 VisibilityPermissionConfig.SCHEMA), 563 /*visibilityConfigs=*/ Collections.emptyList(), 564 /*forceOverride=*/ false, 565 /*version=*/ VisibilityToDocumentConverter.SCHEMA_VERSION_LATEST, 566 /*setSchemaStatsBuilder=*/ null); 567 if (!internalSetSchemaResponse.isSuccess()) { 568 // If you hit problem here it means you made a incompatible change in 569 // Visibility Schema without update the version number. You should bump 570 // the version number and create a VisibilityStoreMigrationHelper which 571 // can analyse the different between the old version and the new version 572 // to migration user's visibility settings. 573 throw new AppSearchException(AppSearchResult.RESULT_INTERNAL_ERROR, 574 "Fail to set the latest visibility schema to AppSearch. " 575 + "You may need to update the visibility schema version " 576 + "number."); 577 } 578 } 579 } 580 581 /** 582 * Verify the existing visibility overlay schema, set the latest overlay schema if it's missing. 583 */ 584 @RequiresNonNull("mAppSearchImpl") verifyOrSetLatestVisibilityOverlaySchema( @nknownInitialization VisibilityStore this, @NonNull GetSchemaResponse getAndroidVOverlaySchemaResponse)585 private void verifyOrSetLatestVisibilityOverlaySchema( 586 @UnknownInitialization VisibilityStore this, 587 @NonNull GetSchemaResponse getAndroidVOverlaySchemaResponse) 588 throws AppSearchException { 589 // Check Android V overlay schema. 590 Set<AppSearchSchema> existingAndroidVOverlaySchema = 591 getAndroidVOverlaySchemaResponse.getSchemas(); 592 if (!existingAndroidVOverlaySchema.contains( 593 VisibilityToDocumentConverter.ANDROID_V_OVERLAY_SCHEMA)) { 594 // We must have a broken schema. Reset it to the latest version. 595 // Do NOT set forceOverride to be true here, see comment below. 596 InternalSetSchemaResponse internalSetSchemaResponse = mAppSearchImpl.setSchema( 597 VISIBILITY_PACKAGE_NAME, 598 mAndroidVOverlayDatabaseName, 599 Collections.singletonList( 600 VisibilityToDocumentConverter.ANDROID_V_OVERLAY_SCHEMA), 601 /*visibilityConfigs=*/ Collections.emptyList(), 602 /*forceOverride=*/ false, 603 VisibilityToDocumentConverter.ANDROID_V_OVERLAY_SCHEMA_VERSION_LATEST, 604 /*setSchemaStatsBuilder=*/ null); 605 if (!internalSetSchemaResponse.isSuccess()) { 606 // If you hit problem here it means you made a incompatible change in 607 // Visibility Schema. You should create new overlay schema 608 throw new AppSearchException(AppSearchResult.RESULT_INTERNAL_ERROR, 609 "Fail to set the overlay visibility schema to AppSearch. " 610 + "You may need to create new overlay schema."); 611 } 612 } 613 } 614 615 /** 616 * Whether the given {@link InternalVisibilityConfig} contains Android V overlay settings. 617 * 618 * <p> Android V overlay {@link VisibilityToDocumentConverter#ANDROID_V_OVERLAY_SCHEMA} 619 * contains public acl and visible to config. 620 */ isConfigContainsAndroidVOverlay( @ullable InternalVisibilityConfig config)621 private static boolean isConfigContainsAndroidVOverlay( 622 @Nullable InternalVisibilityConfig config) { 623 return config != null 624 && (config.getVisibilityConfig().getPubliclyVisibleTargetPackage() != null 625 || !config.getVisibleToConfigs().isEmpty()); 626 } 627 } 628