Merge "Require a package name in AppSearchImpl." into androidx-master-dev
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/cts/AppSearchSessionCtsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/cts/AppSearchSessionCtsTest.java
index 925cff2..1caa06f 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/cts/AppSearchSessionCtsTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/cts/AppSearchSessionCtsTest.java
@@ -289,7 +289,8 @@
         assertThat(failResult1.isSuccess()).isFalse();
         assertThat(failResult1.getErrorMessage()).contains("Schema is incompatible");
         assertThat(failResult1.getErrorMessage())
-                .contains("Deleted types: [" + mDbName1 + "/builtin:Email]");
+                .contains(
+                        "Deleted types: [androidx.appsearch.test$" + mDbName1 + "/builtin:Email]");
 
         // Try to remove the email schema again, which should now work as we set forceOverride to
         // be true.
@@ -315,7 +316,8 @@
                 new PutDocumentsRequest.Builder().addGenericDocument(email2).build()).get();
         assertThat(failResult2.isSuccess()).isFalse();
         assertThat(failResult2.getFailures().get("email2").getErrorMessage())
-                .isEqualTo("Schema type config '" + mDbName1 + "/builtin:Email' not found");
+                .isEqualTo("Schema type config 'androidx.appsearch.test$" + mDbName1
+                        + "/builtin:Email' not found");
     }
 
     @Test
@@ -368,7 +370,8 @@
         assertThat(failResult1.isSuccess()).isFalse();
         assertThat(failResult1.getErrorMessage()).contains("Schema is incompatible");
         assertThat(failResult1.getErrorMessage())
-                .contains("Deleted types: [" + mDbName1 + "/builtin:Email]");
+                .contains(
+                        "Deleted types: [androidx.appsearch.test$" + mDbName1 + "/builtin:Email]");
 
         // Try to remove the email schema again, which should now work as we set forceOverride to
         // be true.
@@ -392,7 +395,8 @@
                 new PutDocumentsRequest.Builder().addGenericDocument(email3).build()).get();
         assertThat(failResult2.isSuccess()).isFalse();
         assertThat(failResult2.getFailures().get("email3").getErrorMessage())
-                .isEqualTo("Schema type config '" + mDbName1 + "/builtin:Email' not found");
+                .isEqualTo("Schema type config 'androidx.appsearch.test$" + mDbName1
+                        + "/builtin:Email' not found");
 
         // Make sure email in database 2 still present.
         outDocuments = doGet(mDb2, GenericDocument.DEFAULT_NAMESPACE, "email2");
diff --git a/appsearch/local-storage/src/androidTest/java/androidx/appsearch/localstorage/AppSearchImplTest.java b/appsearch/local-storage/src/androidTest/java/androidx/appsearch/localstorage/AppSearchImplTest.java
index 101356e..4bdae11 100644
--- a/appsearch/local-storage/src/androidTest/java/androidx/appsearch/localstorage/AppSearchImplTest.java
+++ b/appsearch/local-storage/src/androidTest/java/androidx/appsearch/localstorage/AppSearchImplTest.java
@@ -58,18 +58,19 @@
     public void setUp() throws Exception {
         mAppSearchImpl = AppSearchImpl.create(mTemporaryFolder.newFolder());
 
-        AppSearchSchema visibilityAppSearchSchema =
-                new AppSearchSchema.Builder(
-                        VisibilityStore.DATABASE_NAME + AppSearchImpl.DATABASE_DELIMITER
-                                + VisibilityStore.SCHEMA_TYPE)
-                        .addProperty(new AppSearchSchema.PropertyConfig.Builder(
-                                VisibilityStore.NOT_PLATFORM_SURFACEABLE_PROPERTY)
-                                .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_STRING)
-                                .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
-                                .build())
-                        .build();
+        AppSearchSchema visibilitySchema = VisibilityStore.SCHEMA;
+
+        // We need to rewrite the schema type to follow AppSearchImpl's prefixing scheme.
+        AppSearchSchema.Builder rewrittenVisibilitySchema =
+                new AppSearchSchema.Builder(AppSearchImpl.createPrefix(VisibilityStore.PACKAGE_NAME,
+                        VisibilityStore.DATABASE_NAME) + VisibilityStore.SCHEMA_TYPE);
+        List<AppSearchSchema.PropertyConfig> visibilityProperties =
+                visibilitySchema.getProperties();
+        for (AppSearchSchema.PropertyConfig property : visibilityProperties) {
+            rewrittenVisibilitySchema.addProperty(property);
+        }
         mVisibilitySchemaProto =
-                SchemaToProtoConverter.toSchemaTypeConfigProto(visibilityAppSearchSchema);
+                SchemaToProtoConverter.toSchemaTypeConfigProto(rewrittenVisibilitySchema.build());
     }
 
     /**
@@ -81,7 +82,7 @@
     public void testRewriteSchema_addType() throws Exception {
         SchemaProto.Builder existingSchemaBuilder = SchemaProto.newBuilder()
                 .addTypes(SchemaTypeConfigProto.newBuilder()
-                        .setSchemaType("existingDatabase/Foo").build());
+                        .setSchemaType("package$existingDatabase/Foo").build());
 
         // Create a copy so we can modify it.
         List<SchemaTypeConfigProto> existingTypes =
@@ -112,19 +113,19 @@
                 ).build();
 
         AppSearchImpl.RewrittenSchemaResults rewrittenSchemaResults = mAppSearchImpl.rewriteSchema(
-                "newDatabase", existingSchemaBuilder,
+                AppSearchImpl.createPrefix("package", "newDatabase"), existingSchemaBuilder,
                 newSchema);
 
         // We rewrote all the new types that were added. And nothing was removed.
-        assertThat(rewrittenSchemaResults.mRewrittenQualifiedTypes)
-                .containsExactly("newDatabase/Foo", "newDatabase/TestType");
-        assertThat(rewrittenSchemaResults.mDeletedQualifiedTypes).isEmpty();
+        assertThat(rewrittenSchemaResults.mRewrittenPrefixedTypes)
+                .containsExactly("package$newDatabase/Foo", "package$newDatabase/TestType");
+        assertThat(rewrittenSchemaResults.mDeletedPrefixedTypes).isEmpty();
 
         SchemaProto expectedSchema = SchemaProto.newBuilder()
                 .addTypes(SchemaTypeConfigProto.newBuilder()
-                        .setSchemaType("newDatabase/Foo").build())
+                        .setSchemaType("package$newDatabase/Foo").build())
                 .addTypes(SchemaTypeConfigProto.newBuilder()
-                        .setSchemaType("newDatabase/TestType")
+                        .setSchemaType("package$newDatabase/TestType")
                         .addProperties(PropertyConfigProto.newBuilder()
                                 .setPropertyName("subject")
                                 .setDataType(PropertyConfigProto.DataType.Code.STRING)
@@ -139,7 +140,7 @@
                                 .setPropertyName("link")
                                 .setDataType(PropertyConfigProto.DataType.Code.DOCUMENT)
                                 .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
-                                .setSchemaType("newDatabase/RefType")
+                                .setSchemaType("package$newDatabase/RefType")
                                 .build()
                         ).build())
                 .build();
@@ -156,7 +157,7 @@
     public void testRewriteSchema_rewriteType() throws Exception {
         SchemaProto.Builder existingSchemaBuilder = SchemaProto.newBuilder()
                 .addTypes(SchemaTypeConfigProto.newBuilder()
-                        .setSchemaType("existingDatabase/Foo").build());
+                        .setSchemaType("package$existingDatabase/Foo").build());
 
         SchemaProto newSchema = SchemaProto.newBuilder()
                 .addTypes(SchemaTypeConfigProto.newBuilder()
@@ -164,12 +165,13 @@
                 .build();
 
         AppSearchImpl.RewrittenSchemaResults rewrittenSchemaResults = mAppSearchImpl.rewriteSchema(
-                "existingDatabase", existingSchemaBuilder, newSchema);
+                AppSearchImpl.createPrefix("package", "existingDatabase"), existingSchemaBuilder,
+                newSchema);
 
         // Nothing was removed, but the method did rewrite the type name.
-        assertThat(rewrittenSchemaResults.mRewrittenQualifiedTypes)
-                .containsExactly("existingDatabase/Foo");
-        assertThat(rewrittenSchemaResults.mDeletedQualifiedTypes).isEmpty();
+        assertThat(rewrittenSchemaResults.mRewrittenPrefixedTypes)
+                .containsExactly("package$existingDatabase/Foo");
+        assertThat(rewrittenSchemaResults.mDeletedPrefixedTypes).isEmpty();
 
         // Same schema since nothing was added.
         SchemaProto expectedSchema = existingSchemaBuilder.build();
@@ -185,7 +187,7 @@
     public void testRewriteSchema_deleteType() throws Exception {
         SchemaProto.Builder existingSchemaBuilder = SchemaProto.newBuilder()
                 .addTypes(SchemaTypeConfigProto.newBuilder()
-                        .setSchemaType("existingDatabase/Foo").build());
+                        .setSchemaType("package$existingDatabase/Foo").build());
 
         SchemaProto newSchema = SchemaProto.newBuilder()
                 .addTypes(SchemaTypeConfigProto.newBuilder()
@@ -193,19 +195,20 @@
                 .build();
 
         AppSearchImpl.RewrittenSchemaResults rewrittenSchemaResults = mAppSearchImpl.rewriteSchema(
-                "existingDatabase", existingSchemaBuilder, newSchema);
+                AppSearchImpl.createPrefix("package", "existingDatabase"), existingSchemaBuilder,
+                newSchema);
 
         // Bar type was rewritten, but Foo ended up being deleted since it wasn't included in the
         // new schema.
-        assertThat(rewrittenSchemaResults.mRewrittenQualifiedTypes)
-                .containsExactly("existingDatabase/Bar");
-        assertThat(rewrittenSchemaResults.mDeletedQualifiedTypes)
-                .containsExactly("existingDatabase/Foo");
+        assertThat(rewrittenSchemaResults.mRewrittenPrefixedTypes)
+                .containsExactly("package$existingDatabase/Bar");
+        assertThat(rewrittenSchemaResults.mDeletedPrefixedTypes)
+                .containsExactly("package$existingDatabase/Foo");
 
         // Same schema since nothing was added.
         SchemaProto expectedSchema = SchemaProto.newBuilder()
                 .addTypes(SchemaTypeConfigProto.newBuilder()
-                        .setSchemaType("existingDatabase/Bar").build())
+                        .setSchemaType("package$existingDatabase/Bar").build())
                 .build();
 
         assertThat(existingSchemaBuilder.getTypesList())
@@ -228,18 +231,19 @@
 
         DocumentProto expectedInsideDocument = DocumentProto.newBuilder()
                 .setUri("inside-uri")
-                .setSchema("databaseName/type")
-                .setNamespace("databaseName/namespace")
+                .setSchema("package$databaseName/type")
+                .setNamespace("package$databaseName/namespace")
                 .build();
         DocumentProto expectedDocumentProto = DocumentProto.newBuilder()
                 .setUri("uri")
-                .setSchema("databaseName/type")
-                .setNamespace("databaseName/namespace")
+                .setSchema("package$databaseName/type")
+                .setNamespace("package$databaseName/namespace")
                 .addProperties(PropertyProto.newBuilder().addDocumentValues(expectedInsideDocument))
                 .build();
 
         DocumentProto.Builder actualDocument = documentProto.toBuilder();
-        mAppSearchImpl.addPrefixToDocument(actualDocument, "databaseName/");
+        mAppSearchImpl.addPrefixToDocument(actualDocument, AppSearchImpl.createPrefix("package",
+                "databaseName"));
         assertThat(actualDocument.build()).isEqualTo(expectedDocumentProto);
     }
 
@@ -247,13 +251,13 @@
     public void testRemoveDocumentTypePrefixes() throws Exception {
         DocumentProto insideDocument = DocumentProto.newBuilder()
                 .setUri("inside-uri")
-                .setSchema("databaseName1/type")
-                .setNamespace("databaseName2/namespace")
+                .setSchema("package$databaseName1/type")
+                .setNamespace("package$databaseName2/namespace")
                 .build();
         DocumentProto documentProto = DocumentProto.newBuilder()
                 .setUri("uri")
-                .setSchema("databaseName2/type")
-                .setNamespace("databaseName3/namespace")
+                .setSchema("package$databaseName2/type")
+                .setNamespace("package$databaseName3/namespace")
                 .addProperties(PropertyProto.newBuilder().addDocumentValues(insideDocument))
                 .build();
 
@@ -262,7 +266,7 @@
                 .setSchema("type")
                 .setNamespace("namespace")
                 .build();
-        // Since we don't pass in "databaseName3/" as a prefix to remove, it stays on the Document.
+
         DocumentProto expectedDocumentProto = DocumentProto.newBuilder()
                 .setUri("uri")
                 .setSchema("type")
@@ -271,7 +275,7 @@
                 .build();
 
         DocumentProto.Builder actualDocument = documentProto.toBuilder();
-        mAppSearchImpl.removeDatabasesFromDocument(actualDocument);
+        mAppSearchImpl.removePrefixesFromDocument(actualDocument);
         assertThat(actualDocument.build()).isEqualTo(expectedDocumentProto);
     }
 
@@ -280,7 +284,7 @@
         // Insert schema
         List<AppSearchSchema> schemas =
                 Collections.singletonList(new AppSearchSchema.Builder("type").build());
-        mAppSearchImpl.setSchema("database", schemas, /*schemasNotPlatformSurfaceable=*/
+        mAppSearchImpl.setSchema("package", "database", schemas, /*schemasNotPlatformSurfaceable=*/
                 Collections.emptyList(), /*forceOverride=*/ false);
 
         // Insert enough documents.
