Add visibility migration helper and test.
We upgrade Visibility schema from version 0 to 1.
Version 0: one VisibilityDocument contains all visibility information
for whole package.
Version 1: One VisibilityDocument contains visibility information for
a single schema.
We need a migration plan to ensure upgrading devices won't lost
any information.
Test: VisibilityStoreMigrationHelperTest
Bug: 203736617
Change-Id: I405fe8c130d3c88a69ec4360a83b07faa33b6d0d
diff --git a/service/java/com/android/server/appsearch/external/localstorage/visibilitystore/VisibilityStore.java b/service/java/com/android/server/appsearch/external/localstorage/visibilitystore/VisibilityStore.java
index 114cf8b..e822faa 100644
--- a/service/java/com/android/server/appsearch/external/localstorage/visibilitystore/VisibilityStore.java
+++ b/service/java/com/android/server/appsearch/external/localstorage/visibilitystore/VisibilityStore.java
@@ -36,7 +36,7 @@
*/
String PACKAGE_NAME = "VS#Pkg";
- @VisibleForTesting String DATABASE_NAME = "VS#Db";
+ String DATABASE_NAME = "VS#Db";
/**
* Sets visibility settings for the given {@link VisibilityDocument}s. Any previous
diff --git a/service/java/com/android/server/appsearch/visibilitystore/VisibilityStoreImpl.java b/service/java/com/android/server/appsearch/visibilitystore/VisibilityStoreImpl.java
index 01379db..c986798 100644
--- a/service/java/com/android/server/appsearch/visibilitystore/VisibilityStoreImpl.java
+++ b/service/java/com/android/server/appsearch/visibilitystore/VisibilityStoreImpl.java
@@ -16,10 +16,12 @@
package com.android.server.appsearch.visibilitystore;
import static android.Manifest.permission.READ_GLOBAL_APP_SEARCH_DATA;
+import static android.app.appsearch.AppSearchResult.RESULT_INTERNAL_ERROR;
import static android.app.appsearch.AppSearchResult.RESULT_NOT_FOUND;
import android.annotation.NonNull;
import android.app.appsearch.AppSearchSchema;
+import android.app.appsearch.GenericDocument;
import android.app.appsearch.GetSchemaResponse;
import android.app.appsearch.exceptions.AppSearchException;
import android.content.Context;
@@ -60,6 +62,15 @@
*/
public class VisibilityStoreImpl implements VisibilityStore {
private static final String TAG = "AppSearchVisibilityStor";
+ // The initial schema version, one VisibilityDocument contains all visibility information for
+ // whole package.
+ private static final int SCHEMA_VERSION_DOC_PER_PACKAGE = 0;
+
+ // One VisibilityDocument contains visibility information for a single schema.
+ private static final int SCHEMA_VERSION_DOC_PER_SCHEMA = 1;
+
+ private static final int SCHEMA_VERSION_LATEST = SCHEMA_VERSION_DOC_PER_SCHEMA;
+
/** Version for the visibility schema */
private static final int SCHEMA_VERSION = 1;
@@ -92,30 +103,21 @@
mAppSearchImpl = Objects.requireNonNull(appSearchImpl);
mUserContext = Objects.requireNonNull(userContext);
- // TODO(b/202194495) handle schema migration from version 0 to 1.
GetSchemaResponse getSchemaResponse =
mAppSearchImpl.getSchema(PACKAGE_NAME, PACKAGE_NAME, DATABASE_NAME);
- boolean hasVisibilityType = false;
- for (AppSearchSchema schema : getSchemaResponse.getSchemas()) {
- if (schema.getSchemaType().equals(VisibilityDocument.SCHEMA_TYPE)) {
- hasVisibilityType = true;
- // Found our type, can exit early.
+ switch (getSchemaResponse.getVersion()) {
+ case SCHEMA_VERSION_DOC_PER_PACKAGE:
+ maybeMigrateToLatest(getSchemaResponse);
break;
- }
- }
- if (!hasVisibilityType) {
- // The latest schema type doesn't exist yet. Add it.
- mAppSearchImpl.setSchema(
- PACKAGE_NAME,
- DATABASE_NAME,
- Collections.singletonList(VisibilityDocument.SCHEMA),
- /*visibilityStore=*/ null, // Avoid recursive calls
- /*prefixedVisibilityDocuments=*/ Collections.emptyList(),
- /*forceOverride=*/ false,
- /*version=*/ SCHEMA_VERSION,
- /*setSchemaStatsBuilder=*/ null);
- } else {
- loadVisibilityDocumentMap();
+ case SCHEMA_VERSION_LATEST:
+ // The latest Visibility schema is in AppSearch, we must find our schema type.
+ // Extract all stored Visibility Document into mVisibilityDocumentMap.
+ loadVisibilityDocumentMap();
+ break;
+ default:
+ // We must did something wrong.
+ throw new AppSearchException(RESULT_INTERNAL_ERROR,
+ "Found unsupported visibility version: " + getSchemaResponse.getVersion());
}
}
@@ -134,7 +136,6 @@
mVisibilityDocumentMap.put(prefixedVisibilityDocument.getId(),
prefixedVisibilityDocument);
}
-
// Now that the visibility document has been written. Persist the newly written data.
mAppSearchImpl.persistToDisk(PersistType.Code.LITE);
}
@@ -288,4 +289,58 @@
mVisibilityDocumentMap.put(prefixedSchemaType, visibilityDocument);
}
}
+
+ /**
+ * Migrate the {@link VisibilityDocument} to the latest version.
+ *
+ * <p>If the current version number is 0, it is possible that the database is just empty and it
+ * return 0 as the default version number. So we need to check if the deprecated document
+ * presents to trigger the migration.
+ */
+ private void maybeMigrateToLatest(@NonNull GetSchemaResponse getSchemaResponse)
+ throws AppSearchException {
+ // The database maybe empty or has the oldest version of deprecated schema.
+ boolean hasDeprecatedType = false;
+ List<GenericDocument> deprecatedDocuments = null;
+ for (AppSearchSchema schema : getSchemaResponse.getSchemas()) {
+ if (VisibilityStoreMigrationHelperFromV0
+ .isDeprecatedType(schema.getSchemaType())) {
+ hasDeprecatedType = true;
+ // Found deprecated type, we need to migrate visibility Document. And it's
+ // not possible for us to find the latest visibility schema.
+ break;
+ }
+ }
+ if (hasDeprecatedType) {
+ // Read deprecated Visibility Document, migrate them to the latest version and
+ // save in mVisibilityDocumentMap.
+ deprecatedDocuments = VisibilityStoreMigrationHelperFromV0
+ .getVisibilityDocumentsInVersion0(mAppSearchImpl);
+ }
+ // The latest schema type doesn't exist yet. Add it. Set forceOverride true to
+ // delete old schema.
+ mAppSearchImpl.setSchema(
+ PACKAGE_NAME,
+ DATABASE_NAME,
+ Collections.singletonList(VisibilityDocument.SCHEMA),
+ /*visibilityStore=*/ null, // Avoid recursive calls
+ /*visibilityDocuments=*/ Collections.emptyList(),
+ /*forceOverride=*/ true,
+ /*version=*/ SCHEMA_VERSION,
+ /*setSchemaStatsBuilder=*/ null);
+ if (hasDeprecatedType) {
+ Map<String, VisibilityDocument.Builder> documentBuilderMap =
+ VisibilityStoreMigrationHelperFromV0.toVisibilityDocumentsV1(
+ deprecatedDocuments);
+
+ // Build visibility document from builder and put to AppSearch
+ for (Map.Entry<String, VisibilityDocument.Builder> entry :
+ documentBuilderMap.entrySet()) {
+ VisibilityDocument visibilityDocument = entry.getValue().build();
+ mVisibilityDocumentMap.put(entry.getKey(), visibilityDocument);
+ mAppSearchImpl.putDocument(PACKAGE_NAME, DATABASE_NAME,
+ visibilityDocument, /*logger=*/ null);
+ }
+ }
+ }
}
diff --git a/service/java/com/android/server/appsearch/visibilitystore/VisibilityStoreMigrationHelperFromV0.java b/service/java/com/android/server/appsearch/visibilitystore/VisibilityStoreMigrationHelperFromV0.java
new file mode 100644
index 0000000..f3497bc
--- /dev/null
+++ b/service/java/com/android/server/appsearch/visibilitystore/VisibilityStoreMigrationHelperFromV0.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appsearch.visibilitystore;
+
+import android.annotation.NonNull;
+import android.app.appsearch.AppSearchResult;
+import android.app.appsearch.GenericDocument;
+import android.app.appsearch.PackageIdentifier;
+import android.app.appsearch.exceptions.AppSearchException;
+import android.util.ArrayMap;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.appsearch.external.localstorage.AppSearchImpl;
+import com.android.server.appsearch.external.localstorage.util.PrefixUtil;
+import com.android.server.appsearch.external.localstorage.visibilitystore.VisibilityStore;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * The helper class to store Visibility Document information of version 0 and handle the upgrade to
+ * version 1.
+ *
+ * @hide
+ */
+public class VisibilityStoreMigrationHelperFromV0 {
+ private VisibilityStoreMigrationHelperFromV0() {}
+ /** Prefix to add to all visibility document ids. IcingSearchEngine doesn't allow empty ids. */
+ private static final String DEPRECATED_ID_PREFIX = "uri:";
+
+ /** Schema type for documents that hold AppSearch's metadata, e.g. visibility settings */
+ @VisibleForTesting
+ static final String DEPRECATED_VISIBILITY_SCHEMA_TYPE = "VisibilityType";
+
+ /**
+ * Property that holds the list of platform-hidden schemas, as part of the visibility settings.
+ */
+ @VisibleForTesting
+ static final String DEPRECATED_NOT_DISPLAYED_BY_SYSTEM_PROPERTY =
+ "notPlatformSurfaceable";
+
+ /** Property that holds nested documents of package accessible schemas. */
+ @VisibleForTesting
+ static final String DEPRECATED_VISIBLE_TO_PACKAGES_PROPERTY = "packageAccessible";
+
+ /**
+ * Property that holds the list of platform-hidden schemas, as part of the visibility settings.
+ */
+ @VisibleForTesting
+ static final String DEPRECATED_PACKAGE_SCHEMA_TYPE = "PackageAccessibleType";
+
+ /** Property that holds the prefixed schema type that is accessible by some package. */
+ @VisibleForTesting
+ static final String DEPRECATED_ACCESSIBLE_SCHEMA_PROPERTY = "accessibleSchema";
+
+ /** Property that holds the package name that can access a schema. */
+ @VisibleForTesting
+ static final String DEPRECATED_PACKAGE_NAME_PROPERTY = "packageName";
+
+ /** Property that holds the SHA 256 certificate of the app that can access a schema. */
+ @VisibleForTesting
+ static final String DEPRECATED_SHA_256_CERT_PROPERTY = "sha256Cert";
+
+// The visibility schema of version 0.
+//--------------------------------------------------------------------------------------------------
+// Schema of DEPRECATED_VISIBILITY_SCHEMA_TYPE:
+// new AppSearchSchema.Builder(
+// DEPRECATED_VISIBILITY_SCHEMA_TYPE)
+// .addProperty(new AppSearchSchema.StringPropertyConfig.Builder(
+// DEPRECATED_NOT_DISPLAYED_BY_SYSTEM_PROPERTY)
+// .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
+// .build())
+// .addProperty(new AppSearchSchema.DocumentPropertyConfig.Builder(
+// DEPRECATED_VISIBLE_TO_PACKAGES_PROPERTY,
+// DEPRECATED_PACKAGE_SCHEMA_TYPE)
+// .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
+// .build())
+// .build();
+// Schema of DEPRECATED_PACKAGE_SCHEMA_TYPE:
+// new AppSearchSchema.Builder(DEPRECATED_PACKAGE_SCHEMA_TYPE)
+// .addProperty(new AppSearchSchema.StringPropertyConfig.Builder(
+// DEPRECATED_PACKAGE_NAME_PROPERTY)
+// .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+// .build())
+// .addProperty(new AppSearchSchema.BytesPropertyConfig.Builder(
+// DEPRECATED_SHA_256_CERT_PROPERTY)
+// .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+// .build())
+// .addProperty(new AppSearchSchema.StringPropertyConfig.Builder(
+// DEPRECATED_ACCESSIBLE_SCHEMA_PROPERTY)
+// .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+// .build())
+// .build();
+//--------------------------------------------------------------------------------------------------
+
+ /** Returns whether the given schema type is deprecated. */
+ static boolean isDeprecatedType(@NonNull String schemaType) {
+ return schemaType.equals(DEPRECATED_VISIBILITY_SCHEMA_TYPE)
+ || schemaType.equals(DEPRECATED_PACKAGE_SCHEMA_TYPE);
+ }
+
+ /**
+ * Adds a prefix to create a deprecated visibility document's id.
+ *
+ * @param packageName Package to which the visibility doc refers.
+ * @param databaseName Database to which the visibility doc refers.
+ * @return deprecated visibility document's id.
+ */
+ @NonNull
+ static String getDeprecatedVisibilityDocumentId(
+ @NonNull String packageName, @NonNull String databaseName) {
+ return DEPRECATED_ID_PREFIX + PrefixUtil.createPrefix(packageName, databaseName);
+ }
+
+ /** Reads all stored deprecated Visibility Document in version 0 from icing. */
+ static List<GenericDocument> getVisibilityDocumentsInVersion0(
+ @NonNull AppSearchImpl appSearchImpl) throws AppSearchException {
+ Map<String, Set<String>> packageToDatabases = appSearchImpl.getPackageToDatabases();
+ List<GenericDocument> deprecatedDocuments = new ArrayList<>(packageToDatabases.size());
+ for (Map.Entry<String, Set<String>> entry : packageToDatabases.entrySet()) {
+ String packageName = entry.getKey();
+ if (packageName.equals(VisibilityStore.PACKAGE_NAME)) {
+ continue; // Our own package. Skip.
+ }
+ for (String databaseName : entry.getValue()) {
+ try {
+ // Note: We use the other clients' prefixed names as ids
+ deprecatedDocuments.add(appSearchImpl.getDocument(
+ VisibilityStore.PACKAGE_NAME,
+ VisibilityStore.DATABASE_NAME,
+ VisibilityDocument.NAMESPACE,
+ getDeprecatedVisibilityDocumentId(packageName, databaseName),
+ /*typePropertyPaths=*/ Collections.emptyMap()));
+ } catch (AppSearchException e) {
+ if (e.getResultCode() == AppSearchResult.RESULT_NOT_FOUND) {
+ // TODO(b/172068212): This indicates some desync error. We were expecting a
+ // document, but didn't find one. Should probably reset AppSearch instead
+ // of ignoring it.
+ continue;
+ }
+ // Otherwise, this is some other error we should pass up.
+ throw e;
+ }
+ }
+ }
+ return deprecatedDocuments;
+ }
+
+ /**
+ * Converts the given list of deprecated Visibility Documents into a Map of {@code
+ * <PrefixedSchemaType, VisibilityDocument.Builder of the latest version>}.
+ *
+ * @param deprecatedDocuments The deprecated Visibility Document we found.
+ */
+ @NonNull
+ static Map<String, VisibilityDocument.Builder> toVisibilityDocumentsV1(
+ @NonNull List<GenericDocument> deprecatedDocuments) {
+ Map<String, VisibilityDocument.Builder> documentBuilderMap = new ArrayMap<>();
+
+ // Set all visibility information into documentBuilderMap
+ for (int i = 0; i < deprecatedDocuments.size(); i++) {
+ GenericDocument deprecatedDocument = deprecatedDocuments.get(i);
+
+ // Read not displayed by system property field.
+ String[] notDisplayedBySystemSchemas = deprecatedDocument.getPropertyStringArray(
+ DEPRECATED_NOT_DISPLAYED_BY_SYSTEM_PROPERTY);
+ if (notDisplayedBySystemSchemas != null) {
+ for (String notDisplayedBySystemSchema : notDisplayedBySystemSchemas) {
+ // SetSchemaRequest.Builder.build() make sure all schemas that has visibility
+ // setting must present in the requests.
+ VisibilityDocument.Builder visibilityBuilder = getOrCreateBuilder(
+ documentBuilderMap, notDisplayedBySystemSchema);
+ visibilityBuilder.setNotDisplayedBySystem(true);
+ }
+ }
+
+ // Read visible to packages field.
+ GenericDocument[] deprecatedPackageDocuments = deprecatedDocument
+ .getPropertyDocumentArray(DEPRECATED_VISIBLE_TO_PACKAGES_PROPERTY);
+ if (deprecatedPackageDocuments != null) {
+ for (GenericDocument deprecatedPackageDocument : deprecatedPackageDocuments) {
+ String prefixedSchemaType = deprecatedPackageDocument
+ .getPropertyString(DEPRECATED_ACCESSIBLE_SCHEMA_PROPERTY);
+ VisibilityDocument.Builder visibilityBuilder = getOrCreateBuilder(
+ documentBuilderMap, prefixedSchemaType);
+ visibilityBuilder.addVisibleToPackage(new PackageIdentifier(
+ deprecatedPackageDocument.getPropertyString(
+ DEPRECATED_PACKAGE_NAME_PROPERTY),
+ deprecatedPackageDocument.getPropertyBytes(
+ DEPRECATED_SHA_256_CERT_PROPERTY)));
+ }
+ }
+ }
+ return documentBuilderMap;
+ }
+
+ @NonNull
+ private static VisibilityDocument.Builder getOrCreateBuilder(
+ @NonNull Map<String, VisibilityDocument.Builder> documentBuilderMap,
+ @NonNull String schemaType) {
+ VisibilityDocument.Builder builder = documentBuilderMap.get(schemaType);
+ if (builder == null) {
+ builder = new VisibilityDocument.Builder(/*id=*/ schemaType);
+ documentBuilderMap.put(schemaType, builder);
+ }
+ return builder;
+ }
+}
diff --git a/testing/servicestests/src/com/android/server/appsearch/visibilitystore/VisibilityStoreMigrationHelperFromV0Test.java b/testing/servicestests/src/com/android/server/appsearch/visibilitystore/VisibilityStoreMigrationHelperFromV0Test.java
new file mode 100644
index 0000000..e50a349
--- /dev/null
+++ b/testing/servicestests/src/com/android/server/appsearch/visibilitystore/VisibilityStoreMigrationHelperFromV0Test.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appsearch.visibilitystore;
+
+import static android.Manifest.permission.READ_GLOBAL_APP_SEARCH_DATA;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+
+import static com.android.server.appsearch.visibilitystore.VisibilityStoreMigrationHelperFromV0.DEPRECATED_ACCESSIBLE_SCHEMA_PROPERTY;
+import static com.android.server.appsearch.visibilitystore.VisibilityStoreMigrationHelperFromV0.DEPRECATED_NOT_DISPLAYED_BY_SYSTEM_PROPERTY;
+import static com.android.server.appsearch.visibilitystore.VisibilityStoreMigrationHelperFromV0.DEPRECATED_PACKAGE_NAME_PROPERTY;
+import static com.android.server.appsearch.visibilitystore.VisibilityStoreMigrationHelperFromV0.DEPRECATED_PACKAGE_SCHEMA_TYPE;
+import static com.android.server.appsearch.visibilitystore.VisibilityStoreMigrationHelperFromV0.DEPRECATED_SHA_256_CERT_PROPERTY;
+import static com.android.server.appsearch.visibilitystore.VisibilityStoreMigrationHelperFromV0.DEPRECATED_VISIBILITY_SCHEMA_TYPE;
+import static com.android.server.appsearch.visibilitystore.VisibilityStoreMigrationHelperFromV0.DEPRECATED_VISIBLE_TO_PACKAGES_PROPERTY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.app.appsearch.AppSearchSchema;
+import android.app.appsearch.GenericDocument;
+import android.app.appsearch.PackageIdentifier;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.PackageManager;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.server.appsearch.external.localstorage.AppSearchImpl;
+import com.android.server.appsearch.external.localstorage.OptimizeStrategy;
+import com.android.server.appsearch.external.localstorage.UnlimitedLimitConfig;
+import com.android.server.appsearch.external.localstorage.util.PrefixUtil;
+import com.android.server.appsearch.external.localstorage.visibilitystore.VisibilityStore;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.mockito.Mockito;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.Map;
+
+public class VisibilityStoreMigrationHelperFromV0Test {
+
+ /**
+ * Always trigger optimize in this class. OptimizeStrategy will be tested in its own test class.
+ */
+ private static final OptimizeStrategy ALWAYS_OPTIMIZE = optimizeInfo -> true;
+
+ @Rule
+ public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+ private final Map<UserHandle, PackageManager> mMockPackageManagers = new ArrayMap<>();
+ private Context mContext;
+ private File mFile;
+
+ @Before
+ public void setUp() throws Exception {
+ Context context = ApplicationProvider.getApplicationContext();
+ mContext = new ContextWrapper(context) {
+ @Override
+ public Context createContextAsUser(UserHandle user, int flags) {
+ return new ContextWrapper(super.createContextAsUser(user, flags)) {
+ @Override
+ public PackageManager getPackageManager() {
+ return getMockPackageManager(user);
+ }
+ };
+ }
+
+ @Override
+ public PackageManager getPackageManager() {
+ return createContextAsUser(getUser(), /*flags=*/ 0).getPackageManager();
+ }
+ };
+
+ // Give ourselves global query permissions
+ mFile = mTemporaryFolder.newFolder();
+ }
+
+ @Test
+ public void testVisibilityMigration_from0() throws Exception {
+ // Values for a "foo" client
+ String packageNameFoo = "packageFoo";
+ byte[] sha256CertFoo = new byte[32];
+
+ // Values for a "bar" client
+ String packageNameBar = "packageBar";
+ byte[] sha256CertBar = new byte[32];
+
+ // Create AppSearchImpl with visibility document version 0;
+ AppSearchImpl appSearchImplInV0 = buildAppSearchImplInV0();
+ // Build deprecated visibility documents in version 0
+ // "schema1" and "schema2" are platform hidden.
+ // "schema1" is accessible to packageFoo and "schema2" is accessible to packageBar.
+ String prefix = PrefixUtil.createPrefix("package", "database");
+ GenericDocument deprecatedVisibilityToPackageFoo = new GenericDocument.Builder<>(
+ VisibilityDocument.NAMESPACE, "", DEPRECATED_PACKAGE_SCHEMA_TYPE)
+ .setPropertyString(DEPRECATED_ACCESSIBLE_SCHEMA_PROPERTY, prefix + "Schema1")
+ .setPropertyString(DEPRECATED_PACKAGE_NAME_PROPERTY, packageNameFoo)
+ .setPropertyBytes(DEPRECATED_SHA_256_CERT_PROPERTY, sha256CertFoo)
+ .build();
+ GenericDocument deprecatedVisibilityToPackageBar = new GenericDocument.Builder<>(
+ VisibilityDocument.NAMESPACE, "", DEPRECATED_PACKAGE_SCHEMA_TYPE)
+ .setPropertyString(DEPRECATED_ACCESSIBLE_SCHEMA_PROPERTY, prefix + "Schema2")
+ .setPropertyString(DEPRECATED_PACKAGE_NAME_PROPERTY, packageNameBar)
+ .setPropertyBytes(DEPRECATED_SHA_256_CERT_PROPERTY, sha256CertBar)
+ .build();
+ GenericDocument deprecatedVisibilityDocument = new GenericDocument.Builder<>(
+ VisibilityDocument.NAMESPACE,
+ VisibilityStoreMigrationHelperFromV0.getDeprecatedVisibilityDocumentId(
+ "package", "database"),
+ DEPRECATED_VISIBILITY_SCHEMA_TYPE)
+ .setPropertyString(DEPRECATED_NOT_DISPLAYED_BY_SYSTEM_PROPERTY,
+ prefix + "Schema1", prefix + "Schema2")
+ .setPropertyDocument(DEPRECATED_VISIBLE_TO_PACKAGES_PROPERTY,
+ deprecatedVisibilityToPackageFoo, deprecatedVisibilityToPackageBar)
+ .build();
+
+ // Put deprecated visibility documents in version 0 to AppSearchImpl
+ appSearchImplInV0.putDocument(
+ VisibilityStore.PACKAGE_NAME,
+ VisibilityStore.DATABASE_NAME,
+ deprecatedVisibilityDocument,
+ /*logger=*/null);
+
+ // Set some client schemas into AppSearchImpl.
+ appSearchImplInV0.setSchema(
+ "package",
+ "database",
+ ImmutableList.of(
+ new AppSearchSchema.Builder("schema1").build(),
+ new AppSearchSchema.Builder("schema2").build()),
+ /*VisibilityStore=*/null,
+ /*prefixedVisibilityBundles=*/ Collections.emptyList(),
+ /*forceOverride=*/ false,
+ /*schemaVersion=*/ 0,
+ /*setSchemaStatsBuilder=*/ null);
+
+ // Persist to disk and re-open the AppSearchImpl
+ appSearchImplInV0.close();
+ AppSearchImpl appSearchImpl = AppSearchImpl.create(mFile, new UnlimitedLimitConfig(),
+ /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE);
+
+ // Create Visibility store by AppSearchImpl, all deprecated visibility documents should be
+ // already migrated to version 1. And we won't lost any information.
+ VisibilityStoreImpl.create(appSearchImpl, mContext);
+
+ VisibilityDocument actualDocument1 = new VisibilityDocument(
+ appSearchImpl.getDocument(
+ VisibilityStore.PACKAGE_NAME,
+ VisibilityStore.DATABASE_NAME,
+ VisibilityDocument.NAMESPACE,
+ /*id=*/ prefix + "Schema1",
+ /*typePropertyPaths=*/ Collections.emptyMap()));
+ VisibilityDocument actualDocument2 = new VisibilityDocument(
+ appSearchImpl.getDocument(
+ VisibilityStore.PACKAGE_NAME,
+ VisibilityStore.DATABASE_NAME,
+ VisibilityDocument.NAMESPACE,
+ /*id=*/ prefix + "Schema2",
+ /*typePropertyPaths=*/ Collections.emptyMap()));
+
+ VisibilityDocument expectedDocument1 =
+ new VisibilityDocument.Builder(/*id=*/ prefix + "Schema1")
+ .setNotDisplayedBySystem(true)
+ .setCreationTimestampMillis(actualDocument1.getCreationTimestampMillis())
+ .addVisibleToPackage(new PackageIdentifier(packageNameFoo, sha256CertFoo))
+ .build();
+ VisibilityDocument expectedDocument2 =
+ new VisibilityDocument.Builder(/*id=*/ prefix + "Schema2")
+ .setNotDisplayedBySystem(true)
+ .setCreationTimestampMillis(actualDocument2.getCreationTimestampMillis())
+ .addVisibleToPackage(new PackageIdentifier(packageNameBar, sha256CertBar))
+ .build();
+ assertThat(actualDocument1).isEqualTo(expectedDocument1);
+ assertThat(actualDocument2).isEqualTo(expectedDocument2);
+ }
+
+ /** Build AppSearchImpl with deprecated visibility schemas version 0. */
+ private AppSearchImpl buildAppSearchImplInV0() throws Exception {
+ AppSearchSchema visibilityDocumentSchemaV0 = new AppSearchSchema.Builder(
+ DEPRECATED_VISIBILITY_SCHEMA_TYPE)
+ .addProperty(new AppSearchSchema.StringPropertyConfig.Builder(
+ DEPRECATED_NOT_DISPLAYED_BY_SYSTEM_PROPERTY)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
+ .build())
+ .addProperty(new AppSearchSchema.DocumentPropertyConfig.Builder(
+ DEPRECATED_VISIBLE_TO_PACKAGES_PROPERTY,
+ DEPRECATED_PACKAGE_SCHEMA_TYPE)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
+ .build())
+ .build();
+ AppSearchSchema visibilityToPackagesSchemaV0 = new AppSearchSchema.Builder(
+ DEPRECATED_PACKAGE_SCHEMA_TYPE)
+ .addProperty(new AppSearchSchema.StringPropertyConfig.Builder(
+ DEPRECATED_PACKAGE_NAME_PROPERTY)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .build())
+ .addProperty(new AppSearchSchema.BytesPropertyConfig.Builder(
+ DEPRECATED_SHA_256_CERT_PROPERTY)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .build())
+ .addProperty(new AppSearchSchema.StringPropertyConfig.Builder(
+ DEPRECATED_ACCESSIBLE_SCHEMA_PROPERTY)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .build())
+ .build();
+ // Set deprecated visibility schema version 0 into AppSearchImpl.
+ AppSearchImpl appSearchImpl = AppSearchImpl.create(mFile, new UnlimitedLimitConfig(),
+ /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE);
+ appSearchImpl.setSchema(
+ VisibilityStore.PACKAGE_NAME,
+ VisibilityStore.DATABASE_NAME,
+ ImmutableList.of(visibilityDocumentSchemaV0, visibilityToPackagesSchemaV0),
+ /*VisibilityStore=*/null,
+ /*prefixedVisibilityBundles=*/ Collections.emptyList(),
+ /*forceOverride=*/ false,
+ /*version=*/ 0,
+ /*setSchemaStatsBuilder=*/ null);
+ return appSearchImpl;
+ }
+
+ @NonNull
+ private PackageManager getMockPackageManager(@NonNull UserHandle user) {
+ PackageManager pm = mMockPackageManagers.get(user);
+ if (pm == null) {
+ pm = Mockito.mock(PackageManager.class);
+ mMockPackageManagers.put(user, pm);
+ }
+ return pm;
+ }
+}