Refactor VisibilityStore's docs and maps to separate objects.
This reduces the volume of code VisibilityStore has to manage.
Bug: 169883602
Test: VisibilityStoreTest
Change-Id: I422f1c5f35fd4ded49cbd102530c574092bb4a74
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java b/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java
index 0f643c5..b7e2159 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java
@@ -36,6 +36,10 @@
import android.util.Log;
import com.android.server.appsearch.external.localstorage.util.PrefixUtil;
+import com.android.server.appsearch.visibilitystore.NotPlatformSurfaceableMap;
+import com.android.server.appsearch.visibilitystore.PackageAccessibleDocument;
+import com.android.server.appsearch.visibilitystore.PackageAccessibleMap;
+import com.android.server.appsearch.visibilitystore.VisibilityDocument;
import com.google.android.icing.proto.PersistType;
@@ -74,74 +78,10 @@
/** No-op user id that won't have any visibility settings. */
public static final int NO_OP_USER_ID = -1;
- /** Schema type for documents that hold AppSearch's metadata, e.g. visibility settings */
- private static final String VISIBILITY_TYPE = "VisibilityType";
-
/** Version for the visibility schema */
private static final int SCHEMA_VERSION = 0;
/**
- * Property that holds the list of platform-hidden schemas, as part of the visibility settings.
- */
- private static final String NOT_PLATFORM_SURFACEABLE_PROPERTY = "notPlatformSurfaceable";
-
- /** Property that holds nested documents of package accessible schemas. */
- private static final String PACKAGE_ACCESSIBLE_PROPERTY = "packageAccessible";
-
- /** Schema type for nested documents that hold package accessible information. */
- private static final String PACKAGE_ACCESSIBLE_TYPE = "PackageAccessibleType";
-
- /** Property that holds the package name that can access a schema. */
- private static final String PACKAGE_NAME_PROPERTY = "packageName";
-
- /** Property that holds the SHA 256 certificate of the app that can access a schema. */
- private static final String SHA_256_CERT_PROPERTY = "sha256Cert";
-
- /** Property that holds the prefixed schema type that is accessible by some package. */
- private static final String ACCESSIBLE_SCHEMA_PROPERTY = "accessibleSchema";
-
- /** Schema for the VisibilityStore's documents. */
- private static final AppSearchSchema VISIBILITY_SCHEMA =
- new AppSearchSchema.Builder(VISIBILITY_TYPE)
- .addProperty(
- new AppSearchSchema.StringPropertyConfig.Builder(
- NOT_PLATFORM_SURFACEABLE_PROPERTY)
- .setCardinality(
- AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
- .build())
- .addProperty(
- new AppSearchSchema.DocumentPropertyConfig.Builder(
- PACKAGE_ACCESSIBLE_PROPERTY, PACKAGE_ACCESSIBLE_TYPE)
- .setCardinality(
- AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
- .build())
- .build();
-
- /**
- * Schema for package accessible documents, these will be nested in a top-level visibility
- * document.
- */
- private static final AppSearchSchema PACKAGE_ACCESSIBLE_SCHEMA =
- new AppSearchSchema.Builder(PACKAGE_ACCESSIBLE_TYPE)
- .addProperty(
- new AppSearchSchema.StringPropertyConfig.Builder(PACKAGE_NAME_PROPERTY)
- .setCardinality(
- AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
- .build())
- .addProperty(
- new AppSearchSchema.BytesPropertyConfig.Builder(SHA_256_CERT_PROPERTY)
- .setCardinality(
- AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
- .build())
- .addProperty(
- new AppSearchSchema.StringPropertyConfig.Builder(
- ACCESSIBLE_SCHEMA_PROPERTY)
- .setCardinality(
- AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
- .build())
- .build();
-
- /**
* These cannot have any of the special characters used by AppSearchImpl (e.g. {@code
* AppSearchImpl#PACKAGE_DELIMITER} or {@code AppSearchImpl#DATABASE_DELIMITER}.
*/
@@ -177,24 +117,12 @@
// platform-surfaceable content.
private int mGlobalQuerierUid;
- /**
- * Maps prefixes to the set of schemas that are platform-hidden within that prefix. All schemas
- * in the map are prefixed.
- *
- * <p>Although the prefix key isn't used for lookup, it's helpful in ensuring that all previous
- * visibility settings for a prefix are completely overridden by new visibility settings.
- */
- private final Map<String, Set<String>> mNotPlatformSurfaceableMap = new ArrayMap<>();
+ /** Stores the schemas that are platform-hidden. All values are prefixed. */
+ private final NotPlatformSurfaceableMap mNotPlatformSurfaceableMap =
+ new NotPlatformSurfaceableMap();
- /**
- * Maps prefixes to a an internal map. The internal map maps prefixed schemas to the set of
- * PackageIdentifiers that have access to that schema.
- *
- * <p>Although the prefix key isn't used for lookup, it's helpful in ensuring that all previous
- * visibility settings for a prefix are completely overridden by new visibility settings.
- */
- private final Map<String, Map<String, Set<PackageIdentifier>>> mPackageAccessibleMap =
- new ArrayMap<>();
+ /** Stores the schemas that are package accessible. All values are prefixed. */
+ private final PackageAccessibleMap mPackageAccessibleMap = new PackageAccessibleMap();
/**
* Creates an uninitialized VisibilityStore object. Callers must also call {@link #initialize()}
@@ -228,9 +156,9 @@
boolean hasVisibilityType = false;
boolean hasPackageAccessibleType = false;
for (AppSearchSchema schema : getSchemaResponse.getSchemas()) {
- if (schema.getSchemaType().equals(VISIBILITY_TYPE)) {
+ if (schema.getSchemaType().equals(VisibilityDocument.SCHEMA_TYPE)) {
hasVisibilityType = true;
- } else if (schema.getSchemaType().equals(PACKAGE_ACCESSIBLE_TYPE)) {
+ } else if (schema.getSchemaType().equals(PackageAccessibleDocument.SCHEMA_TYPE)) {
hasPackageAccessibleType = true;
}
@@ -244,7 +172,7 @@
mAppSearchImpl.setSchema(
PACKAGE_NAME,
DATABASE_NAME,
- Arrays.asList(VISIBILITY_SCHEMA, PACKAGE_ACCESSIBLE_SCHEMA),
+ Arrays.asList(VisibilityDocument.SCHEMA, PackageAccessibleDocument.SCHEMA),
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
@@ -261,40 +189,34 @@
try {
// Note: We use the other clients' prefixed names as ids
- GenericDocument document =
+ VisibilityDocument visibilityDocument = new VisibilityDocument(
mAppSearchImpl.getDocument(
PACKAGE_NAME,
DATABASE_NAME,
NAMESPACE,
/*id=*/ addIdPrefix(prefix),
- /*typePropertyPaths=*/ Collections.emptyMap());
+ /*typePropertyPaths=*/ Collections.emptyMap()));
// Update platform visibility settings
String[] notPlatformSurfaceableSchemas =
- document.getPropertyStringArray(NOT_PLATFORM_SURFACEABLE_PROPERTY);
+ visibilityDocument.getNotPlatformSurfaceableSchemas();
if (notPlatformSurfaceableSchemas != null) {
- mNotPlatformSurfaceableMap.put(
- prefix, new ArraySet<>(Arrays.asList(notPlatformSurfaceableSchemas)));
+ mNotPlatformSurfaceableMap.setNotPlatformSurfaceable(
+ prefix,
+ new ArraySet<>(notPlatformSurfaceableSchemas));
}
// Update 3p package visibility settings
Map<String, Set<PackageIdentifier>> schemaToPackageIdentifierMap = new ArrayMap<>();
GenericDocument[] packageAccessibleDocuments =
- document.getPropertyDocumentArray(PACKAGE_ACCESSIBLE_PROPERTY);
+ visibilityDocument.getPackageAccessibleSchemas();
if (packageAccessibleDocuments != null) {
for (int i = 0; i < packageAccessibleDocuments.length; i++) {
- String packageName =
- packageAccessibleDocuments[i].getPropertyString(
- PACKAGE_NAME_PROPERTY);
- byte[] sha256Cert =
- packageAccessibleDocuments[i].getPropertyBytes(
- SHA_256_CERT_PROPERTY);
+ PackageAccessibleDocument packageAccessibleDocument =
+ new PackageAccessibleDocument(packageAccessibleDocuments[i]);
PackageIdentifier packageIdentifier =
- new PackageIdentifier(packageName, sha256Cert);
-
- String prefixedSchema =
- packageAccessibleDocuments[i].getPropertyString(
- ACCESSIBLE_SCHEMA_PROPERTY);
+ packageAccessibleDocument.getPackageIdentifier();
+ String prefixedSchema = packageAccessibleDocument.getAccessibleSchemaType();
Set<PackageIdentifier> packageIdentifiers =
schemaToPackageIdentifierMap.get(prefixedSchema);
if (packageIdentifiers == null) {
@@ -304,7 +226,7 @@
schemaToPackageIdentifierMap.put(prefixedSchema, packageIdentifiers);
}
}
- mPackageAccessibleMap.put(prefix, schemaToPackageIdentifierMap);
+ mPackageAccessibleMap.setPackageAccessible(prefix, schemaToPackageIdentifierMap);
} catch (AppSearchException e) {
if (e.getResultCode() == AppSearchResult.RESULT_NOT_FOUND) {
// TODO(b/172068212): This indicates some desync error. We were expecting a
@@ -339,38 +261,30 @@
Objects.requireNonNull(schemasPackageAccessible);
// Persist the document
- GenericDocument.Builder<?> visibilityDocument =
- new GenericDocument.Builder<>(
- NAMESPACE, /*id=*/ addIdPrefix(prefix), VISIBILITY_TYPE);
+ VisibilityDocument.Builder visibilityDocument =
+ new VisibilityDocument.Builder(NAMESPACE, /*id=*/ addIdPrefix(prefix));
if (!schemasNotPlatformSurfaceable.isEmpty()) {
- visibilityDocument.setPropertyString(
- NOT_PLATFORM_SURFACEABLE_PROPERTY,
+ visibilityDocument.setSchemasNotPlatformSurfaceable(
schemasNotPlatformSurfaceable.toArray(new String[0]));
}
Map<String, Set<PackageIdentifier>> schemaToPackageIdentifierMap = new ArrayMap<>();
- List<GenericDocument> packageAccessibleDocuments = new ArrayList<>();
+ List<PackageAccessibleDocument> packageAccessibleDocuments = new ArrayList<>();
for (Map.Entry<String, List<PackageIdentifier>> entry :
schemasPackageAccessible.entrySet()) {
for (int i = 0; i < entry.getValue().size(); i++) {
- GenericDocument packageAccessibleDocument = new GenericDocument.Builder<>(
- NAMESPACE, /*id=*/ "", PACKAGE_ACCESSIBLE_TYPE)
- .setPropertyString(
- PACKAGE_NAME_PROPERTY,
- entry.getValue().get(i).getPackageName())
- .setPropertyBytes(
- SHA_256_CERT_PROPERTY,
- entry.getValue().get(i).getSha256Certificate())
- .setPropertyString(ACCESSIBLE_SCHEMA_PROPERTY, entry.getKey())
- .build();
+ PackageAccessibleDocument packageAccessibleDocument =
+ new PackageAccessibleDocument.Builder(NAMESPACE, /*id=*/ "")
+ .setAccessibleSchemaType(entry.getKey())
+ .setPackageIdentifier(entry.getValue().get(i))
+ .build();
packageAccessibleDocuments.add(packageAccessibleDocument);
}
schemaToPackageIdentifierMap.put(entry.getKey(), new ArraySet<>(entry.getValue()));
}
if (!packageAccessibleDocuments.isEmpty()) {
- visibilityDocument.setPropertyDocument(
- PACKAGE_ACCESSIBLE_PROPERTY,
- packageAccessibleDocuments.toArray(new GenericDocument[0]));
+ visibilityDocument.setPackageAccessibleSchemas(
+ packageAccessibleDocuments.toArray(new PackageAccessibleDocument[0]));
}
mAppSearchImpl.putDocument(
@@ -379,8 +293,8 @@
mAppSearchImpl.persistToDisk(PersistType.Code.LITE);
// Update derived data structures.
- mNotPlatformSurfaceableMap.put(prefix, schemasNotPlatformSurfaceable);
- mPackageAccessibleMap.put(prefix, schemaToPackageIdentifierMap);
+ mNotPlatformSurfaceableMap.setNotPlatformSurfaceable(prefix, schemasNotPlatformSurfaceable);
+ mPackageAccessibleMap.setPackageAccessible(prefix, schemaToPackageIdentifierMap);
}
/** Checks whether {@code prefixedSchema} can be searched over by the {@code callerUid}. */
@@ -389,10 +303,14 @@
Objects.requireNonNull(prefix);
Objects.requireNonNull(prefixedSchema);
+ if (prefix.equals(VISIBILITY_STORE_PREFIX)) {
+ return false; // VisibilityStore schemas are for internal bookkeeping.
+ }
+
// We compare appIds here rather than direct uids because the package's uid may change based
// on the user that's running.
if (UserHandle.isSameApp(mGlobalQuerierUid, callerUid)
- && isSchemaPlatformSurfaceable(prefix, prefixedSchema)) {
+ && mNotPlatformSurfaceableMap.isSchemaPlatformSurfaceable(prefix, prefixedSchema)) {
return true;
}
@@ -401,29 +319,6 @@
}
/**
- * Returns whether the caller has platform query privileges, and if so, that the schema is
- * surfaceable on the platform.
- */
- private boolean isSchemaPlatformSurfaceable(
- @NonNull String prefix, @NonNull String prefixedSchema) {
- if (prefix.equals(VISIBILITY_STORE_PREFIX)) {
- // VisibilityStore schemas are for internal bookkeeping.
- return false;
- }
-
- Set<String> notPlatformSurfaceableSchemas = mNotPlatformSurfaceableMap.get(prefix);
- if (notPlatformSurfaceableSchemas == null) {
- // No schemas were opted out of being platform-surfaced. So by default, it can be
- // surfaced.
- return true;
- }
-
- // Some schemas were opted out of being platform-surfaced. As long as this schema
- // isn't one of those opt-outs, it's surfaceable.
- return !notPlatformSurfaceableSchemas.contains(prefixedSchema);
- }
-
- /**
* Returns whether the schema is accessible by the {@code callerUid}. Checks that the callerUid
* has one of the allowed PackageIdentifier's package. And if so, that the package also has the
* matching certificate.
@@ -434,20 +329,8 @@
*/
private boolean isSchemaPackageAccessible(
@NonNull String prefix, @NonNull String prefixedSchema, int callerUid) {
- Map<String, Set<PackageIdentifier>> schemaToPackageIdentifierMap =
- mPackageAccessibleMap.get(prefix);
- if (schemaToPackageIdentifierMap == null) {
- // No schemas under this prefix have granted package access, return early.
- return false;
- }
-
Set<PackageIdentifier> packageIdentifiers =
- schemaToPackageIdentifierMap.get(prefixedSchema);
- if (packageIdentifiers == null) {
- // No package identifiers were granted access for this schema, return early.
- return false;
- }
-
+ mPackageAccessibleMap.getAccessiblePackages(prefix, prefixedSchema);
for (PackageIdentifier packageIdentifier : packageIdentifiers) {
// Check that the caller uid matches this allowlisted PackageIdentifier.
// TODO(b/169883602): Consider caching the UIDs of packages. Looking this up in the
@@ -467,7 +350,6 @@
return true;
}
}
-
// If we can't verify the schema is package accessible, default to no access.
return false;
}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/visibilitystore/NotPlatformSurfaceableMap.java b/apex/appsearch/service/java/com/android/server/appsearch/visibilitystore/NotPlatformSurfaceableMap.java
new file mode 100644
index 0000000..5afdda2
--- /dev/null
+++ b/apex/appsearch/service/java/com/android/server/appsearch/visibilitystore/NotPlatformSurfaceableMap.java
@@ -0,0 +1,65 @@
+/*
+ * 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.util.ArrayMap;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Stores information about what types are hidden from platform surfaces through the
+ * {@link android.app.appsearch.SetSchemaRequest.Builder#setSchemaTypeDisplayedBySystem} API.
+ *
+ * This object is not thread safe.
+ * @hide
+ */
+public class NotPlatformSurfaceableMap {
+ /**
+ * Maps prefixes to the set of prefixed schemas that are platform-hidden within that prefix.
+ */
+ private final Map<String, Set<String>> mMap = new ArrayMap<>();
+
+ /**
+ * Sets the prefixed schemas that are opted out of platform surfacing for the prefix.
+ *
+ * <p>Any existing mappings for this prefix are overwritten.
+ */
+ public void setNotPlatformSurfaceable(@NonNull String prefix, @NonNull Set<String> schemas) {
+ mMap.put(prefix, schemas);
+ }
+
+ /**
+ * Returns whether the given prefixed schema is platform surfaceable (has not opted out) in the
+ * given prefix.
+ */
+ public boolean isSchemaPlatformSurfaceable(@NonNull String prefix, @NonNull String schemaType) {
+ Set<String> schemaTypes = mMap.get(prefix);
+ if (schemaTypes == null) {
+ // No opt-outs for this prefix
+ return true;
+ }
+ // Some schemas were opted out of being platform-surfaced. As long as this schema
+ // isn't one of those opt-outs, it's surfaceable.
+ return !schemaTypes.contains(schemaType);
+ }
+
+ /** Discards all data in the map. */
+ public void clear() {
+ mMap.clear();
+ }
+}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/visibilitystore/PackageAccessibleDocument.java b/apex/appsearch/service/java/com/android/server/appsearch/visibilitystore/PackageAccessibleDocument.java
new file mode 100644
index 0000000..5601ef9
--- /dev/null
+++ b/apex/appsearch/service/java/com/android/server/appsearch/visibilitystore/PackageAccessibleDocument.java
@@ -0,0 +1,107 @@
+/*
+ * 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.AppSearchSchema;
+import android.app.appsearch.GenericDocument;
+import android.app.appsearch.PackageIdentifier;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Holds configuration about a package+cert that can access a schema.
+ *
+ * @see android.app.appsearch.SetSchemaRequest.Builder#setSchemaTypeVisibilityForPackage
+ * @hide
+ */
+public class PackageAccessibleDocument extends GenericDocument {
+ /** Schema type for nested documents that hold package accessible information. */
+ public static final String SCHEMA_TYPE = "PackageAccessibleType";
+
+ /** Property that holds the package name that can access a schema. */
+ private static final String PACKAGE_NAME_PROPERTY = "packageName";
+
+ /** Property that holds the SHA 256 certificate of the app that can access a schema. */
+ private static final String SHA_256_CERT_PROPERTY = "sha256Cert";
+
+ /** Property that holds the prefixed schema type that is accessible by some package. */
+ private static final String ACCESSIBLE_SCHEMA_PROPERTY = "accessibleSchema";
+
+ /**
+ * Schema for package accessible documents, these will be nested in a top-level
+ * {@link VisibilityDocument}.
+ *
+ * <p>NOTE: If you update this, also update
+ * {@link com.android.server.appsearch.external.localstorage.VisibilityStore#SCHEMA_VERSION}
+ */
+ public static final AppSearchSchema SCHEMA = new AppSearchSchema.Builder(SCHEMA_TYPE)
+ .addProperty(new AppSearchSchema.StringPropertyConfig.Builder(PACKAGE_NAME_PROPERTY)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .build())
+ .addProperty(new AppSearchSchema.BytesPropertyConfig.Builder(SHA_256_CERT_PROPERTY)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .build())
+ .addProperty(new AppSearchSchema.StringPropertyConfig.Builder(
+ ACCESSIBLE_SCHEMA_PROPERTY)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .build())
+ .build();
+
+ public PackageAccessibleDocument(@NonNull GenericDocument genericDocument) {
+ super(genericDocument);
+ }
+
+ @Nullable
+ public String getAccessibleSchemaType() {
+ return getPropertyString(ACCESSIBLE_SCHEMA_PROPERTY);
+ }
+
+ /** Gets which package is able to access {@link #getAccessibleSchemaType} */
+ @NonNull
+ public PackageIdentifier getPackageIdentifier() {
+ String packageName = getPropertyString(PACKAGE_NAME_PROPERTY);
+ byte[] sha256Cert = getPropertyBytes(SHA_256_CERT_PROPERTY);
+ return new PackageIdentifier(packageName, sha256Cert);
+ }
+
+ /** Builder for {@link PackageAccessibleDocument} instances. */
+ public static class Builder extends GenericDocument.Builder<PackageAccessibleDocument.Builder> {
+ public Builder(@NonNull String namespace, @NonNull String id) {
+ super(namespace, id, SCHEMA_TYPE);
+ }
+
+ /** Sets which prefixed schema type is accessible by the package */
+ @NonNull
+ public Builder setAccessibleSchemaType(@NonNull String schemaType) {
+ return setPropertyString(ACCESSIBLE_SCHEMA_PROPERTY, schemaType);
+ }
+
+ /** Sets which package is able to access the {@link #setAccessibleSchemaType}. */
+ @NonNull
+ public Builder setPackageIdentifier(@NonNull PackageIdentifier packageIdentifier) {
+ return setPropertyString(PACKAGE_NAME_PROPERTY, packageIdentifier.getPackageName())
+ .setPropertyBytes(SHA_256_CERT_PROPERTY,
+ packageIdentifier.getSha256Certificate());
+ }
+
+ @Override
+ @NonNull
+ public PackageAccessibleDocument build() {
+ return new PackageAccessibleDocument(super.build());
+ }
+ }
+}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/visibilitystore/PackageAccessibleMap.java b/apex/appsearch/service/java/com/android/server/appsearch/visibilitystore/PackageAccessibleMap.java
new file mode 100644
index 0000000..e90e8bf
--- /dev/null
+++ b/apex/appsearch/service/java/com/android/server/appsearch/visibilitystore/PackageAccessibleMap.java
@@ -0,0 +1,74 @@
+/*
+ * 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.PackageIdentifier;
+import android.util.ArrayMap;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Stores information about what types are accessible to which packages through the
+ * {@link android.app.appsearch.SetSchemaRequest.Builder#setSchemaTypeVisibilityForPackage} API.
+ *
+ * This object is not thread safe.
+ * @hide
+ */
+public class PackageAccessibleMap {
+ /**
+ * Maps prefixes to prefixed schema types to PackageIdentifiers that have access to that schema.
+ */
+ private final Map<String, Map<String, Set<PackageIdentifier>>> mMap = new ArrayMap<>();
+
+ /**
+ * Sets the prefixed schemas that have package visibility in the given prefix.
+ *
+ * <p>Any existing mappings for this prefix are overwritten.
+ */
+ public void setPackageAccessible(
+ @NonNull String prefix,
+ @NonNull Map<String, Set<PackageIdentifier>> schemaToPackageIdentifier) {
+ mMap.put(prefix, schemaToPackageIdentifier);
+ }
+
+ /**
+ * Returns the set of all {@link android.app.appsearch.PackageIdentifier}s which can access the
+ * given schema type.
+ *
+ * <p>If no such settings exist, returns the empty set.
+ */
+ @NonNull
+ public Set<PackageIdentifier> getAccessiblePackages(
+ @NonNull String prefix, @NonNull String schemaType) {
+ Map<String, Set<PackageIdentifier>> schemaTypeToVisibility = mMap.get(prefix);
+ if (schemaTypeToVisibility == null) {
+ return Collections.emptySet();
+ }
+ Set<PackageIdentifier> accessiblePackages = schemaTypeToVisibility.get(schemaType);
+ if (accessiblePackages == null) {
+ return Collections.emptySet();
+ }
+ return accessiblePackages;
+ }
+
+ /** Discards all data in the map. */
+ public void clear() {
+ mMap.clear();
+ }
+}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/visibilitystore/VisibilityDocument.java b/apex/appsearch/service/java/com/android/server/appsearch/visibilitystore/VisibilityDocument.java
new file mode 100644
index 0000000..327ce85
--- /dev/null
+++ b/apex/appsearch/service/java/com/android/server/appsearch/visibilitystore/VisibilityDocument.java
@@ -0,0 +1,92 @@
+/*
+ * 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.AppSearchSchema;
+import android.app.appsearch.GenericDocument;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Holds the visibility settings that apply to a package's databases.
+ * @hide
+ */
+public class VisibilityDocument extends GenericDocument {
+ /** Schema type for documents that hold AppSearch's metadata, e.g. visibility settings */
+ public static final String SCHEMA_TYPE = "VisibilityType";
+
+ /**
+ * Property that holds the list of platform-hidden schemas, as part of the visibility settings.
+ */
+ private static final String NOT_PLATFORM_SURFACEABLE_PROPERTY = "notPlatformSurfaceable";
+
+ /** Property that holds nested documents of package accessible schemas. */
+ private static final String PACKAGE_ACCESSIBLE_PROPERTY = "packageAccessible";
+
+ /**
+ * Schema for the VisibilityStore's documents.
+ *
+ * <p>NOTE: If you update this, also update
+ * {@link com.android.server.appsearch.external.localstorage.VisibilityStore#SCHEMA_VERSION}
+ */
+ public static final AppSearchSchema SCHEMA = new AppSearchSchema.Builder(SCHEMA_TYPE)
+ .addProperty(new AppSearchSchema.StringPropertyConfig.Builder(
+ NOT_PLATFORM_SURFACEABLE_PROPERTY)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
+ .build())
+ .addProperty(new AppSearchSchema.DocumentPropertyConfig.Builder(
+ PACKAGE_ACCESSIBLE_PROPERTY, PackageAccessibleDocument.SCHEMA_TYPE)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
+ .build())
+ .build();
+
+ public VisibilityDocument(@NonNull GenericDocument genericDocument) {
+ super(genericDocument);
+ }
+
+ @Nullable
+ public String[] getNotPlatformSurfaceableSchemas() {
+ return getPropertyStringArray(NOT_PLATFORM_SURFACEABLE_PROPERTY);
+ }
+
+ @Nullable
+ public GenericDocument[] getPackageAccessibleSchemas() {
+ return getPropertyDocumentArray(PACKAGE_ACCESSIBLE_PROPERTY);
+ }
+
+ /** Builder for {@link VisibilityDocument}. */
+ public static class Builder extends GenericDocument.Builder<VisibilityDocument.Builder> {
+ public Builder(@NonNull String namespace, @NonNull String id) {
+ super(namespace, id, SCHEMA_TYPE);
+ }
+
+ /** Sets which prefixed schemas have opted out of platform surfacing. */
+ @NonNull
+ public Builder setSchemasNotPlatformSurfaceable(
+ @NonNull String[] notPlatformSurfaceableSchemas) {
+ return setPropertyString(
+ NOT_PLATFORM_SURFACEABLE_PROPERTY, notPlatformSurfaceableSchemas);
+ }
+
+ /** Sets which prefixed schemas have configured package access. */
+ @NonNull
+ public Builder setPackageAccessibleSchemas(
+ @NonNull PackageAccessibleDocument[] packageAccessibleSchemas) {
+ return setPropertyDocument(PACKAGE_ACCESSIBLE_PROPERTY, packageAccessibleSchemas);
+ }
+ }
+}