@@ -289,7 +293,7 @@
             GenericDocument document =
                     new GenericDocument.Builder<>("uri" + i, "type").setNamespace(
                             "namespace").build();
-            mAppSearchImpl.putDocument("database", document);
+            mAppSearchImpl.putDocument("package", "database", document);
         }
 
         // Check optimize() will release 0 docs since there is no deletion.
@@ -299,7 +303,7 @@
         // delete 999 documents , we will reach the threshold to trigger optimize() in next
         // deletion.
         for (int i = 0; i < AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT - 1; i++) {
-            mAppSearchImpl.remove("database", "namespace", "uri" + i);
+            mAppSearchImpl.remove("package", "database", "namespace", "uri" + i);
         }
 
         // optimize() still not be triggered since we are in the interval to call getOptimizeInfo()
@@ -311,7 +315,7 @@
         for (int i = AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT;
                 i < AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT
                         + AppSearchImpl.CHECK_OPTIMIZE_INTERVAL; i++) {
-            mAppSearchImpl.remove("database", "namespace", "uri" + i);
+            mAppSearchImpl.remove("package", "database", "namespace", "uri" + i);
         }
 
         // Verify optimize() is triggered
@@ -328,19 +332,21 @@
         // Insert schema
         List<AppSearchSchema> schemas =
                 Collections.singletonList(new AppSearchSchema.Builder("type").build());
-        mAppSearchImpl.setSchema("database", schemas, /*schemasNotPlatformSurfaceable=*/
+        mAppSearchImpl.setSchema("package", "database", schemas, /*schemasNotPlatformSurfaceable=*/
                 Collections.emptyList(), /*forceOverride=*/ false);
 
         // Insert document
         GenericDocument document = new GenericDocument.Builder<>("uri", "type").setNamespace(
                 "namespace").build();
-        mAppSearchImpl.putDocument("database", document);
+        mAppSearchImpl.putDocument("package", "database", document);
 
         // Rewrite SearchSpec
-        mAppSearchImpl.rewriteSearchSpecForDatabasesLocked(
-                searchSpecProto, Collections.singleton("database"));
-        assertThat(searchSpecProto.getSchemaTypeFiltersList()).containsExactly("database/type");
-        assertThat(searchSpecProto.getNamespaceFiltersList()).containsExactly("database/namespace");
+        mAppSearchImpl.rewriteSearchSpecForPrefixesLocked(searchSpecProto,
+                Collections.singleton(AppSearchImpl.createPrefix("package", "database")));
+        assertThat(searchSpecProto.getSchemaTypeFiltersList()).containsExactly(
+                "package$database/type");
+        assertThat(searchSpecProto.getNamespaceFiltersList()).containsExactly(
+                "package$database/namespace");
     }
 
     @Test
@@ -352,36 +358,37 @@
         List<AppSearchSchema> schemas = ImmutableList.of(
                 new AppSearchSchema.Builder("typeA").build(),
                 new AppSearchSchema.Builder("typeB").build());
-        mAppSearchImpl.setSchema("database1", schemas, /*schemasNotPlatformSurfaceable=*/
+        mAppSearchImpl.setSchema("package", "database1", schemas, /*schemasNotPlatformSurfaceable=*/
                 Collections.emptyList(), /*forceOverride=*/ false);
-        mAppSearchImpl.setSchema("database2", schemas, /*schemasNotPlatformSurfaceable=*/
+        mAppSearchImpl.setSchema("package", "database2", schemas, /*schemasNotPlatformSurfaceable=*/
                 Collections.emptyList(), /*forceOverride=*/ false);
 
         // Insert documents
         GenericDocument document1 = new GenericDocument.Builder<>("uri", "typeA").setNamespace(
                 "namespace").build();
-        mAppSearchImpl.putDocument("database1", document1);
+        mAppSearchImpl.putDocument("package", "database1", document1);
 
         GenericDocument document2 = new GenericDocument.Builder<>("uri", "typeB").setNamespace(
                 "namespace").build();
-        mAppSearchImpl.putDocument("database2", document2);
+        mAppSearchImpl.putDocument("package", "database2", document2);
 
         // Rewrite SearchSpec
-        mAppSearchImpl.rewriteSearchSpecForDatabasesLocked(searchSpecProto,
-                ImmutableSet.of("database1", "database2"));
+        mAppSearchImpl.rewriteSearchSpecForPrefixesLocked(searchSpecProto,
+                ImmutableSet.of(AppSearchImpl.createPrefix("package", "database1"),
+                        AppSearchImpl.createPrefix("package", "database2")));
         assertThat(searchSpecProto.getSchemaTypeFiltersList()).containsExactly(
-                "database1/typeA", "database1/typeB", "database2/typeA", "database2/typeB");
+                "package$database1/typeA", "package$database1/typeB", "package$database2/typeA",
+                "package$database2/typeB");
         assertThat(searchSpecProto.getNamespaceFiltersList()).containsExactly(
-                "database1/namespace", "database2/namespace");
+                "package$database1/namespace", "package$database2/namespace");
     }
 
     @Test
     public void testQueryEmptyDatabase() throws Exception {
         SearchSpec searchSpec =
                 new SearchSpec.Builder().setTermMatch(TermMatchType.Code.PREFIX_VALUE).build();
-        SearchResultPage searchResultPage = mAppSearchImpl.query(
-                "EmptyDatabase",
-                "", searchSpec);
+        SearchResultPage searchResultPage = mAppSearchImpl.query("package", "EmptyDatabase", "",
+                searchSpec);
         assertThat(searchResultPage.getResults()).isEmpty();
     }
 
@@ -389,9 +396,7 @@
     public void testGlobalQueryEmptyDatabase() throws Exception {
         SearchSpec searchSpec =
                 new SearchSpec.Builder().setTermMatch(TermMatchType.Code.PREFIX_VALUE).build();
-        SearchResultPage searchResultPage = mAppSearchImpl.query(
-                "EmptyDatabase",
-                "", searchSpec);
+        SearchResultPage searchResultPage = mAppSearchImpl.globalQuery("", searchSpec);
         assertThat(searchResultPage.getResults()).isEmpty();
     }
 
@@ -400,17 +405,17 @@
         SearchSpec searchSpec =
                 new SearchSpec.Builder().addSchemaType("FakeType").setTermMatch(
                         TermMatchType.Code.PREFIX_VALUE).build();
