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