Merge "Fix minor error for export to framework" into androidx-main
diff --git a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/GenericDocumentToProtoConverterTest.java b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/GenericDocumentToProtoConverterTest.java
index cf41477..94d80452 100644
--- a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/GenericDocumentToProtoConverterTest.java
+++ b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/GenericDocumentToProtoConverterTest.java
@@ -20,11 +20,16 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
import androidx.appsearch.app.AppSearchBlobHandle;
import androidx.appsearch.app.EmbeddingVector;
import androidx.appsearch.app.GenericDocument;
+import androidx.appsearch.flags.Flags;
import androidx.appsearch.localstorage.AppSearchConfigImpl;
import androidx.appsearch.localstorage.LocalStorageIcingOptionsConfig;
+import androidx.appsearch.localstorage.SchemaCache;
import androidx.appsearch.localstorage.UnlimitedLimitConfig;
import androidx.appsearch.localstorage.util.PrefixUtil;
@@ -64,16 +69,17 @@
"namespace", "sDocumentProperties2", SCHEMA_TYPE_2)
.setCreationTimestampMillis(6789L)
.build();
+ private static final String PREFIX = "package$databaseName/";
private static final SchemaTypeConfigProto SCHEMA_PROTO_1 = SchemaTypeConfigProto.newBuilder()
- .setSchemaType(SCHEMA_TYPE_1)
+ .setSchemaType(PREFIX + SCHEMA_TYPE_1)
.build();
private static final SchemaTypeConfigProto SCHEMA_PROTO_2 = SchemaTypeConfigProto.newBuilder()
- .setSchemaType(SCHEMA_TYPE_2)
+ .setSchemaType(PREFIX + SCHEMA_TYPE_2)
.build();
- private static final String PREFIX = "package$databaseName/";
- private static final Map<String, SchemaTypeConfigProto> SCHEMA_MAP =
- ImmutableMap.of(PREFIX + SCHEMA_TYPE_1, SCHEMA_PROTO_1, PREFIX + SCHEMA_TYPE_2,
- SCHEMA_PROTO_2);
+ private static final Map<String, Map<String, SchemaTypeConfigProto>> SCHEMA_MAP =
+ ImmutableMap.of(PREFIX, ImmutableMap.of(
+ PREFIX + SCHEMA_TYPE_1, SCHEMA_PROTO_1,
+ PREFIX + SCHEMA_TYPE_2, SCHEMA_PROTO_2));
@Test
public void testDocumentProtoConvert() throws Exception {
@@ -128,7 +134,8 @@
GenericDocument convertedGenericDocument =
GenericDocumentToProtoConverter.toGenericDocument(documentProto, PREFIX,
- SCHEMA_MAP, new AppSearchConfigImpl(new UnlimitedLimitConfig(),
+ new SchemaCache(SCHEMA_MAP),
+ new AppSearchConfigImpl(new UnlimitedLimitConfig(),
new LocalStorageIcingOptionsConfig()));
DocumentProto convertedDocumentProto =
GenericDocumentToProtoConverter.toDocumentProto(document);
@@ -220,13 +227,15 @@
.addProperties(emptyDocumentListProperty)
.setSchemaType(PREFIX + SCHEMA_TYPE_1)
.build();
- Map<String, SchemaTypeConfigProto> schemaMap =
- ImmutableMap.of(PREFIX + SCHEMA_TYPE_1, schemaTypeConfigProto);
+ Map<String, Map<String, SchemaTypeConfigProto>> schemaMap =
+ ImmutableMap.of(PREFIX,
+ ImmutableMap.of(PREFIX + SCHEMA_TYPE_1, schemaTypeConfigProto));
// Convert to the other type and check if they are matched.
GenericDocument convertedGenericDocument =
GenericDocumentToProtoConverter.toGenericDocument(documentProto, PREFIX,
- schemaMap, new AppSearchConfigImpl(new UnlimitedLimitConfig(),
+ new SchemaCache(schemaMap),
+ new AppSearchConfigImpl(new UnlimitedLimitConfig(),
new LocalStorageIcingOptionsConfig()));
DocumentProto convertedDocumentProto =
GenericDocumentToProtoConverter.toDocumentProto(document);
@@ -348,14 +357,16 @@
.addProperties(nestedDocumentProperty)
.setSchemaType(PREFIX + SCHEMA_TYPE_2)
.build();
- Map<String, SchemaTypeConfigProto> schemaMap =
- ImmutableMap.of(PREFIX + SCHEMA_TYPE_1, nestedSchemaTypeConfigProto,
- PREFIX + SCHEMA_TYPE_2, outerSchemaTypeConfigProto);
+ Map<String, Map<String, SchemaTypeConfigProto>> schemaMap =
+ ImmutableMap.of(PREFIX,
+ ImmutableMap.of(PREFIX + SCHEMA_TYPE_1, nestedSchemaTypeConfigProto,
+ PREFIX + SCHEMA_TYPE_2, outerSchemaTypeConfigProto));
// Convert to the other type and check if they are matched.
GenericDocument convertedGenericDocument =
GenericDocumentToProtoConverter.toGenericDocument(outerDocumentProto, PREFIX,
- schemaMap, new AppSearchConfigImpl(new UnlimitedLimitConfig(),
+ new SchemaCache(schemaMap),
+ new AppSearchConfigImpl(new UnlimitedLimitConfig(),
new LocalStorageIcingOptionsConfig()));
DocumentProto convertedDocumentProto =
GenericDocumentToProtoConverter.toDocumentProto(outerDocument);
@@ -365,15 +376,18 @@
// @exportToFramework:startStrip()
// TODO(b/274157614): setParentTypes is hidden
@Test
+ @SuppressWarnings("deprecation")
public void testConvertDocument_withParentTypes() throws Exception {
+ assumeFalse(Flags.enableSearchResultParentTypes());
// Create a type with a parent type.
SchemaTypeConfigProto schemaProto1 = SchemaTypeConfigProto.newBuilder()
.setSchemaType(PREFIX + SCHEMA_TYPE_1)
.addParentTypes(PREFIX + SCHEMA_TYPE_2)
.build();
- Map<String, SchemaTypeConfigProto> schemaMap =
- ImmutableMap.of(PREFIX + SCHEMA_TYPE_1, schemaProto1, PREFIX + SCHEMA_TYPE_2,
- SCHEMA_PROTO_2);
+ Map<String, Map<String, SchemaTypeConfigProto>> schemaMap =
+ ImmutableMap.of(PREFIX, ImmutableMap.of(
+ PREFIX + SCHEMA_TYPE_1, schemaProto1,
+ PREFIX + SCHEMA_TYPE_2, SCHEMA_PROTO_2));
// Create a document proto for the above type.
DocumentProto.Builder documentProtoBuilder = DocumentProto.newBuilder()
@@ -418,13 +432,15 @@
GenericDocument actualDocWithParentAsMetaField =
GenericDocumentToProtoConverter.toGenericDocument(documentProto, PREFIX,
- schemaMap, new AppSearchConfigImpl(new UnlimitedLimitConfig(),
+ new SchemaCache(schemaMap),
+ new AppSearchConfigImpl(new UnlimitedLimitConfig(),
new LocalStorageIcingOptionsConfig(),
/* storeParentInfoAsSyntheticProperty= */ false,
/* shouldRetrieveParentInfo= */ true));
GenericDocument actualDocWithParentAsSyntheticProperty =
GenericDocumentToProtoConverter.toGenericDocument(documentProto, PREFIX,
- schemaMap, new AppSearchConfigImpl(new UnlimitedLimitConfig(),
+ new SchemaCache(schemaMap),
+ new AppSearchConfigImpl(new UnlimitedLimitConfig(),
new LocalStorageIcingOptionsConfig(),
/* storeParentInfoAsSyntheticProperty= */ true,
/* shouldRetrieveParentInfo= */ true));
@@ -441,6 +457,67 @@
// @exportToFramework:endStrip()
@Test
+ public void testConvertDocument_withoutParentTypes() throws Exception {
+ assumeTrue(Flags.enableSearchResultParentTypes());
+ // Create a type with a parent type.
+ SchemaTypeConfigProto schemaProto1 = SchemaTypeConfigProto.newBuilder()
+ .setSchemaType(PREFIX + SCHEMA_TYPE_1)
+ .addParentTypes(PREFIX + SCHEMA_TYPE_2)
+ .build();
+ Map<String, Map<String, SchemaTypeConfigProto>> schemaMap =
+ ImmutableMap.of(PREFIX, ImmutableMap.of(
+ PREFIX + SCHEMA_TYPE_1, schemaProto1,
+ PREFIX + SCHEMA_TYPE_2, SCHEMA_PROTO_2));
+
+ // Create a document proto for the above type.
+ DocumentProto.Builder documentProtoBuilder = DocumentProto.newBuilder()
+ .setUri("id1")
+ .setSchema(SCHEMA_TYPE_1)
+ .setCreationTimestampMs(5L)
+ .setScore(1)
+ .setTtlMs(1L)
+ .setNamespace("namespace");
+ HashMap<String, PropertyProto.Builder> propertyProtoMap = new HashMap<>();
+ propertyProtoMap.put("longKey1",
+ PropertyProto.newBuilder().setName("longKey1").addInt64Values(1L));
+ propertyProtoMap.put("doubleKey1",
+ PropertyProto.newBuilder().setName("doubleKey1").addDoubleValues(1.0));
+ for (Map.Entry<String, PropertyProto.Builder> entry : propertyProtoMap.entrySet()) {
+ documentProtoBuilder.addProperties(entry.getValue());
+ }
+ DocumentProto documentProto = documentProtoBuilder.build();
+
+ // Check that the parent types list is not wrapped anywhere in GenericDocument, neither
+ // as a synthetic property nor as a meta field, since Flags.enableSearchResultParentTypes()
+ // is true.
+ GenericDocument expectedDoc =
+ new GenericDocument.Builder<GenericDocument.Builder<?>>("namespace", "id1",
+ SCHEMA_TYPE_1)
+ .setCreationTimestampMillis(5L)
+ .setScore(1)
+ .setTtlMillis(1L)
+ .setPropertyLong("longKey1", 1L)
+ .setPropertyDouble("doubleKey1", 1.0)
+ .build();
+ GenericDocument actualDoc1 =
+ GenericDocumentToProtoConverter.toGenericDocument(documentProto, PREFIX,
+ new SchemaCache(schemaMap),
+ new AppSearchConfigImpl(new UnlimitedLimitConfig(),
+ new LocalStorageIcingOptionsConfig(),
+ /* storeParentInfoAsSyntheticProperty= */ false,
+ /* shouldRetrieveParentInfo= */ true));
+ GenericDocument actualDoc2 =
+ GenericDocumentToProtoConverter.toGenericDocument(documentProto, PREFIX,
+ new SchemaCache(schemaMap),
+ new AppSearchConfigImpl(new UnlimitedLimitConfig(),
+ new LocalStorageIcingOptionsConfig(),
+ /* storeParentInfoAsSyntheticProperty= */ true,
+ /* shouldRetrieveParentInfo= */ true));
+ assertThat(actualDoc1).isEqualTo(expectedDoc);
+ assertThat(actualDoc2).isEqualTo(expectedDoc);
+ }
+
+ @Test
public void testDocumentProtoConvert_EmbeddingProperty() throws Exception {
GenericDocument document =
new GenericDocument.Builder<GenericDocument.Builder<?>>("namespace", "id1",
@@ -486,7 +563,8 @@
GenericDocument convertedGenericDocument =
GenericDocumentToProtoConverter.toGenericDocument(documentProto, PREFIX,
- SCHEMA_MAP, new AppSearchConfigImpl(new UnlimitedLimitConfig(),
+ new SchemaCache(SCHEMA_MAP),
+ new AppSearchConfigImpl(new UnlimitedLimitConfig(),
new LocalStorageIcingOptionsConfig()));
DocumentProto convertedDocumentProto =
GenericDocumentToProtoConverter.toDocumentProto(document);
@@ -554,7 +632,8 @@
GenericDocument convertedGenericDocument =
GenericDocumentToProtoConverter.toGenericDocument(documentProto, PREFIX,
- SCHEMA_MAP, new AppSearchConfigImpl(new UnlimitedLimitConfig(),
+ new SchemaCache(SCHEMA_MAP),
+ new AppSearchConfigImpl(new UnlimitedLimitConfig(),
new LocalStorageIcingOptionsConfig()));
DocumentProto convertedDocumentProto =
GenericDocumentToProtoConverter.toDocumentProto(document);
diff --git a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SearchResultToProtoConverterTest.java b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SearchResultToProtoConverterTest.java
index 9ce2a32..2560643 100644
--- a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SearchResultToProtoConverterTest.java
+++ b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SearchResultToProtoConverterTest.java
@@ -25,6 +25,7 @@
import androidx.appsearch.app.SearchResult;
import androidx.appsearch.app.SearchResultPage;
import androidx.appsearch.exceptions.AppSearchException;
+import androidx.appsearch.flags.Flags;
import androidx.appsearch.localstorage.AppSearchConfigImpl;
import androidx.appsearch.localstorage.LocalStorageIcingOptionsConfig;
import androidx.appsearch.localstorage.SchemaCache;
@@ -34,6 +35,7 @@
import com.google.android.icing.proto.DocumentProto;
import com.google.android.icing.proto.SchemaTypeConfigProto;
import com.google.android.icing.proto.SearchResultProto;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import org.junit.Test;
@@ -42,27 +44,34 @@
public class SearchResultToProtoConverterTest {
@Test
+ @SuppressWarnings("deprecation")
public void testToSearchResultProto() throws Exception {
final String prefix =
"com.package.foo" + PrefixUtil.PACKAGE_DELIMITER + "databaseName"
+ PrefixUtil.DATABASE_DELIMITER;
final String id = "id";
final String namespace = prefix + "namespace";
- final String schemaType = prefix + "schema";
- final AppSearchConfigImpl config = new AppSearchConfigImpl(new UnlimitedLimitConfig(),
- new LocalStorageIcingOptionsConfig());
+ String schemaType = "schema";
+ String parentSchemaType = "parentSchema";
+ final String prefixedSchemaType = prefix + schemaType;
+ final String prefixedParentSchemaType = prefix + parentSchemaType;
+ final AppSearchConfigImpl config = new AppSearchConfigImpl(
+ new UnlimitedLimitConfig(),
+ new LocalStorageIcingOptionsConfig(),
+ /* storeParentInfoAsSyntheticProperty= */ false,
+ /* shouldRetrieveParentInfo= */ true);
// Building the SearchResult received from query.
DocumentProto.Builder documentProtoBuilder = DocumentProto.newBuilder()
.setUri(id)
.setNamespace(namespace)
- .setSchema(schemaType);
+ .setSchema(prefixedSchemaType);
// A joined document
DocumentProto.Builder joinedDocProtoBuilder = DocumentProto.newBuilder()
.setUri("id2")
.setNamespace(namespace)
- .setSchema(schemaType);
+ .setSchema(prefixedSchemaType);
SearchResultProto.ResultProto joinedResultProto = SearchResultProto.ResultProto.newBuilder()
.setDocument(joinedDocProtoBuilder).build();
@@ -75,31 +84,47 @@
SearchResultProto searchResultProto = SearchResultProto.newBuilder()
.addResults(resultProto).build();
+ SchemaTypeConfigProto parentSchemaTypeConfigProto =
+ SchemaTypeConfigProto.newBuilder()
+ .setSchemaType(prefixedParentSchemaType)
+ .build();
SchemaTypeConfigProto schemaTypeConfigProto =
SchemaTypeConfigProto.newBuilder()
- .setSchemaType(schemaType)
+ .addParentTypes(prefixedParentSchemaType)
+ .setSchemaType(prefixedSchemaType)
.build();
Map<String, Map<String, SchemaTypeConfigProto>> schemaMap = ImmutableMap.of(prefix,
- ImmutableMap.of(schemaType, schemaTypeConfigProto));
+ ImmutableMap.of(
+ prefixedSchemaType, schemaTypeConfigProto,
+ prefixedParentSchemaType, parentSchemaTypeConfigProto
+ ));
+ SchemaCache schemaCache = new SchemaCache(schemaMap);
removePrefixesFromDocument(documentProtoBuilder);
removePrefixesFromDocument(joinedDocProtoBuilder);
SearchResultPage searchResultPage = SearchResultToProtoConverter.toSearchResultPage(
- searchResultProto, new SchemaCache(schemaMap), config);
+ searchResultProto, schemaCache, config);
assertThat(searchResultPage.getResults()).hasSize(1);
SearchResult result = searchResultPage.getResults().get(0);
assertThat(result.getPackageName()).isEqualTo("com.package.foo");
assertThat(result.getDatabaseName()).isEqualTo("databaseName");
assertThat(result.getGenericDocument()).isEqualTo(
GenericDocumentToProtoConverter.toGenericDocument(
- documentProtoBuilder.build(), prefix, schemaMap.get(prefix),
- config));
+ documentProtoBuilder.build(), prefix, schemaCache, config));
assertThat(result.getJoinedResults()).hasSize(1);
assertThat(result.getJoinedResults().get(0).getGenericDocument()).isEqualTo(
GenericDocumentToProtoConverter.toGenericDocument(
- joinedDocProtoBuilder.build(), prefix, schemaMap.get(prefix),
- config));
+ joinedDocProtoBuilder.build(), prefix, schemaCache, config));
+
+ if (Flags.enableSearchResultParentTypes()) {
+ assertThat(result.getParentTypeMap()).isEqualTo(
+ ImmutableMap.of(schemaType, ImmutableList.of(parentSchemaType)));
+ assertThat(result.getGenericDocument().getParentTypes()).isNull();
+ } else {
+ assertThat(result.getParentTypeMap()).isEmpty();
+ assertThat(result.getGenericDocument().getParentTypes()).contains(parentSchemaType);
+ }
}
@Test
diff --git a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SearchSpecToProtoConverterTest.java b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SearchSpecToProtoConverterTest.java
index 371e979..afc4566 100644
--- a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SearchSpecToProtoConverterTest.java
+++ b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SearchSpecToProtoConverterTest.java
@@ -294,7 +294,7 @@
}
@Test
- public void testToScoringSpecProto() {
+ public void testToScoringSpecProto() throws Exception {
String prefix = PrefixUtil.createPrefix("package", "database1");
String schemaType = "schemaType";
String namespace = "namespace";
@@ -328,7 +328,7 @@
}
@Test
- public void testGenerateScoringSpecProtoWhenScorableRankingIsEnabled() {
+ public void testGenerateScoringSpecProtoWhenScorableRankingIsEnabled() throws Exception {
String prefix1 = PrefixUtil.createPrefix("package1", "database2");
String prefix2 = PrefixUtil.createPrefix("package2", "database1");
String gmailSchemaType = "gmail";
@@ -508,7 +508,7 @@
}
@Test
- public void testToResultSpecProto_projection_withJoinSpec_packageFilter() {
+ public void testToResultSpecProto_projection_withJoinSpec_packageFilter() throws Exception {
String personPrefix = PrefixUtil.createPrefix("contacts", "database");
String actionPrefix = PrefixUtil.createPrefix("aiai", "database");
@@ -618,7 +618,8 @@
}
@Test
- public void testToResultSpecProto_projection_removeSchemaWithoutParentInFilter() {
+ public void testToResultSpecProto_projection_removeSchemaWithoutParentInFilter()
+ throws Exception {
SearchSpec searchSpec = new SearchSpec.Builder()
.addFilterSchemas("Person")
.addProjection("Artist", ImmutableList.of("name"))
@@ -671,7 +672,7 @@
}
@Test
- public void testToSearchSpecProto_propertyFilter_withJoinSpec_packageFilter() {
+ public void testToSearchSpecProto_propertyFilter_withJoinSpec_packageFilter() throws Exception {
String personPrefix = PrefixUtil.createPrefix("contacts", "database");
String actionPrefix = PrefixUtil.createPrefix("aiai", "database");
@@ -748,7 +749,8 @@
}
@Test
- public void testToSearchSpecProto_propertyFilter_removeSchemaWithoutParentInFilter() {
+ public void testToSearchSpecProto_propertyFilter_removeSchemaWithoutParentInFilter()
+ throws Exception {
SearchSpec searchSpec = new SearchSpec.Builder()
.addFilterSchemas("Person")
.addFilterProperties("Artist", ImmutableList.of("name"))
@@ -1370,7 +1372,7 @@
}
@Test
- public void testGetTargetSchemaFilters_emptySearchingFilter() {
+ public void testGetTargetSchemaFilters_emptySearchingFilter() throws Exception {
SearchSpec searchSpec = new SearchSpec.Builder().build();
String prefix1 = createPrefix("package", "database1");
String prefix2 = createPrefix("package", "database2");
@@ -1398,7 +1400,7 @@
}
@Test
- public void testGetTargetSchemaFilters_searchPartialFilter() {
+ public void testGetTargetSchemaFilters_searchPartialFilter() throws Exception {
SearchSpec searchSpec = new SearchSpec.Builder().build();
String prefix1 = createPrefix("package", "database1");
String prefix2 = createPrefix("package", "database2");
@@ -1426,7 +1428,7 @@
}
@Test
- public void testGetTargetSchemaFilters_intersectionWithSearchingFilter() {
+ public void testGetTargetSchemaFilters_intersectionWithSearchingFilter() throws Exception {
// Put some searching schemas.
SearchSpec searchSpec = new SearchSpec.Builder()
.addFilterSchemas("typeA", "nonExist").build();
@@ -1453,7 +1455,7 @@
}
@Test
- public void testGetTargetSchemaFilters_polymorphismExpansion() {
+ public void testGetTargetSchemaFilters_polymorphismExpansion() throws Exception {
SearchSpec searchSpec = new SearchSpec.Builder()
.addFilterSchemas("Person", "nonExist").build();
String prefix = createPrefix("package", "database");
@@ -1492,7 +1494,7 @@
}
@Test
- public void testGetTargetSchemaFilters_polymorphismExpansion_multipleLevel() {
+ public void testGetTargetSchemaFilters_polymorphismExpansion_multipleLevel() throws Exception {
SearchSpec searchSpec = new SearchSpec.Builder()
.addFilterSchemas("A", "B").build();
String prefix = createPrefix("package", "database");
@@ -1543,7 +1545,7 @@
}
@Test
- public void testGetTargetSchemaFilters_intersectionWithNonExistFilter() {
+ public void testGetTargetSchemaFilters_intersectionWithNonExistFilter() throws Exception {
// Put non-exist searching schema.
SearchSpec searchSpec = new SearchSpec.Builder()
.addFilterSchemas("nonExist").build();
@@ -1611,7 +1613,7 @@
}
@Test
- public void testIsNothingToSearch() {
+ public void testIsNothingToSearch() throws Exception {
String prefix = PrefixUtil.createPrefix("package", "database");
SearchSpec nestedSearchSpec = new SearchSpec.Builder().build();
JoinSpec joinSpec = new JoinSpec.Builder("entity")
@@ -1710,7 +1712,7 @@
}
@Test
- public void testConvertPropertyWeights() {
+ public void testConvertPropertyWeights() throws Exception {
String prefix1 = PrefixUtil.createPrefix("package", "database1");
String prefix2 = PrefixUtil.createPrefix("package", "database2");
String schemaTypeA = "typeA";
@@ -1776,7 +1778,7 @@
}
@Test
- public void testConvertPropertyWeights_whenNoWeightsSet() {
+ public void testConvertPropertyWeights_whenNoWeightsSet() throws Exception {
SearchSpec searchSpec = new SearchSpec.Builder().build();
String prefix1 = PrefixUtil.createPrefix("package", "database1");
SchemaTypeConfigProto schemaTypeConfigProto =
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AlwaysSupportedFeatures.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AlwaysSupportedFeatures.java
index 92d8c6b..444eccd 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AlwaysSupportedFeatures.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AlwaysSupportedFeatures.java
@@ -96,6 +96,8 @@
case Features.SEARCH_SPEC_ADD_FILTER_DOCUMENT_IDS:
// fall through
case Features.SCHEMA_SCORABLE_PROPERTY_CONFIG:
+ // fall through
+ case Features.SEARCH_RESULT_PARENT_TYPES:
return true;
default:
return false;
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchConfig.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchConfig.java
index 0a8b234..0932377 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchConfig.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchConfig.java
@@ -33,7 +33,9 @@
boolean shouldStoreParentInfoAsSyntheticProperty();
/**
- * Whether to include the list of parent types when returning a {@link GenericDocument}.
+ * Whether to include the list of parent types when returning a {@link GenericDocument} or a
+ * {@link androidx.appsearch.app.SearchResult} when
+ * {@link androidx.appsearch.flags.Flags#FLAG_ENABLE_SEARCH_RESULT_PARENT_TYPES} in on.
*/
boolean shouldRetrieveParentInfo();
}
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchImpl.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchImpl.java
index 91c78e4..416d882 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchImpl.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchImpl.java
@@ -395,7 +395,7 @@
}
// Populate schema parent-to-children map
- mSchemaCacheLocked.rebuildSchemaParentToChildrenMap();
+ mSchemaCacheLocked.rebuildCache();
// Populate namespace map
List<String> prefixedNamespaceList =
@@ -822,7 +822,7 @@
mSchemaCacheLocked.removeFromSchemaMap(prefix, schemaType);
}
- mSchemaCacheLocked.rebuildSchemaParentToChildrenMapForPrefix(prefix);
+ mSchemaCacheLocked.rebuildCacheForPrefix(prefix);
// Since the constructor of VisibilityStore will set schema. Avoid call visibility
// store before we have already created it.
@@ -1321,10 +1321,8 @@
DocumentProto.Builder documentBuilder = documentProto.toBuilder();
removePrefixesFromDocument(documentBuilder);
String prefix = createPrefix(packageName, databaseName);
- Map<String, SchemaTypeConfigProto> schemaTypeMap =
- mSchemaCacheLocked.getSchemaMapForPrefix(prefix);
return GenericDocumentToProtoConverter.toGenericDocument(documentBuilder.build(),
- prefix, schemaTypeMap, mConfig);
+ prefix, mSchemaCacheLocked, mConfig);
} finally {
mReadWriteLock.readLock().unlock();
}
@@ -1363,10 +1361,8 @@
// The schema type map cannot be null at this point. It could only be null if no
// schema had ever been set for that prefix. Given we have retrieved a document from
// the index, we know a schema had to have been set.
- Map<String, SchemaTypeConfigProto> schemaTypeMap =
- mSchemaCacheLocked.getSchemaMapForPrefix(prefix);
return GenericDocumentToProtoConverter.toGenericDocument(documentBuilder.build(),
- prefix, schemaTypeMap, mConfig);
+ prefix, mSchemaCacheLocked, mConfig);
} finally {
mReadWriteLock.readLock().unlock();
}
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/SchemaCache.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/SchemaCache.java
index 7138182..a2e5620 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/SchemaCache.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/SchemaCache.java
@@ -18,6 +18,9 @@
import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
+import androidx.appsearch.exceptions.AppSearchException;
+import androidx.appsearch.flags.Flags;
+import androidx.appsearch.localstorage.util.PrefixUtil;
import androidx.collection.ArrayMap;
import androidx.collection.ArraySet;
import androidx.core.util.Preconditions;
@@ -54,12 +57,23 @@
private final Map<String, Map<String, List<String>>> mSchemaParentToChildrenMap =
new ArrayMap<>();
+ /**
+ * A map that contains schema types and all parent schema types for all package-database
+ * prefixes. It maps each package-database prefix to an inner-map. The inner-map maps each
+ * prefixed schema type to its respective list of unprefixed parent schema types including
+ * transitive parents. It's guaranteed that child types always appear before parent types in
+ * the list.
+ */
+ private final Map<String, Map<String, List<String>>>
+ mSchemaChildToTransitiveUnprefixedParentsMap = new ArrayMap<>();
+
public SchemaCache() {
}
- public SchemaCache(@NonNull Map<String, Map<String, SchemaTypeConfigProto>> schemaMap) {
+ public SchemaCache(@NonNull Map<String, Map<String, SchemaTypeConfigProto>> schemaMap)
+ throws AppSearchException {
mSchemaMap.putAll(Preconditions.checkNotNull(schemaMap));
- rebuildSchemaParentToChildrenMap();
+ rebuildCache();
}
/**
@@ -132,17 +146,47 @@
}
/**
- * Rebuilds the schema parent-to-children map for the given prefix, based on the current
- * schema map.
- *
- * <p>The schema parent-to-children map is required to be updated when
- * {@link #addToSchemaMap} or {@link #removeFromSchemaMap} has been called. Otherwise, the
- * results from {@link #getSchemaTypesWithDescendants} would be stale.
+ * Returns the unprefixed parent schema types, including transitive parents, for the given
+ * prefixed schema type, based on the schema child-to-parents map held in the cache. It's
+ * guaranteed that child types always appear before parent types in the list.
*/
- public void rebuildSchemaParentToChildrenMapForPrefix(@NonNull String prefix) {
+ @NonNull
+ public List<String> getTransitiveUnprefixedParentSchemaTypes(@NonNull String prefix,
+ @NonNull String prefixedSchemaType) throws AppSearchException {
+ Preconditions.checkNotNull(prefix);
+ Preconditions.checkNotNull(prefixedSchemaType);
+
+ // If the flag is on, retrieve the parent types from the cache as it is available.
+ // Otherwise, recalculate the parent types.
+ if (Flags.enableSearchResultParentTypes()) {
+ Map<String, List<String>> unprefixedChildToParentsMap =
+ mSchemaChildToTransitiveUnprefixedParentsMap.get(prefix);
+ if (unprefixedChildToParentsMap == null) {
+ return Collections.emptyList();
+ }
+ List<String> parents = unprefixedChildToParentsMap.get(prefixedSchemaType);
+ return parents == null ? Collections.emptyList() : parents;
+ } else {
+ return calculateTransitiveUnprefixedParentSchemaTypes(prefixedSchemaType,
+ getSchemaMapForPrefix(prefix));
+ }
+ }
+
+ /**
+ * Rebuilds the schema parent-to-children and child-to-parents maps for the given prefix,
+ * based on the current schema map.
+ *
+ * <p>The schema parent-to-children and child-to-parents maps must be updated when
+ * {@link #addToSchemaMap} or {@link #removeFromSchemaMap} has been called. Otherwise, the
+ * results from {@link #getSchemaTypesWithDescendants} and
+ * {@link #getTransitiveUnprefixedParentSchemaTypes} would be stale.
+ */
+ public void rebuildCacheForPrefix(@NonNull String prefix)
+ throws AppSearchException {
Preconditions.checkNotNull(prefix);
mSchemaParentToChildrenMap.remove(prefix);
+ mSchemaChildToTransitiveUnprefixedParentsMap.remove(prefix);
Map<String, SchemaTypeConfigProto> prefixedSchemaMap = mSchemaMap.get(prefix);
if (prefixedSchemaMap == null) {
return;
@@ -161,34 +205,56 @@
children.add(childSchemaConfig.getSchemaType());
}
}
-
// Record the map for the current prefix.
if (!parentToChildrenMap.isEmpty()) {
mSchemaParentToChildrenMap.put(prefix, parentToChildrenMap);
}
+
+ // If the flag is on, build the child-to-parent maps as caches. Otherwise, this
+ // information will have to be recalculated when needed.
+ if (Flags.enableSearchResultParentTypes()) {
+ // Build the child-to-parents maps for the current prefix.
+ Map<String, List<String>> childToTransitiveUnprefixedParentsMap = new ArrayMap<>();
+ for (SchemaTypeConfigProto childSchemaConfig : prefixedSchemaMap.values()) {
+ if (childSchemaConfig.getParentTypesCount() > 0) {
+ childToTransitiveUnprefixedParentsMap.put(
+ childSchemaConfig.getSchemaType(),
+ calculateTransitiveUnprefixedParentSchemaTypes(
+ childSchemaConfig.getSchemaType(),
+ prefixedSchemaMap));
+ }
+ }
+ // Record the map for the current prefix.
+ if (!childToTransitiveUnprefixedParentsMap.isEmpty()) {
+ mSchemaChildToTransitiveUnprefixedParentsMap.put(prefix,
+ childToTransitiveUnprefixedParentsMap);
+ }
+ }
}
/**
- * Rebuilds the schema parent-to-children map based on the current schema map.
+ * Rebuilds the schema parent-to-children and child-to-parents maps based on the current
+ * schema map.
*
- * <p>The schema parent-to-children map is required to be updated when
+ * <p>The schema parent-to-children and child-to-parents maps must be updated when
* {@link #addToSchemaMap} or {@link #removeFromSchemaMap} has been called. Otherwise, the
- * results from {@link #getSchemaTypesWithDescendants} would be stale.
+ * results from {@link #getSchemaTypesWithDescendants} and
+ * {@link #getTransitiveUnprefixedParentSchemaTypes} would be stale.
*/
- public void rebuildSchemaParentToChildrenMap() {
+ public void rebuildCache() throws AppSearchException {
mSchemaParentToChildrenMap.clear();
+ mSchemaChildToTransitiveUnprefixedParentsMap.clear();
for (String prefix : mSchemaMap.keySet()) {
- rebuildSchemaParentToChildrenMapForPrefix(prefix);
+ rebuildCacheForPrefix(prefix);
}
}
/**
* Adds a schema to the schema map.
*
- * <p>Note that this method will invalidate the schema parent-to-children map in the cache,
- * and either {@link #rebuildSchemaParentToChildrenMap} or
- * {@link #rebuildSchemaParentToChildrenMapForPrefix} is required to be called to update the
- * cache.
+ * <p>Note that this method will invalidate the schema parent-to-children and
+ * child-to-parents maps in the cache, and either {@link #rebuildCache} or
+ * {@link #rebuildCacheForPrefix} is required to be called to update the cache.
*/
public void addToSchemaMap(@NonNull String prefix,
@NonNull SchemaTypeConfigProto schemaTypeConfigProto) {
@@ -206,10 +272,9 @@
/**
* Removes a schema from the schema map.
*
- * <p>Note that this method will invalidate the schema parent-to-children map in the cache,
- * and either {@link #rebuildSchemaParentToChildrenMap} or
- * {@link #rebuildSchemaParentToChildrenMapForPrefix} is required to be called to update the
- * cache.
+ * <p>Note that this method will invalidate the schema parent-to-children and
+ * child-to-parents maps in the cache, and either {@link #rebuildCache} or
+ * {@link #rebuildCacheForPrefix} is required to be called to update the cache.
*/
public void removeFromSchemaMap(@NonNull String prefix, @NonNull String schemaType) {
Preconditions.checkNotNull(prefix);
@@ -222,8 +287,8 @@
}
/**
- * Removes the entry of the given prefix from both the schema map and the schema
- * parent-to-children map, and returns the set of removed prefixed schema type.
+ * Removes the entry of the given prefix from the schema map, the schema parent-to-children
+ * map and the child-to-parents map, and returns the set of removed prefixed schema type.
*/
@NonNull
public Set<String> removePrefix(@NonNull String prefix) {
@@ -232,6 +297,7 @@
Map<String, SchemaTypeConfigProto> removedSchemas =
Preconditions.checkNotNull(mSchemaMap.remove(prefix));
mSchemaParentToChildrenMap.remove(prefix);
+ mSchemaChildToTransitiveUnprefixedParentsMap.remove(prefix);
return removedSchemas.keySet();
}
@@ -241,5 +307,65 @@
public void clear() {
mSchemaMap.clear();
mSchemaParentToChildrenMap.clear();
+ mSchemaChildToTransitiveUnprefixedParentsMap.clear();
+ }
+
+ /**
+ * Get the list of unprefixed transitive parent type names of {@code prefixedSchemaType}.
+ *
+ * <p>It's guaranteed that child types always appear before parent types in the list.
+ */
+ @NonNull
+ private List<String> calculateTransitiveUnprefixedParentSchemaTypes(
+ @NonNull String prefixedSchemaType,
+ @NonNull Map<String, SchemaTypeConfigProto> prefixedSchemaMap)
+ throws AppSearchException {
+ // Please note that neither DFS nor BFS order is guaranteed to always put child types
+ // before parent types (due to the diamond problem), so a topological sorting algorithm
+ // is required.
+ Map<String, Integer> inDegreeMap = new ArrayMap<>();
+ collectParentTypeInDegrees(prefixedSchemaType, prefixedSchemaMap,
+ /* visited= */new ArraySet<>(), inDegreeMap);
+
+ List<String> result = new ArrayList<>();
+ Queue<String> queue = new ArrayDeque<>();
+ // prefixedSchemaType is the only type that has zero in-degree at this point.
+ queue.add(prefixedSchemaType);
+ while (!queue.isEmpty()) {
+ SchemaTypeConfigProto currentSchema = Preconditions.checkNotNull(
+ prefixedSchemaMap.get(queue.poll()));
+ for (int i = 0; i < currentSchema.getParentTypesCount(); ++i) {
+ String prefixedParentType = currentSchema.getParentTypes(i);
+ int parentInDegree =
+ Preconditions.checkNotNull(inDegreeMap.get(prefixedParentType)) - 1;
+ inDegreeMap.put(prefixedParentType, parentInDegree);
+ if (parentInDegree == 0) {
+ result.add(PrefixUtil.removePrefix(prefixedParentType));
+ queue.add(prefixedParentType);
+ }
+ }
+ }
+ return result;
+ }
+
+ private void collectParentTypeInDegrees(
+ @NonNull String prefixedSchemaType,
+ @NonNull Map<String, SchemaTypeConfigProto> schemaTypeMap,
+ @NonNull Set<String> visited, @NonNull Map<String, Integer> inDegreeMap) {
+ if (visited.contains(prefixedSchemaType)) {
+ return;
+ }
+ visited.add(prefixedSchemaType);
+ SchemaTypeConfigProto schema =
+ Preconditions.checkNotNull(schemaTypeMap.get(prefixedSchemaType));
+ for (int i = 0; i < schema.getParentTypesCount(); ++i) {
+ String prefixedParentType = schema.getParentTypes(i);
+ Integer parentInDegree = inDegreeMap.get(prefixedParentType);
+ if (parentInDegree == null) {
+ parentInDegree = 0;
+ }
+ inDegreeMap.put(prefixedParentType, parentInDegree + 1);
+ collectParentTypeInDegrees(prefixedParentType, schemaTypeMap, visited, inDegreeMap);
+ }
}
}
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/GenericDocumentToProtoConverter.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/GenericDocumentToProtoConverter.java
index 726e5d4..67cfbf0 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/GenericDocumentToProtoConverter.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/GenericDocumentToProtoConverter.java
@@ -25,10 +25,9 @@
import androidx.appsearch.app.ExperimentalAppSearchApi;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
+import androidx.appsearch.flags.Flags;
import androidx.appsearch.localstorage.AppSearchConfig;
-import androidx.appsearch.localstorage.util.PrefixUtil;
-import androidx.collection.ArrayMap;
-import androidx.collection.ArraySet;
+import androidx.appsearch.localstorage.SchemaCache;
import androidx.core.util.Preconditions;
import com.google.android.icing.proto.DocumentProto;
@@ -37,13 +36,10 @@
import com.google.android.icing.proto.SchemaTypeConfigProto;
import com.google.android.icing.protobuf.ByteString;
-import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
-import java.util.Queue;
-import java.util.Set;
/**
* Translates a {@link GenericDocument} into a {@link DocumentProto}.
@@ -153,17 +149,22 @@
* document proto should have its package + database prefix stripped
* from its fields.
* @param prefix the package + database prefix used searching the {@code schemaTypeMap}.
- * @param schemaTypeMap map of prefixed schema type to {@link SchemaTypeConfigProto}, used
- * for looking up the default empty value to set for a document property
- * that has all empty values.
+ * @param schemaCache The SchemaCache instance held in AppSearch.
*/
@NonNull
+ @SuppressWarnings("deprecation")
@OptIn(markerClass = ExperimentalAppSearchApi.class)
public static GenericDocument toGenericDocument(@NonNull DocumentProtoOrBuilder proto,
@NonNull String prefix,
- @NonNull Map<String, SchemaTypeConfigProto> schemaTypeMap,
+ @NonNull SchemaCache schemaCache,
@NonNull AppSearchConfig config) throws AppSearchException {
Preconditions.checkNotNull(proto);
+ Preconditions.checkNotNull(prefix);
+ Preconditions.checkNotNull(schemaCache);
+ Preconditions.checkNotNull(config);
+ Map<String, SchemaTypeConfigProto> schemaTypeMap =
+ schemaCache.getSchemaMapForPrefix(prefix);
+
GenericDocument.Builder<?> documentBuilder =
new GenericDocument.Builder<>(proto.getNamespace(), proto.getUri(),
proto.getSchema())
@@ -171,9 +172,10 @@
.setTtlMillis(proto.getTtlMs())
.setCreationTimestampMillis(proto.getCreationTimestampMs());
String prefixedSchemaType = prefix + proto.getSchema();
- if (config.shouldRetrieveParentInfo()) {
+ if (config.shouldRetrieveParentInfo() && !Flags.enableSearchResultParentTypes()) {
List<String> parentSchemaTypes =
- getUnprefixedParentSchemaTypes(prefixedSchemaType, schemaTypeMap);
+ schemaCache.getTransitiveUnprefixedParentSchemaTypes(
+ prefix, prefixedSchemaType);
if (!parentSchemaTypes.isEmpty()) {
if (config.shouldStoreParentInfoAsSyntheticProperty()) {
documentBuilder.setPropertyString(
@@ -222,7 +224,7 @@
GenericDocument[] values = new GenericDocument[property.getDocumentValuesCount()];
for (int j = 0; j < values.length; j++) {
values[j] = toGenericDocument(property.getDocumentValues(j), prefix,
- schemaTypeMap, config);
+ schemaCache, config);
}
documentBuilder.setPropertyDocument(name, values);
} else if (property.getVectorValuesCount() > 0) {
@@ -281,66 +283,6 @@
return builder.build();
}
- /**
- * Get the list of unprefixed parent type names of {@code prefixedSchemaType}.
- *
- * <p>It's guaranteed that child types always appear before parent types in the list.
- */
- // TODO(b/290389974): Consider caching the result based prefixedSchemaType, and reset the
- // cache whenever a new setSchema is called.
- @NonNull
- private static List<String> getUnprefixedParentSchemaTypes(
- @NonNull String prefixedSchemaType,
- @NonNull Map<String, SchemaTypeConfigProto> schemaTypeMap) throws AppSearchException {
- // Please note that neither DFS nor BFS order is guaranteed to always put child types
- // before parent types (due to the diamond problem), so a topological sorting algorithm
- // is required.
- Map<String, Integer> inDegreeMap = new ArrayMap<>();
- collectParentTypeInDegrees(prefixedSchemaType, schemaTypeMap,
- /* visited= */new ArraySet<>(), inDegreeMap);
-
- List<String> result = new ArrayList<>();
- Queue<String> queue = new ArrayDeque<>();
- // prefixedSchemaType is the only type that has zero in-degree at this point.
- queue.add(prefixedSchemaType);
- while (!queue.isEmpty()) {
- SchemaTypeConfigProto currentSchema = Preconditions.checkNotNull(
- schemaTypeMap.get(queue.poll()));
- for (int i = 0; i < currentSchema.getParentTypesCount(); ++i) {
- String prefixedParentType = currentSchema.getParentTypes(i);
- int parentInDegree =
- Preconditions.checkNotNull(inDegreeMap.get(prefixedParentType)) - 1;
- inDegreeMap.put(prefixedParentType, parentInDegree);
- if (parentInDegree == 0) {
- result.add(PrefixUtil.removePrefix(prefixedParentType));
- queue.add(prefixedParentType);
- }
- }
- }
- return result;
- }
-
- private static void collectParentTypeInDegrees(
- @NonNull String prefixedSchemaType,
- @NonNull Map<String, SchemaTypeConfigProto> schemaTypeMap,
- @NonNull Set<String> visited, @NonNull Map<String, Integer> inDegreeMap) {
- if (visited.contains(prefixedSchemaType)) {
- return;
- }
- visited.add(prefixedSchemaType);
- SchemaTypeConfigProto schema =
- Preconditions.checkNotNull(schemaTypeMap.get(prefixedSchemaType));
- for (int i = 0; i < schema.getParentTypesCount(); ++i) {
- String prefixedParentType = schema.getParentTypes(i);
- Integer parentInDegree = inDegreeMap.get(prefixedParentType);
- if (parentInDegree == null) {
- parentInDegree = 0;
- }
- inDegreeMap.put(prefixedParentType, parentInDegree + 1);
- collectParentTypeInDegrees(prefixedParentType, schemaTypeMap, visited, inDegreeMap);
- }
- }
-
private static void setEmptyProperty(@NonNull String propertyName,
@NonNull GenericDocument.Builder<?> documentBuilder,
@NonNull SchemaTypeConfigProto schema) {
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/SearchResultToProtoConverter.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/SearchResultToProtoConverter.java
index b88eccd..bae4f74 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/SearchResultToProtoConverter.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/SearchResultToProtoConverter.java
@@ -21,17 +21,22 @@
import static androidx.appsearch.localstorage.util.PrefixUtil.removePrefixesFromDocument;
import androidx.annotation.NonNull;
+import androidx.annotation.OptIn;
import androidx.annotation.RestrictTo;
import androidx.appsearch.app.AppSearchResult;
+import androidx.appsearch.app.ExperimentalAppSearchApi;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.app.SearchResult;
import androidx.appsearch.app.SearchResultPage;
import androidx.appsearch.exceptions.AppSearchException;
+import androidx.appsearch.flags.Flags;
import androidx.appsearch.localstorage.AppSearchConfig;
import androidx.appsearch.localstorage.SchemaCache;
+import androidx.collection.ArrayMap;
import com.google.android.icing.proto.DocumentProto;
-import com.google.android.icing.proto.SchemaTypeConfigProto;
+import com.google.android.icing.proto.DocumentProtoOrBuilder;
+import com.google.android.icing.proto.PropertyProto;
import com.google.android.icing.proto.SearchResultProto;
import com.google.android.icing.proto.SnippetMatchProto;
import com.google.android.icing.proto.SnippetProto;
@@ -79,6 +84,7 @@
* @return A {@link SearchResult}.
*/
@NonNull
+ @OptIn(markerClass = ExperimentalAppSearchApi.class)
private static SearchResult toUnprefixedSearchResult(
@NonNull SearchResultProto.ResultProto proto,
@NonNull SchemaCache schemaCache,
@@ -86,11 +92,9 @@
DocumentProto.Builder documentBuilder = proto.getDocument().toBuilder();
String prefix = removePrefixesFromDocument(documentBuilder);
- Map<String, SchemaTypeConfigProto> schemaTypeMap =
- schemaCache.getSchemaMapForPrefix(prefix);
GenericDocument document =
GenericDocumentToProtoConverter.toGenericDocument(documentBuilder, prefix,
- schemaTypeMap, config);
+ schemaCache, config);
SearchResult.Builder builder =
new SearchResult.Builder(getPackageName(prefix), getDatabaseName(prefix))
.setGenericDocument(document).setRankingSignal(proto.getScore());
@@ -118,9 +122,36 @@
builder.addJoinedResult(
toUnprefixedSearchResult(joinedResultProto, schemaCache, config));
}
+ if (config.shouldRetrieveParentInfo() && Flags.enableSearchResultParentTypes()) {
+ Map<String, List<String>> parentTypeMap = new ArrayMap<>();
+ collectParentTypeMap(documentBuilder, prefix, schemaCache, parentTypeMap);
+ builder.setParentTypeMap(parentTypeMap);
+ }
return builder.build();
}
+ private static void collectParentTypeMap(
+ @NonNull DocumentProtoOrBuilder proto,
+ @NonNull String prefix,
+ @NonNull SchemaCache schemaCache,
+ @NonNull Map<String, List<String>> parentTypeMap) throws AppSearchException {
+ if (!parentTypeMap.containsKey(proto.getSchema())) {
+ List<String> parentSchemaTypes = schemaCache.getTransitiveUnprefixedParentSchemaTypes(
+ prefix, prefix + proto.getSchema());
+ if (!parentSchemaTypes.isEmpty()) {
+ parentTypeMap.put(proto.getSchema(), parentSchemaTypes);
+ }
+ }
+ // Handling nested documents
+ for (int i = 0; i < proto.getPropertiesCount(); i++) {
+ PropertyProto property = proto.getProperties(i);
+ for (int j = 0; j < property.getDocumentValuesCount(); j++) {
+ collectParentTypeMap(property.getDocumentValues(j), prefix, schemaCache,
+ parentTypeMap);
+ }
+ }
+ }
+
private static SearchResult.MatchInfo toMatchInfo(
@NonNull SnippetMatchProto snippetMatchProto, @NonNull String propertyPath) {
int exactMatchPosition = snippetMatchProto.getExactMatchUtf16Position();
diff --git a/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/FeaturesImpl.java b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/FeaturesImpl.java
index 1caea0c..8623198 100644
--- a/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/FeaturesImpl.java
+++ b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/FeaturesImpl.java
@@ -142,6 +142,9 @@
case Features.SCHEMA_SCORABLE_PROPERTY_CONFIG:
// TODO(b/357105837) : Update when feature is ready in service-appsearch.
// fall through
+ case Features.SEARCH_RESULT_PARENT_TYPES:
+ // TODO(b/371610934) : Update when feature is ready in service-appsearch.
+ // fall through
default:
return false;
}
diff --git a/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/converter/GenericDocumentToPlatformConverter.java b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/converter/GenericDocumentToPlatformConverter.java
index 55d5045..89216ba 100644
--- a/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/converter/GenericDocumentToPlatformConverter.java
+++ b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/converter/GenericDocumentToPlatformConverter.java
@@ -115,6 +115,7 @@
* {@link androidx.appsearch.app.GenericDocument}.
*/
@NonNull
+ @SuppressWarnings("deprecation")
public static GenericDocument toJetpackGenericDocument(
@NonNull android.app.appsearch.GenericDocument platformDocument) {
Preconditions.checkNotNull(platformDocument);
diff --git a/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/converter/SearchResultToPlatformConverter.java b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/converter/SearchResultToPlatformConverter.java
index 9817784..b4e9df0 100644
--- a/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/converter/SearchResultToPlatformConverter.java
+++ b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/converter/SearchResultToPlatformConverter.java
@@ -60,6 +60,8 @@
builder.addJoinedResult(toJetpackSearchResult(joinedResult));
}
}
+ // TODO(b/332642571): Add informational ranking signal once it is available in platform.
+ // TODO(b/371610934): Set parentTypeMap once it is available in platform.
return builder.build();
}
diff --git a/appsearch/appsearch-play-services-storage/src/main/java/androidx/appsearch/playservicesstorage/FeaturesImpl.java b/appsearch/appsearch-play-services-storage/src/main/java/androidx/appsearch/playservicesstorage/FeaturesImpl.java
index 00be5f8..512b995 100644
--- a/appsearch/appsearch-play-services-storage/src/main/java/androidx/appsearch/playservicesstorage/FeaturesImpl.java
+++ b/appsearch/appsearch-play-services-storage/src/main/java/androidx/appsearch/playservicesstorage/FeaturesImpl.java
@@ -93,6 +93,8 @@
// fall through
case Features.SCHEMA_SCORABLE_PROPERTY_CONFIG:
// fall through
+ case Features.SEARCH_RESULT_PARENT_TYPES:
+ // fall through
default:
return false; // AppSearch features absent in GMSCore AppSearch.
}
diff --git a/appsearch/appsearch/api/current.txt b/appsearch/appsearch/api/current.txt
index 9a69351..0930ef8 100644
--- a/appsearch/appsearch/api/current.txt
+++ b/appsearch/appsearch/api/current.txt
@@ -330,13 +330,19 @@
}
public interface DocumentClassFactory<T> {
- method public T fromGenericDocument(androidx.appsearch.app.GenericDocument, java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!>?) throws androidx.appsearch.exceptions.AppSearchException;
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public T fromGenericDocument(androidx.appsearch.app.GenericDocument, androidx.appsearch.app.DocumentClassMappingContext) throws androidx.appsearch.exceptions.AppSearchException;
method public java.util.List<java.lang.Class<? extends java.lang.Object!>!> getDependencyDocumentClasses() throws androidx.appsearch.exceptions.AppSearchException;
method public androidx.appsearch.app.AppSearchSchema getSchema() throws androidx.appsearch.exceptions.AppSearchException;
method public String getSchemaName();
method public androidx.appsearch.app.GenericDocument toGenericDocument(T) throws androidx.appsearch.exceptions.AppSearchException;
}
+ public class DocumentClassMappingContext {
+ ctor @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public DocumentClassMappingContext(java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!>?, java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!>?);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!> getDocumentClassMap();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!> getParentTypeMap();
+ }
+
@RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public final class EmbeddingVector {
ctor public EmbeddingVector(float[], String);
method public String getModelSignature();
@@ -373,6 +379,7 @@
field @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public static final String SCHEMA_SCORABLE_PROPERTY_CONFIG = "SCHEMA_SCORABLE_PROPERTY_CONFIG";
field @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public static final String SCHEMA_SET_DESCRIPTION = "SCHEMA_SET_DESCRIPTION";
field public static final String SEARCH_RESULT_MATCH_INFO_SUBMATCH = "SEARCH_RESULT_MATCH_INFO_SUBMATCH";
+ field @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public static final String SEARCH_RESULT_PARENT_TYPES = "SEARCH_RESULT_PARENT_TYPES";
field @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public static final String SEARCH_SPEC_ADD_FILTER_DOCUMENT_IDS = "SEARCH_SPEC_ADD_FILTER_DOCUMENT_IDS";
field public static final String SEARCH_SPEC_ADD_FILTER_PROPERTIES = "SEARCH_SPEC_ADD_FILTER_PROPERTIES";
field public static final String SEARCH_SPEC_ADD_INFORMATIONAL_RANKING_EXPRESSIONS = "SEARCH_SPEC_ADD_INFORMATIONAL_RANKING_EXPRESSIONS";
@@ -417,7 +424,7 @@
method public int getScore();
method public long getTtlMillis();
method public <T> T toDocumentClass(Class<T!>) throws androidx.appsearch.exceptions.AppSearchException;
- method public <T> T toDocumentClass(Class<T!>, java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!>?) throws androidx.appsearch.exceptions.AppSearchException;
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public <T> T toDocumentClass(Class<T!>, androidx.appsearch.app.DocumentClassMappingContext) throws androidx.appsearch.exceptions.AppSearchException;
}
public static class GenericDocument.Builder<BuilderType extends androidx.appsearch.app.GenericDocument.Builder> {
@@ -633,6 +640,7 @@
method public java.util.List<androidx.appsearch.app.SearchResult!> getJoinedResults();
method public java.util.List<androidx.appsearch.app.SearchResult.MatchInfo!> getMatchInfos();
method public String getPackageName();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!> getParentTypeMap();
method public double getRankingSignal();
}
@@ -644,6 +652,7 @@
method public androidx.appsearch.app.SearchResult build();
method public androidx.appsearch.app.SearchResult.Builder setDocument(Object) throws androidx.appsearch.exceptions.AppSearchException;
method public androidx.appsearch.app.SearchResult.Builder setGenericDocument(androidx.appsearch.app.GenericDocument);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.SearchResult.Builder setParentTypeMap(java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!>);
method public androidx.appsearch.app.SearchResult.Builder setRankingSignal(double);
}
diff --git a/appsearch/appsearch/api/restricted_current.txt b/appsearch/appsearch/api/restricted_current.txt
index 9a69351..0930ef8 100644
--- a/appsearch/appsearch/api/restricted_current.txt
+++ b/appsearch/appsearch/api/restricted_current.txt
@@ -330,13 +330,19 @@
}
public interface DocumentClassFactory<T> {
- method public T fromGenericDocument(androidx.appsearch.app.GenericDocument, java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!>?) throws androidx.appsearch.exceptions.AppSearchException;
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public T fromGenericDocument(androidx.appsearch.app.GenericDocument, androidx.appsearch.app.DocumentClassMappingContext) throws androidx.appsearch.exceptions.AppSearchException;
method public java.util.List<java.lang.Class<? extends java.lang.Object!>!> getDependencyDocumentClasses() throws androidx.appsearch.exceptions.AppSearchException;
method public androidx.appsearch.app.AppSearchSchema getSchema() throws androidx.appsearch.exceptions.AppSearchException;
method public String getSchemaName();
method public androidx.appsearch.app.GenericDocument toGenericDocument(T) throws androidx.appsearch.exceptions.AppSearchException;
}
+ public class DocumentClassMappingContext {
+ ctor @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public DocumentClassMappingContext(java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!>?, java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!>?);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!> getDocumentClassMap();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!> getParentTypeMap();
+ }
+
@RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG) public final class EmbeddingVector {
ctor public EmbeddingVector(float[], String);
method public String getModelSignature();
@@ -373,6 +379,7 @@
field @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public static final String SCHEMA_SCORABLE_PROPERTY_CONFIG = "SCHEMA_SCORABLE_PROPERTY_CONFIG";
field @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public static final String SCHEMA_SET_DESCRIPTION = "SCHEMA_SET_DESCRIPTION";
field public static final String SEARCH_RESULT_MATCH_INFO_SUBMATCH = "SEARCH_RESULT_MATCH_INFO_SUBMATCH";
+ field @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public static final String SEARCH_RESULT_PARENT_TYPES = "SEARCH_RESULT_PARENT_TYPES";
field @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public static final String SEARCH_SPEC_ADD_FILTER_DOCUMENT_IDS = "SEARCH_SPEC_ADD_FILTER_DOCUMENT_IDS";
field public static final String SEARCH_SPEC_ADD_FILTER_PROPERTIES = "SEARCH_SPEC_ADD_FILTER_PROPERTIES";
field public static final String SEARCH_SPEC_ADD_INFORMATIONAL_RANKING_EXPRESSIONS = "SEARCH_SPEC_ADD_INFORMATIONAL_RANKING_EXPRESSIONS";
@@ -417,7 +424,7 @@
method public int getScore();
method public long getTtlMillis();
method public <T> T toDocumentClass(Class<T!>) throws androidx.appsearch.exceptions.AppSearchException;
- method public <T> T toDocumentClass(Class<T!>, java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!>?) throws androidx.appsearch.exceptions.AppSearchException;
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public <T> T toDocumentClass(Class<T!>, androidx.appsearch.app.DocumentClassMappingContext) throws androidx.appsearch.exceptions.AppSearchException;
}
public static class GenericDocument.Builder<BuilderType extends androidx.appsearch.app.GenericDocument.Builder> {
@@ -633,6 +640,7 @@
method public java.util.List<androidx.appsearch.app.SearchResult!> getJoinedResults();
method public java.util.List<androidx.appsearch.app.SearchResult.MatchInfo!> getMatchInfos();
method public String getPackageName();
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!> getParentTypeMap();
method public double getRankingSignal();
}
@@ -644,6 +652,7 @@
method public androidx.appsearch.app.SearchResult build();
method public androidx.appsearch.app.SearchResult.Builder setDocument(Object) throws androidx.appsearch.exceptions.AppSearchException;
method public androidx.appsearch.app.SearchResult.Builder setGenericDocument(androidx.appsearch.app.GenericDocument);
+ method @SuppressCompatibility @androidx.appsearch.app.ExperimentalAppSearchApi public androidx.appsearch.app.SearchResult.Builder setParentTypeMap(java.util.Map<java.lang.String!,java.util.List<java.lang.String!>!>);
method public androidx.appsearch.app.SearchResult.Builder setRankingSignal(double);
}
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AnnotationProcessorTestBase.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AnnotationProcessorTestBase.java
index 23cdf04..77b032b 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AnnotationProcessorTestBase.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AnnotationProcessorTestBase.java
@@ -43,6 +43,8 @@
import androidx.test.core.app.ApplicationProvider;
import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.util.concurrent.ListenableFuture;
import org.junit.After;
@@ -1703,6 +1705,7 @@
@Test
public void testPolymorphismForInterface() throws Exception {
assumeTrue(mSession.getFeatures().isFeatureSupported(Features.SCHEMA_ADD_PARENT_TYPE));
+ assumeTrue(mSession.getFeatures().isFeatureSupported(Features.SEARCH_RESULT_PARENT_TYPES));
mSession.setSchemaAsync(new SetSchemaRequest.Builder()
// Adding BusinessImpl should be enough to add all the dependency classes.
@@ -1720,9 +1723,7 @@
assertThat(rootGeneric.getSchemaType()).isEqualTo("InterfaceRoot");
Place place = Place.createPlace("id1", "namespace", 2000, "place_loc");
- GenericDocument placeGeneric =
- new GenericDocument.Builder<>(GenericDocument.fromDocumentClass(place))
- .setParentTypes(Collections.singletonList("InterfaceRoot")).build();
+ GenericDocument placeGeneric = GenericDocument.fromDocumentClass(place);
assertThat(placeGeneric.getId()).isEqualTo("id1");
assertThat(placeGeneric.getNamespace()).isEqualTo("namespace");
assertThat(placeGeneric.getCreationTimestampMillis()).isEqualTo(2000);
@@ -1735,9 +1736,7 @@
.setCreationTimestamp(3000)
.setOrganizationDescription("organization_dec")
.build();
- GenericDocument organizationGeneric =
- new GenericDocument.Builder<>(GenericDocument.fromDocumentClass(organization))
- .setParentTypes(Collections.singletonList("InterfaceRoot")).build();
+ GenericDocument organizationGeneric = GenericDocument.fromDocumentClass(organization);
assertThat(organizationGeneric.getId()).isEqualTo("id2");
assertThat(organizationGeneric.getNamespace()).isEqualTo("namespace");
assertThat(organizationGeneric.getCreationTimestampMillis()).isEqualTo(3000);
@@ -1749,9 +1748,7 @@
"business_dec", "business_name");
// At runtime, business is type of BusinessImpl. As a result, the list of parent types
// for it should contain Business.
- GenericDocument businessGeneric = new GenericDocument.Builder<>(
- GenericDocument.fromDocumentClass(business)).setParentTypes(new ArrayList<>(
- Arrays.asList("Business", "Place", "Organization", "InterfaceRoot"))).build();
+ GenericDocument businessGeneric = GenericDocument.fromDocumentClass(business);
assertThat(businessGeneric.getId()).isEqualTo("id3");
assertThat(businessGeneric.getNamespace()).isEqualTo("namespace");
assertThat(businessGeneric.getCreationTimestampMillis()).isEqualTo(4000);
@@ -2400,7 +2397,8 @@
// Test that even when deserializing genericDocument to InterfaceRoot, we will get a
// Person instance, instead of just an InterfaceRoot.
InterfaceRoot interfaceRoot = genericDocument.toDocumentClass(InterfaceRoot.class,
- AppSearchDocumentClassMap.getGlobalMap());
+ new DocumentClassMappingContext(
+ AppSearchDocumentClassMap.getGlobalMap(), /* parentTypeMap= */null));
assertThat(interfaceRoot).isInstanceOf(Person.class);
Person newPerson = (Person) interfaceRoot;
assertThat(newPerson.getId()).isEqualTo("id");
@@ -2432,7 +2430,8 @@
// Without parent information, toDocumentClass() will try to deserialize unknown type to
// the type that is specified in the parameter.
InterfaceRoot interfaceRoot = genericDocument.toDocumentClass(InterfaceRoot.class,
- AppSearchDocumentClassMap.getGlobalMap());
+ new DocumentClassMappingContext(
+ AppSearchDocumentClassMap.getGlobalMap(), /* parentTypeMap= */null));
assertThat(interfaceRoot).isNotInstanceOf(Person.class);
assertThat(interfaceRoot).isInstanceOf(InterfaceRoot.class);
assertThat(interfaceRoot.getId()).isEqualTo("id");
@@ -2441,11 +2440,10 @@
// With parent information, toDocumentClass() will try to deserialize unknown type to the
// nearest known parent type.
- genericDocument = new GenericDocument.Builder<>(genericDocument)
- .setParentTypes(new ArrayList<>(Arrays.asList("Person", "InterfaceRoot")))
- .build();
interfaceRoot = genericDocument.toDocumentClass(InterfaceRoot.class,
- AppSearchDocumentClassMap.getGlobalMap());
+ new DocumentClassMappingContext(AppSearchDocumentClassMap.getGlobalMap(),
+ ImmutableMap.of("UnknownType",
+ ImmutableList.of("Person", "InterfaceRoot"))));
assertThat(interfaceRoot).isInstanceOf(Person.class);
Person newPerson = (Person) interfaceRoot;
assertThat(newPerson.getId()).isEqualTo("id");
@@ -2456,7 +2454,7 @@
}
@Test
- public void testPolymorphicDeserialization_nestedType() throws Exception {
+ public void testPolymorphicDeserialization_NestedType() throws Exception {
// Create a Person document
Person.Builder personBuilder = new Person.Builder("id_person", "namespace")
.setCreationTimestamp(3000)
@@ -2478,7 +2476,9 @@
// Test that when deserializing genericDocument, we will get nested Person and Place
// instances, instead of just nested InterfaceRoot instances.
DocumentCollection newDocumentCollection = genericDocument.toDocumentClass(
- DocumentCollection.class, AppSearchDocumentClassMap.getGlobalMap());
+ DocumentCollection.class,
+ new DocumentClassMappingContext(
+ AppSearchDocumentClassMap.getGlobalMap(), /* parentTypeMap= */null));
assertThat(newDocumentCollection.mId).isEqualTo("id_collection");
assertThat(newDocumentCollection.mNamespace).isEqualTo("namespace");
assertThat(newDocumentCollection.mCollection).hasLength(2);
@@ -2519,10 +2519,12 @@
}
@Test
- public void testPolymorphicDeserialization_Integration() throws Exception {
+ @SuppressWarnings("deprecation")
+ public void testPolymorphicDeserialization_Integration()
+ throws Exception {
assumeTrue(mSession.getFeatures().isFeatureSupported(Features.SCHEMA_ADD_PARENT_TYPE));
- // Add an unknown business type this is a subtype of Business.
+ // Add an unknown business type that is a subtype of Business.
mSession.setSchemaAsync(new SetSchemaRequest.Builder()
.addDocumentClasses(Business.class)
.addSchemas(new AppSearchSchema.Builder("UnknownBusiness")
@@ -2556,20 +2558,29 @@
.build();
checkIsBatchResultSuccess(mSession.putAsync(
new PutDocumentsRequest.Builder().addGenericDocuments(genericDoc).build()));
+ GenericDocument expectedGenericDoc;
+ if (mSession.getFeatures().isFeatureSupported(Features.SEARCH_RESULT_PARENT_TYPES)) {
+ // When SearchResult wraps parent information, GenericDocument should not do.
+ expectedGenericDoc = genericDoc;
+ } else {
+ // When SearchResult does not wrap parent information, GenericDocument should do.
+ expectedGenericDoc = new GenericDocument.Builder<>(genericDoc)
+ .setParentTypes(
+ new ArrayList<>(Arrays.asList("Business", "Place", "Organization",
+ "InterfaceRoot")))
+ .build();
+ }
// Query to get the document back, with parent information added.
- SearchResults searchResults = mSession.search("", new SearchSpec.Builder().build());
- List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
- assertThat(documents).hasSize(1);
- GenericDocument actualGenericDoc = documents.get(0);
- GenericDocument expectedGenericDoc = new GenericDocument.Builder<>(genericDoc)
- .setParentTypes(new ArrayList<>(Arrays.asList("Business", "Place", "Organization",
- "InterfaceRoot")))
- .build();
- assertThat(actualGenericDoc).isEqualTo(expectedGenericDoc);
+ List<SearchResult> searchResults = retrieveAllSearchResults(
+ mSession.search("", new SearchSpec.Builder().build())
+ );
+ assertThat(searchResults).hasSize(1);
+ SearchResult result = searchResults.get(0);
+ assertThat(result.getGenericDocument()).isEqualTo(expectedGenericDoc);
// Deserializing it to InterfaceRoot will get a Business instance back.
- InterfaceRoot interfaceRoot = actualGenericDoc.toDocumentClass(InterfaceRoot.class,
+ InterfaceRoot interfaceRoot = result.getDocument(InterfaceRoot.class,
AppSearchDocumentClassMap.getGlobalMap());
assertThat(interfaceRoot).isInstanceOf(Business.class);
Business business = (Business) interfaceRoot;
@@ -2581,6 +2592,95 @@
assertThat(business.getBusinessName()).isEqualTo("business_name");
}
+ @Test
+ @SuppressWarnings("deprecation")
+ public void testPolymorphicDeserialization_NestedType_Integration() throws Exception {
+ assumeTrue(mSession.getFeatures().isFeatureSupported(Features.SCHEMA_ADD_PARENT_TYPE));
+
+ // Add an unknown business type that is a subtype of Business, and a DocumentCollection
+ // type that can hold any nested InterfaceRoot document.
+ mSession.setSchemaAsync(new SetSchemaRequest.Builder()
+ .addDocumentClasses(Business.class)
+ .addSchemas(new AppSearchSchema.Builder("UnknownBusiness")
+ .addParentType("Business")
+ .addProperty(new AppSearchSchema.StringPropertyConfig.Builder(
+ "organizationDescription")
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .build())
+ .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("location")
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .build())
+ .addProperty(new AppSearchSchema.StringPropertyConfig.Builder(
+ "businessName")
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .build())
+ .addProperty(new AppSearchSchema.StringPropertyConfig.Builder(
+ "unknownProperty")
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .build())
+ .build())
+ .addDocumentClasses(DocumentCollection.class)
+ .build()).get();
+ // Create and put a DocumentCollection that includes an UnknownBusiness document.
+ GenericDocument unknownBusinessGenericDoc =
+ new GenericDocument.Builder<>("namespace", "id1", "UnknownBusiness")
+ .setCreationTimestampMillis(3000)
+ .setPropertyString("location", "business_loc")
+ .setPropertyString("organizationDescription", "business_dec")
+ .setPropertyString("businessName", "business_name")
+ .setPropertyString("unknownProperty", "foo")
+ .build();
+ GenericDocument documentCollectionGenericDoc = new GenericDocument.Builder<>(
+ "namespace", "id2", "DocumentCollection")
+ .setCreationTimestampMillis(3000)
+ .setPropertyDocument("collection", unknownBusinessGenericDoc)
+ .build();
+ checkIsBatchResultSuccess(mSession.putAsync(
+ new PutDocumentsRequest.Builder().addGenericDocuments(
+ documentCollectionGenericDoc).build()));
+ GenericDocument expectedDocumentCollectionGenericDoc;
+ if (mSession.getFeatures().isFeatureSupported(Features.SEARCH_RESULT_PARENT_TYPES)) {
+ // When SearchResult wraps parent information, GenericDocument should not do.
+ expectedDocumentCollectionGenericDoc = documentCollectionGenericDoc;
+ } else {
+ // When SearchResult does not wrap parent information, GenericDocument should do.
+ GenericDocument expectedUnknownBusinessGenericDoc = new GenericDocument.Builder<>(
+ unknownBusinessGenericDoc)
+ .setParentTypes(
+ new ArrayList<>(Arrays.asList("Business", "Place", "Organization",
+ "InterfaceRoot")))
+ .build();
+ expectedDocumentCollectionGenericDoc = new GenericDocument.Builder<>(
+ "namespace", "id2", "DocumentCollection")
+ .setCreationTimestampMillis(3000)
+ .setPropertyDocument("collection", expectedUnknownBusinessGenericDoc)
+ .build();
+ }
+
+ // Query to get the document back, with parent information added.
+ List<SearchResult> searchResults = retrieveAllSearchResults(
+ mSession.search("", new SearchSpec.Builder().build())
+ );
+ assertThat(searchResults).hasSize(1);
+ SearchResult result = searchResults.get(0);
+ assertThat(result.getGenericDocument()).isEqualTo(expectedDocumentCollectionGenericDoc);
+
+ // Deserialize documentCollectionGenericDoc and check that it includes a Business
+ // instance, instead of an InterfaceRoot instance.
+ DocumentCollection documentCollection = result.getDocument(DocumentCollection.class,
+ AppSearchDocumentClassMap.getGlobalMap());
+ assertThat(documentCollection.mCollection).asList().hasSize(1);
+ assertThat(documentCollection.mCollection[0]).isInstanceOf(Business.class);
+ Business business = (Business) documentCollection.mCollection[0];
+ assertThat(business.getId()).isEqualTo("id1");
+ assertThat(business.getNamespace()).isEqualTo("namespace");
+ assertThat(business.getCreationTimestamp()).isEqualTo(3000);
+ assertThat(business.getLocation()).isEqualTo("business_loc");
+ assertThat(business.getOrganizationDescription()).isEqualTo("business_dec");
+ assertThat(business.getBusinessName()).isEqualTo("business_name");
+ }
+
+
// InterfaceRoot
// | \
// | Person
@@ -2589,6 +2689,7 @@
@Test
public void testPolymorphicDeserialization_IntegrationDiamondThreeTypes() throws Exception {
assumeTrue(mSession.getFeatures().isFeatureSupported(Features.SCHEMA_ADD_PARENT_TYPE));
+ assumeTrue(mSession.getFeatures().isFeatureSupported(Features.SEARCH_RESULT_PARENT_TYPES));
mSession.setSchemaAsync(new SetSchemaRequest.Builder()
.addSchemas(new AppSearchSchema.Builder("UnknownA")
@@ -2617,17 +2718,15 @@
new PutDocumentsRequest.Builder().addGenericDocuments(genericDoc).build()));
// Query to get the document back, with parent information added.
- SearchResults searchResults = mSession.search("", new SearchSpec.Builder().build());
- List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
- assertThat(documents).hasSize(1);
- GenericDocument actualGenericDoc = documents.get(0);
- GenericDocument expectedGenericDoc = new GenericDocument.Builder<>(genericDoc)
- .setParentTypes(new ArrayList<>(Arrays.asList("Person", "InterfaceRoot")))
- .build();
- assertThat(actualGenericDoc).isEqualTo(expectedGenericDoc);
+ List<SearchResult> searchResults = retrieveAllSearchResults(
+ mSession.search("", new SearchSpec.Builder().build())
+ );
+ assertThat(searchResults).hasSize(1);
+ SearchResult result = searchResults.get(0);
+ assertThat(result.getGenericDocument()).isEqualTo(genericDoc);
// Deserializing it to InterfaceRoot will get a Person instance back.
- InterfaceRoot interfaceRoot = actualGenericDoc.toDocumentClass(InterfaceRoot.class,
+ InterfaceRoot interfaceRoot = result.getDocument(InterfaceRoot.class,
AppSearchDocumentClassMap.getGlobalMap());
assertThat(interfaceRoot).isInstanceOf(Person.class);
Person person = (Person) interfaceRoot;
@@ -2646,6 +2745,7 @@
@Test
public void testPolymorphicDeserialization_IntegrationDiamondTwoUnknown() throws Exception {
assumeTrue(mSession.getFeatures().isFeatureSupported(Features.SCHEMA_ADD_PARENT_TYPE));
+ assumeTrue(mSession.getFeatures().isFeatureSupported(Features.SEARCH_RESULT_PARENT_TYPES));
mSession.setSchemaAsync(new SetSchemaRequest.Builder()
.addSchemas(new AppSearchSchema.Builder("UnknownA")
@@ -2677,18 +2777,15 @@
new PutDocumentsRequest.Builder().addGenericDocuments(genericDoc).build()));
// Query to get the document back, with parent information added.
- SearchResults searchResults = mSession.search("", new SearchSpec.Builder().build());
- List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
- assertThat(documents).hasSize(1);
- GenericDocument actualGenericDoc = documents.get(0);
- GenericDocument expectedGenericDoc = new GenericDocument.Builder<>(genericDoc)
- .setParentTypes(new ArrayList<>(
- Arrays.asList("UnknownA", "Person", "InterfaceRoot")))
- .build();
- assertThat(actualGenericDoc).isEqualTo(expectedGenericDoc);
+ List<SearchResult> searchResults = retrieveAllSearchResults(
+ mSession.search("", new SearchSpec.Builder().build())
+ );
+ assertThat(searchResults).hasSize(1);
+ SearchResult result = searchResults.get(0);
+ assertThat(result.getGenericDocument()).isEqualTo(genericDoc);
// Deserializing it to InterfaceRoot will get a Person instance back.
- InterfaceRoot interfaceRoot = actualGenericDoc.toDocumentClass(InterfaceRoot.class,
+ InterfaceRoot interfaceRoot = result.getDocument(InterfaceRoot.class,
AppSearchDocumentClassMap.getGlobalMap());
assertThat(interfaceRoot).isInstanceOf(Person.class);
Person person = (Person) interfaceRoot;
@@ -2707,6 +2804,7 @@
@Test
public void testPolymorphicDeserialization_IntegrationDiamondOneUnknown() throws Exception {
assumeTrue(mSession.getFeatures().isFeatureSupported(Features.SCHEMA_ADD_PARENT_TYPE));
+ assumeTrue(mSession.getFeatures().isFeatureSupported(Features.SEARCH_RESULT_PARENT_TYPES));
mSession.setSchemaAsync(new SetSchemaRequest.Builder()
.addDocumentClasses(Person.class)
@@ -2742,19 +2840,16 @@
new PutDocumentsRequest.Builder().addGenericDocuments(genericDoc).build()));
// Query to get the document back, with parent information added.
- SearchResults searchResults = mSession.search("", new SearchSpec.Builder().build());
- List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
- assertThat(documents).hasSize(1);
- GenericDocument actualGenericDoc = documents.get(0);
- GenericDocument expectedGenericDoc = new GenericDocument.Builder<>(genericDoc)
- .setParentTypes(new ArrayList<>(
- Arrays.asList("Person", "Organization", "InterfaceRoot")))
- .build();
- assertThat(actualGenericDoc).isEqualTo(expectedGenericDoc);
+ List<SearchResult> searchResults = retrieveAllSearchResults(
+ mSession.search("", new SearchSpec.Builder().build())
+ );
+ assertThat(searchResults).hasSize(1);
+ SearchResult result = searchResults.get(0);
+ assertThat(result.getGenericDocument()).isEqualTo(genericDoc);
// Deserializing it to InterfaceRoot will get a Person instance back, which is the first
// known type, instead of an Organization.
- InterfaceRoot interfaceRoot = actualGenericDoc.toDocumentClass(InterfaceRoot.class,
+ InterfaceRoot interfaceRoot = result.getDocument(InterfaceRoot.class,
AppSearchDocumentClassMap.getGlobalMap());
assertThat(interfaceRoot).isInstanceOf(Person.class);
assertThat(interfaceRoot).isNotInstanceOf(Organization.class);
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AppSearchSessionInternalTestBase.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AppSearchSessionInternalTestBase.java
index b72cf54..addbaa3 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AppSearchSessionInternalTestBase.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AppSearchSessionInternalTestBase.java
@@ -21,12 +21,12 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
import androidx.annotation.NonNull;
import androidx.appsearch.app.AppSearchSchema.PropertyConfig;
import androidx.appsearch.app.AppSearchSchema.StringPropertyConfig;
-import androidx.appsearch.testutil.AppSearchEmail;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.ListenableFuture;
@@ -35,8 +35,6 @@
import org.junit.Before;
import org.junit.Test;
-import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
@@ -110,315 +108,13 @@
assertThat(actual).containsExactlyElementsIn(request.getSchemas());
}
+ // TODO(b/371610934): Remove this test once GenericDocument#setParentTypes is removed.
@Test
- public void testQuery_typeFilterWithPolymorphism() throws Exception {
+ @SuppressWarnings("deprecation")
+ public void testQuery_genericDocumentWrapsParentTypeForPolymorphism() throws Exception {
assumeTrue(mDb1.getFeatures().isFeatureSupported(Features.SCHEMA_ADD_PARENT_TYPE));
-
- // Schema registration
- AppSearchSchema personSchema =
- new AppSearchSchema.Builder("Person")
- .addProperty(
- new StringPropertyConfig.Builder("name")
- .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
- .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
- .setIndexingType(
- StringPropertyConfig.INDEXING_TYPE_PREFIXES)
- .build())
- .build();
- AppSearchSchema artistSchema =
- new AppSearchSchema.Builder("Artist")
- .addParentType("Person")
- .addProperty(
- new StringPropertyConfig.Builder("name")
- .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
- .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
- .setIndexingType(
- StringPropertyConfig.INDEXING_TYPE_PREFIXES)
- .build())
- .build();
- mDb1.setSchemaAsync(
- new SetSchemaRequest.Builder()
- .addSchemas(personSchema)
- .addSchemas(artistSchema)
- .addSchemas(AppSearchEmail.SCHEMA)
- .build())
- .get();
-
- // Index some documents
- GenericDocument personDoc =
- new GenericDocument.Builder<>("namespace", "id1", "Person")
- .setPropertyString("name", "Foo")
- .build();
- GenericDocument artistDoc =
- new GenericDocument.Builder<>("namespace", "id2", "Artist")
- .setPropertyString("name", "Foo")
- .build();
- AppSearchEmail emailDoc =
- new AppSearchEmail.Builder("namespace", "id3")
- .setFrom("[email protected]")
- .setTo("[email protected]", "[email protected]")
- .setSubject("testPut example")
- .setBody("Foo")
- .build();
- checkIsBatchResultSuccess(
- mDb1.putAsync(
- new PutDocumentsRequest.Builder()
- .addGenericDocuments(personDoc, artistDoc, emailDoc)
- .build()));
- GenericDocument artistDocWithParent =
- new GenericDocument.Builder<>(artistDoc).setParentTypes(
- Collections.singletonList("Person")).build();
-
- // Query for the documents
- SearchResults searchResults =
- mDb1.search(
- "Foo",
- new SearchSpec.Builder()
- .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
- .build());
- List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
- assertThat(documents).hasSize(3);
- assertThat(documents).containsExactly(personDoc, artistDocWithParent, emailDoc);
-
- // Query with a filter for the "Person" type should also include the "Artist" type.
- searchResults =
- mDb1.search(
- "Foo",
- new SearchSpec.Builder()
- .addFilterSchemas("Person")
- .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
- .build());
- documents = convertSearchResultsToDocuments(searchResults);
- assertThat(documents).hasSize(2);
- assertThat(documents).containsExactly(personDoc, artistDocWithParent);
-
- // Query with a filters for the "Artist" type should not include the "Person" type.
- searchResults =
- mDb1.search(
- "Foo",
- new SearchSpec.Builder()
- .addFilterSchemas("Artist")
- .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
- .build());
- documents = convertSearchResultsToDocuments(searchResults);
- assertThat(documents).hasSize(1);
- assertThat(documents).containsExactly(artistDocWithParent);
- }
-
- @Test
- public void testQuery_projectionWithPolymorphism() throws Exception {
- assumeTrue(mDb1.getFeatures().isFeatureSupported(Features.SCHEMA_ADD_PARENT_TYPE));
-
- // Schema registration
- AppSearchSchema personSchema =
- new AppSearchSchema.Builder("Person")
- .addProperty(
- new StringPropertyConfig.Builder("name")
- .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
- .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
- .setIndexingType(
- StringPropertyConfig.INDEXING_TYPE_PREFIXES)
- .build())
- .addProperty(
- new StringPropertyConfig.Builder("emailAddress")
- .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
- .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
- .setIndexingType(
- StringPropertyConfig.INDEXING_TYPE_PREFIXES)
- .build())
- .build();
- AppSearchSchema artistSchema =
- new AppSearchSchema.Builder("Artist")
- .addParentType("Person")
- .addProperty(
- new StringPropertyConfig.Builder("name")
- .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
- .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
- .setIndexingType(
- StringPropertyConfig.INDEXING_TYPE_PREFIXES)
- .build())
- .addProperty(
- new StringPropertyConfig.Builder("emailAddress")
- .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
- .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
- .setIndexingType(
- StringPropertyConfig.INDEXING_TYPE_PREFIXES)
- .build())
- .addProperty(
- new StringPropertyConfig.Builder("company")
- .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
- .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
- .setIndexingType(
- StringPropertyConfig.INDEXING_TYPE_PREFIXES)
- .build())
- .build();
- mDb1.setSchemaAsync(
- new SetSchemaRequest.Builder()
- .addSchemas(personSchema)
- .addSchemas(artistSchema)
- .build())
- .get();
-
- // Index two documents
- GenericDocument personDoc =
- new GenericDocument.Builder<>("namespace", "id1", "Person")
- .setCreationTimestampMillis(1000)
- .setPropertyString("name", "Foo Person")
- .setPropertyString("emailAddress", "[email protected]")
- .build();
- GenericDocument artistDoc =
- new GenericDocument.Builder<>("namespace", "id2", "Artist")
- .setCreationTimestampMillis(1000)
- .setPropertyString("name", "Foo Artist")
- .setPropertyString("emailAddress", "[email protected]")
- .setPropertyString("company", "Company")
- .build();
- checkIsBatchResultSuccess(
- mDb1.putAsync(
- new PutDocumentsRequest.Builder()
- .addGenericDocuments(personDoc, artistDoc)
- .build()));
-
- // Query with type property paths {"Person", ["name"]}, {"Artist", ["emailAddress"]}
- // This will be expanded to paths {"Person", ["name"]}, {"Artist", ["name", "emailAddress"]}
- // via polymorphism.
- SearchResults searchResults =
- mDb1.search(
- "Foo",
- new SearchSpec.Builder()
- .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
- .addProjection("Person", ImmutableList.of("name"))
- .addProjection("Artist", ImmutableList.of("emailAddress"))
- .build());
- List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
-
- // The person document should have been returned with only the "name" property. The artist
- // document should have been returned with all of its properties.
- GenericDocument expectedPerson =
- new GenericDocument.Builder<>("namespace", "id1", "Person")
- .setCreationTimestampMillis(1000)
- .setPropertyString("name", "Foo Person")
- .build();
- GenericDocument expectedArtist =
- new GenericDocument.Builder<>("namespace", "id2", "Artist")
- .setParentTypes(Collections.singletonList("Person"))
- .setCreationTimestampMillis(1000)
- .setPropertyString("name", "Foo Artist")
- .setPropertyString("emailAddress", "[email protected]")
- .build();
- assertThat(documents).containsExactly(expectedPerson, expectedArtist);
- }
-
- @Test
- public void testQuery_projectionWithPolymorphismAndSchemaFilter() throws Exception {
- assumeTrue(mDb1.getFeatures().isFeatureSupported(Features.SCHEMA_ADD_PARENT_TYPE));
-
- // Schema registration
- AppSearchSchema personSchema =
- new AppSearchSchema.Builder("Person")
- .addProperty(
- new StringPropertyConfig.Builder("name")
- .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
- .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
- .setIndexingType(
- StringPropertyConfig.INDEXING_TYPE_PREFIXES)
- .build())
- .addProperty(
- new StringPropertyConfig.Builder("emailAddress")
- .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
- .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
- .setIndexingType(
- StringPropertyConfig.INDEXING_TYPE_PREFIXES)
- .build())
- .build();
- AppSearchSchema artistSchema =
- new AppSearchSchema.Builder("Artist")
- .addParentType("Person")
- .addProperty(
- new StringPropertyConfig.Builder("name")
- .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
- .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
- .setIndexingType(
- StringPropertyConfig.INDEXING_TYPE_PREFIXES)
- .build())
- .addProperty(
- new StringPropertyConfig.Builder("emailAddress")
- .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
- .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
- .setIndexingType(
- StringPropertyConfig.INDEXING_TYPE_PREFIXES)
- .build())
- .addProperty(
- new StringPropertyConfig.Builder("company")
- .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
- .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
- .setIndexingType(
- StringPropertyConfig.INDEXING_TYPE_PREFIXES)
- .build())
- .build();
- mDb1.setSchemaAsync(
- new SetSchemaRequest.Builder()
- .addSchemas(personSchema)
- .addSchemas(artistSchema)
- .build())
- .get();
-
- // Index two documents
- GenericDocument personDoc =
- new GenericDocument.Builder<>("namespace", "id1", "Person")
- .setCreationTimestampMillis(1000)
- .setPropertyString("name", "Foo Person")
- .setPropertyString("emailAddress", "[email protected]")
- .build();
- GenericDocument artistDoc =
- new GenericDocument.Builder<>("namespace", "id2", "Artist")
- .setCreationTimestampMillis(1000)
- .setPropertyString("name", "Foo Artist")
- .setPropertyString("emailAddress", "[email protected]")
- .setPropertyString("company", "Company")
- .build();
- checkIsBatchResultSuccess(
- mDb1.putAsync(
- new PutDocumentsRequest.Builder()
- .addGenericDocuments(personDoc, artistDoc)
- .build()));
-
- // Query with type property paths {"Person", ["name"]} and {"Artist", ["emailAddress"]}, and
- // a schema filter for the "Person".
- // This will be expanded to paths {"Person", ["name"]} and
- // {"Artist", ["name", "emailAddress"]}, and filters for both "Person" and "Artist" via
- // polymorphism.
- SearchResults searchResults =
- mDb1.search(
- "Foo",
- new SearchSpec.Builder()
- .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
- .addFilterSchemas("Person")
- .addProjection("Person", ImmutableList.of("name"))
- .addProjection("Artist", ImmutableList.of("emailAddress"))
- .build());
- List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
-
- // The person document should have been returned with only the "name" property. The artist
- // document should have been returned with all of its properties.
- GenericDocument expectedPerson =
- new GenericDocument.Builder<>("namespace", "id1", "Person")
- .setCreationTimestampMillis(1000)
- .setPropertyString("name", "Foo Person")
- .build();
- GenericDocument expectedArtist =
- new GenericDocument.Builder<>("namespace", "id2", "Artist")
- .setParentTypes(Collections.singletonList("Person"))
- .setCreationTimestampMillis(1000)
- .setPropertyString("name", "Foo Artist")
- .setPropertyString("emailAddress", "[email protected]")
- .build();
- assertThat(documents).containsExactly(expectedPerson, expectedArtist);
- }
-
- @Test
- public void testQuery_indexBasedOnParentTypePolymorphism() throws Exception {
- assumeTrue(mDb1.getFeatures().isFeatureSupported(Features.SCHEMA_ADD_PARENT_TYPE));
+ // When SearchResult does not wrap parent information, GenericDocument should do.
+ assumeFalse(mDb1.getFeatures().isFeatureSupported(Features.SEARCH_RESULT_PARENT_TYPES));
// Schema registration
AppSearchSchema personSchema =
@@ -449,12 +145,30 @@
StringPropertyConfig.INDEXING_TYPE_PREFIXES)
.build())
.build();
+ AppSearchSchema musicianSchema =
+ new AppSearchSchema.Builder("Musician")
+ .addParentType("Artist")
+ .addProperty(
+ new StringPropertyConfig.Builder("name")
+ .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
+ .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .setIndexingType(
+ StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+ .build())
+ .addProperty(
+ new StringPropertyConfig.Builder("company")
+ .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
+ .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .setIndexingType(
+ StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+ .build())
+ .build();
AppSearchSchema messageSchema =
new AppSearchSchema.Builder("Message")
.addProperty(
new AppSearchSchema.DocumentPropertyConfig.Builder(
- "sender", "Person")
- .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
+ "receivers", "Person")
+ .setCardinality(PropertyConfig.CARDINALITY_REPEATED)
.setShouldIndexNestedProperties(true)
.build())
.build();
@@ -462,290 +176,59 @@
new SetSchemaRequest.Builder()
.addSchemas(personSchema)
.addSchemas(artistSchema)
+ .addSchemas(musicianSchema)
.addSchemas(messageSchema)
.build())
.get();
- // Index some an artistDoc and a messageDoc
+ // Index documents
+ GenericDocument personDoc =
+ new GenericDocument.Builder<>("namespace", "id1", "Person")
+ .setCreationTimestampMillis(1000)
+ .setPropertyString("name", "person")
+ .build();
GenericDocument artistDoc =
- new GenericDocument.Builder<>("namespace", "id1", "Artist")
- .setPropertyString("name", "Foo")
- .setPropertyString("company", "Bar")
+ new GenericDocument.Builder<>("namespace", "id2", "Artist")
+ .setCreationTimestampMillis(1000)
+ .setPropertyString("name", "artist")
+ .setPropertyString("company", "foo")
+ .build();
+ GenericDocument musicianDoc =
+ new GenericDocument.Builder<>("namespace", "id3", "Musician")
+ .setCreationTimestampMillis(1000)
+ .setPropertyString("name", "musician")
+ .setPropertyString("company", "foo")
.build();
GenericDocument messageDoc =
- new GenericDocument.Builder<>("namespace", "id2", "Message")
- // sender is defined as a Person, which accepts an Artist because Artist <:
- // Person.
- // However, indexing will be based on what's defined in Person, so the
- // "company"
- // property in artistDoc cannot be used to search this messageDoc.
- .setPropertyDocument("sender", artistDoc)
+ new GenericDocument.Builder<>("namespace", "id4", "Message")
+ .setCreationTimestampMillis(1000)
+ .setPropertyDocument("receivers", artistDoc, musicianDoc)
.build();
checkIsBatchResultSuccess(
mDb1.putAsync(
new PutDocumentsRequest.Builder()
- .addGenericDocuments(artistDoc, messageDoc)
+ .addGenericDocuments(personDoc, artistDoc, musicianDoc, messageDoc)
.build()));
- GenericDocument expectedArtistDoc =
+ GenericDocument artistDocWithParent =
new GenericDocument.Builder<>(artistDoc).setParentTypes(
- Collections.singletonList("Person")).build();
- GenericDocument expectedMessageDoc =
- new GenericDocument.Builder<>(messageDoc).setPropertyDocument("sender",
- expectedArtistDoc).build();
+ Collections.singletonList("Person")).build();
+ GenericDocument musicianDocWithParent =
+ new GenericDocument.Builder<>(musicianDoc).setParentTypes(
+ ImmutableList.of("Artist", "Person")).build();
+ GenericDocument messageDocWithParent =
+ new GenericDocument.Builder<>("namespace", "id4", "Message")
+ .setCreationTimestampMillis(1000)
+ .setPropertyDocument("receivers", artistDocWithParent,
+ musicianDocWithParent)
+ .build();
- // Query for the documents
+ // Query to get all the documents
SearchResults searchResults =
- mDb1.search(
- "Foo",
- new SearchSpec.Builder()
- .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
- .build());
+ mDb1.search("", new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .build());
List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
- assertThat(documents).hasSize(2);
- assertThat(documents).containsExactly(expectedArtistDoc, expectedMessageDoc);
-
- // The "company" property in artistDoc cannot be used to search messageDoc.
- searchResults =
- mDb1.search(
- "Bar",
- new SearchSpec.Builder()
- .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
- .build());
- documents = convertSearchResultsToDocuments(searchResults);
- assertThat(documents).hasSize(1);
- assertThat(documents).containsExactly(expectedArtistDoc);
- }
-
- @Test
- public void testQuery_parentTypeListIsTopologicalOrder() throws Exception {
- assumeTrue(mDb1.getFeatures().isFeatureSupported(Features.SCHEMA_ADD_PARENT_TYPE));
- // Create the following subtype relation graph, where
- // 1. A's direct parents are B and C.
- // 2. B's direct parent is D.
- // 3. C's direct parent is B and D.
- // DFS order from A: [A, B, D, C]. Not acceptable because B and D appear before C.
- // BFS order from A: [A, B, C, D]. Not acceptable because B appears before C.
- // Topological order (all subtypes appear before supertypes) from A: [A, C, B, D].
- AppSearchSchema schemaA =
- new AppSearchSchema.Builder("A")
- .addParentType("B")
- .addParentType("C")
- .build();
- AppSearchSchema schemaB =
- new AppSearchSchema.Builder("B")
- .addParentType("D")
- .build();
- AppSearchSchema schemaC =
- new AppSearchSchema.Builder("C")
- .addParentType("B")
- .addParentType("D")
- .build();
- AppSearchSchema schemaD =
- new AppSearchSchema.Builder("D")
- .build();
- mDb1.setSchemaAsync(
- new SetSchemaRequest.Builder()
- .addSchemas(schemaA)
- .addSchemas(schemaB)
- .addSchemas(schemaC)
- .addSchemas(schemaD)
- .build())
- .get();
-
- // Index some documents
- GenericDocument docA =
- new GenericDocument.Builder<>("namespace", "id1", "A")
- .build();
- GenericDocument docB =
- new GenericDocument.Builder<>("namespace", "id2", "B")
- .build();
- GenericDocument docC =
- new GenericDocument.Builder<>("namespace", "id3", "C")
- .build();
- GenericDocument docD =
- new GenericDocument.Builder<>("namespace", "id4", "D")
- .build();
- checkIsBatchResultSuccess(
- mDb1.putAsync(
- new PutDocumentsRequest.Builder()
- .addGenericDocuments(docA, docB, docC, docD)
- .build()));
-
- GenericDocument expectedDocA = new GenericDocument.Builder<>(docA).setParentTypes(
- new ArrayList<>(Arrays.asList("C", "B", "D"))).build();
- GenericDocument expectedDocB =
- new GenericDocument.Builder<>(docB).setParentTypes(
- Collections.singletonList("D")).build();
- GenericDocument expectedDocC =
- new GenericDocument.Builder<>(docC).setParentTypes(
- new ArrayList<>(Arrays.asList("B", "D"))).build();
- // Query for the documents
- SearchResults searchResults = mDb1.search("", new SearchSpec.Builder().build());
- List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
- assertThat(documents).hasSize(4);
- assertThat(documents).containsExactly(expectedDocA, expectedDocB, expectedDocC, docD);
- }
-
- // TODO(b/336277840): Move this if setParentTypes becomes public
- @Test
- public void testQuery_wildcardProjection_polymorphism() throws Exception {
- assumeTrue(mDb1.getFeatures().isFeatureSupported(Features.SCHEMA_ADD_PARENT_TYPE));
- AppSearchSchema messageSchema = new AppSearchSchema.Builder("Message")
- .addProperty(new StringPropertyConfig.Builder("sender")
- .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
- .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
- .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
- .build())
- .addProperty(new StringPropertyConfig.Builder("content")
- .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
- .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
- .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
- .build())
- .build();
- AppSearchSchema textSchema = new AppSearchSchema.Builder("Text")
- .addParentType("Message")
- .addProperty(new StringPropertyConfig.Builder("sender")
- .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
- .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
- .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
- .build())
- .addProperty(new StringPropertyConfig.Builder("content")
- .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
- .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
- .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
- .build())
- .build();
- AppSearchSchema emailSchema = new AppSearchSchema.Builder("Email")
- .addParentType("Message")
- .addProperty(new StringPropertyConfig.Builder("sender")
- .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
- .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
- .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
- .build())
- .addProperty(new StringPropertyConfig.Builder("content")
- .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
- .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
- .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
- .build())
- .build();
-
- // Schema registration
- mDb1.setSchemaAsync(new SetSchemaRequest.Builder()
- .addSchemas(messageSchema, textSchema, emailSchema).build()).get();
-
- // Index two child documents
- GenericDocument text = new GenericDocument.Builder<>("namespace", "id1", "Text")
- .setCreationTimestampMillis(1000)
- .setPropertyString("sender", "Some sender")
- .setPropertyString("content", "Some note")
- .build();
- GenericDocument email = new GenericDocument.Builder<>("namespace", "id2", "Email")
- .setCreationTimestampMillis(1000)
- .setPropertyString("sender", "Some sender")
- .setPropertyString("content", "Some note")
- .build();
- checkIsBatchResultSuccess(mDb1.putAsync(new PutDocumentsRequest.Builder()
- .addGenericDocuments(email, text).build()));
-
- SearchResults searchResults = mDb1.search("Some", new SearchSpec.Builder()
- .addFilterSchemas("Message")
- .addProjection(SearchSpec.SCHEMA_TYPE_WILDCARD, ImmutableList.of("sender"))
- .addFilterProperties(SearchSpec.SCHEMA_TYPE_WILDCARD, ImmutableList.of("content"))
- .build());
- List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
-
- // We specified the parent document in the filter schemas, but only indexed child documents.
- // As we also specified a wildcard schema type projection, it should apply to the child docs
- // The content property must not appear. Also emailNoContent should not appear as we are
- // filter on the content property
- GenericDocument expectedText = new GenericDocument.Builder<>("namespace", "id1", "Text")
- .setParentTypes(Collections.singletonList("Message"))
- .setCreationTimestampMillis(1000)
- .setPropertyString("sender", "Some sender")
- .build();
- GenericDocument expectedEmail = new GenericDocument.Builder<>("namespace", "id2", "Email")
- .setParentTypes(Collections.singletonList("Message"))
- .setCreationTimestampMillis(1000)
- .setPropertyString("sender", "Some sender")
- .build();
- assertThat(documents).containsExactly(expectedText, expectedEmail);
- }
-
- // TODO(b/336277840): Move this if setParentTypes becomes public
- @Test
- public void testQuery_wildcardFilterSchema_polymorphism() throws Exception {
- assumeTrue(mDb1.getFeatures().isFeatureSupported(Features.SCHEMA_ADD_PARENT_TYPE));
- AppSearchSchema messageSchema = new AppSearchSchema.Builder("Message")
- .addProperty(new StringPropertyConfig.Builder("content")
- .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
- .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
- .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
- .build())
- .build();
- AppSearchSchema textSchema = new AppSearchSchema.Builder("Text")
- .addParentType("Message")
- .addProperty(new StringPropertyConfig.Builder("content")
- .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
- .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
- .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
- .build())
- .addProperty(new StringPropertyConfig.Builder("carrier")
- .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
- .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
- .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
- .build())
- .build();
- AppSearchSchema emailSchema = new AppSearchSchema.Builder("Email")
- .addParentType("Message")
- .addProperty(new StringPropertyConfig.Builder("content")
- .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
- .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
- .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
- .build())
- .addProperty(new StringPropertyConfig.Builder("attachment")
- .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
- .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
- .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
- .build())
- .build();
-
- // Schema registration
- mDb1.setSchemaAsync(new SetSchemaRequest.Builder()
- .addSchemas(messageSchema, textSchema, emailSchema).build()).get();
-
- // Index two child documents
- GenericDocument text = new GenericDocument.Builder<>("namespace", "id1", "Text")
- .setCreationTimestampMillis(1000)
- .setPropertyString("content", "Some note")
- .setPropertyString("carrier", "Network Inc")
- .build();
- GenericDocument email = new GenericDocument.Builder<>("namespace", "id2", "Email")
- .setCreationTimestampMillis(1000)
- .setPropertyString("content", "Some note")
- .setPropertyString("attachment", "Network report")
- .build();
-
- checkIsBatchResultSuccess(mDb1.putAsync(new PutDocumentsRequest.Builder()
- .addGenericDocuments(email, text).build()));
-
- // Both email and text would match for "Network", but only text should match as it is in the
- // right property
- SearchResults searchResults = mDb1.search("Network", new SearchSpec.Builder()
- .addFilterSchemas("Message")
- .addFilterProperties(SearchSpec.SCHEMA_TYPE_WILDCARD, ImmutableList.of("carrier"))
- .build());
- List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
-
- // We specified the parent document in the filter schemas, but only indexed child documents.
- // As we also specified a wildcard schema type projection, it should apply to the child docs
- // The content property must not appear. Also emailNoContent should not appear as we are
- // filter on the content property
- GenericDocument expectedText = new GenericDocument.Builder<>("namespace", "id1", "Text")
- .setParentTypes(Collections.singletonList("Message"))
- .setCreationTimestampMillis(1000)
- .setPropertyString("content", "Some note")
- .setPropertyString("carrier", "Network Inc")
- .build();
- assertThat(documents).containsExactly(expectedText);
+ assertThat(documents).containsExactly(personDoc, artistDocWithParent, musicianDocWithParent,
+ messageDocWithParent);
}
}
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/GenericDocumentInternalTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/GenericDocumentInternalTest.java
index fc5be8f..5e0025e 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/GenericDocumentInternalTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/GenericDocumentInternalTest.java
@@ -70,6 +70,7 @@
}
@Test
+ @SuppressWarnings("deprecation")
public void testRecreateFromParcelWithParentTypes() {
GenericDocument inDoc = new GenericDocument.Builder<>("namespace", "id1", "schema1")
.setParentTypes(new ArrayList<>(Arrays.asList("Class1", "Class2")))
@@ -112,6 +113,7 @@
}
@Test
+ @SuppressWarnings("deprecation")
public void testGenericDocumentBuilderDoesNotMutateOriginal() {
GenericDocument oldDoc = new GenericDocument.Builder<>("namespace", "id1", "schema1")
.setParentTypes(new ArrayList<>(Arrays.asList("Class1", "Class2")))
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/SearchResultInternalTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/SearchResultInternalTest.java
index bec84cb..443d726 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/SearchResultInternalTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/SearchResultInternalTest.java
@@ -20,6 +20,9 @@
import org.junit.Test;
+import java.util.List;
+import java.util.Map;
+
public class SearchResultInternalTest {
@Test
public void testSearchResultBuilderCopyConstructor() {
@@ -75,6 +78,22 @@
}
@Test
+ public void testSearchResultBuilderCopyConstructor_parentType() {
+ GenericDocument document =
+ new GenericDocument.Builder<>("namespace", "id", "schemaType1").build();
+ SearchResult searchResult = new SearchResult.Builder("package", "database")
+ .setGenericDocument(document)
+ .setParentTypeMap(Map.of(
+ "schemaType1", List.of("parent1", "parent2"),
+ "schemaType2", List.of("parent3", "parent4")
+ ))
+ .build();
+ SearchResult searchResultCopy = new SearchResult.Builder(searchResult).build();
+ assertThat(searchResultCopy.getParentTypeMap()).containsExactlyEntriesIn(
+ searchResult.getParentTypeMap());
+ }
+
+ @Test
public void testSearchResultBuilder_clearJoinedResults() {
GenericDocument document =
new GenericDocument.Builder<>("namespace", "id", "schemaType").build();
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSessionCtsTestBase.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSessionCtsTestBase.java
index 3d87ab6..0b9308d 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSessionCtsTestBase.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSessionCtsTestBase.java
@@ -7863,6 +7863,141 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SEARCH_RESULT_PARENT_TYPES)
+ public void testQuery_searchResultWrapsParentTypeMapForPolymorphism() throws Exception {
+ assumeTrue(mDb1.getFeatures().isFeatureSupported(Features.SCHEMA_ADD_PARENT_TYPE));
+ assumeTrue(mDb1.getFeatures().isFeatureSupported(Features.SEARCH_RESULT_PARENT_TYPES));
+
+ // Schema registration
+ AppSearchSchema personSchema =
+ new AppSearchSchema.Builder("Person")
+ .addProperty(
+ new StringPropertyConfig.Builder("name")
+ .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
+ .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .setIndexingType(
+ StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+ .build())
+ .build();
+ AppSearchSchema artistSchema =
+ new AppSearchSchema.Builder("Artist")
+ .addParentType("Person")
+ .addProperty(
+ new StringPropertyConfig.Builder("name")
+ .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
+ .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .setIndexingType(
+ StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+ .build())
+ .addProperty(
+ new StringPropertyConfig.Builder("company")
+ .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
+ .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .setIndexingType(
+ StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+ .build())
+ .build();
+ AppSearchSchema musicianSchema =
+ new AppSearchSchema.Builder("Musician")
+ .addParentType("Artist")
+ .addProperty(
+ new StringPropertyConfig.Builder("name")
+ .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
+ .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .setIndexingType(
+ StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+ .build())
+ .addProperty(
+ new StringPropertyConfig.Builder("company")
+ .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
+ .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .setIndexingType(
+ StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+ .build())
+ .build();
+ AppSearchSchema messageSchema =
+ new AppSearchSchema.Builder("Message")
+ .addProperty(
+ new AppSearchSchema.DocumentPropertyConfig.Builder(
+ "receivers", "Person")
+ .setCardinality(PropertyConfig.CARDINALITY_REPEATED)
+ .setShouldIndexNestedProperties(true)
+ .build())
+ .build();
+ mDb1.setSchemaAsync(
+ new SetSchemaRequest.Builder()
+ .addSchemas(personSchema)
+ .addSchemas(artistSchema)
+ .addSchemas(musicianSchema)
+ .addSchemas(messageSchema)
+ .build())
+ .get();
+
+ // Index documents
+ GenericDocument personDoc =
+ new GenericDocument.Builder<>("namespace", "id1", "Person")
+ .setPropertyString("name", "person")
+ .build();
+ GenericDocument artistDoc =
+ new GenericDocument.Builder<>("namespace", "id2", "Artist")
+ .setPropertyString("name", "artist")
+ .setPropertyString("company", "foo")
+ .build();
+ GenericDocument musicianDoc =
+ new GenericDocument.Builder<>("namespace", "id3", "Musician")
+ .setPropertyString("name", "musician")
+ .setPropertyString("company", "foo")
+ .build();
+ GenericDocument messageDoc =
+ new GenericDocument.Builder<>("namespace", "id4", "Message")
+ .setPropertyDocument("receivers", artistDoc, musicianDoc)
+ .build();
+
+ Map<String, List<String>> expectedPersonParentTypeMap = Collections.emptyMap();
+ Map<String, List<String>> expectedArtistParentTypeMap =
+ ImmutableMap.of("Artist", ImmutableList.of("Person"));
+ Map<String, List<String>> expectedMusicianParentTypeMap =
+ ImmutableMap.of("Musician", ImmutableList.of("Artist", "Person"));
+ // artistDoc and musicianDoc are nested in messageDoc, so messageDoc's parent type map
+ // should have the entries for both the Artist and Musician type.
+ Map<String, List<String>> expectedMessageParentTypeMap = ImmutableMap.of(
+ "Artist", ImmutableList.of("Person"),
+ "Musician", ImmutableList.of("Artist", "Person"));
+
+ checkIsBatchResultSuccess(
+ mDb1.putAsync(
+ new PutDocumentsRequest.Builder()
+ .addGenericDocuments(personDoc, artistDoc, musicianDoc, messageDoc)
+ .build()));
+
+ // Query to get all the documents
+ List<SearchResult> searchResults = retrieveAllSearchResults(
+ mDb1.search("", new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .build()));
+ assertThat(searchResults).hasSize(4);
+ assertThat(searchResults.get(0).getGenericDocument().getSchemaType())
+ .isEqualTo("Message");
+ assertThat(searchResults.get(0).getParentTypeMap())
+ .isEqualTo(expectedMessageParentTypeMap);
+
+ assertThat(searchResults.get(1).getGenericDocument().getSchemaType())
+ .isEqualTo("Musician");
+ assertThat(searchResults.get(1).getParentTypeMap())
+ .isEqualTo(expectedMusicianParentTypeMap);
+
+ assertThat(searchResults.get(2).getGenericDocument().getSchemaType())
+ .isEqualTo("Artist");
+ assertThat(searchResults.get(2).getParentTypeMap())
+ .isEqualTo(expectedArtistParentTypeMap);
+
+ assertThat(searchResults.get(3).getGenericDocument().getSchemaType())
+ .isEqualTo("Person");
+ assertThat(searchResults.get(3).getParentTypeMap())
+ .isEqualTo(expectedPersonParentTypeMap);
+ }
+
+ @Test
public void testSimpleJoin() throws Exception {
assumeTrue(mDb1.getFeatures()
.isFeatureSupported(Features.JOIN_SPEC_AND_QUALIFIED_ID));
@@ -11283,6 +11418,655 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SEARCH_RESULT_PARENT_TYPES)
+ public void testQuery_typeFilterWithPolymorphism() throws Exception {
+ assumeTrue(mDb1.getFeatures().isFeatureSupported(Features.SCHEMA_ADD_PARENT_TYPE));
+ assumeTrue(mDb1.getFeatures().isFeatureSupported(Features.SEARCH_RESULT_PARENT_TYPES));
+
+ // Schema registration
+ AppSearchSchema personSchema =
+ new AppSearchSchema.Builder("Person")
+ .addProperty(
+ new StringPropertyConfig.Builder("name")
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .setIndexingType(
+ StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+ .build())
+ .build();
+ AppSearchSchema artistSchema =
+ new AppSearchSchema.Builder("Artist")
+ .addParentType("Person")
+ .addProperty(
+ new StringPropertyConfig.Builder("name")
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .setIndexingType(
+ StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+ .build())
+ .build();
+ mDb1.setSchemaAsync(
+ new SetSchemaRequest.Builder()
+ .addSchemas(personSchema)
+ .addSchemas(artistSchema)
+ .addSchemas(AppSearchEmail.SCHEMA)
+ .build())
+ .get();
+
+ // Index some documents
+ GenericDocument personDoc =
+ new GenericDocument.Builder<>("namespace", "id1", "Person")
+ .setPropertyString("name", "Foo")
+ .build();
+ GenericDocument artistDoc =
+ new GenericDocument.Builder<>("namespace", "id2", "Artist")
+ .setPropertyString("name", "Foo")
+ .build();
+ AppSearchEmail emailDoc =
+ new AppSearchEmail.Builder("namespace", "id3")
+ .setFrom("[email protected]")
+ .setTo("[email protected]", "[email protected]")
+ .setSubject("testPut example")
+ .setBody("Foo")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.putAsync(
+ new PutDocumentsRequest.Builder()
+ .addGenericDocuments(personDoc, artistDoc, emailDoc)
+ .build()));
+
+ // Query for the documents
+ SearchResults searchResults =
+ mDb1.search(
+ "Foo",
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .build());
+ List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+ assertThat(documents).hasSize(3);
+ assertThat(documents).containsExactly(personDoc, artistDoc, emailDoc);
+
+ // Query with a filter for the "Person" type should also include the "Artist" type.
+ searchResults =
+ mDb1.search(
+ "Foo",
+ new SearchSpec.Builder()
+ .addFilterSchemas("Person")
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .build());
+ documents = convertSearchResultsToDocuments(searchResults);
+ assertThat(documents).hasSize(2);
+ assertThat(documents).containsExactly(personDoc, artistDoc);
+
+ // Query with a filters for the "Artist" type should not include the "Person" type.
+ searchResults =
+ mDb1.search(
+ "Foo",
+ new SearchSpec.Builder()
+ .addFilterSchemas("Artist")
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .build());
+ documents = convertSearchResultsToDocuments(searchResults);
+ assertThat(documents).hasSize(1);
+ assertThat(documents).containsExactly(artistDoc);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SEARCH_RESULT_PARENT_TYPES)
+ public void testQuery_projectionWithPolymorphism() throws Exception {
+ assumeTrue(mDb1.getFeatures().isFeatureSupported(Features.SCHEMA_ADD_PARENT_TYPE));
+ assumeTrue(mDb1.getFeatures().isFeatureSupported(Features.SEARCH_RESULT_PARENT_TYPES));
+
+ // Schema registration
+ AppSearchSchema personSchema =
+ new AppSearchSchema.Builder("Person")
+ .addProperty(
+ new StringPropertyConfig.Builder("name")
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .setIndexingType(
+ StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+ .build())
+ .addProperty(
+ new StringPropertyConfig.Builder("emailAddress")
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .setIndexingType(
+ StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+ .build())
+ .build();
+ AppSearchSchema artistSchema =
+ new AppSearchSchema.Builder("Artist")
+ .addParentType("Person")
+ .addProperty(
+ new StringPropertyConfig.Builder("name")
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .setIndexingType(
+ StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+ .build())
+ .addProperty(
+ new StringPropertyConfig.Builder("emailAddress")
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .setIndexingType(
+ StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+ .build())
+ .addProperty(
+ new StringPropertyConfig.Builder("company")
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .setIndexingType(
+ StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+ .build())
+ .build();
+ mDb1.setSchemaAsync(
+ new SetSchemaRequest.Builder()
+ .addSchemas(personSchema)
+ .addSchemas(artistSchema)
+ .build())
+ .get();
+
+ // Index two documents
+ GenericDocument personDoc =
+ new GenericDocument.Builder<>("namespace", "id1", "Person")
+ .setCreationTimestampMillis(1000)
+ .setPropertyString("name", "Foo Person")
+ .setPropertyString("emailAddress", "[email protected]")
+ .build();
+ GenericDocument artistDoc =
+ new GenericDocument.Builder<>("namespace", "id2", "Artist")
+ .setCreationTimestampMillis(1000)
+ .setPropertyString("name", "Foo Artist")
+ .setPropertyString("emailAddress", "[email protected]")
+ .setPropertyString("company", "Company")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.putAsync(
+ new PutDocumentsRequest.Builder()
+ .addGenericDocuments(personDoc, artistDoc)
+ .build()));
+
+ // Query with type property paths {"Person", ["name"]}, {"Artist", ["emailAddress"]}
+ // This will be expanded to paths {"Person", ["name"]}, {"Artist", ["name", "emailAddress"]}
+ // via polymorphism.
+ SearchResults searchResults =
+ mDb1.search(
+ "Foo",
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .addProjection("Person", ImmutableList.of("name"))
+ .addProjection("Artist", ImmutableList.of("emailAddress"))
+ .build());
+ List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+
+ // The person document should have been returned with only the "name" property. The artist
+ // document should have been returned with all of its properties.
+ GenericDocument expectedPerson =
+ new GenericDocument.Builder<>("namespace", "id1", "Person")
+ .setCreationTimestampMillis(1000)
+ .setPropertyString("name", "Foo Person")
+ .build();
+ GenericDocument expectedArtist =
+ new GenericDocument.Builder<>("namespace", "id2", "Artist")
+ .setCreationTimestampMillis(1000)
+ .setPropertyString("name", "Foo Artist")
+ .setPropertyString("emailAddress", "[email protected]")
+ .build();
+ assertThat(documents).containsExactly(expectedPerson, expectedArtist);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SEARCH_RESULT_PARENT_TYPES)
+ public void testQuery_projectionWithPolymorphismAndSchemaFilter() throws Exception {
+ assumeTrue(mDb1.getFeatures().isFeatureSupported(Features.SCHEMA_ADD_PARENT_TYPE));
+ assumeTrue(mDb1.getFeatures().isFeatureSupported(Features.SEARCH_RESULT_PARENT_TYPES));
+
+ // Schema registration
+ AppSearchSchema personSchema =
+ new AppSearchSchema.Builder("Person")
+ .addProperty(
+ new StringPropertyConfig.Builder("name")
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .setIndexingType(
+ StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+ .build())
+ .addProperty(
+ new StringPropertyConfig.Builder("emailAddress")
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .setIndexingType(
+ StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+ .build())
+ .build();
+ AppSearchSchema artistSchema =
+ new AppSearchSchema.Builder("Artist")
+ .addParentType("Person")
+ .addProperty(
+ new StringPropertyConfig.Builder("name")
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .setIndexingType(
+ StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+ .build())
+ .addProperty(
+ new StringPropertyConfig.Builder("emailAddress")
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .setIndexingType(
+ StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+ .build())
+ .addProperty(
+ new StringPropertyConfig.Builder("company")
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .setIndexingType(
+ StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+ .build())
+ .build();
+ mDb1.setSchemaAsync(
+ new SetSchemaRequest.Builder()
+ .addSchemas(personSchema)
+ .addSchemas(artistSchema)
+ .build())
+ .get();
+
+ // Index two documents
+ GenericDocument personDoc =
+ new GenericDocument.Builder<>("namespace", "id1", "Person")
+ .setCreationTimestampMillis(1000)
+ .setPropertyString("name", "Foo Person")
+ .setPropertyString("emailAddress", "[email protected]")
+ .build();
+ GenericDocument artistDoc =
+ new GenericDocument.Builder<>("namespace", "id2", "Artist")
+ .setCreationTimestampMillis(1000)
+ .setPropertyString("name", "Foo Artist")
+ .setPropertyString("emailAddress", "[email protected]")
+ .setPropertyString("company", "Company")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.putAsync(
+ new PutDocumentsRequest.Builder()
+ .addGenericDocuments(personDoc, artistDoc)
+ .build()));
+
+ // Query with type property paths {"Person", ["name"]} and {"Artist", ["emailAddress"]}, and
+ // a schema filter for the "Person".
+ // This will be expanded to paths {"Person", ["name"]} and
+ // {"Artist", ["name", "emailAddress"]}, and filters for both "Person" and "Artist" via
+ // polymorphism.
+ SearchResults searchResults =
+ mDb1.search(
+ "Foo",
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .addFilterSchemas("Person")
+ .addProjection("Person", ImmutableList.of("name"))
+ .addProjection("Artist", ImmutableList.of("emailAddress"))
+ .build());
+ List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+
+ // The person document should have been returned with only the "name" property. The artist
+ // document should have been returned with all of its properties.
+ GenericDocument expectedPerson =
+ new GenericDocument.Builder<>("namespace", "id1", "Person")
+ .setCreationTimestampMillis(1000)
+ .setPropertyString("name", "Foo Person")
+ .build();
+ GenericDocument expectedArtist =
+ new GenericDocument.Builder<>("namespace", "id2", "Artist")
+ .setCreationTimestampMillis(1000)
+ .setPropertyString("name", "Foo Artist")
+ .setPropertyString("emailAddress", "[email protected]")
+ .build();
+ assertThat(documents).containsExactly(expectedPerson, expectedArtist);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SEARCH_RESULT_PARENT_TYPES)
+ public void testQuery_indexBasedOnParentTypePolymorphism() throws Exception {
+ assumeTrue(mDb1.getFeatures().isFeatureSupported(Features.SCHEMA_ADD_PARENT_TYPE));
+ assumeTrue(mDb1.getFeatures().isFeatureSupported(Features.SEARCH_RESULT_PARENT_TYPES));
+
+ // Schema registration
+ AppSearchSchema personSchema =
+ new AppSearchSchema.Builder("Person")
+ .addProperty(
+ new StringPropertyConfig.Builder("name")
+ .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
+ .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .setIndexingType(
+ StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+ .build())
+ .build();
+ AppSearchSchema artistSchema =
+ new AppSearchSchema.Builder("Artist")
+ .addParentType("Person")
+ .addProperty(
+ new StringPropertyConfig.Builder("name")
+ .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
+ .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .setIndexingType(
+ StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+ .build())
+ .addProperty(
+ new StringPropertyConfig.Builder("company")
+ .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
+ .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .setIndexingType(
+ StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+ .build())
+ .build();
+ AppSearchSchema messageSchema =
+ new AppSearchSchema.Builder("Message")
+ .addProperty(
+ new AppSearchSchema.DocumentPropertyConfig.Builder(
+ "sender", "Person")
+ .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
+ .setShouldIndexNestedProperties(true)
+ .build())
+ .build();
+ mDb1.setSchemaAsync(
+ new SetSchemaRequest.Builder()
+ .addSchemas(personSchema)
+ .addSchemas(artistSchema)
+ .addSchemas(messageSchema)
+ .build())
+ .get();
+
+ // Index some an artistDoc and a messageDoc
+ GenericDocument artistDoc =
+ new GenericDocument.Builder<>("namespace", "id1", "Artist")
+ .setPropertyString("name", "Foo")
+ .setPropertyString("company", "Bar")
+ .build();
+ GenericDocument messageDoc =
+ new GenericDocument.Builder<>("namespace", "id2", "Message")
+ // sender is defined as a Person, which accepts an Artist because Artist <:
+ // Person.
+ // However, indexing will be based on what's defined in Person, so the
+ // "company"
+ // property in artistDoc cannot be used to search this messageDoc.
+ .setPropertyDocument("sender", artistDoc)
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.putAsync(
+ new PutDocumentsRequest.Builder()
+ .addGenericDocuments(artistDoc, messageDoc)
+ .build()));
+
+ // Query for the documents
+ SearchResults searchResults =
+ mDb1.search(
+ "Foo",
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .build());
+ List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+ assertThat(documents).hasSize(2);
+ assertThat(documents).containsExactly(artistDoc, messageDoc);
+
+ // The "company" property in artistDoc cannot be used to search messageDoc.
+ searchResults =
+ mDb1.search(
+ "Bar",
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .build());
+ documents = convertSearchResultsToDocuments(searchResults);
+ assertThat(documents).hasSize(1);
+ assertThat(documents).containsExactly(artistDoc);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SEARCH_RESULT_PARENT_TYPES)
+ public void testQuery_parentTypeListIsTopologicalOrder() throws Exception {
+ assumeTrue(mDb1.getFeatures().isFeatureSupported(Features.SCHEMA_ADD_PARENT_TYPE));
+ assumeTrue(mDb1.getFeatures().isFeatureSupported(Features.SEARCH_RESULT_PARENT_TYPES));
+ // Create the following subtype relation graph, where
+ // 1. A's direct parents are B and C.
+ // 2. B's direct parent is D.
+ // 3. C's direct parent is B and D.
+ // DFS order from A: [A, B, D, C]. Not acceptable because B and D appear before C.
+ // BFS order from A: [A, B, C, D]. Not acceptable because B appears before C.
+ // Topological order (all subtypes appear before supertypes) from A: [A, C, B, D].
+ AppSearchSchema schemaA =
+ new AppSearchSchema.Builder("A")
+ .addParentType("B")
+ .addParentType("C")
+ .build();
+ AppSearchSchema schemaB =
+ new AppSearchSchema.Builder("B")
+ .addParentType("D")
+ .build();
+ AppSearchSchema schemaC =
+ new AppSearchSchema.Builder("C")
+ .addParentType("B")
+ .addParentType("D")
+ .build();
+ AppSearchSchema schemaD =
+ new AppSearchSchema.Builder("D")
+ .build();
+ mDb1.setSchemaAsync(
+ new SetSchemaRequest.Builder()
+ .addSchemas(schemaA)
+ .addSchemas(schemaB)
+ .addSchemas(schemaC)
+ .addSchemas(schemaD)
+ .build())
+ .get();
+
+ // Index some documents
+ GenericDocument docA =
+ new GenericDocument.Builder<>("namespace", "id1", "A")
+ .build();
+ GenericDocument docB =
+ new GenericDocument.Builder<>("namespace", "id2", "B")
+ .build();
+ GenericDocument docC =
+ new GenericDocument.Builder<>("namespace", "id3", "C")
+ .build();
+ GenericDocument docD =
+ new GenericDocument.Builder<>("namespace", "id4", "D")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.putAsync(
+ new PutDocumentsRequest.Builder()
+ .addGenericDocuments(docA, docB, docC, docD)
+ .build()));
+
+ Map<String, List<String>> expectedDocAParentTypeMap =
+ ImmutableMap.of("A", ImmutableList.of("C", "B", "D"));
+ Map<String, List<String>> expectedDocBParentTypeMap =
+ ImmutableMap.of("B", ImmutableList.of("D"));
+ Map<String, List<String>> expectedDocCParentTypeMap =
+ ImmutableMap.of("C", ImmutableList.of("B", "D"));
+ Map<String, List<String>> expectedDocDParentTypeMap = Collections.emptyMap();
+ // Query for the documents
+ List<SearchResult> searchResults = retrieveAllSearchResults(
+ mDb1.search("", new SearchSpec.Builder().build())
+ );
+ assertThat(searchResults).hasSize(4);
+ assertThat(searchResults.get(0).getGenericDocument()).isEqualTo(docD);
+ assertThat(searchResults.get(0).getParentTypeMap()).isEqualTo(expectedDocDParentTypeMap);
+
+ assertThat(searchResults.get(1).getGenericDocument()).isEqualTo(docC);
+ assertThat(searchResults.get(1).getParentTypeMap()).isEqualTo(expectedDocCParentTypeMap);
+
+ assertThat(searchResults.get(2).getGenericDocument()).isEqualTo(docB);
+ assertThat(searchResults.get(2).getParentTypeMap()).isEqualTo(expectedDocBParentTypeMap);
+
+ assertThat(searchResults.get(3).getGenericDocument()).isEqualTo(docA);
+ assertThat(searchResults.get(3).getParentTypeMap()).isEqualTo(expectedDocAParentTypeMap);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SEARCH_RESULT_PARENT_TYPES)
+ public void testQuery_wildcardProjection_polymorphism() throws Exception {
+ assumeTrue(mDb1.getFeatures().isFeatureSupported(Features.SCHEMA_ADD_PARENT_TYPE));
+ assumeTrue(mDb1.getFeatures().isFeatureSupported(Features.SEARCH_RESULT_PARENT_TYPES));
+
+ AppSearchSchema messageSchema = new AppSearchSchema.Builder("Message")
+ .addProperty(new StringPropertyConfig.Builder("sender")
+ .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
+ .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .build())
+ .addProperty(new StringPropertyConfig.Builder("content")
+ .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
+ .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .build())
+ .build();
+ AppSearchSchema textSchema = new AppSearchSchema.Builder("Text")
+ .addParentType("Message")
+ .addProperty(new StringPropertyConfig.Builder("sender")
+ .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
+ .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .build())
+ .addProperty(new StringPropertyConfig.Builder("content")
+ .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
+ .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .build())
+ .build();
+ AppSearchSchema emailSchema = new AppSearchSchema.Builder("Email")
+ .addParentType("Message")
+ .addProperty(new StringPropertyConfig.Builder("sender")
+ .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
+ .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .build())
+ .addProperty(new StringPropertyConfig.Builder("content")
+ .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
+ .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .build())
+ .build();
+
+ // Schema registration
+ mDb1.setSchemaAsync(new SetSchemaRequest.Builder()
+ .addSchemas(messageSchema, textSchema, emailSchema).build()).get();
+
+ // Index two child documents
+ GenericDocument text = new GenericDocument.Builder<>("namespace", "id1", "Text")
+ .setCreationTimestampMillis(1000)
+ .setPropertyString("sender", "Some sender")
+ .setPropertyString("content", "Some note")
+ .build();
+ GenericDocument email = new GenericDocument.Builder<>("namespace", "id2", "Email")
+ .setCreationTimestampMillis(1000)
+ .setPropertyString("sender", "Some sender")
+ .setPropertyString("content", "Some note")
+ .build();
+ checkIsBatchResultSuccess(mDb1.putAsync(new PutDocumentsRequest.Builder()
+ .addGenericDocuments(email, text).build()));
+
+ SearchResults searchResults = mDb1.search("Some", new SearchSpec.Builder()
+ .addFilterSchemas("Message")
+ .addProjection(SearchSpec.SCHEMA_TYPE_WILDCARD, ImmutableList.of("sender"))
+ .addFilterProperties(SearchSpec.SCHEMA_TYPE_WILDCARD, ImmutableList.of("content"))
+ .build());
+ List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+
+ // We specified the parent document in the filter schemas, but only indexed child documents.
+ // As we also specified a wildcard schema type projection, it should apply to the child docs
+ // The content property must not appear. Also emailNoContent should not appear as we are
+ // filter on the content property
+ GenericDocument expectedText = new GenericDocument.Builder<>("namespace", "id1", "Text")
+ .setCreationTimestampMillis(1000)
+ .setPropertyString("sender", "Some sender")
+ .build();
+ GenericDocument expectedEmail = new GenericDocument.Builder<>("namespace", "id2", "Email")
+ .setCreationTimestampMillis(1000)
+ .setPropertyString("sender", "Some sender")
+ .build();
+ assertThat(documents).containsExactly(expectedText, expectedEmail);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SEARCH_RESULT_PARENT_TYPES)
+ public void testQuery_wildcardFilterSchema_polymorphism() throws Exception {
+ assumeTrue(mDb1.getFeatures().isFeatureSupported(Features.SCHEMA_ADD_PARENT_TYPE));
+ assumeTrue(mDb1.getFeatures().isFeatureSupported(Features.SEARCH_RESULT_PARENT_TYPES));
+
+ AppSearchSchema messageSchema = new AppSearchSchema.Builder("Message")
+ .addProperty(new StringPropertyConfig.Builder("content")
+ .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
+ .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .build())
+ .build();
+ AppSearchSchema textSchema = new AppSearchSchema.Builder("Text")
+ .addParentType("Message")
+ .addProperty(new StringPropertyConfig.Builder("content")
+ .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
+ .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .build())
+ .addProperty(new StringPropertyConfig.Builder("carrier")
+ .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
+ .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .build())
+ .build();
+ AppSearchSchema emailSchema = new AppSearchSchema.Builder("Email")
+ .addParentType("Message")
+ .addProperty(new StringPropertyConfig.Builder("content")
+ .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
+ .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .build())
+ .addProperty(new StringPropertyConfig.Builder("attachment")
+ .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
+ .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .build())
+ .build();
+
+ // Schema registration
+ mDb1.setSchemaAsync(new SetSchemaRequest.Builder()
+ .addSchemas(messageSchema, textSchema, emailSchema).build()).get();
+
+ // Index two child documents
+ GenericDocument text = new GenericDocument.Builder<>("namespace", "id1", "Text")
+ .setCreationTimestampMillis(1000)
+ .setPropertyString("content", "Some note")
+ .setPropertyString("carrier", "Network Inc")
+ .build();
+ GenericDocument email = new GenericDocument.Builder<>("namespace", "id2", "Email")
+ .setCreationTimestampMillis(1000)
+ .setPropertyString("content", "Some note")
+ .setPropertyString("attachment", "Network report")
+ .build();
+
+ checkIsBatchResultSuccess(mDb1.putAsync(new PutDocumentsRequest.Builder()
+ .addGenericDocuments(email, text).build()));
+
+ // Both email and text would match for "Network", but only text should match as it is in the
+ // right property
+ SearchResults searchResults = mDb1.search("Network", new SearchSpec.Builder()
+ .addFilterSchemas("Message")
+ .addFilterProperties(SearchSpec.SCHEMA_TYPE_WILDCARD, ImmutableList.of("carrier"))
+ .build());
+ List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+
+ // We specified the parent document in the filter schemas, but only indexed child documents.
+ // As we also specified a wildcard schema type projection, it should apply to the child docs
+ // The content property must not appear. Also emailNoContent should not appear as we are
+ // filter on the content property
+ GenericDocument expectedText = new GenericDocument.Builder<>("namespace", "id1", "Text")
+ .setCreationTimestampMillis(1000)
+ .setPropertyString("content", "Some note")
+ .setPropertyString("carrier", "Network Inc")
+ .build();
+ assertThat(documents).containsExactly(expectedText);
+ }
+
+ @Test
@RequiresFlagsEnabled(Flags.FLAG_ENABLE_SCORABLE_PROPERTY)
public void testRankWithNonScorableProperty() throws Exception {
// TODO(b/379923400): Implement this test.
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/SearchResultCtsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/SearchResultCtsTest.java
index 382d5272..3761d6f 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/SearchResultCtsTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/SearchResultCtsTest.java
@@ -31,6 +31,9 @@
import org.junit.Rule;
import org.junit.Test;
+import java.util.List;
+import java.util.Map;
+
public class SearchResultCtsTest {
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@@ -223,4 +226,55 @@
assertThat(rebuild.getInformationalRankingSignals())
.containsExactly(3.0, 4.0, 5.0).inOrder();
}
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SEARCH_RESULT_PARENT_TYPES)
+ public void testBuildSearchResult_parentTypeMap() {
+ AppSearchEmail email = new AppSearchEmail.Builder("namespace1", "id1")
+ .setBody("Hello World.")
+ .build();
+ SearchResult searchResult = new SearchResult.Builder("packageName", "databaseName")
+ .setGenericDocument(email)
+ .setParentTypeMap(Map.of(
+ "schema1", List.of("parent1", "parent2"),
+ "schema2", List.of("parent3", "parent4")
+ ))
+ .build();
+
+ assertThat(searchResult.getParentTypeMap())
+ .containsExactly(
+ "schema1", List.of("parent1", "parent2"),
+ "schema2", List.of("parent3", "parent4")
+ ).inOrder();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SEARCH_RESULT_PARENT_TYPES)
+ public void testRebuild_parentTypeMap() {
+ AppSearchEmail email = new AppSearchEmail.Builder("namespace1", "id1")
+ .setBody("Hello World.")
+ .build();
+
+ SearchResult.Builder searchResultBuilder =
+ new SearchResult.Builder("packageName", "databaseName")
+ .setGenericDocument(email)
+ .setParentTypeMap(Map.of(
+ "schema1", List.of("parent1", "parent2"),
+ "schema2", List.of("parent3", "parent4")
+ ));
+
+ SearchResult original = searchResultBuilder.build();
+ SearchResult rebuild = searchResultBuilder
+ .setParentTypeMap(Map.of("schema3", List.of("parent5", "parent6"))).build();
+
+ // Rebuild won't effect the original object
+ assertThat(original.getParentTypeMap())
+ .containsExactly(
+ "schema1", List.of("parent1", "parent2"),
+ "schema2", List.of("parent3", "parent4")
+ ).inOrder();
+
+ assertThat(rebuild.getParentTypeMap())
+ .containsExactly("schema3", List.of("parent5", "parent6")).inOrder();
+ }
}
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/flags/FlagsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/flags/FlagsTest.java
index e0fd2a5..6c7023a 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/flags/FlagsTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/flags/FlagsTest.java
@@ -177,4 +177,11 @@
assertThat(Flags.FLAG_ENABLE_SCORABLE_PROPERTY)
.isEqualTo("com.android.appsearch.flags.enable_scorable_property");
}
+
+ @Test
+ public void testFlagValue_enableSearchResultParentTypes() {
+ assertThat(Flags.FLAG_ENABLE_SEARCH_RESULT_PARENT_TYPES)
+ .isEqualTo(
+ "com.android.appsearch.flags.enable_search_result_parent_types");
+ }
}
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/DocumentClassFactory.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/DocumentClassFactory.java
index 90033ee..2ae65ce 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/DocumentClassFactory.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/DocumentClassFactory.java
@@ -17,7 +17,6 @@
package androidx.appsearch.app;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.appsearch.exceptions.AppSearchException;
import java.util.List;
@@ -59,10 +58,19 @@
/**
* Converts a {@link androidx.appsearch.app.GenericDocument} into an instance of the document
- * class. For nested document properties, this method should pass {@code documentClassMap} down
- * to the nested calls of {@link GenericDocument#toDocumentClass(Class, Map)}.
+ * class. For nested document properties, this method should pass
+ * {@code documentClassMappingContext} down to the nested calls of
+ * {@link GenericDocument#toDocumentClass(Class, DocumentClassMappingContext)}.
+ *
+ * @param genericDoc The document to convert.
+ * @param documentClassMappingContext The context object that holds mapping information for
+ * document classes and their parent types. This context
+ * typically comes from
+ * {@link SearchResult#getDocument(Class, Map)}.
*/
@NonNull
+ @ExperimentalAppSearchApi
T fromGenericDocument(@NonNull GenericDocument genericDoc,
- @Nullable Map<String, List<String>> documentClassMap) throws AppSearchException;
+ @NonNull DocumentClassMappingContext documentClassMappingContext)
+ throws AppSearchException;
}
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/DocumentClassMappingContext.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/DocumentClassMappingContext.java
new file mode 100644
index 0000000..798e839
--- /dev/null
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/DocumentClassMappingContext.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+// @exportToFramework:skipFile()
+package androidx.appsearch.app;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.appsearch.annotation.Document;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A context object that holds mapping information for document classes and their parent types.
+ *
+ * <p>This class encapsulates the {@code documentClassMap} and {@code parentTypeMap} used during
+ * the deserialization of {@link GenericDocument} instances into specific document classes.
+ *
+ * @see GenericDocument#toDocumentClass(Class, DocumentClassMappingContext)
+ */
+public class DocumentClassMappingContext {
+ /** An empty {@link DocumentClassMappingContext} instance. */
+ @ExperimentalAppSearchApi
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public static final DocumentClassMappingContext EMPTY =
+ new DocumentClassMappingContext(/* documentClassMap= */null, /* parentTypeMap= */null);
+
+ private final @NonNull Map<String, List<String>> mDocumentClassMap;
+ private final @NonNull Map<String, List<String>> mParentTypeMap;
+
+ /**
+ * Constructs a new {@link DocumentClassMappingContext}.
+ *
+ * @param documentClassMap A map from AppSearch's type name specified by {@link Document#name()}
+ * to the list of the fully qualified names of the corresponding
+ * document classes. In most cases, passing the value returned by
+ * {@link AppSearchDocumentClassMap#getGlobalMap()} will be sufficient.
+ * @param parentTypeMap A map from AppSearch's type name specified by {@link Document#name()}
+ * to the list of its parent type names. In most cases, passing the
+ * value returned by {@link SearchResult#getParentTypeMap()} will be
+ * sufficient.
+ */
+ @ExperimentalAppSearchApi
+ public DocumentClassMappingContext(
+ @Nullable Map<String, List<String>> documentClassMap,
+ @Nullable Map<String, List<String>> parentTypeMap) {
+ mDocumentClassMap = documentClassMap != null ? Collections.unmodifiableMap(documentClassMap)
+ : Collections.emptyMap();
+ mParentTypeMap = parentTypeMap != null ? Collections.unmodifiableMap(parentTypeMap)
+ : Collections.emptyMap();
+ }
+
+ /**
+ * Returns the document class map.
+ */
+ @NonNull
+ @ExperimentalAppSearchApi
+ public Map<String, List<String>> getDocumentClassMap() {
+ return mDocumentClassMap;
+ }
+
+ /**
+ * Returns the parent type map.
+ */
+ @NonNull
+ @ExperimentalAppSearchApi
+ public Map<String, List<String>> getParentTypeMap() {
+ return mParentTypeMap;
+ }
+}
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/Features.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/Features.java
index 9f43cc1..0777ac6 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/Features.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/Features.java
@@ -280,6 +280,15 @@
String BLOB_STORAGE = "BLOB_STORAGE";
/**
+ * Feature for {@link #isFeatureSupported(String)}. This feature covers whether to wrap the
+ * parent types of a document in the corresponding
+ * {@link androidx.appsearch.app.SearchResult}, instead of in
+ * {@link androidx.appsearch.app.GenericDocument}.
+ */
+ @ExperimentalAppSearchApi
+ String SEARCH_RESULT_PARENT_TYPES = "SEARCH_RESULT_PARENT_TYPES";
+
+ /**
* Returns whether a feature is supported at run-time. Feature support depends on the
* feature in question, the AppSearch backend being used and the Android version of the
* device.
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/GenericDocument.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/GenericDocument.java
index d99dc3b..04e5586 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/GenericDocument.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/GenericDocument.java
@@ -257,9 +257,13 @@
* Returns the list of parent types of the {@link GenericDocument}'s type.
*
* <p>It is guaranteed that child types appear before parent types in the list.
+ *
+ * @deprecated Parent types should no longer be set in {@link GenericDocument}. Use
+ * {@link SearchResult.Builder#getParentTypeMap()} instead.
* <!--@exportToFramework:hide-->
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ @Deprecated
@Nullable
public List<String> getParentTypes() {
List<String> result = mDocumentParcel.getParentTypes();
@@ -1089,8 +1093,9 @@
* @see GenericDocument#fromDocumentClass
*/
@NonNull
+ @OptIn(markerClass = ExperimentalAppSearchApi.class)
public <T> T toDocumentClass(@NonNull Class<T> documentClass) throws AppSearchException {
- return toDocumentClass(documentClass, /* documentClassMap= */null);
+ return toDocumentClass(documentClass, DocumentClassMappingContext.EMPTY);
}
/**
@@ -1106,11 +1111,12 @@
*
* <p>If this GenericDocument's type is recorded as a subtype of the provided
* {@code documentClass}, the method will find an AppSearch document class, using the provided
- * {@code documentClassMap}, that is the most concrete and assignable to {@code documentClass},
- * and then deserialize to that class instead. This allows for more specific and accurate
- * deserialization of GenericDocuments. If {@code documentClassMap} is null or we are not
- * able to find a candidate assignable to {@code documentClass}, the method will deserialize
- * to {@code documentClass} directly.
+ * {@code documentClassMappingContext}, that is the most concrete and assignable to
+ * {@code documentClass}, and then deserialize to that class instead. This allows for more
+ * specific and accurate deserialization of GenericDocuments. If
+ * {@code documentClassMappingContext} has information missing or we are not able to find a
+ * candidate assignable to {@code documentClass}, the method will deserialize to
+ * {@code documentClass} directly.
*
* <p>Assignability is determined by the programing language's type system, and which type is
* more concrete is determined by AppSearch's type system specified via
@@ -1118,13 +1124,15 @@
* {@link Document#parent()}.
*
* <p>For nested document properties, this method will be called recursively, and
- * {@code documentClassMap} will be passed down to the recursive calls of this method.
+ * {@code documentClassMappingContext} will be passed down to the recursive
+ * calls of this method.
*
- * @param documentClass a class annotated with {@link Document}
- * @param documentClassMap a map from AppSearch's type name specified by {@link Document#name()}
- * to the list of the fully qualified names of the corresponding
- * document classes. In most cases, passing the value returned by
- * {@link AppSearchDocumentClassMap#getGlobalMap()} will be sufficient.
+ * <p>For most use cases, it is recommended to utilize
+ * {@link SearchResult#getDocument(Class, Map)} instead of calling this method directly. This
+ * avoids the need to manually create a {@link DocumentClassMappingContext}.
+ *
+ * @param documentClass a class annotated with {@link Document}
+ * @param documentClassMappingContext a {@link DocumentClassMappingContext} instance
* @return an instance of the document class after being converted from a
* {@link GenericDocument}
* @throws AppSearchException if no factory for this document class could be found on the
@@ -1132,20 +1140,26 @@
* @see GenericDocument#fromDocumentClass
*/
@NonNull
+ @ExperimentalAppSearchApi
public <T> T toDocumentClass(@NonNull Class<T> documentClass,
- @Nullable Map<String, List<String>> documentClassMap) throws AppSearchException {
+ @NonNull DocumentClassMappingContext documentClassMappingContext)
+ throws AppSearchException {
Preconditions.checkNotNull(documentClass);
+ Preconditions.checkNotNull(documentClassMappingContext);
DocumentClassFactoryRegistry registry = DocumentClassFactoryRegistry.getInstance();
Class<? extends T> targetClass = findTargetClassToDeserialize(documentClass,
- documentClassMap);
+ documentClassMappingContext.getDocumentClassMap(),
+ documentClassMappingContext.getParentTypeMap());
DocumentClassFactory<? extends T> factory = registry.getOrCreateFactory(targetClass);
- return factory.fromGenericDocument(this, documentClassMap);
+ return factory.fromGenericDocument(this, documentClassMappingContext);
}
/**
* Find a target class that is assignable to {@code documentClass} to deserialize this
- * document, based on the provided document class map. If the provided map is null, return
- * {@code documentClass} directly.
+ * document, based on the provided {@code documentClassMap} and {@code parentTypeMap}. If
+ * {@code documentClassMap} is empty, return {@code documentClass} directly.
+ * If {@code parentTypeMap} does not contain the required parent type information, this
+ * method will try the deprecated {@link #getParentTypes()}.
*
* <p>This method first tries to find a target class corresponding to the document's own type.
* If that fails, it then tries to find a class corresponding to the document's parent type.
@@ -1153,8 +1167,9 @@
*/
@NonNull
private <T> Class<? extends T> findTargetClassToDeserialize(@NonNull Class<T> documentClass,
- @Nullable Map<String, List<String>> documentClassMap) {
- if (documentClassMap == null) {
+ @NonNull Map<String, List<String>> documentClassMap,
+ @NonNull Map<String, List<String>> parentTypeMap) {
+ if (documentClassMap.isEmpty()) {
return documentClass;
}
@@ -1166,7 +1181,12 @@
}
// Find the target class by parent types.
- List<String> parentTypes = getParentTypes();
+ List<String> parentTypes;
+ if (parentTypeMap.containsKey(getSchemaType())) {
+ parentTypes = parentTypeMap.get(getSchemaType());
+ } else {
+ parentTypes = getParentTypes();
+ }
if (parentTypes != null) {
for (int i = 0; i < parentTypes.size(); ++i) {
targetClass = AppSearchDocumentClassMap.getAssignableClassBySchemaName(
@@ -1422,13 +1442,16 @@
* Sets the list of parent types of the {@link GenericDocument}'s type.
*
* <p>Child types must appear before parent types in the list.
+ *
+ * @deprecated Parent types should no longer be set in {@link GenericDocument}. Use
+ * {@link SearchResult.Builder#setParentTypeMap(Map)} instead.
* <!--@exportToFramework:hide-->
*/
@CanIgnoreReturnValue
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ @Deprecated
@NonNull
- public BuilderType setParentTypes(@NonNull List<String> parentTypes) {
- Preconditions.checkNotNull(parentTypes);
+ public BuilderType setParentTypes(@Nullable List<String> parentTypes) {
mDocumentParcelBuilder.setParentTypes(parentTypes);
return mBuilderTypeInstance;
}
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchResult.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchResult.java
index efa6248..736b84e 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchResult.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchResult.java
@@ -16,11 +16,13 @@
package androidx.appsearch.app;
+import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.OptIn;
import androidx.annotation.RequiresFeature;
import androidx.annotation.RestrictTo;
import androidx.appsearch.annotation.CanIgnoreReturnValue;
@@ -32,6 +34,8 @@
import androidx.appsearch.safeparcel.SafeParcelable;
import androidx.appsearch.safeparcel.stub.StubCreators.MatchInfoCreator;
import androidx.appsearch.safeparcel.stub.StubCreators.SearchResultCreator;
+import androidx.appsearch.util.BundleUtil;
+import androidx.collection.ArrayMap;
import androidx.core.util.ObjectsCompat;
import androidx.core.util.Preconditions;
@@ -39,6 +43,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
* This class represents one of the results obtained from an AppSearch query.
@@ -78,6 +83,20 @@
@NonNull
@Field(id = 7, getter = "getInformationalRankingSignals")
private final List<Double> mInformationalRankingSignals;
+ /**
+ * Holds the map from schema type names to the list of their parent types.
+ *
+ * <p>The map includes entries for the {@link GenericDocument}'s own type and all of the
+ * nested documents' types. Child types are guaranteed to appear before parent types in each
+ * list.
+ *
+ * <p>Parent types include transitive parents.
+ *
+ * <p>All schema names in this map are un-prefixed, for both keys and values.
+ */
+ @NonNull
+ @Field(id = 8, getter = "getParentTypeMap")
+ private final Bundle mParentTypeMap;
/** Cache of the {@link GenericDocument}. Comes from mDocument at first use. */
@@ -97,7 +116,8 @@
@Param(id = 4) @NonNull String databaseName,
@Param(id = 5) double rankingSignal,
@Param(id = 6) @NonNull List<SearchResult> joinedResults,
- @Param(id = 7) @Nullable List<Double> informationalRankingSignals) {
+ @Param(id = 7) @Nullable List<Double> informationalRankingSignals,
+ @Param(id = 8) @Nullable Bundle parentTypeMap) {
mDocument = Preconditions.checkNotNull(document);
mMatchInfos = Preconditions.checkNotNull(matchInfos);
mPackageName = Preconditions.checkNotNull(packageName);
@@ -110,6 +130,11 @@
} else {
mInformationalRankingSignals = Collections.emptyList();
}
+ if (parentTypeMap != null) {
+ mParentTypeMap = parentTypeMap;
+ } else {
+ mParentTypeMap = Bundle.EMPTY;
+ }
}
// @exportToFramework:startStrip()
@@ -118,12 +143,15 @@
*
* <p>This is equivalent to calling {@code getGenericDocument().toDocumentClass(T.class)}.
*
+ * <p>Calling this function repeatedly is inefficient. Prefer to retain the object returned
+ * by this function, rather than calling it multiple times.
+ *
* @param documentClass the document class to be passed to
- * {@link GenericDocument#toDocumentClass}.
+ * {@link GenericDocument#toDocumentClass(java.lang.Class)}.
* @return Document object which matched the query.
* @throws AppSearchException if no factory for this document class could be found on the
* classpath.
- * @see GenericDocument#toDocumentClass
+ * @see GenericDocument#toDocumentClass(java.lang.Class)
*/
@NonNull
public <T> T getDocument(@NonNull java.lang.Class<T> documentClass) throws AppSearchException {
@@ -134,22 +162,31 @@
* Contains the matching document, converted to the given document class.
*
* <p>This is equivalent to calling {@code getGenericDocument().toDocumentClass(T.class,
- * documentClassMap)}.
+ * new DocumentClassMappingContext(documentClassMap, getParentTypeMap()))}.
+ *
+ * <p>Calling this function repeatedly is inefficient. Prefer to retain the object returned
+ * by this function, rather than calling it multiple times.
*
* @param documentClass the document class to be passed to
- * {@link GenericDocument#toDocumentClass}.
- * @param documentClassMap the document class map to be passed to
- * {@link GenericDocument#toDocumentClass}.
+ * {@link GenericDocument#toDocumentClass(java.lang.Class, DocumentClassMappingContext)}.
+ * @param documentClassMap A map from AppSearch's type name specified by
+ * {@link androidx.appsearch.annotation.Document#name()}
+ * to the list of the fully qualified names of the corresponding
+ * document classes. In most cases, passing the value returned by
+ * {@link AppSearchDocumentClassMap#getGlobalMap()} will be sufficient.
* @return Document object which matched the query.
* @throws AppSearchException if no factory for this document class could be found on the
* classpath.
- * @see GenericDocument#toDocumentClass
+ * @see GenericDocument#toDocumentClass(java.lang.Class, DocumentClassMappingContext)
*/
@NonNull
+ @OptIn(markerClass = ExperimentalAppSearchApi.class)
public <T> T getDocument(@NonNull java.lang.Class<T> documentClass,
@Nullable Map<String, List<String>> documentClassMap) throws AppSearchException {
Preconditions.checkNotNull(documentClass);
- return getGenericDocument().toDocumentClass(documentClass, documentClassMap);
+ return getGenericDocument().toDocumentClass(documentClass,
+ new DocumentClassMappingContext(documentClassMap,
+ getParentTypeMap()));
}
// @exportToFramework:endStrip()
@@ -253,6 +290,33 @@
}
/**
+ * Returns the map from schema type names to the list of their parent types.
+ *
+ * <p>The map includes entries for the {@link GenericDocument}'s own type and all of the
+ * nested documents' types. Child types are guaranteed to appear before parent types in each
+ * list.
+ *
+ * <p>Parent types include transitive parents.
+ *
+ * <p>Calling this function repeatedly is inefficient. Prefer to retain the Map returned
+ * by this function, rather than calling it multiple times.
+ */
+ @NonNull
+ @ExperimentalAppSearchApi
+ @FlaggedApi(Flags.FLAG_ENABLE_SEARCH_RESULT_PARENT_TYPES)
+ public Map<String, List<String>> getParentTypeMap() {
+ Set<String> schemaTypes = mParentTypeMap.keySet();
+ Map<String, List<String>> parentTypeMap = new ArrayMap<>(schemaTypes.size());
+ for (String schemaType : schemaTypes) {
+ ArrayList<String> parentTypes = mParentTypeMap.getStringArrayList(schemaType);
+ if (parentTypes != null) {
+ parentTypeMap.put(schemaType, parentTypes);
+ }
+ }
+ return parentTypeMap;
+ }
+
+ /**
* Gets a list of {@link SearchResult} joined from the join operation.
*
* <p> These joined documents match the outer document as specified in the {@link JoinSpec}
@@ -286,6 +350,7 @@
private GenericDocument mGenericDocument;
private double mRankingSignal;
private List<Double> mInformationalRankingSignals = new ArrayList<>();
+ private Bundle mParentTypeMap = new Bundle();
private List<SearchResult> mJoinedResults = new ArrayList<>();
private boolean mBuilt = false;
@@ -302,6 +367,7 @@
/** @exportToFramework:hide */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ @OptIn(markerClass = ExperimentalAppSearchApi.class)
public Builder(@NonNull SearchResult searchResult) {
Preconditions.checkNotNull(searchResult);
mPackageName = searchResult.getPackageName();
@@ -310,6 +376,7 @@
mRankingSignal = searchResult.getRankingSignal();
mInformationalRankingSignals = new ArrayList<>(
searchResult.getInformationalRankingSignals());
+ setParentTypeMap(searchResult.getParentTypeMap());
List<MatchInfo> matchInfos = searchResult.getMatchInfos();
for (int i = 0; i < matchInfos.size(); i++) {
addMatchInfo(new MatchInfo.Builder(matchInfos.get(i)).build());
@@ -381,6 +448,46 @@
return this;
}
+ /**
+ * Sets the map from schema type names to the list of their parent types.
+ *
+ * <p>The map should include entries for the {@link GenericDocument}'s own type and all
+ * of the nested documents' types.
+ *
+ * <p>Child types must appear before parent types in each list. Otherwise, the
+ * <!--@exportToFramework:ifJetpack()-->
+ * {@link GenericDocument#toDocumentClass(java.lang.Class, DocumentClassMappingContext)}
+ * <!--@exportToFramework:else()
+ * GenericDocument's toDocumentClass
+ * -->
+ * method may not correctly identify the most concrete type. This could lead to unintended
+ * deserialization into a more general type instead of a more specific type.
+ *
+ * <p>Parent types should include transitive parents.
+ */
+ @CanIgnoreReturnValue
+ @ExperimentalAppSearchApi
+ @FlaggedApi(Flags.FLAG_ENABLE_SEARCH_RESULT_PARENT_TYPES)
+ @NonNull
+ public Builder setParentTypeMap(@NonNull Map<String, List<String>> parentTypeMap) {
+ Preconditions.checkNotNull(parentTypeMap);
+ resetIfBuilt();
+ mParentTypeMap.clear();
+
+ for (Map.Entry<String, List<String>> entry : parentTypeMap.entrySet()) {
+ Preconditions.checkNotNull(entry.getKey());
+ Preconditions.checkNotNull(entry.getValue());
+
+ ArrayList<String> parentTypes = new ArrayList<>(entry.getValue().size());
+ for (int i = 0; i < entry.getValue().size(); i++) {
+ String parentType = entry.getValue().get(i);
+ parentTypes.add(Preconditions.checkNotNull(parentType));
+ }
+ mParentTypeMap.putStringArrayList(entry.getKey(), parentTypes);
+ }
+ return this;
+ }
+
/**
* Adds a {@link SearchResult} that was joined by the {@link JoinSpec}.
@@ -419,7 +526,8 @@
mDatabaseName,
mRankingSignal,
mJoinedResults,
- mInformationalRankingSignals);
+ mInformationalRankingSignals,
+ mParentTypeMap);
}
private void resetIfBuilt() {
@@ -427,6 +535,7 @@
mMatchInfos = new ArrayList<>(mMatchInfos);
mJoinedResults = new ArrayList<>(mJoinedResults);
mInformationalRankingSignals = new ArrayList<>(mInformationalRankingSignals);
+ mParentTypeMap = BundleUtil.deepCopy(mParentTypeMap);
mBuilt = false;
}
}
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/flags/Flags.java b/appsearch/appsearch/src/main/java/androidx/appsearch/flags/Flags.java
index 9b77160..8df00e8 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/flags/Flags.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/flags/Flags.java
@@ -182,6 +182,14 @@
public static final String FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS =
FLAG_PREFIX + "enable_additional_builder_copy_constructors";
+ /**
+ * Enables wrapping the parent types of a document in the corresponding
+ * {@link androidx.appsearch.app.SearchResult}, instead of in
+ * {@link androidx.appsearch.app.GenericDocument}.
+ */
+ public static final String FLAG_ENABLE_SEARCH_RESULT_PARENT_TYPES =
+ FLAG_PREFIX + "enable_search_result_parent_types";
+
// Whether the features should be enabled.
//
// In Jetpack, those should always return true.
@@ -357,4 +365,13 @@
public static boolean enableScorableProperty() {
return true;
}
+
+ /**
+ * Whether to wrap the parent types of a document in the corresponding
+ * {@link androidx.appsearch.app.SearchResult}, instead of in
+ * {@link androidx.appsearch.app.GenericDocument}.
+ */
+ public static boolean enableSearchResultParentTypes() {
+ return true;
+ }
}
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/safeparcel/GenericDocumentParcel.java b/appsearch/appsearch/src/main/java/androidx/appsearch/safeparcel/GenericDocumentParcel.java
index bc61a06..770ee0b 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/safeparcel/GenericDocumentParcel.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/safeparcel/GenericDocumentParcel.java
@@ -434,9 +434,12 @@
*/
@CanIgnoreReturnValue
@NonNull
- public Builder setParentTypes(@NonNull List<String> parentTypes) {
- Objects.requireNonNull(parentTypes);
- mParentTypes = new ArrayList<>(parentTypes);
+ public Builder setParentTypes(@Nullable List<String> parentTypes) {
+ if (parentTypes == null) {
+ mParentTypes = null;
+ } else {
+ mParentTypes = new ArrayList<>(parentTypes);
+ }
return this;
}
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/FromGenericDocumentCodeGenerator.java b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/FromGenericDocumentCodeGenerator.java
index 7296edd..040cd42 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/FromGenericDocumentCodeGenerator.java
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/FromGenericDocumentCodeGenerator.java
@@ -18,6 +18,7 @@
import static androidx.appsearch.compiler.CodegenUtils.createNewArrayExpr;
import static androidx.appsearch.compiler.IntrospectionHelper.APPSEARCH_EXCEPTION_CLASS;
+import static androidx.appsearch.compiler.IntrospectionHelper.DOCUMENT_CLASS_MAPPING_CONTEXT_CLASS;
import static androidx.appsearch.compiler.IntrospectionHelper.GENERIC_DOCUMENT_CLASS;
import androidx.annotation.NonNull;
@@ -29,10 +30,8 @@
import androidx.appsearch.compiler.annotationwrapper.SerializerClass;
import androidx.appsearch.compiler.annotationwrapper.StringPropertyAnnotation;
-import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.MethodSpec;
-import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
@@ -76,16 +75,12 @@
private MethodSpec createFromGenericDocumentMethod() {
// Method header
TypeName documentClass = TypeName.get(mModel.getClassElement().asType());
- // The type of documentClassMap is Map<String, List<String>>.
- TypeName documentClassMapType = ParameterizedTypeName.get(ClassName.get(Map.class),
- ClassName.get(String.class),
- ParameterizedTypeName.get(ClassName.get(List.class), ClassName.get(String.class)));
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("fromGenericDocument")
.addModifiers(Modifier.PUBLIC)
.returns(documentClass)
.addAnnotation(Override.class)
.addParameter(GENERIC_DOCUMENT_CLASS, "genericDoc")
- .addParameter(documentClassMapType, "documentClassMap")
+ .addParameter(DOCUMENT_CLASS_MAPPING_CONTEXT_CLASS, "documentClassMappingContext")
.addException(APPSEARCH_EXCEPTION_CLASS);
// Unpack properties from the GenericDocument into the format desired by the document class.
@@ -489,7 +484,9 @@
getterOrField.getJvmName(), ArrayList.class, getterOrField.getJvmName())
.beginControlFlow("for (int i = 0; i < $NCopy.length; i++)",
getterOrField.getJvmName())
- .addStatement("$NConv.add($NCopy[i].toDocumentClass($T.class, documentClassMap))",
+ .addStatement(
+ "$NConv.add($NCopy[i].toDocumentClass($T.class, "
+ + "documentClassMappingContext))",
getterOrField.getJvmName(),
getterOrField.getJvmName(),
getterOrField.getComponentType())
@@ -611,7 +608,9 @@
getterOrField.getJvmName())
.beginControlFlow("for (int i = 0; i < $NCopy.length; i++)",
getterOrField.getJvmName())
- .addStatement("$NConv[i] = $NCopy[i].toDocumentClass($T.class, documentClassMap)",
+ .addStatement(
+ "$NConv[i] = $NCopy[i].toDocumentClass($T.class, "
+ + "documentClassMappingContext)",
getterOrField.getJvmName(),
getterOrField.getJvmName(),
getterOrField.getComponentType())
@@ -728,7 +727,8 @@
.addStatement("$T $NConv = null",
getterOrField.getJvmType(), getterOrField.getJvmName())
.beginControlFlow("if ($NCopy != null)", getterOrField.getJvmName())
- .addStatement("$NConv = $NCopy.toDocumentClass($T.class, documentClassMap)",
+ .addStatement(
+ "$NConv = $NCopy.toDocumentClass($T.class, documentClassMappingContext)",
getterOrField.getJvmName(),
getterOrField.getJvmName(),
getterOrField.getJvmType())
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/IntrospectionHelper.java b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/IntrospectionHelper.java
index dba768a..5a7bd2e 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/IntrospectionHelper.java
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/IntrospectionHelper.java
@@ -105,6 +105,9 @@
static final ClassName RESTRICT_TO_SCOPE_CLASS =
RESTRICT_TO_ANNOTATION_CLASS.nestedClass("Scope");
+ static final ClassName DOCUMENT_CLASS_MAPPING_CONTEXT_CLASS =
+ ClassName.get(APPSEARCH_PKG, "DocumentClassMappingContext");
+
public final TypeMirror mStringType;
public final TypeMirror mLongPrimitiveType;
public final TypeMirror mIntPrimitiveType;
diff --git a/appsearch/compiler/src/test/java/androidx/appsearch/compiler/AppSearchCompilerTest.java b/appsearch/compiler/src/test/java/androidx/appsearch/compiler/AppSearchCompilerTest.java
index fa7b7aa..efa452b 100644
--- a/appsearch/compiler/src/test/java/androidx/appsearch/compiler/AppSearchCompilerTest.java
+++ b/appsearch/compiler/src/test/java/androidx/appsearch/compiler/AppSearchCompilerTest.java
@@ -2335,7 +2335,7 @@
checkResultContains("Thing.java",
"Thing document = Thing.create(getIdConv, getNamespaceConv)");
checkResultContains("Gift.java",
- "thingConv = thingCopy.toDocumentClass(Thing.class, documentClassMap)");
+ "thingConv = thingCopy.toDocumentClass(Thing.class, documentClassMappingContext)");
checkEqualsGolden("Gift.java");
}
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAllSingleTypes.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAllSingleTypes.JAVA
index 387d97d..089357c 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAllSingleTypes.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAllSingleTypes.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.EmbeddingVector;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
@@ -15,7 +16,6 @@
import java.lang.String;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -109,7 +109,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
String[] stringPropCopy = genericDoc.getPropertyStringArray("stringProp");
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAllSpecialFields_field.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAllSpecialFields_field.JAVA
index b9053a0..339def6 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAllSpecialFields_field.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAllSpecialFields_field.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -49,7 +49,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
long creationTsConv = genericDoc.getCreationTimestampMillis();
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAllSpecialFields_getter.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAllSpecialFields_getter.JAVA
index 66843ec..44f7683 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAllSpecialFields_getter.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAllSpecialFields_getter.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -49,7 +49,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
int scoreConv = genericDoc.getScore();
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAnnotationOnClassGetter.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAnnotationOnClassGetter.JAVA
index 89a4f42..349343c 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAnnotationOnClassGetter.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAnnotationOnClassGetter.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -46,7 +46,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
int getPriceConv = (int) genericDoc.getPropertyLong("price");
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAnnotationOnClassGetterUsingFactory.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAnnotationOnClassGetterUsingFactory.JAVA
index 547faff..2470eba 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAnnotationOnClassGetterUsingFactory.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAnnotationOnClassGetterUsingFactory.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -46,7 +46,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String getNamespaceConv = genericDoc.getNamespace();
String getIdConv = genericDoc.getId();
int getPriceConv = (int) genericDoc.getPropertyLong("price");
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAnnotationOnInterfaceGetter.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAnnotationOnInterfaceGetter.JAVA
index a3ba288..a167313 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAnnotationOnInterfaceGetter.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAnnotationOnInterfaceGetter.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -46,7 +46,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String getNamespaceConv = genericDoc.getNamespace();
String getIdConv = genericDoc.getId();
int getPriceConv = (int) genericDoc.getPropertyLong("price");
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAutoValueDocument.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAutoValueDocument.JAVA
index 9a0e4a1..b1262ef 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAutoValueDocument.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAutoValueDocument.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -51,7 +51,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
String[] propertyCopy = genericDoc.getPropertyStringArray("property");
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAutoValueDocumentWithNormalDocument.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAutoValueDocumentWithNormalDocument.JAVA
index 8fc7620..a6e7c58 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAutoValueDocumentWithNormalDocument.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAutoValueDocumentWithNormalDocument.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -51,7 +51,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String idConv = genericDoc.getId();
String namespaceConv = genericDoc.getNamespace();
String[] propertyCopy = genericDoc.getPropertyStringArray("property");
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testCardinality.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testCardinality.JAVA
index 6317b89..df85788 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testCardinality.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testCardinality.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -11,7 +12,6 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -79,7 +79,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
String[] repeatReqCopy = genericDoc.getPropertyStringArray("repeatReq");
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testClassSpecialValues.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testClassSpecialValues.JAVA
index 9e24b3b..fe7927d 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testClassSpecialValues.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testClassSpecialValues.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -11,7 +12,6 @@
import java.lang.String;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -65,7 +65,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String mNamespaceConv = genericDoc.getNamespace();
String mIdConv = genericDoc.getId();
Long mCreationTimestampMillisConv = genericDoc.getCreationTimestampMillis();
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testCreationByBuilder.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testCreationByBuilder.JAVA
index 458a025..26a5b6f 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testCreationByBuilder.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testCreationByBuilder.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -46,7 +46,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String getNamespaceConv = genericDoc.getNamespace();
String getIdConv = genericDoc.getId();
int getPriceConv = (int) genericDoc.getPropertyLong("price");
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testCreationByBuilderAnnotatingBuilderClass.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testCreationByBuilderAnnotatingBuilderClass.JAVA
index 66798f3..fb5e82f 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testCreationByBuilderAnnotatingBuilderClass.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testCreationByBuilderAnnotatingBuilderClass.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -46,7 +46,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String getNamespaceConv = genericDoc.getNamespace();
String getIdConv = genericDoc.getId();
int getPriceConv = (int) genericDoc.getPropertyLong("price");
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testCreationByBuilderOnly.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testCreationByBuilderOnly.JAVA
index 06074a4..d11873b 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testCreationByBuilderOnly.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testCreationByBuilderOnly.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -46,7 +46,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
int priceConv = (int) genericDoc.getPropertyLong("price");
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testCreationByBuilderWithAutoValue.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testCreationByBuilderWithAutoValue.JAVA
index 28fec1e..d38e8af 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testCreationByBuilderWithAutoValue.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testCreationByBuilderWithAutoValue.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -46,7 +46,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String idConv = genericDoc.getId();
String namespaceConv = genericDoc.getNamespace();
int priceConv = (int) genericDoc.getPropertyLong("price");
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testCreationByBuilderWithParameter.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testCreationByBuilderWithParameter.JAVA
index 4fa4c99..ebdef46 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testCreationByBuilderWithParameter.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testCreationByBuilderWithParameter.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -46,7 +46,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String getNamespaceConv = genericDoc.getNamespace();
String getIdConv = genericDoc.getId();
int getPriceConv = (int) genericDoc.getPropertyLong("price");
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testCreationByBuilderWithParameterAnnotatingBuilderClass.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testCreationByBuilderWithParameterAnnotatingBuilderClass.JAVA
index 0d82055..c09d525 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testCreationByBuilderWithParameterAnnotatingBuilderClass.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testCreationByBuilderWithParameterAnnotatingBuilderClass.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -46,7 +46,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String getNamespaceConv = genericDoc.getNamespace();
String getIdConv = genericDoc.getId();
int getPriceConv = (int) genericDoc.getPropertyLong("price");
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testDifferentTypeName.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testDifferentTypeName.JAVA
index 5dbc8f1..3dec9cb 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testDifferentTypeName.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testDifferentTypeName.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -41,7 +41,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
Gift document = new Gift();
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testEmbeddingFields.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testEmbeddingFields.JAVA
index 9447804..4866a73 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testEmbeddingFields.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testEmbeddingFields.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.EmbeddingVector;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
@@ -12,7 +13,6 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -104,7 +104,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
String[] nameCopy = genericDoc.getPropertyStringArray("name");
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testGeneratedCodeRestrictedToLibrary.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testGeneratedCodeRestrictedToLibrary.JAVA
index 242946f..a499056 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testGeneratedCodeRestrictedToLibrary.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testGeneratedCodeRestrictedToLibrary.JAVA
@@ -3,6 +3,7 @@
import androidx.annotation.RestrictTo;
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -10,7 +11,6 @@
import java.lang.String;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -43,7 +43,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
Gift document = new Gift();
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testGetterAndSetterFunctions_withFieldName.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testGetterAndSetterFunctions_withFieldName.JAVA
index db8540b..2de5161 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testGetterAndSetterFunctions_withFieldName.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testGetterAndSetterFunctions_withFieldName.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -46,7 +46,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
int priceConv = (int) genericDoc.getPropertyLong("price");
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListEmpty.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListEmpty.JAVA
index 553f36b..43c1cc7 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListEmpty.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListEmpty.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.ArrayList;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -62,7 +62,7 @@
@Override
public Person fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
String[] nameCopy = genericDoc.getPropertyStringArray("name");
@@ -73,7 +73,7 @@
GenericDocument livesAtCopy = genericDoc.getPropertyDocument("livesAt");
Address livesAtConv = null;
if (livesAtCopy != null) {
- livesAtConv = livesAtCopy.toDocumentClass(Address.class, documentClassMap);
+ livesAtConv = livesAtCopy.toDocumentClass(Address.class, documentClassMappingContext);
}
Person document = new Person();
document.namespace = namespaceConv;
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListImplicitInheritance.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListImplicitInheritance.JAVA
index ac8da6b..0838213 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListImplicitInheritance.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListImplicitInheritance.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.ArrayList;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -76,7 +76,7 @@
@Override
public Artist fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
String[] nameCopy = genericDoc.getPropertyStringArray("name");
@@ -87,7 +87,7 @@
GenericDocument livesAtCopy = genericDoc.getPropertyDocument("livesAt");
Address livesAtConv = null;
if (livesAtCopy != null) {
- livesAtConv = livesAtCopy.toDocumentClass(Address.class, documentClassMap);
+ livesAtConv = livesAtCopy.toDocumentClass(Address.class, documentClassMappingContext);
}
String[] mostFamousWorkCopy = genericDoc.getPropertyStringArray("mostFamousWork");
String mostFamousWorkConv = null;
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListImplicitlyInheritFromMultipleLevels.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListImplicitlyInheritFromMultipleLevels.JAVA
index ce3287d..09581df 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListImplicitlyInheritFromMultipleLevels.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListImplicitlyInheritFromMultipleLevels.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.ArrayList;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -86,7 +86,7 @@
@Override
public ArtistEmployee fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
String[] nameCopy = genericDoc.getPropertyStringArray("name");
@@ -97,7 +97,7 @@
GenericDocument livesAtCopy = genericDoc.getPropertyDocument("livesAt");
Address livesAtConv = null;
if (livesAtCopy != null) {
- livesAtConv = livesAtCopy.toDocumentClass(Address.class, documentClassMap);
+ livesAtConv = livesAtCopy.toDocumentClass(Address.class, documentClassMappingContext);
}
String[] mostFamousWorkCopy = genericDoc.getPropertyStringArray("mostFamousWork");
String mostFamousWorkConv = null;
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListInheritSuperclassFalse.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListInheritSuperclassFalse.JAVA
index e4d5805..17044ef 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListInheritSuperclassFalse.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListInheritSuperclassFalse.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.ArrayList;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -75,7 +75,7 @@
@Override
public Artist fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
String[] nameCopy = genericDoc.getPropertyStringArray("name");
@@ -86,7 +86,7 @@
GenericDocument livesAtCopy = genericDoc.getPropertyDocument("livesAt");
Address livesAtConv = null;
if (livesAtCopy != null) {
- livesAtConv = livesAtCopy.toDocumentClass(Address.class, documentClassMap);
+ livesAtConv = livesAtCopy.toDocumentClass(Address.class, documentClassMappingContext);
}
String[] mostFamousWorkCopy = genericDoc.getPropertyStringArray("mostFamousWork");
String mostFamousWorkConv = null;
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListInheritSuperclassTrue.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListInheritSuperclassTrue.JAVA
index 1620454..3dd6c48 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListInheritSuperclassTrue.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListInheritSuperclassTrue.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.ArrayList;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -77,7 +77,7 @@
@Override
public Artist fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
String[] nameCopy = genericDoc.getPropertyStringArray("name");
@@ -88,7 +88,7 @@
GenericDocument livesAtCopy = genericDoc.getPropertyDocument("livesAt");
Address livesAtConv = null;
if (livesAtCopy != null) {
- livesAtConv = livesAtCopy.toDocumentClass(Address.class, documentClassMap);
+ livesAtConv = livesAtCopy.toDocumentClass(Address.class, documentClassMappingContext);
}
String[] mostFamousWorkCopy = genericDoc.getPropertyStringArray("mostFamousWork");
String mostFamousWorkConv = null;
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListInheritWithMultipleParentsClasses.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListInheritWithMultipleParentsClasses.JAVA
index 3c4c645..d560555 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListInheritWithMultipleParentsClasses.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListInheritWithMultipleParentsClasses.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.ArrayList;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -79,7 +79,7 @@
@Override
public ArtistEmployee fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
String[] nameCopy = genericDoc.getPropertyStringArray("name");
@@ -90,7 +90,7 @@
GenericDocument livesAtCopy = genericDoc.getPropertyDocument("livesAt");
Address livesAtConv = null;
if (livesAtCopy != null) {
- livesAtConv = livesAtCopy.toDocumentClass(Address.class, documentClassMap);
+ livesAtConv = livesAtCopy.toDocumentClass(Address.class, documentClassMappingContext);
}
String[] mostFamousWorkCopy = genericDoc.getPropertyStringArray("mostFamousWork");
String mostFamousWorkConv = null;
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListSimple.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListSimple.JAVA
index d7fe77d..3f30bc6 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListSimple.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListSimple.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.ArrayList;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -64,7 +64,7 @@
@Override
public Person fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
String[] nameCopy = genericDoc.getPropertyStringArray("name");
@@ -75,7 +75,7 @@
GenericDocument livesAtCopy = genericDoc.getPropertyDocument("livesAt");
Address livesAtConv = null;
if (livesAtCopy != null) {
- livesAtConv = livesAtCopy.toDocumentClass(Address.class, documentClassMap);
+ livesAtConv = livesAtCopy.toDocumentClass(Address.class, documentClassMappingContext);
}
Person document = new Person();
document.namespace = namespaceConv;
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListTopLevelInheritTrue.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListTopLevelInheritTrue.JAVA
index 821dcc4..2fd3487 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListTopLevelInheritTrue.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListTopLevelInheritTrue.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.ArrayList;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -87,7 +87,7 @@
@Override
public ArtistEmployee fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
String[] nameCopy = genericDoc.getPropertyStringArray("name");
@@ -98,7 +98,7 @@
GenericDocument livesAtCopy = genericDoc.getPropertyDocument("livesAt");
Address livesAtConv = null;
if (livesAtCopy != null) {
- livesAtConv = livesAtCopy.toDocumentClass(Address.class, documentClassMap);
+ livesAtConv = livesAtCopy.toDocumentClass(Address.class, documentClassMappingContext);
}
String[] mostFamousWorkCopy = genericDoc.getPropertyStringArray("mostFamousWork");
String mostFamousWorkConv = null;
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexingType.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexingType.JAVA
index 607d0c3..35d9c68 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexingType.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexingType.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -71,7 +71,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
String[] indexNoneCopy = genericDoc.getPropertyStringArray("indexNone");
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testInnerClass.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testInnerClass.JAVA
index e99bb1d..86d2998 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testInnerClass.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testInnerClass.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -51,7 +51,7 @@
@Override
public Gift.InnerGift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
String[] arrStringConv = genericDoc.getPropertyStringArray("arrString");
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testInterfaceAsNestedDocument.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testInterfaceAsNestedDocument.JAVA
index f09ac0b..a53873a 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testInterfaceAsNestedDocument.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testInterfaceAsNestedDocument.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.ArrayList;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -52,13 +52,13 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
GenericDocument thingCopy = genericDoc.getPropertyDocument("thing");
Thing thingConv = null;
if (thingCopy != null) {
- thingConv = thingCopy.toDocumentClass(Thing.class, documentClassMap);
+ thingConv = thingCopy.toDocumentClass(Thing.class, documentClassMappingContext);
}
Gift document = new Gift();
document.namespace = namespaceConv;
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testInterfaceImplementingParents.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testInterfaceImplementingParents.JAVA
index 51c1c41..fdcb2ff 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testInterfaceImplementingParents.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testInterfaceImplementingParents.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.ArrayList;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -71,7 +71,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String getNamespaceConv = genericDoc.getNamespace();
String getIdConv = genericDoc.getId();
String[] getStr2Copy = genericDoc.getPropertyStringArray("str2");
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testLongPropertyIndexingType.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testLongPropertyIndexingType.JAVA
index 0f7d12b..4cbe348 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testLongPropertyIndexingType.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testLongPropertyIndexingType.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -11,7 +12,6 @@
import java.lang.String;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -129,7 +129,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
long[] defaultIndexNoneCopy = genericDoc.getPropertyLongArray("defaultIndexNone");
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testLongSerializer.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testLongSerializer.JAVA
index 9fe4b74..b16c61c4 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testLongSerializer.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testLongSerializer.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -10,7 +11,6 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -79,7 +79,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String mIdConv = genericDoc.getId();
String mNamespaceConv = genericDoc.getNamespace();
long mPricePointCopy = genericDoc.getPropertyLong("pricePoint");
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testMultipleNestedAutoValueDocument.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testMultipleNestedAutoValueDocument.JAVA
index 3ab65b5..be4a17c 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testMultipleNestedAutoValueDocument.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testMultipleNestedAutoValueDocument.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -41,7 +41,7 @@
@Override
public Gift.A fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
Gift.A document = Gift.A.create(idConv, namespaceConv);
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testMultipleNesting.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testMultipleNesting.JAVA
index 7712cb3..57c9576 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testMultipleNesting.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testMultipleNesting.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.ArrayList;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -61,18 +61,18 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String idConv = genericDoc.getId();
String namespaceConv = genericDoc.getNamespace();
GenericDocument middleContentACopy = genericDoc.getPropertyDocument("middleContentA");
Middle middleContentAConv = null;
if (middleContentACopy != null) {
- middleContentAConv = middleContentACopy.toDocumentClass(Middle.class, documentClassMap);
+ middleContentAConv = middleContentACopy.toDocumentClass(Middle.class, documentClassMappingContext);
}
GenericDocument middleContentBCopy = genericDoc.getPropertyDocument("middleContentB");
Middle middleContentBConv = null;
if (middleContentBCopy != null) {
- middleContentBConv = middleContentBCopy.toDocumentClass(Middle.class, documentClassMap);
+ middleContentBConv = middleContentBCopy.toDocumentClass(Middle.class, documentClassMappingContext);
}
Gift document = new Gift();
document.id = idConv;
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testNameNormalization.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testNameNormalization.JAVA
index 54cbc1b..f8866eb 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testNameNormalization.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testNameNormalization.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -60,7 +60,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
int getMPriceConv = (int) genericDoc.getPropertyLong("mPrice");
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testNestedDocumentsIndexing.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testNestedDocumentsIndexing.JAVA
index bccc026..7a71022 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testNestedDocumentsIndexing.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testNestedDocumentsIndexing.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -10,7 +11,6 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -112,7 +112,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String idConv = genericDoc.getId();
String namespaceConv = genericDoc.getNamespace();
GenericDocument[] giftContentsCollectionCopy = genericDoc.getPropertyDocumentArray("giftContentsCollection");
@@ -120,7 +120,7 @@
if (giftContentsCollectionCopy != null) {
giftContentsCollectionConv = new ArrayList<>(giftContentsCollectionCopy.length);
for (int i = 0; i < giftContentsCollectionCopy.length; i++) {
- giftContentsCollectionConv.add(giftContentsCollectionCopy[i].toDocumentClass(GiftContent.class, documentClassMap));
+ giftContentsCollectionConv.add(giftContentsCollectionCopy[i].toDocumentClass(GiftContent.class, documentClassMappingContext));
}
}
GenericDocument[] giftContentsArrayCopy = genericDoc.getPropertyDocumentArray("giftContentsArray");
@@ -128,20 +128,20 @@
if (giftContentsArrayCopy != null) {
giftContentsArrayConv = new GiftContent[giftContentsArrayCopy.length];
for (int i = 0; i < giftContentsArrayCopy.length; i++) {
- giftContentsArrayConv[i] = giftContentsArrayCopy[i].toDocumentClass(GiftContent.class, documentClassMap);
+ giftContentsArrayConv[i] = giftContentsArrayCopy[i].toDocumentClass(GiftContent.class, documentClassMappingContext);
}
}
GenericDocument giftContentCopy = genericDoc.getPropertyDocument("giftContent");
GiftContent giftContentConv = null;
if (giftContentCopy != null) {
- giftContentConv = giftContentCopy.toDocumentClass(GiftContent.class, documentClassMap);
+ giftContentConv = giftContentCopy.toDocumentClass(GiftContent.class, documentClassMappingContext);
}
GenericDocument[] giftContentsCollectionNotIndexedCopy = genericDoc.getPropertyDocumentArray("giftContentsCollectionNotIndexed");
List<GiftContent> giftContentsCollectionNotIndexedConv = null;
if (giftContentsCollectionNotIndexedCopy != null) {
giftContentsCollectionNotIndexedConv = new ArrayList<>(giftContentsCollectionNotIndexedCopy.length);
for (int i = 0; i < giftContentsCollectionNotIndexedCopy.length; i++) {
- giftContentsCollectionNotIndexedConv.add(giftContentsCollectionNotIndexedCopy[i].toDocumentClass(GiftContent.class, documentClassMap));
+ giftContentsCollectionNotIndexedConv.add(giftContentsCollectionNotIndexedCopy[i].toDocumentClass(GiftContent.class, documentClassMappingContext));
}
}
GenericDocument[] giftContentsArrayNotIndexedCopy = genericDoc.getPropertyDocumentArray("giftContentsArrayNotIndexed");
@@ -149,13 +149,13 @@
if (giftContentsArrayNotIndexedCopy != null) {
giftContentsArrayNotIndexedConv = new GiftContent[giftContentsArrayNotIndexedCopy.length];
for (int i = 0; i < giftContentsArrayNotIndexedCopy.length; i++) {
- giftContentsArrayNotIndexedConv[i] = giftContentsArrayNotIndexedCopy[i].toDocumentClass(GiftContent.class, documentClassMap);
+ giftContentsArrayNotIndexedConv[i] = giftContentsArrayNotIndexedCopy[i].toDocumentClass(GiftContent.class, documentClassMappingContext);
}
}
GenericDocument giftContentNotIndexedCopy = genericDoc.getPropertyDocument("giftContentNotIndexed");
GiftContent giftContentNotIndexedConv = null;
if (giftContentNotIndexedCopy != null) {
- giftContentNotIndexedConv = giftContentNotIndexedCopy.toDocumentClass(GiftContent.class, documentClassMap);
+ giftContentNotIndexedConv = giftContentNotIndexedCopy.toDocumentClass(GiftContent.class, documentClassMappingContext);
}
Gift document = new Gift();
document.id = idConv;
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testOneBadConstructor.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testOneBadConstructor.JAVA
index 0bece60..24958a6 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testOneBadConstructor.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testOneBadConstructor.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -41,7 +41,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String mNamespaceConv = genericDoc.getNamespace();
String mIdConv = genericDoc.getId();
Gift document = new Gift(mIdConv);
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testOverloadedGetterIsOk.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testOverloadedGetterIsOk.JAVA
index 89a4f42..349343c 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testOverloadedGetterIsOk.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testOverloadedGetterIsOk.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -46,7 +46,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
int getPriceConv = (int) genericDoc.getPropertyLong("price");
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testPolymorphism.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testPolymorphism.JAVA
index 4bc175c..1a3d212a 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testPolymorphism.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testPolymorphism.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.ArrayList;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -76,7 +76,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
String[] note2Copy = genericDoc.getPropertyStringArray("note2");
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testPolymorphismDuplicatedParents.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testPolymorphismDuplicatedParents.JAVA
index 4bc175c..1a3d212a 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testPolymorphismDuplicatedParents.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testPolymorphismDuplicatedParents.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.ArrayList;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -76,7 +76,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
String[] note2Copy = genericDoc.getPropertyStringArray("note2");
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testPolymorphismOverrideExtendedProperty.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testPolymorphismOverrideExtendedProperty.JAVA
index 91f4581..ff45ac2 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testPolymorphismOverrideExtendedProperty.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testPolymorphismOverrideExtendedProperty.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.ArrayList;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -76,7 +76,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
String[] note2Copy = genericDoc.getPropertyStringArray("note2");
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testPolymorphismWithNestedType.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testPolymorphismWithNestedType.JAVA
index 975b98a..f00d9b1 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testPolymorphismWithNestedType.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testPolymorphismWithNestedType.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.ArrayList;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -86,7 +86,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
String[] note2Copy = genericDoc.getPropertyStringArray("note2");
@@ -107,7 +107,7 @@
GenericDocument innerContentCopy = genericDoc.getPropertyDocument("innerContent");
Inner innerContentConv = null;
if (innerContentCopy != null) {
- innerContentConv = innerContentCopy.toDocumentClass(Inner.class, documentClassMap);
+ innerContentConv = innerContentCopy.toDocumentClass(Inner.class, documentClassMappingContext);
}
Gift document = new Gift();
document.namespace = namespaceConv;
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testPropertyName.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testPropertyName.JAVA
index 1e045ae..4e279be 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testPropertyName.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testPropertyName.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -51,7 +51,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
String[] oldNameCopy = genericDoc.getPropertyStringArray("newName");
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testPropertyNamedAsDocumentClassMap.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testPropertyNamedAsDocumentClassMap.JAVA
index 3059558..bf19f33 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testPropertyNamedAsDocumentClassMap.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testPropertyNamedAsDocumentClassMap.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -46,7 +46,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
int documentClassMapConv = (int) genericDoc.getPropertyLong("documentClassMap");
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testRead_GetterReturnsSubtype.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testRead_GetterReturnsSubtype.JAVA
index dfadca5..bf96697 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testRead_GetterReturnsSubtype.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testRead_GetterReturnsSubtype.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -10,7 +11,6 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -53,7 +53,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
String[] fromCopy = genericDoc.getPropertyStringArray("from");
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testRead_MultipleGetters.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testRead_MultipleGetters.JAVA
index 0360605..23842cb 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testRead_MultipleGetters.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testRead_MultipleGetters.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -46,7 +46,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
int priceConv = (int) genericDoc.getPropertyLong("price");
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testRead_isGetterForBoolean.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testRead_isGetterForBoolean.JAVA
index 4d9acea..ba1128d 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testRead_isGetterForBoolean.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testRead_isGetterForBoolean.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -45,7 +45,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
boolean forSaleConv = genericDoc.getPropertyBoolean("forSale");
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testRepeatedFields.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testRepeatedFields.JAVA
index 810bd5f..da808de 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testRepeatedFields.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testRepeatedFields.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -13,7 +14,6 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -83,7 +83,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
String[] listOfStringCopy = genericDoc.getPropertyStringArray("listOfString");
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSameNameGetterAndFieldAnnotatingField.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSameNameGetterAndFieldAnnotatingField.JAVA
index 2ba3fce..c6bfdd3 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSameNameGetterAndFieldAnnotatingField.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSameNameGetterAndFieldAnnotatingField.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -46,7 +46,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
int priceConv = (int) genericDoc.getPropertyLong("price");
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSameNameGetterAndFieldAnnotatingGetter.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSameNameGetterAndFieldAnnotatingGetter.JAVA
index 89a4f42..349343c 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSameNameGetterAndFieldAnnotatingGetter.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSameNameGetterAndFieldAnnotatingGetter.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -46,7 +46,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
int getPriceConv = (int) genericDoc.getPropertyLong("price");
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testStringPropertyJoinableType.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testStringPropertyJoinableType.JAVA
index 2393da2..5be02c3 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testStringPropertyJoinableType.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testStringPropertyJoinableType.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -51,7 +51,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
String[] objectCopy = genericDoc.getPropertyStringArray("object");
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testStringSerializer.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testStringSerializer.JAVA
index d458eed..aa99fc1 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testStringSerializer.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testStringSerializer.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -11,7 +12,6 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -86,7 +86,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String mIdConv = genericDoc.getId();
String mNamespaceConv = genericDoc.getNamespace();
String mUrlCopy = genericDoc.getPropertyString("url");
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuccessSimple.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuccessSimple.JAVA
index b9f09a0..35d4caa 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuccessSimple.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuccessSimple.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -54,7 +54,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
int priceConv = (int) genericDoc.getPropertyLong("price");
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuperClass.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuperClass.JAVA
index 2306f74..df92190 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuperClass.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuperClass.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -66,7 +66,7 @@
@Override
public Gift.FooGift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
int scoreConv = genericDoc.getScore();
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuperClassPojoAncestor.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuperClassPojoAncestor.JAVA
index 3f68125..b818f8c 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuperClassPojoAncestor.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuperClassPojoAncestor.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -66,7 +66,7 @@
@Override
public Gift.FooGift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
int scoreConv = genericDoc.getScore();
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuperClassWithPrivateFields.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuperClassWithPrivateFields.JAVA
index 77777e8..d4bd15b 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuperClassWithPrivateFields.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuperClassWithPrivateFields.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -71,7 +71,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String mNamespaceConv = genericDoc.getNamespace();
String mIdConv = genericDoc.getId();
String[] mNoteCopy = genericDoc.getPropertyStringArray("note");
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuperClass_changeSchemaName.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuperClass_changeSchemaName.JAVA
index 56dfdb6..900b6bc 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuperClass_changeSchemaName.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuperClass_changeSchemaName.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -61,7 +61,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
String[] noteCopy = genericDoc.getPropertyStringArray("note");
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuperClass_multipleChangedSchemaNames.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuperClass_multipleChangedSchemaNames.JAVA
index 457b1071..2b00ef2 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuperClass_multipleChangedSchemaNames.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testSuperClass_multipleChangedSchemaNames.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -65,7 +65,7 @@
@Override
public FooGift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
String[] noteCopy = genericDoc.getPropertyStringArray("note");
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testToGenericDocument_allSupportedTypes.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testToGenericDocument_allSupportedTypes.JAVA
index 6ac5c27..63e3ae6 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testToGenericDocument_allSupportedTypes.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testToGenericDocument_allSupportedTypes.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.EmbeddingVector;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
@@ -17,7 +18,6 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -390,7 +390,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
long[] collectLongCopy = genericDoc.getPropertyLongArray("collectLong");
@@ -451,7 +451,7 @@
if (collectGiftCopy != null) {
collectGiftConv = new ArrayList<>(collectGiftCopy.length);
for (int i = 0; i < collectGiftCopy.length; i++) {
- collectGiftConv.add(collectGiftCopy[i].toDocumentClass(Gift.class, documentClassMap));
+ collectGiftConv.add(collectGiftCopy[i].toDocumentClass(Gift.class, documentClassMappingContext));
}
}
EmbeddingVector[] collectVecCopy = genericDoc.getPropertyEmbeddingArray("collectVec");
@@ -525,7 +525,7 @@
if (arrGiftCopy != null) {
arrGiftConv = new Gift[arrGiftCopy.length];
for (int i = 0; i < arrGiftCopy.length; i++) {
- arrGiftConv[i] = arrGiftCopy[i].toDocumentClass(Gift.class, documentClassMap);
+ arrGiftConv[i] = arrGiftCopy[i].toDocumentClass(Gift.class, documentClassMappingContext);
}
}
EmbeddingVector[] arrVecConv = genericDoc.getPropertyEmbeddingArray("arrVec");
@@ -572,7 +572,7 @@
GenericDocument giftCopy = genericDoc.getPropertyDocument("gift");
Gift giftConv = null;
if (giftCopy != null) {
- giftConv = giftCopy.toDocumentClass(Gift.class, documentClassMap);
+ giftConv = giftCopy.toDocumentClass(Gift.class, documentClassMappingContext);
}
EmbeddingVector[] vecCopy = genericDoc.getPropertyEmbeddingArray("vec");
EmbeddingVector vecConv = null;
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testTokenizerType.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testTokenizerType.JAVA
index 6425984..ab1eaa1 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testTokenizerType.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testTokenizerType.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -161,7 +161,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
String[] tokNoneInvalidCopy = genericDoc.getPropertyStringArray("tokNoneInvalid");
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testWrite_multipleConventions.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testWrite_multipleConventions.JAVA
index c3ddb1f..34e5321 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testWrite_multipleConventions.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testWrite_multipleConventions.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -61,7 +61,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String id_Conv = genericDoc.getId();
int price1Conv = (int) genericDoc.getPropertyLong("price1");
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testWrite_multipleSetters.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testWrite_multipleSetters.JAVA
index 0360605..23842cb 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testWrite_multipleSetters.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testWrite_multipleSetters.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -46,7 +46,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
int priceConv = (int) genericDoc.getPropertyLong("price");
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testWrite_usableFactoryMethod_unusableConstructor.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testWrite_usableFactoryMethod_unusableConstructor.JAVA
index ca916cf..027282b 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testWrite_usableFactoryMethod_unusableConstructor.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testWrite_usableFactoryMethod_unusableConstructor.JAVA
@@ -2,6 +2,7 @@
import androidx.appsearch.app.AppSearchSchema;
import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.DocumentClassMappingContext;
import androidx.appsearch.app.GenericDocument;
import androidx.appsearch.exceptions.AppSearchException;
import java.lang.Class;
@@ -9,7 +10,6 @@
import java.lang.String;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.annotation.processing.Generated;
@Generated("androidx.appsearch.compiler.AppSearchCompiler")
@@ -46,7 +46,7 @@
@Override
public Gift fromGenericDocument(GenericDocument genericDoc,
- Map<String, List<String>> documentClassMap) throws AppSearchException {
+ DocumentClassMappingContext documentClassMappingContext) throws AppSearchException {
String namespaceConv = genericDoc.getNamespace();
String idConv = genericDoc.getId();
int priceConv = (int) genericDoc.getPropertyLong("price");
diff --git a/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/Camera2ExtensionsActivity.kt b/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/Camera2ExtensionsActivity.kt
index 58c355a..6cf5746 100644
--- a/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/Camera2ExtensionsActivity.kt
+++ b/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/Camera2ExtensionsActivity.kt
@@ -22,15 +22,31 @@
import android.content.Intent
import android.graphics.SurfaceTexture
import android.hardware.camera2.CameraCaptureSession
+import android.hardware.camera2.CameraCaptureSession.CaptureCallback
import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CameraDevice
import android.hardware.camera2.CameraExtensionCharacteristics
import android.hardware.camera2.CameraExtensionSession
+import android.hardware.camera2.CameraExtensionSession.ExtensionCaptureCallback
import android.hardware.camera2.CameraManager
import android.hardware.camera2.CameraMetadata
+import android.hardware.camera2.CameraMetadata.CONTROL_AF_TRIGGER_CANCEL
+import android.hardware.camera2.CameraMetadata.CONTROL_AF_TRIGGER_IDLE
import android.hardware.camera2.CaptureFailure
import android.hardware.camera2.CaptureRequest
+import android.hardware.camera2.CaptureRequest.CONTROL_AE_MODE
+import android.hardware.camera2.CaptureRequest.CONTROL_AE_MODE_ON
+import android.hardware.camera2.CaptureRequest.CONTROL_AE_REGIONS
+import android.hardware.camera2.CaptureRequest.CONTROL_AF_MODE
+import android.hardware.camera2.CaptureRequest.CONTROL_AF_MODE_AUTO
+import android.hardware.camera2.CaptureRequest.CONTROL_AF_REGIONS
+import android.hardware.camera2.CaptureRequest.CONTROL_AF_TRIGGER
+import android.hardware.camera2.CaptureRequest.CONTROL_AF_TRIGGER_START
+import android.hardware.camera2.CaptureRequest.CONTROL_AWB_REGIONS
+import android.hardware.camera2.CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE
+import android.hardware.camera2.TotalCaptureResult
import android.hardware.camera2.params.ExtensionSessionConfiguration
+import android.hardware.camera2.params.MeteringRectangle
import android.hardware.camera2.params.OutputConfiguration
import android.hardware.camera2.params.SessionConfiguration
import android.hardware.camera2.params.SessionConfiguration.SESSION_REGULAR
@@ -68,6 +84,7 @@
import androidx.camera.integration.extensions.IntentExtraKey.INTENT_EXTRA_KEY_IMAGE_ROTATION_DEGREES
import androidx.camera.integration.extensions.IntentExtraKey.INTENT_EXTRA_KEY_IMAGE_URI
import androidx.camera.integration.extensions.IntentExtraKey.INTENT_EXTRA_KEY_REQUEST_CODE
+import androidx.camera.integration.extensions.TapToFocusDetector.CameraInfo
import androidx.camera.integration.extensions.TestResultType.TEST_RESULT_FAILED
import androidx.camera.integration.extensions.TestResultType.TEST_RESULT_NOT_TESTED
import androidx.camera.integration.extensions.TestResultType.TEST_RESULT_PASSED
@@ -152,6 +169,9 @@
*/
private var cameraCaptureSession: Any? = null
+ private val focusMeteringControl = FocusMeteringControl(::startAfTrigger, ::cancelAfTrigger)
+ private var meteringRectangles: Array<MeteringRectangle?> = EMPTY_RECTANGLES
+
// ===============================================================
// Fields that will be accessed on the camera thread
// ===============================================================
@@ -172,6 +192,8 @@
private val supportedExtensionModes = mutableListOf<Int>()
private var extensionModeEnabled = false
+ private lateinit var tapToFocusDetector: TapToFocusDetector
+
// ===============================================================
// Fields that will be accessed under synchronization protection
// ===============================================================
@@ -254,18 +276,13 @@
}
}
- private val captureCallbacks =
- object : CameraExtensionSession.ExtensionCaptureCallback() {
+ private val captureCallbackExtensionMode =
+ object : ExtensionCaptureCallback() {
override fun onCaptureProcessStarted(
session: CameraExtensionSession,
request: CaptureRequest
) {
- if (
- receivedCaptureProcessStartedCount.getAndIncrement() >=
- FRAMES_UNTIL_VIEW_IS_READY && !captureProcessStartedIdlingResource.isIdleNow
- ) {
- captureProcessStartedIdlingResource.decrement()
- }
+ handleCaptureStartedEvent()
}
override fun onCaptureFailed(session: CameraExtensionSession, request: CaptureRequest) {
@@ -273,23 +290,36 @@
}
}
- private val captureCallbacksNormalMode =
- object : CameraCaptureSession.CaptureCallback() {
+ private val captureCallbackNormalMode =
+ object : CaptureCallback() {
override fun onCaptureStarted(
session: CameraCaptureSession,
request: CaptureRequest,
timestamp: Long,
frameNumber: Long
) {
- if (
- receivedCaptureProcessStartedCount.getAndIncrement() >=
- FRAMES_UNTIL_VIEW_IS_READY && !captureProcessStartedIdlingResource.isIdleNow
- ) {
- captureProcessStartedIdlingResource.decrement()
- }
+ handleCaptureStartedEvent()
}
}
+ private fun handleCaptureStartedEvent() {
+ checkRunOnCameraThread()
+ if (
+ receivedCaptureProcessStartedCount.getAndIncrement() >= FRAMES_UNTIL_VIEW_IS_READY &&
+ !captureProcessStartedIdlingResource.isIdleNow
+ ) {
+ captureProcessStartedIdlingResource.decrement()
+ }
+ }
+
+ private val comboCaptureCallbackExtensionMode =
+ ComboCaptureCallbackExtensionMode().apply {
+ addCaptureCallback(captureCallbackExtensionMode)
+ }
+
+ private val comboCaptureCallbackNormalMode =
+ ComboCaptureCallbackNormalMode().apply { addCaptureCallback(captureCallbackNormalMode) }
+
private val cameraTaskDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
private lateinit var cameraThread: Thread
@@ -443,7 +473,7 @@
enableUiControl(false)
setupUiControl()
setupVideoStabilizationModeView()
- enableZoomGesture()
+ enableZoomAndTapToFocusGesture()
}
private fun setupForRequestMode() {
@@ -620,10 +650,12 @@
findViewById<Button>(R.id.Picture).isEnabled = enabled
}
- private fun enableZoomGesture() {
+ private fun enableZoomAndTapToFocusGesture() {
val scaleGestureDetector = ScaleGestureDetector(this, scaleGestureListener)
textureView.setOnTouchListener { _, event ->
- event != null && scaleGestureDetector.onTouchEvent(event)
+ scaleGestureDetector.onTouchEvent(event)
+ tapToFocusDetector.onTouchEvent(event)
+ true
}
}
@@ -895,6 +927,30 @@
cameraSensorRotationDegrees,
lensFacing == CameraCharacteristics.LENS_FACING_BACK
)
+
+ tapToFocusDetector =
+ TapToFocusDetector(this, textureView, getCameraInfo(), display!!.rotation, ::tapToFocus)
+ }
+
+ private fun getCameraInfo(): CameraInfo {
+ checkRunOnMainThread()
+ val lensFacing =
+ cameraManager
+ .getCameraCharacteristics(currentCameraId)[CameraCharacteristics.LENS_FACING]
+ val sensorOrientation =
+ cameraManager
+ .getCameraCharacteristics(currentCameraId)[CameraCharacteristics.SENSOR_ORIENTATION]
+ val activeArraySize =
+ cameraManager
+ .getCameraCharacteristics(currentCameraId)[
+ CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE]
+ return CameraInfo(lensFacing!!, sensorOrientation!!.toFloat(), activeArraySize!!)
+ }
+
+ private fun tapToFocus(meteringRectangles: Array<MeteringRectangle?>) {
+ coroutineScope.launch(cameraTaskDispatcher) {
+ focusMeteringControl.updateMeteringRectangles(meteringRectangles)
+ }
}
private fun checkRunOnMainThread() {
@@ -907,7 +963,10 @@
}
private fun checkRunOnCameraThread() {
- if (Thread.currentThread() != cameraThread) {
+ if (
+ Thread.currentThread() != cameraThread &&
+ Thread.currentThread() != normalModeCaptureThread
+ ) {
val exception = IllegalStateException("Must run on the camera thread!")
Log.e(TAG, exception.toString())
exception.printStackTrace()
@@ -1018,6 +1077,8 @@
private fun openCaptureSession(extensionMode: Int, extensionModeEnabled: Boolean) =
coroutineScope.async(cameraTaskDispatcher) {
Log.d(TAG, "openCaptureSession")
+ // Resets the metering rectangles
+ meteringRectangles = EMPTY_RECTANGLES
setCurrentState(STATE_CAPTURE_SESSION_OPENING)
if (stillImageReader != null) {
@@ -1134,39 +1195,109 @@
closeCamera()
}
- private fun setRepeatingRequest() {
+ private fun startAfTrigger(meteringRectangles: Array<MeteringRectangle?>) {
coroutineScope.launch(cameraTaskDispatcher) {
- val captureBuilder = cameraDevice!!.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
- captureBuilder.addTarget(previewSurface!!)
- val videoStabilizationMode =
- if (videoStabilizationToggleView.isChecked) {
- CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION
- } else {
- CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_OFF
- }
+ [email protected] = meteringRectangles
+ addFocusMeteringCaptureCallback()
- captureBuilder.set(
- CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE,
- videoStabilizationMode
+ val captureBuilder = getCaptureRequestBuilder()
+
+ captureBuilder.set(CONTROL_AF_TRIGGER, CONTROL_AF_TRIGGER_START)
+
+ setRepeatingRequest(captureBuilder.build())
+ }
+ }
+
+ private fun cancelAfTrigger(afTriggerType: Int) {
+ coroutineScope.launch(cameraTaskDispatcher) {
+ if (afTriggerType == CONTROL_AF_TRIGGER_CANCEL) {
+ [email protected] = EMPTY_RECTANGLES
+ }
+
+ removeFocusMeteringCaptureCallback()
+
+ val captureBuilder = getCaptureRequestBuilder()
+
+ if (afTriggerType == CONTROL_AF_TRIGGER_IDLE) {
+ captureBuilder.set(CONTROL_AF_TRIGGER, CONTROL_AF_TRIGGER_IDLE)
+ } else {
+ captureBuilder.set(CONTROL_AF_TRIGGER, CONTROL_AF_TRIGGER_CANCEL)
+ }
+
+ setRepeatingRequest(captureBuilder.build())
+ }
+ }
+
+ private fun addFocusMeteringCaptureCallback() {
+ checkRunOnCameraThread()
+ val captureCallback =
+ focusMeteringControl.getCaptureCallback(cameraCaptureSession is CameraExtensionSession)
+ if (cameraCaptureSession is CameraExtensionSession) {
+ comboCaptureCallbackExtensionMode.addCaptureCallback(
+ captureCallback as ExtensionCaptureCallback
)
+ } else {
+ comboCaptureCallbackNormalMode.addCaptureCallback(captureCallback as CaptureCallback)
+ }
+ }
- captureBuilder.set(CaptureRequest.CONTROL_ZOOM_RATIO, zoomRatio)
+ private fun removeFocusMeteringCaptureCallback() {
+ checkRunOnCameraThread()
+ val captureCallback =
+ focusMeteringControl.getCaptureCallback(cameraCaptureSession is CameraExtensionSession)
+ if (cameraCaptureSession is CameraExtensionSession) {
+ comboCaptureCallbackExtensionMode.removeCaptureCallback(
+ captureCallback as ExtensionCaptureCallback
+ )
+ } else {
+ comboCaptureCallbackNormalMode.removeCaptureCallback(captureCallback as CaptureCallback)
+ }
+ }
+
+ private fun setRepeatingRequest(captureRequest: CaptureRequest? = null) {
+ coroutineScope.launch(cameraTaskDispatcher) {
if (cameraCaptureSession is CameraCaptureSession) {
(cameraCaptureSession as CameraCaptureSession).setRepeatingRequest(
- captureBuilder.build(),
- captureCallbacksNormalMode,
+ captureRequest ?: getCaptureRequestBuilder().build(),
+ comboCaptureCallbackNormalMode,
normalModeCaptureHandler
)
} else {
(cameraCaptureSession as CameraExtensionSession).setRepeatingRequest(
- captureBuilder.build(),
+ captureRequest ?: getCaptureRequestBuilder().build(),
cameraTaskDispatcher.asExecutor(),
- captureCallbacks
+ comboCaptureCallbackExtensionMode
)
}
}
}
+ private fun getCaptureRequestBuilder(): CaptureRequest.Builder {
+ checkRunOnCameraThread()
+ val captureBuilder = cameraDevice!!.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
+ captureBuilder.addTarget(previewSurface!!)
+ val videoStabilizationMode =
+ if (videoStabilizationToggleView.isChecked) {
+ CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION
+ } else {
+ CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_OFF
+ }
+
+ captureBuilder.set(CONTROL_VIDEO_STABILIZATION_MODE, videoStabilizationMode)
+
+ captureBuilder.set(CaptureRequest.CONTROL_ZOOM_RATIO, zoomRatio)
+
+ if (!meteringRectangles.contentEquals(EMPTY_RECTANGLES)) {
+ captureBuilder.set(CONTROL_AF_MODE, CONTROL_AF_MODE_AUTO)
+ captureBuilder.set(CONTROL_AF_REGIONS, meteringRectangles)
+ captureBuilder.set(CONTROL_AE_MODE, CONTROL_AE_MODE_ON)
+ captureBuilder.set(CONTROL_AE_REGIONS, meteringRectangles)
+ captureBuilder.set(CONTROL_AWB_REGIONS, meteringRectangles)
+ }
+
+ return captureBuilder
+ }
+
private fun setupImageReader(cameraId: String, extensionMode: Int): ImageReader {
val (size, format) =
pickStillImageResolution(
@@ -1308,7 +1439,7 @@
if (cameraCaptureSession is CameraCaptureSession) {
(cameraCaptureSession as CameraCaptureSession).capture(
captureBuilder.build(),
- object : CameraCaptureSession.CaptureCallback() {
+ object : CaptureCallback() {
override fun onCaptureFailed(
session: CameraCaptureSession,
request: CaptureRequest,
@@ -1324,7 +1455,7 @@
(cameraCaptureSession as CameraExtensionSession).capture(
captureBuilder.build(),
cameraTaskDispatcher.asExecutor(),
- object : CameraExtensionSession.ExtensionCaptureCallback() {
+ object : ExtensionCaptureCallback() {
override fun onCaptureFailed(
session: CameraExtensionSession,
request: CaptureRequest
@@ -1536,6 +1667,89 @@
fun maxZoom(characteristics: CameraCharacteristics): Float =
characteristics.get(CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE)?.upper ?: 1.0f
}
+
+ /**
+ * A combo ExtensionCaptureCallback implementation to receive to pass the events to the
+ * underlying callbacks.
+ */
+ private class ComboCaptureCallbackExtensionMode : ExtensionCaptureCallback() {
+ private val captureCallbacks: MutableList<ExtensionCaptureCallback> = mutableListOf()
+
+ fun addCaptureCallback(captureCallback: ExtensionCaptureCallback) {
+ if (!captureCallbacks.contains(captureCallback)) {
+ captureCallbacks.add(captureCallback)
+ }
+ }
+
+ fun removeCaptureCallback(captureCallback: ExtensionCaptureCallback) {
+ captureCallbacks.remove(captureCallback)
+ }
+
+ override fun onCaptureStarted(
+ session: CameraExtensionSession,
+ request: CaptureRequest,
+ timestamp: Long
+ ) {
+ captureCallbacks.forEach { it.onCaptureStarted(session, request, timestamp) }
+ }
+
+ override fun onCaptureProcessStarted(
+ session: CameraExtensionSession,
+ request: CaptureRequest
+ ) {
+ captureCallbacks.forEach { it.onCaptureProcessStarted(session, request) }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+ override fun onCaptureResultAvailable(
+ session: CameraExtensionSession,
+ request: CaptureRequest,
+ result: TotalCaptureResult
+ ) {
+ captureCallbacks.forEach { it.onCaptureResultAvailable(session, request, result) }
+ }
+
+ override fun onCaptureFailed(session: CameraExtensionSession, request: CaptureRequest) {
+ captureCallbacks.forEach { it.onCaptureFailed(session, request) }
+ }
+ }
+
+ /**
+ * A combo CaptureCallback implementation to receive to pass the events to the underlying
+ * callbacks.
+ */
+ private class ComboCaptureCallbackNormalMode : CaptureCallback() {
+ private val captureCallbacks: MutableList<CaptureCallback> = mutableListOf()
+
+ fun addCaptureCallback(captureCallback: CaptureCallback) {
+ if (!captureCallbacks.contains(captureCallback)) {
+ captureCallbacks.add(captureCallback)
+ }
+ }
+
+ fun removeCaptureCallback(captureCallback: CaptureCallback) {
+ captureCallbacks.remove(captureCallback)
+ }
+
+ override fun onCaptureStarted(
+ session: CameraCaptureSession,
+ request: CaptureRequest,
+ timestamp: Long,
+ frameNumber: Long
+ ) {
+ captureCallbacks.forEach {
+ it.onCaptureStarted(session, request, timestamp, frameNumber)
+ }
+ }
+
+ override fun onCaptureCompleted(
+ session: CameraCaptureSession,
+ request: CaptureRequest,
+ result: TotalCaptureResult
+ ) {
+ captureCallbacks.forEach { it.onCaptureCompleted(session, request, result) }
+ }
+ }
}
fun Double.format(scale: Int): String = String.format("%.${scale}f", this)
diff --git a/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/FocusMeteringControl.kt b/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/FocusMeteringControl.kt
new file mode 100644
index 0000000..166cee0
--- /dev/null
+++ b/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/FocusMeteringControl.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.integration.extensions
+
+import android.hardware.camera2.CameraCaptureSession
+import android.hardware.camera2.CameraExtensionSession
+import android.hardware.camera2.CameraMetadata.CONTROL_AF_TRIGGER_CANCEL
+import android.hardware.camera2.CameraMetadata.CONTROL_AF_TRIGGER_IDLE
+import android.hardware.camera2.CaptureRequest
+import android.hardware.camera2.CaptureResult
+import android.hardware.camera2.TotalCaptureResult
+import android.hardware.camera2.params.MeteringRectangle
+import android.util.Log
+import androidx.annotation.RequiresApi
+import java.util.concurrent.Executors
+import java.util.concurrent.ScheduledExecutorService
+import java.util.concurrent.ScheduledFuture
+import java.util.concurrent.TimeUnit
+
+val EMPTY_RECTANGLES = arrayOfNulls<MeteringRectangle>(0)
+
+private const val TAG = "FocusMeteringControl"
+private const val AUTO_FOCUS_TIMEOUT_DURATION_MS = 5000L
+
+/**
+ * A class to manage focus-metering related operations. This class will help to monitor whether the
+ * state is locked or not and then make the AF-Trigger become to idle or cancel state.
+ */
+@RequiresApi(31)
+class FocusMeteringControl(
+ private val startAfTriggerImpl: (Array<MeteringRectangle?>) -> Unit,
+ private val cancelAfTriggerImpl: (Int) -> Unit
+) {
+
+ private val scheduler: ScheduledExecutorService = Executors.newSingleThreadScheduledExecutor()
+ private var currentAfState: Int = CaptureResult.CONTROL_AF_STATE_INACTIVE
+ private var autoCancelHandle: ScheduledFuture<*>? = null
+ private var autoFocusTimeoutHandle: ScheduledFuture<*>? = null
+ private var focusTimeoutCounter: Long = 0
+ private var isAutoFocusCompleted: Boolean = true
+
+ private val captureCallbackExtensionMode =
+ object : CameraExtensionSession.ExtensionCaptureCallback() {
+ override fun onCaptureResultAvailable(
+ session: CameraExtensionSession,
+ request: CaptureRequest,
+ result: TotalCaptureResult
+ ) {
+ result.get(CaptureResult.CONTROL_AF_STATE)?.let { handleCaptureResultForAf(it) }
+ }
+ }
+
+ private val captureCallbackNormalMode =
+ object : CameraCaptureSession.CaptureCallback() {
+ override fun onCaptureCompleted(
+ session: CameraCaptureSession,
+ request: CaptureRequest,
+ result: TotalCaptureResult
+ ) {
+ result.get(CaptureResult.CONTROL_AF_STATE)?.let { handleCaptureResultForAf(it) }
+ }
+ }
+
+ private fun handleCaptureResultForAf(afState: Int?) {
+ if (isAutoFocusCompleted) {
+ return
+ }
+
+ if (afState == null) {
+ Log.e(TAG, "afState == null")
+ // set isAutoFocusCompleted to true when camera does not support AF_AUTO.
+ isAutoFocusCompleted = true
+ } else if (currentAfState == CaptureResult.CONTROL_AF_STATE_ACTIVE_SCAN) {
+ if (afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED) {
+ Log.d(TAG, "afState == CONTROL_AF_STATE_FOCUSED_LOCKED")
+ isAutoFocusCompleted = true
+ } else if (afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) {
+ Log.d(TAG, "afState == CONTROL_AF_STATE_NOT_FOCUSED_LOCKED")
+ isAutoFocusCompleted = true
+ }
+ }
+
+ // Check 3A regions
+ if (isAutoFocusCompleted) {
+ clearAutoFocusTimeoutHandle()
+ Log.d(TAG, "cancelAfTrigger: CONTROL_AF_TRIGGER_IDLE")
+ cancelAfTriggerImpl.invoke(CONTROL_AF_TRIGGER_IDLE)
+ }
+
+ if (currentAfState != afState && afState != null) {
+ currentAfState = afState
+ }
+ }
+
+ fun updateMeteringRectangles(meteringRectangles: Array<MeteringRectangle?>) {
+ clearAutoFocusTimeoutHandle()
+ isAutoFocusCompleted = false
+ val timeoutId: Long = ++focusTimeoutCounter
+ autoFocusTimeoutHandle =
+ scheduler.schedule(
+ {
+ Log.d(TAG, "cancelAfTrigger: CONTROL_AF_TRIGGER_CANCEL")
+ cancelAfTriggerImpl.invoke(CONTROL_AF_TRIGGER_CANCEL)
+ if (timeoutId == focusTimeoutCounter) {
+ isAutoFocusCompleted = true
+ }
+ },
+ AUTO_FOCUS_TIMEOUT_DURATION_MS,
+ TimeUnit.MILLISECONDS
+ )
+ currentAfState = CaptureResult.CONTROL_AF_STATE_INACTIVE
+ startAfTriggerImpl.invoke(meteringRectangles)
+ }
+
+ fun getCaptureCallback(extensionEnabled: Boolean): Any =
+ if (extensionEnabled) {
+ captureCallbackExtensionMode
+ } else {
+ captureCallbackNormalMode
+ }
+
+ private fun clearAutoFocusTimeoutHandle() {
+ autoFocusTimeoutHandle?.let {
+ it.cancel(/* mayInterruptIfRunning= */ true)
+ autoCancelHandle = null
+ }
+ }
+}
diff --git a/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/TapToFocusDetector.kt b/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/TapToFocusDetector.kt
new file mode 100644
index 0000000..eb4cc73
--- /dev/null
+++ b/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/TapToFocusDetector.kt
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.integration.extensions
+
+import android.content.Context
+import android.graphics.Matrix
+import android.graphics.Rect
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.params.MeteringRectangle
+import android.util.Log
+import android.view.GestureDetector
+import android.view.MotionEvent
+import android.view.Surface
+import android.view.TextureView
+import androidx.core.math.MathUtils.clamp
+
+private const val TAG = "TapToFocusDetector"
+private const val METERING_RECTANGLE_SIZE = 0.15f
+
+/**
+ * A class helps to detect the tap-to-focus event and also normalize the point to mapping to the
+ * camera sensor coordinate.
+ */
+class TapToFocusDetector(
+ context: Context,
+ private val textureView: TextureView,
+ private val cameraInfo: CameraInfo,
+ private val displayRotation: Int,
+ private val tapToFocusImpl: (Array<MeteringRectangle?>) -> Unit
+) {
+ private val mTapToFocusListener: GestureDetector.SimpleOnGestureListener =
+ object : GestureDetector.SimpleOnGestureListener() {
+ override fun onSingleTapUp(motionEvent: MotionEvent): Boolean {
+ return tapToFocus(motionEvent)
+ }
+ }
+
+ private val tapToFocusGestureDetector = GestureDetector(context, mTapToFocusListener)
+
+ fun onTouchEvent(event: MotionEvent) {
+ tapToFocusGestureDetector.onTouchEvent(event)
+ }
+
+ private fun tapToFocus(motionEvent: MotionEvent): Boolean {
+ val normalizedPoint = calculateCameraSensorMappingPoint(motionEvent)
+ val meteringRectangle = calculateMeteringRectangle(normalizedPoint)
+ tapToFocusImpl.invoke(arrayOf(meteringRectangle))
+ return true
+ }
+
+ /**
+ * Calculates the point which will be mapped to a point in the camera sensor coordinate
+ * dimension.
+ */
+ private fun calculateCameraSensorMappingPoint(motionEvent: MotionEvent): FloatArray {
+ // Gets the dimension info to calculate the normalized point info in the camera sensor
+ // coordinate dimension first.
+ val activeArraySize = cameraInfo.activeArraySize
+ val relativeRotationDegrees = calculateRelativeRotationDegrees()
+ val dimension =
+ if (relativeRotationDegrees % 180 == 0) {
+ activeArraySize
+ } else {
+ Rect(0, 0, activeArraySize.height(), activeArraySize.width())
+ }
+
+ // Calculates what should the full dimension be because the preview might be cropped from
+ // the full FOV of camera sensor.
+ val scaledFullDimension =
+ if (
+ dimension.width() / dimension.height().toFloat() >
+ textureView.width / textureView.height.toFloat()
+ ) {
+ Rect(
+ 0,
+ 0,
+ dimension.width() * textureView.height / dimension.height(),
+ textureView.height
+ )
+ } else {
+ Rect(
+ 0,
+ 0,
+ textureView.width,
+ dimension.height() * textureView.width / dimension.width()
+ )
+ }
+
+ // Calculates the shift values for calibration.
+ val shiftX = (scaledFullDimension.width() - textureView.width) / 2
+ val shiftY = (scaledFullDimension.height() - textureView.height) / 2
+
+ // Calculates the normalized point which will be the point between [0, 0] to [1, 1].
+ val normalizedPoint =
+ floatArrayOf(
+ (motionEvent.x + shiftX) / scaledFullDimension.width(),
+ (motionEvent.y + shiftY) / scaledFullDimension.height()
+ )
+
+ // Transforms the normalizedPoint to the camera sensor coordinate.
+ val matrix = Matrix()
+ // Rotates the normalized point to the camera sensor orientation
+ matrix.postRotate(-relativeRotationDegrees.toFloat(), 0.5f, 0.5f)
+ // Flips if current working camera is front camera
+ if (cameraInfo.lensFacing == CameraCharacteristics.LENS_FACING_FRONT) {
+ matrix.postScale(1.0f, -1.0f, 0.5f, 0.5f)
+ }
+ // Scales the point to the camera sensor coordinate dimension.
+ matrix.postScale(activeArraySize.width().toFloat(), activeArraySize.height().toFloat())
+ matrix.mapPoints(normalizedPoint)
+
+ Log.e(TAG, "Tap-to-focus point: ${normalizedPoint.toList()}")
+
+ return normalizedPoint
+ }
+
+ private fun calculateRelativeRotationDegrees(): Int {
+ val rotationDegrees =
+ when (displayRotation) {
+ Surface.ROTATION_0 -> 0
+ Surface.ROTATION_90 -> 90
+ Surface.ROTATION_180 -> 180
+ Surface.ROTATION_270 -> 270
+ else ->
+ throw IllegalArgumentException("Unsupported surface rotation: $displayRotation")
+ }
+ return if (cameraInfo.lensFacing == CameraCharacteristics.LENS_FACING_BACK) {
+ (cameraInfo.sensorOrientation.toInt() - rotationDegrees + 360) % 360
+ } else {
+ (cameraInfo.sensorOrientation.toInt() + rotationDegrees) % 360
+ }
+ }
+
+ /**
+ * Calculates the metering rectangle according to the camera sensor coordinate dimension mapping
+ * point.
+ */
+ private fun calculateMeteringRectangle(point: FloatArray): MeteringRectangle {
+ val activeArraySize = cameraInfo.activeArraySize
+ val halfMeteringRectWidth: Float = (METERING_RECTANGLE_SIZE * activeArraySize.width()) / 2
+ val halfMeteringRectHeight: Float = (METERING_RECTANGLE_SIZE * activeArraySize.height()) / 2
+
+ val meteringRegion =
+ Rect(
+ clamp((point[0] - halfMeteringRectWidth).toInt(), 0, activeArraySize.width()),
+ clamp((point[1] - halfMeteringRectHeight).toInt(), 0, activeArraySize.height()),
+ clamp((point[0] + halfMeteringRectWidth).toInt(), 0, activeArraySize.width()),
+ clamp((point[1] + halfMeteringRectHeight).toInt(), 0, activeArraySize.height())
+ )
+
+ return MeteringRectangle(meteringRegion, MeteringRectangle.METERING_WEIGHT_MAX)
+ }
+
+ data class CameraInfo(
+ val lensFacing: Int,
+ val sensorOrientation: Float,
+ val activeArraySize: Rect
+ )
+}
diff --git a/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml b/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
index bd53d86..f4ba5f7c 100644
--- a/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
+++ b/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
@@ -48,6 +48,15 @@
</intent-filter>
</activity>
<activity
+ android:name=".InsightActivity"
+ android:exported="true"
+ android:label="C Insight">
+ <intent-filter>
+ <action android:name="androidx.compose.integration.macrobenchmark.target.INSIGHT_ACTIVITY" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+ <activity
android:name=".StaticScrollingContentWithChromeInitialCompositionActivity"
android:exported="true"
android:label="C StaticScrollingWithChrome Init">
diff --git a/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/InsightActivity.kt b/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/InsightActivity.kt
new file mode 100644
index 0000000..74ecb07
--- /dev/null
+++ b/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/InsightActivity.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.integration.macrobenchmark.target
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.material.Text
+import androidx.compose.ui.util.trace
+
+class InsightActivity : ComponentActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ // artificial trace sections + sleeps to trigger artificial Insights. If these fail to
+ // trigger insights, see src/trace_processor/metrics/sql/android/android_startup.sql
+ trace("ResourcesManager#getResources") { // duration based insight
+ trace("inflate") { // duration based insight
+ // currently sleep works for this, but spin loop may more accurately simulate work
+ // if this breaks
+ @Suppress("BanThreadSleep") Thread.sleep(500)
+
+ repeat(50) { // count based insight
+ trace("Broadcast dispatched SOMETHING") {}
+ }
+ repeat(100) { // count based insight
+ trace("broadcastReceiveReginald") {}
+ }
+ }
+ }
+
+ setContent { Text("Compose Macrobenchmark Target") }
+ }
+}
diff --git a/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/InsightBenchmark.kt b/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/InsightBenchmark.kt
new file mode 100644
index 0000000..c021e09
--- /dev/null
+++ b/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/InsightBenchmark.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.integration.macrobenchmark
+
+import androidx.benchmark.macro.CompilationMode
+import androidx.benchmark.macro.StartupMode
+import androidx.benchmark.macro.junit4.MacrobenchmarkRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.testutils.measureStartup
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class InsightBenchmark {
+ @get:Rule val benchmarkRule = MacrobenchmarkRule()
+
+ @Test
+ fun startup() =
+ benchmarkRule.measureStartup(
+ compilationMode = CompilationMode.DEFAULT,
+ startupMode = StartupMode.COLD,
+ iterations = 1,
+ packageName = "androidx.compose.integration.macrobenchmark.target",
+ ) {
+ action = "androidx.compose.integration.macrobenchmark.target.INSIGHT_ACTIVITY"
+ }
+}
diff --git a/compose/material3/adaptive/adaptive-layout/api/current.txt b/compose/material3/adaptive/adaptive-layout/api/current.txt
index 30e582d..f2bb0f2 100644
--- a/compose/material3/adaptive/adaptive-layout/api/current.txt
+++ b/compose/material3/adaptive/adaptive-layout/api/current.txt
@@ -259,6 +259,15 @@
property public abstract Role paneRole;
}
+ @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public sealed interface PaneScaffoldParentData {
+ method public float getMinTouchTargetSize();
+ method public float getPreferredWidth();
+ method public boolean isAnimatedPane();
+ property public abstract boolean isAnimatedPane;
+ property public abstract float minTouchTargetSize;
+ property public abstract float preferredWidth;
+ }
+
public sealed interface PaneScaffoldScope {
method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public androidx.compose.ui.Modifier paneExpansionDraggable(androidx.compose.ui.Modifier, androidx.compose.material3.adaptive.layout.PaneExpansionState state, float minTouchTargetSize, androidx.compose.foundation.interaction.MutableInteractionSource interactionSource);
method public androidx.compose.ui.Modifier preferredWidth(androidx.compose.ui.Modifier, float width);
@@ -334,6 +343,10 @@
property public int size;
}
+ public final class ThreePaneScaffoldHorizontalOrderKt {
+ method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.layout.ThreePaneScaffoldHorizontalOrder toLtrOrder(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldHorizontalOrder, androidx.compose.ui.unit.LayoutDirection layoutDirection);
+ }
+
public final class ThreePaneScaffoldKt {
method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.material3.adaptive.layout.ThreePaneScaffoldOverride> getLocalThreePaneScaffoldOverride();
property @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.material3.adaptive.layout.ThreePaneScaffoldOverride> LocalThreePaneScaffoldOverride;
diff --git a/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt b/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt
index 30e582d..f2bb0f2 100644
--- a/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt
+++ b/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt
@@ -259,6 +259,15 @@
property public abstract Role paneRole;
}
+ @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public sealed interface PaneScaffoldParentData {
+ method public float getMinTouchTargetSize();
+ method public float getPreferredWidth();
+ method public boolean isAnimatedPane();
+ property public abstract boolean isAnimatedPane;
+ property public abstract float minTouchTargetSize;
+ property public abstract float preferredWidth;
+ }
+
public sealed interface PaneScaffoldScope {
method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public androidx.compose.ui.Modifier paneExpansionDraggable(androidx.compose.ui.Modifier, androidx.compose.material3.adaptive.layout.PaneExpansionState state, float minTouchTargetSize, androidx.compose.foundation.interaction.MutableInteractionSource interactionSource);
method public androidx.compose.ui.Modifier preferredWidth(androidx.compose.ui.Modifier, float width);
@@ -334,6 +343,10 @@
property public int size;
}
+ public final class ThreePaneScaffoldHorizontalOrderKt {
+ method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.layout.ThreePaneScaffoldHorizontalOrder toLtrOrder(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldHorizontalOrder, androidx.compose.ui.unit.LayoutDirection layoutDirection);
+ }
+
public final class ThreePaneScaffoldKt {
method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.material3.adaptive.layout.ThreePaneScaffoldOverride> getLocalThreePaneScaffoldOverride();
property @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.material3.adaptive.layout.ThreePaneScaffoldOverride> LocalThreePaneScaffoldOverride;
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneExpansionDraggableModifier.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneExpansionDraggableModifier.kt
index 51623a1..1881c29 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneExpansionDraggableModifier.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneExpansionDraggableModifier.kt
@@ -48,7 +48,7 @@
internal class MinTouchTargetSizeNode(var size: Dp) : ParentDataModifierNode, Modifier.Node() {
override fun Density.modifyParentData(parentData: Any?) =
- ((parentData as? PaneScaffoldParentData) ?: PaneScaffoldParentData()).also {
+ ((parentData as? PaneScaffoldParentDataImpl) ?: PaneScaffoldParentDataImpl()).also {
it.minTouchTargetSize = size
}
}
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneMargins.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneMargins.kt
index c6e6f11..cdc8ef9 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneMargins.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneMargins.kt
@@ -104,7 +104,7 @@
private class PaneMarginsNode(var paneMargins: PaneMargins) :
ParentDataModifierNode, Modifier.Node() {
override fun Density.modifyParentData(parentData: Any?) =
- ((parentData as? PaneScaffoldParentData) ?: PaneScaffoldParentData()).also {
+ ((parentData as? PaneScaffoldParentDataImpl) ?: PaneScaffoldParentDataImpl()).also {
it.paneMargins = paneMargins
}
}
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffold.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffold.kt
index cb6e5f0f..be3751a 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffold.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffold.kt
@@ -194,7 +194,7 @@
private class PreferredWidthNode(var width: Dp) : ParentDataModifierNode, Modifier.Node() {
override fun Density.modifyParentData(parentData: Any?) =
- ((parentData as? PaneScaffoldParentData) ?: PaneScaffoldParentData()).also {
+ ((parentData as? PaneScaffoldParentDataImpl) ?: PaneScaffoldParentDataImpl()).also {
it.preferredWidth = with(this) { width.toPx() }
}
}
@@ -230,11 +230,12 @@
private class AnimatedPaneNode : ParentDataModifierNode, Modifier.Node() {
override fun Density.modifyParentData(parentData: Any?) =
- ((parentData as? PaneScaffoldParentData) ?: PaneScaffoldParentData()).also {
+ ((parentData as? PaneScaffoldParentDataImpl) ?: PaneScaffoldParentDataImpl()).also {
it.isAnimatedPane = true
}
}
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
internal val List<Measurable>.minTouchTargetSize: Dp
get() =
fastMaxOfOrNull {
@@ -247,9 +248,33 @@
}
} ?: 0.dp
-internal data class PaneScaffoldParentData(
- var preferredWidth: Float? = null,
+/**
+ * The parent data passed to pane scaffolds by their contents like panes and drag handles.
+ *
+ * @see PaneScaffoldScope.preferredWidth
+ */
+@ExperimentalMaterial3AdaptiveApi
+sealed interface PaneScaffoldParentData {
+ /**
+ * The preferred width of the child, which is supposed to be set via
+ * [PaneScaffoldScope.preferredWidth] on a pane composable, like [AnimatedPane].
+ */
+ val preferredWidth: Float
+
+ /** `true` to indicate that the child is an [AnimatedPane]; otherwise `false`. */
+ val isAnimatedPane: Boolean
+
+ /**
+ * The minimum touch target size of the child, which is supposed to be set via
+ * [PaneScaffoldScope.paneExpansionDraggable] on a drag handle component.
+ */
+ val minTouchTargetSize: Dp
+}
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+internal data class PaneScaffoldParentDataImpl(
+ override var preferredWidth: Float = Float.NaN,
var paneMargins: PaneMargins = PaneMargins.Unspecified,
- var isAnimatedPane: Boolean = false,
- var minTouchTargetSize: Dp = Dp.Unspecified
-)
+ override var isAnimatedPane: Boolean = false,
+ override var minTouchTargetSize: Dp = Dp.Unspecified
+) : PaneScaffoldParentData
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt
index a861892..1400410 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt
@@ -777,6 +777,7 @@
}
}
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
private class PaneMeasurable(
val measurable: Measurable,
val priority: Int,
@@ -784,16 +785,17 @@
defaultPreferredWidth: Int
) {
private val data =
- ((measurable.parentData as? PaneScaffoldParentData) ?: PaneScaffoldParentData())
+ ((measurable.parentData as? PaneScaffoldParentData) ?: PaneScaffoldParentDataImpl())
var measuringWidth =
- if (data.preferredWidth == null || data.preferredWidth!!.isNaN()) {
+ if (data.preferredWidth.isNaN()) {
defaultPreferredWidth
} else {
- data.preferredWidth!!.toInt()
+ data.preferredWidth.toInt()
}
- val margins: PaneMargins = data.paneMargins
+ // TODO(conradchen): uncomment it when we can expose PaneMargins
+ // val margins: PaneMargins = data.paneMargins
val isAnimatedPane = data.isAnimatedPane
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldHorizontalOrder.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldHorizontalOrder.kt
index f24f7fa..c49ce79 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldHorizontalOrder.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldHorizontalOrder.kt
@@ -89,9 +89,13 @@
}
}
-/** Converts a bidirectional order to a left-to-right order. */
+/**
+ * Converts a bidirectional order to a left-to-right order.
+ *
+ * @param layoutDirection the current [LayoutDirection]
+ */
@ExperimentalMaterial3AdaptiveApi
-internal fun ThreePaneScaffoldHorizontalOrder.toLtrOrder(
+fun ThreePaneScaffoldHorizontalOrder.toLtrOrder(
layoutDirection: LayoutDirection
): ThreePaneScaffoldHorizontalOrder {
return if (layoutDirection == LayoutDirection.Rtl) {
diff --git a/compose/runtime/runtime/api/current.txt b/compose/runtime/runtime/api/current.txt
index c20e335..e09c09f 100644
--- a/compose/runtime/runtime/api/current.txt
+++ b/compose/runtime/runtime/api/current.txt
@@ -1138,6 +1138,10 @@
property public abstract boolean isEmpty;
}
+ public final class CompositionDataKt {
+ method public static androidx.compose.runtime.tooling.CompositionInstance? findCompositionInstance(androidx.compose.runtime.tooling.CompositionData);
+ }
+
@kotlin.jvm.JvmDefaultWithCompatibility public interface CompositionGroup extends androidx.compose.runtime.tooling.CompositionData {
method public Iterable<java.lang.Object?> getData();
method public default int getGroupSize();
@@ -1155,6 +1159,14 @@
property public abstract String? sourceInfo;
}
+ public interface CompositionInstance {
+ method public androidx.compose.runtime.tooling.CompositionGroup? findContextGroup();
+ method public androidx.compose.runtime.tooling.CompositionData getData();
+ method public androidx.compose.runtime.tooling.CompositionInstance? getParent();
+ property public abstract androidx.compose.runtime.tooling.CompositionData data;
+ property public abstract androidx.compose.runtime.tooling.CompositionInstance? parent;
+ }
+
@SuppressCompatibility @androidx.compose.runtime.ExperimentalComposeRuntimeApi public interface CompositionObserver {
method public void onBeginComposition(androidx.compose.runtime.Composition composition, java.util.Map<androidx.compose.runtime.RecomposeScope,? extends java.util.Set<?>> invalidationMap);
method public void onEndComposition(androidx.compose.runtime.Composition composition);
diff --git a/compose/runtime/runtime/api/restricted_current.txt b/compose/runtime/runtime/api/restricted_current.txt
index a5104eee..4c7407c 100644
--- a/compose/runtime/runtime/api/restricted_current.txt
+++ b/compose/runtime/runtime/api/restricted_current.txt
@@ -1210,6 +1210,10 @@
property public abstract boolean isEmpty;
}
+ public final class CompositionDataKt {
+ method public static androidx.compose.runtime.tooling.CompositionInstance? findCompositionInstance(androidx.compose.runtime.tooling.CompositionData);
+ }
+
@kotlin.jvm.JvmDefaultWithCompatibility public interface CompositionGroup extends androidx.compose.runtime.tooling.CompositionData {
method public Iterable<java.lang.Object?> getData();
method public default int getGroupSize();
@@ -1227,6 +1231,14 @@
property public abstract String? sourceInfo;
}
+ public interface CompositionInstance {
+ method public androidx.compose.runtime.tooling.CompositionGroup? findContextGroup();
+ method public androidx.compose.runtime.tooling.CompositionData getData();
+ method public androidx.compose.runtime.tooling.CompositionInstance? getParent();
+ property public abstract androidx.compose.runtime.tooling.CompositionData data;
+ property public abstract androidx.compose.runtime.tooling.CompositionInstance? parent;
+ }
+
@SuppressCompatibility @androidx.compose.runtime.ExperimentalComposeRuntimeApi public interface CompositionObserver {
method public void onBeginComposition(androidx.compose.runtime.Composition composition, java.util.Map<androidx.compose.runtime.RecomposeScope,? extends java.util.Set<?>> invalidationMap);
method public void onEndComposition(androidx.compose.runtime.Composition composition);
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
index abd9693..da4f65e 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
@@ -32,6 +32,7 @@
import androidx.collection.mutableScatterMapOf
import androidx.collection.mutableScatterSetOf
import androidx.compose.runtime.Composer.Companion.equals
+import androidx.compose.runtime.ComposerImpl.CompositionContextHolder
import androidx.compose.runtime.changelist.ChangeList
import androidx.compose.runtime.changelist.ComposerChangeListWriter
import androidx.compose.runtime.changelist.FixupList
@@ -48,6 +49,8 @@
import androidx.compose.runtime.snapshots.fastMap
import androidx.compose.runtime.snapshots.fastToSet
import androidx.compose.runtime.tooling.CompositionData
+import androidx.compose.runtime.tooling.CompositionGroup
+import androidx.compose.runtime.tooling.CompositionInstance
import androidx.compose.runtime.tooling.LocalInspectionTables
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
@@ -1652,7 +1655,7 @@
}
parentProvider.read(LocalInspectionTables)?.let {
- it.add(slotTable)
+ it.add(compositionData)
parentContext.recordInspectionTable(it)
}
startGroup(parentContext.compoundHashKey)
@@ -2187,8 +2190,18 @@
} else null
}
+ private var _compositionData: CompositionData? = null
+
override val compositionData: CompositionData
- get() = slotTable
+ get() {
+ val data = _compositionData
+ if (data == null) {
+ val newData = CompositionDataImpl(composition)
+ _compositionData = newData
+ return newData
+ }
+ return data
+ }
/** Schedule a side effect to run when we apply composition changes. */
override fun recordSideEffect(effect: () -> Unit) {
@@ -3944,8 +3957,9 @@
* A holder that will dispose of its [CompositionContext] when it leaves the composition that
* will not have its reference made visible to user code.
*/
- private class CompositionContextHolder(val ref: ComposerImpl.CompositionContextImpl) :
+ internal class CompositionContextHolder(val ref: ComposerImpl.CompositionContextImpl) :
ReusableRememberObserver {
+
override fun onRemembered() {}
override fun onAbandoned() {
@@ -3958,7 +3972,7 @@
}
@OptIn(ExperimentalComposeRuntimeApi::class)
- private inner class CompositionContextImpl(
+ internal inner class CompositionContextImpl(
override val compoundHashKey: Int,
override val collectingParameterInformation: Boolean,
override val collectingSourceInformation: Boolean,
@@ -4010,7 +4024,7 @@
@OptIn(ExperimentalComposeApi::class)
@get:OptIn(ExperimentalComposeApi::class)
override val recomposeCoroutineContext: CoroutineContext
- get() = composition.recomposeCoroutineContext
+ get() = [email protected]
override fun composeInitial(
composition: ControlledComposition,
@@ -4105,6 +4119,9 @@
override fun reportRemovedComposition(composition: ControlledComposition) {
parentContext.reportRemovedComposition(composition)
}
+
+ override val composition: Composition
+ get() = [email protected]
}
private inline fun updateCompoundKeyWhenWeEnterGroup(
@@ -4804,3 +4821,69 @@
}
return state
}
+
+internal class CompositionDataImpl(val composition: Composition) :
+ CompositionData, CompositionInstance {
+ private val slotTable
+ get() = (composition as CompositionImpl).slotTable
+
+ override val compositionGroups: Iterable<CompositionGroup>
+ get() = slotTable.compositionGroups
+
+ override val isEmpty: Boolean
+ get() = slotTable.isEmpty
+
+ override fun find(identityToFind: Any): CompositionGroup? = slotTable.find(identityToFind)
+
+ override fun hashCode(): Int = composition.hashCode() * 31
+
+ override fun equals(other: Any?): Boolean =
+ other is CompositionDataImpl && composition == other.composition
+
+ override val parent: CompositionInstance?
+ get() = composition.parent?.let { CompositionDataImpl(it) }
+
+ override val data: CompositionData
+ get() = this
+
+ override fun findContextGroup(): CompositionGroup? {
+ val parentSlotTable = composition.parent?.slotTable ?: return null
+ val context = composition.context
+
+ parentSlotTable.read { reader ->
+ fun scanGroup(group: Int, end: Int): CompositionGroup? {
+ var current = group
+ while (current < end) {
+ val next = current + reader.groupSize(current)
+ if (
+ reader.hasMark(current) &&
+ reader.groupKey(current) == referenceKey &&
+ reader.groupObjectKey(current) == reference
+ ) {
+ val contextHolder = reader.groupGet(current, 0) as? CompositionContextHolder
+ if (contextHolder != null && contextHolder.ref == context) {
+ return parentSlotTable.compositionGroupOf(current)
+ }
+ }
+ if (reader.containsMark(current)) {
+ scanGroup(current + 1, next)?.let {
+ return it
+ }
+ }
+ current = next
+ }
+ return null
+ }
+ return scanGroup(0, reader.size)
+ }
+ }
+
+ private val Composition.slotTable
+ get() = (this as? CompositionImpl)?.slotTable
+
+ private val Composition.context
+ get() = (this as? CompositionImpl)?.parent
+
+ private val Composition.parent
+ get() = context?.composition
+}
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/CompositionContext.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/CompositionContext.kt
index 25b9431..6ccb274 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/CompositionContext.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/CompositionContext.kt
@@ -103,4 +103,6 @@
): MovableContentState? = null
internal abstract fun reportRemovedComposition(composition: ControlledComposition)
+
+ internal abstract val composition: Composition?
}
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt
index 0926fe9..9a382bd 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt
@@ -1595,6 +1595,9 @@
): MovableContentState? =
synchronized(stateLock) { movableContentStatesAvailable.remove(reference) }
+ override val composition: Composition?
+ get() = null
+
/**
* hack: the companion object is thread local in Kotlin/Native to avoid freezing
* [_runningRecomposers] with the current memory model. As a side effect, recomposers are now
@@ -1617,6 +1620,10 @@
val runningRecomposers: StateFlow<Set<RecomposerInfo>>
get() = _runningRecomposers
+ internal fun currentRunningRecomposers(): Set<RecomposerInfo> {
+ return _runningRecomposers.value
+ }
+
internal fun setHotReloadEnabled(value: Boolean) {
_hotReloadEnabled.set(value)
}
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt
index cdfa183..68b5f16 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt
@@ -3218,6 +3218,10 @@
.replace("MutableState", "σ")
.let { it.substring(0, min(size, it.length)) }
+internal fun SlotTable.compositionGroupOf(group: Int): CompositionGroup {
+ return SlotTableGroup(this, group, this.version)
+}
+
private class SlotTableGroup(
val table: SlotTable,
val group: Int,
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/tooling/CompositionData.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/tooling/CompositionData.kt
index 290fe38..4ea8db0 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/tooling/CompositionData.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/tooling/CompositionData.kt
@@ -16,7 +16,9 @@
package androidx.compose.runtime.tooling
+import androidx.compose.runtime.CompositionContext
import androidx.compose.runtime.internal.JvmDefaultWithCompatibility
+import androidx.compose.runtime.rememberCompositionContext
/**
* A [CompositionData] is the data tracked by the composer during composition.
@@ -98,3 +100,35 @@
val slotsSize: Int
get() = 0
}
+
+/**
+ * [CompositionInstance] provides information about the composition of which a [CompositionData] is
+ * part.
+ */
+interface CompositionInstance {
+ /**
+ * The parent composition instance, if the instance is part of a sub-composition. If this is the
+ * root of a composition (such as the content of a ComposeView), then [parent] will be `null`.
+ */
+ val parent: CompositionInstance?
+
+ /** The [CompositionData] for the instance */
+ val data: CompositionData
+
+ /**
+ * Find the [CompositionGroup] that contains the [CompositionContext] created by a call to
+ * [rememberCompositionContext] that is the parent context for this composition. If this is the
+ * root of the composition (e.g. [parent] is `null`) then this method also returns `null`.
+ */
+ fun findContextGroup(): CompositionGroup?
+}
+
+/**
+ * Find the [CompositionInstance] associated with the root [CompositionData]. This is only valid for
+ * instances of [CompositionData] that are recorded in a [LocalInspectionTables] table directly.
+ *
+ * Even though [CompositionGroup]s implement the [CompositionData] interface, only the root
+ * [CompositionData] has an associated [CompositionInstance]. All [CompositionGroup] instances will
+ * return `null`.
+ */
+fun CompositionData.findCompositionInstance(): CompositionInstance? = this as? CompositionInstance
diff --git a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/tooling/CompositionInstanceTests.kt b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/tooling/CompositionInstanceTests.kt
new file mode 100644
index 0000000..53b2a8b
--- /dev/null
+++ b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/tooling/CompositionInstanceTests.kt
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.runtime.tooling
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Composition
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mock.Text
+import androidx.compose.runtime.mock.View
+import androidx.compose.runtime.mock.ViewApplier
+import androidx.compose.runtime.mock.compositionTest
+import androidx.compose.runtime.rememberCompositionContext
+import androidx.compose.runtime.rememberUpdatedState
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertTrue
+
+class CompositionInstanceTests {
+ @Test
+ fun canFindACompositionInstance() = compositionTest {
+ val table = mutableSetOf<CompositionData>()
+ compose {
+ CompositionLocalProvider(LocalInspectionTables provides table) {
+ TestSubcomposition { Text("Some value") }
+ }
+ }
+
+ assertEquals(1, table.size)
+ val data = table.first()
+ val instance = data.findCompositionInstance()
+ assertNotNull(instance)
+ }
+
+ @Test
+ fun canFindContextGroup() = compositionTest {
+ val table = mutableSetOf<CompositionData>()
+ compose {
+ CompositionLocalProvider(LocalInspectionTables provides table) {
+ TestSubcomposition { Text("Some value") }
+ }
+ }
+
+ val data = table.first()
+ val instance = data.findCompositionInstance()
+ assertNotNull(instance)
+ val contextGroup = instance.findContextGroup()
+ assertNotNull(contextGroup)
+ }
+
+ @Test
+ fun canFindParentInstance() = compositionTest {
+ val table = mutableSetOf<CompositionData>()
+ compose {
+ CompositionLocalProvider(LocalInspectionTables provides table) {
+ TestSubcomposition {
+ TestSubcomposition { TestSubcomposition { Text("Some value") } }
+ }
+ }
+ }
+
+ assertEquals(3, table.size)
+
+ // Find the root (which will not be in the table)
+ fun findRootOf(data: CompositionData): CompositionData {
+ val parentData = data.findCompositionInstance()?.parent?.data
+ return if (parentData == null) data else findRootOf(parentData)
+ }
+
+ val root = findRootOf(table.first())
+
+ // Verify that the instance and its parents (not the root) are in the table
+ fun verify(data: CompositionData) {
+ if (data != root) {
+ assertTrue(data in table)
+ data.findCompositionInstance()?.parent?.let { verify(it.data) }
+ }
+ }
+
+ for (instance in table) {
+ assertEquals(root, findRootOf(instance))
+ verify(instance)
+ }
+ }
+
+ @Test
+ fun canFindParentNotInFirstPosition() = compositionTest {
+ val table = mutableSetOf<CompositionData>()
+ compose {
+ CompositionLocalProvider(LocalInspectionTables provides table) {
+ Text("Some value")
+ Text("Some value")
+ Text("Some value")
+ TestSubcomposition { Text("Some value") }
+ }
+ }
+ val instance = table.first().findCompositionInstance()
+ assertNotNull(instance)
+ val contextGroup = instance.findContextGroup()
+ assertNotNull(contextGroup)
+ }
+
+ @Test
+ fun contextGroupIsInParent() = compositionTest {
+ val table = mutableSetOf<CompositionData>()
+ compose {
+ CompositionLocalProvider(LocalInspectionTables provides table) {
+ TestSubcomposition { Text("Some value") }
+ }
+ }
+ val instance = table.first().findCompositionInstance()
+ assertNotNull(instance)
+ val contextGroup = instance.findContextGroup()
+ assertNotNull(contextGroup)
+ val parentData = instance.parent?.data
+ assertNotNull(parentData)
+ val identity = contextGroup.identity
+ assertNotNull(identity)
+ val foundGroup = parentData.find(identity)
+ assertNotNull(foundGroup)
+ assertEquals(identity, foundGroup.identity)
+
+ fun identityMap(data: CompositionData): Map<Any, CompositionGroup> {
+ val result = mutableMapOf<Any, CompositionGroup>()
+ fun addToMap(group: CompositionGroup) {
+ val groupIdentity = group.identity
+ if (groupIdentity != null) result[groupIdentity] = group
+ group.compositionGroups.forEach(::addToMap)
+ }
+ data.compositionGroups.forEach(::addToMap)
+ return result
+ }
+
+ val map = identityMap(parentData)
+ val mapFoundGroup = map[contextGroup.identity]
+ assertNotNull(mapFoundGroup)
+ }
+}
+
+@Composable
+internal fun TestSubcomposition(content: @Composable () -> Unit) {
+ val parentRef = rememberCompositionContext()
+ val currentContent by rememberUpdatedState(content)
+ DisposableEffect(parentRef) {
+ val subComposeRoot = View().apply { name = "subComposeRoot" }
+ val subcomposition = Composition(ViewApplier(subComposeRoot), parentRef)
+ // TODO: work around for b/179701728
+ callSetContent(subcomposition) {
+ // Note: This is in a lambda invocation to keep the currentContent state read
+ // in the sub-composition's content composable. Changing this to be
+ // subcomposition.setContent(currentContent) would snapshot read only on initial set.
+ currentContent()
+ }
+ onDispose { subcomposition.dispose() }
+ }
+}
+
+private fun callSetContent(composition: Composition, content: @Composable () -> Unit) {
+ composition.setContent(content)
+}
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/autofill/AndroidAutoFillTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/autofill/AndroidAutoFillTest.kt
index 939dfd0..3e46712 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/autofill/AndroidAutoFillTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/autofill/AndroidAutoFillTest.kt
@@ -21,6 +21,7 @@
import android.view.ViewStructure
import android.view.autofill.AutofillValue
import androidx.autofill.HintConstants.AUTOFILL_HINT_PERSON_NAME
+import androidx.compose.ui.ComposeUiFlags.isSemanticAutofillEnabled
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.platform.LocalAutofill
@@ -76,6 +77,7 @@
@SdkSuppress(minSdkVersion = 26)
@Test
fun onProvideAutofillVirtualStructure_populatesViewStructure() {
+ if (isSemanticAutofillEnabled) return
// Arrange.
val viewStructure: ViewStructure = FakeViewStructure()
val autofillNode =
@@ -110,6 +112,7 @@
@SdkSuppress(minSdkVersion = 26)
@Test
fun autofill_triggersOnFill() {
+ if (isSemanticAutofillEnabled) return
// Arrange.
val expectedValue = "PersonName"
var autofilledValue = ""
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofillManager.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofillManager.android.kt
index 2546cb2..7f80faf 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofillManager.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofillManager.android.kt
@@ -187,8 +187,8 @@
private fun notifyAutofillValueChanged(semanticsId: Int, newAutofillValue: Any) {
val currSemanticsNode = currentSemanticsNodes[semanticsId]?.semanticsNode
- val currDataType = currSemanticsNode?.unmergedConfig?.getOrNull(SemanticsContentDataType)
-
+ val currDataType =
+ currSemanticsNode?.unmergedConfig?.getOrNull(SemanticsContentDataType) ?: return
when (currDataType) {
ContentDataType.Text ->
autofillManager.notifyValueChanged(
diff --git a/core/core/src/androidTest/java/androidx/core/widget/NestedScrollViewTest.java b/core/core/src/androidTest/java/androidx/core/widget/NestedScrollViewTest.java
index 2756300..880b9a2 100644
--- a/core/core/src/androidTest/java/androidx/core/widget/NestedScrollViewTest.java
+++ b/core/core/src/androidTest/java/androidx/core/widget/NestedScrollViewTest.java
@@ -21,6 +21,12 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
import android.content.Context;
import android.graphics.Rect;
@@ -32,6 +38,8 @@
import android.view.MotionEvent;
import android.view.View;
+import androidx.core.view.ScrollFeedbackProviderCompat;
+import androidx.core.view.ViewCompat;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -45,6 +53,7 @@
private NestedScrollView mNestedScrollView;
private View mChild;
+ private ScrollFeedbackProviderCompat mScrollFeedbackProvider;
@Test
public void getBottomFadingEdgeStrength_childBottomIsBelowParentWithoutMargins_isCorrect() {
@@ -456,6 +465,78 @@
assertEquals(EdgeEffectSubstitute.State.Idle, edgeEffect.getState());
}
+ @Test
+ public void scrollFeedbackCallbacks() {
+ setup(200);
+ mScrollFeedbackProvider = mock(ScrollFeedbackProviderCompat.class);
+ mNestedScrollView.mScrollFeedbackProvider = mScrollFeedbackProvider;
+ setChildMargins(0, 0);
+ measureAndLayout(100);
+
+ MotionEvent ev = createScrollMotionEvent(
+ /* scrollAmount= */ 2f,
+ /* deviceId= */ 3,
+ /* source= */ InputDevice.SOURCE_ROTARY_ENCODER,
+ /* axis= */ MotionEvent.AXIS_SCROLL);
+ // Scroll up by -2.
+ mNestedScrollView.scrollBy(
+ /* verticalScrollDistance= */ -2, MotionEvent.AXIS_SCROLL, ev, /* x= */ 3,
+ ViewCompat.TYPE_TOUCH, /* isSourceMouseOrKeyboard= */ false);
+
+ // Since the view was already at the very top, a scroll up by -2 should not cause a
+ // onScrollProgress call, but should call onScrollLimit.
+ verify(mScrollFeedbackProvider, never()).onScrollProgress(
+ anyInt(), anyInt(), anyInt(), anyInt());
+ verify(mScrollFeedbackProvider).onScrollLimit(
+ /* inputDeviceId= */ 3, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* isStart= */ true);
+ reset(mScrollFeedbackProvider);
+
+ // Scroll down by 20.
+ mNestedScrollView.scrollBy(
+ /* verticalScrollDistance= */ 20, MotionEvent.AXIS_SCROLL, ev, /* x= */ 3,
+ ViewCompat.TYPE_TOUCH, /* isSourceMouseOrKeyboard= */ false);
+
+ // The height of the view is 100. Since the scroll is 20 pixels, we expect all of it to be
+ // consumed. So, expect onScrollProgress with 20 pixels, and no onScrollLimit.
+ verify(mScrollFeedbackProvider).onScrollProgress(
+ /* inputDeviceId= */ 3, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* deltaInPixels= */ 20);
+ verify(mScrollFeedbackProvider, never()).onScrollLimit(
+ anyInt(), anyInt(), anyInt(), anyBoolean());
+
+ // At this point, the view was at y=20. So a scroll of 100 pixels should do a consumed
+ // scroll of 80 pixels, and also cause an onScrollLimit call.
+ mNestedScrollView.scrollBy(
+ /* verticalScrollDistance= */ 100, MotionEvent.AXIS_SCROLL, ev, /* x= */ 3,
+ ViewCompat.TYPE_TOUCH, /* isSourceMouseOrKeyboard= */ false);
+
+ verify(mScrollFeedbackProvider).onScrollProgress(
+ /* inputDeviceId= */ 3, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* deltaInPixels= */ 80);
+ verify(mScrollFeedbackProvider).onScrollLimit(
+ /* inputDeviceId= */ 3, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* isStart= */ false);
+ }
+
+ @Test
+ public void scrollFeedbackCallbacks_motionEventUnavailable() {
+ setup(200);
+ mScrollFeedbackProvider = mock(ScrollFeedbackProviderCompat.class);
+ mNestedScrollView.mScrollFeedbackProvider = mScrollFeedbackProvider;
+ setChildMargins(0, 0);
+ measureAndLayout(100);
+
+ mNestedScrollView.scrollBy(
+ /* verticalScrollDistance= */ -2, MotionEvent.AXIS_SCROLL, /* ev= */ null,
+ /* x= */ 3, ViewCompat.TYPE_TOUCH, /* isSourceMouseOrKeyboard= */ false);
+
+ verify(mScrollFeedbackProvider, never()).onScrollProgress(
+ anyInt(), anyInt(), anyInt(), anyInt());
+ verify(mScrollFeedbackProvider, never()).onScrollLimit(
+ anyInt(), anyInt(), anyInt(), anyBoolean());
+ }
+
private void swipeDown(boolean shortSwipe) {
float endY = shortSwipe ? mNestedScrollView.getHeight() / 2f :
mNestedScrollView.getHeight() - 1;
@@ -495,7 +576,8 @@
mNestedScrollView.dispatchTouchEvent(up);
}
- private void sendScroll(float scrollAmount, int source) {
+ private MotionEvent createScrollMotionEvent(
+ float scrollAmount, int deviceId, int source, int axis) {
float x = mNestedScrollView.getWidth() / 2f;
float y = mNestedScrollView.getHeight() / 2f;
MotionEvent.PointerProperties pointerProperties = new MotionEvent.PointerProperties();
@@ -503,11 +585,10 @@
MotionEvent.PointerCoords pointerCoords = new MotionEvent.PointerCoords();
pointerCoords.x = x;
pointerCoords.y = y;
- int axis = source == InputDevice.SOURCE_ROTARY_ENCODER ? MotionEvent.AXIS_SCROLL
- : MotionEvent.AXIS_VSCROLL;
+
pointerCoords.setAxisValue(axis, scrollAmount);
- MotionEvent scroll = MotionEvent.obtain(
+ return MotionEvent.obtain(
0, /* downTime */
0, /* eventTime */
MotionEvent.ACTION_SCROLL, /* action */
@@ -518,13 +599,18 @@
0, /* buttonState */
0f, /* xPrecision */
0f, /* yPrecision */
- 0, /* deviceId */
+ deviceId,
0, /* edgeFlags */
source, /* source */
0 /* flags */
);
+ }
- mNestedScrollView.dispatchGenericMotionEvent(scroll);
+ private void sendScroll(float scrollAmount, int source) {
+ int axis = source == InputDevice.SOURCE_ROTARY_ENCODER
+ ? MotionEvent.AXIS_SCROLL : MotionEvent.AXIS_VSCROLL;
+ mNestedScrollView.dispatchGenericMotionEvent(
+ createScrollMotionEvent(scrollAmount, /* deviceId= */ 1, source, axis));
}
private void setup(int childHeight) {
diff --git a/core/core/src/main/java/androidx/core/widget/NestedScrollView.java b/core/core/src/main/java/androidx/core/widget/NestedScrollView.java
index cbe5e8c..29e8f48 100644
--- a/core/core/src/main/java/androidx/core/widget/NestedScrollView.java
+++ b/core/core/src/main/java/androidx/core/widget/NestedScrollView.java
@@ -63,6 +63,7 @@
import androidx.core.view.NestedScrollingChildHelper;
import androidx.core.view.NestedScrollingParent3;
import androidx.core.view.NestedScrollingParentHelper;
+import androidx.core.view.ScrollFeedbackProviderCompat;
import androidx.core.view.ScrollingView;
import androidx.core.view.ViewCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
@@ -136,6 +137,10 @@
@NonNull
public EdgeEffect mEdgeGlowBottom;
+ @VisibleForTesting
+ ScrollFeedbackProviderCompat mScrollFeedbackProvider =
+ ScrollFeedbackProviderCompat.createProvider(this);
+
/**
* Position of the last motion event; only used with touch related events (usually to assist
* in movement changes in a drag gesture).
@@ -973,7 +978,9 @@
if (mIsBeingDragged) {
final int x = (int) motionEvent.getX(activePointerIndex);
- int scrollOffset = scrollBy(deltaY, x, ViewCompat.TYPE_TOUCH, false);
+ int scrollOffset =
+ scrollBy(deltaY, MotionEvent.AXIS_Y, motionEvent, x,
+ ViewCompat.TYPE_TOUCH, false);
// Updates the global positions (used by later move events to properly scroll).
mLastMotionY = y - scrollOffset;
mNestedYOffset += scrollOffset;
@@ -1052,7 +1059,24 @@
mEdgeGlowBottom.onRelease();
}
- /*
+ /**
+ * Same as {@link #scrollBy(int, int, MotionEvent, int, int, boolean)}, but with no entry for
+ * the vertical motion axis as well as the {@link MotionEvent}.
+ *
+ * <p>Use this method (instead of the other overload) if the {@link MotionEvent} that caused
+ * this scroll request is not known.
+ */
+ private int scrollBy(
+ int verticalScrollDistance,
+ int x,
+ int touchType,
+ boolean isSourceMouseOrKeyboard
+ ) {
+ return scrollBy(verticalScrollDistance, /* verticalScrollAxis= */ -1, null, x, touchType,
+ isSourceMouseOrKeyboard);
+ }
+
+ /**
* Handles scroll events for both touch and non-touch events (mouse scroll wheel,
* rotary button, keyboard, etc.).
*
@@ -1060,12 +1084,28 @@
* for calculating the total scroll between multiple move events (touch). This returned value
* is NOT needed for non-touch events since a scroll is a one time event (vs. touch where a
* drag may be triggered multiple times with the movement of the finger).
+ *
+ * @param verticalScrollDistance the amount of distance (in pixels) to scroll vertically.
+ * @param verticalScrollAxis the motion axis that triggered the vertical scroll. This is not
+ * always {@link MotionEvent#AXIS_Y}, because there could be other
+ * axes that trigger a vertical scroll on the view. For example,
+ * generic motion events reported via {@link MotionEvent#AXIS_SCROLL}
+ * or {@link MotionEvent#AXIS_VSCROLL}. Use {@code -1} if the vertical
+ * scroll axis is not known.
+ * @param ev the {@link MotionEvent} that caused this scroll. {@code null} if the event is not
+ * known.
+ * @param x the target location on the x axis.
+ * @param touchType the {@link ViewCompat.NestedScrollType} for this scroll.
+ * @param isSourceMouseOrKeyboard whether or not the scroll was caused by a mouse or a keyboard.
*/
// TODO: You should rename this to nestedScrollBy() so it is different from View.scrollBy
- private int scrollBy(
+ @VisibleForTesting
+ int scrollBy(
int verticalScrollDistance,
+ int verticalScrollAxis,
+ @Nullable MotionEvent ev,
int x,
- int touchType,
+ @ViewCompat.NestedScrollType int touchType,
boolean isSourceMouseOrKeyboard
) {
int totalScrollOffset = 0;
@@ -1121,6 +1161,10 @@
// The position may have been adjusted in the previous call, so we must revise our values.
final int scrollYDelta = getScrollY() - initialScrollY;
+ if (ev != null && scrollYDelta != 0) {
+ mScrollFeedbackProvider.onScrollProgress(
+ ev.getDeviceId(), ev.getSource(), verticalScrollAxis, scrollYDelta);
+ }
final int unconsumedY = verticalScrollDistance - scrollYDelta;
// Reset the Y consumed scroll to zero
@@ -1150,6 +1194,11 @@
(float) -verticalScrollDistance / getHeight(),
(float) x / getWidth()
);
+ if (ev != null) {
+ mScrollFeedbackProvider.onScrollLimit(
+ ev.getDeviceId(), ev.getSource(), verticalScrollAxis,
+ /* isStart= */ true);
+ }
if (!mEdgeGlowBottom.isFinished()) {
mEdgeGlowBottom.onRelease();
@@ -1163,6 +1212,11 @@
(float) verticalScrollDistance / getHeight(),
1.f - ((float) x / getWidth())
);
+ if (ev != null) {
+ mScrollFeedbackProvider.onScrollLimit(
+ ev.getDeviceId(), ev.getSource(), verticalScrollAxis,
+ /* isStart= */ false);
+ }
if (!mEdgeGlowTop.isFinished()) {
mEdgeGlowTop.onRelease();
@@ -1328,12 +1382,12 @@
if (motionEvent.getAction() == MotionEvent.ACTION_SCROLL && !mIsBeingDragged) {
final float verticalScroll;
final int x;
- final int flingAxis;
+ final int axis;
if (MotionEventCompat.isFromSource(motionEvent, InputDevice.SOURCE_CLASS_POINTER)) {
verticalScroll = motionEvent.getAxisValue(MotionEvent.AXIS_VSCROLL);
x = (int) motionEvent.getX();
- flingAxis = MotionEvent.AXIS_VSCROLL;
+ axis = MotionEvent.AXIS_VSCROLL;
} else if (
MotionEventCompat.isFromSource(motionEvent, InputDevice.SOURCE_ROTARY_ENCODER)
) {
@@ -1341,11 +1395,11 @@
// Since a Wear rotary event doesn't have a true X and we want to support proper
// overscroll animations, we put the x at the center of the screen.
x = getWidth() / 2;
- flingAxis = MotionEvent.AXIS_SCROLL;
+ axis = MotionEvent.AXIS_SCROLL;
} else {
verticalScroll = 0;
x = 0;
- flingAxis = 0;
+ axis = 0;
}
if (verticalScroll != 0) {
@@ -1355,9 +1409,10 @@
final boolean isSourceMouse =
MotionEventCompat.isFromSource(motionEvent, InputDevice.SOURCE_MOUSE);
- scrollBy(-invertedDelta, x, ViewCompat.TYPE_NON_TOUCH, isSourceMouse);
- if (flingAxis != 0) {
- mDifferentialMotionFlingController.onMotionEvent(motionEvent, flingAxis);
+ scrollBy(-invertedDelta, axis, motionEvent, x, ViewCompat.TYPE_NON_TOUCH,
+ isSourceMouse);
+ if (axis != 0) {
+ mDifferentialMotionFlingController.onMotionEvent(motionEvent, axis);
}
return true;
diff --git a/libraryversions.toml b/libraryversions.toml
index e0fec0e..7139a8e 100644
--- a/libraryversions.toml
+++ b/libraryversions.toml
@@ -7,7 +7,7 @@
ARCH_CORE = "2.3.0-alpha01"
ASYNCLAYOUTINFLATER = "1.1.0-alpha02"
AUTOFILL = "1.3.0-rc01"
-BENCHMARK = "1.4.0-alpha05"
+BENCHMARK = "1.4.0-alpha06"
BIOMETRIC = "1.4.0-alpha02"
BLUETOOTH = "1.0.0-alpha02"
BROWSER = "1.9.0-alpha01"
@@ -116,7 +116,7 @@
PRIVACYSANDBOX_UI = "1.0.0-alpha11"
PROFILEINSTALLER = "1.5.0-alpha01"
RECOMMENDATION = "1.1.0-alpha01"
-RECYCLERVIEW = "1.4.0-rc01"
+RECYCLERVIEW = "1.5.0-alpha01"
RECYCLERVIEW_SELECTION = "1.2.0-alpha02"
REMOTECALLBACK = "1.0.0-alpha02"
RESOURCEINSPECTION = "1.1.0-alpha01"
diff --git a/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/MainActivity.kt b/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/MainActivity.kt
index d241850..1ea74b7 100644
--- a/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/MainActivity.kt
+++ b/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/MainActivity.kt
@@ -119,7 +119,7 @@
}
// TODO(b/337793172): Replace with a default fragment
- switchContentFragment(ResizeFragment(), "Resize Fragment")
+ switchContentFragment(ResizeFragment(), "Resize CUJ")
setWindowsInsetsListener()
initializeOptionsButton()
diff --git a/recyclerview/recyclerview/build.gradle b/recyclerview/recyclerview/build.gradle
index e60f8b5..f9c876e 100644
--- a/recyclerview/recyclerview/build.gradle
+++ b/recyclerview/recyclerview/build.gradle
@@ -15,7 +15,7 @@
dependencies {
api("androidx.annotation:annotation:1.8.1")
- api("androidx.core:core:1.13.0")
+ implementation(project(path: ":core:core"))
implementation("androidx.collection:collection:1.4.2")
api("androidx.customview:customview:1.0.0")
implementation("androidx.customview:customview-poolingcontainer:1.0.0")
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewBasicTest.java b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewBasicTest.java
index f524970..0ec5d08 100644
--- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewBasicTest.java
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewBasicTest.java
@@ -24,6 +24,12 @@
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
import android.annotation.SuppressLint;
import android.content.Context;
@@ -44,9 +50,14 @@
import android.widget.TextView;
import androidx.annotation.NonNull;
+import androidx.core.util.Pair;
+import androidx.core.view.InputDeviceCompat;
+import androidx.core.view.ScrollFeedbackProviderCompat;
+import androidx.core.view.ViewCompat;
import androidx.recyclerview.test.R;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import org.junit.Before;
@@ -66,6 +77,8 @@
RecyclerView mRecyclerView;
+ ScrollFeedbackProviderCompat mScrollFeedbackProvider;
+
@Before
public void setUp() throws Exception {
mRecyclerView = new RecyclerView(getContext());
@@ -148,6 +161,76 @@
}
@Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public void scrollFeedbackCallbacks() {
+ mScrollFeedbackProvider = mock(ScrollFeedbackProviderCompat.class);
+ mRecyclerView.mScrollFeedbackProvider = mScrollFeedbackProvider;
+ mRecyclerView.setAdapter(new MockAdapter(20));
+ MockLayoutManager layoutManager = new MockLayoutManager();
+ mRecyclerView.setLayoutManager(layoutManager);
+ measure();
+ layout();
+
+ MotionEvent ev = TouchUtils.createMotionEvent(
+ /* inputDeviceId= */ 1,
+ InputDeviceCompat.SOURCE_TOUCHSCREEN,
+ MotionEvent.ACTION_MOVE,
+ List.of(
+ Pair.create(MotionEvent.AXIS_X, 10),
+ Pair.create(MotionEvent.AXIS_Y, -20)));
+ layoutManager.mConsumedHorizontalScroll = 3;
+ layoutManager.mConsumedVerticalScroll = -20;
+ mRecyclerView.scrollByInternal(
+ /* x= */ 10,
+ /* y= */ -20,
+ /* horizontalAxis= */ MotionEvent.AXIS_X,
+ /* verticalAxis= */ MotionEvent.AXIS_Y,
+ ev,
+ ViewCompat.TYPE_TOUCH);
+
+ // Verify onScrollProgress calls equating to the amount of consumed pixels on each axis.
+ verify(mScrollFeedbackProvider).onScrollProgress(
+ /* inputDeviceId= */ 1, InputDeviceCompat.SOURCE_TOUCHSCREEN, MotionEvent.AXIS_X,
+ /* deltaInPixels= */ 3);
+ verify(mScrollFeedbackProvider).onScrollProgress(
+ /* inputDeviceId= */ 1, InputDeviceCompat.SOURCE_TOUCHSCREEN, MotionEvent.AXIS_Y,
+ /* deltaInPixels= */ -20);
+ // Part of the X scroll was not consumed, so expect an onScrollLimit call.
+ verify(mScrollFeedbackProvider).onScrollLimit(
+ /* inputDeviceId= */ 1, InputDeviceCompat.SOURCE_TOUCHSCREEN, MotionEvent.AXIS_X,
+ /* isStart= */ false);
+ // All of the Y scroll was consumed. So expect no onScrollLimit call.
+ verify(mScrollFeedbackProvider, never()).onScrollLimit(
+ /* inputDeviceId= */ anyInt(), anyInt(), eq(MotionEvent.AXIS_Y),
+ /* isStart= */ eq(false));
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public void scrollFeedbackCallbacks_motionEventUnavailable() {
+ mScrollFeedbackProvider = mock(ScrollFeedbackProviderCompat.class);
+ mRecyclerView.mScrollFeedbackProvider = mScrollFeedbackProvider;
+ mRecyclerView.setAdapter(new MockAdapter(20));
+ MockLayoutManager layoutManager = new MockLayoutManager();
+ mRecyclerView.setLayoutManager(layoutManager);
+ measure();
+ layout();
+
+ mRecyclerView.scrollByInternal(
+ /* x= */ 10,
+ /* y= */ -20,
+ /* horizontalAxis= */ -1,
+ /* verticalAxis= */ -1,
+ null,
+ ViewCompat.TYPE_TOUCH);
+
+ verify(mScrollFeedbackProvider, never()).onScrollProgress(
+ anyInt(), anyInt(), anyInt(), anyInt());
+ verify(mScrollFeedbackProvider, never()).onScrollLimit(
+ anyInt(), anyInt(), anyInt(), anyBoolean());
+ }
+
+ @Test
public void smoothScrollToPositionWithoutLayoutManager() throws InterruptedException {
mRecyclerView.setAdapter(new MockAdapter(20));
measure();
@@ -614,6 +697,9 @@
int mAdapterChangedCount = 0;
int mItemsChangedCount = 0;
+ int mConsumedHorizontalScroll = Integer.MIN_VALUE;
+ int mConsumedVerticalScroll = Integer.MIN_VALUE;
+
RecyclerView.Adapter mPrevAdapter;
RecyclerView.Adapter mNextAdapter;
@@ -675,13 +761,13 @@
@Override
public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
RecyclerView.State state) {
- return dx;
+ return mConsumedHorizontalScroll != Integer.MIN_VALUE ? mConsumedHorizontalScroll : dx;
}
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
RecyclerView.State state) {
- return dy;
+ return mConsumedVerticalScroll != Integer.MIN_VALUE ? mConsumedVerticalScroll : dy;
}
@Override
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewOnGenericMotionEventTest.java b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewOnGenericMotionEventTest.java
index e164745..f1098a1 100644
--- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewOnGenericMotionEventTest.java
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewOnGenericMotionEventTest.java
@@ -328,10 +328,16 @@
return super.onGenericMotionEvent(ev);
}
- boolean scrollByInternal(int x, int y, MotionEvent ev, int type) {
+ boolean scrollByInternal(
+ int x,
+ int y,
+ int horizontalAxis,
+ int verticalAxis,
+ @Nullable MotionEvent ev,
+ int type) {
mTotalX += x;
mTotalY += y;
- return super.scrollByInternal(x, y, ev, type);
+ return super.scrollByInternal(x, y, horizontalAxis, verticalAxis, ev, type);
}
void smoothScrollBy(@Px int dx, @Px int dy, @Nullable Interpolator interpolator,
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/TouchUtils.java b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/TouchUtils.java
index 69df81d..83785d8 100644
--- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/TouchUtils.java
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/TouchUtils.java
@@ -23,6 +23,10 @@
import android.view.View;
import android.view.ViewConfiguration;
+import androidx.core.util.Pair;
+
+import java.util.List;
+
/**
* RV specific layout tests.
*/
@@ -173,14 +177,30 @@
/** Creates a {@link MotionEvent} with provided input and motion values. */
static MotionEvent createMotionEvent(
int inputDeviceId, int inputSource, int axis, int axisValue) {
+ return createMotionEvent(
+ inputDeviceId, inputSource, MotionEvent.ACTION_SCROLL,
+ List.of(Pair.create(axis, axisValue)));
+ }
+
+ /**
+ * Creates a {@link MotionEvent} with provided input and motion values.
+ *
+ * <p>Allows passing values for multiple axes. Each axis value is represented as a
+ * {@link Pair} of the axis and the respective axis value.
+ */
+ static MotionEvent createMotionEvent(
+ int inputDeviceId, int inputSource, int action,
+ List<Pair<Integer, Integer>> axisValues) {
MotionEvent.PointerProperties props = new MotionEvent.PointerProperties();
props.id = 0;
MotionEvent.PointerProperties[] pointerProperties = {props};
MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
- coords.setAxisValue(axis, axisValue);
+ for (Pair<Integer, Integer> axisValue : axisValues) {
+ coords.setAxisValue(axisValue.first, axisValue.second);
+ }
MotionEvent.PointerCoords[] pointerCoords = {coords};
return MotionEvent.obtain(
- 0, System.currentTimeMillis(), MotionEvent.ACTION_SCROLL,
+ 0, System.currentTimeMillis(), action,
1, pointerProperties, pointerCoords, 0, 0, 1, 1, inputDeviceId, 0, inputSource, 0);
}
diff --git a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java
index 0693836..ab7d486 100644
--- a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java
+++ b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java
@@ -82,6 +82,7 @@
import androidx.core.view.NestedScrollingChild2;
import androidx.core.view.NestedScrollingChild3;
import androidx.core.view.NestedScrollingChildHelper;
+import androidx.core.view.ScrollFeedbackProviderCompat;
import androidx.core.view.ScrollingView;
import androidx.core.view.ViewCompat;
import androidx.core.view.ViewConfigurationCompat;
@@ -768,6 +769,11 @@
@VisibleForTesting
DifferentialMotionFlingController mDifferentialMotionFlingController =
new DifferentialMotionFlingController(getContext(), mDifferentialMotionFlingTarget);
+
+ @VisibleForTesting
+ ScrollFeedbackProviderCompat mScrollFeedbackProvider =
+ ScrollFeedbackProviderCompat.createProvider(this);
+
public RecyclerView(@NonNull Context context) {
this(context, null);
}
@@ -2060,7 +2066,12 @@
final boolean canScrollHorizontal = mLayout.canScrollHorizontally();
final boolean canScrollVertical = mLayout.canScrollVertically();
if (canScrollHorizontal || canScrollVertical) {
- scrollByInternal(canScrollHorizontal ? x : 0, canScrollVertical ? y : 0, null,
+ scrollByInternal(
+ canScrollHorizontal ? x : 0,
+ canScrollVertical ? y : 0,
+ /* horizontalAxis= */ -1,
+ /* verticalAxis= */ -1,
+ /* ev= */ null,
TYPE_TOUCH);
}
}
@@ -2072,21 +2083,32 @@
* @see androidx.core.view.NestedScrollingChild
*/
public void nestedScrollBy(int x, int y) {
- nestedScrollByInternal(x, y, null, TYPE_NON_TOUCH);
+ nestedScrollByInternal(x, y, -1, -1, null, TYPE_NON_TOUCH);
}
/**
- * Similar to {@link RecyclerView#scrollByInternal(int, int, MotionEvent, int)}, but fully
- * participates in nested scrolling "end to end", meaning that it will start nested scrolling,
- * participate in nested scrolling, and then end nested scrolling all within one call.
+ * Similar to {@link RecyclerView#scrollByInternal(int, int, int, int, MotionEvent, int)}, but
+ * fully participates in nested scrolling "end to end", meaning that it will start nested
+ * scrolling, participate in nested scrolling, and then end nested scrolling all within one
+ * call.
+ *
* @param x The amount of horizontal scroll requested.
* @param y The amount of vertical scroll requested.
+ * @param horizontalAxis the {@link MotionEvent} axis that caused the {@code x} scroll, or -1 if
+ * not known.
+ * @param verticalAxis the {@link MotionEvent} axis that caused the {@code y} scroll, or -1 if
+ * not known.
* @param motionEvent The originating MotionEvent if any.
* @param type The type of nested scrolling to engage in (TYPE_TOUCH or TYPE_NON_TOUCH).
*/
@SuppressWarnings("SameParameterValue")
- private void nestedScrollByInternal(int x, int y, @Nullable MotionEvent motionEvent, int type) {
-
+ private void nestedScrollByInternal(
+ int x,
+ int y,
+ int horizontalAxis,
+ int verticalAxis,
+ @Nullable MotionEvent motionEvent,
+ int type) {
if (mLayout == null) {
Log.e(TAG, "Cannot scroll without a LayoutManager set. "
+ "Call setLayoutManager with a non-null argument.");
@@ -2126,6 +2148,8 @@
scrollByInternal(
canScrollHorizontal ? x : 0,
canScrollVertical ? y : 0,
+ horizontalAxis,
+ verticalAxis,
motionEvent, type);
if (mGapWorker != null && (x != 0 || y != 0)) {
mGapWorker.postFromTraversal(this, x, y);
@@ -2243,11 +2267,21 @@
*
* @param x The amount of horizontal scroll request
* @param y The amount of vertical scroll request
- * @param ev The originating MotionEvent, or null if not from a touch event.
+ * @param horizontalAxis the {@link MotionEvent} axis that caused the {@code x} scroll, or -1 if
+ * not known.
+ * @param verticalAxis the {@link MotionEvent} axis that caused the {@code y} scroll, or -1 if
+ * not known.
+ * @param ev The originating MotionEvent, or null if unknown.
* @param type NestedScrollType, TOUCH or NON_TOUCH.
* @return Whether any scroll was consumed in either direction.
*/
- boolean scrollByInternal(int x, int y, MotionEvent ev, int type) {
+ boolean scrollByInternal(
+ int x,
+ int y,
+ int horizontalAxis,
+ int verticalAxis,
+ @Nullable MotionEvent ev,
+ int type) {
int unconsumedX = 0;
int unconsumedY = 0;
int consumedX = 0;
@@ -2281,9 +2315,21 @@
mNestedOffsets[0] += mScrollOffset[0];
mNestedOffsets[1] += mScrollOffset[1];
+ if (ev != null) {
+ if (consumedX != 0) {
+ mScrollFeedbackProvider.onScrollProgress(
+ ev.getDeviceId(), ev.getSource(), horizontalAxis, consumedX);
+ }
+ if (consumedY != 0) {
+ mScrollFeedbackProvider.onScrollProgress(
+ ev.getDeviceId(), ev.getSource(), verticalAxis, consumedY);
+ }
+ }
+
if (getOverScrollMode() != View.OVER_SCROLL_NEVER) {
if (ev != null && !MotionEventCompat.isFromSource(ev, InputDevice.SOURCE_MOUSE)) {
- pullGlows(ev.getX(), unconsumedX, ev.getY(), unconsumedY);
+ pullGlows(ev, ev.getX(), horizontalAxis, unconsumedX, ev.getY(), verticalAxis,
+ unconsumedY);
// For rotary encoders, we release stretch EdgeEffects after they are pulled, to
// avoid the effects being stuck pulled.
if (Build.VERSION.SDK_INT >= 31
@@ -3061,27 +3107,50 @@
/**
* Apply a pull to relevant overscroll glow effects
*/
- private void pullGlows(float x, float overscrollX, float y, float overscrollY) {
+ private void pullGlows(
+ MotionEvent ev,
+ float x,
+ int horizontalAxis,
+ float overscrollX,
+ float y,
+ int verticalAxis,
+ float overscrollY) {
boolean invalidate = false;
if (overscrollX < 0) {
ensureLeftGlow();
EdgeEffectCompat.onPullDistance(mLeftGlow, -overscrollX / getWidth(),
1f - y / getHeight());
+ if (ev != null) {
+ mScrollFeedbackProvider.onScrollLimit(
+ ev.getDeviceId(), ev.getSource(), horizontalAxis, /* isStart= */ true);
+ }
invalidate = true;
} else if (overscrollX > 0) {
ensureRightGlow();
EdgeEffectCompat.onPullDistance(mRightGlow, overscrollX / getWidth(), y / getHeight());
+ if (ev != null) {
+ mScrollFeedbackProvider.onScrollLimit(
+ ev.getDeviceId(), ev.getSource(), horizontalAxis, /* isStart= */ false);
+ }
invalidate = true;
}
if (overscrollY < 0) {
ensureTopGlow();
EdgeEffectCompat.onPullDistance(mTopGlow, -overscrollY / getHeight(), x / getWidth());
+ if (ev != null) {
+ mScrollFeedbackProvider.onScrollLimit(
+ ev.getDeviceId(), ev.getSource(), verticalAxis, /* isStart= */ true);
+ }
invalidate = true;
} else if (overscrollY > 0) {
ensureBottomGlow();
EdgeEffectCompat.onPullDistance(mBottomGlow, overscrollY / getHeight(),
1f - x / getWidth());
+ if (ev != null) {
+ mScrollFeedbackProvider.onScrollLimit(
+ ev.getDeviceId(), ev.getSource(), verticalAxis, /* isStart= */ false);
+ }
invalidate = true;
}
@@ -3969,6 +4038,8 @@
if (scrollByInternal(
canScrollHorizontally ? dx : 0,
canScrollVertically ? dy : 0,
+ MotionEvent.AXIS_X,
+ MotionEvent.AXIS_Y,
e, TYPE_TOUCH)) {
getParent().requestDisallowInterceptTouchEvent(true);
}
@@ -4047,6 +4118,8 @@
}
int flingAxis = 0;
+ int horizontalAxis = 0;
+ int verticalAxis = 0;
boolean useSmoothScroll = false;
if (event.getAction() == MotionEvent.ACTION_SCROLL) {
final float vScroll, hScroll;
@@ -4055,11 +4128,13 @@
// Inverse the sign of the vertical scroll to align the scroll orientation
// with AbsListView.
vScroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL);
+ verticalAxis = MotionEvent.AXIS_VSCROLL;
} else {
vScroll = 0f;
}
if (mLayout.canScrollHorizontally()) {
hScroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL);
+ horizontalAxis = MotionEvent.AXIS_HSCROLL;
} else {
hScroll = 0f;
}
@@ -4069,10 +4144,12 @@
// Invert the sign of the vertical scroll to align the scroll orientation
// with AbsListView.
vScroll = -axisScroll;
+ verticalAxis = MotionEvent.AXIS_SCROLL;
hScroll = 0f;
} else if (mLayout.canScrollHorizontally()) {
vScroll = 0f;
hScroll = axisScroll;
+ horizontalAxis = MotionEvent.AXIS_SCROLL;
} else {
vScroll = 0f;
hScroll = 0f;
@@ -4097,7 +4174,8 @@
smoothScrollBy(scaledHScroll, scaledVScroll, /* interpolator= */ null,
UNDEFINED_DURATION, /* withNestedScrolling= */ true);
} else {
- nestedScrollByInternal(scaledHScroll, scaledVScroll, event, TYPE_NON_TOUCH);
+ nestedScrollByInternal(scaledHScroll, scaledVScroll, horizontalAxis,
+ verticalAxis, event, TYPE_NON_TOUCH);
}
if (flingAxis != 0 && !useSmoothScroll) {