-        mAppSearchImpl.removeByQuery("EmptyDatabase",
+        mAppSearchImpl.removeByQuery("package", "EmptyDatabase",
                 "", searchSpec);
 
         searchSpec =
                 new SearchSpec.Builder().addNamespace("FakeNamespace").setTermMatch(
                         TermMatchType.Code.PREFIX_VALUE).build();
-        mAppSearchImpl.removeByQuery("EmptyDatabase",
+        mAppSearchImpl.removeByQuery("package", "EmptyDatabase",
                 "", searchSpec);
 
         searchSpec = new SearchSpec.Builder().setTermMatch(TermMatchType.Code.PREFIX_VALUE).build();
-        mAppSearchImpl.removeByQuery("EmptyDatabase", "", searchSpec);
+        mAppSearchImpl.removeByQuery("package", "EmptyDatabase", "", searchSpec);
     }
 
     @Test
@@ -418,12 +423,13 @@
         List<AppSearchSchema> schemas =
                 Collections.singletonList(new AppSearchSchema.Builder("Email").build());
         // Set schema Email to AppSearch database1
-        mAppSearchImpl.setSchema("database1", schemas, /*schemasNotPlatformSurfaceable=*/
+        mAppSearchImpl.setSchema("package", "database1", schemas, /*schemasNotPlatformSurfaceable=*/
                 Collections.emptyList(), /*forceOverride=*/ false);
 
         // Create expected schemaType proto.
         SchemaProto expectedProto = SchemaProto.newBuilder()
-                .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Email"))
+                .addTypes(
+                        SchemaTypeConfigProto.newBuilder().setSchemaType("package$database1/Email"))
                 .build();
 
         List<SchemaTypeConfigProto> expectedTypes = new ArrayList<>();
@@ -435,17 +441,19 @@
 
     @Test
     public void testSetSchema_existingSchemaRetainsVisibilitySetting() throws Exception {
-        mAppSearchImpl.setSchema("database", Collections.singletonList(new AppSearchSchema.Builder(
+        String prefix = AppSearchImpl.createPrefix("package", "database");
+        mAppSearchImpl.setSchema("package", "database",
+                Collections.singletonList(new AppSearchSchema.Builder(
                         "schema1").build()), /*schemasNotPlatformSurfaceable=*/
                 Collections.singletonList("schema1"), /*forceOverride=*/ false);
 
         // "schema1" is platform hidden now
-        assertThat(mAppSearchImpl.getVisibilityStoreLocked().getSchemasNotPlatformSurfaceable(
-                "database")).containsExactly("database/schema1");
+        assertThat(mAppSearchImpl.getVisibilityStoreLocked().isSchemaPlatformSurfaceable(
+                prefix, prefix + "schema1")).isFalse();
 
         // Add a new schema, and include the already-existing "schema1"
         mAppSearchImpl.setSchema(
-                "database",
+                "package", "database",
                 ImmutableList.of(
                         new AppSearchSchema.Builder("schema1").build(),
                         new AppSearchSchema.Builder("schema2").build()),
@@ -454,8 +462,10 @@
 
         // Check that "schema1" is still platform hidden, but "schema2" is the default platform
         // visible.
-        assertThat(mAppSearchImpl.getVisibilityStoreLocked().getSchemasNotPlatformSurfaceable(
-                "database")).containsExactly("database/schema1");
+        assertThat(mAppSearchImpl.getVisibilityStoreLocked().isSchemaPlatformSurfaceable(
+                prefix, prefix + "schema1")).isFalse();
+        assertThat(mAppSearchImpl.getVisibilityStoreLocked().isSchemaPlatformSurfaceable(
+                prefix, prefix + "schema2")).isTrue();
     }
 
     @Test
@@ -464,13 +474,15 @@
                 new AppSearchSchema.Builder("Email").build(),
                 new AppSearchSchema.Builder("Document").build());
         // Set schema Email and Document to AppSearch database1
-        mAppSearchImpl.setSchema("database1", schemas, /*schemasNotPlatformSurfaceable=*/
+        mAppSearchImpl.setSchema("package", "database1", schemas, /*schemasNotPlatformSurfaceable=*/
                 Collections.emptyList(), /*forceOverride=*/ false);
 
         // Create expected schemaType proto.
         SchemaProto expectedProto = SchemaProto.newBuilder()
-                .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Email"))
-                .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Document"))
+                .addTypes(
+                        SchemaTypeConfigProto.newBuilder().setSchemaType("package$database1/Email"))
+                .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType(
+                        "package$database1/Document"))
                 .build();
 
         // Check both schema Email and Document saved correctly.
@@ -485,19 +497,21 @@
                         "Email").build());
         // Check the incompatible error has been thrown.
         AppSearchException e = assertThrows(AppSearchException.class, () ->
-                mAppSearchImpl.setSchema("database1",
+                mAppSearchImpl.setSchema("package", "database1",
                         finalSchemas, /*schemasNotPlatformSurfaceable=*/
                         Collections.emptyList(), /*forceOverride=*/ false));
         assertThat(e).hasMessageThat().contains("Schema is incompatible");
-        assertThat(e).hasMessageThat().contains("Deleted types: [database1/Document]");
+        assertThat(e).hasMessageThat().contains("Deleted types: [package$database1/Document]");
 
         // ForceOverride to delete.
-        mAppSearchImpl.setSchema("database1", finalSchemas, /*schemasNotPlatformSurfaceable=*/
+        mAppSearchImpl.setSchema("package", "database1",
+                finalSchemas, /*schemasNotPlatformSurfaceable=*/
                 Collections.emptyList(), /*forceOverride=*/ true);
 
         // Check Document schema is removed.
         expectedProto = SchemaProto.newBuilder()
-                .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Email"))
+                .addTypes(
+                        SchemaTypeConfigProto.newBuilder().setSchemaType("package$database1/Email"))
                 .build();
 
         expectedTypes = new ArrayList<>();
@@ -515,17 +529,21 @@
                 new AppSearchSchema.Builder("Document").build());
 
         // Set schema Email and Document to AppSearch database1 and 2
-        mAppSearchImpl.setSchema("database1", schemas, /*schemasNotPlatformSurfaceable=*/
+        mAppSearchImpl.setSchema("package", "database1", schemas, /*schemasNotPlatformSurfaceable=*/
                 Collections.emptyList(), /*forceOverride=*/ false);
-        mAppSearchImpl.setSchema("database2", schemas, /*schemasNotPlatformSurfaceable=*/
+        mAppSearchImpl.setSchema("package", "database2", schemas, /*schemasNotPlatformSurfaceable=*/
                 Collections.emptyList(), /*forceOverride=*/ false);
 
         // Create expected schemaType proto.
         SchemaProto expectedProto = SchemaProto.newBuilder()
-                .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Email"))
-                .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Document"))
-                .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database2/Email"))
-                .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database2/Document"))
+                .addTypes(
+                        SchemaTypeConfigProto.newBuilder().setSchemaType("package$database1/Email"))
+                .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType(
+                        "package$database1/Document"))
+                .addTypes(
+                        SchemaTypeConfigProto.newBuilder().setSchemaType("package$database2/Email"))
+                .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType(
+                        "package$database2/Document"))
                 .build();
 
         // Check Email and Document is saved in database 1 and 2 correctly.
@@ -537,15 +555,18 @@
 
         // Save only Email to database1 this time.
         schemas = Collections.singletonList(new AppSearchSchema.Builder("Email").build());
-        mAppSearchImpl.setSchema("database1", schemas, /*schemasNotPlatformSurfaceable=*/
+        mAppSearchImpl.setSchema("package", "database1", schemas, /*schemasNotPlatformSurfaceable=*/
                 Collections.emptyList(), /*forceOverride=*/ true);
 
         // Create expected schemaType list, database 1 should only contain Email but database 2
         // remains in same.
         expectedProto = SchemaProto.newBuilder()
-                .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Email"))
-                .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database2/Email"))
-                .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database2/Document"))
+                .addTypes(
+                        SchemaTypeConfigProto.newBuilder().setSchemaType("package$database1/Email"))
+                .addTypes(
+                        SchemaTypeConfigProto.newBuilder().setSchemaType("package$database2/Email"))
+                .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType(
+                        "package$database2/Document"))
                 .build();
 
         // Check nothing changed in database2.
@@ -559,84 +580,101 @@
 
     @Test
     public void testRemoveSchema_removedFromVisibilityStore() throws Exception {
-        mAppSearchImpl.setSchema("database", Collections.singletonList(new AppSearchSchema.Builder(
+        String prefix = AppSearchImpl.createPrefix("package", "database");
+        mAppSearchImpl.setSchema("package", "database",
+                Collections.singletonList(new AppSearchSchema.Builder(
                         "schema1").build()), /*schemasNotPlatformSurfaceable=*/
                 Collections.singletonList("schema1"), /*forceOverride=*/ false);
 
         // "schema1" is platform hidden now
-        assertThat(mAppSearchImpl.getVisibilityStoreLocked().getSchemasNotPlatformSurfaceable(
-                "database")).containsExactly("database/schema1");
+        assertThat(mAppSearchImpl.getVisibilityStoreLocked().isSchemaPlatformSurfaceable(
+                prefix, prefix + "schema1")).isFalse();
 
         // Remove "schema1" by force overriding
-        mAppSearchImpl.setSchema("database",
+        mAppSearchImpl.setSchema("package", "database",
                 Collections.emptyList(), /*schemasNotPlatformSurfaceable=*/
                 Collections.emptyList(), /*forceOverride=*/ true);
 
         // Check that "schema1" is no longer considered platform hidden
         assertThat(
-                mAppSearchImpl.getVisibilityStoreLocked().getSchemasNotPlatformSurfaceable(
-                        "database")).isEmpty();
+                mAppSearchImpl.getVisibilityStoreLocked().isSchemaPlatformSurfaceable(
+                        prefix, prefix + "schema1")).isTrue();
 
         // Add "schema1" back, it gets default visibility settings which means it's not platform
         // hidden.
-        mAppSearchImpl.setSchema("database", Collections.singletonList(new AppSearchSchema.Builder(
+        mAppSearchImpl.setSchema("package", "database",
+                Collections.singletonList(new AppSearchSchema.Builder(
                         "schema1").build()), /*schemasNotPlatformSurfaceable=*/
                 Collections.emptyList(), /*forceOverride=*/ false);
         assertThat(
-                mAppSearchImpl.getVisibilityStoreLocked().getSchemasNotPlatformSurfaceable(
-                        "database")).isEmpty();
+                mAppSearchImpl.getVisibilityStoreLocked().isSchemaPlatformSurfaceable(
+                        prefix, prefix + "schema1")).isTrue();
     }
 
     @Test
     public void testSetSchema_defaultPlatformVisible() throws Exception {
-        mAppSearchImpl.setSchema("database", Collections.singletonList(new AppSearchSchema.Builder(
+        String prefix = AppSearchImpl.createPrefix("package", "database");
+        mAppSearchImpl.setSchema("package", "database",
+                Collections.singletonList(new AppSearchSchema.Builder(
                         "Schema").build()), /*schemasNotPlatformSurfaceable=*/
                 Collections.emptyList(), /*forceOverride=*/ false);
         assertThat(
-                mAppSearchImpl.getVisibilityStoreLocked().getSchemasNotPlatformSurfaceable(
-                        "database")).isEmpty();
+                mAppSearchImpl.getVisibilityStoreLocked().isSchemaPlatformSurfaceable(
+                        prefix, prefix + "Schema")).isTrue();
     }
 
     @Test
     public void testSetSchema_platformHidden() throws Exception {
-        mAppSearchImpl.setSchema("database", Collections.singletonList(new AppSearchSchema.Builder(
+        String prefix = AppSearchImpl.createPrefix("package", "database");
+        mAppSearchImpl.setSchema("package", "database",
+                Collections.singletonList(new AppSearchSchema.Builder(
                         "Schema").build()), /*schemasNotPlatformSurfaceable=*/
                 Collections.singletonList("Schema"), /*forceOverride=*/ false);
-        assertThat(mAppSearchImpl.getVisibilityStoreLocked().getSchemasNotPlatformSurfaceable(
-                "database")).containsExactly("database/Schema");
+        assertThat(mAppSearchImpl.getVisibilityStoreLocked().isSchemaPlatformSurfaceable(
+                prefix, prefix + "Schema")).isFalse();
     }
 
     @Test
     public void testHasSchemaType() throws Exception {
         // Nothing exists yet
-        assertThat(mAppSearchImpl.hasSchemaTypeLocked("database", "Schema")).isFalse();
+        assertThat(mAppSearchImpl.hasSchemaTypeLocked("package", "database", "Schema")).isFalse();
 
-        mAppSearchImpl.setSchema("database", Collections.singletonList(new AppSearchSchema.Builder(
+        mAppSearchImpl.setSchema("package", "database",
+                Collections.singletonList(new AppSearchSchema.Builder(
                         "Schema").build()), /*schemasNotPlatformSurfaceable=*/
                 Collections.emptyList(), /*forceOverride=*/ false);
-        assertThat(mAppSearchImpl.hasSchemaTypeLocked("database", "Schema")).isTrue();
+        assertThat(mAppSearchImpl.hasSchemaTypeLocked("package", "database", "Schema")).isTrue();
 
-        assertThat(mAppSearchImpl.hasSchemaTypeLocked("database", "UnknownSchema")).isFalse();
+        assertThat(mAppSearchImpl.hasSchemaTypeLocked("package", "database",
+                "UnknownSchema")).isFalse();
     }
 
     @Test
     public void testGetDatabases() throws Exception {
         // No client databases exist yet, but the VisibilityStore's does
-        assertThat(mAppSearchImpl.getDatabasesLocked()).containsExactly(
-                VisibilityStore.DATABASE_NAME);
+        assertThat(mAppSearchImpl.getPrefixesLocked()).containsExactly(
+                AppSearchImpl.createPrefix(VisibilityStore.PACKAGE_NAME,
+                        VisibilityStore.DATABASE_NAME));
 
         // Has database1
-        mAppSearchImpl.setSchema("database1", Collections.singletonList(new AppSearchSchema.Builder(
+        mAppSearchImpl.setSchema("package", "database1",
+                Collections.singletonList(new AppSearchSchema.Builder(
                         "schema").build()), /*schemasNotPlatformSurfaceable=*/
                 Collections.emptyList(), /*forceOverride=*/ false);
-        assertThat(mAppSearchImpl.getDatabasesLocked()).containsExactly(
-                VisibilityStore.DATABASE_NAME, "database1");
+        assertThat(mAppSearchImpl.getPrefixesLocked()).containsExactly(
+                AppSearchImpl.createPrefix(VisibilityStore.PACKAGE_NAME,
+                        VisibilityStore.DATABASE_NAME),
+                AppSearchImpl.createPrefix("package", "database1"));
 
         // Has both databases
-        mAppSearchImpl.setSchema("database2", Collections.singletonList(new AppSearchSchema.Builder(
+        mAppSearchImpl.setSchema("package", "database2",
+                Collections.singletonList(new AppSearchSchema.Builder(
                         "schema").build()), /*schemasNotPlatformSurfaceable=*/
                 Collections.emptyList(), /*forceOverride=*/ false);
-        assertThat(mAppSearchImpl.getDatabasesLocked()).containsExactly(
-                VisibilityStore.DATABASE_NAME, "database1", "database2");
+        assertThat(mAppSearchImpl.getPrefixesLocked()).containsExactly(
+                AppSearchImpl.createPrefix(VisibilityStore.PACKAGE_NAME,
+                        VisibilityStore.DATABASE_NAME),
+                AppSearchImpl.createPrefix("package", "database1"), AppSearchImpl.createPrefix(
+                        "package", "database2"));
     }
 }
diff --git a/appsearch/local-storage/src/androidTest/java/androidx/appsearch/localstorage/VisibilityStoreTest.java b/appsearch/local-storage/src/androidTest/java/androidx/appsearch/localstorage/VisibilityStoreTest.java
index f3732a1..4577f5a 100644
--- a/appsearch/local-storage/src/androidTest/java/androidx/appsearch/localstorage/VisibilityStoreTest.java
+++ b/appsearch/local-storage/src/androidTest/java/androidx/appsearch/localstorage/VisibilityStoreTest.java
@@ -40,30 +40,69 @@
         mVisibilityStore = mAppSearchImpl.getVisibilityStoreLocked();
     }
 
+    /**
+     * Make sure that we don't conflict with any special characters that AppSearchImpl has
+     * reserved.
+     */
     @Test
-    public void testSetVisibility() throws Exception {
-        mVisibilityStore.setVisibility("database",
-                /*schemasNotPlatformSurfaceable=*/ ImmutableSet.of("schema1", "schema2"));
-        assertThat(mVisibilityStore.getSchemasNotPlatformSurfaceable("database"))
-                .containsExactlyElementsIn(ImmutableSet.of("schema1", "schema2"));
+    public void testValidPackageName() {
+        assertThat(VisibilityStore.PACKAGE_NAME).doesNotContain(
+                "" + AppSearchImpl.PACKAGE_DELIMITER); // Convert the chars to CharSequences
+        assertThat(VisibilityStore.PACKAGE_NAME).doesNotContain(
+                "" + AppSearchImpl.DATABASE_DELIMITER); // Convert the chars to CharSequences
+    }
 
-        // New .setVisibility() call completely overrides previous visibility settings. So
-        // "schema2" isn't preserved.
-        mVisibilityStore.setVisibility("database",
-                /*schemasNotPlatformSurfaceable=*/ ImmutableSet.of("schema1", "schema3"));
-        assertThat(mVisibilityStore.getSchemasNotPlatformSurfaceable("database"))
-                .containsExactlyElementsIn(ImmutableSet.of("schema1", "schema3"));
-
-        mVisibilityStore.setVisibility(
-                "database", /*schemasNotPlatformSurfaceable=*/ Collections.emptySet());
-        assertThat(mVisibilityStore.getSchemasNotPlatformSurfaceable("database")).isEmpty();
+    /**
+     * Make sure that we don't conflict with any special characters that AppSearchImpl has
+     * reserved.
+     */
+    @Test
+    public void testValidDatabaseName() {
+        assertThat(VisibilityStore.DATABASE_NAME).doesNotContain(
+                "" + AppSearchImpl.PACKAGE_DELIMITER); // Convert the chars to CharSequences
+        assertThat(VisibilityStore.DATABASE_NAME).doesNotContain(
+                "" + AppSearchImpl.DATABASE_DELIMITER); // Convert the chars to CharSequences
     }
 
     @Test
-    public void testEmptyDatabase() throws Exception {
-        mVisibilityStore.setVisibility(LocalStorage.DEFAULT_DATABASE_NAME,
+    public void testSetVisibility() throws Exception {
+        mVisibilityStore.setVisibility("prefix",
+                /*schemasNotPlatformSurfaceable=*/
+                ImmutableSet.of("prefix/schema1", "prefix/schema2"));
+        assertThat(
+                mVisibilityStore.isSchemaPlatformSurfaceable("prefix", "prefix/schema1")).isFalse();
+        assertThat(
+                mVisibilityStore.isSchemaPlatformSurfaceable("prefix", "prefix/schema2")).isFalse();
+
+        // New .setVisibility() call completely overrides previous visibility settings. So
+        // "schema2" isn't preserved.
+        mVisibilityStore.setVisibility("prefix",
+                /*schemasNotPlatformSurfaceable=*/
+                ImmutableSet.of("prefix/schema1", "prefix/schema3"));
+        assertThat(
+                mVisibilityStore.isSchemaPlatformSurfaceable("prefix", "prefix/schema1")).isFalse();
+        assertThat(
+                mVisibilityStore.isSchemaPlatformSurfaceable("prefix", "prefix/schema2")).isTrue();
+        assertThat(
+                mVisibilityStore.isSchemaPlatformSurfaceable("prefix", "prefix/schema3")).isFalse();
+
+        mVisibilityStore.setVisibility(
+                "prefix", /*schemasNotPlatformSurfaceable=*/ Collections.emptySet());
+        assertThat(
+                mVisibilityStore.isSchemaPlatformSurfaceable("prefix", "prefix/schema1")).isTrue();
+        assertThat(
+                mVisibilityStore.isSchemaPlatformSurfaceable("prefix", "prefix/schema2")).isTrue();
+        assertThat(
+                mVisibilityStore.isSchemaPlatformSurfaceable("prefix", "prefix/schema3")).isTrue();
+    }
+
+    @Test
+    public void testEmptyPrefix() throws Exception {
+        mVisibilityStore.setVisibility(/*prefix=*/ "",
                 /*schemasNotPlatformSurfaceable=*/ ImmutableSet.of("schema1", "schema2"));
-        assertThat(mVisibilityStore.getSchemasNotPlatformSurfaceable(""))
-                .containsExactlyElementsIn(ImmutableSet.of("schema1", "schema2"));
+        assertThat(
+                mVisibilityStore.isSchemaPlatformSurfaceable(/*prefix=*/ "", "schema1")).isFalse();
+        assertThat(
+                mVisibilityStore.isSchemaPlatformSurfaceable(/*prefix=*/ "", "schema2")).isFalse();
     }
 }
diff --git a/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchImpl.java b/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchImpl.java
index 9c25f4b..5018664 100644
--- a/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchImpl.java
+++ b/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchImpl.java
@@ -76,14 +76,16 @@
  *
  * <p>Never create two instances using the same folder.
  *
- * <p>A single instance of {@link AppSearchImpl} can support all databases. Schemas and documents
- * are physically saved together in {@link IcingSearchEngine}, but logically isolated:
+ * <p>A single instance of {@link AppSearchImpl} can support all packages and databases.
+ * This is done by combining the package and database name into a unique prefix and
+ * prefixing the schemas and documents stored under that owner. Schemas and documents are
+ * physically saved together in {@link IcingSearchEngine}, but logically isolated:
  * <ul>
- *      <li>Rewrite SchemaType in SchemaProto by adding database name prefix and save into
+ *      <li>Rewrite SchemaType in SchemaProto by adding the package-database prefix and save into
  *          SchemaTypes set in {@link #setSchema}.
- *      <li>Rewrite namespace and SchemaType in DocumentProto by adding database name prefix and
+ *      <li>Rewrite namespace and SchemaType in DocumentProto by adding package-database prefix and
  *          save to namespaces set in {@link #putDocument}.
- *      <li>Remove database name prefix when retrieve documents in {@link #getDocument} and
+ *      <li>Remove package-database prefix when retrieving documents in {@link #getDocument} and
  *          {@link #query}.
  *      <li>Rewrite filters in {@link SearchSpecProto} to have all namespaces and schema types of
  *          the queried database when user using empty filters in {@link #query}.
@@ -110,6 +112,9 @@
     static final char DATABASE_DELIMITER = '/';
 
     @VisibleForTesting
+    static final char PACKAGE_DELIMITER = '$';
+
+    @VisibleForTesting
     static final int OPTIMIZE_THRESHOLD_DOC_COUNT = 1000;
     @VisibleForTesting
     static final int OPTIMIZE_THRESHOLD_BYTES = 1_000_000; // 1MB
@@ -124,12 +129,14 @@
     @GuardedBy("mReadWriteLock")
     private final VisibilityStore mVisibilityStoreLocked;
 
-    // The map contains schemaTypes and namespaces for all database. All values in the map have
-    // the database name prefix.
+    // This map contains schemaTypes for all package-database prefixes. All values in the map are
+    // prefixed with the package-database prefix.
     // TODO(b/172360376): Check if this can be replaced with an ArrayMap
     @GuardedBy("mReadWriteLock")
     private final Map<String, Set<String>> mSchemaMapLocked = new HashMap<>();
 
+    // This map contains namespaces for all package-database prefixes. All values in the map are
+    // prefixed with the package-database prefix.
     // TODO(b/172360376): Check if this can be replaced with an ArrayMap
     @GuardedBy("mReadWriteLock")
     private final Map<String, Set<String>> mNamespaceMapLocked = new HashMap<>();
@@ -182,15 +189,15 @@
 
             // Populate schema map
             for (SchemaTypeConfigProto schema : schemaProto.getTypesList()) {
-                String qualifiedSchemaType = schema.getSchemaType();
-                addToMap(mSchemaMapLocked, getDatabaseName(qualifiedSchemaType),
-                        qualifiedSchemaType);
+                String prefixedSchemaType = schema.getSchemaType();
+                addToMap(mSchemaMapLocked, getPrefix(prefixedSchemaType),
+                        prefixedSchemaType);
             }
 
             // Populate namespace map
-            for (String qualifiedNamespace : getAllNamespacesResultProto.getNamespacesList()) {
-                addToMap(mNamespaceMapLocked, getDatabaseName(qualifiedNamespace),
-                        qualifiedNamespace);
+            for (String prefixedNamespace : getAllNamespacesResultProto.getNamespacesList()) {
+                addToMap(mNamespaceMapLocked, getPrefix(prefixedNamespace),
+                        prefixedNamespace);
             }
 
             // TODO(b/155939114): It's possible to optimize after init, which would reduce the time
@@ -224,6 +231,7 @@
      *
      * <p>This method belongs to mutate group.
      *
+     * @param packageName                   The package name that owns the schemas.
      * @param databaseName                  The name of the database where this schema lives.
      * @param schemas                       Schemas to set for this app.
      * @param schemasNotPlatformSurfaceable Schema types that should not be surfaced on platform
@@ -234,6 +242,7 @@
      * @throws AppSearchException on IcingSearchEngine error.
      */
     public void setSchema(
+            @NonNull String packageName,
             @NonNull String databaseName,
             @NonNull List<AppSearchSchema> schemas,
             @NonNull List<String> schemasNotPlatformSurfaceable,
@@ -249,9 +258,10 @@
                 newSchemaBuilder.addTypes(schemaTypeProto);
             }
 
-            // Combine the existing schema (which may have types from other databases) with this
-            // database's new schema. Modifies the existingSchemaBuilder.
-            RewrittenSchemaResults rewrittenSchemaResults = rewriteSchema(databaseName,
+            String prefix = createPrefix(packageName, databaseName);
+            // Combine the existing schema (which may have types from other prefixes) with this
+            // prefix's new schema. Modifies the existingSchemaBuilder.
+            RewrittenSchemaResults rewrittenSchemaResults = rewriteSchema(prefix,
                     existingSchemaBuilder,
                     newSchemaBuilder.build());
 
@@ -279,17 +289,16 @@
             }
 
             // Update derived data structures.
-            mSchemaMapLocked.put(databaseName, rewrittenSchemaResults.mRewrittenQualifiedTypes);
+            mSchemaMapLocked.put(prefix, rewrittenSchemaResults.mRewrittenPrefixedTypes);
 
-            String databasePrefix = getDatabasePrefix(databaseName);
-            Set<String> qualifiedSchemasNotPlatformSurfaceable =
+            Set<String> prefixedSchemasNotPlatformSurfaceable =
                     new ArraySet<>(schemasNotPlatformSurfaceable.size());
             for (int i = 0; i < schemasNotPlatformSurfaceable.size(); i++) {
-                qualifiedSchemasNotPlatformSurfaceable.add(
-                        databasePrefix + schemasNotPlatformSurfaceable.get(i));
+                prefixedSchemasNotPlatformSurfaceable.add(
+                        prefix + schemasNotPlatformSurfaceable.get(i));
             }
-            mVisibilityStoreLocked.setVisibility(databaseName,
-                    qualifiedSchemasNotPlatformSurfaceable);
+            mVisibilityStoreLocked.setVisibility(prefix,
+                    prefixedSchemasNotPlatformSurfaceable);
 
             // Determine whether to schedule an immediate optimize.
             if (setSchemaResultProto.getDeletedSchemaTypesCount() > 0
@@ -306,15 +315,17 @@
     }
 
     /**
-     * Retrieves the AppSearch schema for this database.
+     * Retrieves the AppSearch schema for this package name, database.
      *
      * <p>This method belongs to query group.
      *
+     * @param packageName  Package name that owns this schema
      * @param databaseName The name of the database where this schema lives.
      * @throws AppSearchException on IcingSearchEngine error.
      */
     @NonNull
-    public List<AppSearchSchema> getSchema(@NonNull String databaseName) throws AppSearchException {
+    public List<AppSearchSchema> getSchema(@NonNull String packageName,
+            @NonNull String databaseName) throws AppSearchException {
         SchemaProto fullSchema;
         mReadWriteLock.readLock().lock();
         try {
@@ -323,16 +334,17 @@
             mReadWriteLock.readLock().unlock();
         }
 
+        String prefix = createPrefix(packageName, databaseName);
         List<AppSearchSchema> result = new ArrayList<>();
         for (int i = 0; i < fullSchema.getTypesCount(); i++) {
-            String typeDatabase = getDatabaseName(fullSchema.getTypes(i).getSchemaType());
-            if (!databaseName.equals(typeDatabase)) {
+            String typePrefix = getPrefix(fullSchema.getTypes(i).getSchemaType());
+            if (!prefix.equals(typePrefix)) {
                 continue;
             }
             // Rewrite SchemaProto.types.schema_type
             SchemaTypeConfigProto.Builder typeConfigBuilder = fullSchema.getTypes(i).toBuilder();
             String newSchemaType =
-                    typeConfigBuilder.getSchemaType().substring(databaseName.length() + 1);
+                    typeConfigBuilder.getSchemaType().substring(prefix.length());
             typeConfigBuilder.setSchemaType(newSchemaType);
 
             // Rewrite SchemaProto.types.properties.schema_type
@@ -343,7 +355,7 @@
                         typeConfigBuilder.getProperties(propertyIdx).toBuilder();
                 if (!propertyConfigBuilder.getSchemaType().isEmpty()) {
                     String newPropertySchemaType = propertyConfigBuilder.getSchemaType()
-                            .substring(databaseName.length() + 1);
+                            .substring(prefix.length());
                     propertyConfigBuilder.setSchemaType(newPropertySchemaType);
                     typeConfigBuilder.setProperties(propertyIdx, propertyConfigBuilder);
                 }
@@ -360,24 +372,27 @@
      *
      * <p>This method belongs to mutate group.
      *
+     * @param packageName  The package name that owns this document.
      * @param databaseName The databaseName this document resides in.
      * @param document     The document to index.
      * @throws AppSearchException on IcingSearchEngine error.
      */
-    public void putDocument(@NonNull String databaseName, @NonNull GenericDocument document)
+    public void putDocument(@NonNull String packageName, @NonNull String databaseName,
+            @NonNull GenericDocument document)
             throws AppSearchException {
         DocumentProto.Builder documentBuilder = GenericDocumentToProtoConverter.toDocumentProto(
                 document).toBuilder();
-        addPrefixToDocument(documentBuilder, getDatabasePrefix(databaseName));
+        String prefix = createPrefix(packageName, databaseName);
+        addPrefixToDocument(documentBuilder, prefix);
 
         PutResultProto putResultProto;
         mReadWriteLock.writeLock().lock();
         try {
             putResultProto = mIcingSearchEngineLocked.put(documentBuilder.build());
-            addToMap(mNamespaceMapLocked, databaseName, documentBuilder.getNamespace());
+            addToMap(mNamespaceMapLocked, prefix, documentBuilder.getNamespace());
             // The existing documents with same URI will be deleted, so there maybe some resources
             // could be released after optimize().
-            checkForOptimizeLocked(/* force= */false);
+            checkForOptimizeLocked(/* force= */ false);
         } finally {
             mReadWriteLock.writeLock().unlock();
         }
@@ -389,6 +404,7 @@
      *
      * <p>This method belongs to query group.
      *
+     * @param packageName  The package that owns this document.
      * @param databaseName The databaseName this document resides in.
      * @param namespace    The namespace this document resides in.
      * @param uri          The URI of the document to get.
@@ -396,20 +412,21 @@
      * @throws AppSearchException on IcingSearchEngine error.
      */
     @NonNull
-    public GenericDocument getDocument(@NonNull String databaseName, @NonNull String namespace,
+    public GenericDocument getDocument(@NonNull String packageName, @NonNull String databaseName,
+            @NonNull String namespace,
             @NonNull String uri) throws AppSearchException {
         GetResultProto getResultProto;
         mReadWriteLock.readLock().lock();
         try {
             getResultProto = mIcingSearchEngineLocked.get(
-                    getDatabasePrefix(databaseName) + namespace, uri);
+                    createPrefix(packageName, databaseName) + namespace, uri);
         } finally {
             mReadWriteLock.readLock().unlock();
         }
         checkSuccess(getResultProto.getStatus());
 
         DocumentProto.Builder documentBuilder = getResultProto.getDocument().toBuilder();
-        removeDatabasesFromDocument(documentBuilder);
+        removePrefixesFromDocument(documentBuilder);
         return GenericDocumentToProtoConverter.toGenericDocument(documentBuilder.build());
     }
 
@@ -418,6 +435,7 @@
      *
      * <p>This method belongs to query group.
      *
+     * @param packageName     The package name that is performing the query.
      * @param databaseName    The databaseName this query for.
      * @param queryExpression Query String to search.
      * @param searchSpec      Spec for setting filters, raw query etc.
@@ -427,12 +445,14 @@
      */
     @NonNull
     public SearchResultPage query(
+            @NonNull String packageName,
             @NonNull String databaseName,
             @NonNull String queryExpression,
             @NonNull SearchSpec searchSpec) throws AppSearchException {
         mReadWriteLock.readLock().lock();
         try {
-            return doQueryLocked(Collections.singleton(databaseName), queryExpression,
+            return doQueryLocked(Collections.singleton(createPrefix(packageName, databaseName)),
+                    queryExpression,
                     searchSpec);
         } finally {
             mReadWriteLock.readLock().unlock();
@@ -440,7 +460,7 @@
     }
 
     /**
-     * Executes a global query, i.e. over all permitted databases, against the AppSearch index and
+     * Executes a global query, i.e. over all permitted prefixes, against the AppSearch index and
      * returns results.
      *
      * <p>This method belongs to query group.
@@ -460,9 +480,15 @@
         //  verified.
         mReadWriteLock.readLock().lock();
         try {
-            // We use the mNamespaceMap.keySet here because it's the smaller set of valid databases
+            // We use the mNamespaceMap.keySet here because it's the smaller set of valid prefixes
             // that could exist.
-            return doQueryLocked(mNamespaceMapLocked.keySet(), queryExpression, searchSpec);
+            Set<String> prefixes = mNamespaceMapLocked.keySet();
+
+            // Filter out any VisibilityStore documents which are AppSearch-internal only.
+            prefixes.remove(createPrefix(VisibilityStore.PACKAGE_NAME,
+                    VisibilityStore.DATABASE_NAME));
+
+            return doQueryLocked(prefixes, queryExpression, searchSpec);
         } finally {
             mReadWriteLock.readLock().unlock();
         }
@@ -470,7 +496,7 @@
 
     @GuardedBy("mReadWriteLock")
     private SearchResultPage doQueryLocked(
-            @NonNull Set<String> databases, @NonNull String queryExpression,
+            @NonNull Set<String> prefixes, @NonNull String queryExpression,
             @NonNull SearchSpec searchSpec)
             throws AppSearchException {
         SearchSpecProto searchSpecProto =
@@ -482,12 +508,10 @@
         ScoringSpecProto scoringSpec = SearchSpecToProtoConverter.toScoringSpecProto(searchSpec);
         SearchResultProto searchResultProto;
 
-        // rewriteSearchSpecForDatabases will return false if none of the databases that the
+        // rewriteSearchSpecForPrefixesLocked will return false if none of the prefixes that the
         // client is trying to search on exist, so we can return an empty SearchResult and skip
         // sending request to Icing.
-        // We use the mNamespaceMap.keySet here because it's the smaller set of valid databases
-        // that could exist.
-        if (!rewriteSearchSpecForDatabasesLocked(searchSpecBuilder, databases)) {
+        if (!rewriteSearchSpecForPrefixesLocked(searchSpecBuilder, prefixes)) {
             return new SearchResultPage(Bundle.EMPTY);
         }
         searchResultProto = mIcingSearchEngineLocked.search(
@@ -543,18 +567,20 @@
      *
      * <p>This method belongs to mutate group.
      *
+     * @param packageName  The package name that owns the document.
      * @param databaseName The databaseName the document is in.
      * @param namespace    Namespace of the document to remove.
      * @param uri          URI of the document to remove.
      * @throws AppSearchException on IcingSearchEngine error.
      */
-    public void remove(@NonNull String databaseName, @NonNull String namespace,
+    public void remove(@NonNull String packageName, @NonNull String databaseName,
+            @NonNull String namespace,
             @NonNull String uri) throws AppSearchException {
-        String qualifiedNamespace = getDatabasePrefix(databaseName) + namespace;
+        String prefixedNamespace = createPrefix(packageName, databaseName) + namespace;
         DeleteResultProto deleteResultProto;
         mReadWriteLock.writeLock().lock();
         try {
-            deleteResultProto = mIcingSearchEngineLocked.delete(qualifiedNamespace, uri);
+            deleteResultProto = mIcingSearchEngineLocked.delete(prefixedNamespace, uri);
             checkForOptimizeLocked(/* force= */false);
         } finally {
             mReadWriteLock.writeLock().unlock();
@@ -567,12 +593,14 @@
      *
      * <p>This method belongs to mutate group.
      *
+     * @param packageName     The package name that owns the documents.
      * @param databaseName    The databaseName the document is in.
      * @param queryExpression Query String to search.
      * @param searchSpec      Defines what and how to remove
      * @throws AppSearchException on IcingSearchEngine error.
      */
-    public void removeByQuery(@NonNull String databaseName, @NonNull String queryExpression,
+    public void removeByQuery(@NonNull String packageName, @NonNull String databaseName,
+            @NonNull String queryExpression,
             @NonNull SearchSpec searchSpec)
             throws AppSearchException {
         SearchSpecProto searchSpecProto =
@@ -582,11 +610,11 @@
         DeleteResultProto deleteResultProto;
         mReadWriteLock.writeLock().lock();
         try {
-            // Only rewrite SearchSpec for non empty database.
-            // rewriteSearchSpecForNonEmptyDatabase will return false for empty database, we
+            // Only rewrite SearchSpec for non empty prefixes.
+            // rewriteSearchSpecForPrefixesLocked will return false for empty prefixes, we
             // should skip sending request to Icing and return in here.
-            if (!rewriteSearchSpecForDatabasesLocked(searchSpecBuilder,
-                    Collections.singleton(databaseName))) {
+            if (!rewriteSearchSpecForPrefixesLocked(searchSpecBuilder,
+                    Collections.singleton(createPrefix(packageName, databaseName)))) {
                 return;
             }
             deleteResultProto = mIcingSearchEngineLocked.deleteByQuery(
@@ -602,7 +630,7 @@
     }
 
     /**
-     * Clears documents and schema across all databaseNames.
+     * Clears documents and schema across all packages and databaseNames.
      *
      * <p>This method belongs to mutate group.
      *
@@ -629,31 +657,29 @@
     /** Wrapper around schema changes */
     @VisibleForTesting
     static class RewrittenSchemaResults {
-        // Any database-qualified types that used to exist in the schema, but are deleted in the
-        // new one.
-        final Set<String> mDeletedQualifiedTypes = new ArraySet<>();
+        // Any prefixed types that used to exist in the schema, but are deleted in the new one.
+        final Set<String> mDeletedPrefixedTypes = new ArraySet<>();
 
-        // Database-qualified types that were part of the new schema.
-        final Set<String> mRewrittenQualifiedTypes = new ArraySet<>();
+        // Prefixed types that were part of the new schema.
+        final Set<String> mRewrittenPrefixedTypes = new ArraySet<>();
     }
 
     /**
      * Rewrites all types mentioned in the given {@code newSchema} to prepend {@code prefix}.
      * Rewritten types will be added to the {@code existingSchema}.
      *
-     * @param databaseName   The name of the database where this schema lives.
-     * @param existingSchema A schema that may contain existing types from across all database
-     *                       instances. Will be mutated to contain the properly rewritten schema
+     * @param prefix         The full prefix to prepend to the schema.
+     * @param existingSchema A schema that may contain existing types from across all prefixes.
+     *                       Will be mutated to contain the properly rewritten schema
      *                       types from {@code newSchema}.
      * @param newSchema      Schema with types to add to the {@code existingSchema}.
-     * @return a RewrittenSchemaResults contains all qualified schema type names in the given
-     * database as well as a set of schema types that were deleted from the database.
+     * @return a RewrittenSchemaResults that contains all prefixed schema type names in the given
+     * prefix as well as a set of schema types that were deleted.
      */
     @VisibleForTesting
-    static RewrittenSchemaResults rewriteSchema(@NonNull String databaseName,
+    static RewrittenSchemaResults rewriteSchema(@NonNull String prefix,
             @NonNull SchemaProto.Builder existingSchema,
             @NonNull SchemaProto newSchema) throws AppSearchException {
-        String prefix = getDatabasePrefix(databaseName);
         HashMap<String, SchemaTypeConfigProto> newTypesToProto = new HashMap<>();
         // Rewrite the schema type to include the typePrefix.
         for (int typeIdx = 0; typeIdx < newSchema.getTypesCount(); typeIdx++) {
@@ -683,10 +709,10 @@
 
         // newTypesToProto is modified below, so we need a copy first
         RewrittenSchemaResults rewrittenSchemaResults = new RewrittenSchemaResults();
-        rewrittenSchemaResults.mRewrittenQualifiedTypes.addAll(newTypesToProto.keySet());
+        rewrittenSchemaResults.mRewrittenPrefixedTypes.addAll(newTypesToProto.keySet());
 
-        // Combine the existing schema (which may have types from other databases) with this
-        // database's new schema. Modifies the existingSchemaBuilder.
+        // Combine the existing schema (which may have types from other prefixes) with this
+        // prefix's new schema. Modifies the existingSchemaBuilder.
         // Check if we need to replace any old schema types with the new ones.
         for (int i = 0; i < existingSchema.getTypesCount(); i++) {
             String schemaType = existingSchema.getTypes(i).getSchemaType();
@@ -694,11 +720,11 @@
             if (newProto != null) {
                 // Replacement
                 existingSchema.setTypes(i, newProto);
-            } else if (databaseName.equals(getDatabaseName(schemaType))) {
+            } else if (prefix.equals(getPrefix(schemaType))) {
                 // All types existing before but not in newSchema should be removed.
                 existingSchema.removeTypes(i);
                 --i;
-                rewrittenSchemaResults.mDeletedQualifiedTypes.add(schemaType);
+                rewrittenSchemaResults.mDeletedPrefixedTypes.add(schemaType);
             }
         }
         // We've been removing existing types from newTypesToProto, so everything that remains is
@@ -746,17 +772,17 @@
     }
 
     /**
-     * Removes any database names from types and namespaces mentioned anywhere in
+     * Removes any prefixes from types and namespaces mentioned anywhere in
      * {@code documentBuilder}.
      *
      * @param documentBuilder The document to mutate
      */
     @VisibleForTesting
-    static void removeDatabasesFromDocument(@NonNull DocumentProto.Builder documentBuilder)
+    static void removePrefixesFromDocument(@NonNull DocumentProto.Builder documentBuilder)
             throws AppSearchException {
         // Rewrite the type name and namespace to remove the prefix.
-        documentBuilder.setSchema(removeDatabasePrefix(documentBuilder.getSchema()));
-        documentBuilder.setNamespace(removeDatabasePrefix(documentBuilder.getNamespace()));
+        documentBuilder.setSchema(removePrefix(documentBuilder.getSchema()));
+        documentBuilder.setNamespace(removePrefix(documentBuilder.getNamespace()));
 
         // Recurse into derived documents
         for (int propertyIdx = 0;
@@ -769,7 +795,7 @@
                 for (int documentIdx = 0; documentIdx < documentCount; documentIdx++) {
                     DocumentProto.Builder derivedDocumentBuilder =
                             propertyBuilder.getDocumentValues(documentIdx).toBuilder();
-                    removeDatabasesFromDocument(derivedDocumentBuilder);
+                    removePrefixesFromDocument(derivedDocumentBuilder);
                     propertyBuilder.setDocumentValues(documentIdx, derivedDocumentBuilder);
                 }
                 documentBuilder.setProperties(propertyIdx, propertyBuilder);
@@ -778,26 +804,25 @@
     }
 
     /**
-     * Rewrites the schemaTypeFilters and namespacesFilters that exist in {@code databaseNames}.
+     * Rewrites the schemaTypeFilters and namespacesFilters that exist with {@code prefixes}.
      *
-     * <p>If the searchSpec has empty filter lists, all existing databases from
-     * {@code databaseNames} will be added.
+     * <p>If the searchSpec has empty filter lists, all prefixes filters will be added.
      * <p>This method should be only called in query methods and get the READ lock to keep thread
      * safety.
      *
-     * @return false if none of the requested databases exist.
+     * @return false if none of the requested prefixes exist.
      */
     @VisibleForTesting
     @GuardedBy("mReadWriteLock")
-    boolean rewriteSearchSpecForDatabasesLocked(
+    boolean rewriteSearchSpecForPrefixesLocked(
             @NonNull SearchSpecProto.Builder searchSpecBuilder,
-            @NonNull Set<String> databaseNames) {
+            @NonNull Set<String> prefixes) {
         // Create a copy since retainAll() modifies the original set.
-        Set<String> existingDatabases = new ArraySet<>(mNamespaceMapLocked.keySet());
-        existingDatabases.retainAll(databaseNames);
+        Set<String> existingPrefixes = new ArraySet<>(mNamespaceMapLocked.keySet());
+        existingPrefixes.retainAll(prefixes);
 
-        if (existingDatabases.isEmpty()) {
-            // None of the databases exist, empty query.
+        if (existingPrefixes.isEmpty()) {
+            // None of the prefixes exist, empty query.
             return false;
         }
 
@@ -808,33 +833,32 @@
         List<String> namespaceFilters = searchSpecBuilder.getNamespaceFiltersList();
         searchSpecBuilder.clearNamespaceFilters();
 
-        // Rewrite filters to include a database prefix.
-        for (String databaseName : existingDatabases) {
-            Set<String> existingSchemaTypes = mSchemaMapLocked.get(databaseName);
-            String databaseNamePrefix = getDatabasePrefix(databaseName);
+        // Rewrite filters to include a prefix.
+        for (String prefix : existingPrefixes) {
+            Set<String> existingSchemaTypes = mSchemaMapLocked.get(prefix);
             if (schemaTypeFilters.isEmpty()) {
                 // Include all schema types
                 searchSpecBuilder.addAllSchemaTypeFilters(existingSchemaTypes);
             } else {
-                // Qualify the given schema types
+                // Add the prefix to the given schema types
                 for (int i = 0; i < schemaTypeFilters.size(); i++) {
-                    String qualifiedType = databaseNamePrefix + schemaTypeFilters.get(i);
-                    if (existingSchemaTypes.contains(qualifiedType)) {
-                        searchSpecBuilder.addSchemaTypeFilters(qualifiedType);
+                    String prefixedType = prefix + schemaTypeFilters.get(i);
+                    if (existingSchemaTypes.contains(prefixedType)) {
+                        searchSpecBuilder.addSchemaTypeFilters(prefixedType);
                     }
                 }
             }
 
-            Set<String> existingNamespaces = mNamespaceMapLocked.get(databaseName);
+            Set<String> existingNamespaces = mNamespaceMapLocked.get(prefix);
             if (namespaceFilters.isEmpty()) {
                 // Include all namespaces
                 searchSpecBuilder.addAllNamespaceFilters(existingNamespaces);
             } else {
-                // Qualify the given namespaces.
+                // Prefix the given namespaces.
                 for (int i = 0; i < namespaceFilters.size(); i++) {
-                    String qualifiedNamespace = databaseNamePrefix + namespaceFilters.get(i);
-                    if (existingNamespaces.contains(qualifiedNamespace)) {
-                        searchSpecBuilder.addNamespaceFilters(qualifiedNamespace);
+                    String prefixedNamespace = prefix + namespaceFilters.get(i);
+                    if (existingNamespaces.contains(prefixedNamespace)) {
+                        searchSpecBuilder.addNamespaceFilters(prefixedNamespace);
                     }
                 }
             }
@@ -853,36 +877,43 @@
         return schemaProto.getSchema();
     }
 
-    /** Returns true if {@code databaseName} has a {@code schemaType} */
+    /**
+     * Returns true if the {@code packageName} and {@code databaseName} has the
+     * {@code schemaType}
+     */
     @GuardedBy("mReadWriteLock")
-    boolean hasSchemaTypeLocked(@NonNull String databaseName, @NonNull String schemaType) {
+    boolean hasSchemaTypeLocked(@NonNull String packageName, @NonNull String databaseName,
+            @NonNull String schemaType) {
+        Preconditions.checkNotNull(packageName);
         Preconditions.checkNotNull(databaseName);
         Preconditions.checkNotNull(schemaType);
 
-        Set<String> schemaTypes = mSchemaMapLocked.get(databaseName);
+        String prefix = createPrefix(packageName, databaseName);
+        Set<String> schemaTypes = mSchemaMapLocked.get(prefix);
         if (schemaTypes == null) {
             return false;
         }
 
-        return schemaTypes.contains(getDatabasePrefix(databaseName) + schemaType);
+        return schemaTypes.contains(prefix + schemaType);
     }
 
-    /** Returns a set of all databases AppSearchImpl knows about. */
+    /** Returns a set of all prefixes AppSearchImpl knows about. */
     @GuardedBy("mReadWriteLock")
     @NonNull
-    Set<String> getDatabasesLocked() {
+    Set<String> getPrefixesLocked() {
         return mSchemaMapLocked.keySet();
     }
 
     @NonNull
-    private static String getDatabasePrefix(@NonNull String databaseName) {
-        // TODO(b/170370381): Reconsider the way we separate database names for security reasons.
-        return databaseName + DATABASE_DELIMITER;
+    static String createPrefix(@NonNull String packageName, @NonNull String databaseName) {
+        return packageName + PACKAGE_DELIMITER + databaseName + DATABASE_DELIMITER;
     }
 
     @NonNull
-    private static String removeDatabasePrefix(@NonNull String prefixedString)
+    private static String removePrefix(@NonNull String prefixedString)
             throws AppSearchException {
+        // The prefix is made up of the package, then the database. So we only need to find the
+        // database cutoff.
         int delimiterIndex;
         if ((delimiterIndex = prefixedString.indexOf(DATABASE_DELIMITER)) != -1) {
             // Add 1 to include the char size of the DATABASE_DELIMITER
@@ -893,21 +924,23 @@
     }
 
     @NonNull
-    private static String getDatabaseName(@NonNull String prefixedValue) throws AppSearchException {
-        int delimiterIndex = prefixedValue.indexOf(DATABASE_DELIMITER);
-        if (delimiterIndex == -1) {
+    private static String getPrefix(@NonNull String prefixedString) throws AppSearchException {
+        int databaseDelimiterIndex = prefixedString.indexOf(DATABASE_DELIMITER);
+        if (databaseDelimiterIndex == -1) {
             throw new AppSearchException(AppSearchResult.RESULT_UNKNOWN_ERROR,
                     "The databaseName prefixed value doesn't contains a valid database name.");
         }
-        return prefixedValue.substring(0, delimiterIndex);
+
+        // Add 1 to include the char size of the DATABASE_DELIMITER
+        return prefixedString.substring(0, databaseDelimiterIndex + 1);
     }
 
-    private static void addToMap(Map<String, Set<String>> map, String databaseName,
+    private static void addToMap(Map<String, Set<String>> map, String prefix,
             String prefixedValue) {
-        Set<String> values = map.get(databaseName);
+        Set<String> values = map.get(prefix);
         if (values == null) {
             values = new ArraySet<>();
-            map.put(databaseName, values);
+            map.put(prefix, values);
         }
         values.add(prefixedValue);
     }
@@ -987,7 +1020,7 @@
                 SearchResultProto.ResultProto.Builder resultBuilder =
                         searchResultProto.getResults(i).toBuilder();
                 DocumentProto.Builder documentBuilder = resultBuilder.getDocument().toBuilder();
-                removeDatabasesFromDocument(documentBuilder);
+                removePrefixesFromDocument(documentBuilder);
                 resultBuilder.setDocument(documentBuilder);
                 resultsBuilder.setResults(i, resultBuilder);
             }
diff --git a/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/GlobalSearchSessionImpl.java b/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/GlobalSearchSessionImpl.java
index b6c2bac..ac8fec2 100644
--- a/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/GlobalSearchSessionImpl.java
+++ b/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/GlobalSearchSessionImpl.java
@@ -52,6 +52,7 @@
         return new SearchResultsImpl(
                 mAppSearchImpl,
                 mExecutorService,
+                /*packageName=*/ null,
                 /*databaseName=*/ null,
                 queryExpression,
                 searchSpec);
diff --git a/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/LocalStorage.java b/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/LocalStorage.java
index 3426cfe..4bb0dbd 100644
--- a/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/LocalStorage.java
+++ b/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/LocalStorage.java
@@ -268,7 +268,8 @@
     }
 
     AppSearchSession doCreateSearchSession(@NonNull SearchContext context) {
-        return new SearchSessionImpl(mAppSearchImpl, mExecutorService, context.mDatabaseName);
+        return new SearchSessionImpl(mAppSearchImpl, mExecutorService,
+                context.mContext.getPackageName(), context.mDatabaseName);
     }
 
     GlobalSearchSession doCreateGlobalSearchSession() {
diff --git a/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/SearchResultsImpl.java b/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/SearchResultsImpl.java
index be3876b..4f56f9a 100644
--- a/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/SearchResultsImpl.java
+++ b/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/SearchResultsImpl.java
@@ -36,6 +36,11 @@
 
     private final ExecutorService mExecutorService;
 
+    // The package name to search over. If null, this will search over all package names.
+    @Nullable
+    private final String mPackageName;
+
+    // The database name to search over. If null, this will search over all database names.
     @Nullable
     private final String mDatabaseName;
 
@@ -50,11 +55,13 @@
     SearchResultsImpl(
             @NonNull AppSearchImpl appSearchImpl,
             @NonNull ExecutorService executorService,
+            @Nullable String packageName,
             @Nullable String databaseName,
             @NonNull String queryExpression,
             @NonNull SearchSpec searchSpec) {
         mAppSearchImpl = Preconditions.checkNotNull(appSearchImpl);
         mExecutorService = Preconditions.checkNotNull(executorService);
+        mPackageName = packageName;
         mDatabaseName = databaseName;
         mQueryExpression = Preconditions.checkNotNull(queryExpression);
         mSearchSpec = Preconditions.checkNotNull(searchSpec);
@@ -68,14 +75,23 @@
                 SearchResultPage searchResultPage;
                 if (mIsFirstLoad) {
                     mIsFirstLoad = false;
-                    if (mDatabaseName == null) {
-                        // Global query, there's no one database to check.
+                    if (mDatabaseName == null && mPackageName == null) {
+                        // Global query, there's no one package-database combination to check.
                         searchResultPage = mAppSearchImpl.globalQuery(
                                 mQueryExpression, mSearchSpec);
+                    } else if (mPackageName == null) {
+                        return AppSearchResult.newFailedResult(
+                                AppSearchResult.RESULT_INVALID_ARGUMENT,
+                                "Invalid null package name for query");
+                    } else if (mDatabaseName == null) {
+                        return AppSearchResult.newFailedResult(
+                                AppSearchResult.RESULT_INVALID_ARGUMENT,
+                                "Invalid null database name for query");
                     } else {
                         // Normal local query, pass in specified database.
                         searchResultPage = mAppSearchImpl.query(
-                                mDatabaseName, mQueryExpression, mSearchSpec);
+                                mPackageName, mDatabaseName, mQueryExpression, mSearchSpec);
+
                     }
                 } else {
                     searchResultPage = mAppSearchImpl.getNextPage(mNextPageToken);
diff --git a/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/SearchSessionImpl.java b/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/SearchSessionImpl.java
index f9440ec..00ff301 100644
--- a/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/SearchSessionImpl.java
+++ b/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/SearchSessionImpl.java
@@ -52,14 +52,17 @@
 class SearchSessionImpl implements AppSearchSession {
     private final AppSearchImpl mAppSearchImpl;
     private final ExecutorService mExecutorService;
+    private final String mPackageName;
     private final String mDatabaseName;
 
     SearchSessionImpl(
             @NonNull AppSearchImpl appSearchImpl,
             @NonNull ExecutorService executorService,
+            @NonNull String packageName,
             @NonNull String databaseName) {
         mAppSearchImpl = Preconditions.checkNotNull(appSearchImpl);
         mExecutorService = Preconditions.checkNotNull(executorService);
+        mPackageName = packageName;
         mDatabaseName = Preconditions.checkNotNull(databaseName);
     }
 
@@ -70,6 +73,7 @@
         return execute(() -> {
             try {
                 mAppSearchImpl.setSchema(
+                        mPackageName,
                         mDatabaseName,
                         new ArrayList<>(request.getSchemas()),
                         new ArrayList<>(request.getSchemasNotPlatformSurfaceable()),
@@ -86,7 +90,8 @@
     public ListenableFuture<AppSearchResult<Set<AppSearchSchema>>> getSchema() {
         return execute(() -> {
             try {
-                List<AppSearchSchema> schemas = mAppSearchImpl.getSchema(mDatabaseName);
+                List<AppSearchSchema> schemas = mAppSearchImpl.getSchema(mPackageName,
+                        mDatabaseName);
                 return AppSearchResult.newSuccessfulResult(new ArraySet<>(schemas));
             } catch (Throwable t) {
                 return throwableToFailedResult(t);
@@ -105,7 +110,7 @@
             for (int i = 0; i < request.getDocuments().size(); i++) {
                 GenericDocument document = request.getDocuments().get(i);
                 try {
-                    mAppSearchImpl.putDocument(mDatabaseName, document);
+                    mAppSearchImpl.putDocument(mPackageName, mDatabaseName, document);
                     resultBuilder.setSuccess(document.getUri(), /*result=*/ null);
                 } catch (Throwable t) {
                     resultBuilder.setResult(document.getUri(), throwableToFailedResult(t));
@@ -127,7 +132,8 @@
             for (String uri : request.getUris()) {
                 try {
                     GenericDocument document =
-                            mAppSearchImpl.getDocument(mDatabaseName, request.getNamespace(), uri);
+                            mAppSearchImpl.getDocument(mPackageName, mDatabaseName,
+                                    request.getNamespace(), uri);
                     resultBuilder.setSuccess(uri, document);
                 } catch (Throwable t) {
                     resultBuilder.setResult(uri, throwableToFailedResult(t));
@@ -147,6 +153,7 @@
         return new SearchResultsImpl(
                 mAppSearchImpl,
                 mExecutorService,
+                mPackageName,
                 mDatabaseName,
                 queryExpression,
                 searchSpec);
@@ -162,7 +169,7 @@
                     new AppSearchBatchResult.Builder<>();
             for (String uri : request.getUris()) {
                 try {
-                    mAppSearchImpl.remove(mDatabaseName, request.getNamespace(), uri);
+                    mAppSearchImpl.remove(mPackageName, mDatabaseName, request.getNamespace(), uri);
                     resultBuilder.setSuccess(uri, /*result=*/null);
                 } catch (Throwable t) {
                     resultBuilder.setResult(uri, throwableToFailedResult(t));
@@ -180,7 +187,8 @@
         Preconditions.checkNotNull(searchSpec);
         return execute(() -> {
             try {
-                mAppSearchImpl.removeByQuery(mDatabaseName, queryExpression, searchSpec);
+                mAppSearchImpl.removeByQuery(mPackageName, mDatabaseName, queryExpression,
+                        searchSpec);
                 return AppSearchResult.newSuccessfulResult(null);
             } catch (Throwable t) {
                 return throwableToFailedResult(t);
diff --git a/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/VisibilityStore.java b/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/VisibilityStore.java
index 935662e..b976291 100644
--- a/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/VisibilityStore.java
+++ b/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/VisibilityStore.java
@@ -50,30 +50,57 @@
  * this class. Take care to not cause any circular dependencies.
  */
 class VisibilityStore {
-    // Schema type for documents that hold AppSearch's metadata, e.g. visibility settings
+    /** Schema type for documents that hold AppSearch's metadata, e.g. visibility settings */
     @VisibleForTesting
     static final String SCHEMA_TYPE = "Visibility";
 
-    // Property that holds the list of platform-hidden schemas, as part of the visibility
-    // settings.
+    /**
+     * Property that holds the list of platform-hidden schemas, as part of the visibility settings.
+     */
     @VisibleForTesting
     static final String NOT_PLATFORM_SURFACEABLE_PROPERTY = "notPlatformSurfaceable";
-    // Database name to prefix all visibility schemas and documents with. Special-cased to
-    // minimize the chance of collision with a client-supplied database.
 
+    /** Schema for the VisibilityStore's docuemnts. */
     @VisibleForTesting
-    static final String DATABASE_NAME = "$$__AppSearch__Database";
+    static final AppSearchSchema SCHEMA = new AppSearchSchema.Builder(SCHEMA_TYPE)
+            .addProperty(new AppSearchSchema.PropertyConfig.Builder(
+                    NOT_PLATFORM_SURFACEABLE_PROPERTY)
+                    .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_STRING)
+                    .setCardinality(
+                            AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
+                    .build())
+            .build();
 
-    // Namespace of documents that contain visibility settings
+    /**
+     * These cannot have any of the special characters used by AppSearchImpl (e.g.
+     * {@link AppSearchImpl#PACKAGE_DELIMITER} or {@link AppSearchImpl#DATABASE_DELIMITER}.
+     */
+    static final String PACKAGE_NAME = "VS#Pkg";
+    static final String DATABASE_NAME = "VS#Db";
+
+    /**
+     * Prefix that AppSearchImpl creates for the VisibilityStore based on our package name and
+     * database name. Tracked here to tell when we're looking at our own prefix when looking
+     * through AppSearchImpl.
+     */
+    private static final String VISIBILITY_STORE_PREFIX = AppSearchImpl.createPrefix(PACKAGE_NAME,
+            DATABASE_NAME);
+
+    /** Namespace of documents that contain visibility settings */
     private static final String NAMESPACE = GenericDocument.DEFAULT_NAMESPACE;
 
-    // Prefix to add to all visibility document uri's. IcingSearchEngine doesn't allow empty uri's.
+    /**
+     * Prefix to add to all visibility document uri's. IcingSearchEngine doesn't allow empty
+     * uri's.
+     */
     private static final String URI_PREFIX = "uri:";
 
     private final AppSearchImpl mAppSearchImpl;
 
-    // The map contains schemas that are platform-hidden for each database. All schemas in the map
-    // have a database name prefix.
+    /**
+     * Maps prefixes to the set of schemas that are platform-hidden within that prefix. All schemas
+     * in the map are prefixed.
+     */
     private final Map<String, Set<String>> mNotPlatformSurfaceableMap = new ArrayMap<>();
 
     /**
@@ -98,36 +125,30 @@
      * @throws AppSearchException AppSearchException on AppSearchImpl error.
      */
     public void initialize() throws AppSearchException {
-        if (!mAppSearchImpl.hasSchemaTypeLocked(DATABASE_NAME, SCHEMA_TYPE)) {
+        if (!mAppSearchImpl.hasSchemaTypeLocked(PACKAGE_NAME, DATABASE_NAME, SCHEMA_TYPE)) {
             // Schema type doesn't exist yet. Add it.
-            mAppSearchImpl.setSchema(DATABASE_NAME,
-                    Collections.singletonList(new AppSearchSchema.Builder(SCHEMA_TYPE)
-                            .addProperty(new AppSearchSchema.PropertyConfig.Builder(
-                                    NOT_PLATFORM_SURFACEABLE_PROPERTY)
-                                    .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_STRING)
-                                    .setCardinality(
-                                            AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
-                                    .build())
-                            .build()),
+            mAppSearchImpl.setSchema(PACKAGE_NAME, DATABASE_NAME,
+                    Collections.singletonList(SCHEMA),
                     /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
                     /*forceOverride=*/ false);
         }
 
-        // Populate visibility settings map
-        for (String database : mAppSearchImpl.getDatabasesLocked()) {
-            if (database.equals(DATABASE_NAME)) {
-                // Our own database. Skip
+        // Populate visibility settings set
+        mNotPlatformSurfaceableMap.clear();
+        for (String prefix : mAppSearchImpl.getPrefixesLocked()) {
+            if (prefix.equals(VISIBILITY_STORE_PREFIX)) {
+                // Our own prefix. Skip
                 continue;
             }
 
             try {
-                // Note: We use the other clients' database names as uris
+                // Note: We use the other clients' prefixed names as uris
                 GenericDocument document = mAppSearchImpl.getDocument(
-                        DATABASE_NAME, NAMESPACE, /*uri=*/ addUriPrefix(database));
+                        PACKAGE_NAME, DATABASE_NAME, NAMESPACE, /*uri=*/ addUriPrefix(prefix));
 
                 String[] schemas = document.getPropertyStringArray(
                         NOT_PLATFORM_SURFACEABLE_PROPERTY);
-                mNotPlatformSurfaceableMap.put(database,
+                mNotPlatformSurfaceableMap.put(prefix,
                         new ArraySet<>(Arrays.asList(schemas)));
             } catch (AppSearchException e) {
                 if (e.getResultCode() == AppSearchResult.RESULT_NOT_FOUND) {
@@ -143,51 +164,45 @@
     }
 
     /**
-     * Sets visibility settings for {@code databaseName}. Any previous visibility settings will be
+     * Sets visibility settings for {@code prefix}. Any previous visibility settings will be
      * overwritten.
      *
-     * @param databaseName                  Database name that owns the {@code
+     * @param prefix                        Prefix that identifies who owns the {@code
      *                                      schemasNotPlatformSurfaceable}.
-     * @param schemasNotPlatformSurfaceable Set of database-qualified schemas that should be
-     *                                      hidden from
-     *                                      the platform.
+     * @param schemasNotPlatformSurfaceable Set of prefixed schemas that should be
+     *                                      hidden from the platform.
      * @throws AppSearchException on AppSearchImpl error.
      */
-    public void setVisibility(@NonNull String databaseName,
+    public void setVisibility(@NonNull String prefix,
             @NonNull Set<String> schemasNotPlatformSurfaceable) throws AppSearchException {
-        Preconditions.checkNotNull(databaseName);
+        Preconditions.checkNotNull(prefix);
         Preconditions.checkNotNull(schemasNotPlatformSurfaceable);
 
         // Persist the document
         GenericDocument.Builder visibilityDocument = new GenericDocument.Builder(
-                /*uri=*/ addUriPrefix(databaseName), SCHEMA_TYPE)
+                /*uri=*/ addUriPrefix(prefix), SCHEMA_TYPE)
                 .setNamespace(NAMESPACE);
         if (!schemasNotPlatformSurfaceable.isEmpty()) {
             visibilityDocument.setPropertyString(NOT_PLATFORM_SURFACEABLE_PROPERTY,
                     schemasNotPlatformSurfaceable.toArray(new String[0]));
         }
-        mAppSearchImpl.putDocument(DATABASE_NAME, visibilityDocument.build());
+        mAppSearchImpl.putDocument(PACKAGE_NAME, DATABASE_NAME, visibilityDocument.build());
 
         // Update derived data structures.
-        mNotPlatformSurfaceableMap.put(databaseName, schemasNotPlatformSurfaceable);
+        mNotPlatformSurfaceableMap.put(prefix, schemasNotPlatformSurfaceable);
     }
 
-    /**
-     * Returns the set of database-qualified schemas in {@code databaseName} that are hidden from
-     * the platform.
-     *
-     * @param databaseName Database name to retrieve schemas for
-     * @return Set of database-qualified schemas that are hidden from the platform. Empty set if
-     * none exist.
-     */
+    /** Returns if the schema is surfaceable by the platform. */
     @NonNull
-    public Set<String> getSchemasNotPlatformSurfaceable(@NonNull String databaseName) {
-        Preconditions.checkNotNull(databaseName);
-        Set<String> schemasNotPlatformSurfaceable = mNotPlatformSurfaceableMap.get(databaseName);
-        if (schemasNotPlatformSurfaceable == null) {
-            return Collections.emptySet();
+    public boolean isSchemaPlatformSurfaceable(@NonNull String prefix,
+            @NonNull String prefixedSchema) {
+        Preconditions.checkNotNull(prefix);
+        Preconditions.checkNotNull(prefixedSchema);
+        Set<String> notPlatformSurfaceableSchemas = mNotPlatformSurfaceableMap.get(prefix);
+        if (notPlatformSurfaceableSchemas == null) {
+            return true;
         }
-        return schemasNotPlatformSurfaceable;
+        return !notPlatformSurfaceableSchemas.contains(prefixedSchema);
     }
 
     /**