Merge "Simplify GitClient implementations" into androidx-main
diff --git a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SchemaToProtoConverterTest.java b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SchemaToProtoConverterTest.java
index e67ab5f..4513357 100644
--- a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SchemaToProtoConverterTest.java
+++ b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SchemaToProtoConverterTest.java
@@ -180,19 +180,11 @@
.addParentTypes("Email")
.addParentTypes("Message")
.build();
- SchemaTypeConfigProto alternativeExpectedSchemaProto = SchemaTypeConfigProto.newBuilder()
- .setSchemaType("EmailMessage")
- .setVersion(12345)
- .addParentTypes("Message")
- .addParentTypes("Email")
- .build();
assertThat(SchemaToProtoConverter.toSchemaTypeConfigProto(schema, /*version=*/12345))
- .isAnyOf(expectedSchemaProto, alternativeExpectedSchemaProto);
+ .isEqualTo(expectedSchemaProto);
assertThat(SchemaToProtoConverter.toAppSearchSchema(expectedSchemaProto))
.isEqualTo(schema);
- assertThat(SchemaToProtoConverter.toAppSearchSchema(alternativeExpectedSchemaProto))
- .isEqualTo(schema);
}
@Test
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 5abb9d0..28638a4 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AnnotationProcessorTestBase.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AnnotationProcessorTestBase.java
@@ -1546,4 +1546,382 @@
expectedDocumentMap.get(key));
}
}
+
+ @Test
+ public void testGetAssignableClassBySchemaName() throws Exception {
+ // Assignable to InterfaceRoot
+ assertThat(AppSearchDocumentClassMap.getAssignableClassBySchemaName(
+ "InterfaceRoot", InterfaceRoot.class))
+ .isEqualTo(InterfaceRoot.class);
+ assertThat(AppSearchDocumentClassMap.getAssignableClassBySchemaName(
+ "Place", InterfaceRoot.class))
+ .isEqualTo(Place.class);
+ assertThat(AppSearchDocumentClassMap.getAssignableClassBySchemaName(
+ "Organization", InterfaceRoot.class))
+ .isEqualTo(Organization.class);
+ assertThat(AppSearchDocumentClassMap.getAssignableClassBySchemaName(
+ "Business", InterfaceRoot.class))
+ .isEqualTo(Business.class);
+ assertThat(AppSearchDocumentClassMap.getAssignableClassBySchemaName(
+ "BusinessImpl", InterfaceRoot.class))
+ .isEqualTo(BusinessImpl.class);
+ assertThat(AppSearchDocumentClassMap.getAssignableClassBySchemaName(
+ "Person", InterfaceRoot.class))
+ .isEqualTo(Person.class);
+
+ // Assignable to Place
+ assertThat(AppSearchDocumentClassMap.getAssignableClassBySchemaName(
+ "InterfaceRoot", Place.class))
+ .isNull();
+ assertThat(AppSearchDocumentClassMap.getAssignableClassBySchemaName(
+ "Place", Place.class))
+ .isEqualTo(Place.class);
+ assertThat(AppSearchDocumentClassMap.getAssignableClassBySchemaName(
+ "Organization", Place.class))
+ .isNull();
+ assertThat(AppSearchDocumentClassMap.getAssignableClassBySchemaName(
+ "Business", Place.class))
+ .isEqualTo(Business.class);
+ assertThat(AppSearchDocumentClassMap.getAssignableClassBySchemaName(
+ "BusinessImpl", Place.class))
+ .isEqualTo(BusinessImpl.class);
+ assertThat(AppSearchDocumentClassMap.getAssignableClassBySchemaName(
+ "Person", Place.class))
+ .isNull();
+
+ // Assignable to Business
+ assertThat(AppSearchDocumentClassMap.getAssignableClassBySchemaName(
+ "InterfaceRoot", Business.class))
+ .isNull();
+ assertThat(AppSearchDocumentClassMap.getAssignableClassBySchemaName(
+ "Place", Business.class))
+ .isNull();
+ assertThat(AppSearchDocumentClassMap.getAssignableClassBySchemaName(
+ "Organization", Business.class))
+ .isNull();
+ assertThat(AppSearchDocumentClassMap.getAssignableClassBySchemaName(
+ "Business", Business.class))
+ .isEqualTo(Business.class);
+ assertThat(AppSearchDocumentClassMap.getAssignableClassBySchemaName(
+ "BusinessImpl", Business.class))
+ .isEqualTo(BusinessImpl.class);
+ assertThat(AppSearchDocumentClassMap.getAssignableClassBySchemaName(
+ "Person", Business.class))
+ .isNull();
+
+ // Assignable to Person
+ assertThat(AppSearchDocumentClassMap.getAssignableClassBySchemaName(
+ "InterfaceRoot", Person.class))
+ .isNull();
+ assertThat(AppSearchDocumentClassMap.getAssignableClassBySchemaName(
+ "Place", Person.class))
+ .isNull();
+ assertThat(AppSearchDocumentClassMap.getAssignableClassBySchemaName(
+ "Organization", Person.class))
+ .isNull();
+ assertThat(AppSearchDocumentClassMap.getAssignableClassBySchemaName(
+ "Business", Person.class))
+ .isNull();
+ assertThat(AppSearchDocumentClassMap.getAssignableClassBySchemaName(
+ "BusinessImpl", Person.class))
+ .isNull();
+ assertThat(AppSearchDocumentClassMap.getAssignableClassBySchemaName(
+ "Person", Person.class))
+ .isEqualTo(Person.class);
+ }
+
+ @Test
+ public void testPolymorphicDeserialization_ToOriginalType() throws Exception {
+ // Create Person document
+ Person.Builder personBuilder = new Person.Builder("id", "namespace")
+ .setCreationTimestamp(3000)
+ .setFirstName("first");
+ personBuilder.setLastName("last");
+ Person person = personBuilder.build();
+
+ // Convert person to GenericDocument
+ GenericDocument genericDocument = GenericDocument.fromDocumentClass(person);
+
+ // Test that even when deserializing genericDocument to InterfaceRoot, we will get an
+ // Person instance, instead of just an InterfaceRoot.
+ InterfaceRoot interfaceRoot = genericDocument.toDocumentClass(InterfaceRoot.class);
+ assertThat(interfaceRoot).isInstanceOf(Person.class);
+ Person newPerson = (Person) interfaceRoot;
+ assertThat(newPerson.getId()).isEqualTo("id");
+ assertThat(newPerson.getNamespace()).isEqualTo("namespace");
+ assertThat(newPerson.getCreationTimestamp()).isEqualTo(3000);
+ assertThat(newPerson.getFirstName()).isEqualTo("first");
+ assertThat(newPerson.getLastName()).isEqualTo("last");
+ }
+
+ @Test
+ public void testPolymorphicDeserialization_ToBestCompatibleType() throws Exception {
+ // Create a GenericDocument of unknown type.
+ GenericDocument genericDocument =
+ new GenericDocument.Builder<>("namespace", "id", "UnknownType")
+ .setCreationTimestampMillis(3000)
+ .setPropertyString("firstName", "first")
+ .setPropertyString("lastName", "last")
+ .build();
+
+ // 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);
+ assertThat(interfaceRoot).isNotInstanceOf(Person.class);
+ assertThat(interfaceRoot).isInstanceOf(InterfaceRoot.class);
+ assertThat(interfaceRoot.getId()).isEqualTo("id");
+ assertThat(interfaceRoot.getNamespace()).isEqualTo("namespace");
+ assertThat(interfaceRoot.getCreationTimestamp()).isEqualTo(3000);
+
+ // With parent information, toDocumentClass() will try to deserialize unknown type to the
+ // nearest known parent type.
+ genericDocument = genericDocument.toBuilder()
+ .setParentTypes(new ArrayList<>(Arrays.asList("Person", "InterfaceRoot")))
+ .build();
+ interfaceRoot = genericDocument.toDocumentClass(InterfaceRoot.class);
+ assertThat(interfaceRoot).isInstanceOf(Person.class);
+ Person newPerson = (Person) interfaceRoot;
+ assertThat(newPerson.getId()).isEqualTo("id");
+ assertThat(newPerson.getNamespace()).isEqualTo("namespace");
+ assertThat(newPerson.getCreationTimestamp()).isEqualTo(3000);
+ assertThat(newPerson.getFirstName()).isEqualTo("first");
+ assertThat(newPerson.getLastName()).isEqualTo("last");
+ }
+
+ @Test
+ 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.
+ 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())
+ .build()).get();
+
+ // Create and put an UnknownBusiness document.
+ GenericDocument genericDoc =
+ new GenericDocument.Builder<>("namespace", "id", "UnknownBusiness")
+ .setCreationTimestampMillis(3000)
+ .setPropertyString("location", "business_loc")
+ .setPropertyString("organizationDescription", "business_dec")
+ .setPropertyString("businessName", "business_name")
+ .setPropertyString("unknownProperty", "foo")
+ .build();
+ checkIsBatchResultSuccess(mSession.putAsync(
+ 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 = genericDoc.toBuilder().setParentTypes(new ArrayList<>(
+ Arrays.asList("Business", "Place", "Organization", "InterfaceRoot"))).build();
+ assertThat(actualGenericDoc).isEqualTo(expectedGenericDoc);
+
+ // Deserializing it to InterfaceRoot will get a Business instance back.
+ InterfaceRoot interfaceRoot = actualGenericDoc.toDocumentClass(InterfaceRoot.class);
+ assertThat(interfaceRoot).isInstanceOf(Business.class);
+ Business business = (Business) interfaceRoot;
+ assertThat(business.getId()).isEqualTo("id");
+ 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
+ // | /
+ // UnknownA
+ @Test
+ public void testPolymorphicDeserialization_IntegrationDiamondThreeTypes() throws Exception {
+ assumeTrue(mSession.getFeatures().isFeatureSupported(Features.SCHEMA_ADD_PARENT_TYPE));
+
+ mSession.setSchemaAsync(new SetSchemaRequest.Builder()
+ .addSchemas(new AppSearchSchema.Builder("UnknownA")
+ .addParentType("InterfaceRoot")
+ .addParentType("Person")
+ .addProperty(new AppSearchSchema.StringPropertyConfig.Builder(
+ "firstName")
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .build())
+ .addProperty(new AppSearchSchema.StringPropertyConfig.Builder(
+ "lastName")
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .build())
+ .build())
+ .addDocumentClasses(Person.class)
+ .build()).get();
+
+ // Create and put an UnknownA document.
+ GenericDocument genericDoc =
+ new GenericDocument.Builder<>("namespace", "id", "UnknownA")
+ .setCreationTimestampMillis(3000)
+ .setPropertyString("firstName", "first")
+ .setPropertyString("lastName", "last")
+ .build();
+ checkIsBatchResultSuccess(mSession.putAsync(
+ 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 = genericDoc.toBuilder().setParentTypes(new ArrayList<>(
+ Arrays.asList("Person", "InterfaceRoot"))).build();
+ assertThat(actualGenericDoc).isEqualTo(expectedGenericDoc);
+
+ // Deserializing it to InterfaceRoot will get a Person instance back.
+ InterfaceRoot interfaceRoot = actualGenericDoc.toDocumentClass(InterfaceRoot.class);
+ assertThat(interfaceRoot).isInstanceOf(Person.class);
+ Person person = (Person) interfaceRoot;
+ assertThat(person.getId()).isEqualTo("id");
+ assertThat(person.getNamespace()).isEqualTo("namespace");
+ assertThat(person.getCreationTimestamp()).isEqualTo(3000);
+ assertThat(person.getFirstName()).isEqualTo("first");
+ assertThat(person.getLastName()).isEqualTo("last");
+ }
+
+ // InterfaceRoot
+ // / \
+ // UnknownA Person
+ // \ /
+ // Unknown B
+ @Test
+ public void testPolymorphicDeserialization_IntegrationDiamondTwoUnknown() throws Exception {
+ assumeTrue(mSession.getFeatures().isFeatureSupported(Features.SCHEMA_ADD_PARENT_TYPE));
+
+ mSession.setSchemaAsync(new SetSchemaRequest.Builder()
+ .addSchemas(new AppSearchSchema.Builder("UnknownA")
+ .addParentType("InterfaceRoot")
+ .build())
+ .addSchemas(new AppSearchSchema.Builder("UnknownB")
+ .addParentType("UnknownA")
+ .addParentType("Person")
+ .addProperty(new AppSearchSchema.StringPropertyConfig.Builder(
+ "firstName")
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .build())
+ .addProperty(new AppSearchSchema.StringPropertyConfig.Builder(
+ "lastName")
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .build())
+ .build())
+ .addDocumentClasses(Person.class)
+ .build()).get();
+
+ // Create and put an UnknownB document.
+ GenericDocument genericDoc =
+ new GenericDocument.Builder<>("namespace", "id", "UnknownB")
+ .setCreationTimestampMillis(3000)
+ .setPropertyString("firstName", "first")
+ .setPropertyString("lastName", "last")
+ .build();
+ checkIsBatchResultSuccess(mSession.putAsync(
+ 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 = genericDoc.toBuilder().setParentTypes(new ArrayList<>(
+ Arrays.asList("UnknownA", "Person", "InterfaceRoot"))).build();
+ assertThat(actualGenericDoc).isEqualTo(expectedGenericDoc);
+
+ // Deserializing it to InterfaceRoot will get a Person instance back.
+ InterfaceRoot interfaceRoot = actualGenericDoc.toDocumentClass(InterfaceRoot.class);
+ assertThat(interfaceRoot).isInstanceOf(Person.class);
+ Person person = (Person) interfaceRoot;
+ assertThat(person.getId()).isEqualTo("id");
+ assertThat(person.getNamespace()).isEqualTo("namespace");
+ assertThat(person.getCreationTimestamp()).isEqualTo(3000);
+ assertThat(person.getFirstName()).isEqualTo("first");
+ assertThat(person.getLastName()).isEqualTo("last");
+ }
+
+ // InterfaceRoot
+ // / \
+ // Person Organization
+ // \ /
+ // Unknown A
+ @Test
+ public void testPolymorphicDeserialization_IntegrationDiamondOneUnknown() throws Exception {
+ assumeTrue(mSession.getFeatures().isFeatureSupported(Features.SCHEMA_ADD_PARENT_TYPE));
+
+ mSession.setSchemaAsync(new SetSchemaRequest.Builder()
+ .addDocumentClasses(Person.class)
+ .addDocumentClasses(Organization.class)
+ .addSchemas(new AppSearchSchema.Builder("UnknownA")
+ .addParentType("Person")
+ .addParentType("Organization")
+ .addProperty(new AppSearchSchema.StringPropertyConfig.Builder(
+ "firstName")
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .build())
+ .addProperty(new AppSearchSchema.StringPropertyConfig.Builder(
+ "lastName")
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .build())
+ .addProperty(new AppSearchSchema.StringPropertyConfig.Builder(
+ "organizationDescription")
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .build())
+ .build())
+ .addDocumentClasses(Person.class)
+ .build()).get();
+
+ // Create and put an UnknownA document.
+ GenericDocument genericDoc =
+ new GenericDocument.Builder<>("namespace", "id", "UnknownA")
+ .setCreationTimestampMillis(3000)
+ .setPropertyString("firstName", "first")
+ .setPropertyString("lastName", "last")
+ .setPropertyString("organizationDescription", "person")
+ .build();
+ checkIsBatchResultSuccess(mSession.putAsync(
+ 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 = genericDoc.toBuilder().setParentTypes(new ArrayList<>(
+ Arrays.asList("Person", "Organization", "InterfaceRoot"))).build();
+ assertThat(actualGenericDoc).isEqualTo(expectedGenericDoc);
+
+ // 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);
+ assertThat(interfaceRoot).isInstanceOf(Person.class);
+ assertThat(interfaceRoot).isNotInstanceOf(Organization.class);
+ Person person = (Person) interfaceRoot;
+ assertThat(person.getId()).isEqualTo("id");
+ assertThat(person.getNamespace()).isEqualTo("namespace");
+ assertThat(person.getCreationTimestamp()).isEqualTo(3000);
+ assertThat(person.getFirstName()).isEqualTo("first");
+ assertThat(person.getLastName()).isEqualTo("last");
+ }
}
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchDocumentClassMap.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchDocumentClassMap.java
index 633e6c6..40736a0 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchDocumentClassMap.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchDocumentClassMap.java
@@ -16,9 +16,12 @@
// @exportToFramework:skipFile()
package androidx.appsearch.app;
+import android.util.Log;
+
import androidx.annotation.AnyThread;
import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.collection.ArrayMap;
@@ -40,12 +43,19 @@
@AnyThread
public abstract class AppSearchDocumentClassMap {
+ private static final String TAG = "AppSearchDocumentClassM";
+
/**
* The cached value of {@link #getMergedMap()}.
*/
private static volatile Map<String, List<String>> sMergedMap = null;
/**
+ * The cached value of {@code Class.forName(className)} for AppSearch document classes.
+ */
+ private static volatile Map<String, Class<?>> sCachedAppSearchClasses = new ArrayMap<>();
+
+ /**
* Collects all of the instances of the generated {@link AppSearchDocumentClassMap} classes
* available in the current JVM environment, and calls the {@link #getMap()} method from them to
* build, cache and return the merged map. The keys are schema type names, and the values are
@@ -65,6 +75,37 @@
}
/**
+ * Looks up the merged map to find a class for {@code schemaName} that is assignable to
+ * {@code documentClass}. Returns null if such class is not found.
+ */
+ @Nullable
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public static <T> Class<? extends T> getAssignableClassBySchemaName(@NonNull String schemaName,
+ @NonNull Class<T> documentClass) {
+ Map<String, List<String>> map = getMergedMap();
+ List<String> classNames = map.get(schemaName);
+ if (classNames == null) {
+ return null;
+ }
+ // If there are multiple classes that correspond to the schema name, then we will:
+ // 1. skip any classes that are not assignable to documentClass.
+ // 2. if there are still multiple candidates, return the first one in the merged map.
+ for (int i = 0; i < classNames.size(); ++i) {
+ String className = classNames.get(i);
+ try {
+ Class<?> clazz = getAppSearchDocumentClass(className);
+ if (documentClass.isAssignableFrom(clazz)) {
+ return clazz.asSubclass(documentClass);
+ }
+ } catch (ClassNotFoundException e) {
+ Log.w(TAG, "Failed to load document class \"" + className + "\". Perhaps the "
+ + "class was proguarded out?");
+ }
+ }
+ return null;
+ }
+
+ /**
* Returns the map from schema type names to the list of the fully qualified names of the
* corresponding document classes.
*/
@@ -72,6 +113,22 @@
protected abstract Map<String, List<String>> getMap();
@NonNull
+ private static Class<?> getAppSearchDocumentClass(@NonNull String className)
+ throws ClassNotFoundException {
+ Class<?> result;
+ synchronized (AppSearchDocumentClassMap.class) {
+ result = sCachedAppSearchClasses.get(className);
+ }
+ if (result == null) {
+ result = Class.forName(className);
+ synchronized (AppSearchDocumentClassMap.class) {
+ sCachedAppSearchClasses.put(className, result);
+ }
+ }
+ return result;
+ }
+
+ @NonNull
@GuardedBy("AppSearchDocumentClassMap.class")
private static Map<String, List<String>> buildMergedMapLocked() {
ServiceLoader<AppSearchDocumentClassMap> loader = ServiceLoader.load(
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchSchema.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchSchema.java
index 81bcd40c..f149cb2 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchSchema.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchSchema.java
@@ -39,6 +39,7 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
@@ -187,7 +188,7 @@
public static final class Builder {
private final String mSchemaType;
private ArrayList<Bundle> mPropertyBundles = new ArrayList<>();
- private ArraySet<String> mParentTypes = new ArraySet<>();
+ private LinkedHashSet<String> mParentTypes = new LinkedHashSet<>();
private final Set<String> mPropertyNames = new ArraySet<>();
private boolean mBuilt = false;
@@ -299,7 +300,7 @@
private void resetIfBuilt() {
if (mBuilt) {
mPropertyBundles = new ArrayList<>(mPropertyBundles);
- mParentTypes = new ArraySet<>(mParentTypes);
+ mParentTypes = new LinkedHashSet<>(mParentTypes);
mBuilt = false;
}
}
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 3e28f33..f4cc825 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/GenericDocument.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/GenericDocument.java
@@ -939,6 +939,13 @@
* assigned into fields of the given document class. As such, the most likely outcome of
* supplying the wrong document class would be an empty or partially populated result.
*
+ * <p>If this GenericDocument's type is recorded as a subtype of the provided
+ * {@code documentClass}, the method will find an AppSearch document class 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.
+ * Parent types are specified via {@link AppSearchSchema.Builder#addParentType(String)} or
+ * the annotation parameter {@link Document#parent()}.
+ *
* @param documentClass a class annotated with {@link Document}
* @return an instance of the document class after being converted from a
* {@link GenericDocument}
@@ -950,9 +957,44 @@
public <T> T toDocumentClass(@NonNull Class<T> documentClass) throws AppSearchException {
Preconditions.checkNotNull(documentClass);
DocumentClassFactoryRegistry registry = DocumentClassFactoryRegistry.getInstance();
- DocumentClassFactory<T> factory = registry.getOrCreateFactory(documentClass);
+ Class<? extends T> targetClass = findTargetClassToDeserialize(documentClass);
+ DocumentClassFactory<? extends T> factory = registry.getOrCreateFactory(targetClass);
return factory.fromGenericDocument(this);
}
+
+ /**
+ * Find a target class that is assignable to {@code documentClass} to deserialize this document.
+ *
+ * <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.
+ * If that still fails, {@code documentClass} itself will be returned.
+ */
+ @NonNull
+ private <T> Class<? extends T> findTargetClassToDeserialize(@NonNull Class<T> documentClass) {
+ // Find the target class by the doc's original type.
+ Class<? extends T> targetClass = AppSearchDocumentClassMap.getAssignableClassBySchemaName(
+ getSchemaType(), documentClass);
+ if (targetClass != null) {
+ return targetClass;
+ }
+
+ // Find the target class by parent types.
+ List<String> parentTypes = getParentTypes();
+ if (parentTypes != null) {
+ for (int i = 0; i < parentTypes.size(); ++i) {
+ targetClass = AppSearchDocumentClassMap.getAssignableClassBySchemaName(
+ parentTypes.get(i), documentClass);
+ if (targetClass != null) {
+ return targetClass;
+ }
+ }
+ }
+
+ Log.w(TAG, "Cannot find any compatible target class to deserialize. Perhaps the annotation "
+ + "processor was not run or the generated document class map was proguarded out?\n"
+ + "Try to deserialize to " + documentClass.getCanonicalName() + " directly.");
+ return documentClass;
+ }
// @exportToFramework:endStrip()
/**
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/CodeGenerator.java b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/CodeGenerator.java
index caa2e58..e319de9 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/CodeGenerator.java
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/CodeGenerator.java
@@ -16,6 +16,9 @@
package androidx.appsearch.compiler;
+import static androidx.appsearch.compiler.IntrospectionHelper.DOCUMENT_CLASS_FACTORY_CLASS;
+import static androidx.appsearch.compiler.IntrospectionHelper.getDocumentClassFactoryForClass;
+
import androidx.annotation.NonNull;
import com.google.auto.common.GeneratedAnnotationSpecs;
@@ -36,7 +39,6 @@
*/
class CodeGenerator {
private final ProcessingEnvironment mEnv;
- private final IntrospectionHelper mHelper;
private final DocumentModel mModel;
private final String mOutputPackage;
@@ -49,11 +51,10 @@
}
private CodeGenerator(
- @NonNull ProcessingEnvironment env, @NonNull DocumentModel model)
- throws ProcessingException {
+ @NonNull ProcessingEnvironment env,
+ @NonNull DocumentModel model) throws ProcessingException {
// Prepare constants needed for processing
mEnv = env;
- mHelper = new IntrospectionHelper(env);
mModel = model;
// Perform the actual work of generating code
@@ -69,22 +70,21 @@
* Creates factory class for any class annotated with
* {@link androidx.appsearch.annotation.Document}
* <p>Class Example 1:
- * For a class Foo annotated with @Document, we will generated a
- * $$__AppSearch__Foo.class under the output package.
+ * For a class Foo annotated with @Document, we will generated a
+ * $$__AppSearch__Foo.class under the output package.
* <p>Class Example 2:
- * For an inner class Foo.Bar annotated with @Document, we will generated a
- * $$__AppSearch__Foo$$__Bar.class under the output package.
+ * For an inner class Foo.Bar annotated with @Document, we will generated a
+ * $$__AppSearch__Foo$$__Bar.class under the output package.
*/
private TypeSpec createClass() throws ProcessingException {
// Gets the full name of target class.
String qualifiedName = mModel.getQualifiedDocumentClassName();
String className = qualifiedName.substring(mOutputPackage.length() + 1);
- ClassName genClassName = mHelper.getDocumentClassFactoryForClass(mOutputPackage, className);
+ ClassName genClassName = getDocumentClassFactoryForClass(mOutputPackage, className);
TypeName genClassType = TypeName.get(mModel.getClassElement().asType());
- TypeName factoryType = ParameterizedTypeName.get(
- mHelper.getAppSearchClass("DocumentClassFactory"),
- genClassType);
+ TypeName factoryType =
+ ParameterizedTypeName.get(DOCUMENT_CLASS_FACTORY_CLASS, genClassType);
TypeSpec.Builder genClass = TypeSpec
.classBuilder(genClassName)
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/DocumentModel.java b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/DocumentModel.java
index e8faa1e..a6c6a24 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/DocumentModel.java
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/DocumentModel.java
@@ -15,45 +15,35 @@
*/
package androidx.appsearch.compiler;
-import static androidx.appsearch.compiler.IntrospectionHelper.BUILDER_PRODUCER_CLASS;
-import static androidx.appsearch.compiler.IntrospectionHelper.DOCUMENT_ANNOTATION_CLASS;
import static androidx.appsearch.compiler.IntrospectionHelper.generateClassHierarchy;
import static androidx.appsearch.compiler.IntrospectionHelper.getDocumentAnnotation;
+import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.groupingBy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
-import androidx.appsearch.compiler.IntrospectionHelper.PropertyClass;
import androidx.appsearch.compiler.annotationwrapper.DataPropertyAnnotation;
import androidx.appsearch.compiler.annotationwrapper.MetadataPropertyAnnotation;
import androidx.appsearch.compiler.annotationwrapper.PropertyAnnotation;
-import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
-import java.util.EnumMap;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.Set;
import java.util.function.Predicate;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
-import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
-import javax.lang.model.element.VariableElement;
import javax.lang.model.util.Elements;
-import javax.lang.model.util.Types;
/**
* Processes @Document annotations.
@@ -62,52 +52,20 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
class DocumentModel {
-
- /** Enumeration of fields that must be handled specially (i.e. are not properties) */
- enum SpecialField {NAMESPACE, ID, CREATION_TIMESTAMP_MILLIS, TTL_MILLIS, SCORE}
-
- /** Determines how the annotation processor has decided to write the value of a field. */
- enum WriteKind {FIELD, SETTER, CREATION_METHOD}
-
private static final String CLASS_SUFFIX = ".class";
private final IntrospectionHelper mHelper;
- private final TypeElement mClass;
- private final Types mTypeUtil;
+
private final Elements mElementUtil;
+
+ private final TypeElement mClass;
+
// The name of the original class annotated with @Document
private final String mQualifiedDocumentClassName;
- private String mSchemaName;
- private final Set<TypeElement> mParentTypes = new LinkedHashSet<>();
- // All methods in the current @Document annotated class/interface, or in the generated class
- // for AutoValue document.
- // Warning: if you change this to a HashSet, we may choose different getters or setters from
- // run to run, causing the generated code to bounce.
- private final LinkedHashSet<ExecutableElement> mAllMethods;
- // All methods in the builder class, if a builder producer is provided.
- private final LinkedHashSet<ExecutableElement> mAllBuilderMethods;
- // Key: Name of the element whose value is set through the setter method.
- // Value: ExecutableElement of the setter method.
- private final Map<String, ExecutableElement> mSetterMethods = new HashMap<>();
- // Warning: if you change this to a HashMap, we may assign elements in a different order from
- // run to run, causing the generated code to bounce.
- // Keeps tracks of all AppSearch elements so we can find creation and access methods for them
- // all
- private final Map<String, Element> mAllAppSearchElements = new LinkedHashMap<>();
- // Warning: if you change this to a HashMap, we may assign elements in a different order from
- // run to run, causing the generated code to bounce.
- // Keeps track of property elements so we don't allow multiple annotated elements of the same
- // name
- private final Map<String, Element> mPropertyElements = new LinkedHashMap<>();
- private final Map<SpecialField, String> mSpecialFieldNames = new EnumMap<>(SpecialField.class);
- private final Map<Element, WriteKind> mWriteKinds = new HashMap<>();
- // Contains the reason why that element couldn't be written either by field or by setter.
- private final Map<Element, ProcessingException> mWriteWhyCreationMethod =
- new HashMap<>();
- private ExecutableElement mChosenCreationMethod = null;
- private List<String> mChosenCreationMethodParams = null;
- private TypeElement mBuilderClass = null;
- private Set<ExecutableElement> mBuilderProducers = new LinkedHashSet<>();
+
+ private final String mSchemaName;
+
+ private final LinkedHashSet<TypeElement> mParentTypes;
private final LinkedHashSet<AnnotatedGetterOrField> mAnnotatedGettersAndFields;
@@ -133,13 +91,16 @@
}
mHelper = new IntrospectionHelper(env);
- mClass = clazz;
- mTypeUtil = env.getTypeUtils();
mElementUtil = env.getElementUtils();
+ mClass = clazz;
mQualifiedDocumentClassName = generatedAutoValueElement != null
? generatedAutoValueElement.getQualifiedName().toString()
: clazz.getQualifiedName().toString();
- mAnnotatedGettersAndFields = scanAnnotatedGettersAndFields(clazz, env);
+ mParentTypes = getParentSchemaTypes(clazz);
+
+ List<TypeElement> classHierarchy = generateClassHierarchy(clazz);
+ mSchemaName = computeSchemaName(classHierarchy);
+ mAnnotatedGettersAndFields = scanAnnotatedGettersAndFields(classHierarchy, env);
requireNoDuplicateMetadataProperties();
mIdAnnotatedGetterOrField = requireGetterOrFieldMatchingPredicate(
@@ -152,83 +113,17 @@
/* errorMessage= */"All @Document classes must have exactly one field annotated "
+ "with @Namespace");
- mAllMethods = mHelper.getAllMethods(clazz);
- mAccessors = inferPropertyAccessors(mAnnotatedGettersAndFields, mAllMethods, mHelper);
+ LinkedHashSet<ExecutableElement> allMethods = mHelper.getAllMethods(clazz);
+ mAccessors = inferPropertyAccessors(mAnnotatedGettersAndFields, allMethods, mHelper);
mDocumentClassCreationInfo =
DocumentClassCreationInfo.infer(clazz, mAnnotatedGettersAndFields, mHelper);
-
- // Scan methods and constructors. We will need this info when processing fields to
- // make sure the fields can be get and set.
- Set<ExecutableElement> potentialCreationMethods = extractCreationMethods(clazz);
- mAllBuilderMethods = mBuilderClass != null
- ? mHelper.getAllMethods(mBuilderClass) : new LinkedHashSet<>();
- scanFields(mClass);
- chooseCreationMethod(potentialCreationMethods);
- }
-
- /**
- * Scans all the elements in typeElement to find a builder producer. If found, set
- * mBuilderProducers and mBuilderClass to the builder producer candidates and the builder class
- * respectively.
- *
- * @throws ProcessingException if there are more than one elements annotated with
- * {@code @Document.BuilderProducer}, or if the builder producer
- * element is not a visible static
- * method or a class.
- */
- private void extractBuilderProducer(TypeElement typeElement)
- throws ProcessingException {
- for (Element child : typeElement.getEnclosedElements()) {
- boolean isAnnotated = false;
- for (AnnotationMirror annotation : child.getAnnotationMirrors()) {
- if (annotation.getAnnotationType().toString().equals(
- BUILDER_PRODUCER_CLASS.canonicalName())) {
- isAnnotated = true;
- break;
- }
- }
- if (!isAnnotated) {
- continue;
- }
- if (child.getKind() != ElementKind.METHOD && child.getKind() != ElementKind.CLASS) {
- // Since @Document.BuilderProducer is configured with
- // @Target({ElementType.METHOD, ElementType.TYPE}), it's not possible to reach here.
- throw new ProcessingException("Builder producer must be a method or a class",
- child);
- }
- if (mBuilderClass != null) {
- throw new ProcessingException("Found duplicated builder producer", typeElement);
- }
- Set<Modifier> methodModifiers = child.getModifiers();
- if (!methodModifiers.contains(Modifier.STATIC)) {
- throw new ProcessingException("Builder producer must be static", child);
- }
- if (methodModifiers.contains(Modifier.PRIVATE)) {
- throw new ProcessingException("Builder producer cannot be private", child);
- }
- if (child.getKind() == ElementKind.METHOD) {
- ExecutableElement method = (ExecutableElement) child;
- mBuilderProducers.add(method);
- mBuilderClass = (TypeElement) mTypeUtil.asElement(method.getReturnType());
- } else {
- // child is a class, so extract all of its constructors as builder producer
- // candidates. The validity of the constructor will be checked later when we
- // choose the right creation method.
- mBuilderClass = (TypeElement) child;
- for (Element builderProducer : mBuilderClass.getEnclosedElements()) {
- if (builderProducer.getKind() == ElementKind.CONSTRUCTOR) {
- mBuilderProducers.add((ExecutableElement) builderProducer);
- }
- }
- }
- }
}
private static LinkedHashSet<AnnotatedGetterOrField> scanAnnotatedGettersAndFields(
- @NonNull TypeElement clazz,
+ @NonNull List<TypeElement> hierarchy,
@NonNull ProcessingEnvironment env) throws ProcessingException {
AnnotatedGetterAndFieldAccumulator accumulator = new AnnotatedGetterAndFieldAccumulator();
- for (TypeElement type : generateClassHierarchy(clazz)) {
+ for (TypeElement type : hierarchy) {
for (Element enclosedElement : type.getEnclosedElements()) {
AnnotatedGetterOrField getterOrField =
AnnotatedGetterOrField.tryCreateFor(enclosedElement, env);
@@ -285,29 +180,6 @@
.orElseThrow(() -> new ProcessingException(errorMessage, mClass));
}
- private Set<ExecutableElement> extractCreationMethods(TypeElement typeElement)
- throws ProcessingException {
- extractBuilderProducer(typeElement);
- // If a builder producer is provided, then only the builder can be used as a creation
- // method.
- if (mBuilderClass != null) {
- return Collections.unmodifiableSet(mBuilderProducers);
- }
-
- Set<ExecutableElement> creationMethods = new LinkedHashSet<>();
- for (Element child : typeElement.getEnclosedElements()) {
- if (child.getKind() == ElementKind.CONSTRUCTOR) {
- creationMethods.add((ExecutableElement) child);
- } else if (child.getKind() == ElementKind.METHOD) {
- ExecutableElement method = (ExecutableElement) child;
- if (isFactoryMethod(method)) {
- creationMethods.add(method);
- }
- }
- }
- return Collections.unmodifiableSet(creationMethods);
- }
-
/**
* Tries to create an {@link DocumentModel} from the given {@link Element}.
*
@@ -360,11 +232,6 @@
return mParentTypes;
}
- @NonNull
- public Map<String, Element> getAllElements() {
- return Collections.unmodifiableMap(mAllAppSearchElements);
- }
-
/**
* Returns all getters/fields (declared or inherited) annotated with some
* {@link PropertyAnnotation}.
@@ -409,93 +276,6 @@
}
/**
- * @deprecated Use {@link #getAnnotatedGettersAndFields()} instead.
- */
- @Deprecated
- @NonNull
- public Map<String, Element> getPropertyElements() {
- return Collections.unmodifiableMap(mPropertyElements);
- }
-
- @Nullable
- public String getSpecialFieldName(SpecialField field) {
- return mSpecialFieldNames.get(field);
- }
-
- @Nullable
- public WriteKind getElementWriteKind(String elementName) {
- Element element = mAllAppSearchElements.get(elementName);
- return mWriteKinds.get(element);
- }
-
- @Nullable
- public ExecutableElement getSetterForElement(String elementName) {
- return mSetterMethods.get(elementName);
- }
-
- /**
- * Finds the AppSearch name for the given property.
- *
- * <p>This is usually the name of the field in Java, but may be changed if the developer
- * specifies a different 'name' parameter in the annotation.
- *
- * @deprecated Use {@link #getAnnotatedGettersAndFields()} and
- * {@link DataPropertyAnnotation#getName()} ()} instead.
- */
- @Deprecated
- @NonNull
- public String getPropertyName(@NonNull Element property) throws ProcessingException {
- AnnotationMirror annotation = getPropertyAnnotation(property);
- Map<String, Object> params = mHelper.getAnnotationParams(annotation);
- String propertyName = params.get("name").toString();
- if (propertyName.isEmpty()) {
- propertyName = getNormalizedElementName(property);
- }
- return propertyName;
- }
-
- /**
- * Returns the first found AppSearch property annotation element from the input element's
- * annotations.
- *
- * @throws ProcessingException if no AppSearch property annotation is found.
- * @deprecated Use {@link #getAnnotatedGettersAndFields()} and
- * {@link AnnotatedGetterOrField#getAnnotation()} instead.
- */
- @Deprecated
- @NonNull
- public AnnotationMirror getPropertyAnnotation(@NonNull Element element)
- throws ProcessingException {
- Objects.requireNonNull(element);
- Set<String> propertyClassPaths = new HashSet<>();
- for (PropertyClass propertyClass : PropertyClass.values()) {
- propertyClassPaths.add(propertyClass.getClassFullPath());
- }
- for (AnnotationMirror annotation : element.getAnnotationMirrors()) {
- String annotationFq = annotation.getAnnotationType().toString();
- if (propertyClassPaths.contains(annotationFq)) {
- return annotation;
- }
- }
- throw new ProcessingException("Missing AppSearch property annotation.", element);
- }
-
- @NonNull
- public ExecutableElement getChosenCreationMethod() {
- return mChosenCreationMethod;
- }
-
- @NonNull
- public List<String> getChosenCreationMethodParams() {
- return Collections.unmodifiableList(mChosenCreationMethodParams);
- }
-
- @Nullable
- public TypeElement getBuilderClass() {
- return mBuilderClass;
- }
-
- /**
* Infers the {@link PropertyAccessor} for each of the {@link AnnotatedGetterOrField}.
*
* <p>Each accessor may be the {@link AnnotatedGetterOrField} itself or some other non-private
@@ -515,493 +295,30 @@
return accessors;
}
- private boolean isFactoryMethod(ExecutableElement method) {
- Set<Modifier> methodModifiers = method.getModifiers();
- return methodModifiers.contains(Modifier.STATIC)
- && !methodModifiers.contains(Modifier.PRIVATE)
- && mTypeUtil.isSameType(method.getReturnType(), mClass.asType());
- }
-
/**
- * Scan the annotations of a field to determine the fields type and handle it accordingly
- *
- * @param childElement the member of class elements currently being scanned
- * @deprecated Rely on {@link #mAnnotatedGettersAndFields} instead of
- * {@link #mAllAppSearchElements} and {@link #mSpecialFieldNames}.
+ * Returns the parent types mentioned within the {@code @Document} annotation.
*/
- @Deprecated
- private void scanAnnotatedField(@NonNull Element childElement) throws ProcessingException {
- String fieldName = childElement.getSimpleName().toString();
-
- // a property field shouldn't be able to override a special field
- if (mSpecialFieldNames.containsValue(fieldName)) {
+ @NonNull
+ private LinkedHashSet<TypeElement> getParentSchemaTypes(
+ @NonNull TypeElement documentClass) throws ProcessingException {
+ AnnotationMirror documentAnnotation = requireNonNull(getDocumentAnnotation(documentClass));
+ Map<String, Object> params = mHelper.getAnnotationParams(documentAnnotation);
+ LinkedHashSet<TypeElement> parentsSchemaTypes = new LinkedHashSet<>();
+ Object parentsParam = params.get("parent");
+ if (parentsParam instanceof List) {
+ for (Object parent : (List<?>) parentsParam) {
+ String parentClassName = parent.toString();
+ parentClassName = parentClassName.substring(0,
+ parentClassName.length() - CLASS_SUFFIX.length());
+ parentsSchemaTypes.add(mElementUtil.getTypeElement(parentClassName));
+ }
+ }
+ if (!parentsSchemaTypes.isEmpty() && params.get("name").toString().isEmpty()) {
throw new ProcessingException(
- "Non-annotated field overriding special annotated fields named: "
- + fieldName, mAllAppSearchElements.get(fieldName));
- }
-
- // no annotation mirrors -> non-indexable field
- for (AnnotationMirror annotation : childElement.getAnnotationMirrors()) {
- String annotationFq = annotation.getAnnotationType().toString();
- if (!annotationFq.startsWith(DOCUMENT_ANNOTATION_CLASS.canonicalName())
- || annotationFq.equals(BUILDER_PRODUCER_CLASS.canonicalName())) {
- continue;
- }
- if (childElement.getKind() == ElementKind.CLASS) {
- continue;
- }
-
- if (annotationFq.equals(MetadataPropertyAnnotation.ID.getClassName().canonicalName())) {
- if (mSpecialFieldNames.containsKey(SpecialField.ID)) {
- throw new ProcessingException(
- "Class hierarchy contains multiple fields annotated @Id",
- childElement);
- }
- mSpecialFieldNames.put(SpecialField.ID, fieldName);
- } else if (annotationFq.equals(
- MetadataPropertyAnnotation.NAMESPACE.getClassName().canonicalName())) {
- if (mSpecialFieldNames.containsKey(SpecialField.NAMESPACE)) {
- throw new ProcessingException(
- "Class hierarchy contains multiple fields annotated @Namespace",
- childElement);
- }
- mSpecialFieldNames.put(SpecialField.NAMESPACE, fieldName);
- } else if (annotationFq.equals(
- MetadataPropertyAnnotation.CREATION_TIMESTAMP_MILLIS
- .getClassName()
- .canonicalName())) {
- if (mSpecialFieldNames.containsKey(SpecialField.CREATION_TIMESTAMP_MILLIS)) {
- throw new ProcessingException("Class hierarchy contains multiple fields "
- + "annotated @CreationTimestampMillis", childElement);
- }
- mSpecialFieldNames.put(
- SpecialField.CREATION_TIMESTAMP_MILLIS, fieldName);
- } else if (annotationFq.equals(
- MetadataPropertyAnnotation.TTL_MILLIS.getClassName().canonicalName())) {
- if (mSpecialFieldNames.containsKey(SpecialField.TTL_MILLIS)) {
- throw new ProcessingException(
- "Class hierarchy contains multiple fields annotated @TtlMillis",
- childElement);
- }
- mSpecialFieldNames.put(SpecialField.TTL_MILLIS, fieldName);
- } else if (annotationFq.equals(
- MetadataPropertyAnnotation.SCORE.getClassName().canonicalName())) {
- if (mSpecialFieldNames.containsKey(SpecialField.SCORE)) {
- throw new ProcessingException(
- "Class hierarchy contains multiple fields annotated @Score",
- childElement);
- }
- mSpecialFieldNames.put(SpecialField.SCORE, fieldName);
- } else {
- PropertyClass propertyClass = getPropertyClass(annotationFq);
- if (propertyClass != null) {
- // A property must either:
- // 1. be unique
- // 2. override a property from the Java parent while maintaining the same
- // AppSearch property name
- checkFieldTypeForPropertyAnnotation(childElement, propertyClass);
- // It's assumed that parent types, in the context of Java's type system,
- // are always visited before child types, so existingProperty must come
- // from the parent type. To make this assumption valid, the result
- // returned by generateClassHierarchy must put parent types before child
- // types.
- Element existingProperty = mPropertyElements.get(fieldName);
- if (existingProperty != null) {
- if (!mTypeUtil.isSameType(
- existingProperty.asType(), childElement.asType())) {
- throw new ProcessingException(
- "Cannot override a property with a different type",
- childElement);
- }
- if (!getPropertyName(existingProperty).equals(getPropertyName(
- childElement))) {
- throw new ProcessingException(
- "Cannot override a property with a different name",
- childElement);
- }
- }
- mPropertyElements.put(fieldName, childElement);
- }
- }
-
- mAllAppSearchElements.put(fieldName, childElement);
- }
- }
-
- /**
- * Scans all the fields of the class, as well as superclasses annotated with @Document,
- * to get AppSearch fields such as id
- *
- * @param element the class to scan
- */
- private void scanFields(@NonNull TypeElement element) throws ProcessingException {
- AnnotationMirror documentAnnotation = getDocumentAnnotation(element);
- if (documentAnnotation != null) {
- Map<String, Object> params = mHelper.getAnnotationParams(documentAnnotation);
- Object parents = params.get("parent");
- if (parents instanceof List) {
- for (Object parent : (List<?>) parents) {
- String parentClassName = parent.toString();
- parentClassName = parentClassName.substring(0,
- parentClassName.length() - CLASS_SUFFIX.length());
- mParentTypes.add(mElementUtil.getTypeElement(parentClassName));
- }
- }
- if (!mParentTypes.isEmpty() && params.get("name").toString().isEmpty()) {
- throw new ProcessingException(
- "All @Document classes with a parent must explicitly provide a name",
- mClass);
- }
- }
-
- List<TypeElement> hierarchy = generateClassHierarchy(element);
-
- for (TypeElement clazz : hierarchy) {
- List<? extends Element> enclosedElements = clazz.getEnclosedElements();
- for (Element childElement : enclosedElements) {
- scanAnnotatedField(childElement);
- }
- }
-
- // Every document must always have a namespace
- if (!mSpecialFieldNames.containsKey(SpecialField.NAMESPACE)) {
- throw new ProcessingException(
- "All @Document classes must have exactly one field annotated with @Namespace",
+ "All @Document classes with a parent must explicitly provide a name",
mClass);
}
-
- // Every document must always have an ID
- if (!mSpecialFieldNames.containsKey(SpecialField.ID)) {
- throw new ProcessingException(
- "All @Document classes must have exactly one field annotated with @Id",
- mClass);
- }
-
- mSchemaName = computeSchemaName(hierarchy);
-
- for (Element appSearchField : mAllAppSearchElements.values()) {
- chooseWriteKind(appSearchField);
- }
- }
-
- /**
- * Checks whether property's data type matches the {@code androidx.appsearch.annotation
- * .Document} property annotation's requirement.
- *
- * @throws ProcessingException if data type doesn't match property annotation's requirement.
- */
- void checkFieldTypeForPropertyAnnotation(@NonNull Element property,
- PropertyClass propertyClass) throws ProcessingException {
- switch (propertyClass) {
- case BOOLEAN_PROPERTY_CLASS:
- if (mHelper.isFieldOfExactType(property, mHelper.mBooleanBoxType,
- mHelper.mBooleanPrimitiveType)) {
- return;
- }
- break;
- case BYTES_PROPERTY_CLASS:
- if (mHelper.isFieldOfExactType(property, mHelper.mByteBoxType,
- mHelper.mBytePrimitiveType, mHelper.mByteBoxArrayType,
- mHelper.mBytePrimitiveArrayType)) {
- return;
- }
- break;
- case DOCUMENT_PROPERTY_CLASS:
- if (mHelper.isFieldOfDocumentType(property)) {
- return;
- }
- break;
- case DOUBLE_PROPERTY_CLASS:
- if (mHelper.isFieldOfExactType(property, mHelper.mDoubleBoxType,
- mHelper.mDoublePrimitiveType, mHelper.mFloatBoxType,
- mHelper.mFloatPrimitiveType)) {
- return;
- }
- break;
- case LONG_PROPERTY_CLASS:
- if (mHelper.isFieldOfExactType(property, mHelper.mIntegerBoxType,
- mHelper.mIntPrimitiveType, mHelper.mLongBoxType,
- mHelper.mLongPrimitiveType)) {
- return;
- }
- break;
- case STRING_PROPERTY_CLASS:
- if (mHelper.isFieldOfExactType(property, mHelper.mStringType)) {
- return;
- }
- break;
- default:
- // do nothing
- }
- throw new ProcessingException(
- "Property Annotation " + propertyClass.getClassFullPath() + " doesn't accept the "
- + "data type of property field " + property.getSimpleName(), property);
- }
-
- /**
- * Returns the {@link PropertyClass} with {@code annotationFq} as full class path, and {@code
- * null} if failed to find such a {@link PropertyClass}.
- */
- @Nullable
- private PropertyClass getPropertyClass(@Nullable String annotationFq) {
- for (PropertyClass propertyClass : PropertyClass.values()) {
- if (propertyClass.isPropertyClass(annotationFq)) {
- return propertyClass;
- }
- }
- return null;
- }
-
- /**
- * Chooses how to write a given field.
- *
- * <p>The writing strategy can be one of: visible mutable field, or visible setter, or visible
- * creation method accepting at minimum all fields that aren't mutable and have no visible
- * setter.
- */
- private void chooseWriteKind(@NonNull Element field) {
- // TODO(b/300114568): Carve out better distinction b/w the different write strategies
- Set<Modifier> modifiers = field.getModifiers();
- // Choose set access
- if (modifiers.contains(Modifier.PRIVATE) || modifiers.contains(Modifier.FINAL)
- || modifiers.contains(Modifier.STATIC) || field.getKind() == ElementKind.METHOD
- || mBuilderClass != null) {
- // Try to find a setter. If we can't find one, mark the WriteKind as {@code
- // CREATION_METHOD}. We don't know if this is true yet, the creation methods will be
- // inspected in a subsequent pass.
- try {
- findSetter(field);
- mWriteKinds.put(field, WriteKind.SETTER);
- } catch (ProcessingException e) {
- // We'll look for a creation method, so we may still be able to set this field,
- // but it's more likely the developer configured the setter incorrectly. Keep
- // the exception around to include it in the report if no creation method is found.
- mWriteWhyCreationMethod.put(field, e);
- mWriteKinds.put(field, WriteKind.CREATION_METHOD);
- }
- } else {
- mWriteKinds.put(field, WriteKind.FIELD);
- }
- }
-
- private void chooseCreationMethod(Set<ExecutableElement> creationMethods)
- throws ProcessingException {
- // Maps field name to Element.
- // If this is changed to a HashSet, we might report errors to the developer in a different
- // order about why a field was written via creation method.
- Map<String, Element> creationMethodWrittenFields = new LinkedHashMap<>();
- for (Map.Entry<Element, WriteKind> it : mWriteKinds.entrySet()) {
- if (it.getValue() == WriteKind.CREATION_METHOD) {
- String name = it.getKey().getSimpleName().toString();
- creationMethodWrittenFields.put(name, it.getKey());
- }
- }
-
- // Maps normalized field name to real field name.
- Map<String, String> normalizedToRawFieldName = new HashMap<>();
- for (Element field : mAllAppSearchElements.values()) {
- normalizedToRawFieldName.put(getNormalizedElementName(field),
- field.getSimpleName().toString());
- }
-
- Map<ExecutableElement, String> whyNotCreationMethod = new HashMap<>();
- creationMethodSearch:
- for (ExecutableElement method : creationMethods) {
- if (method.getModifiers().contains(Modifier.PRIVATE)) {
- whyNotCreationMethod.put(method, "Creation method is private");
- continue creationMethodSearch;
- }
- // The field name of each field that goes into the creation method, in the order they
- // are declared in the creation method signature.
- List<String> creationMethodParamFields = new ArrayList<>();
- Set<String> remainingFields = new HashSet<>(creationMethodWrittenFields.keySet());
- for (VariableElement parameter : method.getParameters()) {
- String paramName = parameter.getSimpleName().toString();
- String fieldName = normalizedToRawFieldName.get(paramName);
- if (fieldName == null) {
- whyNotCreationMethod.put(
- method,
- "Parameter \"" + paramName + "\" is not an AppSearch parameter; don't "
- + "know how to supply it.");
- continue creationMethodSearch;
- }
- remainingFields.remove(fieldName);
- creationMethodParamFields.add(fieldName);
- }
- if (!remainingFields.isEmpty()) {
- whyNotCreationMethod.put(
- method,
- "This method doesn't have parameters for the following fields: "
- + remainingFields);
- continue creationMethodSearch;
- }
-
- // If the field is set in the constructor, choose creation method for the write kind
- for (String param : creationMethodParamFields) {
- for (Element appSearchField : mAllAppSearchElements.values()) {
- if (appSearchField.getSimpleName().toString().equals(param)) {
- mWriteKinds.put(appSearchField, WriteKind.CREATION_METHOD);
- break;
- }
- }
- }
-
- // Found one!
- mChosenCreationMethod = method;
- mChosenCreationMethodParams = creationMethodParamFields;
- return;
- }
-
- // If we got here, we couldn't find any creation methods.
- ProcessingException e =
- new ProcessingException(
- "Failed to find any suitable creation methods to build class \""
- + mClass.getQualifiedName()
- + "\". See warnings for details.",
- mClass);
-
- // Inform the developer why we started looking for creation methods in the first place.
- for (Element field : creationMethodWrittenFields.values()) {
- ProcessingException warning = mWriteWhyCreationMethod.get(field);
- if (warning != null) {
- e.addWarning(warning);
- }
- }
-
- // Inform the developer about why each creation method we considered was rejected.
- for (Map.Entry<ExecutableElement, String> it : whyNotCreationMethod.entrySet()) {
- ProcessingException warning = new ProcessingException(
- "Cannot use this creation method to construct the class: " + it.getValue(),
- it.getKey());
- e.addWarning(warning);
- }
-
- throw e;
- }
-
- /**
- * Finds setter function for a private field, or for a property defined by a annotated getter
- * method.
- */
- private void findSetter(@NonNull Element element) throws ProcessingException {
- String elementName = element.getSimpleName().toString();
- // We can't report setter failure until we've searched the creation methods, so this
- // message is anticipatory and should be buffered by the caller.
- String error;
- if (mBuilderClass != null) {
- error = "Element cannot be written directly because a builder producer is provided";
- } else if (element.getKind() == ElementKind.METHOD) {
- error = "Element cannot be written directly because it is an annotated getter";
- } else {
- error = "Field cannot be written directly because it is private, final, or static";
- }
- error += ", and we failed to find a suitable setter for \"" + elementName + "\". "
- + "Trying to find a suitable creation method.";
- ProcessingException e = new ProcessingException(error,
- mAllAppSearchElements.get(elementName));
-
- // When using the builder pattern, setters can only come from the builder.
- Set<ExecutableElement> methods;
- if (mBuilderClass != null) {
- methods = mAllBuilderMethods;
- } else {
- methods = mAllMethods;
- }
- for (ExecutableElement method : methods) {
- String methodName = method.getSimpleName().toString();
- String normalizedElementName = getNormalizedElementName(element);
- if (methodName.equals(normalizedElementName)
- || methodName.equals("set"
- + normalizedElementName.substring(0, 1).toUpperCase()
- + normalizedElementName.substring(1))) {
- if (method.getModifiers().contains(Modifier.PRIVATE)) {
- e.addWarning(new ProcessingException(
- "Setter cannot be used: private visibility", method));
- continue;
- }
- if (method.getParameters().size() != 1) {
- e.addWarning(new ProcessingException(
- "Setter cannot be used: takes " + method.getParameters().size()
- + " parameters instead of 1",
- method));
- continue;
- }
- // Found one!
- mSetterMethods.put(elementName, method);
- return;
- }
- }
-
- // Broke out of the loop without finding anything.
- throw e;
- }
-
- /**
- * Produces the canonical name of a field element.
- *
- * @see #getNormalizedElementName(Element)
- */
- private String getNormalizedFieldElementName(Element fieldElement) {
- String fieldName = fieldElement.getSimpleName().toString();
-
- if (fieldName.length() < 2) {
- return fieldName;
- }
-
- // Handle convention of having field names start with m
- // (e.g. String mName; public String getName())
- if (fieldName.charAt(0) == 'm' && Character.isUpperCase(fieldName.charAt(1))) {
- return fieldName.substring(1, 2).toLowerCase() + fieldName.substring(2);
- }
-
- // Handle convention of having field names start with _
- // (e.g. String _name; public String getName())
- if (fieldName.charAt(0) == '_'
- && fieldName.charAt(1) != '_'
- && Character.isLowerCase(fieldName.charAt(1))) {
- return fieldName.substring(1);
- }
-
- // Handle convention of having field names end with _
- // (e.g. String name_; public String getName())
- if (fieldName.charAt(fieldName.length() - 1) == '_'
- && fieldName.charAt(fieldName.length() - 2) != '_') {
- return fieldName.substring(0, fieldName.length() - 1);
- }
-
- return fieldName;
- }
-
- /**
- * Produces the canonical name of a method element.
- *
- * @see #getNormalizedElementName(Element)
- */
- private String getNormalizedMethodElementName(Element methodElement) {
- String methodName = methodElement.getSimpleName().toString();
-
- // If this property is defined by an annotated getter, then we can remove the prefix
- // "get" or "is" if possible.
- if (methodName.startsWith("get") && methodName.length() > 3) {
- methodName = methodName.substring(3, 4).toLowerCase() + methodName.substring(4);
- } else if (mHelper.isFieldOfBooleanType(methodElement) && methodName.startsWith("is")
- && methodName.length() > 2) {
- // "is" is a valid getter prefix for boolean property.
- methodName = methodName.substring(2, 3).toLowerCase() + methodName.substring(3);
- }
- // Return early because the rest normalization procedures do not apply to getters.
- return methodName;
- }
-
- /**
- * Produces the canonical name of a element (which is used as the default property name as
- * well as to find accessors) by removing prefixes and suffixes of common conventions.
- */
- private String getNormalizedElementName(Element property) {
- if (property.getKind() == ElementKind.METHOD) {
- return getNormalizedMethodElementName(property);
- }
- return getNormalizedFieldElementName(property);
+ return parentsSchemaTypes;
}
/**
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 1947dea..8bb9788 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/IntrospectionHelper.java
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/IntrospectionHelper.java
@@ -89,6 +89,9 @@
public static final ClassName BUILDER_PRODUCER_CLASS =
DOCUMENT_ANNOTATION_CLASS.nestedClass("BuilderProducer");
+ static final ClassName DOCUMENT_CLASS_FACTORY_CLASS =
+ ClassName.get(APPSEARCH_PKG, "DocumentClassFactory");
+
public final TypeMirror mStringType;
public final TypeMirror mLongPrimitiveType;
public final TypeMirror mIntPrimitiveType;
@@ -172,7 +175,8 @@
}
/** Checks whether the property data type is one of the valid types. */
- public boolean isFieldOfExactType(Element property, TypeMirror... validTypes) {
+ public boolean isFieldOfExactType(
+ @NonNull Element property, @NonNull TypeMirror... validTypes) {
TypeMirror propertyType = getPropertyType(property);
for (TypeMirror validType : validTypes) {
if (propertyType.getKind() == TypeKind.ARRAY) {
@@ -193,31 +197,14 @@
}
/** Checks whether the property data type is of boolean type. */
- public boolean isFieldOfBooleanType(Element property) {
+ public boolean isFieldOfBooleanType(@NonNull Element property) {
return isFieldOfExactType(property, mBooleanBoxType, mBooleanPrimitiveType);
}
/**
- * Checks whether the property data class has {@code androidx.appsearch.annotation.Document
- * .DocumentProperty} annotation.
+ * Returns the annotation's params as a map. Includes the default values.
*/
- public boolean isFieldOfDocumentType(Element property) {
- TypeMirror propertyType = getPropertyType(property);
-
- AnnotationMirror documentAnnotation = null;
-
- if (propertyType.getKind() == TypeKind.ARRAY) {
- documentAnnotation = getDocumentAnnotation(
- mTypeUtils.asElement(((ArrayType) propertyType).getComponentType()));
- } else if (mTypeUtils.isAssignable(mTypeUtils.erasure(propertyType), mCollectionType)) {
- documentAnnotation = getDocumentAnnotation(mTypeUtils.asElement(
- ((DeclaredType) propertyType).getTypeArguments().get(0)));
- } else {
- documentAnnotation = getDocumentAnnotation(mTypeUtils.asElement(propertyType));
- }
- return documentAnnotation != null;
- }
-
+ @NonNull
public Map<String, Object> getAnnotationParams(@NonNull AnnotationMirror annotation) {
Map<? extends ExecutableElement, ? extends AnnotationValue> values =
mEnv.getElementUtils().getElementValuesWithDefaults(annotation);
@@ -251,10 +238,6 @@
return getDocumentClassFactoryForClass(clazz.packageName(), className);
}
- public ClassName getAppSearchClass(String clazz, String... nested) {
- return ClassName.get(APPSEARCH_PKG, clazz, nested);
- }
-
/**
* Returns all the methods within a class, whether inherited or declared directly.
*
@@ -454,27 +437,4 @@
hierarchy, visited);
}
}
-
- enum PropertyClass {
- BOOLEAN_PROPERTY_CLASS("androidx.appsearch.annotation.Document.BooleanProperty"),
- BYTES_PROPERTY_CLASS("androidx.appsearch.annotation.Document.BytesProperty"),
- DOCUMENT_PROPERTY_CLASS("androidx.appsearch.annotation.Document.DocumentProperty"),
- DOUBLE_PROPERTY_CLASS("androidx.appsearch.annotation.Document.DoubleProperty"),
- LONG_PROPERTY_CLASS("androidx.appsearch.annotation.Document.LongProperty"),
- STRING_PROPERTY_CLASS("androidx.appsearch.annotation.Document.StringProperty");
-
- private final String mClassFullPath;
-
- PropertyClass(String classFullPath) {
- mClassFullPath = classFullPath;
- }
-
- String getClassFullPath() {
- return mClassFullPath;
- }
-
- boolean isPropertyClass(String annotationFq) {
- return mClassFullPath.equals(annotationFq);
- }
- }
}
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 5690a38..22910ce 100644
--- a/appsearch/compiler/src/test/java/androidx/appsearch/compiler/AppSearchCompilerTest.java
+++ b/appsearch/compiler/src/test/java/androidx/appsearch/compiler/AppSearchCompilerTest.java
@@ -96,7 +96,7 @@
}
@Test
- public void testAutoValueInheritance() throws Exception {
+ public void testAutoValueInheritance() {
Compilation docExtendsAutoValueDoc = compile(
"import com.google.auto.value.AutoValue;\n"
+ "import com.google.auto.value.AutoValue.*;\n"
@@ -144,7 +144,7 @@
}
@Test
- public void testSuperClassErrors() throws Exception {
+ public void testSuperClassErrors() {
Compilation specialFieldReassigned = compile(
"@Document\n"
+ "public class Gift {\n"
@@ -169,27 +169,6 @@
"Property type must stay consistent when overriding annotated "
+ "members but changed from @Id -> @StringProperty");
- Compilation nonAnnotatedFieldHasSameName = compile(
- "@Document\n"
- + "public class Gift {\n"
- + " @Document.Namespace String namespace;\n"
- + " @Document.Id String id;\n"
- + " Gift(String id, String namespace) {\n"
- + " this.id = id;\n"
- + " this.namespace = namespace;\n"
- + " }\n"
- + "}\n"
- + "@Document\n"
- + "class CoolGift extends Gift {\n"
- + " String id;\n"
- + " CoolGift(String id, String namespace) {\n"
- + " super(id, namespace);\n"
- + " }\n"
- + " public String getId() { return id; }\n"
- + "}\n");
- assertThat(nonAnnotatedFieldHasSameName).hadErrorContaining(
- "Non-annotated field overriding special annotated fields named: id");
-
//error on collision
Compilation idCollision = compile(
"@Document\n"
@@ -1209,7 +1188,7 @@
}
@Test
- public void testInvalidLongPropertyIndexingType() throws Exception {
+ public void testInvalidLongPropertyIndexingType() {
// AppSearchSchema requires Android and is not available in this desktop test, so we cheat
// by using the integer constants directly.
Compilation compilation = compile(
@@ -1241,7 +1220,7 @@
}
@Test
- public void testRepeatedPropertyJoinableType_throwsError() throws Exception {
+ public void testRepeatedPropertyJoinableType_throwsError() {
Compilation compilation = compile(
"import java.util.*;\n"
+ "@Document\n"
@@ -1711,7 +1690,7 @@
}
@Test
- public void testPolymorphismOverrideExtendedPropertyInvalid() throws Exception {
+ public void testPolymorphismOverrideExtendedPropertyInvalid() {
// Overridden properties cannot change the names.
Compilation compilation = compile(
"@Document\n"
@@ -1828,7 +1807,7 @@
}
@Test
- public void testPolymorphismChildTypeWithoutName() throws Exception {
+ public void testPolymorphismChildTypeWithoutName() {
Compilation compilation = compile(
"@Document\n"
+ "class Parent {\n"
@@ -1922,7 +1901,7 @@
}
@Test
- public void testAnnotationOnGetterWithoutFactory() throws Exception {
+ public void testAnnotationOnGetterWithoutFactory() {
// An interface without any factory method is not able to initialize, as interfaces do
// not have constructors.
Compilation compilation = compile(
@@ -2098,7 +2077,7 @@
}
@Test
- public void testSameNameGetterAndFieldAnnotatingBothButGetterIsPrivate() throws Exception {
+ public void testSameNameGetterAndFieldAnnotatingBothButGetterIsPrivate() {
Compilation compilation = compile(
"@Document\n"
+ "public class Gift {\n"
@@ -2168,7 +2147,7 @@
}
@Test
- public void testGetterWithParameterCannotBeUsed() throws Exception {
+ public void testGetterWithParameterCannotBeUsed() {
Compilation compilation = compile(
"@Document\n"
+ "public class Gift {\n"
@@ -2185,7 +2164,7 @@
}
@Test
- public void testPrivateGetterCannotBeUsed() throws Exception {
+ public void testPrivateGetterCannotBeUsed() {
Compilation compilation = compile(
"@Document\n"
+ "public class Gift {\n"
@@ -2224,7 +2203,7 @@
}
@Test
- public void testGetterWithWrongReturnType() throws Exception {
+ public void testGetterWithWrongReturnType() {
Compilation compilation = compile(
"@Document\n"
+ "public class Gift {\n"
@@ -2571,7 +2550,7 @@
}
@Test
- public void testCreationByBuilderErrors() throws Exception {
+ public void testCreationByBuilderErrors() {
// Cannot have multiple builder producer
Compilation compilation = compile(
"@Document\n"
diff --git a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/InitializationTest.kt b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/InitializationTest.kt
index 5e28362..67a3432 100644
--- a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/InitializationTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/InitializationTest.kt
@@ -44,7 +44,7 @@
ProcessCameraProvider.getInstance(ApplicationProvider.getApplicationContext()).await()
assertThat(cameraProvider).isNotNull()
// Ensure retrieved provider is shut down
- cameraProvider.shutdown().await()
+ cameraProvider.shutdownAsync().await()
return@runBlocking
}
}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/media/OutputDistributor.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/media/OutputDistributor.kt
new file mode 100644
index 0000000..279754d
--- /dev/null
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/media/OutputDistributor.kt
@@ -0,0 +1,333 @@
+/*
+ * Copyright 2023 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.camera2.pipe.media
+
+import android.os.Build
+import androidx.annotation.GuardedBy
+import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.CameraTimestamp
+import androidx.camera.camera2.pipe.FrameNumber
+import kotlinx.atomicfu.atomic
+
+/**
+ * The goal of this class is simple: Associate a start event with its associated output.
+ *
+ * In addition this class must:
+ * 1. Track and cancel events due to skipped [onOutputStarted] events.
+ * 2. Track and finalize resources due to skipped [onOutputAvailable] events.
+ * 3. Track and cancel events that match [onOutputFailure] events.
+ * 4. Track and handle out-of-order [onOutputStarted] events.
+ * 5. Finalize all resources and cancel all events during [close]
+ *
+ * This class makes several assumptions:
+ * 1. [onOutputStarted] events *usually* arrive in order, relative to each other.
+ * 2. [onOutputAvailable] events *usually* arrive in order, relative to each other.
+ * 3. [onOutputStarted] events *usually* happen before a corresponding [onOutputAvailable] event
+ * 4. [onOutputStarted] events may have a large number of events (1-50) before [onOutputAvailable]
+ * events start coming in.
+ * 5. [onOutputStarted] and [onOutputAvailable] are 1:1 under normal circumstances.
+ *
+ * @param maximumCachedOutputs indicates how many available outputs this distributor will accept
+ * without matching [onOutputStarted] event before closing them with the [outputFinalizer].
+ * @param outputFinalizer is responsible for closing outputs, if required.
+ */
+@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
+internal class OutputDistributor<T>(
+ private val maximumCachedOutputs: Int = 3,
+ private val outputFinalizer: Finalizer<T>
+) : AutoCloseable {
+
+ internal interface OutputCompleteListener<T> {
+ /**
+ * Invoked when an output is in a completed state, and will *always* be invoked exactly
+ * once per [OutputDistributor.onOutputStarted] event.
+ *
+ * On failures (The output being unavailable, the [OutputDistributor] being closed before
+ * an output has arrived, or an explicit output failure event), this method will still be
+ * invoked with a null [output].
+ */
+ fun onOutputComplete(
+ cameraFrameNumber: FrameNumber,
+ cameraTimestamp: CameraTimestamp,
+ outputSequence: Long,
+ outputNumber: Long,
+ output: T?
+ )
+ }
+
+ private val lock = Any()
+
+ @GuardedBy("lock") private var closed = false
+ @GuardedBy("lock") private var outputSequenceNumbers = 1L
+ @GuardedBy("lock") private var newestOutputNumber = Long.MIN_VALUE
+ @GuardedBy("lock") private var newestFrameNumber = FrameNumber(Long.MIN_VALUE)
+ @GuardedBy("lock") private var lastFailedFrameNumber = Long.MIN_VALUE
+ @GuardedBy("lock") private var lastFailedOutputNumber = Long.MIN_VALUE
+
+ private val startedOutputs = mutableListOf<StartedOutput<T>>()
+ private val availableOutputs = mutableMapOf<Long, T?>()
+
+ /**
+ * Indicates a camera2 output has started at a particular frameNumber and timestamp as well as
+ * supplying the callback to listen for the output to become available. The
+ * [outputCompleteListener] can be invoked synchronously if the output is already available.
+ *
+ * @param cameraFrameNumber The Camera2 FrameNumber for this output
+ * @param cameraTimestamp The Camera2 CameraTimestamp for this output
+ * @param outputNumber untyped number that corresponds to the number provided by
+ * [onOutputAvailable]. For Images, this will likely be the timestamp of the image (Which may
+ * be the same as the CameraTimestamp, but may also be different if the timebase of the
+ * the images is different), or the value of the frameNumber if this OutputDistributor is
+ * handling metadata.
+ * @param outputCompleteListener will be invoked whenever the output is fully resolved,
+ * either because the output has been successfully matched, or because the output has failed,
+ * or because this OutputDistributor is now closed.
+ */
+ fun onOutputStarted(
+ cameraFrameNumber: FrameNumber,
+ cameraTimestamp: CameraTimestamp,
+ outputNumber: Long,
+ outputCompleteListener: OutputCompleteListener<T>
+ ) {
+ var outputsToCancel: List<StartedOutput<T>>? = null
+ var outputToComplete: T? = null
+ var invokeOutputCompleteListener = false
+ var outputToFinalize: T? = null
+
+ val outputSequence: Long
+ synchronized(lock) {
+ outputSequence = outputSequenceNumbers++
+ if (closed ||
+ lastFailedFrameNumber == cameraFrameNumber.value ||
+ lastFailedOutputNumber == outputNumber
+ ) {
+ outputToFinalize = availableOutputs.remove(outputNumber)
+ invokeOutputCompleteListener = true
+ return@synchronized
+ }
+
+ // Determine if the frameNumber is out of order relative to other onOutputStarted calls
+ val isFrameNumberOutOfOrder = cameraFrameNumber.value < newestFrameNumber.value
+ if (!isFrameNumberOutOfOrder) {
+ newestFrameNumber = cameraFrameNumber
+ }
+
+ // Determine if the outputNumber is out of order relative to other onOutputStarted calls
+ val isOutputNumberOutOfOrder = outputNumber < newestOutputNumber
+ if (!isOutputNumberOutOfOrder) {
+ newestOutputNumber = outputNumber
+ }
+ val isOutOfOrder = isFrameNumberOutOfOrder || isOutputNumberOutOfOrder
+
+ // onOutputStarted should only be invoked once. Check to see that there are no other
+ // duplicate events.
+ check(
+ !startedOutputs.any {
+ it.cameraFrameNumber == cameraFrameNumber ||
+ it.cameraTimestamp == cameraTimestamp ||
+ it.outputNumber == outputNumber
+ }
+ ) {
+ "onOutputStarted was invoked multiple times with a previously started output!" +
+ "onOutputStarted with $cameraFrameNumber, $cameraTimestamp, $outputNumber. " +
+ "Previously started outputs: $startedOutputs"
+ }
+
+ // Check for matching outputs
+ if (availableOutputs.containsKey(outputNumber)) {
+ // If we found a matching output, get and remove it from the list of
+ // availableOutputs.
+ outputToComplete = availableOutputs.remove(outputNumber)
+ invokeOutputCompleteListener = true
+ outputsToCancel = removeOutputsOlderThan(
+ isOutOfOrder,
+ outputSequence,
+ outputNumber
+ )
+ return@synchronized
+ }
+
+ // If there are no available outputs that match the outputNumber, add it to the list
+ // of startedOutputs.
+ startedOutputs.add(
+ StartedOutput(
+ isOutOfOrder,
+ cameraFrameNumber,
+ cameraTimestamp,
+ outputSequence,
+ outputNumber,
+ outputCompleteListener
+ )
+ )
+ }
+
+ // Invoke finalizers and listeners outside of the synchronized block to avoid holding locks.
+ outputsToCancel?.forEach { it.completeWith(null) }
+ outputToFinalize?.let { outputFinalizer.finalize(it) }
+ if (invokeOutputCompleteListener) {
+ outputCompleteListener.onOutputComplete(
+ cameraFrameNumber = cameraFrameNumber,
+ cameraTimestamp = cameraTimestamp,
+ outputSequence = outputSequence,
+ outputNumber = outputNumber,
+ outputToComplete
+ )
+ }
+ }
+
+ /**
+ * Indicates a camera2 output has arrived for a specific [outputNumber]. outputNumber will
+ * often refer to a FrameNumber for TotalCaptureResult distribution, and will often refer to a
+ * nanosecond timestamp for ImageReader Image distribution.
+ */
+ fun onOutputAvailable(outputNumber: Long, output: T?) {
+ var outputToFinalize: T? = null
+ var outputsToCancel: List<StartedOutput<T>>? = null
+
+ synchronized(lock) {
+ if (closed || lastFailedOutputNumber == outputNumber) {
+ outputToFinalize = output
+ return@synchronized
+ }
+
+ val matchingOutput = startedOutputs.firstOrNull { it.outputNumber == outputNumber }
+
+ // Complete the matching output, if possible, and remove it from the list of started
+ // outputs.
+ if (matchingOutput != null) {
+ outputsToCancel = removeOutputsOlderThan(matchingOutput)
+
+ matchingOutput.completeWith(output)
+ startedOutputs.remove(matchingOutput)
+ return@synchronized
+ }
+
+ // If there is no started output, put this output into the queue of pending outputs.
+ availableOutputs[outputNumber] = output
+
+ // If there are too many pending outputs, remove the oldest one.
+ if (availableOutputs.size > maximumCachedOutputs) {
+ val oldestOutput = availableOutputs.keys.first()
+ outputToFinalize = availableOutputs.remove(oldestOutput)
+ return@synchronized
+ }
+ }
+
+ // Invoke finalizers and listeners outside of the synchronized block to avoid holding locks.
+ outputToFinalize?.let { outputFinalizer.finalize(it) }
+ outputsToCancel?.forEach { it.completeWith(null) }
+ }
+
+ /**
+ * Indicates an output will not arrive for a specific [FrameNumber].
+ */
+ fun onOutputFailure(frameNumber: FrameNumber) {
+ var outputToCancel: StartedOutput<T>? = null
+
+ synchronized(lock) {
+ if (closed) {
+ return
+ }
+ lastFailedFrameNumber = frameNumber.value
+ startedOutputs
+ .singleOrNull { it.cameraFrameNumber == frameNumber }
+ ?.let {
+ lastFailedOutputNumber = it.outputNumber
+ startedOutputs.remove(it)
+ outputToCancel = it
+ }
+ }
+
+ // Invoke listeners outside of the synchronized block to avoid holding locks.
+ outputToCancel?.completeWith(null)
+ }
+
+ @GuardedBy("lock")
+ private fun removeOutputsOlderThan(output: StartedOutput<T>): List<StartedOutput<T>> =
+ removeOutputsOlderThan(output.isOutOfOrder, output.outputSequence, output.outputNumber)
+
+ private fun removeOutputsOlderThan(
+ isOutOfOrder: Boolean,
+ outputSequence: Long,
+ outputNumber: Long
+ ): List<StartedOutput<T>> {
+ // This filter is bi-modal: If [output] is outOfOrder, it will only remove *other* out of
+ // order events that are older than the most recent event. Similarly, if it's normal and in
+ // order, then this will ignore other outOfOrder events.
+ val outputsToCancel =
+ startedOutputs.filter {
+ it.isOutOfOrder == isOutOfOrder &&
+ it.outputSequence < outputSequence &&
+ it.outputNumber < outputNumber
+ }
+ startedOutputs.removeAll(outputsToCancel)
+ return outputsToCancel
+ }
+
+ override fun close() {
+ var outputsToFinalize: List<T?>
+ var outputsToCancel: List<StartedOutput<T>>
+
+ synchronized(lock) {
+ if (closed) {
+ return
+ }
+ closed = true
+
+ outputsToFinalize = availableOutputs.values.toMutableList()
+ availableOutputs.clear()
+ outputsToCancel = startedOutputs.toMutableList()
+ startedOutputs.clear()
+ }
+
+ for (pendingOutput in outputsToFinalize) {
+ outputFinalizer.finalize(pendingOutput)
+ }
+ for (startedOutput in outputsToCancel) {
+ startedOutput.completeWith(null)
+ }
+ }
+
+ /**
+ * Utility class that holds the parameters of an [onOutputStarted] event until the output
+ * arrives.
+ */
+ private data class StartedOutput<T>(
+ val isOutOfOrder: Boolean,
+ val cameraFrameNumber: FrameNumber,
+ val cameraTimestamp: CameraTimestamp,
+ val outputSequence: Long,
+ val outputNumber: Long,
+ private val outputCompleteListener: OutputCompleteListener<T>
+ ) {
+ private val complete = atomic(false)
+
+ fun completeWith(output: T?) {
+ check(complete.compareAndSet(expect = false, update = true)) {
+ "Output $outputSequence at $cameraFrameNumber for $outputNumber was completed " +
+ "multiple times!"
+ }
+ outputCompleteListener.onOutputComplete(
+ cameraFrameNumber,
+ cameraTimestamp,
+ outputSequence,
+ outputNumber,
+ output
+ )
+ }
+ }
+}
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/media/OutputDistributorTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/media/OutputDistributorTest.kt
new file mode 100644
index 0000000..25f3235
--- /dev/null
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/media/OutputDistributorTest.kt
@@ -0,0 +1,462 @@
+/*
+ * Copyright 2023 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.camera2.pipe.media
+
+import android.os.Build
+import androidx.camera.camera2.pipe.CameraTimestamp
+import androidx.camera.camera2.pipe.FrameNumber
+import androidx.camera.camera2.pipe.media.OutputDistributor.OutputCompleteListener
+import com.google.common.truth.Truth.assertThat
+import kotlinx.atomicfu.atomic
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+
+/** Tests for [OutputDistributor] */
+@RunWith(RobolectricTestRunner::class)
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+class OutputDistributorTest {
+ private val fakeOutput1 = FakeOutput(101)
+ private val fakeOutput2 = FakeOutput(102)
+ private val fakeOutput3 = FakeOutput(103)
+ private val fakeOutput4 = FakeOutput(104)
+ private val fakeOutput5 = FakeOutput(105)
+ private val fakeOutput6 = FakeOutput(106)
+
+ private val pendingOutput1 =
+ PendingOutput(FrameNumber(1), CameraTimestamp(11), outputNumber = 101)
+ private val pendingOutput2 =
+ PendingOutput(FrameNumber(2), CameraTimestamp(12), outputNumber = 102)
+ private val pendingOutput3 =
+ PendingOutput(FrameNumber(3), CameraTimestamp(13), outputNumber = 103)
+ private val pendingOutput4 =
+ PendingOutput(FrameNumber(4), CameraTimestamp(14), outputNumber = 104)
+
+ private val outputDistributor =
+ OutputDistributor(
+ maximumCachedOutputs = 3,
+ outputFinalizer =
+ object : Finalizer<FakeOutput> {
+ override fun finalize(value: FakeOutput?) {
+ value?.finalize()
+ }
+ }
+ )
+
+ @Test
+ fun onOutputAvailableDoesNotFinalizeOutputs() {
+ outputDistributor.onOutputAvailable(fakeOutput1.outputNumber, fakeOutput1)
+
+ // When an output becomes available, ensure it is not immediately finalized.
+ assertThat(fakeOutput1.finalized).isFalse()
+ }
+
+ @Test
+ fun onOutputAvailableEvictsAndFinalizesPreviousOutputs() {
+ outputDistributor.onOutputAvailable(fakeOutput1.outputNumber, fakeOutput1)
+ outputDistributor.onOutputAvailable(fakeOutput2.outputNumber, fakeOutput2)
+ outputDistributor.onOutputAvailable(fakeOutput3.outputNumber, fakeOutput3)
+ outputDistributor.onOutputAvailable(fakeOutput4.outputNumber, fakeOutput4)
+
+ // outputDistributor will only cache up to three outputs without matching start events.
+
+ // Ensure the oldest output is finalized:
+ assertThat(fakeOutput1.finalized).isTrue()
+
+ // The newest outputs are not finalized:
+ assertThat(fakeOutput2.finalized).isFalse()
+ assertThat(fakeOutput3.finalized).isFalse()
+ assertThat(fakeOutput4.finalized).isFalse()
+ }
+
+ @Test
+ fun onOutputAvailableEvictsAndFinalizesOutputsInSequence() {
+ outputDistributor.onOutputAvailable(fakeOutput2.outputNumber, fakeOutput2)
+ outputDistributor.onOutputAvailable(fakeOutput3.outputNumber, fakeOutput3)
+ outputDistributor.onOutputAvailable(fakeOutput4.outputNumber, fakeOutput4)
+ outputDistributor.onOutputAvailable(fakeOutput1.outputNumber, fakeOutput1) // Out of order
+
+ // FIFO Order for outputs, regardless of the output number.
+ // Note: Outputs are provided as [2, 3, 4, *1*]
+ assertThat(fakeOutput2.finalized).isTrue()
+ assertThat(fakeOutput3.finalized).isFalse()
+ assertThat(fakeOutput4.finalized).isFalse()
+ assertThat(fakeOutput1.finalized).isFalse()
+ }
+
+ @Test
+ fun onOutputAvailableWithNullEvictsAndFinalizesOutputs() {
+ outputDistributor.onOutputAvailable(fakeOutput1.outputNumber, fakeOutput1)
+ outputDistributor.onOutputAvailable(fakeOutput2.outputNumber, fakeOutput2)
+ outputDistributor.onOutputAvailable(fakeOutput3.outputNumber, fakeOutput3)
+
+ outputDistributor.onOutputAvailable(fakeOutput4.outputNumber, null)
+ outputDistributor.onOutputAvailable(fakeOutput5.outputNumber, null)
+ outputDistributor.onOutputAvailable(fakeOutput6.outputNumber, null)
+
+ // Dropped outputs (null) still evict old outputs.
+ assertThat(fakeOutput1.finalized).isTrue()
+ assertThat(fakeOutput2.finalized).isTrue()
+ assertThat(fakeOutput3.finalized).isTrue()
+ }
+
+ @Test
+ fun closingOutputDistributorFinalizesCachedOutputs() {
+ outputDistributor.onOutputAvailable(fakeOutput1.outputNumber, fakeOutput1)
+ outputDistributor.onOutputAvailable(fakeOutput2.outputNumber, fakeOutput2)
+
+ // Outputs that have not been matched with started events must be closed when the
+ // outputDistributor is closed.
+ outputDistributor.close()
+
+ assertThat(fakeOutput1.finalized).isTrue()
+ assertThat(fakeOutput2.finalized).isTrue()
+ }
+
+ @Test
+ fun closingOutputDistributorBeforeOnOutputAvailableFinalizesNewOutputs() {
+ outputDistributor.close()
+
+ // Outputs that occur after close must always be finalized immediately.
+ outputDistributor.onOutputAvailable(fakeOutput1.outputNumber, fakeOutput1)
+ outputDistributor.onOutputAvailable(fakeOutput2.outputNumber, fakeOutput2)
+
+ assertThat(fakeOutput1.finalized).isTrue()
+ assertThat(fakeOutput2.finalized).isTrue()
+ }
+
+ @Test
+ fun pendingResultsAreMatchedWithOutputs() {
+ // When a a start event occurs and an output is also available, ensure the callback
+ // is correctly invoked.
+ outputDistributor.startWith(pendingOutput1)
+ outputDistributor.onOutputAvailable(fakeOutput1.outputNumber, fakeOutput1)
+
+ assertThat(pendingOutput1.isComplete).isTrue()
+ assertThat(pendingOutput1.output).isEqualTo(fakeOutput1)
+ }
+
+ @Test
+ fun onOutputStartedEventsAreQueuedUp() {
+ outputDistributor.startWith(pendingOutput1)
+ outputDistributor.startWith(pendingOutput2)
+
+ assertThat(pendingOutput1.isComplete).isFalse()
+ assertThat(pendingOutput2.isComplete).isFalse()
+ }
+
+ @Test
+ fun pendingResultsAreMatchedWithNullOutputs() {
+ outputDistributor.startWith(pendingOutput1)
+ outputDistributor.onOutputAvailable(fakeOutput1.outputNumber, null)
+
+ assertThat(pendingOutput1.isComplete).isTrue()
+ assertThat(pendingOutput1.output).isNull()
+ }
+
+ @Test
+ fun previousOutputsAreCompletedWhenNewerOutputIsMatched() {
+ outputDistributor.startWith(pendingOutput1)
+ outputDistributor.startWith(pendingOutput2)
+ outputDistributor.startWith(pendingOutput3)
+ outputDistributor.startWith(pendingOutput4)
+
+ outputDistributor.onOutputAvailable(fakeOutput3.outputNumber, fakeOutput3) // Match 3
+
+ assertThat(pendingOutput1.isComplete).isTrue() // #1 is Canceled
+ assertThat(pendingOutput2.isComplete).isTrue() // #2 is Canceled
+ assertThat(pendingOutput3.isComplete).isTrue()
+ assertThat(pendingOutput4.isComplete).isFalse()
+
+ assertThat(pendingOutput1.output).isNull() // #1 is Canceled
+ assertThat(pendingOutput2.output).isNull() // #2 is Canceled
+ assertThat(pendingOutput3.output).isEqualTo(fakeOutput3)
+ assertThat(pendingOutput4.output).isNull() // #4 is still pending
+ }
+
+ @Test
+ fun closingOutputDistributorBeforeOnOutputStartedCompletesOutputs() {
+ outputDistributor.close()
+
+ // Outputs that are started after the outputDistributor is closed have the callback invoked,
+ // but are immediately completed with a null output.
+ outputDistributor.startWith(pendingOutput1)
+ outputDistributor.startWith(pendingOutput2)
+
+ assertThat(pendingOutput1.isComplete).isTrue()
+ assertThat(pendingOutput2.isComplete).isTrue()
+ assertThat(pendingOutput1.output).isNull()
+ assertThat(pendingOutput2.output).isNull()
+ }
+
+ @Test
+ fun closingOutputDistributorAfterOnOutputStartedCompletesOutputs() {
+ // Outputs that are started before the outputDistributor is closed have the callback
+ // invoked, and are completed with the correct values an null output.
+ outputDistributor.startWith(pendingOutput1)
+ outputDistributor.startWith(pendingOutput2)
+
+ outputDistributor.close()
+
+ assertThat(pendingOutput1.isComplete).isTrue()
+ assertThat(pendingOutput2.isComplete).isTrue()
+ assertThat(pendingOutput1.output).isNull()
+ assertThat(pendingOutput2.output).isNull()
+ }
+
+ @Test
+ fun availableOutputsAreNotDistributedToStartedOutputsAfterClose() {
+ outputDistributor.onOutputAvailable(fakeOutput1.outputNumber, fakeOutput1)
+ outputDistributor.onOutputAvailable(fakeOutput2.outputNumber, fakeOutput2)
+ outputDistributor.close()
+ outputDistributor.startWith(pendingOutput1) // Note: Would normally match fakeOutput1
+ outputDistributor.startWith(pendingOutput2) // Note: Would normally match fakeOutput2
+
+ // If we have valid outputs, but then receive close, and then receive matching start events
+ // ensure the outputs are considered dropped.
+
+ assertThat(pendingOutput1.isComplete).isTrue()
+ assertThat(pendingOutput2.isComplete).isTrue()
+ assertThat(pendingOutput1.output).isNull()
+ assertThat(pendingOutput2.output).isNull()
+
+ assertThat(fakeOutput1.finalized).isTrue()
+ assertThat(fakeOutput2.finalized).isTrue()
+ }
+
+ @Test
+ fun startedOutputsOutputsAreNotDistributedToAvailableOutputsAfterClose() {
+ outputDistributor.startWith(pendingOutput1) // Note: Would normally match fakeOutput1
+ outputDistributor.startWith(pendingOutput2) // Note: Would normally match fakeOutput2
+ outputDistributor.close()
+ outputDistributor.onOutputAvailable(fakeOutput1.outputNumber, fakeOutput1)
+ outputDistributor.onOutputAvailable(fakeOutput2.outputNumber, fakeOutput2)
+
+ // If we have valid start events, but then receive close, and then receive matching outputs,
+ // ensure all outputs are still considered dropped.
+
+ assertThat(pendingOutput1.isComplete).isTrue()
+ assertThat(pendingOutput2.isComplete).isTrue()
+ assertThat(pendingOutput1.output).isNull()
+ assertThat(pendingOutput2.output).isNull()
+
+ assertThat(fakeOutput1.finalized).isTrue()
+ assertThat(fakeOutput2.finalized).isTrue()
+ }
+
+ @Test
+ fun outOfOrderOutputsAreNotImmediatelyCanceled() {
+ outputDistributor.startWith(pendingOutput2)
+ outputDistributor.startWith(pendingOutput3)
+ outputDistributor.startWith(pendingOutput4)
+ outputDistributor.startWith(pendingOutput1) // Note! Out of order start event
+
+ // Complete Output 1
+ outputDistributor.onOutputAvailable(fakeOutput1.outputNumber, fakeOutput1)
+
+ assertThat(pendingOutput1.isComplete).isTrue()
+ assertThat(pendingOutput2.isComplete).isFalse() // Since 1 was out of order, do not cancel
+ assertThat(pendingOutput3.isComplete).isFalse() // Since 1 was out of order, do not cancel
+ assertThat(pendingOutput4.isComplete).isFalse() // Since 1 was out of order, do not cancel
+
+ assertThat(pendingOutput1.output).isEqualTo(fakeOutput1)
+ assertThat(pendingOutput2.output).isNull()
+ assertThat(pendingOutput3.output).isNull()
+ assertThat(pendingOutput4.output).isNull()
+ }
+
+ @Test
+ fun multipleOutOfOrderOutputsAreCanceledWhenOldOutputCompletes() {
+ outputDistributor.startWith(pendingOutput4)
+
+ outputDistributor.startWith(pendingOutput1) // Out of order (relative to 4)
+ outputDistributor.startWith(pendingOutput2) // Out of order (relative to 4)
+ outputDistributor.startWith(pendingOutput3) // Out of order (relative to 4)
+
+ // Complete output 3!
+ outputDistributor.onOutputAvailable(fakeOutput3.outputNumber, fakeOutput3)
+
+ assertThat(pendingOutput1.isComplete).isTrue() // Cancelled. 1 < 3
+ assertThat(pendingOutput2.isComplete).isTrue() // Cancelled. 2 < 3
+ assertThat(pendingOutput3.isComplete).isTrue() // Success: 3 = 3
+ assertThat(pendingOutput4.isComplete).isFalse() // Ignored 4 > 3
+
+ assertThat(pendingOutput1.output).isNull()
+ assertThat(pendingOutput2.output).isNull()
+ assertThat(pendingOutput3.output).isEqualTo(fakeOutput3)
+ }
+
+ @Test
+ fun fullyCompletedOutputAfterOutOfOrderResultDoesNotCancelPendingOutput() {
+ outputDistributor.startWith(pendingOutput2) // Normal (first)
+ outputDistributor.startWith(pendingOutput1) // Out of order start
+ outputDistributor.startWith(pendingOutput3) // Normal (> 2)
+ outputDistributor.startWith(pendingOutput4) // Normal (> 3)
+
+ // Normal outputs complete
+ outputDistributor.onOutputAvailable(fakeOutput2.outputNumber, fakeOutput2)
+ outputDistributor.onOutputAvailable(fakeOutput3.outputNumber, fakeOutput3)
+ outputDistributor.onOutputAvailable(fakeOutput4.outputNumber, fakeOutput4)
+
+ // Then the out of order event completes
+ outputDistributor.onOutputAvailable(fakeOutput1.outputNumber, fakeOutput1)
+
+ // All of the outputs are correctly distributed:
+ assertThat(pendingOutput1.isComplete).isTrue()
+ assertThat(pendingOutput2.isComplete).isTrue()
+ assertThat(pendingOutput3.isComplete).isTrue()
+ assertThat(pendingOutput4.isComplete).isTrue()
+
+ assertThat(pendingOutput1.output).isEqualTo(fakeOutput1)
+ assertThat(pendingOutput2.output).isEqualTo(fakeOutput2)
+ assertThat(pendingOutput3.output).isEqualTo(fakeOutput3)
+ assertThat(pendingOutput4.output).isEqualTo(fakeOutput4)
+
+ // Sequence is based on the order the outputs were started:
+ assertThat(pendingOutput1.outputSequence).isEqualTo(2)
+ assertThat(pendingOutput2.outputSequence).isEqualTo(1)
+ assertThat(pendingOutput3.outputSequence).isEqualTo(3)
+ assertThat(pendingOutput4.outputSequence).isEqualTo(4)
+ }
+
+ @Test
+ fun failedOutputFailPendingOutputs() {
+ outputDistributor.startWith(pendingOutput1)
+ outputDistributor.startWith(pendingOutput2)
+ outputDistributor.startWith(pendingOutput3)
+
+ outputDistributor.onOutputFailure(pendingOutput2.cameraFrameNumber)
+
+ assertThat(pendingOutput1.isComplete).isFalse()
+ assertThat(pendingOutput2.isComplete).isTrue()
+ assertThat(pendingOutput3.isComplete).isFalse()
+
+ assertThat(pendingOutput2.output).isNull()
+ }
+
+ @Test
+ fun failedOutputFailMultiplePendingOutputs() {
+ outputDistributor.startWith(pendingOutput1)
+ outputDistributor.startWith(pendingOutput2)
+ outputDistributor.startWith(pendingOutput3)
+
+ outputDistributor.onOutputFailure(pendingOutput2.cameraFrameNumber)
+ outputDistributor.onOutputFailure(pendingOutput3.cameraFrameNumber)
+ outputDistributor.onOutputFailure(pendingOutput1.cameraFrameNumber)
+
+ assertThat(pendingOutput1.isComplete).isTrue()
+ assertThat(pendingOutput2.isComplete).isTrue()
+ assertThat(pendingOutput3.isComplete).isTrue()
+
+ assertThat(pendingOutput1.output).isNull()
+ assertThat(pendingOutput2.output).isNull()
+ assertThat(pendingOutput3.output).isNull()
+ }
+
+ @Test
+ fun outputFailureBeforeStartFailsPendingOutput() {
+ outputDistributor.onOutputFailure(pendingOutput2.cameraFrameNumber)
+
+ outputDistributor.startWith(pendingOutput1)
+ outputDistributor.startWith(pendingOutput2)
+ outputDistributor.startWith(pendingOutput3)
+
+ assertThat(pendingOutput1.isComplete).isFalse()
+ assertThat(pendingOutput2.isComplete).isTrue()
+ assertThat(pendingOutput3.isComplete).isFalse()
+
+ assertThat(pendingOutput2.output).isNull()
+ }
+
+ @Test
+ fun previouslyAddedOutputIsClosedAfterFailure() {
+ outputDistributor.onOutputAvailable(fakeOutput1.outputNumber, fakeOutput1)
+ outputDistributor.onOutputFailure(pendingOutput1.cameraFrameNumber)
+
+ // Output cannot be matched with frameNumber
+ assertThat(fakeOutput1.finalized).isFalse()
+ // Output can be matched after onOutputStart occurs
+ outputDistributor.startWith(pendingOutput1)
+
+ // Output should be finalized, and the pending output should NOT receive it:
+ assertThat(fakeOutput1.finalized).isTrue()
+ assertThat(pendingOutput1.isComplete).isTrue()
+ assertThat(pendingOutput1.output).isNull()
+ }
+
+ /**
+ * Utility class that implements [OutputCompleteListener] and can be used to observe when an
+ * output is complete and the callback is invoked.
+ */
+ private class PendingOutput(
+ val cameraFrameNumber: FrameNumber,
+ val cameraTimestamp: CameraTimestamp,
+ val outputNumber: Long
+ ) : OutputCompleteListener<FakeOutput> {
+ private val _complete = atomic(false)
+ val isComplete: Boolean
+ get() = _complete.value
+
+ var outputSequence: Long? = null
+ var output: FakeOutput? = null
+
+ override fun onOutputComplete(
+ cameraFrameNumber: FrameNumber,
+ cameraTimestamp: CameraTimestamp,
+ outputSequence: Long,
+ outputNumber: Long,
+ output: FakeOutput?
+ ) {
+ // Assert that this callback has only been invoked once.
+ assertThat(_complete.compareAndSet(expect = false, update = true)).isTrue()
+
+ // Assert that the parameters in the invoked callback match the expected values.
+ assertThat(cameraFrameNumber).isEqualTo(cameraFrameNumber)
+ assertThat(cameraTimestamp).isEqualTo(cameraTimestamp)
+ assertThat(outputNumber).isEqualTo(outputNumber)
+
+ // Record the actual output and outputSequence for future checks.
+ this.outputSequence = outputSequence
+ this.output = output
+ }
+ }
+
+ /** Utility function for invoking [OutputDistributor.onOutputStarted] with a test output */
+ private fun OutputDistributor<FakeOutput>.startWith(pendingOutput: PendingOutput) {
+ this.onOutputStarted(
+ pendingOutput.cameraFrameNumber,
+ pendingOutput.cameraTimestamp,
+ pendingOutput.outputNumber,
+ pendingOutput
+ )
+ }
+
+ /** Utility class for testing if an output was finalized (closed) or not */
+ private class FakeOutput(
+ val outputNumber: Long,
+ ) {
+ private val _finalized = atomic(false)
+ val finalized: Boolean
+ get() = _finalized.value
+
+ fun finalize() {
+ // Check that this is only ever finalized once
+ assertThat(_finalized.compareAndSet(expect = false, update = true)).isTrue()
+ }
+ }
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/utils/Exif.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/utils/Exif.java
index b26e75f..729fa09 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/utils/Exif.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/utils/Exif.java
@@ -50,6 +50,9 @@
/** Timestamp value indicating a timestamp value that is either not set or not valid */
public static final long INVALID_TIMESTAMP = -1;
+ // Forked from ExifInterface.TAG_THUMBNAIL_ORIENTATION. The value is library-internal so we
+ // can't depend on it directly.
+ public static final String TAG_THUMBNAIL_ORIENTATION = "ThumbnailOrientation";
private static final String TAG = Exif.class.getSimpleName();
@@ -94,7 +97,7 @@
ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH,
ExifInterface.TAG_THUMBNAIL_IMAGE_LENGTH,
ExifInterface.TAG_THUMBNAIL_IMAGE_WIDTH,
- ExifInterface.TAG_THUMBNAIL_ORIENTATION);
+ TAG_THUMBNAIL_ORIENTATION);
private final ExifInterface mExifInterface;
@@ -863,7 +866,7 @@
ExifInterface.TAG_INTEROPERABILITY_INDEX,
ExifInterface.TAG_THUMBNAIL_IMAGE_LENGTH,
ExifInterface.TAG_THUMBNAIL_IMAGE_WIDTH,
- ExifInterface.TAG_THUMBNAIL_ORIENTATION,
+ TAG_THUMBNAIL_ORIENTATION,
ExifInterface.TAG_DNG_VERSION,
ExifInterface.TAG_DEFAULT_CROP_SIZE,
ExifInterface.TAG_ORF_THUMBNAIL_IMAGE,
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionsManagerTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionsManagerTest.kt
index 6d929df..b3506e4 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionsManagerTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionsManagerTest.kt
@@ -97,7 +97,7 @@
@After
fun teardown(): Unit = runBlocking {
if (::cameraProvider.isInitialized) {
- cameraProvider.shutdown()[10000, TimeUnit.MILLISECONDS]
+ cameraProvider.shutdownAsync()[10000, TimeUnit.MILLISECONDS]
}
if (::extensionsManager.isInitialized) {
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ImageAnalysisTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ImageAnalysisTest.kt
index aa37b5d..2fb9866 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ImageAnalysisTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ImageAnalysisTest.kt
@@ -110,7 +110,7 @@
@After
fun teardown(): Unit = runBlocking {
if (::cameraProvider.isInitialized) {
- cameraProvider.shutdown()[10000, TimeUnit.MILLISECONDS]
+ cameraProvider.shutdownAsync()[10000, TimeUnit.MILLISECONDS]
}
if (::extensionsManager.isInitialized) {
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ImageCaptureTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ImageCaptureTest.kt
index fd309a3..0b1b26c 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ImageCaptureTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ImageCaptureTest.kt
@@ -108,7 +108,7 @@
@After
fun teardown(): Unit = runBlocking {
if (::cameraProvider.isInitialized) {
- cameraProvider.shutdown()[10000, TimeUnit.MILLISECONDS]
+ cameraProvider.shutdownAsync()[10000, TimeUnit.MILLISECONDS]
}
if (::extensionsManager.isInitialized) {
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/PreviewTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/PreviewTest.kt
index 4326ff0..7ba9407 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/PreviewTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/PreviewTest.kt
@@ -140,7 +140,7 @@
@After
fun teardown(): Unit = runBlocking {
if (::cameraProvider.isInitialized) {
- cameraProvider.shutdown()[10000, TimeUnit.MILLISECONDS]
+ cameraProvider.shutdownAsync()[10000, TimeUnit.MILLISECONDS]
}
if (::extensionsManager.isInitialized) {
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/AdvancedSessionProcessorTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/AdvancedSessionProcessorTest.kt
index b0b4d0c..d780b54 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/AdvancedSessionProcessorTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/AdvancedSessionProcessorTest.kt
@@ -129,7 +129,7 @@
fun tearDown() = runBlocking {
if (::cameraProvider.isInitialized) {
withContext(Dispatchers.Main) {
- cameraProvider.shutdown()[10, TimeUnit.SECONDS]
+ cameraProvider.shutdownAsync()[10, TimeUnit.SECONDS]
}
}
}
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/ImageCaptureConfigProviderTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/ImageCaptureConfigProviderTest.kt
index e599e7f..85a11f7 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/ImageCaptureConfigProviderTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/ImageCaptureConfigProviderTest.kt
@@ -82,7 +82,7 @@
@After
fun cleanUp(): Unit = runBlocking {
if (::cameraProvider.isInitialized) {
- cameraProvider.shutdown()[10000, TimeUnit.MILLISECONDS]
+ cameraProvider.shutdownAsync()[10000, TimeUnit.MILLISECONDS]
}
}
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/PreviewConfigProviderTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/PreviewConfigProviderTest.kt
index 7fd007a..e3f3091 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/PreviewConfigProviderTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/PreviewConfigProviderTest.kt
@@ -82,7 +82,7 @@
@After
fun cleanUp(): Unit = runBlocking {
if (::cameraProvider.isInitialized) {
- cameraProvider.shutdown()[10000, TimeUnit.MILLISECONDS]
+ cameraProvider.shutdownAsync()[10000, TimeUnit.MILLISECONDS]
}
}
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessorTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessorTest.kt
index ddd974c..b082271 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessorTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessorTest.kt
@@ -182,7 +182,7 @@
fun tearDown() = runBlocking {
if (::cameraProvider.isInitialized) {
withContext(Dispatchers.Main) {
- cameraProvider.shutdown()[10, TimeUnit.SECONDS]
+ cameraProvider.shutdownAsync()[10, TimeUnit.SECONDS]
}
}
diff --git a/camera/camera-lifecycle/api/current.txt b/camera/camera-lifecycle/api/current.txt
index d0621cb..5dd135e 100644
--- a/camera/camera-lifecycle/api/current.txt
+++ b/camera/camera-lifecycle/api/current.txt
@@ -15,6 +15,7 @@
method public boolean hasCamera(androidx.camera.core.CameraSelector) throws androidx.camera.core.CameraInfoUnavailableException;
method public boolean isBound(androidx.camera.core.UseCase);
method @MainThread public boolean isConcurrentCameraModeOn();
+ method @VisibleForTesting public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> shutdownAsync();
method @MainThread public void unbind(androidx.camera.core.UseCase!...);
method @MainThread public void unbindAll();
}
diff --git a/camera/camera-lifecycle/api/restricted_current.txt b/camera/camera-lifecycle/api/restricted_current.txt
index d0621cb..5dd135e 100644
--- a/camera/camera-lifecycle/api/restricted_current.txt
+++ b/camera/camera-lifecycle/api/restricted_current.txt
@@ -15,6 +15,7 @@
method public boolean hasCamera(androidx.camera.core.CameraSelector) throws androidx.camera.core.CameraInfoUnavailableException;
method public boolean isBound(androidx.camera.core.UseCase);
method @MainThread public boolean isConcurrentCameraModeOn();
+ method @VisibleForTesting public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> shutdownAsync();
method @MainThread public void unbind(androidx.camera.core.UseCase!...);
method @MainThread public void unbindAll();
}
diff --git a/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/ProcessCameraProviderTest.kt b/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/ProcessCameraProviderTest.kt
index 465a0c9..0939e5f 100644
--- a/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/ProcessCameraProviderTest.kt
+++ b/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/ProcessCameraProviderTest.kt
@@ -73,7 +73,7 @@
runBlocking(MainScope().coroutineContext) {
try {
val provider = ProcessCameraProvider.getInstance(context).await()
- provider.shutdown().await()
+ provider.shutdownAsync().await()
} catch (e: IllegalStateException) {
// ProcessCameraProvider may not be configured. Ignore.
}
@@ -662,7 +662,7 @@
runBlocking {
provider = ProcessCameraProvider.getInstance(context).await()
// Clear the configuration so we can reinit
- provider.shutdown().await()
+ provider.shutdownAsync().await()
}
// Should not throw exception
diff --git a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.java b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.java
index f6e56cd..4736550 100644
--- a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.java
+++ b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.java
@@ -286,6 +286,26 @@
@VisibleForTesting
@NonNull
public ListenableFuture<Void> shutdown() {
+ return shutdownAsync();
+ }
+
+ /**
+ * Allows shutting down this {@link ProcessCameraProvider} instance so a new instance can be
+ * retrieved by {@link #getInstance(Context)}.
+ *
+ * <p>Once shutdownAsync is invoked, a new instance can be retrieved with
+ * {@link ProcessCameraProvider#getInstance(Context)}.
+ *
+ * <p>This method should be used for testing purposes only. Along with
+ * {@link #configureInstance(CameraXConfig)}, this allows the process camera provider to be
+ * used in test suites which may need to initialize CameraX in different ways in between tests.
+ *
+ * @return A {@link ListenableFuture} representing the shutdown status. Cancellation of this
+ * future is a no-op.
+ */
+ @VisibleForTesting
+ @NonNull
+ public ListenableFuture<Void> shutdownAsync() {
runOnMainSync(() -> {
unbindAll();
mLifecycleCameraRepository.clear();
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/SupportedQualitiesVerificationTest.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/SupportedQualitiesVerificationTest.kt
index ec802df..1f05175 100644
--- a/camera/camera-video/src/androidTest/java/androidx/camera/video/SupportedQualitiesVerificationTest.kt
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/SupportedQualitiesVerificationTest.kt
@@ -177,7 +177,7 @@
@After
fun tearDown() {
if (this::cameraProvider.isInitialized) {
- cameraProvider.shutdown()[10, TimeUnit.SECONDS]
+ cameraProvider.shutdownAsync()[10, TimeUnit.SECONDS]
}
for (surfaceProcessor in surfaceProcessorsToRelease) {
surfaceProcessor.release()
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingFrameDropTest.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingFrameDropTest.kt
index ccebfc0..0167c0b 100644
--- a/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingFrameDropTest.kt
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingFrameDropTest.kt
@@ -179,7 +179,7 @@
fun tearDown() = runBlocking {
if (needsShutdown) {
needsShutdown = false
- cameraProvider.shutdown().await()
+ cameraProvider.shutdownAsync().await()
}
}
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingTest.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingTest.kt
index 17202e8..b873ed5 100644
--- a/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingTest.kt
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingTest.kt
@@ -223,7 +223,7 @@
@After
fun tearDown() {
if (this::cameraProvider.isInitialized) {
- cameraProvider.shutdown()[10, TimeUnit.SECONDS]
+ cameraProvider.shutdownAsync()[10, TimeUnit.SECONDS]
}
}
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/CapabilitiesByQuality.java b/camera/camera-video/src/main/java/androidx/camera/video/CapabilitiesByQuality.java
new file mode 100644
index 0000000..5386200
--- /dev/null
+++ b/camera/camera-video/src/main/java/androidx/camera/video/CapabilitiesByQuality.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright 2023 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.video;
+
+import android.util.Size;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.RestrictTo;
+import androidx.camera.core.Logger;
+import androidx.camera.core.impl.EncoderProfilesProvider;
+import androidx.camera.core.impl.EncoderProfilesProxy;
+import androidx.camera.core.impl.utils.CompareSizesByArea;
+import androidx.camera.video.internal.VideoValidatedEncoderProfilesProxy;
+import androidx.core.util.Preconditions;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * This class implements the video capabilities query logic related to quality and resolution.
+ */
+@RequiresApi(21)
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class CapabilitiesByQuality {
+ private static final String TAG = "CapabilitiesByQuality";
+
+ /**
+ * Maps quality to supported {@link VideoValidatedEncoderProfilesProxy}. The order is from
+ * size large to small.
+ */
+ private final Map<Quality, VideoValidatedEncoderProfilesProxy> mSupportedProfilesMap =
+ new LinkedHashMap<>();
+ private final TreeMap<Size, Quality> mAreaSortedSizeToQualityMap =
+ new TreeMap<>(new CompareSizesByArea());
+ private final VideoValidatedEncoderProfilesProxy mHighestProfiles;
+ private final VideoValidatedEncoderProfilesProxy mLowestProfiles;
+
+ public CapabilitiesByQuality(@NonNull EncoderProfilesProvider provider) {
+ // Construct supported profile map.
+ for (Quality quality : Quality.getSortedQualities()) {
+ EncoderProfilesProxy profiles = getEncoderProfiles(quality, provider);
+ if (profiles == null) {
+ continue;
+ }
+
+ // Validate that EncoderProfiles contain video information.
+ Logger.d(TAG, "profiles = " + profiles);
+ VideoValidatedEncoderProfilesProxy validatedProfiles = toValidatedProfiles(
+ profiles);
+ if (validatedProfiles == null) {
+ Logger.w(TAG, "EncoderProfiles of quality " + quality + " has no video "
+ + "validated profiles.");
+ continue;
+ }
+
+ EncoderProfilesProxy.VideoProfileProxy videoProfile =
+ validatedProfiles.getDefaultVideoProfile();
+ Size size = new Size(videoProfile.getWidth(), videoProfile.getHeight());
+ mAreaSortedSizeToQualityMap.put(size, quality);
+
+ // SortedQualities is from size large to small.
+ mSupportedProfilesMap.put(quality, validatedProfiles);
+ }
+ if (mSupportedProfilesMap.isEmpty()) {
+ Logger.e(TAG, "No supported EncoderProfiles");
+ mLowestProfiles = null;
+ mHighestProfiles = null;
+ } else {
+ Deque<VideoValidatedEncoderProfilesProxy> profileQueue = new ArrayDeque<>(
+ mSupportedProfilesMap.values());
+ mHighestProfiles = profileQueue.peekFirst();
+ mLowestProfiles = profileQueue.peekLast();
+ }
+ }
+
+ /**
+ * Gets the supported quality list.
+ *
+ * <p>The returned list is sorted by quality size from largest to smallest. For the qualities
+ * in the returned list, {@link #isQualitySupported(Quality)} will return {@code true}.
+ *
+ * <p>Note: Constants {@link Quality#HIGHEST} and {@link Quality#LOWEST} are not included in
+ * the returned list, but their corresponding qualities are included.
+ */
+ @NonNull
+ public List<Quality> getSupportedQualities() {
+ return new ArrayList<>(mSupportedProfilesMap.keySet());
+ }
+
+ /**
+ * Checks whether the quality is supported.
+ *
+ * <p>If this method is called with {@link Quality#LOWEST} or {@link Quality#HIGHEST}, it
+ * will return {@code true} except the case that none of the qualities can be supported.
+ */
+ public boolean isQualitySupported(@NonNull Quality quality) {
+ checkQualityConstantsOrThrow(quality);
+ return getProfiles(quality) != null;
+ }
+
+ /**
+ * Gets a {@link VideoValidatedEncoderProfilesProxy} for the input quality or {@code null} if
+ * the quality is not supported.
+ */
+ @Nullable
+ public VideoValidatedEncoderProfilesProxy getProfiles(@NonNull Quality quality) {
+ checkQualityConstantsOrThrow(quality);
+ if (quality == Quality.HIGHEST) {
+ return mHighestProfiles;
+ } else if (quality == Quality.LOWEST) {
+ return mLowestProfiles;
+ }
+ return mSupportedProfilesMap.get(quality);
+ }
+
+ /**
+ * Finds the nearest higher supported {@link VideoValidatedEncoderProfilesProxy} for the
+ * input size.
+ */
+ @Nullable
+ public VideoValidatedEncoderProfilesProxy findNearestHigherSupportedEncoderProfilesFor(
+ @NonNull Size size) {
+ VideoValidatedEncoderProfilesProxy encoderProfiles = null;
+ Quality highestSupportedQuality = findNearestHigherSupportedQualityFor(size);
+ Logger.d(TAG,
+ "Using supported quality of " + highestSupportedQuality + " for size " + size);
+ if (highestSupportedQuality != Quality.NONE) {
+ encoderProfiles = getProfiles(highestSupportedQuality);
+ if (encoderProfiles == null) {
+ throw new AssertionError("Camera advertised available quality but did not "
+ + "produce EncoderProfiles for advertised quality.");
+ }
+ }
+ return encoderProfiles;
+ }
+
+ /** Finds the nearest higher supported {@link Quality} for the input size. */
+ @NonNull
+ public Quality findNearestHigherSupportedQualityFor(@NonNull Size size) {
+ Map.Entry<Size, Quality> ceilEntry = mAreaSortedSizeToQualityMap.ceilingEntry(size);
+
+ if (ceilEntry != null) {
+ // The ceiling entry will either be equivalent or higher in size, so always
+ // return it.
+ return ceilEntry.getValue();
+ } else {
+ // If a ceiling entry doesn't exist and a floor entry exists, it is the closest
+ // we have, so return it.
+ Map.Entry<Size, Quality> floorEntry = mAreaSortedSizeToQualityMap.floorEntry(size);
+ if (floorEntry != null) {
+ return floorEntry.getValue();
+ }
+ }
+
+ // No supported qualities.
+ return Quality.NONE;
+ }
+
+ @Nullable
+ private EncoderProfilesProxy getEncoderProfiles(@NonNull Quality quality,
+ @NonNull EncoderProfilesProvider provider) {
+ Preconditions.checkState(quality instanceof Quality.ConstantQuality,
+ "Currently only support ConstantQuality");
+ int qualityValue = ((Quality.ConstantQuality) quality).getValue();
+
+ return provider.getAll(qualityValue);
+ }
+
+ @Nullable
+ private VideoValidatedEncoderProfilesProxy toValidatedProfiles(
+ @NonNull EncoderProfilesProxy profiles) {
+ // According to the document, the first profile is the default video profile.
+ List<EncoderProfilesProxy.VideoProfileProxy> videoProfiles =
+ profiles.getVideoProfiles();
+ if (videoProfiles.isEmpty()) {
+ return null;
+ }
+
+ return VideoValidatedEncoderProfilesProxy.from(profiles);
+ }
+
+ private static void checkQualityConstantsOrThrow(@NonNull Quality quality) {
+ Preconditions.checkArgument(Quality.containsQuality(quality),
+ "Unknown quality: " + quality);
+ }
+}
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/Recorder.java b/camera/camera-video/src/main/java/androidx/camera/video/Recorder.java
index bc2f76f0..3283a48 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/Recorder.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/Recorder.java
@@ -1095,8 +1095,8 @@
DynamicRange dynamicRange = surfaceRequest.getDynamicRange();
VideoCapabilities capabilities = getVideoCapabilities(
surfaceRequest.getCamera().getCameraInfo());
- Quality highestSupportedQuality = capabilities.findHighestSupportedQualityFor(surfaceSize,
- dynamicRange);
+ Quality highestSupportedQuality = capabilities.findNearestHigherSupportedQualityFor(
+ surfaceSize, dynamicRange);
Logger.d(TAG, "Using supported quality of " + highestSupportedQuality
+ " for surface size " + surfaceSize);
if (highestSupportedQuality != Quality.NONE) {
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/RecorderVideoCapabilities.java b/camera/camera-video/src/main/java/androidx/camera/video/RecorderVideoCapabilities.java
index 8e5c043..74fde74 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/RecorderVideoCapabilities.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/RecorderVideoCapabilities.java
@@ -33,14 +33,12 @@
import androidx.arch.core.util.Function;
import androidx.camera.core.CameraInfo;
import androidx.camera.core.DynamicRange;
-import androidx.camera.core.Logger;
import androidx.camera.core.impl.CameraInfoInternal;
import androidx.camera.core.impl.EncoderProfilesProvider;
import androidx.camera.core.impl.EncoderProfilesProxy;
import androidx.camera.core.impl.EncoderProfilesProxy.VideoProfileProxy;
import androidx.camera.core.impl.Quirks;
import androidx.camera.core.impl.ResolutionValidatedEncoderProfilesProvider;
-import androidx.camera.core.impl.utils.CompareSizesByArea;
import androidx.camera.video.internal.BackupHdrProfileEncoderProfilesProvider;
import androidx.camera.video.internal.DynamicRangeMatchedEncoderProfilesProvider;
import androidx.camera.video.internal.VideoValidatedEncoderProfilesProxy;
@@ -49,15 +47,11 @@
import androidx.camera.video.internal.workaround.QualityValidatedEncoderProfilesProvider;
import androidx.core.util.Preconditions;
-import java.util.ArrayDeque;
import java.util.ArrayList;
-import java.util.Deque;
import java.util.HashMap;
-import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.TreeMap;
/**
* RecorderVideoCapabilities is used to query video recording capabilities related to Recorder.
@@ -73,8 +67,6 @@
@RestrictTo(RestrictTo.Scope.LIBRARY)
public final class RecorderVideoCapabilities implements VideoCapabilities {
- private static final String TAG = "RecorderVideoCapabilities";
-
private final EncoderProfilesProvider mProfilesProvider;
private final boolean mIsStabilizationSupported;
@@ -191,20 +183,20 @@
@Nullable
@Override
- public VideoValidatedEncoderProfilesProxy findHighestSupportedEncoderProfilesFor(
+ public VideoValidatedEncoderProfilesProxy findNearestHigherSupportedEncoderProfilesFor(
@NonNull Size size, @NonNull DynamicRange dynamicRange) {
CapabilitiesByQuality capabilities = getCapabilities(dynamicRange);
- return capabilities == null ? null : capabilities.findHighestSupportedEncoderProfilesFor(
- size);
+ return capabilities == null ? null
+ : capabilities.findNearestHigherSupportedEncoderProfilesFor(size);
}
@NonNull
@Override
- public Quality findHighestSupportedQualityFor(@NonNull Size size,
+ public Quality findNearestHigherSupportedQualityFor(@NonNull Size size,
@NonNull DynamicRange dynamicRange) {
CapabilitiesByQuality capabilities = getCapabilities(dynamicRange);
- return capabilities == null ? Quality.NONE : capabilities.findHighestSupportedQualityFor(
- size);
+ return capabilities == null ? Quality.NONE
+ : capabilities.findNearestHigherSupportedQualityFor(size);
}
@Nullable
@@ -309,147 +301,4 @@
&& dynamicRange.getEncoding() != ENCODING_HDR_UNSPECIFIED
&& dynamicRange.getBitDepth() != BIT_DEPTH_UNSPECIFIED;
}
-
- /**
- * This class implements the video capabilities query logic related to quality and resolution.
- */
- @VisibleForTesting
- static class CapabilitiesByQuality {
-
- /**
- * Maps quality to supported {@link VideoValidatedEncoderProfilesProxy}. The order is from
- * size large to small.
- */
- private final Map<Quality, VideoValidatedEncoderProfilesProxy> mSupportedProfilesMap =
- new LinkedHashMap<>();
- private final TreeMap<Size, Quality> mAreaSortedSizeToQualityMap =
- new TreeMap<>(new CompareSizesByArea());
- private final VideoValidatedEncoderProfilesProxy mHighestProfiles;
- private final VideoValidatedEncoderProfilesProxy mLowestProfiles;
-
- CapabilitiesByQuality(@NonNull EncoderProfilesProvider provider) {
- // Construct supported profile map.
- for (Quality quality : Quality.getSortedQualities()) {
- EncoderProfilesProxy profiles = getEncoderProfiles(quality, provider);
- if (profiles == null) {
- continue;
- }
-
- // Validate that EncoderProfiles contain video information.
- Logger.d(TAG, "profiles = " + profiles);
- VideoValidatedEncoderProfilesProxy validatedProfiles = toValidatedProfiles(
- profiles);
- if (validatedProfiles == null) {
- Logger.w(TAG, "EncoderProfiles of quality " + quality + " has no video "
- + "validated profiles.");
- continue;
- }
-
- EncoderProfilesProxy.VideoProfileProxy videoProfile =
- validatedProfiles.getDefaultVideoProfile();
- Size size = new Size(videoProfile.getWidth(), videoProfile.getHeight());
- mAreaSortedSizeToQualityMap.put(size, quality);
-
- // SortedQualities is from size large to small.
- mSupportedProfilesMap.put(quality, validatedProfiles);
- }
- if (mSupportedProfilesMap.isEmpty()) {
- Logger.e(TAG, "No supported EncoderProfiles");
- mLowestProfiles = null;
- mHighestProfiles = null;
- } else {
- Deque<VideoValidatedEncoderProfilesProxy> profileQueue = new ArrayDeque<>(
- mSupportedProfilesMap.values());
- mHighestProfiles = profileQueue.peekFirst();
- mLowestProfiles = profileQueue.peekLast();
- }
- }
-
- @NonNull
- public List<Quality> getSupportedQualities() {
- return new ArrayList<>(mSupportedProfilesMap.keySet());
- }
-
- public boolean isQualitySupported(@NonNull Quality quality) {
- checkQualityConstantsOrThrow(quality);
- return getProfiles(quality) != null;
- }
-
- @Nullable
- public VideoValidatedEncoderProfilesProxy getProfiles(@NonNull Quality quality) {
- checkQualityConstantsOrThrow(quality);
- if (quality == Quality.HIGHEST) {
- return mHighestProfiles;
- } else if (quality == Quality.LOWEST) {
- return mLowestProfiles;
- }
- return mSupportedProfilesMap.get(quality);
- }
-
- @Nullable
- public VideoValidatedEncoderProfilesProxy findHighestSupportedEncoderProfilesFor(
- @NonNull Size size) {
- VideoValidatedEncoderProfilesProxy encoderProfiles = null;
- Quality highestSupportedQuality = findHighestSupportedQualityFor(size);
- Logger.d(TAG,
- "Using supported quality of " + highestSupportedQuality + " for size " + size);
- if (highestSupportedQuality != Quality.NONE) {
- encoderProfiles = getProfiles(highestSupportedQuality);
- if (encoderProfiles == null) {
- throw new AssertionError("Camera advertised available quality but did not "
- + "produce EncoderProfiles for advertised quality.");
- }
- }
- return encoderProfiles;
- }
-
- @NonNull
- public Quality findHighestSupportedQualityFor(@NonNull Size size) {
- Map.Entry<Size, Quality> ceilEntry = mAreaSortedSizeToQualityMap.ceilingEntry(size);
-
- if (ceilEntry != null) {
- // The ceiling entry will either be equivalent or higher in size, so always
- // return it.
- return ceilEntry.getValue();
- } else {
- // If a ceiling entry doesn't exist and a floor entry exists, it is the closest
- // we have, so return it.
- Map.Entry<Size, Quality> floorEntry = mAreaSortedSizeToQualityMap.floorEntry(size);
- if (floorEntry != null) {
- return floorEntry.getValue();
- }
- }
-
- // No supported qualities.
- return Quality.NONE;
- }
-
- @Nullable
- private EncoderProfilesProxy getEncoderProfiles(@NonNull Quality quality,
- @NonNull EncoderProfilesProvider provider) {
- Preconditions.checkState(quality instanceof Quality.ConstantQuality,
- "Currently only support ConstantQuality");
- int qualityValue = ((Quality.ConstantQuality) quality).getValue();
-
- return provider.getAll(qualityValue);
- }
-
- @Nullable
- private VideoValidatedEncoderProfilesProxy toValidatedProfiles(
- @NonNull EncoderProfilesProxy profiles) {
- // According to the document, the first profile is the default video profile.
- List<EncoderProfilesProxy.VideoProfileProxy> videoProfiles =
- profiles.getVideoProfiles();
- if (videoProfiles.isEmpty()) {
- return null;
- }
-
- return VideoValidatedEncoderProfilesProxy.from(profiles);
- }
-
- private static void checkQualityConstantsOrThrow(@NonNull Quality quality) {
- Preconditions.checkArgument(Quality.containsQuality(quality),
- "Unknown quality: " + quality);
- }
- }
}
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/VideoCapabilities.java b/camera/camera-video/src/main/java/androidx/camera/video/VideoCapabilities.java
index e10278d..6cb48a4 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/VideoCapabilities.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/VideoCapabilities.java
@@ -165,11 +165,11 @@
* nearest EncoderProfilesProxy will be selected, whether that EncoderProfilesProxy's
* resolution is above or below the given size.
*
- * @see #findHighestSupportedQualityFor(Size, DynamicRange)
+ * @see #findNearestHigherSupportedQualityFor(Size, DynamicRange)
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
@Nullable
- default VideoValidatedEncoderProfilesProxy findHighestSupportedEncoderProfilesFor(
+ default VideoValidatedEncoderProfilesProxy findNearestHigherSupportedEncoderProfilesFor(
@NonNull Size size, @NonNull DynamicRange dynamicRange) {
return null;
}
@@ -190,7 +190,7 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
@NonNull
- default Quality findHighestSupportedQualityFor(@NonNull Size size,
+ default Quality findNearestHigherSupportedQualityFor(@NonNull Size size,
@NonNull DynamicRange dynamicRange) {
return Quality.NONE;
}
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java b/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
index 3f73068..8f97391 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
@@ -1136,7 +1136,8 @@
// Find the nearest EncoderProfiles
VideoValidatedEncoderProfilesProxy encoderProfiles =
- videoCapabilities.findHighestSupportedEncoderProfilesFor(resolution, dynamicRange);
+ videoCapabilities.findNearestHigherSupportedEncoderProfilesFor(resolution,
+ dynamicRange);
VideoEncoderInfo videoEncoderInfo = resolveVideoEncoderInfo(videoEncoderInfoFinder,
encoderProfiles, mediaSpec, resolution, dynamicRange, expectedFrameRate);
if (videoEncoderInfo == null) {
diff --git a/camera/camera-video/src/test/java/androidx/camera/video/CapabilitiesByQualityTest.kt b/camera/camera-video/src/test/java/androidx/camera/video/CapabilitiesByQualityTest.kt
new file mode 100644
index 0000000..607b335
--- /dev/null
+++ b/camera/camera-video/src/test/java/androidx/camera/video/CapabilitiesByQualityTest.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2023 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.video
+
+import android.media.CamcorderProfile.QUALITY_2160P
+import android.media.CamcorderProfile.QUALITY_720P
+import android.media.CamcorderProfile.QUALITY_HIGH
+import android.media.CamcorderProfile.QUALITY_LOW
+import android.os.Build
+import androidx.camera.testing.impl.EncoderProfilesUtil.PROFILES_2160P
+import androidx.camera.testing.impl.EncoderProfilesUtil.PROFILES_720P
+import androidx.camera.testing.impl.EncoderProfilesUtil.RESOLUTION_1080P
+import androidx.camera.testing.impl.EncoderProfilesUtil.RESOLUTION_2160P
+import androidx.camera.testing.impl.EncoderProfilesUtil.RESOLUTION_480P
+import androidx.camera.testing.impl.EncoderProfilesUtil.RESOLUTION_4KDCI
+import androidx.camera.testing.impl.EncoderProfilesUtil.RESOLUTION_720P
+import androidx.camera.testing.impl.EncoderProfilesUtil.RESOLUTION_QVGA
+import androidx.camera.testing.impl.fakes.FakeEncoderProfilesProvider
+import androidx.camera.video.Quality.FHD
+import androidx.camera.video.Quality.HD
+import androidx.camera.video.Quality.HIGHEST
+import androidx.camera.video.Quality.LOWEST
+import androidx.camera.video.Quality.SD
+import androidx.camera.video.Quality.UHD
+import androidx.camera.video.internal.VideoValidatedEncoderProfilesProxy
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.internal.DoNotInstrument
+
+@RunWith(RobolectricTestRunner::class)
+@DoNotInstrument
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+class CapabilitiesByQualityTest {
+
+ private val capabilitiesByQuality = CapabilitiesByQuality(FakeEncoderProfilesProvider.Builder()
+ .add(QUALITY_HIGH, PROFILES_2160P)
+ .add(QUALITY_2160P, PROFILES_2160P) // UHD
+ .add(QUALITY_720P, PROFILES_720P) // HD
+ .add(QUALITY_LOW, PROFILES_720P)
+ .build())
+
+ @Test
+ fun canGetSupportedQualities() {
+ assertThat(capabilitiesByQuality.supportedQualities).containsExactly(UHD, HD)
+ }
+
+ @Test
+ fun isQualitySupported_returnExpectedResults() {
+ assertThat(capabilitiesByQuality.isQualitySupported(HIGHEST)).isTrue()
+ assertThat(capabilitiesByQuality.isQualitySupported(LOWEST)).isTrue()
+ assertThat(capabilitiesByQuality.isQualitySupported(UHD)).isTrue()
+ assertThat(capabilitiesByQuality.isQualitySupported(FHD)).isFalse()
+ assertThat(capabilitiesByQuality.isQualitySupported(HD)).isTrue()
+ assertThat(capabilitiesByQuality.isQualitySupported(SD)).isFalse()
+ }
+
+ @Test
+ fun canGetProfiles() {
+ assertThat(capabilitiesByQuality.getProfiles(HIGHEST)).isNotNull()
+ assertThat(capabilitiesByQuality.getProfiles(LOWEST)).isNotNull()
+ assertThat(capabilitiesByQuality.getProfiles(UHD)).isNotNull()
+ assertThat(capabilitiesByQuality.getProfiles(FHD)).isNull()
+ assertThat(capabilitiesByQuality.getProfiles(HD)).isNotNull()
+ assertThat(capabilitiesByQuality.getProfiles(SD)).isNull()
+ }
+
+ @Test
+ fun canFindNearestHigherSupportedEncoderProfiles() {
+ val videoValidProfile2160P = VideoValidatedEncoderProfilesProxy.from(PROFILES_2160P)
+ val videoValidProfile720P = VideoValidatedEncoderProfilesProxy.from(PROFILES_720P)
+
+ assertThat(
+ capabilitiesByQuality.findNearestHigherSupportedEncoderProfilesFor(RESOLUTION_4KDCI)
+ ).isEqualTo(videoValidProfile2160P)
+ assertThat(
+ capabilitiesByQuality.findNearestHigherSupportedEncoderProfilesFor(RESOLUTION_2160P)
+ ).isEqualTo(videoValidProfile2160P)
+ assertThat(
+ capabilitiesByQuality.findNearestHigherSupportedEncoderProfilesFor(RESOLUTION_1080P)
+ ).isEqualTo(videoValidProfile2160P)
+ assertThat(
+ capabilitiesByQuality.findNearestHigherSupportedEncoderProfilesFor(RESOLUTION_720P)
+ ).isEqualTo(videoValidProfile720P)
+ assertThat(
+ capabilitiesByQuality.findNearestHigherSupportedEncoderProfilesFor(RESOLUTION_480P)
+ ).isEqualTo(videoValidProfile720P)
+ assertThat(
+ capabilitiesByQuality.findNearestHigherSupportedEncoderProfilesFor(RESOLUTION_QVGA)
+ ).isEqualTo(videoValidProfile720P)
+ }
+
+ @Test
+ fun canFindNearestHigherSupportedQuality() {
+ assertThat(capabilitiesByQuality.findNearestHigherSupportedQualityFor(RESOLUTION_4KDCI))
+ .isEqualTo(UHD)
+ assertThat(capabilitiesByQuality.findNearestHigherSupportedQualityFor(RESOLUTION_2160P))
+ .isEqualTo(UHD)
+ assertThat(capabilitiesByQuality.findNearestHigherSupportedQualityFor(RESOLUTION_1080P))
+ .isEqualTo(UHD)
+ assertThat(capabilitiesByQuality.findNearestHigherSupportedQualityFor(RESOLUTION_720P))
+ .isEqualTo(HD)
+ assertThat(capabilitiesByQuality.findNearestHigherSupportedQualityFor(RESOLUTION_480P))
+ .isEqualTo(HD)
+ assertThat(capabilitiesByQuality.findNearestHigherSupportedQualityFor(RESOLUTION_QVGA))
+ .isEqualTo(HD)
+ }
+}
diff --git a/camera/camera-video/src/test/java/androidx/camera/video/RecorderVideoCapabilitiesTest.kt b/camera/camera-video/src/test/java/androidx/camera/video/RecorderVideoCapabilitiesTest.kt
index 1c50931..f16b558 100644
--- a/camera/camera-video/src/test/java/androidx/camera/video/RecorderVideoCapabilitiesTest.kt
+++ b/camera/camera-video/src/test/java/androidx/camera/video/RecorderVideoCapabilitiesTest.kt
@@ -212,78 +212,83 @@
}
@Test
- fun findHighestSupportedQuality_returnsHigherQuality() {
+ fun findNearestHigherSupportedQuality_returnsHigherQuality() {
// Create a size between 720p and 2160p
val (width720p, height720p) = RESOLUTION_720P
val inBetweenSize = Size(width720p + 10, height720p)
- assertThat(videoCapabilities.findHighestSupportedQualityFor(inBetweenSize, SDR))
+ assertThat(videoCapabilities.findNearestHigherSupportedQualityFor(inBetweenSize, SDR))
.isEqualTo(UHD)
}
@Test
- fun findHighestSupportedQuality_returnsHighestQuality_whenAboveHighest() {
+ fun findNearestHigherSupportedQuality_returnsHighestQuality_whenAboveHighest() {
// Create a size between greater than the max quality (UHD)
val (width2160p, height2160p) = RESOLUTION_2160P
val aboveHighestSize = Size(width2160p + 10, height2160p)
- assertThat(videoCapabilities.findHighestSupportedQualityFor(aboveHighestSize, SDR))
+ assertThat(videoCapabilities.findNearestHigherSupportedQualityFor(aboveHighestSize, SDR))
.isEqualTo(UHD)
}
@Test
- fun findHighestSupportedQuality_returnsLowestQuality_whenBelowLowest() {
+ fun findNearestHigherSupportedQuality_returnsLowestQuality_whenBelowLowest() {
// Create a size below the lowest quality (HD)
val (width720p, height720p) = RESOLUTION_720P
val belowLowestSize = Size(width720p - 10, height720p)
- assertThat(videoCapabilities.findHighestSupportedQualityFor(belowLowestSize, SDR))
+ assertThat(videoCapabilities.findNearestHigherSupportedQualityFor(belowLowestSize, SDR))
.isEqualTo(HD)
}
@Test
- fun findHighestSupportedQuality_returnsExactQuality_whenExactSizeGiven() {
+ fun findNearestHigherSupportedQuality_returnsExactQuality_whenExactSizeGiven() {
val exactSize720p = RESOLUTION_720P
- assertThat(videoCapabilities.findHighestSupportedQualityFor(exactSize720p, SDR))
- .isEqualTo(HD)
+ assertThat(
+ videoCapabilities.findNearestHigherSupportedQualityFor(exactSize720p, SDR)
+ ).isEqualTo(HD)
}
@Test
- fun findHighestSupportedEncoderProfilesFor_returnsHigherProfile() {
+ fun findNearestHigherSupportedEncoderProfilesFor_returnsHigherProfile() {
// Create a size between 720p and 2160p
val (width720p, height720p) = RESOLUTION_720P
val inBetweenSize = Size(width720p + 10, height720p)
- assertThat(videoCapabilities.findHighestSupportedEncoderProfilesFor(inBetweenSize, SDR))
- .isEqualTo(validatedProfiles2160p)
+ assertThat(
+ videoCapabilities.findNearestHigherSupportedEncoderProfilesFor(inBetweenSize, SDR)
+ ).isEqualTo(validatedProfiles2160p)
}
@Test
- fun findHighestSupportedEncoderProfilesFor_returnsHighestProfile_whenAboveHighest() {
+ fun findNearestHigherSupportedEncoderProfilesFor_returnsHighestProfile_whenAboveHighest() {
// Create a size between greater than the max quality (UHD)
val (width2160p, height2160p) = RESOLUTION_2160P
val aboveHighestSize = Size(width2160p + 10, height2160p)
- assertThat(videoCapabilities.findHighestSupportedEncoderProfilesFor(aboveHighestSize, SDR))
- .isEqualTo(validatedProfiles2160p)
+ assertThat(
+ videoCapabilities.findNearestHigherSupportedEncoderProfilesFor(aboveHighestSize, SDR)
+ ).isEqualTo(validatedProfiles2160p)
}
@Test
- fun findHighestSupportedEncoderProfilesFor_returnsLowestProfile_whenBelowLowest() {
+ fun findNearestHigherSupportedEncoderProfilesFor_returnsLowestProfile_whenBelowLowest() {
// Create a size below the lowest quality (HD)
val (width720p, height720p) = RESOLUTION_720P
val belowLowestSize = Size(width720p - 10, height720p)
- assertThat(videoCapabilities.findHighestSupportedEncoderProfilesFor(belowLowestSize, SDR))
- .isEqualTo(validatedProfiles720p)
+ assertThat(
+ videoCapabilities.findNearestHigherSupportedEncoderProfilesFor(belowLowestSize, SDR)
+ ).isEqualTo(validatedProfiles720p)
}
@Test
- fun findHighestSupportedEncoderProfilesFor_returnsExactProfile_whenExactSizeGiven() {
+ fun findNearestHigherSupportedEncoderProfilesFor_returnsExactProfile_whenExactSizeGiven() {
val exactSize720p = RESOLUTION_720P
- assertThat(videoCapabilities.findHighestSupportedEncoderProfilesFor(exactSize720p, SDR))
- .isEqualTo(validatedProfiles720p)
+ assertThat(
+ videoCapabilities.findNearestHigherSupportedEncoderProfilesFor(exactSize720p, SDR)
+ ).isEqualTo(validatedProfiles720p)
}
}
diff --git a/camera/camera-video/src/test/java/androidx/camera/video/VideoCaptureTest.kt b/camera/camera-video/src/test/java/androidx/camera/video/VideoCaptureTest.kt
index 734deea..2700615 100644
--- a/camera/camera-video/src/test/java/androidx/camera/video/VideoCaptureTest.kt
+++ b/camera/camera-video/src/test/java/androidx/camera/video/VideoCaptureTest.kt
@@ -103,7 +103,6 @@
import androidx.camera.video.Quality.NONE
import androidx.camera.video.Quality.SD
import androidx.camera.video.Quality.UHD
-import androidx.camera.video.RecorderVideoCapabilities.CapabilitiesByQuality
import androidx.camera.video.StreamInfo.StreamState
import androidx.camera.video.impl.VideoCaptureConfig
import androidx.camera.video.internal.VideoValidatedEncoderProfilesProxy
@@ -1727,20 +1726,20 @@
return videoCapabilitiesMap[dynamicRange]?.getProfiles(quality)
}
- override fun findHighestSupportedEncoderProfilesFor(
+ override fun findNearestHigherSupportedEncoderProfilesFor(
size: Size,
dynamicRange: DynamicRange
): VideoValidatedEncoderProfilesProxy? {
return videoCapabilitiesMap[dynamicRange]
- ?.findHighestSupportedEncoderProfilesFor(size)
+ ?.findNearestHigherSupportedEncoderProfilesFor(size)
}
- override fun findHighestSupportedQualityFor(
+ override fun findNearestHigherSupportedQualityFor(
size: Size,
dynamicRange: DynamicRange
): Quality {
return videoCapabilitiesMap[dynamicRange]
- ?.findHighestSupportedQualityFor(size) ?: NONE
+ ?.findNearestHigherSupportedQualityFor(size) ?: NONE
}
}
}
diff --git a/camera/camera-view/src/androidTest/java/androidx/camera/view/CameraControllerDeviceTest.kt b/camera/camera-view/src/androidTest/java/androidx/camera/view/CameraControllerDeviceTest.kt
index f70d227..bd5384d4 100644
--- a/camera/camera-view/src/androidTest/java/androidx/camera/view/CameraControllerDeviceTest.kt
+++ b/camera/camera-view/src/androidTest/java/androidx/camera/view/CameraControllerDeviceTest.kt
@@ -106,7 +106,7 @@
fun tearDown() {
instrumentation.runOnMainSync {
controller?.shutDownForTests()
- cameraProvider?.shutdown()?.get(10000, TimeUnit.MILLISECONDS)
+ cameraProvider?.shutdownAsync()?.get(10000, TimeUnit.MILLISECONDS)
cameraProvider = null
}
}
diff --git a/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewViewBitmapTest.kt b/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewViewBitmapTest.kt
index 24c462c..3543148 100644
--- a/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewViewBitmapTest.kt
+++ b/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewViewBitmapTest.kt
@@ -86,7 +86,7 @@
@After
fun tearDown() {
if (cameraProvider != null) {
- cameraProvider!!.shutdown()[10000, TimeUnit.MILLISECONDS]
+ cameraProvider!!.shutdownAsync()[10000, TimeUnit.MILLISECONDS]
cameraProvider = null
}
}
diff --git a/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewViewDeviceTest.kt b/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewViewDeviceTest.kt
index 5c4bc4c..8126ea7 100644
--- a/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewViewDeviceTest.kt
+++ b/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewViewDeviceTest.kt
@@ -128,7 +128,7 @@
surfaceRequest.deferrableSurface.close()
}
if (cameraProvider != null) {
- cameraProvider!!.shutdown()[10000, TimeUnit.MILLISECONDS]
+ cameraProvider!!.shutdownAsync()[10000, TimeUnit.MILLISECONDS]
}
}
diff --git a/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewViewStreamStateTest.kt b/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewViewStreamStateTest.kt
index a25afef..37278af 100644
--- a/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewViewStreamStateTest.kt
+++ b/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewViewStreamStateTest.kt
@@ -102,7 +102,7 @@
fun tearDown() {
if (isSetup) {
instrumentation.runOnMainSync {
- cameraProvider.shutdown()[10000, TimeUnit.MILLISECONDS]
+ cameraProvider.shutdownAsync()[10000, TimeUnit.MILLISECONDS]
}
isSetup = false
}
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/LifecycleCameraController.java b/camera/camera-view/src/main/java/androidx/camera/view/LifecycleCameraController.java
index 2f29672..9511eff 100644
--- a/camera/camera-view/src/main/java/androidx/camera/view/LifecycleCameraController.java
+++ b/camera/camera-view/src/main/java/androidx/camera/view/LifecycleCameraController.java
@@ -155,7 +155,7 @@
@SuppressWarnings("FutureReturnValueIgnored")
void shutDownForTests() {
if (mCameraProvider != null) {
- mCameraProvider.shutdown();
+ mCameraProvider.shutdownAsync();
}
}
}
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/ProcessCameraProviderWrapper.java b/camera/camera-view/src/main/java/androidx/camera/view/ProcessCameraProviderWrapper.java
index b42e391..3102720 100644
--- a/camera/camera-view/src/main/java/androidx/camera/view/ProcessCameraProviderWrapper.java
+++ b/camera/camera-view/src/main/java/androidx/camera/view/ProcessCameraProviderWrapper.java
@@ -64,10 +64,10 @@
@NonNull UseCaseGroup useCaseGroup);
/**
- * Wrapper method for {@link ProcessCameraProvider#shutdown()}.
+ * Wrapper method for {@link ProcessCameraProvider#shutdownAsync()}.
*
*/
@NonNull
@VisibleForTesting
- ListenableFuture<Void> shutdown();
+ ListenableFuture<Void> shutdownAsync();
}
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/ProcessCameraProviderWrapperImpl.java b/camera/camera-view/src/main/java/androidx/camera/view/ProcessCameraProviderWrapperImpl.java
index 30b38fc..7e4c46b 100644
--- a/camera/camera-view/src/main/java/androidx/camera/view/ProcessCameraProviderWrapperImpl.java
+++ b/camera/camera-view/src/main/java/androidx/camera/view/ProcessCameraProviderWrapperImpl.java
@@ -70,7 +70,7 @@
@VisibleForTesting
@NonNull
@Override
- public ListenableFuture<Void> shutdown() {
- return mProcessCameraProvider.shutdown();
+ public ListenableFuture<Void> shutdownAsync() {
+ return mProcessCameraProvider.shutdownAsync();
}
}
diff --git a/camera/camera-view/src/test/java/androidx/camera/view/FakeProcessCameraProviderWrapper.kt b/camera/camera-view/src/test/java/androidx/camera/view/FakeProcessCameraProviderWrapper.kt
index 082b4f4..d0f340b 100644
--- a/camera/camera-view/src/test/java/androidx/camera/view/FakeProcessCameraProviderWrapper.kt
+++ b/camera/camera-view/src/test/java/androidx/camera/view/FakeProcessCameraProviderWrapper.kt
@@ -67,7 +67,7 @@
return camera
}
- override fun shutdown(): ListenableFuture<Void> {
+ override fun shutdownAsync(): ListenableFuture<Void> {
return Futures.immediateFuture(null)
}
}
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/BasicUITest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/BasicUITest.kt
index 9dbed4c..8bb821e 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/BasicUITest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/BasicUITest.kt
@@ -128,7 +128,7 @@
val context = ApplicationProvider.getApplicationContext<Context>()
val cameraProvider = ProcessCameraProvider.getInstance(context)[10, TimeUnit.SECONDS]
- cameraProvider.shutdown()[10, TimeUnit.SECONDS]
+ cameraProvider.shutdownAsync()[10, TimeUnit.SECONDS]
}
@Test
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/BindUnbindUseCasesStressTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/BindUnbindUseCasesStressTest.kt
index ea8344c..a3cd480 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/BindUnbindUseCasesStressTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/BindUnbindUseCasesStressTest.kt
@@ -169,7 +169,7 @@
fun cleanUp(): Unit = runBlocking {
if (::cameraProvider.isInitialized) {
withContext(Dispatchers.Main) {
- cameraProvider.shutdown()[10000, TimeUnit.MILLISECONDS]
+ cameraProvider.shutdownAsync()[10000, TimeUnit.MILLISECONDS]
}
}
}
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/Camera2InteropIntegrationTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/Camera2InteropIntegrationTest.kt
index 51731a5..d2d984c 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/Camera2InteropIntegrationTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/Camera2InteropIntegrationTest.kt
@@ -101,7 +101,7 @@
@After
fun tearDown(): Unit = runBlocking {
processCameraProvider?.apply {
- shutdown().await()
+ shutdownAsync().await()
}
}
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CameraDisconnectTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CameraDisconnectTest.kt
index 9fbbe07..8c208ff 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CameraDisconnectTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CameraDisconnectTest.kt
@@ -102,7 +102,7 @@
}
if (::cameraProvider.isInitialized) {
- cameraProvider.shutdown()[10, TimeUnit.SECONDS]
+ cameraProvider.shutdownAsync()[10, TimeUnit.SECONDS]
}
}
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CameraXInitTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CameraXInitTest.kt
index e861d96..ab2e708 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CameraXInitTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CameraXInitTest.kt
@@ -80,7 +80,7 @@
@After
fun tearDown() {
if (::cameraProvider.isInitialized) {
- cameraProvider.shutdown()[10, TimeUnit.SECONDS]
+ cameraProvider.shutdownAsync()[10, TimeUnit.SECONDS]
}
}
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ExistingActivityLifecycleTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ExistingActivityLifecycleTest.kt
index 2523a1b..3d8d755 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ExistingActivityLifecycleTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ExistingActivityLifecycleTest.kt
@@ -122,7 +122,7 @@
val context = ApplicationProvider.getApplicationContext<Context>()
val cameraProvider = ProcessCameraProvider.getInstance(context)[10, TimeUnit.SECONDS]
- cameraProvider.shutdown()[10, TimeUnit.SECONDS]
+ cameraProvider.shutdownAsync()[10, TimeUnit.SECONDS]
}
// Check if Preview screen is updated or not, after Destroy-Create lifecycle.
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/FlashTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/FlashTest.kt
index 5c7783d..f8b1618 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/FlashTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/FlashTest.kt
@@ -119,7 +119,7 @@
fun tearDown(): Unit = runBlocking {
if (::cameraProvider.isInitialized) {
withContext(Dispatchers.Main) {
- cameraProvider.shutdown()[10, TimeUnit.SECONDS]
+ cameraProvider.shutdownAsync()[10, TimeUnit.SECONDS]
}
}
}
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/FocusMeteringDeviceTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/FocusMeteringDeviceTest.kt
index 2b592ae..f1df41b 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/FocusMeteringDeviceTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/FocusMeteringDeviceTest.kt
@@ -136,7 +136,7 @@
fun tearDown(): Unit = runBlocking {
if (::cameraProvider.isInitialized) {
withContext(Dispatchers.Main) {
- cameraProvider.shutdown()[10, TimeUnit.SECONDS]
+ cameraProvider.shutdownAsync()[10, TimeUnit.SECONDS]
}
}
}
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageAnalysisTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageAnalysisTest.kt
index f97f1f3..200a3d9 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageAnalysisTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageAnalysisTest.kt
@@ -134,7 +134,7 @@
fun tearDown(): Unit = runBlocking {
if (::cameraProvider.isInitialized) {
withContext(Dispatchers.Main) {
- cameraProvider.shutdown()[10, TimeUnit.SECONDS]
+ cameraProvider.shutdownAsync()[10, TimeUnit.SECONDS]
}
}
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageCaptureLatencyTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageCaptureLatencyTest.kt
index 068fa6c..f6e3f3e 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageCaptureLatencyTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageCaptureLatencyTest.kt
@@ -108,7 +108,7 @@
fun tearDown() = runBlocking {
if (::cameraProvider.isInitialized) {
withContext(Dispatchers.Main) {
- cameraProvider.shutdown()
+ cameraProvider.shutdownAsync()
}
}
}
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageCaptureTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageCaptureTest.kt
index e10ff38..e786bc6 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageCaptureTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageCaptureTest.kt
@@ -176,7 +176,7 @@
fun tearDown(): Unit = runBlocking {
if (::cameraProvider.isInitialized) {
withContext(Dispatchers.Main) {
- cameraProvider.shutdown()[10, TimeUnit.SECONDS]
+ cameraProvider.shutdownAsync()[10, TimeUnit.SECONDS]
}
}
}
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageCaptureWithoutStoragePermissionTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageCaptureWithoutStoragePermissionTest.kt
index 8dfc0fd..760ff05 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageCaptureWithoutStoragePermissionTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageCaptureWithoutStoragePermissionTest.kt
@@ -102,7 +102,7 @@
fun tearDown(): Unit = runBlocking {
if (::cameraProvider.isInitialized) {
withContext(Dispatchers.Main) {
- cameraProvider.shutdown()[10, TimeUnit.SECONDS]
+ cameraProvider.shutdownAsync()[10, TimeUnit.SECONDS]
}
}
}
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageProcessingLatencyTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageProcessingLatencyTest.kt
index da056ab..2a4505d 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageProcessingLatencyTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageProcessingLatencyTest.kt
@@ -88,7 +88,7 @@
fun tearDown(): Unit = runBlocking {
if (::cameraProvider.isInitialized) {
withContext(Dispatchers.Main) {
- cameraProvider.shutdown()[10, TimeUnit.SECONDS]
+ cameraProvider.shutdownAsync()[10, TimeUnit.SECONDS]
}
}
}
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/InitializationTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/InitializationTest.kt
index bfb70ef..ca18be5 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/InitializationTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/InitializationTest.kt
@@ -82,7 +82,7 @@
fun shutdownCameraX() {
val context = ApplicationProvider.getApplicationContext<Context>()
val cameraProvider = ProcessCameraProvider.getInstance(context)[10, TimeUnit.SECONDS]
- cameraProvider.shutdown()[10, TimeUnit.SECONDS]
+ cameraProvider.shutdownAsync()[10, TimeUnit.SECONDS]
}
}
@@ -105,7 +105,7 @@
fun tearDown() {
runBlocking {
if (providerResult?.hasProvider() == true) {
- providerResult!!.provider!!.shutdown().await()
+ providerResult!!.provider!!.shutdownAsync().await()
providerResult = null
}
}
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/MLKitBarcodeTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/MLKitBarcodeTest.kt
index 7e041a0..92a002a 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/MLKitBarcodeTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/MLKitBarcodeTest.kt
@@ -106,7 +106,7 @@
fun tearDown(): Unit = runBlocking {
if (::cameraProvider.isInitialized) {
withContext(Dispatchers.Main) {
- cameraProvider.shutdown()[10, TimeUnit.SECONDS]
+ cameraProvider.shutdownAsync()[10, TimeUnit.SECONDS]
}
}
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/OpenCloseCameraStressTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/OpenCloseCameraStressTest.kt
index e244909..6fd2677 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/OpenCloseCameraStressTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/OpenCloseCameraStressTest.kt
@@ -121,7 +121,7 @@
fun cleanUp(): Unit = runBlocking {
if (::cameraProvider.isInitialized) {
withContext(Dispatchers.Main) {
- cameraProvider.shutdown()[10000, TimeUnit.MILLISECONDS]
+ cameraProvider.shutdownAsync()[10000, TimeUnit.MILLISECONDS]
}
}
}
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/OpenCloseCaptureSessionStressTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/OpenCloseCaptureSessionStressTest.kt
index 14b2aa9..cb494fb 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/OpenCloseCaptureSessionStressTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/OpenCloseCaptureSessionStressTest.kt
@@ -124,7 +124,7 @@
fun cleanUp(): Unit = runBlocking {
if (::cameraProvider.isInitialized) {
withContext(Dispatchers.Main) {
- cameraProvider.shutdown()[10000, TimeUnit.MILLISECONDS]
+ cameraProvider.shutdownAsync()[10000, TimeUnit.MILLISECONDS]
}
}
}
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/TakePictureTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/TakePictureTest.kt
index 642ee18..034c7be 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/TakePictureTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/TakePictureTest.kt
@@ -111,7 +111,7 @@
fun tearDown() {
val context = ApplicationProvider.getApplicationContext<Context>()
val cameraProvider = ProcessCameraProvider.getInstance(context)[10, TimeUnit.SECONDS]
- cameraProvider.shutdown()[10, TimeUnit.SECONDS]
+ cameraProvider.shutdownAsync()[10, TimeUnit.SECONDS]
}
// Take a photo, wait for callback via imageSavedIdlingResource resource.
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ToggleButtonUITest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ToggleButtonUITest.kt
index 32ba80b..cc9cada 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ToggleButtonUITest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ToggleButtonUITest.kt
@@ -122,7 +122,7 @@
val context = ApplicationProvider.getApplicationContext<Context>()
val cameraProvider = ProcessCameraProvider.getInstance(context)[10, TimeUnit.SECONDS]
- cameraProvider.shutdown()[10, TimeUnit.SECONDS]
+ cameraProvider.shutdownAsync()[10, TimeUnit.SECONDS]
}
@Test
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/UseCaseCombinationTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/UseCaseCombinationTest.kt
index 467fabe..f9c16d0 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/UseCaseCombinationTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/UseCaseCombinationTest.kt
@@ -109,7 +109,7 @@
fun shutdownCameraX(): Unit = runBlocking {
if (::cameraProvider.isInitialized) {
withContext(Dispatchers.Main) {
- cameraProvider.shutdown()[10, TimeUnit.SECONDS]
+ cameraProvider.shutdownAsync()[10, TimeUnit.SECONDS]
}
}
}
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ZoomControlDeviceTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ZoomControlDeviceTest.kt
index 9386834..46544c91 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ZoomControlDeviceTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ZoomControlDeviceTest.kt
@@ -134,7 +134,7 @@
fun tearDown(): Unit = runBlocking {
if (::cameraProvider.isInitialized) {
withContext(Dispatchers.Main) {
- cameraProvider.shutdown()[10, TimeUnit.SECONDS]
+ cameraProvider.shutdownAsync()[10, TimeUnit.SECONDS]
}
}
}
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/stresstest/ImageCaptureStressTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/stresstest/ImageCaptureStressTest.kt
index 9817e12..935848d 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/stresstest/ImageCaptureStressTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/stresstest/ImageCaptureStressTest.kt
@@ -132,7 +132,7 @@
fun tearDown(): Unit = runBlocking {
if (::cameraProvider.isInitialized) {
withContext(Dispatchers.Main) {
- cameraProvider.shutdown()[10000, TimeUnit.MILLISECONDS]
+ cameraProvider.shutdownAsync()[10000, TimeUnit.MILLISECONDS]
}
}
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/stresstest/LifecycleStatusChangeStressTestBase.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/stresstest/LifecycleStatusChangeStressTestBase.kt
index ad4dfa0..5918784 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/stresstest/LifecycleStatusChangeStressTestBase.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/stresstest/LifecycleStatusChangeStressTestBase.kt
@@ -140,7 +140,7 @@
fun tearDown(): Unit = runBlocking {
if (::cameraProvider.isInitialized) {
withContext(Dispatchers.Main) {
- cameraProvider.shutdown()[10000, TimeUnit.MILLISECONDS]
+ cameraProvider.shutdownAsync()[10000, TimeUnit.MILLISECONDS]
}
}
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/stresstest/SwitchCameraStressTestBase.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/stresstest/SwitchCameraStressTestBase.kt
index 54325a8..feadb0b 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/stresstest/SwitchCameraStressTestBase.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/stresstest/SwitchCameraStressTestBase.kt
@@ -143,7 +143,7 @@
fun tearDown(): Unit = runBlocking {
if (::cameraProvider.isInitialized) {
withContext(Dispatchers.Main) {
- cameraProvider.shutdown()[10000, TimeUnit.MILLISECONDS]
+ cameraProvider.shutdownAsync()[10000, TimeUnit.MILLISECONDS]
}
}
diff --git a/camera/integration-tests/extensionstestapp/proguard-rules.pro b/camera/integration-tests/extensionstestapp/proguard-rules.pro
index 8f6c13c..9a45dea 100644
--- a/camera/integration-tests/extensionstestapp/proguard-rules.pro
+++ b/camera/integration-tests/extensionstestapp/proguard-rules.pro
@@ -3,7 +3,7 @@
-keep class androidx.camera.integration.extensions.CameraExtensionsActivity {*;}
-keepclassmembers class androidx.camera.lifecycle.ProcessCameraProvider {
- ** shutdown();
+ ** shutdownAsync();
}
-keepclassmembers class androidx.camera.extensions.ExtensionsManager {
diff --git a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/AdvancedExtenderValidation.kt b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/AdvancedExtenderValidation.kt
index f112b59..1953233 100644
--- a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/AdvancedExtenderValidation.kt
+++ b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/AdvancedExtenderValidation.kt
@@ -108,7 +108,7 @@
}
withContext(Dispatchers.Main) {
extensionsManager.shutdown()[10000, TimeUnit.MILLISECONDS]
- cameraProvider.shutdown()[10000, TimeUnit.MILLISECONDS]
+ cameraProvider.shutdownAsync()[10000, TimeUnit.MILLISECONDS]
}
}
diff --git a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/BindUnbindUseCasesStressTest.kt b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/BindUnbindUseCasesStressTest.kt
index c721499..421a844 100644
--- a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/BindUnbindUseCasesStressTest.kt
+++ b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/BindUnbindUseCasesStressTest.kt
@@ -117,7 +117,7 @@
fun cleanUp(): Unit = runBlocking {
if (::cameraProvider.isInitialized) {
withContext(Dispatchers.Main) {
- cameraProvider.shutdown()[10000, TimeUnit.MILLISECONDS]
+ cameraProvider.shutdownAsync()[10000, TimeUnit.MILLISECONDS]
}
}
diff --git a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/ClientVersionBackwardCompatibilityTest.kt b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/ClientVersionBackwardCompatibilityTest.kt
index 2acf14d..dc8ad05 100644
--- a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/ClientVersionBackwardCompatibilityTest.kt
+++ b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/ClientVersionBackwardCompatibilityTest.kt
@@ -99,7 +99,7 @@
fun tearDown() = runBlocking {
if (::cameraProvider.isInitialized) {
withContext(Dispatchers.Main) {
- cameraProvider.shutdown()[10, TimeUnit.SECONDS]
+ cameraProvider.shutdownAsync()[10, TimeUnit.SECONDS]
}
}
diff --git a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/ImageAnalysisTest.kt b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/ImageAnalysisTest.kt
index d1e8b64..8814aec 100644
--- a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/ImageAnalysisTest.kt
+++ b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/ImageAnalysisTest.kt
@@ -113,7 +113,7 @@
@After
fun tearDown() = runBlocking(Dispatchers.Main) {
if (::cameraProvider.isInitialized) {
- cameraProvider.shutdown()[10, SECONDS]
+ cameraProvider.shutdownAsync()[10, SECONDS]
}
if (::extensionsManager.isInitialized) {
diff --git a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/ImageCaptureExtenderValidationTest.kt b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/ImageCaptureExtenderValidationTest.kt
index 4933bca..00b5c3e8 100644
--- a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/ImageCaptureExtenderValidationTest.kt
+++ b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/ImageCaptureExtenderValidationTest.kt
@@ -97,7 +97,7 @@
val cameraProvider =
ProcessCameraProvider.getInstance(context)[10000, TimeUnit.MILLISECONDS]
withContext(Dispatchers.Main) {
- cameraProvider.shutdown()
+ cameraProvider.shutdownAsync()
}
val extensionsManager = ExtensionsManager.getInstanceAsync(
diff --git a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/ImageCaptureTest.kt b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/ImageCaptureTest.kt
index d3b4818..e937134 100644
--- a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/ImageCaptureTest.kt
+++ b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/ImageCaptureTest.kt
@@ -95,7 +95,7 @@
fun tearDown() {
val cameraProvider =
ProcessCameraProvider.getInstance(context)[10000, TimeUnit.MILLISECONDS]
- cameraProvider.shutdown()
+ cameraProvider.shutdownAsync()
val extensionsManager = ExtensionsManager.getInstanceAsync(
context,
diff --git a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/LifecycleStatusChangeStressTest.kt b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/LifecycleStatusChangeStressTest.kt
index 4ede281..31fc9c4 100644
--- a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/LifecycleStatusChangeStressTest.kt
+++ b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/LifecycleStatusChangeStressTest.kt
@@ -113,7 +113,7 @@
val cameraProvider =
ProcessCameraProvider.getInstance(context)[10000, TimeUnit.MILLISECONDS]
withContext(Dispatchers.Main) {
- cameraProvider.shutdown()
+ cameraProvider.shutdownAsync()
}
val extensionsManager = ExtensionsManager.getInstanceAsync(
diff --git a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/OpenCloseCameraStressTest.kt b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/OpenCloseCameraStressTest.kt
index 9525ee5..e9fd19c 100644
--- a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/OpenCloseCameraStressTest.kt
+++ b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/OpenCloseCameraStressTest.kt
@@ -109,7 +109,7 @@
fun cleanUp(): Unit = runBlocking {
if (::cameraProvider.isInitialized) {
withContext(Dispatchers.Main) {
- cameraProvider.shutdown()[10000, TimeUnit.MILLISECONDS]
+ cameraProvider.shutdownAsync()[10000, TimeUnit.MILLISECONDS]
}
}
diff --git a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/OpenCloseCaptureSessionStressTest.kt b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/OpenCloseCaptureSessionStressTest.kt
index 52db202..41ac298 100644
--- a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/OpenCloseCaptureSessionStressTest.kt
+++ b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/OpenCloseCaptureSessionStressTest.kt
@@ -165,7 +165,7 @@
fun cleanUp(): Unit = runBlocking {
if (::cameraProvider.isInitialized) {
withContext(Dispatchers.Main) {
- cameraProvider.shutdown()[10000, TimeUnit.MILLISECONDS]
+ cameraProvider.shutdownAsync()[10000, TimeUnit.MILLISECONDS]
}
}
diff --git a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/PreviewExtenderValidationTest.kt b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/PreviewExtenderValidationTest.kt
index 3183597..c32e349 100644
--- a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/PreviewExtenderValidationTest.kt
+++ b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/PreviewExtenderValidationTest.kt
@@ -99,7 +99,7 @@
fun cleanUp(): Unit = runBlocking {
if (::cameraProvider.isInitialized) {
withContext(Dispatchers.Main) {
- cameraProvider.shutdown()[10000, TimeUnit.MILLISECONDS]
+ cameraProvider.shutdownAsync()[10000, TimeUnit.MILLISECONDS]
}
}
diff --git a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/PreviewTest.kt b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/PreviewTest.kt
index 4c8e5bb..e0f0e17 100644
--- a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/PreviewTest.kt
+++ b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/PreviewTest.kt
@@ -94,7 +94,7 @@
fun tearDown() {
val cameraProvider =
ProcessCameraProvider.getInstance(context)[10000, TimeUnit.MILLISECONDS]
- cameraProvider.shutdown()
+ cameraProvider.shutdownAsync()
val extensionsManager = ExtensionsManager.getInstanceAsync(
context,
diff --git a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/SwitchAvailableModesStressTest.kt b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/SwitchAvailableModesStressTest.kt
index 6128afb..3cf5d2f 100644
--- a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/SwitchAvailableModesStressTest.kt
+++ b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/SwitchAvailableModesStressTest.kt
@@ -123,7 +123,7 @@
val cameraProvider =
ProcessCameraProvider.getInstance(context)[10000, TimeUnit.MILLISECONDS]
withContext(Dispatchers.Main) {
- cameraProvider.shutdown()
+ cameraProvider.shutdownAsync()
}
val extensionsManager = ExtensionsManager.getInstanceAsync(
diff --git a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/SwitchCameraStressTest.kt b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/SwitchCameraStressTest.kt
index 98622cf..7a0ee23 100644
--- a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/SwitchCameraStressTest.kt
+++ b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/SwitchCameraStressTest.kt
@@ -126,7 +126,7 @@
fun tearDown() {
val cameraProvider =
ProcessCameraProvider.getInstance(context)[10000, TimeUnit.MILLISECONDS]
- cameraProvider.shutdown()
+ cameraProvider.shutdownAsync()
val extensionsManager = ExtensionsManager.getInstanceAsync(
context,
diff --git a/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/rotations/ImageAnalysisBaseTest.kt b/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/rotations/ImageAnalysisBaseTest.kt
index b284127..4759d4f 100644
--- a/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/rotations/ImageAnalysisBaseTest.kt
+++ b/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/rotations/ImageAnalysisBaseTest.kt
@@ -94,7 +94,7 @@
withContext(Dispatchers.Main) {
val context = ApplicationProvider.getApplicationContext<Context>()
val cameraProvider = ProcessCameraProvider.getInstance(context)[10, TimeUnit.SECONDS]
- cameraProvider.shutdown()[10, TimeUnit.SECONDS]
+ cameraProvider.shutdownAsync()[10, TimeUnit.SECONDS]
}
mDevice.unfreezeRotation()
}
diff --git a/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/rotations/ImageCaptureBaseTest.kt b/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/rotations/ImageCaptureBaseTest.kt
index b82a067d..1fd66f4 100644
--- a/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/rotations/ImageCaptureBaseTest.kt
+++ b/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/rotations/ImageCaptureBaseTest.kt
@@ -116,7 +116,7 @@
withContext(Dispatchers.Main) {
val context = ApplicationProvider.getApplicationContext<Context>()
val cameraProvider = ProcessCameraProvider.getInstance(context)[10, TimeUnit.SECONDS]
- cameraProvider.shutdown()[10, TimeUnit.SECONDS]
+ cameraProvider.shutdownAsync()[10, TimeUnit.SECONDS]
}
mDevice.unfreezeRotation()
}
diff --git a/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/viewpager/ViewPager2ActivityTest.kt b/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/viewpager/ViewPager2ActivityTest.kt
index 22a5ac2..75ed2eb 100644
--- a/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/viewpager/ViewPager2ActivityTest.kt
+++ b/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/viewpager/ViewPager2ActivityTest.kt
@@ -134,7 +134,7 @@
fun tearDown() {
val context = ApplicationProvider.getApplicationContext<Context>()
val cameraProvider = ProcessCameraProvider.getInstance(context)[10, TimeUnit.SECONDS]
- cameraProvider.shutdown()[10, TimeUnit.SECONDS]
+ cameraProvider.shutdownAsync()[10, TimeUnit.SECONDS]
mDevice.unfreezeRotation()
}
diff --git a/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/viewpager/ViewPagerActivityTest.kt b/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/viewpager/ViewPagerActivityTest.kt
index 49a518d..903c988 100644
--- a/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/viewpager/ViewPagerActivityTest.kt
+++ b/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/viewpager/ViewPagerActivityTest.kt
@@ -123,7 +123,7 @@
fun tearDown() {
val context = ApplicationProvider.getApplicationContext<Context>()
val cameraProvider = ProcessCameraProvider.getInstance(context)[10, TimeUnit.SECONDS]
- cameraProvider.shutdown()[10, TimeUnit.SECONDS]
+ cameraProvider.shutdownAsync()[10, TimeUnit.SECONDS]
mDevice.unfreezeRotation()
}
diff --git a/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/CameraControllerFragmentTest.kt b/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/CameraControllerFragmentTest.kt
index 1a866e7..b4d3a88 100644
--- a/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/CameraControllerFragmentTest.kt
+++ b/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/CameraControllerFragmentTest.kt
@@ -135,7 +135,7 @@
}
if (::cameraProvider.isInitialized) {
- cameraProvider.shutdown()[10000, TimeUnit.MILLISECONDS]
+ cameraProvider.shutdownAsync()[10000, TimeUnit.MILLISECONDS]
}
}
diff --git a/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/EffectsFragmentDeviceTest.kt b/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/EffectsFragmentDeviceTest.kt
index e5ca0e7..ae0a33b 100644
--- a/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/EffectsFragmentDeviceTest.kt
+++ b/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/EffectsFragmentDeviceTest.kt
@@ -95,7 +95,7 @@
fragmentScenario.moveToState(Lifecycle.State.DESTROYED)
}
if (::cameraProvider.isInitialized) {
- cameraProvider.shutdown()[10000, TimeUnit.MILLISECONDS]
+ cameraProvider.shutdownAsync()[10000, TimeUnit.MILLISECONDS]
}
}
diff --git a/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/PreviewViewFragmentTest.kt b/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/PreviewViewFragmentTest.kt
index f72b2f1..b8fdde0 100644
--- a/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/PreviewViewFragmentTest.kt
+++ b/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/PreviewViewFragmentTest.kt
@@ -93,7 +93,7 @@
if (scenario != null) {
scenario!!.moveToState(Lifecycle.State.DESTROYED)
}
- ProcessCameraProvider.getInstance(context)[10, TimeUnit.SECONDS].shutdown()
+ ProcessCameraProvider.getInstance(context)[10, TimeUnit.SECONDS].shutdownAsync()
}
@Test
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/Draggable2DTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/Draggable2DTest.kt
new file mode 100644
index 0000000..c7e679c
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/Draggable2DTest.kt
@@ -0,0 +1,958 @@
+/*
+ * Copyright 2023 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.foundation
+
+import androidx.compose.foundation.gestures.Draggable2DState
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.draggable
+import androidx.compose.foundation.gestures.draggable2D
+import androidx.compose.foundation.gestures.rememberDraggable2DState
+import androidx.compose.foundation.gestures.rememberDraggableState
+import androidx.compose.foundation.interaction.DragInteraction
+import androidx.compose.foundation.interaction.Interaction
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.composed
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.platform.InspectableValue
+import androidx.compose.ui.platform.LocalViewConfiguration
+import androidx.compose.ui.platform.ViewConfiguration
+import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performMouseInput
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.swipeWithVelocity
+import androidx.compose.ui.unit.Velocity
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth.assertThat
+import java.lang.Float.NaN
+import kotlin.test.Ignore
+import kotlin.test.assertTrue
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import org.junit.After
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class Draggable2DTest {
+
+ @get:Rule
+ val rule = createComposeRule()
+
+ private val draggable2DBoxTag = "drag2DTag"
+
+ @Before
+ fun before() {
+ isDebugInspectorInfoEnabled = true
+ }
+
+ @After
+ fun after() {
+ isDebugInspectorInfoEnabled = false
+ }
+
+ @Test
+ fun draggable2D_2d_drag() {
+ var total = Offset.Zero
+ setDraggable2DContent {
+ Modifier.draggable2D { total += it }
+ }
+ rule.onNodeWithTag(draggable2DBoxTag).performTouchInput {
+ down(center)
+ moveBy(Offset(100f, 100f))
+ }
+ rule.runOnIdle {
+ assertThat(total.x).isGreaterThan(0)
+ assertThat(total.y).isGreaterThan(0)
+ }
+ rule.onNodeWithTag(draggable2DBoxTag).performTouchInput {
+ moveBy(Offset(-100f, -100f))
+ }
+ rule.runOnIdle {
+ assertThat(total.x).isLessThan(0.01f)
+ assertThat(total.y).isLessThan(0.01f)
+ }
+ }
+
+ @Test
+ fun draggable2D_startStop() {
+ var startTrigger = 0f
+ var stopTrigger = 0f
+ setDraggable2DContent {
+ Modifier.draggable2D(
+ onDragStarted = { startTrigger += 1 },
+ onDragStopped = { stopTrigger += 1 }
+ ) {}
+ }
+ rule.runOnIdle {
+ assertThat(startTrigger).isEqualTo(0)
+ assertThat(stopTrigger).isEqualTo(0)
+ }
+ rule.onNodeWithTag(draggable2DBoxTag).performTouchInput {
+ down(center)
+ moveBy(Offset(100f, 100f))
+ up()
+ }
+ rule.runOnIdle {
+ assertThat(startTrigger).isEqualTo(1)
+ assertThat(stopTrigger).isEqualTo(1)
+ }
+ }
+
+ @Test
+ fun draggable2D_disableWontCallLambda() {
+ var total = Offset.Zero
+ val enabled = mutableStateOf(true)
+ setDraggable2DContent {
+ Modifier.draggable2D(enabled = enabled.value) {
+ total += it
+ }
+ }
+ rule.onNodeWithTag(draggable2DBoxTag).performTouchInput {
+ down(center)
+ moveBy(Offset(100f, 100f))
+ }
+ val prevTotal = rule.runOnIdle {
+ assertThat(total.x).isGreaterThan(0f)
+ assertThat(total.y).isGreaterThan(0f)
+ enabled.value = false
+ total
+ }
+ rule.onNodeWithTag(draggable2DBoxTag).performTouchInput {
+ moveBy(Offset(100f, 100f))
+ }
+ rule.runOnIdle {
+ assertThat(total).isEqualTo(prevTotal)
+ }
+ }
+
+ @Test
+ fun draggable2D_velocityProxy() {
+ var velocityTriggered = Velocity.Zero
+ setDraggable2DContent {
+ Modifier.draggable2D(
+ onDragStopped = { velocityTriggered = it }
+ ) {}
+ }
+ rule.onNodeWithTag(draggable2DBoxTag).performTouchInput {
+ down(center)
+ moveBy(Offset(100f, 100f))
+ }
+ rule.runOnIdle {
+ assertThat(velocityTriggered.x - 112f).isLessThan(0.1f)
+ assertThat(velocityTriggered.y - 112f).isLessThan(0.1f)
+ }
+ }
+
+ @Test
+ fun draggable2D_startWithoutSlop_ifAnimating() {
+ var total = Offset.Zero
+ setDraggable2DContent {
+ Modifier.draggable2D(startDragImmediately = true) {
+ total += it
+ }
+ }
+ rule.onNodeWithTag(draggable2DBoxTag).performTouchInput {
+ down(center)
+ moveBy(Offset(100f, 100f))
+ }
+ rule.runOnIdle {
+ // should be exactly 100 as there's no slop
+ assertThat(total).isEqualTo(Offset(100f, 100f))
+ }
+ }
+
+ @Test
+ @Ignore("b/303237627")
+ fun draggable2D_cancel_callsDragStop() {
+ var total = Offset.Zero
+ var dragStopped = 0f
+ setDraggable2DContent {
+ if (total.x < 20f) {
+ Modifier.draggable2D(
+ onDragStopped = { dragStopped += 1 },
+ startDragImmediately = true
+ ) { total += it }
+ } else Modifier
+ }
+ rule.onNodeWithTag(draggable2DBoxTag).performTouchInput {
+ down(center)
+ moveBy(Offset(100f, 100f))
+ }
+ rule.runOnIdle {
+ assertThat(total.x).isGreaterThan(0f)
+ assertThat(total.y).isGreaterThan(0f)
+ assertThat(dragStopped).isEqualTo(1f)
+ }
+ }
+
+ @OptIn(ExperimentalTestApi::class)
+ @Test
+ fun draggable2D_immediateStart_callsStopWithoutSlop() {
+ var total = Offset.Zero
+ var dragStopped = 0f
+ var dragStarted = 0f
+ setDraggable2DContent {
+ Modifier.draggable2D(
+ onDragStopped = { dragStopped += 1 },
+ onDragStarted = { dragStarted += 1 },
+ startDragImmediately = true
+ ) { total += it }
+ }
+ rule.onNodeWithTag(draggable2DBoxTag).performMouseInput {
+ this.press()
+ }
+ rule.runOnIdle {
+ assertThat(dragStarted).isEqualTo(1f)
+ }
+ rule.onNodeWithTag(draggable2DBoxTag).performMouseInput {
+ this.release()
+ }
+ rule.runOnIdle {
+ assertThat(dragStopped).isEqualTo(1f)
+ }
+ }
+
+ @Test
+ fun draggable2D_callsDragStop_whenNewState() {
+ var dragStopped = 0f
+ val state = mutableStateOf(Draggable2DState { })
+ setDraggable2DContent {
+ Modifier.draggable2D(
+ onDragStopped = { dragStopped += 1 },
+ state = state.value
+ )
+ }
+ rule.onNodeWithTag(draggable2DBoxTag).performTouchInput {
+ down(center)
+ moveBy(Offset(100f, 100f))
+ }
+ rule.runOnIdle {
+ assertThat(dragStopped).isEqualTo(0f)
+ state.value = Draggable2DState { /* Do nothing */ }
+ }
+ rule.runOnIdle {
+ assertThat(dragStopped).isEqualTo(1f)
+ }
+ }
+
+ @Test
+ fun draggable2D_callsDragStop_whenDisabled() {
+ var dragStopped = 0f
+ var enabled by mutableStateOf(true)
+ setDraggable2DContent {
+ Modifier.draggable2D(
+ onDragStopped = { dragStopped += 1 },
+ enabled = enabled,
+ onDrag = {}
+ )
+ }
+ rule.onNodeWithTag(draggable2DBoxTag).performTouchInput {
+ down(center)
+ moveBy(Offset(100f, 100f))
+ }
+ rule.runOnIdle {
+ assertThat(dragStopped).isEqualTo(0f)
+ enabled = false
+ }
+ rule.runOnIdle {
+ assertThat(dragStopped).isEqualTo(1f)
+ }
+ }
+
+ @Test
+ fun draggable2D_callsDragStop_whenNewReverseDirection() {
+ var dragStopped = 0f
+ var reverseDirection by mutableStateOf(false)
+ setDraggable2DContent {
+ Modifier.draggable2D(
+ onDragStopped = { dragStopped += 1 },
+ onDrag = {},
+ reverseDirection = reverseDirection
+ )
+ }
+ rule.onNodeWithTag(draggable2DBoxTag).performTouchInput {
+ down(center)
+ moveBy(Offset(100f, 100f))
+ }
+ rule.runOnIdle {
+ assertThat(dragStopped).isEqualTo(0f)
+ reverseDirection = true
+ }
+ rule.runOnIdle {
+ assertThat(dragStopped).isEqualTo(1f)
+ }
+ }
+
+ @Test
+ fun draggable2D_updates_startDragImmediately() {
+ var total = Offset.Zero
+ var startDragImmediately by mutableStateOf(false)
+ var touchSlop: Float? = null
+ setDraggable2DContent {
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ Modifier.draggable2D(
+ onDrag = { total += it },
+ startDragImmediately = startDragImmediately
+ )
+ }
+ val delta = touchSlop!! / 2f
+ rule.onNodeWithTag(draggable2DBoxTag).performTouchInput {
+ down(center)
+ moveBy(Offset(delta, delta))
+ up()
+ }
+ rule.runOnIdle {
+ assertThat(total).isEqualTo(Offset.Zero)
+ startDragImmediately = true
+ }
+ rule.onNodeWithTag(draggable2DBoxTag).performTouchInput {
+ down(center)
+ moveBy(Offset(delta, delta))
+ up()
+ }
+ rule.runOnIdle {
+ assertThat(total).isEqualTo(Offset(delta, delta))
+ }
+ }
+
+ @Test
+ fun draggable2D_updates_onDragStarted() {
+ var total = Offset.Zero
+ var onDragStarted1Calls = 0
+ var onDragStarted2Calls = 0
+ var onDragStarted: (Offset) -> Unit by mutableStateOf({ onDragStarted1Calls += 1 })
+ setDraggable2DContent {
+ Modifier.draggable2D(
+ onDrag = { total += it },
+ onDragStarted = onDragStarted
+ )
+ }
+ rule.onNodeWithTag(draggable2DBoxTag).performTouchInput {
+ down(center)
+ moveBy(Offset(100f, 100f))
+ up()
+ }
+ rule.runOnIdle {
+ assertThat(onDragStarted1Calls).isEqualTo(1)
+ assertThat(onDragStarted2Calls).isEqualTo(0)
+ onDragStarted = { onDragStarted2Calls += 1 }
+ }
+ rule.onNodeWithTag(draggable2DBoxTag).performTouchInput {
+ down(center)
+ moveBy(Offset(100f, 100f))
+ up()
+ }
+ rule.runOnIdle {
+ assertThat(onDragStarted1Calls).isEqualTo(1)
+ assertThat(onDragStarted2Calls).isEqualTo(1)
+ }
+ }
+
+ @Test
+ fun draggable2D_updates_onDragStopped() {
+ var total = Offset.Zero
+ var onDragStopped1Calls = 0
+ var onDragStopped2Calls = 0
+ var onDragStopped: (Velocity) -> Unit by mutableStateOf({ onDragStopped1Calls += 1 })
+ setDraggable2DContent {
+ Modifier.draggable2D(
+ onDrag = { total += it },
+ onDragStopped = onDragStopped
+ )
+ }
+ rule.onNodeWithTag(draggable2DBoxTag).performTouchInput {
+ down(center)
+ moveBy(Offset(100f, 100f))
+ }
+ rule.runOnIdle {
+ assertThat(onDragStopped1Calls).isEqualTo(0)
+ assertThat(onDragStopped2Calls).isEqualTo(0)
+ onDragStopped = { onDragStopped2Calls += 1 }
+ }
+ rule.onNodeWithTag(draggable2DBoxTag).performTouchInput {
+ up()
+ }
+ rule.runOnIdle {
+ // We changed the lambda before we ever stopped dragging, so only the new one should be
+ // called
+ assertThat(onDragStopped1Calls).isEqualTo(0)
+ assertThat(onDragStopped2Calls).isEqualTo(1)
+ }
+ }
+
+ @Test
+ fun draggable2D_resumesNormally_whenInterruptedWithHigherPriority() = runBlocking {
+ var total = Offset.Zero
+ var dragStopped = 0f
+ val state = Draggable2DState { total += it }
+ setDraggable2DContent {
+ Modifier.draggable2D(
+ onDragStopped = { dragStopped += 1 },
+ state = state
+ )
+ }
+ rule.onNodeWithTag(draggable2DBoxTag).performTouchInput {
+ down(center)
+ moveBy(Offset(100f, 100f))
+ }
+ val prevTotal = rule.runOnIdle {
+ assertThat(total.x).isGreaterThan(0f)
+ assertThat(total.y).isGreaterThan(0f)
+ total
+ }
+ state.drag(MutatePriority.PreventUserInput) {
+ dragBy(Offset(123f, 123f))
+ }
+ rule.runOnIdle {
+ assertThat(total).isEqualTo(prevTotal + Offset(123f, 123f))
+ assertThat(dragStopped).isEqualTo(1f)
+ }
+ rule.onNodeWithTag(draggable2DBoxTag).performTouchInput {
+ up()
+ down(center)
+ moveBy(Offset(100f, 100f))
+ up()
+ }
+ rule.runOnIdle {
+ assertThat(total.x).isGreaterThan(prevTotal.x + 123f)
+ assertThat(total.y).isGreaterThan(prevTotal.y + 123f)
+ }
+ }
+
+ @Test
+ fun draggable2D_noNestedDrag() {
+ var innerDrag = Offset.Zero
+ var outerDrag = Offset.Zero
+ rule.setContent {
+ Box {
+ Box(
+ contentAlignment = Alignment.Center,
+ modifier = Modifier
+ .testTag(draggable2DBoxTag)
+ .size(300.dp)
+ .draggable2D {
+ outerDrag += it
+ }
+ ) {
+ Box(
+ modifier = Modifier
+ .size(300.dp)
+ .draggable2D { delta ->
+ innerDrag += delta / 2f
+ }
+ )
+ }
+ }
+ }
+ rule.onNodeWithTag(draggable2DBoxTag).performTouchInput {
+ down(center)
+ moveBy(Offset(200f, 200f))
+ }
+ rule.runOnIdle {
+ assertThat(innerDrag.x).isGreaterThan(0f)
+ assertThat(innerDrag.y).isGreaterThan(0f)
+ // draggable2D doesn't participate in nested scrolling, so outer should receive 0 events
+ assertThat(outerDrag).isEqualTo(Offset.Zero)
+ }
+ }
+
+ @Test
+ fun draggable2D_noNestedDragWithDraggable() {
+ var innerDrag2D = Offset.Zero
+ var outerDrag = 0f
+ rule.setContent {
+ Box {
+ Box(
+ contentAlignment = Alignment.Center,
+ modifier = Modifier
+ .testTag(draggable2DBoxTag)
+ .size(300.dp)
+ .draggable(Orientation.Horizontal) {
+ outerDrag += it
+ }
+ ) {
+ Box(
+ modifier = Modifier
+ .size(300.dp)
+ .draggable2D { delta ->
+ innerDrag2D += delta / 2f
+ }
+ )
+ }
+ }
+ }
+ rule.onNodeWithTag(draggable2DBoxTag).performTouchInput {
+ down(center)
+ moveBy(Offset(200f, 200f))
+ }
+ rule.runOnIdle {
+ assertThat(innerDrag2D.x).isGreaterThan(0f)
+ assertThat(innerDrag2D.y).isGreaterThan(0f)
+ // draggable2D doesn't participate in nested scrolling, so outer should receive 0 events
+ assertThat(outerDrag).isEqualTo(0f)
+ }
+ }
+
+ @Test
+ fun draggable2D_interactionSource() {
+ val interactionSource = MutableInteractionSource()
+
+ var scope: CoroutineScope? = null
+
+ setDraggable2DContent {
+ scope = rememberCoroutineScope()
+ Modifier.draggable2D(
+ interactionSource = interactionSource
+ ) {}
+ }
+
+ val interactions = mutableListOf<Interaction>()
+
+ scope!!.launch {
+ interactionSource.interactions.collect { interactions.add(it) }
+ }
+
+ rule.runOnIdle {
+ assertThat(interactions).isEmpty()
+ }
+
+ rule.onNodeWithTag(draggable2DBoxTag)
+ .performTouchInput {
+ down(Offset(visibleSize.width / 4f, visibleSize.height / 4f))
+ moveBy(Offset(visibleSize.width / 2f, visibleSize.height / 2f))
+ }
+
+ rule.runOnIdle {
+ assertThat(interactions).hasSize(1)
+ assertThat(interactions.first()).isInstanceOf(DragInteraction.Start::class.java)
+ }
+
+ rule.onNodeWithTag(draggable2DBoxTag)
+ .performTouchInput {
+ up()
+ }
+
+ rule.runOnIdle {
+ assertThat(interactions).hasSize(2)
+ assertThat(interactions.first()).isInstanceOf(DragInteraction.Start::class.java)
+ assertThat(interactions[1]).isInstanceOf(DragInteraction.Stop::class.java)
+ assertThat((interactions[1] as DragInteraction.Stop).start)
+ .isEqualTo(interactions[0])
+ }
+ }
+
+ @Test
+ fun draggable2D_interactionSource_resetWhenDisposed() {
+ val interactionSource = MutableInteractionSource()
+ var emitDraggableBox by mutableStateOf(true)
+
+ var scope: CoroutineScope? = null
+
+ rule.setContent {
+ scope = rememberCoroutineScope()
+ Box {
+ if (emitDraggableBox) {
+ Box(
+ modifier = Modifier
+ .testTag(draggable2DBoxTag)
+ .size(100.dp)
+ .draggable2D(
+ interactionSource = interactionSource
+ ) {}
+ )
+ }
+ }
+ }
+
+ val interactions = mutableListOf<Interaction>()
+
+ scope!!.launch {
+ interactionSource.interactions.collect { interactions.add(it) }
+ }
+
+ rule.runOnIdle {
+ assertThat(interactions).isEmpty()
+ }
+
+ rule.onNodeWithTag(draggable2DBoxTag)
+ .performTouchInput {
+ down(Offset(visibleSize.width / 4f, visibleSize.height / 4f))
+ moveBy(Offset(visibleSize.width / 2f, visibleSize.height / 2f))
+ }
+
+ rule.runOnIdle {
+ assertThat(interactions).hasSize(1)
+ assertThat(interactions.first()).isInstanceOf(DragInteraction.Start::class.java)
+ }
+
+ // Dispose draggable
+ rule.runOnIdle {
+ emitDraggableBox = false
+ }
+
+ rule.runOnIdle {
+ assertThat(interactions).hasSize(2)
+ assertThat(interactions.first()).isInstanceOf(DragInteraction.Start::class.java)
+ assertThat(interactions[1]).isInstanceOf(DragInteraction.Cancel::class.java)
+ assertThat((interactions[1] as DragInteraction.Cancel).start)
+ .isEqualTo(interactions[0])
+ }
+ }
+
+ @Test
+ fun draggable2D_interactionSource_resetWhenEnabledChanged() {
+ val interactionSource = MutableInteractionSource()
+ val enabledState = mutableStateOf(true)
+
+ var scope: CoroutineScope? = null
+
+ setDraggable2DContent {
+ scope = rememberCoroutineScope()
+ Modifier.draggable2D(
+ enabled = enabledState.value,
+ interactionSource = interactionSource
+ ) {}
+ }
+
+ val interactions = mutableListOf<Interaction>()
+
+ scope!!.launch {
+ interactionSource.interactions.collect { interactions.add(it) }
+ }
+
+ rule.runOnIdle {
+ assertThat(interactions).isEmpty()
+ }
+
+ rule.onNodeWithTag(draggable2DBoxTag)
+ .performTouchInput {
+ down(Offset(visibleSize.width / 4f, visibleSize.height / 4f))
+ moveBy(Offset(visibleSize.width / 2f, visibleSize.height / 2f))
+ }
+
+ rule.runOnIdle {
+ assertThat(interactions).hasSize(1)
+ assertThat(interactions.first()).isInstanceOf(DragInteraction.Start::class.java)
+ }
+
+ rule.runOnIdle {
+ enabledState.value = false
+ }
+
+ rule.runOnIdle {
+ assertThat(interactions).hasSize(2)
+ assertThat(interactions.first()).isInstanceOf(DragInteraction.Start::class.java)
+ assertThat(interactions[1]).isInstanceOf(DragInteraction.Cancel::class.java)
+ assertThat((interactions[1] as DragInteraction.Cancel).start)
+ .isEqualTo(interactions[0])
+ }
+ }
+
+ @Test
+ fun draggable2D_velocityIsLimitedByViewConfiguration() {
+ var latestVelocity = Velocity.Zero
+ val maxVelocity = 1000f
+
+ rule.setContent {
+ val viewConfig = LocalViewConfiguration.current
+ val newConfig = object : ViewConfiguration by viewConfig {
+ override val maximumFlingVelocity: Int
+ get() = maxVelocity.toInt()
+ }
+ CompositionLocalProvider(LocalViewConfiguration provides newConfig) {
+ Box {
+ Box(
+ modifier = Modifier
+ .testTag(draggable2DBoxTag)
+ .size(100.dp)
+ .draggable2D(
+ onDragStopped = {
+ latestVelocity = it
+ }) {}
+ )
+ }
+ }
+ }
+
+ rule.onNodeWithTag(draggable2DBoxTag).performTouchInput {
+ this.swipeWithVelocity(
+ start = this.topLeft,
+ end = this.bottomRight,
+ endVelocity = 2000f
+ )
+ }
+ rule.runOnIdle {
+ assertThat(latestVelocity).isEqualTo(Velocity(maxVelocity, maxVelocity))
+ }
+ }
+
+ @Test
+ fun draggable2D_interactionSource_resetWhenInteractionSourceChanged() {
+ val interactionSource1 = MutableInteractionSource()
+ val interactionSource2 = MutableInteractionSource()
+ val interactionSourceState = mutableStateOf(interactionSource1)
+
+ var scope: CoroutineScope? = null
+
+ setDraggable2DContent {
+ scope = rememberCoroutineScope()
+ Modifier.draggable2D(
+ interactionSource = interactionSourceState.value
+ ) {}
+ }
+
+ val interactions1 = mutableListOf<Interaction>()
+ val interactions2 = mutableListOf<Interaction>()
+
+ scope!!.launch {
+ interactionSource1.interactions.collect { interactions1.add(it) }
+ }
+
+ rule.runOnIdle {
+ assertThat(interactions1).isEmpty()
+ assertThat(interactions2).isEmpty()
+ }
+
+ rule.onNodeWithTag(draggable2DBoxTag)
+ .performTouchInput {
+ down(Offset(visibleSize.width / 4f, visibleSize.height / 4f))
+ moveBy(Offset(visibleSize.width / 2f, visibleSize.height / 2f))
+ }
+
+ rule.runOnIdle {
+ assertThat(interactions1).hasSize(1)
+ assertThat(interactions1.first()).isInstanceOf(DragInteraction.Start::class.java)
+ assertThat(interactions2).isEmpty()
+ }
+
+ rule.runOnIdle {
+ interactionSourceState.value = interactionSource2
+ }
+
+ rule.runOnIdle {
+ assertThat(interactions1).hasSize(2)
+ assertThat(interactions1.first()).isInstanceOf(DragInteraction.Start::class.java)
+ assertThat(interactions1[1]).isInstanceOf(DragInteraction.Cancel::class.java)
+ assertThat((interactions1[1] as DragInteraction.Cancel).start)
+ .isEqualTo(interactions1[0])
+ // Currently we don't emit drag start for an in progress drag, but this might change
+ // in the future.
+ assertThat(interactions2).isEmpty()
+ }
+ }
+
+ @OptIn(ExperimentalTestApi::class)
+ @Test
+ fun draggable2D_cancelMidDown_shouldContinueWithNextDown() {
+ var total = Offset.Zero
+
+ setDraggable2DContent {
+ Modifier.draggable2D(startDragImmediately = true) { total += it }
+ }
+
+ rule.onNodeWithTag(draggable2DBoxTag).performMouseInput {
+ enter()
+ exit()
+ }
+
+ assertThat(total).isEqualTo(Offset.Zero)
+ rule.onNodeWithTag(draggable2DBoxTag).performTouchInput {
+ down(center)
+ cancel()
+ }
+
+ assertThat(total).isEqualTo(Offset.Zero)
+ rule.onNodeWithTag(draggable2DBoxTag).performMouseInput {
+ enter()
+ exit()
+ }
+
+ assertThat(total).isEqualTo(Offset.Zero)
+ rule.onNodeWithTag(draggable2DBoxTag).performTouchInput {
+ down(center)
+ moveBy(Offset(100f, 100f))
+ }
+
+ assertThat(total.x).isGreaterThan(0f)
+ assertThat(total.y).isGreaterThan(0f)
+ }
+
+ @Test
+ fun draggable2D_noMomentumDragging_onDragStopped_shouldGenerateZeroVelocity() {
+ val delta = -10f
+ var flingVelocity = Velocity(NaN, NaN)
+ setDraggable2DContent {
+ Modifier.draggable2D(
+ state = rememberDraggable2DState {},
+ onDragStopped = { velocity ->
+ flingVelocity = velocity
+ }
+ )
+ }
+ // Drag, stop and release. The resulting velocity should be zero because we lost the
+ // gesture momentum.
+ rule.onNodeWithTag(draggable2DBoxTag).performTouchInput {
+ down(center)
+ // generate various move events
+ repeat(30) {
+ moveBy(Offset(delta, delta), delayMillis = 16L)
+ }
+ // stop for a moment
+ advanceEventTime(3000L)
+ up()
+ }
+ rule.runOnIdle {
+ Assert.assertEquals(Velocity.Zero, flingVelocity)
+ }
+ }
+
+ @Test
+ fun onDragStopped_inputChanged_shouldNotCancelScope() {
+ val enabled = mutableStateOf(true)
+ lateinit var runningJob: Job
+ rule.setContent {
+ Box(
+ modifier = Modifier
+ .testTag(draggable2DBoxTag)
+ .size(100.dp)
+ .draggable2D(
+ enabled = enabled.value,
+ state = rememberDraggable2DState { },
+ onDragStopped = { _ ->
+ runningJob = launch { delay(10_000L) } // long running operation
+ }
+ )
+ )
+ }
+
+ rule.onNodeWithTag(draggable2DBoxTag).performTouchInput {
+ down(center)
+ moveBy(Offset(100f, 100f))
+ up()
+ }
+
+ rule.runOnIdle {
+ enabled.value = false // cancels pointer input scope
+ }
+
+ rule.runOnIdle {
+ assertTrue { runningJob.isActive } // check if scope is still active
+ }
+ }
+
+ @Test
+ fun testInspectableValue() {
+ rule.setContent {
+ val modifier = Modifier.draggable2D(
+ state = rememberDraggable2DState { }
+ ) as InspectableValue
+ assertThat(modifier.nameFallback).isEqualTo("draggable2D")
+ assertThat(modifier.valueOverride).isNull()
+ assertThat(modifier.inspectableElements.map { it.name }.asIterable()).containsExactly(
+ "enabled",
+ "canDrag",
+ "reverseDirection",
+ "interactionSource",
+ "startDragImmediately",
+ "onDragStarted",
+ "onDragStopped",
+ "state",
+ )
+ }
+ }
+
+ private fun setDraggable2DContent(draggable2DFactory: @Composable () -> Modifier) {
+ rule.setContent {
+ Box {
+ val draggable2D = draggable2DFactory()
+ Box(
+ modifier = Modifier
+ .testTag(draggable2DBoxTag)
+ .size(100.dp)
+ .then(draggable2D)
+ )
+ }
+ }
+ }
+
+ private fun Modifier.draggable2D(
+ enabled: Boolean = true,
+ reverseDirection: Boolean = false,
+ interactionSource: MutableInteractionSource? = null,
+ startDragImmediately: Boolean = false,
+ onDragStarted: (startedPosition: Offset) -> Unit = {},
+ onDragStopped: (velocity: Velocity) -> Unit = {},
+ onDrag: (Offset) -> Unit
+ ): Modifier = composed {
+ val state = rememberDraggable2DState(onDrag)
+ draggable2D(
+ enabled = enabled,
+ reverseDirection = reverseDirection,
+ interactionSource = interactionSource,
+ startDragImmediately = startDragImmediately,
+ onDragStarted = { onDragStarted(it) },
+ onDragStopped = { onDragStopped(it) },
+ state = state
+ )
+ }
+
+ private fun Modifier.draggable(
+ orientation: Orientation,
+ enabled: Boolean = true,
+ reverseDirection: Boolean = false,
+ interactionSource: MutableInteractionSource? = null,
+ startDragImmediately: Boolean = false,
+ onDragStarted: (startedPosition: Offset) -> Unit = {},
+ onDragStopped: (velocity: Float) -> Unit = {},
+ onDrag: (Float) -> Unit
+ ): Modifier = composed {
+ val state = rememberDraggableState(onDrag)
+ draggable(
+ orientation = orientation,
+ enabled = enabled,
+ reverseDirection = reverseDirection,
+ interactionSource = interactionSource,
+ startDragImmediately = startDragImmediately,
+ onDragStarted = { onDragStarted(it) },
+ onDragStopped = { onDragStopped(it) },
+ state = state
+ )
+ }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/DraggableTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/DraggableTest.kt
index 1ec698c..3a20285 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/DraggableTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/DraggableTest.kt
@@ -51,6 +51,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import com.google.common.truth.Truth.assertThat
+import kotlin.test.Ignore
import kotlin.test.assertTrue
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
@@ -162,6 +163,7 @@
}
@Test
+ @Ignore("b/303237627")
fun draggable_verticalDrag_newState() {
var total = 0f
setDraggableContent {
@@ -308,6 +310,7 @@
}
@Test
+ @Ignore("b/303237627")
fun draggable_cancel_callsDragStop() {
var total = 0f
var dragStopped = 0f
@@ -321,14 +324,10 @@
} else Modifier
}
rule.onNodeWithTag(draggableBoxTag).performTouchInput {
- this.swipe(
- start = this.center,
- end = Offset(this.center.x + 100f, this.center.y),
- durationMillis = 100
- )
+ down(center)
+ moveBy(Offset(100f, 100f))
}
rule.runOnIdle {
- // should be exactly 100 as there's no slop
assertThat(total).isGreaterThan(0f)
assertThat(dragStopped).isEqualTo(1f)
}
@@ -562,6 +561,7 @@
}
@Test
+ @Ignore("b/303237627")
fun draggable_resumesNormally_whenInterruptedWithHigherPriority() = runBlocking {
var total = 0f
var dragStopped = 0f
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Draggable2d.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Draggable2D.kt
similarity index 82%
rename from compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Draggable2d.kt
rename to compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Draggable2D.kt
index 5091a99..9259cd6 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Draggable2d.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Draggable2D.kt
@@ -34,10 +34,10 @@
import kotlinx.coroutines.coroutineScope
/**
- * State of Draggable2d. Allows for granular control of how deltas are consumed by the user as well
+ * State of Draggable2D. Allows for granular control of how deltas are consumed by the user as well
* as to write custom drag methods using [drag] suspend function.
*/
-internal interface Draggable2dState {
+internal interface Draggable2DState {
/**
* Call this function to take control of drag logic.
*
@@ -53,7 +53,7 @@
*/
suspend fun drag(
dragPriority: MutatePriority = MutatePriority.Default,
- block: suspend Drag2dScope.() -> Unit
+ block: suspend Drag2DScope.() -> Unit
)
/**
@@ -61,10 +61,10 @@
*
* **Note:** unlike [drag], dispatching any delta with this method will bypass scrolling of
* any priority. This method will also ignore `reverseDirection` and other parameters set in
- * draggable2d.
+ * draggable2D.
*
* This method is used internally for low level operations, allowing implementers of
- * [Draggable2dState] influence the consumption as suits them.
+ * [Draggable2DState] influence the consumption as suits them.
* Manually dispatching delta via this method will likely result in a bad user experience,
* you must prefer [drag] method over this one.
*
@@ -76,7 +76,7 @@
/**
* Scope used for suspending drag blocks
*/
-internal interface Drag2dScope {
+internal interface Drag2DScope {
/**
* Attempts to drag by [pixels] px.
*/
@@ -84,82 +84,82 @@
}
/**
- * Default implementation of [Draggable2dState] interface that allows to pass a simple action that
+ * Default implementation of [Draggable2DState] interface that allows to pass a simple action that
* will be invoked when the drag occurs.
*
- * This is the simplest way to set up a draggable2d modifier. When constructing this
- * [Draggable2dState], you must provide a [onDelta] lambda, which will be invoked whenever
- * drag happens (by gesture input or a custom [Draggable2dState.drag] call) with the delta in
+ * This is the simplest way to set up a draggable2D modifier. When constructing this
+ * [Draggable2DState], you must provide a [onDelta] lambda, which will be invoked whenever
+ * drag happens (by gesture input or a custom [Draggable2DState.drag] call) with the delta in
* pixels.
*
- * If you are creating [Draggable2dState] in composition, consider using [rememberDraggable2dState].
+ * If you are creating [Draggable2DState] in composition, consider using [rememberDraggable2DState].
*
* @param onDelta callback invoked when drag occurs. The callback receives the delta in pixels.
*/
@Suppress("PrimitiveInLambda")
-internal fun Draggable2dState(onDelta: (Offset) -> Unit): Draggable2dState =
- DefaultDraggable2dState(onDelta)
+internal fun Draggable2DState(onDelta: (Offset) -> Unit): Draggable2DState =
+ DefaultDraggable2DState(onDelta)
/**
- * Create and remember default implementation of [Draggable2dState] interface that allows to pass a
+ * Create and remember default implementation of [Draggable2DState] interface that allows to pass a
* simple action that will be invoked when the drag occurs.
*
* This is the simplest way to set up a [draggable] modifier. When constructing this
- * [Draggable2dState], you must provide a [onDelta] lambda, which will be invoked whenever
- * drag happens (by gesture input or a custom [Draggable2dState.drag] call) with the delta in
+ * [Draggable2DState], you must provide a [onDelta] lambda, which will be invoked whenever
+ * drag happens (by gesture input or a custom [Draggable2DState.drag] call) with the delta in
* pixels.
*
* @param onDelta callback invoked when drag occurs. The callback receives the delta in pixels.
*/
@Suppress("PrimitiveInLambda")
@Composable
-internal fun rememberDraggable2dState(onDelta: (Offset) -> Unit): Draggable2dState {
+internal fun rememberDraggable2DState(onDelta: (Offset) -> Unit): Draggable2DState {
val onDeltaState = rememberUpdatedState(onDelta)
- return remember { Draggable2dState { onDeltaState.value.invoke(it) } }
+ return remember { Draggable2DState { onDeltaState.value.invoke(it) } }
}
/**
* Configure touch dragging for the UI element in both orientations. The drag distance
- * reported to [Draggable2dState], allowing users to react to the drag delta and update their state.
+ * reported to [Draggable2DState], allowing users to react to the drag delta and update their state.
*
* The common common usecase for this component is when you need to be able to drag something
* inside the component on the screen and represent this state via one float value
*
* If you are implementing dragging in a single orientation, consider using [draggable].
*
- * @param state [Draggable2dState] state of the draggable2d. Defines how drag events will be
+ * @param state [Draggable2DState] state of the draggable2D. Defines how drag events will be
* interpreted by the user land logic.
* @param enabled whether or not drag is enabled
* @param interactionSource [MutableInteractionSource] that will be used to emit
* [DragInteraction.Start] when this draggable is being dragged.
- * @param startDragImmediately when set to true, draggable2d will start dragging immediately and
+ * @param startDragImmediately when set to true, draggable2D will start dragging immediately and
* prevent other gesture detectors from reacting to "down" events (in order to block composed
* press-based gestures). This is intended to allow end users to "catch" an animating widget by
* pressing on it. It's useful to set it when value you're dragging is settling / animating.
* @param onDragStarted callback that will be invoked when drag is about to start at the starting
* position, allowing user to suspend and perform preparation for drag, if desired.This suspend
- * function is invoked with the draggable2d scope, allowing for async processing, if desired. Note
- * that the scope used here is the onw provided by the draggable2d node, for long running work that
+ * function is invoked with the draggable2D scope, allowing for async processing, if desired. Note
+ * that the scope used here is the onw provided by the draggable2D node, for long running work that
* needs to outlast the modifier being in the composition you should use a scope that fits the
* lifecycle needed.
* @param onDragStopped callback that will be invoked when drag is finished, allowing the
- * user to react on velocity and process it. This suspend function is invoked with the draggable2d
+ * user to react on velocity and process it. This suspend function is invoked with the draggable2D
* scope, allowing for async processing, if desired. Note that the scope used here is the onw
- * provided by the draggable2d scope, for long running work that needs to outlast the modifier being
+ * provided by the draggable2D scope, for long running work that needs to outlast the modifier being
* in the composition you should use a scope that fits the lifecycle needed.
* @param reverseDirection reverse the direction of the scroll, so top to bottom scroll will
* behave like bottom to top and left to right will behave like right to left.
*/
@Suppress("PrimitiveInLambda")
-internal fun Modifier.draggable2d(
- state: Draggable2dState,
+internal fun Modifier.draggable2D(
+ state: Draggable2DState,
enabled: Boolean = true,
interactionSource: MutableInteractionSource? = null,
startDragImmediately: Boolean = false,
onDragStarted: suspend CoroutineScope.(startedPosition: Offset) -> Unit = {},
onDragStopped: suspend CoroutineScope.(velocity: Velocity) -> Unit = {},
reverseDirection: Boolean = false
-): Modifier = this then Draggable2dElement(
+): Modifier = this then Draggable2DElement(
state = state,
enabled = enabled,
interactionSource = interactionSource,
@@ -171,8 +171,8 @@
)
@Suppress("PrimitiveInLambda")
-internal class Draggable2dElement(
- private val state: Draggable2dState,
+internal class Draggable2DElement(
+ private val state: Draggable2DState,
private val canDrag: (PointerInputChange) -> Boolean,
private val enabled: Boolean,
private val interactionSource: MutableInteractionSource?,
@@ -181,8 +181,8 @@
private val onDragStopped: suspend CoroutineScope.(velocity: Velocity) -> Unit,
private val reverseDirection: Boolean,
- ) : ModifierNodeElement<Draggable2dNode>() {
- override fun create(): Draggable2dNode = Draggable2dNode(
+ ) : ModifierNodeElement<Draggable2DNode>() {
+ override fun create(): Draggable2DNode = Draggable2DNode(
state,
canDrag,
enabled,
@@ -193,7 +193,7 @@
reverseDirection
)
- override fun update(node: Draggable2dNode) {
+ override fun update(node: Draggable2DNode) {
node.update(
state,
canDrag,
@@ -211,7 +211,7 @@
if (other === null) return false
if (this::class != other::class) return false
- other as Draggable2dElement
+ other as Draggable2DElement
if (state != other.state) return false
if (canDrag != other.canDrag) return false
@@ -238,7 +238,7 @@
}
override fun InspectorInfo.inspectableProperties() {
- name = "draggable2d"
+ name = "draggable2D"
properties["canDrag"] = canDrag
properties["enabled"] = enabled
properties["interactionSource"] = interactionSource
@@ -251,8 +251,8 @@
}
@Suppress("PrimitiveInLambda")
-internal class Draggable2dNode(
- private var state: Draggable2dState,
+internal class Draggable2DNode(
+ private var state: Draggable2DState,
canDrag: (PointerInputChange) -> Boolean,
enabled: Boolean,
interactionSource: MutableInteractionSource?,
@@ -269,17 +269,17 @@
onDragStopped,
reverseDirection
) {
- var drag2dScope: Drag2dScope = NoOpDrag2dScope
+ var drag2DScope: Drag2DScope = NoOpDrag2DScope
private val abstractDragScope = object : AbstractDragScope {
override fun dragBy(pixels: Offset) {
- drag2dScope.dragBy(pixels)
+ drag2DScope.dragBy(pixels)
}
}
override suspend fun drag(block: suspend AbstractDragScope.() -> Unit) {
state.drag(MutatePriority.UserInput) {
- drag2dScope = this
+ drag2DScope = this
block.invoke(abstractDragScope)
}
}
@@ -292,7 +292,7 @@
@Suppress("PrimitiveInLambda")
fun update(
- state: Draggable2dState,
+ state: Draggable2DState,
canDrag: (PointerInputChange) -> Boolean,
enabled: Boolean,
interactionSource: MutableInteractionSource?,
@@ -331,23 +331,23 @@
}
}
-private val NoOpDrag2dScope: Drag2dScope = object : Drag2dScope {
+private val NoOpDrag2DScope: Drag2DScope = object : Drag2DScope {
override fun dragBy(pixels: Offset) {}
}
@Suppress("PrimitiveInLambda")
-private class DefaultDraggable2dState(val onDelta: (Offset) -> Unit) : Draggable2dState {
- private val drag2dScope: Drag2dScope = object : Drag2dScope {
+private class DefaultDraggable2DState(val onDelta: (Offset) -> Unit) : Draggable2DState {
+ private val drag2DScope: Drag2DScope = object : Drag2DScope {
override fun dragBy(pixels: Offset) = onDelta(pixels)
}
- private val drag2dMutex = MutatorMutex()
+ private val drag2DMutex = MutatorMutex()
override suspend fun drag(
dragPriority: MutatePriority,
- block: suspend Drag2dScope.() -> Unit
+ block: suspend Drag2DScope.() -> Unit
): Unit = coroutineScope {
- drag2dMutex.mutateWith(drag2dScope, dragPriority, block)
+ drag2DMutex.mutateWith(drag2DScope, dragPriority, block)
}
override fun dispatchRawDelta(delta: Offset) {
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Text.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Text.kt
index d2992f3..5fc4f0d 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Text.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Text.kt
@@ -142,7 +142,7 @@
style = style.merge(
fontSize = fontSize,
fontWeight = fontWeight,
- textAlign = textAlign,
+ textAlign = textAlign ?: TextAlign.Unspecified,
lineHeight = lineHeight,
fontFamily = fontFamily,
textDecoration = textDecoration,
@@ -310,7 +310,7 @@
style = style.merge(
fontSize = fontSize,
fontWeight = fontWeight,
- textAlign = textAlign,
+ textAlign = textAlign ?: TextAlign.Unspecified,
lineHeight = lineHeight,
fontFamily = fontFamily,
textDecoration = textDecoration,
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Text.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Text.kt
index c16d817..0930a98 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Text.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Text.kt
@@ -122,7 +122,7 @@
color = textColor,
fontSize = fontSize,
fontWeight = fontWeight,
- textAlign = textAlign,
+ textAlign = textAlign ?: TextAlign.Unspecified,
lineHeight = lineHeight,
fontFamily = fontFamily,
textDecoration = textDecoration,
@@ -267,7 +267,7 @@
color = textColor,
fontSize = fontSize,
fontWeight = fontWeight,
- textAlign = textAlign,
+ textAlign = textAlign ?: TextAlign.Unspecified,
lineHeight = lineHeight,
fontFamily = fontFamily,
textDecoration = textDecoration,
diff --git a/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/ParameterFactoryTest.kt b/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/ParameterFactoryTest.kt
index c75966c..63c4e0b 100644
--- a/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/ParameterFactoryTest.kt
+++ b/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/ParameterFactoryTest.kt
@@ -66,6 +66,7 @@
import androidx.compose.ui.text.intl.LocaleList
import androidx.compose.ui.text.style.BaselineShift
import androidx.compose.ui.text.style.TextDecoration
+import androidx.compose.ui.text.style.TextDirection
import androidx.compose.ui.text.style.TextGeometricTransform
import androidx.compose.ui.text.style.TextIndent
import androidx.compose.ui.unit.Density
@@ -922,7 +923,8 @@
fun testTextStyle() {
val style = TextStyle(
color = Color.Red,
- textDecoration = TextDecoration.Underline
+ textDecoration = TextDecoration.Underline,
+ textDirection = TextDirection.Content
)
validate(create("style", style)) {
parameter("style", ParameterType.String, TextStyle::class.java.simpleName) {
@@ -931,6 +933,7 @@
parameter("letterSpacing", ParameterType.String, "Unspecified", index = 7)
parameter("background", ParameterType.String, "Unspecified", index = 11)
parameter("textDecoration", ParameterType.String, "Underline", index = 12)
+ parameter("textDirection", ParameterType.String, "Content", index = 14)
parameter("lineHeight", ParameterType.String, "Unspecified", index = 15)
}
}
diff --git a/compose/ui/ui-text/api/current.ignore b/compose/ui/ui-text/api/current.ignore
index fe35567..6fba8e1 100644
--- a/compose/ui/ui-text/api/current.ignore
+++ b/compose/ui/ui-text/api/current.ignore
@@ -3,6 +3,42 @@
Added method androidx.compose.ui.text.Paragraph.fillBoundingBoxes(long,float[],int)
+ChangedType: androidx.compose.ui.text.TextStyle#getHyphens():
+ Method androidx.compose.ui.text.TextStyle.getHyphens has changed return type from androidx.compose.ui.text.style.Hyphens to int
+ChangedType: androidx.compose.ui.text.TextStyle#getLineBreak():
+ Method androidx.compose.ui.text.TextStyle.getLineBreak has changed return type from androidx.compose.ui.text.style.LineBreak to int
+ChangedType: androidx.compose.ui.text.TextStyle#getTextAlign():
+ Method androidx.compose.ui.text.TextStyle.getTextAlign has changed return type from androidx.compose.ui.text.style.TextAlign to int
+ChangedType: androidx.compose.ui.text.TextStyle#getTextDirection():
+ Method androidx.compose.ui.text.TextStyle.getTextDirection has changed return type from androidx.compose.ui.text.style.TextDirection to int
+
+
+InvalidNullConversion: Field ParagraphStyle.hyphens:
+ Attempted to remove @Nullable annotation from Field ParagraphStyle.hyphens
+InvalidNullConversion: Field ParagraphStyle.lineBreak:
+ Attempted to remove @Nullable annotation from Field ParagraphStyle.lineBreak
+InvalidNullConversion: Field ParagraphStyle.textAlign:
+ Attempted to remove @Nullable annotation from Field ParagraphStyle.textAlign
+InvalidNullConversion: Field ParagraphStyle.textDirection:
+ Attempted to remove @Nullable annotation from Field ParagraphStyle.textDirection
+InvalidNullConversion: Field TextStyle.hyphens:
+ Attempted to remove @Nullable annotation from Field TextStyle.hyphens
+InvalidNullConversion: Field TextStyle.lineBreak:
+ Attempted to remove @Nullable annotation from Field TextStyle.lineBreak
+InvalidNullConversion: Field TextStyle.textAlign:
+ Attempted to remove @Nullable annotation from Field TextStyle.textAlign
+InvalidNullConversion: Field TextStyle.textDirection:
+ Attempted to remove @Nullable annotation from Field TextStyle.textDirection
+InvalidNullConversion: androidx.compose.ui.text.TextStyle#getHyphens():
+ Attempted to remove @Nullable annotation from method androidx.compose.ui.text.TextStyle.getHyphens()
+InvalidNullConversion: androidx.compose.ui.text.TextStyle#getLineBreak():
+ Attempted to remove @Nullable annotation from method androidx.compose.ui.text.TextStyle.getLineBreak()
+InvalidNullConversion: androidx.compose.ui.text.TextStyle#getTextAlign():
+ Attempted to remove @Nullable annotation from method androidx.compose.ui.text.TextStyle.getTextAlign()
+InvalidNullConversion: androidx.compose.ui.text.TextStyle#getTextDirection():
+ Attempted to remove @Nullable annotation from method androidx.compose.ui.text.TextStyle.getTextDirection()
+
+
ParameterNameChange: androidx.compose.ui.text.TextMeasurer#TextMeasurer(androidx.compose.ui.text.font.FontFamily.Resolver, androidx.compose.ui.unit.Density, androidx.compose.ui.unit.LayoutDirection, int) parameter #0:
Attempted to change parameter name from fallbackFontFamilyResolver to defaultFontFamilyResolver in constructor androidx.compose.ui.text.TextMeasurer
ParameterNameChange: androidx.compose.ui.text.TextMeasurer#TextMeasurer(androidx.compose.ui.text.font.FontFamily.Resolver, androidx.compose.ui.unit.Density, androidx.compose.ui.unit.LayoutDirection, int) parameter #1:
diff --git a/compose/ui/ui-text/api/current.txt b/compose/ui/ui-text/api/current.txt
index 5ca485b..bda3926 100644
--- a/compose/ui/ui-text/api/current.txt
+++ b/compose/ui/ui-text/api/current.txt
@@ -242,29 +242,39 @@
ctor @Deprecated public ParagraphStyle(optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent);
ctor @Deprecated public ParagraphStyle(optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformParagraphStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle);
ctor @Deprecated public ParagraphStyle(optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformParagraphStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens);
- ctor public ParagraphStyle(optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformParagraphStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens, optional androidx.compose.ui.text.style.TextMotion? textMotion);
+ ctor @Deprecated public ParagraphStyle(optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformParagraphStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens, optional androidx.compose.ui.text.style.TextMotion? textMotion);
+ ctor public ParagraphStyle(optional int textAlign, optional int textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformParagraphStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional int lineBreak, optional int hyphens, optional androidx.compose.ui.text.style.TextMotion? textMotion);
method @Deprecated public androidx.compose.ui.text.ParagraphStyle copy(optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent);
method @Deprecated public androidx.compose.ui.text.ParagraphStyle copy(optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformParagraphStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle);
method @Deprecated public androidx.compose.ui.text.ParagraphStyle copy(optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformParagraphStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens);
- method public androidx.compose.ui.text.ParagraphStyle copy(optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformParagraphStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens, optional androidx.compose.ui.text.style.TextMotion? textMotion);
- method public androidx.compose.ui.text.style.Hyphens? getHyphens();
- method public androidx.compose.ui.text.style.LineBreak? getLineBreak();
+ method @Deprecated public androidx.compose.ui.text.ParagraphStyle copy(optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformParagraphStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens, optional androidx.compose.ui.text.style.TextMotion? textMotion);
+ method public androidx.compose.ui.text.ParagraphStyle copy(optional int textAlign, optional int textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformParagraphStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional int lineBreak, optional int hyphens, optional androidx.compose.ui.text.style.TextMotion? textMotion);
+ method @Deprecated public androidx.compose.ui.text.style.Hyphens? getHyphens();
+ method public int getHyphens();
+ method @Deprecated public androidx.compose.ui.text.style.LineBreak? getLineBreak();
+ method public int getLineBreak();
method public long getLineHeight();
method public androidx.compose.ui.text.style.LineHeightStyle? getLineHeightStyle();
method public androidx.compose.ui.text.PlatformParagraphStyle? getPlatformStyle();
- method public androidx.compose.ui.text.style.TextAlign? getTextAlign();
- method public androidx.compose.ui.text.style.TextDirection? getTextDirection();
+ method @Deprecated public androidx.compose.ui.text.style.TextAlign? getTextAlign();
+ method public int getTextAlign();
+ method @Deprecated public androidx.compose.ui.text.style.TextDirection? getTextDirection();
+ method public int getTextDirection();
method public androidx.compose.ui.text.style.TextIndent? getTextIndent();
method public androidx.compose.ui.text.style.TextMotion? getTextMotion();
method @androidx.compose.runtime.Stable public androidx.compose.ui.text.ParagraphStyle merge(optional androidx.compose.ui.text.ParagraphStyle? other);
method @androidx.compose.runtime.Stable public operator androidx.compose.ui.text.ParagraphStyle plus(androidx.compose.ui.text.ParagraphStyle other);
- property public final androidx.compose.ui.text.style.Hyphens? hyphens;
- property public final androidx.compose.ui.text.style.LineBreak? lineBreak;
+ property @Deprecated public final androidx.compose.ui.text.style.Hyphens? deprecated_boxing_hyphens;
+ property @Deprecated public final androidx.compose.ui.text.style.LineBreak? deprecated_boxing_lineBreak;
+ property @Deprecated public final androidx.compose.ui.text.style.TextAlign? deprecated_boxing_textAlign;
+ property @Deprecated public final androidx.compose.ui.text.style.TextDirection? deprecated_boxing_textDirection;
+ property public final int hyphens;
+ property public final int lineBreak;
property public final long lineHeight;
property public final androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle;
property public final androidx.compose.ui.text.PlatformParagraphStyle? platformStyle;
- property public final androidx.compose.ui.text.style.TextAlign? textAlign;
- property public final androidx.compose.ui.text.style.TextDirection? textDirection;
+ property public final int textAlign;
+ property public final int textDirection;
property public final androidx.compose.ui.text.style.TextIndent? textIndent;
property public final androidx.compose.ui.text.style.TextMotion? textMotion;
}
@@ -529,13 +539,17 @@
}
@androidx.compose.runtime.Immutable public final class TextStyle {
- ctor public TextStyle(androidx.compose.ui.graphics.Brush? brush, optional float alpha, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens, optional androidx.compose.ui.text.style.TextMotion? textMotion);
- ctor public TextStyle(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens, optional androidx.compose.ui.text.style.TextMotion? textMotion);
+ ctor @Deprecated public TextStyle(androidx.compose.ui.graphics.Brush? brush, optional float alpha, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens, optional androidx.compose.ui.text.style.TextMotion? textMotion);
+ ctor public TextStyle(androidx.compose.ui.graphics.Brush? brush, optional float alpha, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle, optional int textAlign, optional int textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional int lineBreak, optional int hyphens, optional androidx.compose.ui.text.style.TextMotion? textMotion);
+ ctor @Deprecated public TextStyle(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens, optional androidx.compose.ui.text.style.TextMotion? textMotion);
+ ctor public TextStyle(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle, optional int textAlign, optional int textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional int lineBreak, optional int hyphens, optional androidx.compose.ui.text.style.TextMotion? textMotion);
ctor @Deprecated public TextStyle(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent);
ctor @Deprecated public TextStyle(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle);
ctor @Deprecated public TextStyle(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens);
- method public androidx.compose.ui.text.TextStyle copy(androidx.compose.ui.graphics.Brush? brush, optional float alpha, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens, optional androidx.compose.ui.text.style.TextMotion? textMotion);
- method public androidx.compose.ui.text.TextStyle copy(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens, optional androidx.compose.ui.text.style.TextMotion? textMotion);
+ method @Deprecated public androidx.compose.ui.text.TextStyle copy(androidx.compose.ui.graphics.Brush? brush, optional float alpha, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens, optional androidx.compose.ui.text.style.TextMotion? textMotion);
+ method public androidx.compose.ui.text.TextStyle copy(androidx.compose.ui.graphics.Brush? brush, optional float alpha, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle, optional int textAlign, optional int textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional int lineBreak, optional int hyphens, optional androidx.compose.ui.text.style.TextMotion? textMotion);
+ method @Deprecated public androidx.compose.ui.text.TextStyle copy(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens, optional androidx.compose.ui.text.style.TextMotion? textMotion);
+ method public androidx.compose.ui.text.TextStyle copy(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle, optional int textAlign, optional int textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional int lineBreak, optional int hyphens, optional androidx.compose.ui.text.style.TextMotion? textMotion);
method @Deprecated public androidx.compose.ui.text.TextStyle copy(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent);
method @Deprecated public androidx.compose.ui.text.TextStyle copy(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle);
method @Deprecated public androidx.compose.ui.text.TextStyle copy(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens);
@@ -551,17 +565,21 @@
method public androidx.compose.ui.text.font.FontStyle? getFontStyle();
method public androidx.compose.ui.text.font.FontSynthesis? getFontSynthesis();
method public androidx.compose.ui.text.font.FontWeight? getFontWeight();
- method public androidx.compose.ui.text.style.Hyphens? getHyphens();
+ method public int getHyphens();
+ method @Deprecated public androidx.compose.ui.text.style.Hyphens? getHyphens();
method public long getLetterSpacing();
- method public androidx.compose.ui.text.style.LineBreak? getLineBreak();
+ method public int getLineBreak();
+ method @Deprecated public androidx.compose.ui.text.style.LineBreak? getLineBreak();
method public long getLineHeight();
method public androidx.compose.ui.text.style.LineHeightStyle? getLineHeightStyle();
method public androidx.compose.ui.text.intl.LocaleList? getLocaleList();
method public androidx.compose.ui.text.PlatformTextStyle? getPlatformStyle();
method public androidx.compose.ui.graphics.Shadow? getShadow();
- method public androidx.compose.ui.text.style.TextAlign? getTextAlign();
+ method public int getTextAlign();
+ method @Deprecated public androidx.compose.ui.text.style.TextAlign? getTextAlign();
method public androidx.compose.ui.text.style.TextDecoration? getTextDecoration();
- method public androidx.compose.ui.text.style.TextDirection? getTextDirection();
+ method public int getTextDirection();
+ method @Deprecated public androidx.compose.ui.text.style.TextDirection? getTextDirection();
method public androidx.compose.ui.text.style.TextGeometricTransform? getTextGeometricTransform();
method public androidx.compose.ui.text.style.TextIndent? getTextIndent();
method public androidx.compose.ui.text.style.TextMotion? getTextMotion();
@@ -570,7 +588,8 @@
method @androidx.compose.runtime.Stable public androidx.compose.ui.text.TextStyle merge(androidx.compose.ui.text.ParagraphStyle other);
method @androidx.compose.runtime.Stable public androidx.compose.ui.text.TextStyle merge(androidx.compose.ui.text.SpanStyle other);
method @androidx.compose.runtime.Stable public androidx.compose.ui.text.TextStyle merge(optional androidx.compose.ui.text.TextStyle? other);
- method @androidx.compose.runtime.Stable public androidx.compose.ui.text.TextStyle merge(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.TextMotion? textMotion);
+ method @Deprecated @androidx.compose.runtime.Stable public androidx.compose.ui.text.TextStyle merge(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.TextMotion? textMotion);
+ method @androidx.compose.runtime.Stable public androidx.compose.ui.text.TextStyle merge(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle, optional int textAlign, optional int textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional int lineBreak, optional int hyphens, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.TextMotion? textMotion);
method @androidx.compose.runtime.Stable public operator androidx.compose.ui.text.TextStyle plus(androidx.compose.ui.text.ParagraphStyle other);
method @androidx.compose.runtime.Stable public operator androidx.compose.ui.text.TextStyle plus(androidx.compose.ui.text.SpanStyle other);
method @androidx.compose.runtime.Stable public operator androidx.compose.ui.text.TextStyle plus(androidx.compose.ui.text.TextStyle other);
@@ -581,6 +600,10 @@
property public final androidx.compose.ui.text.style.BaselineShift? baselineShift;
property public final androidx.compose.ui.graphics.Brush? brush;
property public final long color;
+ property @Deprecated public final androidx.compose.ui.text.style.Hyphens? deprecated_boxing_hyphens;
+ property @Deprecated public final androidx.compose.ui.text.style.LineBreak? deprecated_boxing_lineBreak;
+ property @Deprecated public final androidx.compose.ui.text.style.TextAlign? deprecated_boxing_textAlign;
+ property @Deprecated public final androidx.compose.ui.text.style.TextDirection? deprecated_boxing_textDirection;
property public final androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle;
property public final androidx.compose.ui.text.font.FontFamily? fontFamily;
property public final String? fontFeatureSettings;
@@ -588,17 +611,17 @@
property public final androidx.compose.ui.text.font.FontStyle? fontStyle;
property public final androidx.compose.ui.text.font.FontSynthesis? fontSynthesis;
property public final androidx.compose.ui.text.font.FontWeight? fontWeight;
- property public final androidx.compose.ui.text.style.Hyphens? hyphens;
+ property public final int hyphens;
property public final long letterSpacing;
- property public final androidx.compose.ui.text.style.LineBreak? lineBreak;
+ property public final int lineBreak;
property public final long lineHeight;
property public final androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle;
property public final androidx.compose.ui.text.intl.LocaleList? localeList;
property public final androidx.compose.ui.text.PlatformTextStyle? platformStyle;
property public final androidx.compose.ui.graphics.Shadow? shadow;
- property public final androidx.compose.ui.text.style.TextAlign? textAlign;
+ property public final int textAlign;
property public final androidx.compose.ui.text.style.TextDecoration? textDecoration;
- property public final androidx.compose.ui.text.style.TextDirection? textDirection;
+ property public final int textDirection;
property public final androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform;
property public final androidx.compose.ui.text.style.TextIndent? textIndent;
property public final androidx.compose.ui.text.style.TextMotion? textMotion;
@@ -1322,8 +1345,10 @@
public static final class Hyphens.Companion {
method public int getAuto();
method public int getNone();
+ method public int getUnspecified();
property public final int Auto;
property public final int None;
+ property public final int Unspecified;
}
@androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class LineBreak {
@@ -1342,9 +1367,11 @@
method public int getHeading();
method public int getParagraph();
method public int getSimple();
+ method public int getUnspecified();
property public final int Heading;
property public final int Paragraph;
property public final int Simple;
+ property public final int Unspecified;
}
@kotlin.jvm.JvmInline public static final value class LineBreak.Strategy {
@@ -1355,9 +1382,11 @@
method public int getBalanced();
method public int getHighQuality();
method public int getSimple();
+ method public int getUnspecified();
property public final int Balanced;
property public final int HighQuality;
property public final int Simple;
+ property public final int Unspecified;
}
@kotlin.jvm.JvmInline public static final value class LineBreak.Strictness {
@@ -1369,10 +1398,12 @@
method public int getLoose();
method public int getNormal();
method public int getStrict();
+ method public int getUnspecified();
property public final int Default;
property public final int Loose;
property public final int Normal;
property public final int Strict;
+ property public final int Unspecified;
}
@kotlin.jvm.JvmInline public static final value class LineBreak.WordBreak {
@@ -1382,8 +1413,10 @@
public static final class LineBreak.WordBreak.Companion {
method public int getDefault();
method public int getPhrase();
+ method public int getUnspecified();
property public final int Default;
property public final int Phrase;
+ property public final int Unspecified;
}
public final class LineHeightStyle {
@@ -1449,6 +1482,7 @@
method public int getLeft();
method public int getRight();
method public int getStart();
+ method public int getUnspecified();
method public java.util.List<androidx.compose.ui.text.style.TextAlign> values();
property public final int Center;
property public final int End;
@@ -1456,6 +1490,7 @@
property public final int Left;
property public final int Right;
property public final int Start;
+ property public final int Unspecified;
}
@androidx.compose.runtime.Immutable public final class TextDecoration {
@@ -1486,11 +1521,13 @@
method public int getContentOrRtl();
method public int getLtr();
method public int getRtl();
+ method public int getUnspecified();
property public final int Content;
property public final int ContentOrLtr;
property public final int ContentOrRtl;
property public final int Ltr;
property public final int Rtl;
+ property public final int Unspecified;
}
@androidx.compose.runtime.Immutable public final class TextGeometricTransform {
diff --git a/compose/ui/ui-text/api/restricted_current.ignore b/compose/ui/ui-text/api/restricted_current.ignore
index fe35567..6fba8e1 100644
--- a/compose/ui/ui-text/api/restricted_current.ignore
+++ b/compose/ui/ui-text/api/restricted_current.ignore
@@ -3,6 +3,42 @@
Added method androidx.compose.ui.text.Paragraph.fillBoundingBoxes(long,float[],int)
+ChangedType: androidx.compose.ui.text.TextStyle#getHyphens():
+ Method androidx.compose.ui.text.TextStyle.getHyphens has changed return type from androidx.compose.ui.text.style.Hyphens to int
+ChangedType: androidx.compose.ui.text.TextStyle#getLineBreak():
+ Method androidx.compose.ui.text.TextStyle.getLineBreak has changed return type from androidx.compose.ui.text.style.LineBreak to int
+ChangedType: androidx.compose.ui.text.TextStyle#getTextAlign():
+ Method androidx.compose.ui.text.TextStyle.getTextAlign has changed return type from androidx.compose.ui.text.style.TextAlign to int
+ChangedType: androidx.compose.ui.text.TextStyle#getTextDirection():
+ Method androidx.compose.ui.text.TextStyle.getTextDirection has changed return type from androidx.compose.ui.text.style.TextDirection to int
+
+
+InvalidNullConversion: Field ParagraphStyle.hyphens:
+ Attempted to remove @Nullable annotation from Field ParagraphStyle.hyphens
+InvalidNullConversion: Field ParagraphStyle.lineBreak:
+ Attempted to remove @Nullable annotation from Field ParagraphStyle.lineBreak
+InvalidNullConversion: Field ParagraphStyle.textAlign:
+ Attempted to remove @Nullable annotation from Field ParagraphStyle.textAlign
+InvalidNullConversion: Field ParagraphStyle.textDirection:
+ Attempted to remove @Nullable annotation from Field ParagraphStyle.textDirection
+InvalidNullConversion: Field TextStyle.hyphens:
+ Attempted to remove @Nullable annotation from Field TextStyle.hyphens
+InvalidNullConversion: Field TextStyle.lineBreak:
+ Attempted to remove @Nullable annotation from Field TextStyle.lineBreak
+InvalidNullConversion: Field TextStyle.textAlign:
+ Attempted to remove @Nullable annotation from Field TextStyle.textAlign
+InvalidNullConversion: Field TextStyle.textDirection:
+ Attempted to remove @Nullable annotation from Field TextStyle.textDirection
+InvalidNullConversion: androidx.compose.ui.text.TextStyle#getHyphens():
+ Attempted to remove @Nullable annotation from method androidx.compose.ui.text.TextStyle.getHyphens()
+InvalidNullConversion: androidx.compose.ui.text.TextStyle#getLineBreak():
+ Attempted to remove @Nullable annotation from method androidx.compose.ui.text.TextStyle.getLineBreak()
+InvalidNullConversion: androidx.compose.ui.text.TextStyle#getTextAlign():
+ Attempted to remove @Nullable annotation from method androidx.compose.ui.text.TextStyle.getTextAlign()
+InvalidNullConversion: androidx.compose.ui.text.TextStyle#getTextDirection():
+ Attempted to remove @Nullable annotation from method androidx.compose.ui.text.TextStyle.getTextDirection()
+
+
ParameterNameChange: androidx.compose.ui.text.TextMeasurer#TextMeasurer(androidx.compose.ui.text.font.FontFamily.Resolver, androidx.compose.ui.unit.Density, androidx.compose.ui.unit.LayoutDirection, int) parameter #0:
Attempted to change parameter name from fallbackFontFamilyResolver to defaultFontFamilyResolver in constructor androidx.compose.ui.text.TextMeasurer
ParameterNameChange: androidx.compose.ui.text.TextMeasurer#TextMeasurer(androidx.compose.ui.text.font.FontFamily.Resolver, androidx.compose.ui.unit.Density, androidx.compose.ui.unit.LayoutDirection, int) parameter #1:
diff --git a/compose/ui/ui-text/api/restricted_current.txt b/compose/ui/ui-text/api/restricted_current.txt
index 5ca485b..bda3926 100644
--- a/compose/ui/ui-text/api/restricted_current.txt
+++ b/compose/ui/ui-text/api/restricted_current.txt
@@ -242,29 +242,39 @@
ctor @Deprecated public ParagraphStyle(optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent);
ctor @Deprecated public ParagraphStyle(optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformParagraphStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle);
ctor @Deprecated public ParagraphStyle(optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformParagraphStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens);
- ctor public ParagraphStyle(optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformParagraphStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens, optional androidx.compose.ui.text.style.TextMotion? textMotion);
+ ctor @Deprecated public ParagraphStyle(optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformParagraphStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens, optional androidx.compose.ui.text.style.TextMotion? textMotion);
+ ctor public ParagraphStyle(optional int textAlign, optional int textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformParagraphStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional int lineBreak, optional int hyphens, optional androidx.compose.ui.text.style.TextMotion? textMotion);
method @Deprecated public androidx.compose.ui.text.ParagraphStyle copy(optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent);
method @Deprecated public androidx.compose.ui.text.ParagraphStyle copy(optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformParagraphStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle);
method @Deprecated public androidx.compose.ui.text.ParagraphStyle copy(optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformParagraphStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens);
- method public androidx.compose.ui.text.ParagraphStyle copy(optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformParagraphStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens, optional androidx.compose.ui.text.style.TextMotion? textMotion);
- method public androidx.compose.ui.text.style.Hyphens? getHyphens();
- method public androidx.compose.ui.text.style.LineBreak? getLineBreak();
+ method @Deprecated public androidx.compose.ui.text.ParagraphStyle copy(optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformParagraphStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens, optional androidx.compose.ui.text.style.TextMotion? textMotion);
+ method public androidx.compose.ui.text.ParagraphStyle copy(optional int textAlign, optional int textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformParagraphStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional int lineBreak, optional int hyphens, optional androidx.compose.ui.text.style.TextMotion? textMotion);
+ method @Deprecated public androidx.compose.ui.text.style.Hyphens? getHyphens();
+ method public int getHyphens();
+ method @Deprecated public androidx.compose.ui.text.style.LineBreak? getLineBreak();
+ method public int getLineBreak();
method public long getLineHeight();
method public androidx.compose.ui.text.style.LineHeightStyle? getLineHeightStyle();
method public androidx.compose.ui.text.PlatformParagraphStyle? getPlatformStyle();
- method public androidx.compose.ui.text.style.TextAlign? getTextAlign();
- method public androidx.compose.ui.text.style.TextDirection? getTextDirection();
+ method @Deprecated public androidx.compose.ui.text.style.TextAlign? getTextAlign();
+ method public int getTextAlign();
+ method @Deprecated public androidx.compose.ui.text.style.TextDirection? getTextDirection();
+ method public int getTextDirection();
method public androidx.compose.ui.text.style.TextIndent? getTextIndent();
method public androidx.compose.ui.text.style.TextMotion? getTextMotion();
method @androidx.compose.runtime.Stable public androidx.compose.ui.text.ParagraphStyle merge(optional androidx.compose.ui.text.ParagraphStyle? other);
method @androidx.compose.runtime.Stable public operator androidx.compose.ui.text.ParagraphStyle plus(androidx.compose.ui.text.ParagraphStyle other);
- property public final androidx.compose.ui.text.style.Hyphens? hyphens;
- property public final androidx.compose.ui.text.style.LineBreak? lineBreak;
+ property @Deprecated public final androidx.compose.ui.text.style.Hyphens? deprecated_boxing_hyphens;
+ property @Deprecated public final androidx.compose.ui.text.style.LineBreak? deprecated_boxing_lineBreak;
+ property @Deprecated public final androidx.compose.ui.text.style.TextAlign? deprecated_boxing_textAlign;
+ property @Deprecated public final androidx.compose.ui.text.style.TextDirection? deprecated_boxing_textDirection;
+ property public final int hyphens;
+ property public final int lineBreak;
property public final long lineHeight;
property public final androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle;
property public final androidx.compose.ui.text.PlatformParagraphStyle? platformStyle;
- property public final androidx.compose.ui.text.style.TextAlign? textAlign;
- property public final androidx.compose.ui.text.style.TextDirection? textDirection;
+ property public final int textAlign;
+ property public final int textDirection;
property public final androidx.compose.ui.text.style.TextIndent? textIndent;
property public final androidx.compose.ui.text.style.TextMotion? textMotion;
}
@@ -529,13 +539,17 @@
}
@androidx.compose.runtime.Immutable public final class TextStyle {
- ctor public TextStyle(androidx.compose.ui.graphics.Brush? brush, optional float alpha, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens, optional androidx.compose.ui.text.style.TextMotion? textMotion);
- ctor public TextStyle(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens, optional androidx.compose.ui.text.style.TextMotion? textMotion);
+ ctor @Deprecated public TextStyle(androidx.compose.ui.graphics.Brush? brush, optional float alpha, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens, optional androidx.compose.ui.text.style.TextMotion? textMotion);
+ ctor public TextStyle(androidx.compose.ui.graphics.Brush? brush, optional float alpha, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle, optional int textAlign, optional int textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional int lineBreak, optional int hyphens, optional androidx.compose.ui.text.style.TextMotion? textMotion);
+ ctor @Deprecated public TextStyle(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens, optional androidx.compose.ui.text.style.TextMotion? textMotion);
+ ctor public TextStyle(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle, optional int textAlign, optional int textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional int lineBreak, optional int hyphens, optional androidx.compose.ui.text.style.TextMotion? textMotion);
ctor @Deprecated public TextStyle(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent);
ctor @Deprecated public TextStyle(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle);
ctor @Deprecated public TextStyle(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens);
- method public androidx.compose.ui.text.TextStyle copy(androidx.compose.ui.graphics.Brush? brush, optional float alpha, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens, optional androidx.compose.ui.text.style.TextMotion? textMotion);
- method public androidx.compose.ui.text.TextStyle copy(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens, optional androidx.compose.ui.text.style.TextMotion? textMotion);
+ method @Deprecated public androidx.compose.ui.text.TextStyle copy(androidx.compose.ui.graphics.Brush? brush, optional float alpha, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens, optional androidx.compose.ui.text.style.TextMotion? textMotion);
+ method public androidx.compose.ui.text.TextStyle copy(androidx.compose.ui.graphics.Brush? brush, optional float alpha, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle, optional int textAlign, optional int textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional int lineBreak, optional int hyphens, optional androidx.compose.ui.text.style.TextMotion? textMotion);
+ method @Deprecated public androidx.compose.ui.text.TextStyle copy(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens, optional androidx.compose.ui.text.style.TextMotion? textMotion);
+ method public androidx.compose.ui.text.TextStyle copy(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle, optional int textAlign, optional int textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional int lineBreak, optional int hyphens, optional androidx.compose.ui.text.style.TextMotion? textMotion);
method @Deprecated public androidx.compose.ui.text.TextStyle copy(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent);
method @Deprecated public androidx.compose.ui.text.TextStyle copy(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle);
method @Deprecated public androidx.compose.ui.text.TextStyle copy(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens);
@@ -551,17 +565,21 @@
method public androidx.compose.ui.text.font.FontStyle? getFontStyle();
method public androidx.compose.ui.text.font.FontSynthesis? getFontSynthesis();
method public androidx.compose.ui.text.font.FontWeight? getFontWeight();
- method public androidx.compose.ui.text.style.Hyphens? getHyphens();
+ method public int getHyphens();
+ method @Deprecated public androidx.compose.ui.text.style.Hyphens? getHyphens();
method public long getLetterSpacing();
- method public androidx.compose.ui.text.style.LineBreak? getLineBreak();
+ method public int getLineBreak();
+ method @Deprecated public androidx.compose.ui.text.style.LineBreak? getLineBreak();
method public long getLineHeight();
method public androidx.compose.ui.text.style.LineHeightStyle? getLineHeightStyle();
method public androidx.compose.ui.text.intl.LocaleList? getLocaleList();
method public androidx.compose.ui.text.PlatformTextStyle? getPlatformStyle();
method public androidx.compose.ui.graphics.Shadow? getShadow();
- method public androidx.compose.ui.text.style.TextAlign? getTextAlign();
+ method public int getTextAlign();
+ method @Deprecated public androidx.compose.ui.text.style.TextAlign? getTextAlign();
method public androidx.compose.ui.text.style.TextDecoration? getTextDecoration();
- method public androidx.compose.ui.text.style.TextDirection? getTextDirection();
+ method public int getTextDirection();
+ method @Deprecated public androidx.compose.ui.text.style.TextDirection? getTextDirection();
method public androidx.compose.ui.text.style.TextGeometricTransform? getTextGeometricTransform();
method public androidx.compose.ui.text.style.TextIndent? getTextIndent();
method public androidx.compose.ui.text.style.TextMotion? getTextMotion();
@@ -570,7 +588,8 @@
method @androidx.compose.runtime.Stable public androidx.compose.ui.text.TextStyle merge(androidx.compose.ui.text.ParagraphStyle other);
method @androidx.compose.runtime.Stable public androidx.compose.ui.text.TextStyle merge(androidx.compose.ui.text.SpanStyle other);
method @androidx.compose.runtime.Stable public androidx.compose.ui.text.TextStyle merge(optional androidx.compose.ui.text.TextStyle? other);
- method @androidx.compose.runtime.Stable public androidx.compose.ui.text.TextStyle merge(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.TextMotion? textMotion);
+ method @Deprecated @androidx.compose.runtime.Stable public androidx.compose.ui.text.TextStyle merge(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.TextMotion? textMotion);
+ method @androidx.compose.runtime.Stable public androidx.compose.ui.text.TextStyle merge(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle, optional int textAlign, optional int textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional int lineBreak, optional int hyphens, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.TextMotion? textMotion);
method @androidx.compose.runtime.Stable public operator androidx.compose.ui.text.TextStyle plus(androidx.compose.ui.text.ParagraphStyle other);
method @androidx.compose.runtime.Stable public operator androidx.compose.ui.text.TextStyle plus(androidx.compose.ui.text.SpanStyle other);
method @androidx.compose.runtime.Stable public operator androidx.compose.ui.text.TextStyle plus(androidx.compose.ui.text.TextStyle other);
@@ -581,6 +600,10 @@
property public final androidx.compose.ui.text.style.BaselineShift? baselineShift;
property public final androidx.compose.ui.graphics.Brush? brush;
property public final long color;
+ property @Deprecated public final androidx.compose.ui.text.style.Hyphens? deprecated_boxing_hyphens;
+ property @Deprecated public final androidx.compose.ui.text.style.LineBreak? deprecated_boxing_lineBreak;
+ property @Deprecated public final androidx.compose.ui.text.style.TextAlign? deprecated_boxing_textAlign;
+ property @Deprecated public final androidx.compose.ui.text.style.TextDirection? deprecated_boxing_textDirection;
property public final androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle;
property public final androidx.compose.ui.text.font.FontFamily? fontFamily;
property public final String? fontFeatureSettings;
@@ -588,17 +611,17 @@
property public final androidx.compose.ui.text.font.FontStyle? fontStyle;
property public final androidx.compose.ui.text.font.FontSynthesis? fontSynthesis;
property public final androidx.compose.ui.text.font.FontWeight? fontWeight;
- property public final androidx.compose.ui.text.style.Hyphens? hyphens;
+ property public final int hyphens;
property public final long letterSpacing;
- property public final androidx.compose.ui.text.style.LineBreak? lineBreak;
+ property public final int lineBreak;
property public final long lineHeight;
property public final androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle;
property public final androidx.compose.ui.text.intl.LocaleList? localeList;
property public final androidx.compose.ui.text.PlatformTextStyle? platformStyle;
property public final androidx.compose.ui.graphics.Shadow? shadow;
- property public final androidx.compose.ui.text.style.TextAlign? textAlign;
+ property public final int textAlign;
property public final androidx.compose.ui.text.style.TextDecoration? textDecoration;
- property public final androidx.compose.ui.text.style.TextDirection? textDirection;
+ property public final int textDirection;
property public final androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform;
property public final androidx.compose.ui.text.style.TextIndent? textIndent;
property public final androidx.compose.ui.text.style.TextMotion? textMotion;
@@ -1322,8 +1345,10 @@
public static final class Hyphens.Companion {
method public int getAuto();
method public int getNone();
+ method public int getUnspecified();
property public final int Auto;
property public final int None;
+ property public final int Unspecified;
}
@androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class LineBreak {
@@ -1342,9 +1367,11 @@
method public int getHeading();
method public int getParagraph();
method public int getSimple();
+ method public int getUnspecified();
property public final int Heading;
property public final int Paragraph;
property public final int Simple;
+ property public final int Unspecified;
}
@kotlin.jvm.JvmInline public static final value class LineBreak.Strategy {
@@ -1355,9 +1382,11 @@
method public int getBalanced();
method public int getHighQuality();
method public int getSimple();
+ method public int getUnspecified();
property public final int Balanced;
property public final int HighQuality;
property public final int Simple;
+ property public final int Unspecified;
}
@kotlin.jvm.JvmInline public static final value class LineBreak.Strictness {
@@ -1369,10 +1398,12 @@
method public int getLoose();
method public int getNormal();
method public int getStrict();
+ method public int getUnspecified();
property public final int Default;
property public final int Loose;
property public final int Normal;
property public final int Strict;
+ property public final int Unspecified;
}
@kotlin.jvm.JvmInline public static final value class LineBreak.WordBreak {
@@ -1382,8 +1413,10 @@
public static final class LineBreak.WordBreak.Companion {
method public int getDefault();
method public int getPhrase();
+ method public int getUnspecified();
property public final int Default;
property public final int Phrase;
+ property public final int Unspecified;
}
public final class LineHeightStyle {
@@ -1449,6 +1482,7 @@
method public int getLeft();
method public int getRight();
method public int getStart();
+ method public int getUnspecified();
method public java.util.List<androidx.compose.ui.text.style.TextAlign> values();
property public final int Center;
property public final int End;
@@ -1456,6 +1490,7 @@
property public final int Left;
property public final int Right;
property public final int Start;
+ property public final int Unspecified;
}
@androidx.compose.runtime.Immutable public final class TextDecoration {
@@ -1486,11 +1521,13 @@
method public int getContentOrRtl();
method public int getLtr();
method public int getRtl();
+ method public int getUnspecified();
property public final int Content;
property public final int ContentOrLtr;
property public final int ContentOrRtl;
property public final int Ltr;
property public final int Rtl;
+ property public final int Unspecified;
}
@androidx.compose.runtime.Immutable public final class TextGeometricTransform {
diff --git a/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/AndroidParagraphTest.kt b/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/AndroidParagraphTest.kt
index 1aef72f..f715019 100644
--- a/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/AndroidParagraphTest.kt
+++ b/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/AndroidParagraphTest.kt
@@ -2065,7 +2065,7 @@
text: String = "",
spanStyles: List<AnnotatedString.Range<SpanStyle>> = listOf(),
textIndent: TextIndent? = null,
- textAlign: TextAlign? = null,
+ textAlign: TextAlign = TextAlign.Unspecified,
ellipsis: Boolean = false,
maxLines: Int = Int.MAX_VALUE,
width: Float,
diff --git a/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/AndroidParagraphTextDirectionTest.kt b/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/AndroidParagraphTextDirectionTest.kt
index fabf7eb..4356938 100644
--- a/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/AndroidParagraphTextDirectionTest.kt
+++ b/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/AndroidParagraphTextDirectionTest.kt
@@ -52,34 +52,46 @@
}
@Test
- fun resolveTextDirectionHeuristics_nullTextDirection_nullLocaleList_defaultLtrLocale() {
+ fun resolveTextDirectionHeuristics_unspecifiedTextDirection_nullLocaleList_defaultLtrLocale() {
Locale.setDefault(ltrLocale)
assertThat(
- resolveTextDirectionHeuristics(textDirection = null, localeList = null)
+ resolveTextDirectionHeuristics(
+ textDirection = TextDirection.Unspecified,
+ localeList = null
+ )
).isEqualTo(LayoutCompat.TEXT_DIRECTION_FIRST_STRONG_LTR)
}
@Test
- fun resolveTextDirectionHeuristics_nullTextDirection_nullLocaleList_defaultRtlLocale() {
+ fun resolveTextDirectionHeuristics_unspecifiedTextDirection_nullLocaleList_defaultRtlLocale() {
Locale.setDefault(rtlLocale)
assertThat(
- resolveTextDirectionHeuristics(textDirection = null, localeList = null)
+ resolveTextDirectionHeuristics(
+ textDirection = TextDirection.Unspecified,
+ localeList = null
+ )
).isEqualTo(LayoutCompat.TEXT_DIRECTION_FIRST_STRONG_RTL)
}
@Test
- fun resolveTextDirectionHeuristics_nullTextDirection_ltrLocaleList() {
+ fun resolveTextDirectionHeuristics_unspecifiedTextDirection_ltrLocaleList() {
assertThat(
- resolveTextDirectionHeuristics(textDirection = null, localeList = ltrLocaleList)
+ resolveTextDirectionHeuristics(
+ textDirection = TextDirection.Unspecified,
+ localeList = ltrLocaleList
+ )
).isEqualTo(LayoutCompat.TEXT_DIRECTION_FIRST_STRONG_LTR)
}
@Test
- fun resolveTextDirectionHeuristics_nullTextDirection_RtlLocaleList() {
+ fun resolveTextDirectionHeuristics_unspecifiedTextDirection_RtlLocaleList() {
assertThat(
- resolveTextDirectionHeuristics(textDirection = null, localeList = rtlLocaleList)
+ resolveTextDirectionHeuristics(
+ textDirection = TextDirection.Unspecified,
+ localeList = rtlLocaleList
+ )
).isEqualTo(LayoutCompat.TEXT_DIRECTION_FIRST_STRONG_RTL)
}
diff --git a/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/MultiParagraphIntegrationTextDirectionTest.kt b/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/MultiParagraphIntegrationTextDirectionTest.kt
index 3eb3630..1b95fdb 100644
--- a/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/MultiParagraphIntegrationTextDirectionTest.kt
+++ b/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/MultiParagraphIntegrationTextDirectionTest.kt
@@ -58,34 +58,34 @@
}
@Test
- fun nullTextDirection_withLtrLocale_resolvesToLtr() {
+ fun unspecifiedTextDirection_withLtrLocale_resolvesToLtr() {
Locale.setDefault(ltrLocale)
val paragraph = multiParagraph(
text = AnnotatedString(""),
- textDirection = null
+ textDirection = TextDirection.Unspecified
)
assertThat(paragraph.getParagraphDirection(0)).isEqualTo(ResolvedTextDirection.Ltr)
}
@Test
- fun nullTextDirection_withRtlLocale_resolvesToRtl() {
+ fun unspecifiedTextDirection_withRtlLocale_resolvesToRtl() {
Locale.setDefault(rtlLocale)
val paragraph = multiParagraph(
text = AnnotatedString(""),
- textDirection = null
+ textDirection = TextDirection.Unspecified
)
assertThat(paragraph.getParagraphDirection(0)).isEqualTo(ResolvedTextDirection.Rtl)
}
@Test
- fun nullTextDirection_withLtrLocaleList_resolvesToLtr() {
+ fun unspecifiedTextDirection_withLtrLocaleList_resolvesToLtr() {
val paragraph = multiParagraph(
text = AnnotatedString(""),
- textDirection = null,
+ textDirection = TextDirection.Unspecified,
localeList = ltrLocaleList
)
@@ -93,10 +93,10 @@
}
@Test
- fun nullTextDirection_withRtlLocaleList_resolvesToRtl() {
+ fun unspecifiedTextDirection_withRtlLocaleList_resolvesToRtl() {
val paragraph = multiParagraph(
text = AnnotatedString(""),
- textDirection = null,
+ textDirection = TextDirection.Unspecified,
localeList = rtlLocaleList
)
@@ -272,7 +272,7 @@
private fun multiParagraph(
text: AnnotatedString,
localeList: LocaleList? = null,
- textDirection: TextDirection? = null,
+ textDirection: TextDirection = TextDirection.Unspecified,
fontSize: TextUnit = TextUnit.Unspecified,
width: Float = Float.MAX_VALUE
): MultiParagraph {
diff --git a/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/ParagraphIntegrationTextDirectionTest.kt b/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/ParagraphIntegrationTextDirectionTest.kt
index f5bcc9c..2902fa8 100644
--- a/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/ParagraphIntegrationTextDirectionTest.kt
+++ b/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/ParagraphIntegrationTextDirectionTest.kt
@@ -18,6 +18,7 @@
import androidx.compose.ui.text.intl.LocaleList
import androidx.compose.ui.text.style.ResolvedTextDirection
+import androidx.compose.ui.text.style.TextDirection
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Density
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -54,12 +55,12 @@
}
@Test
- fun nullTextDirection_withLtrLocale_resolvesToLtr() {
+ fun unspecifiedTextDirection_withLtrLocale_resolvesToLtr() {
Locale.setDefault(ltrLocale)
val paragraph = Paragraph(
text = "",
- style = TextStyle(textDirection = null),
+ style = TextStyle(textDirection = TextDirection.Unspecified),
constraints = Constraints(),
density = defaultDensity,
fontFamilyResolver = resourceLoader
@@ -69,12 +70,12 @@
}
@Test
- fun nullTextDirection_withRtlLocale_resolvesToRtl() {
+ fun unspecifiedTextDirection_withRtlLocale_resolvesToRtl() {
Locale.setDefault(rtlLocale)
val paragraph = Paragraph(
text = "",
- style = TextStyle(textDirection = null),
+ style = TextStyle(textDirection = TextDirection.Unspecified),
constraints = Constraints(),
density = defaultDensity,
fontFamilyResolver = resourceLoader
@@ -84,10 +85,13 @@
}
@Test
- fun nullTextDirection_withLtrLocaleList_resolvesToLtr() {
+ fun unspecifiedTextDirection_withLtrLocaleList_resolvesToLtr() {
val paragraph = Paragraph(
text = "",
- style = TextStyle(textDirection = null, localeList = ltrLocaleList),
+ style = TextStyle(
+ textDirection = TextDirection.Unspecified,
+ localeList = ltrLocaleList
+ ),
constraints = Constraints(),
density = defaultDensity,
fontFamilyResolver = resourceLoader
@@ -97,10 +101,13 @@
}
@Test
- fun nullTextDirection_withRtlLocaleList_resolvesToRtl() {
+ fun unspecifiedTextDirection_withRtlLocaleList_resolvesToRtl() {
val paragraph = Paragraph(
text = "",
- style = TextStyle(textDirection = null, localeList = rtlLocaleList),
+ style = TextStyle(
+ textDirection = TextDirection.Unspecified,
+ localeList = rtlLocaleList
+ ),
constraints = Constraints(),
density = defaultDensity,
fontFamilyResolver = resourceLoader
diff --git a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/AndroidParagraph.android.kt b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/AndroidParagraph.android.kt
index a0f59f2..026046f 100644
--- a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/AndroidParagraph.android.kt
+++ b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/AndroidParagraph.android.kt
@@ -152,9 +152,9 @@
val hyphens = toLayoutHyphenationFrequency(style.paragraphStyle.hyphens)
- val breakStrategy = toLayoutBreakStrategy(style.lineBreak?.strategy)
- val lineBreakStyle = toLayoutLineBreakStyle(style.lineBreak?.strictness)
- val lineBreakWordStyle = toLayoutLineBreakWordStyle(style.lineBreak?.wordBreak)
+ val breakStrategy = toLayoutBreakStrategy(style.lineBreak.strategy)
+ val lineBreakStyle = toLayoutLineBreakStyle(style.lineBreak.strictness)
+ val lineBreakWordStyle = toLayoutLineBreakWordStyle(style.lineBreak.wordBreak)
val ellipsize = if (ellipsis) {
TextUtils.TruncateAt.END
@@ -552,7 +552,7 @@
* Converts [TextAlign] into [TextLayout] alignment constants.
*/
@OptIn(InternalPlatformTextApi::class)
-private fun toLayoutAlign(align: TextAlign?): Int = when (align) {
+private fun toLayoutAlign(align: TextAlign): Int = when (align) {
TextAlign.Left -> ALIGN_LEFT
TextAlign.Right -> ALIGN_RIGHT
TextAlign.Center -> ALIGN_CENTER
@@ -562,7 +562,7 @@
}
@OptIn(InternalPlatformTextApi::class)
-private fun toLayoutHyphenationFrequency(hyphens: Hyphens?): Int = when (hyphens) {
+private fun toLayoutHyphenationFrequency(hyphens: Hyphens): Int = when (hyphens) {
Hyphens.Auto -> if (Build.VERSION.SDK_INT <= 32) {
HYPHENATION_FREQUENCY_FULL
} else {
@@ -573,7 +573,7 @@
}
@OptIn(InternalPlatformTextApi::class)
-private fun toLayoutBreakStrategy(breakStrategy: LineBreak.Strategy?): Int = when (breakStrategy) {
+private fun toLayoutBreakStrategy(breakStrategy: LineBreak.Strategy): Int = when (breakStrategy) {
LineBreak.Strategy.Simple -> BREAK_STRATEGY_SIMPLE
LineBreak.Strategy.HighQuality -> BREAK_STRATEGY_HIGH_QUALITY
LineBreak.Strategy.Balanced -> BREAK_STRATEGY_BALANCED
@@ -581,7 +581,7 @@
}
@OptIn(InternalPlatformTextApi::class)
-private fun toLayoutLineBreakStyle(lineBreakStrictness: LineBreak.Strictness?): Int =
+private fun toLayoutLineBreakStyle(lineBreakStrictness: LineBreak.Strictness): Int =
when (lineBreakStrictness) {
LineBreak.Strictness.Default -> LINE_BREAK_STYLE_NONE
LineBreak.Strictness.Loose -> LINE_BREAK_STYLE_LOOSE
@@ -591,7 +591,7 @@
}
@OptIn(InternalPlatformTextApi::class)
-private fun toLayoutLineBreakWordStyle(lineBreakWordStyle: LineBreak.WordBreak?): Int =
+private fun toLayoutLineBreakWordStyle(lineBreakWordStyle: LineBreak.WordBreak): Int =
when (lineBreakWordStyle) {
LineBreak.WordBreak.Default -> LINE_BREAK_WORD_STYLE_NONE
LineBreak.WordBreak.Phrase -> LINE_BREAK_WORD_STYLE_PHRASE
@@ -609,7 +609,8 @@
private fun shouldAttachIndentationFixSpan(textStyle: TextStyle, ellipsis: Boolean) =
with(textStyle) {
ellipsis && (letterSpacing != 0.sp && letterSpacing != TextUnit.Unspecified) &&
- (textAlign != null && textAlign != TextAlign.Start && textAlign != TextAlign.Justify)
+ (textAlign != TextAlign.Unspecified && textAlign != TextAlign.Start &&
+ textAlign != TextAlign.Justify)
}
@OptIn(InternalPlatformTextApi::class)
diff --git a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidParagraphIntrinsics.android.kt b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidParagraphIntrinsics.android.kt
index 9a20cf8..cd2f0b4 100644
--- a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidParagraphIntrinsics.android.kt
+++ b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidParagraphIntrinsics.android.kt
@@ -151,15 +151,15 @@
*/
@OptIn(InternalPlatformTextApi::class)
internal fun resolveTextDirectionHeuristics(
- textDirection: TextDirection? = null,
+ textDirection: TextDirection,
localeList: LocaleList? = null
): Int {
- return when (textDirection ?: TextDirection.Content) {
+ return when (textDirection) {
TextDirection.ContentOrLtr -> LayoutCompat.TEXT_DIRECTION_FIRST_STRONG_LTR
TextDirection.ContentOrRtl -> LayoutCompat.TEXT_DIRECTION_FIRST_STRONG_RTL
TextDirection.Ltr -> LayoutCompat.TEXT_DIRECTION_LTR
TextDirection.Rtl -> LayoutCompat.TEXT_DIRECTION_RTL
- TextDirection.Content -> {
+ TextDirection.Content, TextDirection.Unspecified -> {
val currentLocale = localeList?.let {
(it[0].platformLocale as AndroidLocale).javaLocale
} ?: Locale.getDefault()
diff --git a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/style/LineBreak.android.kt b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/style/LineBreak.android.kt
index 6bd954f..e981510 100644
--- a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/style/LineBreak.android.kt
+++ b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/style/LineBreak.android.kt
@@ -104,9 +104,11 @@
* </pre>
*/
actual val Simple: LineBreak = LineBreak(
- strategy = Strategy.Simple,
- strictness = Strictness.Normal,
- wordBreak = WordBreak.Default
+ packBytes(
+ Strategy.Simple.value,
+ Strictness.Normal.value,
+ WordBreak.Default.value
+ )
)
/**
@@ -128,9 +130,11 @@
* </pre>
*/
actual val Heading: LineBreak = LineBreak(
- strategy = Strategy.Balanced,
- strictness = Strictness.Loose,
- wordBreak = WordBreak.Phrase
+ packBytes(
+ Strategy.Balanced.value,
+ Strictness.Loose.value,
+ WordBreak.Phrase.value
+ )
)
/**
@@ -152,10 +156,18 @@
* </pre>
*/
actual val Paragraph: LineBreak = LineBreak(
- strategy = Strategy.HighQuality,
- strictness = Strictness.Strict,
- wordBreak = WordBreak.Default
+ packBytes(
+ Strategy.HighQuality.value,
+ Strictness.Strict.value,
+ WordBreak.Default.value
+ )
)
+
+ /**
+ * This represents an unset value, a usual replacement for "null" when a primitive value
+ * is desired.
+ */
+ actual val Unspecified: LineBreak = LineBreak(0)
}
/**
@@ -206,12 +218,19 @@
* </pre>
*/
val Balanced: Strategy = Strategy(3)
+
+ /**
+ * This represents an unset value, a usual replacement for "null" when a primitive value
+ * is desired.
+ */
+ val Unspecified: Strategy = Strategy(0)
}
override fun toString(): String = when (this) {
Simple -> "Strategy.Simple"
HighQuality -> "Strategy.HighQuality"
Balanced -> "Strategy.Balanced"
+ Unspecified -> "Strategy.Unspecified"
else -> "Invalid"
}
}
@@ -250,6 +269,12 @@
* small hiragana (ぁ), small katakana (ァ), halfwidth variants (ァ).
*/
val Strict: Strictness = Strictness(4)
+
+ /**
+ * This represents an unset value, a usual replacement for "null" when a primitive value
+ * is desired.
+ */
+ val Unspecified: Strictness = Strictness(0)
}
override fun toString(): String = when (this) {
@@ -257,6 +282,7 @@
Loose -> "Strictness.Loose"
Normal -> "Strictness.Normal"
Strict -> "Strictness.Strict"
+ Unspecified -> "Strictness.Unspecified"
else -> "Invalid"
}
}
@@ -310,11 +336,18 @@
* </pre>
*/
val Phrase: WordBreak = WordBreak(2)
+
+ /**
+ * This represents an unset value, a usual replacement for "null" when a primitive value
+ * is desired.
+ */
+ val Unspecified: WordBreak = WordBreak(0)
}
override fun toString(): String = when (this) {
Default -> "WordBreak.None"
Phrase -> "WordBreak.Phrase"
+ Unspecified -> "WordBreak.Unspecified"
else -> "Invalid"
}
}
diff --git a/compose/ui/ui-text/src/androidUnitTest/kotlin/androidx/compose/ui/text/ParagraphStyleTest.kt b/compose/ui/ui-text/src/androidUnitTest/kotlin/androidx/compose/ui/text/ParagraphStyleTest.kt
index 1d3f964..8b20ad5 100644
--- a/compose/ui/ui-text/src/androidUnitTest/kotlin/androidx/compose/ui/text/ParagraphStyleTest.kt
+++ b/compose/ui/ui-text/src/androidUnitTest/kotlin/androidx/compose/ui/text/ParagraphStyleTest.kt
@@ -52,9 +52,9 @@
}
@Test
- fun `merge textAlign other null, return original`() {
+ fun `merge textAlign other unspecified, return original`() {
val style = ParagraphStyle(textAlign = TextAlign.Justify)
- val otherStyle = ParagraphStyle(textAlign = null)
+ val otherStyle = ParagraphStyle(textAlign = TextAlign.Unspecified)
val newStyle = style.merge(otherStyle)
@@ -62,13 +62,13 @@
}
@Test
- fun `merge textAlign both null returns null`() {
- val style = ParagraphStyle(textAlign = null)
- val otherStyle = ParagraphStyle(textAlign = null)
+ fun `merge textAlign both unspecified returns unspecified`() {
+ val style = ParagraphStyle(textAlign = TextAlign.Unspecified)
+ val otherStyle = ParagraphStyle(textAlign = TextAlign.Unspecified)
val newStyle = style.merge(otherStyle)
- assertThat(newStyle.textAlign).isNull()
+ assertThat(newStyle.textAlign).isEqualTo(TextAlign.Unspecified)
}
@Test
@@ -86,7 +86,7 @@
@Test
fun `merge textDirection other null, returns original`() {
val style = ParagraphStyle(textDirection = TextDirection.Rtl)
- val otherStyle = ParagraphStyle(textDirection = null)
+ val otherStyle = ParagraphStyle(textDirection = TextDirection.Unspecified)
val newStyle = style.merge(otherStyle)
@@ -94,13 +94,13 @@
}
@Test
- fun `merge textDirection both null returns null`() {
- val style = ParagraphStyle(textDirection = null)
- val otherStyle = ParagraphStyle(textDirection = null)
+ fun `merge textDirection both unspecified returns unspecified`() {
+ val style = ParagraphStyle(textDirection = TextDirection.Unspecified)
+ val otherStyle = ParagraphStyle(textDirection = TextDirection.Unspecified)
val newStyle = style.merge(otherStyle)
- assertThat(newStyle.textDirection).isNull()
+ assertThat(newStyle.textDirection).isEqualTo(TextDirection.Unspecified)
}
@Test
@@ -114,9 +114,9 @@
}
@Test
- fun `merge hyphens other null, returns original`() {
+ fun `merge hyphens other unspecified, returns original`() {
val style = ParagraphStyle(hyphens = Hyphens.Auto)
- val otherStyle = ParagraphStyle(hyphens = null)
+ val otherStyle = ParagraphStyle(hyphens = Hyphens.Unspecified)
val newStyle = style.merge(otherStyle)
@@ -124,8 +124,8 @@
}
@Test
- fun `merge null hyphens other non-null, returns other's hyphens`() {
- val style = ParagraphStyle(hyphens = null)
+ fun `merge unspecified hyphens other non-null, returns other's hyphens`() {
+ val style = ParagraphStyle(hyphens = Hyphens.Unspecified)
val otherStyle = ParagraphStyle(hyphens = Hyphens.Auto)
val newStyle = style.merge(otherStyle)
@@ -134,13 +134,13 @@
}
@Test
- fun `merge hyphens both null returns null`() {
- val style = ParagraphStyle(hyphens = null)
- val otherStyle = ParagraphStyle(hyphens = null)
+ fun `merge hyphens both unspecified returns unspecified`() {
+ val style = ParagraphStyle(hyphens = Hyphens.Unspecified)
+ val otherStyle = ParagraphStyle(hyphens = Hyphens.Unspecified)
val newStyle = style.merge(otherStyle)
- assertThat(newStyle.hyphens).isNull()
+ assertThat(newStyle.hyphens).isEqualTo(Hyphens.Unspecified)
}
@Test
@@ -204,8 +204,8 @@
}
@Test
- fun `merge null with non-null lineBreak uses other's lineBreak`() {
- val style = ParagraphStyle(lineBreak = null)
+ fun `merge unspecified with non-null lineBreak uses other's lineBreak`() {
+ val style = ParagraphStyle(lineBreak = LineBreak.Unspecified)
val otherStyle = ParagraphStyle(lineBreak = LineBreak.Heading)
val mergedStyle = style.merge(otherStyle)
@@ -214,9 +214,9 @@
}
@Test
- fun `merge non-null with null lineBreak returns original's lineBreak`() {
+ fun `merge non-null with unspecified lineBreak returns original's lineBreak`() {
val style = ParagraphStyle(lineBreak = LineBreak.Paragraph)
- val otherStyle = ParagraphStyle(lineBreak = null)
+ val otherStyle = ParagraphStyle(lineBreak = LineBreak.Unspecified)
val mergedStyle = style.merge(otherStyle)
@@ -224,13 +224,13 @@
}
@Test
- fun `merge null with null lineBreak returns null`() {
- val style = ParagraphStyle(lineBreak = null)
- val otherStyle = ParagraphStyle(lineBreak = null)
+ fun `merge unspecified with unspecified lineBreak returns unspecified`() {
+ val style = ParagraphStyle(lineBreak = LineBreak.Unspecified)
+ val otherStyle = ParagraphStyle(lineBreak = LineBreak.Unspecified)
val mergedStyle = style.merge(otherStyle)
- assertThat(mergedStyle.lineBreak).isEqualTo(null)
+ assertThat(mergedStyle.lineBreak).isEqualTo(LineBreak.Unspecified)
}
@Test
@@ -271,13 +271,13 @@
}
@Test
- fun `lerp textAlign with a null, b not null and t is smaller than half`() {
- val style1 = ParagraphStyle(textAlign = null)
+ fun `lerp textAlign with a unspecified, b not null and t is smaller than half`() {
+ val style1 = ParagraphStyle(textAlign = TextAlign.Unspecified)
val style2 = ParagraphStyle(textAlign = TextAlign.Right)
val newStyle = lerp(start = style1, stop = style2, fraction = 0.4f)
- assertThat(newStyle.textAlign).isNull()
+ assertThat(newStyle.textAlign).isEqualTo(TextAlign.Unspecified)
}
@Test
@@ -301,13 +301,13 @@
}
@Test
- fun `lerp textDirection with a null, b not null and t is smaller than half`() {
- val style1 = ParagraphStyle(textDirection = null)
+ fun `lerp textDirection with a unspecified, b not null and t is smaller than half`() {
+ val style1 = ParagraphStyle(textDirection = TextDirection.Unspecified)
val style2 = ParagraphStyle(textDirection = TextDirection.Rtl)
val newStyle = lerp(start = style1, stop = style2, fraction = 0.4f)
- assertThat(newStyle.textDirection).isNull()
+ assertThat(newStyle.textDirection).isEqualTo(TextDirection.Unspecified)
}
@Test
@@ -331,18 +331,18 @@
}
@Test
- fun `lerp hyphens with a null, b not null and t is smaller than half`() {
- val style1 = ParagraphStyle(hyphens = null)
+ fun `lerp hyphens with a unspecified, b not null and t is smaller than half`() {
+ val style1 = ParagraphStyle(hyphens = Hyphens.Unspecified)
val style2 = ParagraphStyle(hyphens = Hyphens.Auto)
val newStyle = lerp(start = style1, stop = style2, fraction = 0.4f)
- assertThat(newStyle.hyphens).isNull()
+ assertThat(newStyle.hyphens).isEqualTo(Hyphens.Unspecified)
}
@Test
- fun `lerp hyphens with a null, b not null and t is equal to half`() {
- val style1 = ParagraphStyle(hyphens = null)
+ fun `lerp hyphens with a unspecified, b not null and t is equal to half`() {
+ val style1 = ParagraphStyle(hyphens = Hyphens.Unspecified)
val style2 = ParagraphStyle(hyphens = Hyphens.Auto)
val newStyle = lerp(start = style1, stop = style2, fraction = 0.5f)
@@ -491,43 +491,43 @@
}
@Test
- fun `lerp with non-null start, null end, closer to start has non-null lineBreak`() {
+ fun `lerp with non-null start, unspecified end, closer to start has non-null lineBreak`() {
val style = ParagraphStyle(lineBreak = LineBreak.Heading)
- val otherStyle = ParagraphStyle(lineHeightStyle = null)
+ val otherStyle = ParagraphStyle(lineBreak = LineBreak.Unspecified)
val lerpedStyle = lerp(start = style, stop = otherStyle, fraction = 0.4f)
- assertThat(lerpedStyle.lineBreak).isSameInstanceAs(style.lineBreak)
+ assertThat(lerpedStyle.lineBreak).isEqualTo(style.lineBreak)
}
@Test
- fun `lerp with non-null start, null end, closer to end has null lineBreak`() {
+ fun `lerp with non-null start, unspecified end, closer to end has unspecified lineBreak`() {
val style = ParagraphStyle(lineBreak = LineBreak.Heading)
- val otherStyle = ParagraphStyle(lineHeightStyle = null)
+ val otherStyle = ParagraphStyle(lineBreak = LineBreak.Unspecified)
val lerpedStyle = lerp(start = style, stop = otherStyle, fraction = 0.6f)
- assertThat(lerpedStyle.lineBreak).isNull()
+ assertThat(lerpedStyle.lineBreak).isEqualTo(LineBreak.Unspecified)
}
@Test
- fun `lerp with null start, non-null end, closer to start has null lineBreak`() {
+ fun `lerp with unspecified start, non-null end, closer to start has unspecified lineBreak`() {
val style = ParagraphStyle(lineHeightStyle = null)
val otherStyle = ParagraphStyle(lineBreak = LineBreak.Heading)
val lerpedStyle = lerp(start = style, stop = otherStyle, fraction = 0.4f)
- assertThat(lerpedStyle.lineBreak).isNull()
+ assertThat(lerpedStyle.lineBreak).isEqualTo(LineBreak.Unspecified)
}
@Test
- fun `lerp with null start, non-null end, closer to end has non-null lineBreak`() {
- val style = ParagraphStyle(lineBreak = null)
+ fun `lerp with unspecified start, non-null end, closer to end has non-null lineBreak`() {
+ val style = ParagraphStyle(lineBreak = LineBreak.Unspecified)
val otherStyle = ParagraphStyle(lineBreak = LineBreak.Heading)
val lerpedStyle = lerp(start = style, stop = otherStyle, fraction = 0.6f)
- assertThat(lerpedStyle.lineBreak).isSameInstanceAs(otherStyle.lineBreak)
+ assertThat(lerpedStyle.lineBreak).isEqualTo(otherStyle.lineBreak)
}
@Test
@@ -627,8 +627,8 @@
}
@Test
- fun `equals return false for null and non-null hyphens`() {
- val style = ParagraphStyle(hyphens = null)
+ fun `equals return false for unspecified and non-null hyphens`() {
+ val style = ParagraphStyle(hyphens = Hyphens.Unspecified)
val otherStyle = ParagraphStyle(hyphens = Hyphens.Auto)
assertThat(style == otherStyle).isFalse()
@@ -643,9 +643,9 @@
}
@Test
- fun `equals return true for both null hyphens`() {
- val style = ParagraphStyle(hyphens = null)
- val otherStyle = ParagraphStyle(hyphens = null)
+ fun `equals return true for both unspecified hyphens`() {
+ val style = ParagraphStyle(hyphens = Hyphens.Unspecified)
+ val otherStyle = ParagraphStyle(hyphens = Hyphens.Unspecified)
assertThat(style == otherStyle).isTrue()
}
diff --git a/compose/ui/ui-text/src/androidUnitTest/kotlin/androidx/compose/ui/text/TextStyleLayoutAttributesTest.kt b/compose/ui/ui-text/src/androidUnitTest/kotlin/androidx/compose/ui/text/TextStyleLayoutAttributesTest.kt
index 3c7d779..2c30f67 100644
--- a/compose/ui/ui-text/src/androidUnitTest/kotlin/androidx/compose/ui/text/TextStyleLayoutAttributesTest.kt
+++ b/compose/ui/ui-text/src/androidUnitTest/kotlin/androidx/compose/ui/text/TextStyleLayoutAttributesTest.kt
@@ -372,7 +372,12 @@
getProperty("lineHeightStyle"),
getProperty("hyphens"),
getProperty("lineBreak"),
- getProperty("textMotion")
+ getProperty("textMotion"),
+ // deprecated properties kept for binary compatibility
+ getProperty("deprecated_boxing_textAlign"),
+ getProperty("deprecated_boxing_textDirection"),
+ getProperty("deprecated_boxing_hyphens"),
+ getProperty("deprecated_boxing_lineBreak")
)
val textStyleProperties = TextStyle::class.memberProperties.map { Property(it) }
diff --git a/compose/ui/ui-text/src/androidUnitTest/kotlin/androidx/compose/ui/text/TextStyleTest.kt b/compose/ui/ui-text/src/androidUnitTest/kotlin/androidx/compose/ui/text/TextStyleTest.kt
index 040d279..5ba06a7 100644
--- a/compose/ui/ui-text/src/androidUnitTest/kotlin/androidx/compose/ui/text/TextStyleTest.kt
+++ b/compose/ui/ui-text/src/androidUnitTest/kotlin/androidx/compose/ui/text/TextStyleTest.kt
@@ -77,10 +77,13 @@
assertThat(style.localeList).isNull()
assertThat(style.background).isEqualTo(Color.Unspecified)
assertThat(style.drawStyle).isNull()
+ assertThat(style.textAlign).isEqualTo(TextAlign.Unspecified)
+ assertThat(style.textDirection).isEqualTo(TextDirection.Unspecified)
assertThat(style.textDecoration).isNull()
assertThat(style.fontFamily).isNull()
assertThat(style.platformStyle).isNull()
- assertThat(style.hyphens).isNull()
+ assertThat(style.lineBreak).isEqualTo(LineBreak.Unspecified)
+ assertThat(style.hyphens).isEqualTo(Hyphens.Unspecified)
assertThat(style.textMotion).isNull()
}
@@ -89,7 +92,7 @@
val style = TextStyle(hyphens = Hyphens.Auto)
assertThat(style.hyphens).isEqualTo(Hyphens.Auto)
- assertThat(style.lineBreak).isNull()
+ assertThat(style.lineBreak).isEqualTo(LineBreak.Unspecified)
}
@Test
@@ -159,7 +162,6 @@
assertThat(style.copy().drawStyle).isEqualTo(Stroke(2f))
}
- @Suppress("DEPRECATION")
@Test
fun `platformTextStyle copy with existing drawStyle should not remove drawStyle`() {
val style = TextStyle(drawStyle = Stroke(2f))
@@ -333,9 +335,9 @@
}
@Test
- fun `merge with other's hyphens is null should use this hyphens`() {
+ fun `merge with other's hyphens is unspecified should use this hyphens`() {
val style = TextStyle(hyphens = Hyphens.Auto)
- val otherStyle = TextStyle(hyphens = null)
+ val otherStyle = TextStyle(hyphens = Hyphens.Unspecified)
val newStyle = style.merge(otherStyle)
@@ -638,21 +640,21 @@
}
@Test
- fun `merge textAlign other null, return original`() {
+ fun `merge textAlign other unspecified, return original`() {
val style = TextStyle(textAlign = TextAlign.Justify)
- val newStyle = style.merge(TextStyle(textAlign = null))
+ val newStyle = style.merge(TextStyle(textAlign = TextAlign.Unspecified))
assertThat(newStyle.textAlign).isEqualTo(style.textAlign)
}
@Test
- fun `merge textAlign both null returns null`() {
- val style = TextStyle(textAlign = null)
+ fun `merge textAlign both unspecified returns unspecified`() {
+ val style = TextStyle(textAlign = TextAlign.Unspecified)
- val newStyle = style.merge(TextStyle(textAlign = null))
+ val newStyle = style.merge(TextStyle(textAlign = TextAlign.Unspecified))
- assertThat(newStyle.textAlign).isNull()
+ assertThat(newStyle.textAlign).isEqualTo(TextAlign.Unspecified)
}
@Test
@@ -666,21 +668,21 @@
}
@Test
- fun `merge textDirection other null, returns original`() {
+ fun `merge textDirection other unspecified, returns original`() {
val style = TextStyle(textDirection = TextDirection.Rtl)
- val newStyle = style.merge(TextStyle(textDirection = null))
+ val newStyle = style.merge(TextStyle(textDirection = TextDirection.Unspecified))
assertThat(newStyle.textDirection).isEqualTo(style.textDirection)
}
@Test
- fun `merge textDirection both null returns null`() {
- val style = TextStyle(textDirection = null)
+ fun `merge textDirection both unspecified returns unspecified`() {
+ val style = TextStyle(textDirection = TextDirection.Unspecified)
- val newStyle = style.merge(TextStyle(textDirection = null))
+ val newStyle = style.merge(TextStyle(textDirection = TextDirection.Unspecified))
- assertThat(newStyle.textDirection).isNull()
+ assertThat(newStyle.textDirection).isEqualTo(TextDirection.Unspecified)
}
@Test
@@ -804,8 +806,8 @@
}
@Test
- fun `merge null and non-null lineBreak uses other's lineBreak`() {
- val style = TextStyle(lineBreak = null)
+ fun `merge unspecified linebreak and lineBreak uses other's lineBreak`() {
+ val style = TextStyle(lineBreak = LineBreak.Unspecified)
val otherStyle = TextStyle(lineBreak = LineBreak.Heading)
val mergedStyle = style.merge(otherStyle)
@@ -814,9 +816,9 @@
}
@Test
- fun `merge non-null and null lineBreak uses original`() {
+ fun `merge linebreak and unspecified lineBreak uses original`() {
val style = TextStyle(lineBreak = LineBreak.Paragraph)
- val otherStyle = TextStyle(lineBreak = null)
+ val otherStyle = TextStyle(lineBreak = LineBreak.Unspecified)
val mergedStyle = style.merge(otherStyle)
@@ -824,13 +826,13 @@
}
@Test
- fun `merge with both null lineBreak uses null`() {
- val style = TextStyle(lineBreak = null)
- val otherStyle = TextStyle(lineBreak = null)
+ fun `merge with both unspecified lineBreak uses unspecified`() {
+ val style = TextStyle(lineBreak = LineBreak.Unspecified)
+ val otherStyle = TextStyle(lineBreak = LineBreak.Unspecified)
val mergedStyle = style.merge(otherStyle)
- assertThat(mergedStyle.lineBreak).isEqualTo(null)
+ assertThat(mergedStyle.lineBreak).isEqualTo(LineBreak.Unspecified)
}
@Test
@@ -1300,13 +1302,13 @@
}
@Test
- fun `lerp textAlign with a null, b not null and t is smaller than half`() {
- val style1 = TextStyle(textAlign = null)
+ fun `lerp textAlign with a unspecified, b not null and t is smaller than half`() {
+ val style1 = TextStyle(textAlign = TextAlign.Unspecified)
val style2 = TextStyle(textAlign = TextAlign.Right)
val newStyle = lerp(start = style1, stop = style2, fraction = 0.4f)
- assertThat(newStyle.textAlign).isNull()
+ assertThat(newStyle.textAlign).isEqualTo(TextAlign.Unspecified)
}
@Test
@@ -1330,13 +1332,13 @@
}
@Test
- fun `lerp textDirection with a null, b not null and t is smaller than half`() {
- val style1 = TextStyle(textDirection = null)
+ fun `lerp textDirection with a unspecified, b not null and t is smaller than half`() {
+ val style1 = TextStyle(textDirection = TextDirection.Unspecified)
val style2 = TextStyle(textDirection = TextDirection.Rtl)
val newStyle = lerp(start = style1, stop = style2, fraction = 0.4f)
- assertThat(newStyle.textDirection).isNull()
+ assertThat(newStyle.textDirection).isEqualTo(TextDirection.Unspecified)
}
@Test
@@ -1425,7 +1427,6 @@
@Test
fun `copy without platformStyle uses existing platformStyle`() {
- @Suppress("DEPRECATION")
val style = TextStyle(
platformStyle = PlatformTextStyle(includeFontPadding = false)
)
@@ -1471,43 +1472,43 @@
}
@Test
- fun `lerp with non-null start, null end, closer to start has non-null lineBreak`() {
+ fun `lerp with non-null start, unspecified end, closer to start has non-null lineBreak`() {
val style = TextStyle(lineBreak = LineBreak.Heading)
- val otherStyle = TextStyle(lineHeightStyle = null)
+ val otherStyle = TextStyle(lineBreak = LineBreak.Unspecified)
val lerpedStyle = lerp(start = style, stop = otherStyle, fraction = 0.4f)
- assertThat(lerpedStyle.lineBreak).isSameInstanceAs(style.lineBreak)
+ assertThat(lerpedStyle.lineBreak).isEqualTo(style.lineBreak)
}
@Test
- fun `lerp with non-null start, null end, closer to end has null lineBreak`() {
+ fun `lerp with non-null start, unspecified end, closer to end has unspecified lineBreak`() {
val style = TextStyle(lineBreak = LineBreak.Heading)
- val otherStyle = TextStyle(lineHeightStyle = null)
+ val otherStyle = TextStyle(lineBreak = LineBreak.Unspecified)
val lerpedStyle = lerp(start = style, stop = otherStyle, fraction = 0.6f)
- assertThat(lerpedStyle.lineBreak).isNull()
+ assertThat(lerpedStyle.lineBreak).isEqualTo(LineBreak.Unspecified)
}
@Test
- fun `lerp with null start, non-null end, closer to start has null lineBreak`() {
- val style = TextStyle(lineHeightStyle = null)
+ fun `lerp with unspecified start, non-null end, closer to start has null lineBreak`() {
+ val style = TextStyle(lineBreak = LineBreak.Unspecified)
val otherStyle = TextStyle(lineBreak = LineBreak.Heading)
val lerpedStyle = lerp(start = style, stop = otherStyle, fraction = 0.4f)
- assertThat(lerpedStyle.lineBreak).isNull()
+ assertThat(lerpedStyle.lineBreak).isEqualTo(LineBreak.Unspecified)
}
@Test
- fun `lerp with null start, non-null end, closer to end has non-null lineBreak`() {
- val style = TextStyle(lineBreak = null)
+ fun `lerp with unspecified start, non-null end, closer to end has non-null lineBreak`() {
+ val style = TextStyle(lineBreak = LineBreak.Unspecified)
val otherStyle = TextStyle(lineBreak = LineBreak.Heading)
val lerpedStyle = lerp(start = style, stop = otherStyle, fraction = 0.6f)
- assertThat(lerpedStyle.lineBreak).isSameInstanceAs(otherStyle.lineBreak)
+ assertThat(lerpedStyle.lineBreak).isEqualTo(otherStyle.lineBreak)
}
@Test
@@ -1889,18 +1890,18 @@
}
@Test
- fun resolveTextDirection_null() {
+ fun resolveTextDirection_unspecified() {
assertThat(
resolveTextDirection(
LayoutDirection.Ltr,
- null
+ TextDirection.Unspecified
)
).isEqualTo(TextDirection.Ltr)
assertThat(
resolveTextDirection(
LayoutDirection.Rtl,
- null
+ TextDirection.Unspecified
)
).isEqualTo(TextDirection.Rtl)
}
@@ -2053,7 +2054,6 @@
/**
* Compute a distinct value for [KParameter] from the value in [kParameter]
*/
-@OptIn(ExperimentalTextApi::class)
private fun TextStyle.getNotEqualValueFor(kParameter: KParameter): Any {
val prop: KProperty1<TextStyle, *> =
TextStyle::class.memberProperties.first { it.name == kParameter.name }
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/MultiParagraphIntrinsics.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/MultiParagraphIntrinsics.kt
index b1f07cb..a4d967b 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/MultiParagraphIntrinsics.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/MultiParagraphIntrinsics.kt
@@ -132,7 +132,7 @@
style: ParagraphStyle,
defaultStyle: ParagraphStyle
): ParagraphStyle {
- return style.textDirection?.let { style } ?: style.copy(
+ return if (style.textDirection != TextDirection.Unspecified) style else style.copy(
textDirection = defaultStyle.textDirection
)
}
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/ParagraphStyle.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/ParagraphStyle.kt
index aea5a76..4137c8c 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/ParagraphStyle.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/ParagraphStyle.kt
@@ -63,24 +63,63 @@
* @see TextStyle
*/
@Immutable
-class ParagraphStyle constructor(
- val textAlign: TextAlign? = null,
- val textDirection: TextDirection? = null,
+class ParagraphStyle(
+ val textAlign: TextAlign = TextAlign.Unspecified,
+ val textDirection: TextDirection = TextDirection.Unspecified,
val lineHeight: TextUnit = TextUnit.Unspecified,
val textIndent: TextIndent? = null,
val platformStyle: PlatformParagraphStyle? = null,
val lineHeightStyle: LineHeightStyle? = null,
- val lineBreak: LineBreak? = null,
- val hyphens: Hyphens? = null,
+ val lineBreak: LineBreak = LineBreak.Unspecified,
+ val hyphens: Hyphens = Hyphens.Unspecified,
val textMotion: TextMotion? = null
) {
+ @Deprecated("Kept for backwards compatibility.", level = DeprecationLevel.WARNING)
+ @get:JvmName("getTextAlign")
+ @Suppress("unused")
+ val deprecated_boxing_textAlign: TextAlign? get() = this.textAlign
- // these public nullable parameters box - do it now (init) not during every paragraph resolution
- // for future value(int) parameters please avoid boxing by defining Unspecified
- internal val textAlignOrDefault: TextAlign = textAlign ?: TextAlign.Start
- internal val lineBreakOrDefault: LineBreak = lineBreak ?: LineBreak.Simple
- internal val hyphensOrDefault: Hyphens = hyphens ?: Hyphens.None
+ @Deprecated("Kept for backwards compatibility.", level = DeprecationLevel.WARNING)
+ @get:JvmName("getTextDirection")
+ @Suppress("unused")
+ val deprecated_boxing_textDirection: TextDirection? get() = this.textDirection
+ @Deprecated("Kept for backwards compatibility.", level = DeprecationLevel.WARNING)
+ @get:JvmName("getHyphens")
+ @Suppress("unused")
+ val deprecated_boxing_hyphens: Hyphens? get() = this.hyphens
+
+ @Deprecated("Kept for backwards compatibility.", level = DeprecationLevel.WARNING)
+ @get:JvmName("getLineBreak")
+ @Suppress("unused")
+ val deprecated_boxing_lineBreak: LineBreak? get() = this.lineBreak
+
+ @Deprecated("ParagraphStyle constructors that take nullable TextAlign, " +
+ "TextDirection, LineBreak, and Hyphens are deprecated. Please use a new constructor " +
+ "where these parameters are non-nullable. Null value has been replaced by a special " +
+ "Unspecified object for performance reason.",
+ level = DeprecationLevel.HIDDEN)
+ constructor(
+ textAlign: TextAlign? = null,
+ textDirection: TextDirection? = null,
+ lineHeight: TextUnit = TextUnit.Unspecified,
+ textIndent: TextIndent? = null,
+ platformStyle: PlatformParagraphStyle? = null,
+ lineHeightStyle: LineHeightStyle? = null,
+ lineBreak: LineBreak? = null,
+ hyphens: Hyphens? = null,
+ textMotion: TextMotion? = null
+ ) : this(
+ textAlign = textAlign ?: TextAlign.Unspecified,
+ textDirection = textDirection ?: TextDirection.Unspecified,
+ lineHeight = lineHeight,
+ textIndent = textIndent,
+ platformStyle = platformStyle,
+ lineHeightStyle = lineHeightStyle,
+ lineBreak = lineBreak ?: LineBreak.Unspecified,
+ hyphens = hyphens ?: Hyphens.Unspecified,
+ textMotion = textMotion
+ )
@Deprecated(
"ParagraphStyle constructors that do not take new stable parameters " +
"like LineHeightStyle, LineBreak, Hyphens are deprecated. Please use the new stable " +
@@ -93,14 +132,14 @@
lineHeight: TextUnit = TextUnit.Unspecified,
textIndent: TextIndent? = null
) : this(
- textAlign = textAlign,
- textDirection = textDirection,
+ textAlign = textAlign ?: TextAlign.Unspecified,
+ textDirection = textDirection ?: TextDirection.Unspecified,
lineHeight = lineHeight,
textIndent = textIndent,
platformStyle = null,
lineHeightStyle = null,
- lineBreak = null,
- hyphens = null,
+ lineBreak = LineBreak.Unspecified,
+ hyphens = Hyphens.Unspecified,
textMotion = null
)
@@ -118,14 +157,14 @@
platformStyle: PlatformParagraphStyle? = null,
lineHeightStyle: LineHeightStyle? = null
) : this(
- textAlign = textAlign,
- textDirection = textDirection,
+ textAlign = textAlign ?: TextAlign.Unspecified,
+ textDirection = textDirection ?: TextDirection.Unspecified,
lineHeight = lineHeight,
textIndent = textIndent,
platformStyle = platformStyle,
lineHeightStyle = lineHeightStyle,
- lineBreak = null,
- hyphens = null,
+ lineBreak = LineBreak.Unspecified,
+ hyphens = Hyphens.Unspecified,
textMotion = null
)
@@ -145,14 +184,14 @@
lineBreak: LineBreak? = null,
hyphens: Hyphens? = null
) : this(
- textAlign = textAlign,
- textDirection = textDirection,
+ textAlign = textAlign ?: TextAlign.Unspecified,
+ textDirection = textDirection ?: TextDirection.Unspecified,
lineHeight = lineHeight,
textIndent = textIndent,
platformStyle = platformStyle,
lineHeightStyle = lineHeightStyle,
- lineBreak = lineBreak,
- hyphens = hyphens,
+ lineBreak = lineBreak ?: LineBreak.Unspecified,
+ hyphens = hyphens ?: Hyphens.Unspecified,
textMotion = null
)
@@ -207,8 +246,8 @@
textIndent: TextIndent? = this.textIndent
): ParagraphStyle {
return ParagraphStyle(
- textAlign = textAlign,
- textDirection = textDirection,
+ textAlign = textAlign ?: TextAlign.Unspecified,
+ textDirection = textDirection ?: TextDirection.Unspecified,
lineHeight = lineHeight,
textIndent = textIndent,
platformStyle = this.platformStyle,
@@ -234,8 +273,8 @@
lineHeightStyle: LineHeightStyle? = this.lineHeightStyle
): ParagraphStyle {
return ParagraphStyle(
- textAlign = textAlign,
- textDirection = textDirection,
+ textAlign = textAlign ?: TextAlign.Unspecified,
+ textDirection = textDirection ?: TextDirection.Unspecified,
lineHeight = lineHeight,
textIndent = textIndent,
platformStyle = platformStyle,
@@ -263,18 +302,23 @@
hyphens: Hyphens? = this.hyphens
): ParagraphStyle {
return ParagraphStyle(
- textAlign = textAlign,
- textDirection = textDirection,
+ textAlign = textAlign ?: TextAlign.Unspecified,
+ textDirection = textDirection ?: TextDirection.Unspecified,
lineHeight = lineHeight,
textIndent = textIndent,
platformStyle = platformStyle,
lineHeightStyle = lineHeightStyle,
- lineBreak = lineBreak,
- hyphens = hyphens,
+ lineBreak = lineBreak ?: LineBreak.Unspecified,
+ hyphens = hyphens ?: Hyphens.Unspecified,
textMotion = this.textMotion
)
}
+ @Deprecated("ParagraphStyle copy constructors that take nullable TextAlign, " +
+ "TextDirection, LineBreak, and Hyphens are deprecated. Please use a new constructor " +
+ "where these parameters are non-nullable. Null value has been replaced by a special " +
+ "Unspecified object for performance reason.",
+ level = DeprecationLevel.HIDDEN)
fun copy(
textAlign: TextAlign? = this.textAlign,
textDirection: TextDirection? = this.textDirection,
@@ -287,6 +331,30 @@
textMotion: TextMotion? = this.textMotion
): ParagraphStyle {
return ParagraphStyle(
+ textAlign = textAlign ?: TextAlign.Unspecified,
+ textDirection = textDirection ?: TextDirection.Unspecified,
+ lineHeight = lineHeight,
+ textIndent = textIndent,
+ platformStyle = platformStyle,
+ lineHeightStyle = lineHeightStyle,
+ lineBreak = lineBreak ?: LineBreak.Unspecified,
+ hyphens = hyphens ?: Hyphens.Unspecified,
+ textMotion = textMotion
+ )
+ }
+
+ fun copy(
+ textAlign: TextAlign = this.textAlign,
+ textDirection: TextDirection = this.textDirection,
+ lineHeight: TextUnit = this.lineHeight,
+ textIndent: TextIndent? = this.textIndent,
+ platformStyle: PlatformParagraphStyle? = this.platformStyle,
+ lineHeightStyle: LineHeightStyle? = this.lineHeightStyle,
+ lineBreak: LineBreak = this.lineBreak,
+ hyphens: Hyphens = this.hyphens,
+ textMotion: TextMotion? = this.textMotion
+ ): ParagraphStyle {
+ return ParagraphStyle(
textAlign = textAlign,
textDirection = textDirection,
lineHeight = lineHeight,
@@ -317,14 +385,14 @@
}
override fun hashCode(): Int {
- var result = textAlign?.hashCode() ?: 0
- result = 31 * result + (textDirection?.hashCode() ?: 0)
+ var result = textAlign.hashCode()
+ result = 31 * result + textDirection.hashCode()
result = 31 * result + lineHeight.hashCode()
result = 31 * result + (textIndent?.hashCode() ?: 0)
result = 31 * result + (platformStyle?.hashCode() ?: 0)
result = 31 * result + (lineHeightStyle?.hashCode() ?: 0)
- result = 31 * result + (lineBreak?.hashCode() ?: 0)
- result = 31 * result + (hyphens?.hashCode() ?: 0)
+ result = 31 * result + lineBreak.hashCode()
+ result = 31 * result + hyphens.hashCode()
result = 31 * result + (textMotion?.hashCode() ?: 0)
return result
}
@@ -399,27 +467,26 @@
style: ParagraphStyle,
direction: LayoutDirection
) = ParagraphStyle(
- textAlign = style.textAlignOrDefault,
+ textAlign = if (style.textAlign == TextAlign.Unspecified) TextAlign.Start else style.textAlign,
textDirection = resolveTextDirection(direction, style.textDirection),
lineHeight = if (style.lineHeight.isUnspecified) DefaultLineHeight else style.lineHeight,
textIndent = style.textIndent ?: TextIndent.None,
platformStyle = style.platformStyle,
lineHeightStyle = style.lineHeightStyle,
- lineBreak = style.lineBreakOrDefault,
- hyphens = style.hyphensOrDefault,
+ lineBreak = if (style.lineBreak == LineBreak.Unspecified) LineBreak.Simple else style.lineBreak,
+ hyphens = if (style.hyphens == Hyphens.Unspecified) Hyphens.None else style.hyphens,
textMotion = style.textMotion ?: TextMotion.Static
)
- @OptIn(ExperimentalTextApi::class)
internal fun ParagraphStyle.fastMerge(
- textAlign: TextAlign?,
- textDirection: TextDirection?,
+ textAlign: TextAlign,
+ textDirection: TextDirection,
lineHeight: TextUnit,
textIndent: TextIndent?,
platformStyle: PlatformParagraphStyle?,
lineHeightStyle: LineHeightStyle?,
- lineBreak: LineBreak?,
- hyphens: Hyphens?,
+ lineBreak: LineBreak,
+ hyphens: Hyphens,
textMotion: TextMotion?
): ParagraphStyle {
// prioritize the parameters to Text in diffs here
@@ -429,14 +496,14 @@
*/
// any new vals should do a pre-merge check here
- val requiresAlloc = textAlign != null && textAlign != this.textAlign ||
+ val requiresAlloc = textAlign != TextAlign.Unspecified && textAlign != this.textAlign ||
lineHeight.isSpecified && lineHeight != this.lineHeight ||
textIndent != null && textIndent != this.textIndent ||
- textDirection != null && textDirection != this.textDirection ||
+ textDirection != TextDirection.Unspecified && textDirection != this.textDirection ||
platformStyle != null && platformStyle != this.platformStyle ||
lineHeightStyle != null && lineHeightStyle != this.lineHeightStyle ||
- lineBreak != null && lineBreak != this.lineBreak ||
- hyphens != null && hyphens != this.hyphens ||
+ lineBreak != LineBreak.Unspecified && lineBreak != this.lineBreak ||
+ hyphens != Hyphens.Unspecified && hyphens != this.hyphens ||
textMotion != null && textMotion != this.textMotion
if (!requiresAlloc) {
@@ -450,12 +517,13 @@
lineHeight
},
textIndent = textIndent ?: this.textIndent,
- textAlign = textAlign ?: this.textAlign,
- textDirection = textDirection ?: this.textDirection,
+ textAlign = if (textAlign != TextAlign.Unspecified) textAlign else this.textAlign,
+ textDirection =
+ if (textDirection != TextDirection.Unspecified) textDirection else this.textDirection,
platformStyle = mergePlatformStyle(platformStyle),
lineHeightStyle = lineHeightStyle ?: this.lineHeightStyle,
- lineBreak = lineBreak ?: this.lineBreak,
- hyphens = hyphens ?: this.hyphens,
+ lineBreak = if (lineBreak != LineBreak.Unspecified) lineBreak else this.lineBreak,
+ hyphens = if (hyphens != Hyphens.Unspecified) hyphens else this.hyphens,
textMotion = textMotion ?: this.textMotion
)
}
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/Savers.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/Savers.kt
index 4ebba20..52ae369 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/Savers.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/Savers.kt
@@ -210,8 +210,8 @@
restore = {
val list = it as List<Any?>
ParagraphStyle(
- textAlign = restore(list[0]),
- textDirection = restore(list[1]),
+ textAlign = restore(list[0])!!,
+ textDirection = restore(list[1])!!,
lineHeight = restore(list[2], TextUnit.Saver)!!,
textIndent = restore(list[3], TextIndent.Saver)
)
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/SpanStyle.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/SpanStyle.kt
index c24f8d2..8587bda 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/SpanStyle.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/SpanStyle.kt
@@ -853,7 +853,6 @@
drawStyle = style.drawStyle ?: Fill
)
-@OptIn(ExperimentalTextApi::class)
internal fun SpanStyle.fastMerge(
color: Color,
brush: Brush?,
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextStyle.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextStyle.kt
index c6b3a41..061b743 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextStyle.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextStyle.kt
@@ -117,14 +117,14 @@
drawStyle = null
),
ParagraphStyle(
- textAlign = textAlign,
- textDirection = textDirection,
+ textAlign = textAlign ?: TextAlign.Unspecified,
+ textDirection = textDirection ?: TextDirection.Unspecified,
lineHeight = lineHeight,
textIndent = textIndent,
platformStyle = null,
lineHeightStyle = null,
- lineBreak = null,
- hyphens = null,
+ lineBreak = LineBreak.Unspecified,
+ hyphens = Hyphens.Unspecified,
textMotion = null
),
platformStyle = null
@@ -177,14 +177,14 @@
drawStyle = null
),
ParagraphStyle(
- textAlign = textAlign,
- textDirection = textDirection,
+ textAlign = textAlign ?: TextAlign.Unspecified,
+ textDirection = textDirection ?: TextDirection.Unspecified,
lineHeight = lineHeight,
textIndent = textIndent,
platformStyle = platformStyle?.paragraphStyle,
lineHeightStyle = lineHeightStyle,
- lineBreak = null,
- hyphens = null,
+ lineBreak = LineBreak.Unspecified,
+ hyphens = Hyphens.Unspecified,
textMotion = null
),
platformStyle = platformStyle
@@ -275,14 +275,77 @@
platformStyle = platformStyle?.spanStyle
),
ParagraphStyle(
- textAlign = textAlign,
- textDirection = textDirection,
+ textAlign = textAlign ?: TextAlign.Unspecified,
+ textDirection = textDirection ?: TextDirection.Unspecified,
lineHeight = lineHeight,
textIndent = textIndent,
platformStyle = platformStyle?.paragraphStyle,
lineHeightStyle = lineHeightStyle,
- lineBreak = lineBreak,
- hyphens = hyphens
+ lineBreak = lineBreak ?: LineBreak.Unspecified,
+ hyphens = hyphens ?: Hyphens.Unspecified
+ ),
+ platformStyle = platformStyle
+ )
+
+ @Deprecated("TextStyle constructors that take nullable TextAlign, " +
+ "TextDirection, LineBreak, and Hyphens are deprecated. Please use a new constructor " +
+ "where these parameters are non-nullable. Null value has been replaced by a special " +
+ "Unspecified object for performance reason.",
+ level = DeprecationLevel.HIDDEN)
+ constructor(
+ color: Color = Color.Unspecified,
+ fontSize: TextUnit = TextUnit.Unspecified,
+ fontWeight: FontWeight? = null,
+ fontStyle: FontStyle? = null,
+ fontSynthesis: FontSynthesis? = null,
+ fontFamily: FontFamily? = null,
+ fontFeatureSettings: String? = null,
+ letterSpacing: TextUnit = TextUnit.Unspecified,
+ baselineShift: BaselineShift? = null,
+ textGeometricTransform: TextGeometricTransform? = null,
+ localeList: LocaleList? = null,
+ background: Color = Color.Unspecified,
+ textDecoration: TextDecoration? = null,
+ shadow: Shadow? = null,
+ drawStyle: DrawStyle? = null,
+ textAlign: TextAlign? = null,
+ textDirection: TextDirection? = null,
+ lineHeight: TextUnit = TextUnit.Unspecified,
+ textIndent: TextIndent? = null,
+ platformStyle: PlatformTextStyle? = null,
+ lineHeightStyle: LineHeightStyle? = null,
+ lineBreak: LineBreak? = null,
+ hyphens: Hyphens? = null,
+ textMotion: TextMotion? = null,
+ ) : this(
+ SpanStyle(
+ color = color,
+ fontSize = fontSize,
+ fontWeight = fontWeight,
+ fontStyle = fontStyle,
+ fontSynthesis = fontSynthesis,
+ fontFamily = fontFamily,
+ fontFeatureSettings = fontFeatureSettings,
+ letterSpacing = letterSpacing,
+ baselineShift = baselineShift,
+ textGeometricTransform = textGeometricTransform,
+ localeList = localeList,
+ background = background,
+ textDecoration = textDecoration,
+ shadow = shadow,
+ platformStyle = platformStyle?.spanStyle,
+ drawStyle = drawStyle
+ ),
+ ParagraphStyle(
+ textAlign = textAlign ?: TextAlign.Unspecified,
+ textDirection = textDirection ?: TextDirection.Unspecified,
+ lineHeight = lineHeight,
+ textIndent = textIndent,
+ platformStyle = platformStyle?.paragraphStyle,
+ lineHeightStyle = lineHeightStyle,
+ lineBreak = lineBreak ?: LineBreak.Unspecified,
+ hyphens = hyphens ?: Hyphens.Unspecified,
+ textMotion = textMotion
),
platformStyle = platformStyle
)
@@ -343,14 +406,14 @@
textDecoration: TextDecoration? = null,
shadow: Shadow? = null,
drawStyle: DrawStyle? = null,
- textAlign: TextAlign? = null,
- textDirection: TextDirection? = null,
+ textAlign: TextAlign = TextAlign.Unspecified,
+ textDirection: TextDirection = TextDirection.Unspecified,
lineHeight: TextUnit = TextUnit.Unspecified,
textIndent: TextIndent? = null,
platformStyle: PlatformTextStyle? = null,
lineHeightStyle: LineHeightStyle? = null,
- lineBreak: LineBreak? = null,
- hyphens: Hyphens? = null,
+ lineBreak: LineBreak = LineBreak.Unspecified,
+ hyphens: Hyphens = Hyphens.Unspecified,
textMotion: TextMotion? = null,
) : this(
SpanStyle(
@@ -445,14 +508,14 @@
textDecoration: TextDecoration? = null,
shadow: Shadow? = null,
drawStyle: DrawStyle? = null,
- textAlign: TextAlign? = null,
- textDirection: TextDirection? = null,
+ textAlign: TextAlign = TextAlign.Unspecified,
+ textDirection: TextDirection = TextDirection.Unspecified,
lineHeight: TextUnit = TextUnit.Unspecified,
textIndent: TextIndent? = null,
platformStyle: PlatformTextStyle? = null,
lineHeightStyle: LineHeightStyle? = null,
- lineBreak: LineBreak? = null,
- hyphens: Hyphens? = null,
+ lineBreak: LineBreak = LineBreak.Unspecified,
+ hyphens: Hyphens = Hyphens.Unspecified,
textMotion: TextMotion? = null
) : this(
SpanStyle(
@@ -488,6 +551,71 @@
platformStyle = platformStyle
)
+ @Deprecated("TextStyle constructors that take nullable TextAlign, " +
+ "TextDirection, LineBreak, and Hyphens are deprecated. Please use a new constructor " +
+ "where these parameters are non-nullable. Null value has been replaced by a special " +
+ "Unspecified object for performance reason.",
+ level = DeprecationLevel.HIDDEN)
+ constructor(
+ brush: Brush?,
+ alpha: Float = Float.NaN,
+ fontSize: TextUnit = TextUnit.Unspecified,
+ fontWeight: FontWeight? = null,
+ fontStyle: FontStyle? = null,
+ fontSynthesis: FontSynthesis? = null,
+ fontFamily: FontFamily? = null,
+ fontFeatureSettings: String? = null,
+ letterSpacing: TextUnit = TextUnit.Unspecified,
+ baselineShift: BaselineShift? = null,
+ textGeometricTransform: TextGeometricTransform? = null,
+ localeList: LocaleList? = null,
+ background: Color = Color.Unspecified,
+ textDecoration: TextDecoration? = null,
+ shadow: Shadow? = null,
+ drawStyle: DrawStyle? = null,
+ textAlign: TextAlign? = null,
+ textDirection: TextDirection? = null,
+ lineHeight: TextUnit = TextUnit.Unspecified,
+ textIndent: TextIndent? = null,
+ platformStyle: PlatformTextStyle? = null,
+ lineHeightStyle: LineHeightStyle? = null,
+ lineBreak: LineBreak? = null,
+ hyphens: Hyphens? = null,
+ textMotion: TextMotion? = null
+ ) : this(
+ SpanStyle(
+ brush = brush,
+ alpha = alpha,
+ fontSize = fontSize,
+ fontWeight = fontWeight,
+ fontStyle = fontStyle,
+ fontSynthesis = fontSynthesis,
+ fontFamily = fontFamily,
+ fontFeatureSettings = fontFeatureSettings,
+ letterSpacing = letterSpacing,
+ baselineShift = baselineShift,
+ textGeometricTransform = textGeometricTransform,
+ localeList = localeList,
+ background = background,
+ textDecoration = textDecoration,
+ shadow = shadow,
+ platformStyle = platformStyle?.spanStyle,
+ drawStyle = drawStyle
+ ),
+ ParagraphStyle(
+ textAlign = textAlign ?: TextAlign.Unspecified,
+ textDirection = textDirection ?: TextDirection.Unspecified,
+ lineHeight = lineHeight,
+ textIndent = textIndent,
+ platformStyle = platformStyle?.paragraphStyle,
+ lineHeightStyle = lineHeightStyle,
+ lineBreak = lineBreak ?: LineBreak.Unspecified,
+ hyphens = hyphens ?: Hyphens.Unspecified,
+ textMotion = textMotion
+ ),
+ platformStyle = platformStyle
+ )
+
@Stable
fun toSpanStyle(): SpanStyle = spanStyle
@@ -564,13 +692,13 @@
textDecoration: TextDecoration? = null,
shadow: Shadow? = null,
drawStyle: DrawStyle? = null,
- textAlign: TextAlign? = null,
- textDirection: TextDirection? = null,
+ textAlign: TextAlign = TextAlign.Unspecified,
+ textDirection: TextDirection = TextDirection.Unspecified,
lineHeight: TextUnit = TextUnit.Unspecified,
textIndent: TextIndent? = null,
lineHeightStyle: LineHeightStyle? = null,
- lineBreak: LineBreak? = null,
- hyphens: Hyphens? = null,
+ lineBreak: LineBreak = LineBreak.Unspecified,
+ hyphens: Hyphens = Hyphens.Unspecified,
platformStyle: PlatformTextStyle? = null,
textMotion: TextMotion? = null
): TextStyle {
@@ -609,6 +737,73 @@
return TextStyle(mergedSpanStyle, mergedParagraphStyle)
}
+ @Deprecated("merge that takes nullable TextAlign, " +
+ "TextDirection, LineBreak, and Hyphens are deprecated. Please use a new constructor " +
+ "where these parameters are non-nullable. Null value has been replaced by a special " +
+ "Unspecified object for performance reason.",
+ level = DeprecationLevel.HIDDEN)
+ @Stable
+ fun merge(
+ color: Color = Color.Unspecified,
+ fontSize: TextUnit = TextUnit.Unspecified,
+ fontWeight: FontWeight? = null,
+ fontStyle: FontStyle? = null,
+ fontSynthesis: FontSynthesis? = null,
+ fontFamily: FontFamily? = null,
+ fontFeatureSettings: String? = null,
+ letterSpacing: TextUnit = TextUnit.Unspecified,
+ baselineShift: BaselineShift? = null,
+ textGeometricTransform: TextGeometricTransform? = null,
+ localeList: LocaleList? = null,
+ background: Color = Color.Unspecified,
+ textDecoration: TextDecoration? = null,
+ shadow: Shadow? = null,
+ drawStyle: DrawStyle? = null,
+ textAlign: TextAlign? = null,
+ textDirection: TextDirection? = null,
+ lineHeight: TextUnit = TextUnit.Unspecified,
+ textIndent: TextIndent? = null,
+ lineHeightStyle: LineHeightStyle? = null,
+ lineBreak: LineBreak? = null,
+ hyphens: Hyphens? = null,
+ platformStyle: PlatformTextStyle? = null,
+ textMotion: TextMotion? = null
+ ): TextStyle {
+ val mergedSpanStyle: SpanStyle = spanStyle.fastMerge(
+ color = color,
+ brush = null,
+ alpha = Float.NaN,
+ fontSize = fontSize,
+ fontWeight = fontWeight,
+ fontStyle = fontStyle,
+ fontSynthesis = fontSynthesis,
+ fontFamily = fontFamily,
+ fontFeatureSettings = fontFeatureSettings,
+ letterSpacing = letterSpacing,
+ baselineShift = baselineShift,
+ textGeometricTransform = textGeometricTransform,
+ localeList = localeList,
+ background = background,
+ textDecoration = textDecoration,
+ shadow = shadow,
+ platformStyle = platformStyle?.spanStyle,
+ drawStyle = drawStyle
+ )
+ val mergedParagraphStyle: ParagraphStyle = paragraphStyle.fastMerge(
+ textAlign = textAlign ?: TextAlign.Unspecified,
+ textDirection = textDirection ?: TextDirection.Unspecified,
+ lineHeight = lineHeight,
+ textIndent = textIndent,
+ platformStyle = platformStyle?.paragraphStyle,
+ lineHeightStyle = lineHeightStyle,
+ lineBreak = lineBreak ?: LineBreak.Unspecified,
+ hyphens = hyphens ?: Hyphens.Unspecified,
+ textMotion = textMotion
+ )
+ if (spanStyle === mergedSpanStyle && paragraphStyle === mergedParagraphStyle) return this
+ return TextStyle(mergedSpanStyle, mergedParagraphStyle)
+ }
+
/**
* Returns a new text style that is a combination of this style and the given [other] style.
*
@@ -703,8 +898,8 @@
drawStyle = this.spanStyle.drawStyle
),
paragraphStyle = ParagraphStyle(
- textAlign = textAlign,
- textDirection = textDirection,
+ textAlign = textAlign ?: TextAlign.Unspecified,
+ textDirection = textDirection ?: TextDirection.Unspecified,
lineHeight = lineHeight,
textIndent = textIndent,
platformStyle = this.paragraphStyle.platformStyle,
@@ -769,8 +964,8 @@
drawStyle = this.spanStyle.drawStyle
),
paragraphStyle = ParagraphStyle(
- textAlign = textAlign,
- textDirection = textDirection,
+ textAlign = textAlign ?: TextAlign.Unspecified,
+ textDirection = textDirection ?: TextDirection.Unspecified,
lineHeight = lineHeight,
textIndent = textIndent,
platformStyle = platformStyle?.paragraphStyle,
@@ -837,20 +1032,25 @@
drawStyle = this.drawStyle
),
paragraphStyle = ParagraphStyle(
- textAlign = textAlign,
- textDirection = textDirection,
+ textAlign = textAlign ?: TextAlign.Unspecified,
+ textDirection = textDirection ?: TextDirection.Unspecified,
lineHeight = lineHeight,
textIndent = textIndent,
platformStyle = platformStyle?.paragraphStyle,
lineHeightStyle = lineHeightStyle,
- lineBreak = lineBreak,
- hyphens = hyphens,
+ lineBreak = lineBreak ?: LineBreak.Unspecified,
+ hyphens = hyphens ?: Hyphens.Unspecified,
textMotion = this.textMotion
),
platformStyle = platformStyle
)
}
+ @Deprecated("copy constructors that take nullable TextAlign, " +
+ "TextDirection, LineBreak, and Hyphens are deprecated. Please use a new constructor " +
+ "where these parameters are non-nullable. Null value has been replaced by a special " +
+ "Unspecified object for performance reason.",
+ level = DeprecationLevel.HIDDEN)
fun copy(
color: Color = this.spanStyle.color,
fontSize: TextUnit = this.spanStyle.fontSize,
@@ -901,6 +1101,70 @@
drawStyle = drawStyle
),
paragraphStyle = ParagraphStyle(
+ textAlign = textAlign ?: TextAlign.Unspecified,
+ textDirection = textDirection ?: TextDirection.Unspecified,
+ lineHeight = lineHeight,
+ textIndent = textIndent,
+ platformStyle = platformStyle?.paragraphStyle,
+ lineHeightStyle = lineHeightStyle,
+ lineBreak = lineBreak ?: LineBreak.Unspecified,
+ hyphens = hyphens ?: Hyphens.Unspecified,
+ textMotion = textMotion
+ ),
+ platformStyle = platformStyle
+ )
+ }
+
+ fun copy(
+ color: Color = this.spanStyle.color,
+ fontSize: TextUnit = this.spanStyle.fontSize,
+ fontWeight: FontWeight? = this.spanStyle.fontWeight,
+ fontStyle: FontStyle? = this.spanStyle.fontStyle,
+ fontSynthesis: FontSynthesis? = this.spanStyle.fontSynthesis,
+ fontFamily: FontFamily? = this.spanStyle.fontFamily,
+ fontFeatureSettings: String? = this.spanStyle.fontFeatureSettings,
+ letterSpacing: TextUnit = this.spanStyle.letterSpacing,
+ baselineShift: BaselineShift? = this.spanStyle.baselineShift,
+ textGeometricTransform: TextGeometricTransform? = this.spanStyle.textGeometricTransform,
+ localeList: LocaleList? = this.spanStyle.localeList,
+ background: Color = this.spanStyle.background,
+ textDecoration: TextDecoration? = this.spanStyle.textDecoration,
+ shadow: Shadow? = this.spanStyle.shadow,
+ drawStyle: DrawStyle? = this.spanStyle.drawStyle,
+ textAlign: TextAlign = this.paragraphStyle.textAlign,
+ textDirection: TextDirection = this.paragraphStyle.textDirection,
+ lineHeight: TextUnit = this.paragraphStyle.lineHeight,
+ textIndent: TextIndent? = this.paragraphStyle.textIndent,
+ platformStyle: PlatformTextStyle? = this.platformStyle,
+ lineHeightStyle: LineHeightStyle? = this.paragraphStyle.lineHeightStyle,
+ lineBreak: LineBreak = this.paragraphStyle.lineBreak,
+ hyphens: Hyphens = this.paragraphStyle.hyphens,
+ textMotion: TextMotion? = this.paragraphStyle.textMotion,
+ ): TextStyle {
+ return TextStyle(
+ spanStyle = SpanStyle(
+ textForegroundStyle = if (color == this.spanStyle.color) {
+ spanStyle.textForegroundStyle
+ } else {
+ TextForegroundStyle.from(color)
+ },
+ fontSize = fontSize,
+ fontWeight = fontWeight,
+ fontStyle = fontStyle,
+ fontSynthesis = fontSynthesis,
+ fontFamily = fontFamily,
+ fontFeatureSettings = fontFeatureSettings,
+ letterSpacing = letterSpacing,
+ baselineShift = baselineShift,
+ textGeometricTransform = textGeometricTransform,
+ localeList = localeList,
+ background = background,
+ textDecoration = textDecoration,
+ shadow = shadow,
+ platformStyle = platformStyle?.spanStyle,
+ drawStyle = drawStyle
+ ),
+ paragraphStyle = ParagraphStyle(
textAlign = textAlign,
textDirection = textDirection,
lineHeight = lineHeight,
@@ -915,6 +1179,11 @@
)
}
+ @Deprecated("copy constructors that take nullable TextAlign, " +
+ "TextDirection, LineBreak, and Hyphens are deprecated. Please use a new constructor " +
+ "where these parameters are non-nullable. Null value has been replaced by a special " +
+ "Unspecified object for performance reason.",
+ level = DeprecationLevel.HIDDEN)
fun copy(
brush: Brush?,
alpha: Float = this.spanStyle.alpha,
@@ -963,6 +1232,68 @@
drawStyle = drawStyle
),
paragraphStyle = ParagraphStyle(
+ textAlign = textAlign ?: TextAlign.Unspecified,
+ textDirection = textDirection ?: TextDirection.Unspecified,
+ lineHeight = lineHeight,
+ textIndent = textIndent,
+ platformStyle = platformStyle?.paragraphStyle,
+ lineHeightStyle = lineHeightStyle,
+ lineBreak = lineBreak ?: LineBreak.Unspecified,
+ hyphens = hyphens ?: Hyphens.Unspecified,
+ textMotion = textMotion
+ ),
+ platformStyle = platformStyle
+ )
+ }
+
+ fun copy(
+ brush: Brush?,
+ alpha: Float = this.spanStyle.alpha,
+ fontSize: TextUnit = this.spanStyle.fontSize,
+ fontWeight: FontWeight? = this.spanStyle.fontWeight,
+ fontStyle: FontStyle? = this.spanStyle.fontStyle,
+ fontSynthesis: FontSynthesis? = this.spanStyle.fontSynthesis,
+ fontFamily: FontFamily? = this.spanStyle.fontFamily,
+ fontFeatureSettings: String? = this.spanStyle.fontFeatureSettings,
+ letterSpacing: TextUnit = this.spanStyle.letterSpacing,
+ baselineShift: BaselineShift? = this.spanStyle.baselineShift,
+ textGeometricTransform: TextGeometricTransform? = this.spanStyle.textGeometricTransform,
+ localeList: LocaleList? = this.spanStyle.localeList,
+ background: Color = this.spanStyle.background,
+ textDecoration: TextDecoration? = this.spanStyle.textDecoration,
+ shadow: Shadow? = this.spanStyle.shadow,
+ drawStyle: DrawStyle? = this.spanStyle.drawStyle,
+ textAlign: TextAlign = this.paragraphStyle.textAlign,
+ textDirection: TextDirection = this.paragraphStyle.textDirection,
+ lineHeight: TextUnit = this.paragraphStyle.lineHeight,
+ textIndent: TextIndent? = this.paragraphStyle.textIndent,
+ platformStyle: PlatformTextStyle? = this.platformStyle,
+ lineHeightStyle: LineHeightStyle? = this.paragraphStyle.lineHeightStyle,
+ lineBreak: LineBreak = this.paragraphStyle.lineBreak,
+ hyphens: Hyphens = this.paragraphStyle.hyphens,
+ textMotion: TextMotion? = this.paragraphStyle.textMotion,
+ ): TextStyle {
+ return TextStyle(
+ spanStyle = SpanStyle(
+ brush = brush,
+ alpha = alpha,
+ fontSize = fontSize,
+ fontWeight = fontWeight,
+ fontStyle = fontStyle,
+ fontSynthesis = fontSynthesis,
+ fontFamily = fontFamily,
+ fontFeatureSettings = fontFeatureSettings,
+ letterSpacing = letterSpacing,
+ baselineShift = baselineShift,
+ textGeometricTransform = textGeometricTransform,
+ localeList = localeList,
+ background = background,
+ textDecoration = textDecoration,
+ shadow = shadow,
+ platformStyle = platformStyle?.spanStyle,
+ drawStyle = drawStyle
+ ),
+ paragraphStyle = ParagraphStyle(
textAlign = textAlign,
textDirection = textDirection,
lineHeight = lineHeight,
@@ -1071,14 +1402,24 @@
/**
* The alignment of the text within the lines of the paragraph.
*/
- val textAlign: TextAlign? get() = this.paragraphStyle.textAlign
+ val textAlign: TextAlign get() = this.paragraphStyle.textAlign
+
+ @Deprecated("Kept for backwards compatibility.", level = DeprecationLevel.WARNING)
+ @get:JvmName("getTextAlign")
+ @Suppress("unused")
+ val deprecated_boxing_textAlign: TextAlign? get() = this.textAlign
/**
* The algorithm to be used to resolve the final text and paragraph
* direction: Left To Right or Right To Left. If no value is provided the system will use the
* [LayoutDirection] as the primary signal.
*/
- val textDirection: TextDirection? get() = this.paragraphStyle.textDirection
+ val textDirection: TextDirection get() = this.paragraphStyle.textDirection
+
+ @Deprecated("Kept for backwards compatibility.", level = DeprecationLevel.WARNING)
+ @get:JvmName("getTextDirection")
+ @Suppress("unused")
+ val deprecated_boxing_textDirection: TextDirection? get() = this.textDirection
/**
* Line height for the [Paragraph] in [TextUnit] unit, e.g. SP or EM.
@@ -1103,12 +1444,22 @@
/**
* The hyphens configuration of the paragraph.
*/
- val hyphens: Hyphens? get() = this.paragraphStyle.hyphens
+ val hyphens: Hyphens get() = this.paragraphStyle.hyphens
+
+ @Deprecated("Kept for backwards compatibility.", level = DeprecationLevel.WARNING)
+ @get:JvmName("getHyphens")
+ @Suppress("unused")
+ val deprecated_boxing_hyphens: Hyphens? get() = this.hyphens
/**
* The line breaking configuration of the paragraph.
*/
- val lineBreak: LineBreak? get() = this.paragraphStyle.lineBreak
+ val lineBreak: LineBreak get() = this.paragraphStyle.lineBreak
+
+ @Deprecated("Kept for backwards compatibility.", level = DeprecationLevel.WARNING)
+ @get:JvmName("getLineBreak")
+ @Suppress("unused")
+ val deprecated_boxing_lineBreak: LineBreak? get() = this.lineBreak
/**
* Text character placement configuration, whether to optimize for animated or static text.
@@ -1241,14 +1592,14 @@
*/
internal fun resolveTextDirection(
layoutDirection: LayoutDirection,
- textDirection: TextDirection?
+ textDirection: TextDirection
): TextDirection {
return when (textDirection) {
TextDirection.Content -> when (layoutDirection) {
LayoutDirection.Ltr -> TextDirection.ContentOrLtr
LayoutDirection.Rtl -> TextDirection.ContentOrRtl
}
- null -> when (layoutDirection) {
+ TextDirection.Unspecified -> when (layoutDirection) {
LayoutDirection.Ltr -> TextDirection.Ltr
LayoutDirection.Rtl -> TextDirection.Rtl
}
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/Hyphens.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/Hyphens.kt
index 5d8db44..9d9bb40 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/Hyphens.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/Hyphens.kt
@@ -75,11 +75,18 @@
* </pre>
*/
val Auto = Hyphens(2)
+
+ /**
+ * This represents an unset value, a usual replacement for "null" when a primitive value
+ * is desired.
+ */
+ val Unspecified = Hyphens(Int.MIN_VALUE)
}
override fun toString() = when (this) {
None -> "Hyphens.None"
Auto -> "Hyphens.Auto"
+ Unspecified -> "Hyphens.Unspecified"
else -> "Invalid"
}
}
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/LineBreak.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/LineBreak.kt
index 0dea51a3..a7a1d19 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/LineBreak.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/LineBreak.kt
@@ -66,5 +66,12 @@
*/
@Stable
val Paragraph: LineBreak
+
+ /**
+ * This represents an unset value, a usual replacement for "null" when a primitive value
+ * is desired.
+ */
+ @Stable
+ val Unspecified: LineBreak
}
}
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/TextAlign.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/TextAlign.kt
index 305ec81..786ffa8 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/TextAlign.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/TextAlign.kt
@@ -30,6 +30,7 @@
Justify -> "Justify"
Start -> "Start"
End -> "End"
+ Unspecified -> "Unspecified"
else -> "Invalid"
}
}
@@ -74,5 +75,11 @@
* Return a list containing all possible values of TextAlign.
*/
fun values(): List<TextAlign> = listOf(Left, Right, Center, Justify, Start, End)
+
+ /**
+ * This represents an unset value, a usual replacement for "null" when a primitive value
+ * is desired.
+ */
+ val Unspecified = TextAlign(Int.MIN_VALUE)
}
}
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/TextDirection.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/TextDirection.kt
index 663b45d..4ea31b3 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/TextDirection.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/TextDirection.kt
@@ -31,6 +31,7 @@
Content -> "Content"
ContentOrLtr -> "ContentOrLtr"
ContentOrRtl -> "ContentOrRtl"
+ Unspecified -> "Unspecified"
else -> "Invalid"
}
}
@@ -70,5 +71,11 @@
* directional character is present, then Right to Left will be used as the default direction.
*/
val ContentOrRtl = TextDirection(5)
+
+ /**
+ * This represents an unset value, a usual replacement for "null" when a primitive value
+ * is desired.
+ */
+ val Unspecified = TextDirection(Int.MIN_VALUE)
}
}
diff --git a/compose/ui/ui-text/src/skikoMain/kotlin/androidx/compose/ui/text/platform/SkiaParagraph.skiko.kt b/compose/ui/ui-text/src/skikoMain/kotlin/androidx/compose/ui/text/platform/SkiaParagraph.skiko.kt
index 58f1b98..adcb9dd 100644
--- a/compose/ui/ui-text/src/skikoMain/kotlin/androidx/compose/ui/text/platform/SkiaParagraph.skiko.kt
+++ b/compose/ui/ui-text/src/skikoMain/kotlin/androidx/compose/ui/text/platform/SkiaParagraph.skiko.kt
@@ -498,7 +498,7 @@
): ParagraphStyle {
val pStyle = ParagraphStyle()
pStyle.textStyle = makeSkTextStyle(computedStyle)
- style.textAlign?.let {
+ style.textAlign.let {
pStyle.alignment = it.toSkAlignment()
}
diff --git a/compose/ui/ui-text/src/skikoMain/kotlin/androidx/compose/ui/text/style/LineBreak.skiko.kt b/compose/ui/ui-text/src/skikoMain/kotlin/androidx/compose/ui/text/style/LineBreak.skiko.kt
index dad3ca8..9a75118 100644
--- a/compose/ui/ui-text/src/skikoMain/kotlin/androidx/compose/ui/text/style/LineBreak.skiko.kt
+++ b/compose/ui/ui-text/src/skikoMain/kotlin/androidx/compose/ui/text/style/LineBreak.skiko.kt
@@ -29,5 +29,7 @@
actual val Heading: LineBreak = LineBreak(2)
actual val Paragraph: LineBreak = LineBreak(3)
+
+ actual val Unspecified: LineBreak = LineBreak(Int.MIN_VALUE)
}
}
diff --git a/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/LowLatencyCanvasViewTest.kt b/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/LowLatencyCanvasViewTest.kt
index bca306d..4bd47aa 100644
--- a/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/LowLatencyCanvasViewTest.kt
+++ b/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/LowLatencyCanvasViewTest.kt
@@ -126,7 +126,6 @@
view.post {
drawLatch.countDown()
}
- it.getLowLatencyCanvasView().clear()
}
assertTrue(drawLatch.await(3000, TimeUnit.MILLISECONDS))
},
diff --git a/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/SingleBufferedCanvasRendererV29Test.kt b/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/SingleBufferedCanvasRendererV29Test.kt
index 69f6088..fbf76cf 100644
--- a/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/SingleBufferedCanvasRendererV29Test.kt
+++ b/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/SingleBufferedCanvasRendererV29Test.kt
@@ -19,6 +19,7 @@
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.ColorSpace
+import android.graphics.Paint
import android.hardware.HardwareBuffer
import android.os.Build
import androidx.annotation.RequiresApi
@@ -35,6 +36,7 @@
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
+import kotlin.math.roundToInt
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
@@ -47,7 +49,7 @@
class SingleBufferedCanvasRendererV29Test {
companion object {
- const val TEST_WIDTH = 20
+ const val TEST_WIDTH = 40
const val TEST_HEIGHT = 20
}
@@ -390,6 +392,81 @@
}
}
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
+ @Test
+ fun testVisiblePreservesContents() {
+ rendererVisibilityTestHelper(true, Color.RED, Color.BLUE)
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
+ @Test
+ fun testInvisibleClearsContents() {
+ rendererVisibilityTestHelper(false, 0, Color.BLUE)
+ }
+ @RequiresApi(Build.VERSION_CODES.Q)
+ fun rendererVisibilityTestHelper(visible: Boolean, leftColor: Int, rightColor: Int) {
+ val transformer = BufferTransformer().apply {
+ computeTransform(TEST_WIDTH, TEST_HEIGHT, BUFFER_TRANSFORM_IDENTITY)
+ }
+ val executor = HandlerThreadExecutor("thread")
+ val renderLatch = CountDownLatch(2) // wait for 2 renders
+ var buffer: HardwareBuffer? = null
+ val renderer = SingleBufferedCanvasRendererV29(
+ TEST_WIDTH,
+ TEST_HEIGHT,
+ transformer,
+ executor,
+ object : SingleBufferedCanvasRenderer.RenderCallbacks<Int> {
+
+ val paint = Paint()
+
+ override fun render(canvas: Canvas, width: Int, height: Int, param: Int) {
+ paint.color = param
+ if (param == Color.RED) {
+ canvas.drawRect(0f, 0f, width / 2f, height.toFloat(), paint)
+ } else {
+ canvas.drawRect(width / 2f, 0f, width.toFloat(), height.toFloat(), paint)
+ }
+ }
+
+ override fun onBufferReady(
+ hardwareBuffer: HardwareBuffer,
+ syncFenceCompat: SyncFenceCompat?
+ ) {
+ syncFenceCompat?.awaitForever()
+ buffer = hardwareBuffer
+ renderLatch.countDown()
+ }
+ }).apply {
+ isVisible = visible
+ }
+ try {
+ renderer.render(Color.RED)
+ renderer.render(Color.BLUE)
+ assertTrue(renderLatch.await(3000, TimeUnit.MILLISECONDS))
+ assertNotNull(buffer)
+
+ val copy = Bitmap.wrapHardwareBuffer(buffer!!, renderer.colorSpace)!!
+ .copy(Bitmap.Config.ARGB_8888, false)
+
+ assertEquals(
+ leftColor,
+ copy.getPixel(copy.width / 4, copy.height / 2)
+ )
+ assertEquals(
+ rightColor,
+ copy.getPixel((copy.width * 3f / 4f).roundToInt(), copy.height / 2)
+ )
+ } finally {
+ val latch = CountDownLatch(1)
+ renderer.release(false) {
+ executor.quit()
+ latch.countDown()
+ }
+ assertTrue(latch.await(3000, TimeUnit.MILLISECONDS))
+ }
+ }
+
@RequiresApi(Build.VERSION_CODES.Q)
private fun testRenderWithTransform(
transform: Int,
diff --git a/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/LowLatencyCanvasView.kt b/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/LowLatencyCanvasView.kt
index c30656d..2adf475 100644
--- a/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/LowLatencyCanvasView.kt
+++ b/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/LowLatencyCanvasView.kt
@@ -190,6 +190,11 @@
*/
private var mSceneBitmapDrawn = false
+ /**
+ * Configured ColorSpace
+ */
+ private var mColorSpace = BufferedRendererImpl.DefaultColorSpace
+
private val mSurfaceHolderCallbacks = object : SurfaceHolder.Callback2 {
override fun surfaceCreated(holder: SurfaceHolder) {
// NO-OP wait for surfaceChanged
@@ -252,7 +257,6 @@
.setName("FrontBufferedLayer")
.build()
- var frontBufferRenderer: SingleBufferedCanvasRenderer<Unit>? = null
val dataSpace: Int
val colorSpace: ColorSpace
if (isAndroidUPlus() && supportsWideColorGamut()) {
@@ -266,7 +270,7 @@
dataSpace = DataSpace.DATASPACE_SRGB
colorSpace = BufferedRendererImpl.DefaultColorSpace
}
- frontBufferRenderer = SingleBufferedCanvasRenderer.create(
+ val frontBufferRenderer = SingleBufferedCanvasRenderer.create(
width,
height,
bufferTransformer,
@@ -301,7 +305,6 @@
pendingRenders = true
}
if (mFrontBufferTarget.get() || pendingRenders) {
- frontBufferRenderer?.isVisible = true
val transaction = SurfaceControlCompat.Transaction()
.setLayer(frontBufferSurfaceControl, Integer.MAX_VALUE)
.setBuffer(
@@ -324,7 +327,6 @@
transaction.commit()
syncFenceCompat?.close()
} else {
- frontBufferRenderer?.isVisible = false
syncFenceCompat?.awaitForever()
val bitmap = if (!hardwareBitmapConfigured) {
hardwareBitmapConfigured = true
@@ -347,8 +349,10 @@
}
).apply {
this.colorSpace = colorSpace
+ this.isVisible = true
}
+ mColorSpace = colorSpace
mFrontBufferedRenderer = frontBufferRenderer
mFrontBufferedSurfaceControl = frontBufferSurfaceControl
mWidth = width
@@ -440,7 +444,7 @@
val buffer = mHardwareBuffer
if (buffer != null) {
mBufferFence?.awaitForever()
- val bitmap = Bitmap.wrapHardwareBuffer(buffer, null)
+ val bitmap = Bitmap.wrapHardwareBuffer(buffer, mColorSpace)
post {
mSceneBitmap = bitmap
hideFrontBuffer()
diff --git a/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/SingleBufferedCanvasRendererV29.kt b/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/SingleBufferedCanvasRendererV29.kt
index 1556a52..87f7926 100644
--- a/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/SingleBufferedCanvasRendererV29.kt
+++ b/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/SingleBufferedCanvasRendererV29.kt
@@ -115,7 +115,7 @@
override var isVisible: Boolean = false
set(value) {
- mBufferedRenderer.preserveContents = isVisible
+ mBufferedRenderer.preserveContents = value
field = value
}
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/ExerciseSessionRecord.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/ExerciseSessionRecord.kt
index bfca023..187d9f3 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/ExerciseSessionRecord.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/ExerciseSessionRecord.kt
@@ -59,7 +59,11 @@
*/
val laps: List<ExerciseLap> = emptyList(),
- /** [ExerciseRouteResult] [ExerciseRouteResult] of the session. */
+ /**
+ * [ExerciseRouteResult] [ExerciseRouteResult] of the session. Location data points of
+ * [ExerciseRoute] should be within the parent session, and should be before the end time of the
+ * session.
+ */
val exerciseRouteResult: ExerciseRouteResult = ExerciseRouteResult.NoData(),
) : IntervalRecord {
diff --git a/libraryversions.toml b/libraryversions.toml
index 8ed0c2d..89da799 100644
--- a/libraryversions.toml
+++ b/libraryversions.toml
@@ -163,7 +163,7 @@
WINDOW_EXTENSIONS_CORE = "1.1.0-alpha01"
WINDOW_SIDECAR = "1.0.0-rc01"
# Do not remove comment
-WORK = "2.9.0-beta01"
+WORK = "2.9.0-rc01"
[groups]
ACTIVITY = { group = "androidx.activity", atomicGroupVersion = "versions.ACTIVITY" }
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/SdkLoaderTest.kt b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/SdkLoaderTest.kt
index ff79f98..41bea2f 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/SdkLoaderTest.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/SdkLoaderTest.kt
@@ -118,7 +118,8 @@
val packageIdField = rPackageClass.getDeclaredField("packageId")
val value = packageIdField.get(null)
- assertThat(value).isEqualTo(42)
+ // 42 (0x2A) -> (0x2A000000)
+ assertThat(value).isEqualTo(0x2A000000)
}
@Test
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/loader/ResourceRemapping.kt b/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/loader/ResourceRemapping.kt
index d45305d..6bb7afd 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/loader/ResourceRemapping.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/loader/ResourceRemapping.kt
@@ -21,7 +21,10 @@
/**
* Update RPackage.packageId for supporting Android Resource remapping for SDK.
* Each resource has id calculated as id = RPackage.packageId + index.
+ * Id structure is (1 byte - packageId) (1 byte - type) (2 byte - index).
* Updating packageId effectively shifting all SDK resource ids in resource table.
+ * [ResourceRemappingConfig] contains a new packageId component (first byte) for SDK.
+ * This value need to be shifted before updating RPackage.packageId.
* IMPORTANT: ResourceRemapping should happen before ANY interactions with R.class
*/
internal object ResourceRemapping {
@@ -43,6 +46,9 @@
val field = rPackageClass.getDeclaredField(PACKAGE_ID_FIELD_NAME)
- field.setInt(null, remappingConfig.packageId)
+ // 0x7E -> 0x7E000000
+ val shiftedPackageId = remappingConfig.packageId shl 24
+
+ field.setInt(null, shiftedPackageId)
}
}
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/test/java/androidx/privacysandbox/sdkruntime/client/loader/ResourceRemappingTest.kt b/privacysandbox/sdkruntime/sdkruntime-client/src/test/java/androidx/privacysandbox/sdkruntime/client/loader/ResourceRemappingTest.kt
index 87e152f..ac4c7ee 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/src/test/java/androidx/privacysandbox/sdkruntime/client/loader/ResourceRemappingTest.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/test/java/androidx/privacysandbox/sdkruntime/client/loader/ResourceRemappingTest.kt
@@ -53,7 +53,7 @@
classLoader,
ResourceRemappingConfig(
rPackageClassName = "RPackage",
- packageId = 42
+ packageId = 0x2A
)
)
@@ -61,7 +61,7 @@
val packageIdField = rPackageClass.getDeclaredField("packageId")
val value = packageIdField.getInt(null)
- assertThat(value).isEqualTo(42)
+ assertThat(value).isEqualTo(0x2A000000)
}
@Test
diff --git a/privacysandbox/ui/ui-client/src/androidTest/java/androidx/privacysandbox/ui/client/test/SandboxedSdkViewTest.kt b/privacysandbox/ui/ui-client/src/androidTest/java/androidx/privacysandbox/ui/client/test/SandboxedSdkViewTest.kt
index e0709fb..6c0298d 100644
--- a/privacysandbox/ui/ui-client/src/androidTest/java/androidx/privacysandbox/ui/client/test/SandboxedSdkViewTest.kt
+++ b/privacysandbox/ui/ui-client/src/androidTest/java/androidx/privacysandbox/ui/client/test/SandboxedSdkViewTest.kt
@@ -465,13 +465,6 @@
val surfaceView = SurfaceView(context)
val surfaceViewLatch = CountDownLatch(1)
- // Attach SurfaceView
- activityScenarioRule.withActivity {
- layout = findViewById(
- R.id.mainlayout
- )
- layout.addView(surfaceView)
- }
var token: IBinder? = null
surfaceView.addOnAttachStateChangeListener(
object : View.OnAttachStateChangeListener {
@@ -485,13 +478,19 @@
}
)
- // Verify SurfaceView has a non-null token when attached.
- assertThat(surfaceViewLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
- assertThat(surfaceView.hostToken).isNotNull()
+ // Attach SurfaceView
activityScenarioRule.withActivity {
+ layout = findViewById(
+ R.id.mainlayout
+ )
+ layout.addView(surfaceView)
layout.removeView(surfaceView)
}
+ // Verify SurfaceView has a non-null token when attached.
+ assertThat(surfaceViewLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
+ assertThat(token).isNotNull()
+
// Verify that the UI adapter receives the same host token object when opening a session.
addViewToLayout()
assertThat(openSessionLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/Text.kt b/tv/tv-material/src/main/java/androidx/tv/material3/Text.kt
index af000b7..b268658 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/Text.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/Text.kt
@@ -120,7 +120,7 @@
color = textColor,
fontSize = fontSize,
fontWeight = fontWeight,
- textAlign = textAlign,
+ textAlign = textAlign ?: TextAlign.Unspecified,
lineHeight = lineHeight,
fontFamily = fontFamily,
textDecoration = textDecoration,
@@ -218,7 +218,7 @@
color = textColor,
fontSize = fontSize,
fontWeight = fontWeight,
- textAlign = textAlign,
+ textAlign = textAlign ?: TextAlign.Unspecified,
lineHeight = lineHeight,
fontFamily = fontFamily,
textDecoration = textDecoration,
diff --git a/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/Text.kt b/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/Text.kt
index 40c3fc1..e11e3fc 100644
--- a/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/Text.kt
+++ b/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/Text.kt
@@ -106,7 +106,7 @@
color = color,
fontSize = fontSize,
fontWeight = fontWeight,
- textAlign = textAlign,
+ textAlign = textAlign ?: TextAlign.Unspecified,
lineHeight = lineHeight,
fontFamily = fontFamily,
textDecoration = textDecoration,
diff --git a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebSettingsCompatUserAgentMetadataTest.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebSettingsCompatUserAgentMetadataTest.java
index 8ce6e03..9800ea6 100644
--- a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebSettingsCompatUserAgentMetadataTest.java
+++ b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebSettingsCompatUserAgentMetadataTest.java
@@ -21,7 +21,10 @@
import android.os.Build;
+import android.webkit.WebResourceRequest;
+import android.webkit.WebResourceResponse;
import android.webkit.WebSettings;
+import android.webkit.WebView;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SdkSuppress;
@@ -33,23 +36,49 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.ByteArrayInputStream;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
-/**
- * TODO(b/294183509): Add user-agent client hints HTTP header verification tests when unhide the
- * override user-agent metadata APIs.
- */
@SmallTest
@RunWith(AndroidJUnit4.class)
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
public class WebSettingsCompatUserAgentMetadataTest {
+ private static final String[] USER_AGENT_CLIENT_HINTS = {"sec-ch-ua", "sec-ch-ua-arch",
+ "sec-ch-ua-platform", "sec-ch-ua-model", "sec-ch-ua-mobile", "sec-ch-ua-full-version",
+ "sec-ch-ua-platform-version", "sec-ch-ua-bitness", "sec-ch-ua-full-version-list",
+ "sec-ch-ua-wow64"};
+
+ private static final String FIRST_URL = "/first.html";
+ private static final String SECOND_URL = "/second.html";
+
+ private static final String FIRST_RAW_HTML =
+ "<!DOCTYPE html>\n"
+ + "<html>\n"
+ + " <body>\n"
+ + " <iframe src=\"" + SECOND_URL + "\">\n"
+ + " </iframe>\n"
+ + " </body>\n"
+ + "</html>\n";
+
+ private static final String SECOND_RAW_HTML =
+ "<!DOCTYPE html>\n"
+ + "<html>\n"
+ + " <body>\n"
+ + " </body>\n"
+ + "</html>\n";
+
private WebViewOnUiThread mWebViewOnUiThread;
+ private TestHttpsWebViewClient mTestHttpsWebViewClient;
@Before
public void setUp() throws Exception {
mWebViewOnUiThread = new androidx.webkit.WebViewOnUiThread();
+ mTestHttpsWebViewClient = new TestHttpsWebViewClient(mWebViewOnUiThread);
}
@After
@@ -59,6 +88,45 @@
}
}
+ /**
+ * A WebViewClient to intercept the request to mock HTTPS response.
+ */
+ private static final class TestHttpsWebViewClient extends
+ WebViewOnUiThread.WaitForLoadedClient {
+ private final List<WebResourceRequest> mInterceptedRequests = new ArrayList<>();
+ TestHttpsWebViewClient(WebViewOnUiThread webViewOnUiThread) throws Exception {
+ super(webViewOnUiThread);
+ }
+
+ @Override
+ public WebResourceResponse shouldInterceptRequest(WebView view,
+ WebResourceRequest request) {
+ // Only return content for FIRST_URL and SECOND_URL, deny all other requests.
+ Map<String, String> responseHeaders = new HashMap<>();
+ responseHeaders.put("Content-Type", "text/html");
+ responseHeaders.put("Accept-Ch", String.join(",", USER_AGENT_CLIENT_HINTS));
+
+ if (request.getUrl().toString().endsWith(FIRST_URL)) {
+ mInterceptedRequests.add(request);
+ return new WebResourceResponse("text/html", "utf-8",
+ 200, "OK", responseHeaders,
+ new ByteArrayInputStream(
+ FIRST_RAW_HTML.getBytes(StandardCharsets.UTF_8)));
+ } else if (request.getUrl().toString().endsWith(SECOND_URL)) {
+ mInterceptedRequests.add(request);
+ return new WebResourceResponse("text/html", "utf-8",
+ 200, "OK", responseHeaders,
+ new ByteArrayInputStream(
+ SECOND_RAW_HTML.getBytes(StandardCharsets.UTF_8)));
+ }
+ return new WebResourceResponse("text/html", "UTF-8", null);
+ }
+
+ public List<WebResourceRequest> getInterceptedRequests() {
+ return mInterceptedRequests;
+ }
+ }
+
@Test
public void testSetUserAgentMetadataDefault() throws Throwable {
WebkitUtils.checkFeature(WebViewFeature.USER_AGENT_METADATA);
@@ -83,6 +151,55 @@
}
@Test
+ public void testSetUserAgentMetadataDefaultHttpHeader() throws Throwable {
+ WebkitUtils.checkFeature(WebViewFeature.USER_AGENT_METADATA);
+
+ mWebViewOnUiThread.setWebViewClient(mTestHttpsWebViewClient);
+ WebSettings settings = mWebViewOnUiThread.getSettings();
+ settings.setJavaScriptEnabled(true);
+
+ // As WebViewOnUiThread clear cache doesn't work well, using different origin to avoid
+ // client hints cache impacts other tests.
+ String baseUrl = "https://example1.com";
+ mWebViewOnUiThread.loadUrlAndWaitForCompletion(baseUrl + FIRST_URL);
+ List<WebResourceRequest> requests = mTestHttpsWebViewClient.getInterceptedRequests();
+ Assert.assertEquals(2, requests.size());
+
+ // Make sure the first request has low-entropy client hints.
+ WebResourceRequest recordedRequest = requests.get(0);
+ Assert.assertEquals(baseUrl + FIRST_URL, recordedRequest.getUrl().toString());
+ Map<String, String> requestHeaders = recordedRequest.getRequestHeaders();
+ Assert.assertTrue(
+ requestHeaders.get("sec-ch-ua").contains("Android WebView"));
+ Assert.assertFalse(requestHeaders.get("sec-ch-ua-mobile").isEmpty());
+ Assert.assertEquals("\"Android\"",
+ requestHeaders.get("sec-ch-ua-platform"));
+ Assert.assertNull(requestHeaders.get("sec-ch-ua-platform-version"));
+ Assert.assertNull(requestHeaders.get("sec-ch-ua-arch"));
+ Assert.assertNull(requestHeaders.get("sec-ch-ua-full-version-list"));
+ Assert.assertNull(requestHeaders.get("sec-ch-ua-bitness"));
+ Assert.assertNull(requestHeaders.get("sec-ch-ua-model"));
+ Assert.assertNull(requestHeaders.get("sec-ch-ua-wow64"));
+
+ // Verify all user-agent client hints on the second request.
+ recordedRequest = requests.get(1);
+ Assert.assertEquals(baseUrl + SECOND_URL, recordedRequest.getUrl().toString());
+ requestHeaders = recordedRequest.getRequestHeaders();
+
+ Assert.assertTrue(
+ requestHeaders.get("sec-ch-ua").contains("Android WebView"));
+ Assert.assertFalse(requestHeaders.get("sec-ch-ua-mobile").isEmpty());
+ Assert.assertEquals("\"Android\"",
+ requestHeaders.get("sec-ch-ua-platform"));
+ Assert.assertFalse(requestHeaders.get("sec-ch-ua-platform-version").isEmpty());
+ Assert.assertFalse(requestHeaders.get("sec-ch-ua-arch").isEmpty());
+ Assert.assertFalse(requestHeaders.get("sec-ch-ua-full-version-list").isEmpty());
+ Assert.assertEquals("\"\"", requestHeaders.get("sec-ch-ua-bitness"));
+ Assert.assertFalse(requestHeaders.get("sec-ch-ua-model").isEmpty());
+ Assert.assertEquals("?0", requestHeaders.get("sec-ch-ua-wow64"));
+ }
+
+ @Test
public void testSetUserAgentMetadataFullOverrides() throws Throwable {
WebkitUtils.checkFeature(WebViewFeature.USER_AGENT_METADATA);
@@ -95,7 +212,7 @@
.setFullVersion("1.1.1.1")
.setPlatform("myPlatform").setPlatformVersion("2.2.2.2").setArchitecture("myArch")
.setMobile(true).setModel("myModel").setBitness(32)
- .setWow64(false).setFormFactor("myFormFactor").build();
+ .setWow64(false).build();
WebSettingsCompat.setUserAgentMetadata(settings, overrideSetting);
assertEquals(
@@ -105,6 +222,71 @@
}
@Test
+ public void testSetUserAgentMetadataFullOverridesHttpHeader() throws Throwable {
+ WebkitUtils.checkFeature(WebViewFeature.USER_AGENT_METADATA);
+
+ mWebViewOnUiThread.setWebViewClient(mTestHttpsWebViewClient);
+ WebSettings settings = mWebViewOnUiThread.getSettings();
+ settings.setJavaScriptEnabled(true);
+
+ // Overrides user-agent metadata.
+ UserAgentMetadata overrideSetting = new UserAgentMetadata.Builder()
+ .setBrandVersionList(Collections.singletonList(
+ new UserAgentMetadata.BrandVersion(
+ "myBrand", "1", "1.1.1.1")))
+ .setFullVersion("1.1.1.1").setPlatform("myPlatform")
+ .setPlatformVersion("2.2.2.2").setArchitecture("myArch")
+ .setMobile(true).setModel("myModel").setBitness(32)
+ .setWow64(false).build();
+ WebSettingsCompat.setUserAgentMetadata(settings, overrideSetting);
+
+ // As WebViewOnUiThread clear cache doesn't work well, using different origin to avoid
+ // client hints cache impacts other tests.
+ String baseUrl = "https://example2.com";
+ mWebViewOnUiThread.loadUrlAndWaitForCompletion(baseUrl + FIRST_URL);
+ List<WebResourceRequest> requests = mTestHttpsWebViewClient.getInterceptedRequests();
+ Assert.assertEquals(2, requests.size());
+
+ // Make sure the first request has low-entropy client hints.
+ WebResourceRequest recordedRequest = requests.get(0);
+ Assert.assertEquals(baseUrl + FIRST_URL, recordedRequest.getUrl().toString());
+ Map<String, String> requestHeaders = recordedRequest.getRequestHeaders();
+ Assert.assertEquals("\"myBrand\";v=\"1\"",
+ requestHeaders.get("sec-ch-ua"));
+ Assert.assertEquals("?1", requestHeaders.get("sec-ch-ua-mobile"));
+ Assert.assertEquals("\"myPlatform\"",
+ requestHeaders.get("sec-ch-ua-platform"));
+ Assert.assertNull(requestHeaders.get("sec-ch-ua-platform-version"));
+ Assert.assertNull(requestHeaders.get("sec-ch-ua-arch"));
+ Assert.assertNull(requestHeaders.get("sec-ch-ua-full-version-list"));
+ Assert.assertNull(requestHeaders.get("sec-ch-ua-bitness"));
+ Assert.assertNull(requestHeaders.get("sec-ch-ua-model"));
+ Assert.assertNull(requestHeaders.get("sec-ch-ua-wow64"));
+
+ // Verify all user-agent client hints on the second request.
+ recordedRequest = requests.get(1);
+ Assert.assertEquals(baseUrl + SECOND_URL, recordedRequest.getUrl().toString());
+ requestHeaders = recordedRequest.getRequestHeaders();
+
+ Assert.assertEquals("\"myBrand\";v=\"1\"",
+ requestHeaders.get("sec-ch-ua"));
+ Assert.assertEquals("?1", requestHeaders.get("sec-ch-ua-mobile"));
+ Assert.assertEquals("\"myPlatform\"",
+ requestHeaders.get("sec-ch-ua-platform"));
+ Assert.assertEquals("\"2.2.2.2\"",
+ requestHeaders.get("sec-ch-ua-platform-version"));
+ Assert.assertEquals("\"myArch\"",
+ requestHeaders.get("sec-ch-ua-arch"));
+ Assert.assertEquals("\"myBrand\";v=\"1.1.1.1\"",
+ requestHeaders.get("sec-ch-ua-full-version-list"));
+ Assert.assertEquals("\"32\"",
+ requestHeaders.get("sec-ch-ua-bitness"));
+ Assert.assertEquals("\"myModel\"",
+ requestHeaders.get("sec-ch-ua-model"));
+ Assert.assertEquals("?0", requestHeaders.get("sec-ch-ua-wow64"));
+ }
+
+ @Test
public void testSetUserAgentMetadataPartialOverride() throws Throwable {
WebkitUtils.checkFeature(WebViewFeature.USER_AGENT_METADATA);
@@ -116,7 +298,7 @@
"myBrand", "1", "1.1.1.1")))
.setFullVersion("1.1.1.1")
.setPlatformVersion("2.2.2.2").setArchitecture("myArch").setMobile(true)
- .setModel("myModel").setWow64(false).setFormFactor("myFormFactor").build();
+ .setModel("myModel").setWow64(false).build();
WebSettingsCompat.setUserAgentMetadata(settings, overrideSetting);
UserAgentMetadata actualSetting = WebSettingsCompat.getUserAgentMetadata(
@@ -125,9 +307,66 @@
"Android", actualSetting.getPlatform());
assertEquals("Bitness should reset to system default if no overrides.",
UserAgentMetadata.BITNESS_DEFAULT, actualSetting.getBitness());
- assertEquals("FormFactor should be overridden value.",
- "myFormFactor", WebSettingsCompat.getUserAgentMetadata(
- settings).getFormFactor());
+ }
+
+ @Test
+ public void testSetUserAgentMetadataPartialOverrideHttpHeader() throws Throwable {
+ WebkitUtils.checkFeature(WebViewFeature.USER_AGENT_METADATA);
+
+ mWebViewOnUiThread.setWebViewClient(mTestHttpsWebViewClient);
+ WebSettings settings = mWebViewOnUiThread.getSettings();
+ settings.setJavaScriptEnabled(true);
+
+ // Overrides without setting user-agent metadata platform and bitness.
+ UserAgentMetadata overrideSetting = new UserAgentMetadata.Builder()
+ .setBrandVersionList(Collections.singletonList(
+ new UserAgentMetadata.BrandVersion(
+ "myBrand", "1", "1.1.1.1")))
+ .setFullVersion("1.1.1.1").setPlatformVersion("2.2.2.2")
+ .setArchitecture("myArch").setMobile(true).setModel("myModel").setWow64(false)
+ .build();
+ WebSettingsCompat.setUserAgentMetadata(settings, overrideSetting);
+
+ // As WebViewOnUiThread clear cache doesn't work well, using different origin to avoid
+ // client hints cache impacts other tests.
+ String baseUrl = "https://example3.com";
+ mWebViewOnUiThread.loadUrlAndWaitForCompletion(baseUrl + FIRST_URL);
+ mWebViewOnUiThread.loadUrlAndWaitForCompletion(FIRST_URL);
+ List<WebResourceRequest> requests = mTestHttpsWebViewClient.getInterceptedRequests();
+ Assert.assertEquals(2, requests.size());
+
+ // Make sure the first request has low-entropy client hints.
+ WebResourceRequest recordedRequest = requests.get(0);
+ Assert.assertEquals(baseUrl + FIRST_URL, recordedRequest.getUrl().toString());
+ Map<String, String> requestHeaders = recordedRequest.getRequestHeaders();
+ Assert.assertEquals("\"myBrand\";v=\"1\"",
+ requestHeaders.get("sec-ch-ua"));
+ Assert.assertEquals("?1", requestHeaders.get("sec-ch-ua-mobile"));
+ Assert.assertEquals("\"Android\"",
+ requestHeaders.get("sec-ch-ua-platform"));
+ Assert.assertNull(requestHeaders.get("sec-ch-ua-platform-version"));
+ Assert.assertNull(requestHeaders.get("sec-ch-ua-arch"));
+ Assert.assertNull(requestHeaders.get("sec-ch-ua-full-version-list"));
+ Assert.assertNull(requestHeaders.get("sec-ch-ua-bitness"));
+ Assert.assertNull(requestHeaders.get("sec-ch-ua-model"));
+ Assert.assertNull(requestHeaders.get("sec-ch-ua-wow64"));
+
+ // Verify all user-agent client hints on the second request.
+ recordedRequest = requests.get(1);
+ Assert.assertEquals(baseUrl + SECOND_URL, recordedRequest.getUrl().toString());
+ requestHeaders = recordedRequest.getRequestHeaders();
+
+ Assert.assertEquals("\"myBrand\";v=\"1\"",
+ requestHeaders.get("sec-ch-ua"));
+ Assert.assertEquals("?1", requestHeaders.get("sec-ch-ua-mobile"));
+ Assert.assertNotNull(requestHeaders.get("sec-ch-ua-platform-version"));
+ Assert.assertNotNull(requestHeaders.get("sec-ch-ua-arch"));
+ Assert.assertNotNull(requestHeaders.get("sec-ch-ua-full-version-list"));
+ // The default bitness on HTTP header is empty string instead of 0.
+ Assert.assertEquals("\"\"",
+ requestHeaders.get("sec-ch-ua-bitness"));
+ Assert.assertNotNull(requestHeaders.get("sec-ch-ua-model"));
+ Assert.assertNotNull(requestHeaders.get("sec-ch-ua-wow64"));
}
@Test
diff --git a/webkit/integration-tests/testapp/src/main/AndroidManifest.xml b/webkit/integration-tests/testapp/src/main/AndroidManifest.xml
index d337e48..4b1e168 100644
--- a/webkit/integration-tests/testapp/src/main/AndroidManifest.xml
+++ b/webkit/integration-tests/testapp/src/main/AndroidManifest.xml
@@ -142,6 +142,9 @@
<activity
android:name=".ImageDragActivity"
android:exported="false" />
+ <activity
+ android:name=".UserAgentMetadataActivity"
+ android:exported="true" />
<provider
android:authorities="com.example.androidx.webkit.DropDataProvider"
diff --git a/webkit/integration-tests/testapp/src/main/assets/www/user_agent_metadata_main.html b/webkit/integration-tests/testapp/src/main/assets/www/user_agent_metadata_main.html
new file mode 100644
index 0000000..aca6b25
--- /dev/null
+++ b/webkit/integration-tests/testapp/src/main/assets/www/user_agent_metadata_main.html
@@ -0,0 +1,86 @@
+<html>
+<!-- Copyright 2023 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.
+-->
+
+<head>
+ <style>
+ pre.input {
+ color: hsl(300, 24%, 40%);
+ font-style: italic;
+ font-weight: 400;
+ padding-bottom: 0;
+ }
+
+ pre.input::before {
+ content: "> ";
+ color: hsl(300, 24%, 70%);
+ }
+ </style>
+
+ <script type="text/javascript">
+ var xhr = new XMLHttpRequest();
+ xhr.onreadystatechange = function () {
+ if (this.readyState === 4 && this.status === 200) {
+ <!-- Low-entropy client hints -->
+ const brandsText = document.querySelector(".brands");
+ brandsText.textContent = JSON.stringify(navigator.userAgentData.brands, null, " ");
+ const platform = document.querySelector(".platform");
+ platform.textContent = navigator.userAgentData.platform;
+ const mobileText = document.querySelector(".mobile");
+ mobileText.textContent = navigator.userAgentData.mobile ? "true" : "false";
+
+ <!-- High-entropy client hints -->
+ const highEntropyText = document.querySelector(".high-entropy");
+ navigator.userAgentData
+ .getHighEntropyValues([
+ "architecture",
+ "bitness",
+ "brands",
+ "mobile",
+ "model",
+ "platform",
+ "platformVersion",
+ "uaFullVersion",
+ "fullVersionList",
+ ]).then((ua) => {
+ highEntropyText.textContent = JSON.stringify(ua, null, " ");
+ });
+
+ document.querySelector(".user-agent").textContent = navigator.userAgent;
+ }
+ };
+
+ xhr.open("GET", "https://example.com/androidx_webkit/example/assets/www/some_text.html", true);
+ xhr.send();
+ </script>
+</head>
+
+<body>
+ <pre class="input">console.log(navigator.userAgentData.brands);</pre>
+ <pre class="brands" disabled>[…]</pre>
+ <pre class="input">console.log(navigator.userAgentData.mobile);</pre>
+ <pre class="mobile" disabled>[…]</pre>
+ <pre class="input">console.log(navigator.userAgentData.platform);</pre>
+ <pre class="platform" disabled>[…]</pre>
+ <pre class="input">
+ navigator.userAgentData
+ .getHighEntropyValues(["architecture", "bitness", "model", "platform", "platformVersion", "uaFullVersion", "fullVersionList"])
+ .then(ua => { console.log(ua) });</pre
+ >
+ <pre class="high-entropy" disabled></pre>
+ <pre class="input">console.log(navigator.userAgent);</pre>
+ <pre class="user-agent" disabled>[…]</pre>
+</body>
+</html>
diff --git a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/MainActivity.java b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/MainActivity.java
index bf8f85e..7aaf25e 100644
--- a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/MainActivity.java
+++ b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/MainActivity.java
@@ -87,6 +87,9 @@
new MenuListView.MenuItem(
getResources().getString(R.string.image_drag_drop_activity_title),
new Intent(activityContext, ImageDragActivity.class)),
+ new MenuListView.MenuItem(
+ getResources().getString(R.string.user_agent_metadata_activity_title),
+ new Intent(activityContext, UserAgentMetadataActivity.class)),
};
listView.setItems(menuItems);
}
diff --git a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/UserAgentMetadataActivity.java b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/UserAgentMetadataActivity.java
new file mode 100644
index 0000000..e1213a7
--- /dev/null
+++ b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/UserAgentMetadataActivity.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.androidx.webkit;
+
+import static androidx.webkit.WebViewAssetLoader.AssetsPathHandler;
+
+import android.annotation.SuppressLint;
+import android.net.Uri;
+import android.os.Bundle;
+import android.webkit.WebResourceRequest;
+import android.webkit.WebResourceResponse;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.widget.RadioGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.webkit.UserAgentMetadata;
+import androidx.webkit.WebSettingsCompat;
+import androidx.webkit.WebViewAssetLoader;
+import androidx.webkit.WebViewFeature;
+
+import java.util.Collections;
+
+/**
+ * Demo activity to demonstrate the behaviour of overriding user-agent metadata APIs.
+ */
+public class UserAgentMetadataActivity extends AppCompatActivity {
+
+ private final Uri mExampleUri = new Uri.Builder()
+ .scheme("https")
+ .authority("example.com")
+ .appendPath("androidx_webkit")
+ .appendPath("example")
+ .appendPath("assets")
+ .build();
+
+ /**
+ * A WebViewClient to intercept the request to mock HTTPS response.
+ */
+ private static class MyWebViewClient extends WebViewClient {
+ private final WebViewAssetLoader mAssetLoader;
+
+ MyWebViewClient(WebViewAssetLoader loader) {
+ mAssetLoader = loader;
+ }
+
+ @Override
+ @RequiresApi(21)
+ public WebResourceResponse shouldInterceptRequest(WebView view,
+ WebResourceRequest request) {
+ return mAssetLoader.shouldInterceptRequest(Api21Impl.getUrl(request));
+ }
+
+ @Override
+ @SuppressWarnings("deprecation") // use the old one for compatibility with all API levels.
+ public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
+ return mAssetLoader.shouldInterceptRequest(Uri.parse(url));
+ }
+ }
+
+ private WebView mWebView;
+
+
+ @SuppressLint("SetJavascriptEnabled")
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_user_agent_metadata);
+
+ setTitle(R.string.user_agent_metadata_activity_title);
+ WebkitHelpers.appendWebViewVersionToTitle(this);
+
+ // Check if override user-agent metadata feature is enabled
+ if (!WebViewFeature.isFeatureSupported(WebViewFeature.USER_AGENT_METADATA)) {
+ WebkitHelpers.showMessageInActivity(this, R.string.webkit_api_not_available);
+ return;
+ }
+
+ mWebView = findViewById(R.id.user_agent_metadata_webview);
+ mWebView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
+ mWebView.getSettings().setJavaScriptEnabled(true);
+
+ RadioGroup radioGroup = findViewById(R.id.user_agent_metadata_radio_group);
+ radioGroup.check(R.id.user_agent_metadata_without_override_mode);
+ radioGroup.setOnCheckedChangeListener(this::onRadioGroupChanged);
+
+ // Initially send a request without overrides
+ refreshView(false);
+ }
+
+ private void refreshView(boolean setOverrides) {
+ UserAgentMetadata overrideSetting;
+ if (setOverrides) {
+ overrideSetting = new UserAgentMetadata.Builder()
+ .setBrandVersionList(Collections.singletonList(
+ new UserAgentMetadata.BrandVersion(
+ "myBrand", "1", "1.1.1.1")))
+ .setFullVersion("1.1.1.1").setPlatform("myPlatform")
+ .setPlatformVersion("2.2.2.2").setArchitecture("myArch")
+ .setMobile(true).setModel("myModel").setBitness(32)
+ .setWow64(false).build();
+
+ } else {
+ overrideSetting = new UserAgentMetadata.Builder().build();
+ }
+ WebSettingsCompat.setUserAgentMetadata(mWebView.getSettings(), overrideSetting);
+
+ // Use WebViewAssetLoader to load html page from app's assets.
+ WebViewAssetLoader assetLoader =
+ new WebViewAssetLoader.Builder()
+ .setDomain("example.com")
+ .addPathHandler(mExampleUri.getPath() + "/", new AssetsPathHandler(this))
+ .build();
+ mWebView.setWebViewClient(new MyWebViewClient(assetLoader));
+ mWebView.loadUrl(Uri.withAppendedPath(mExampleUri,
+ "www/user_agent_metadata_main.html").toString());
+ }
+
+ /**
+ * Handler for selecting w/o user-agent metadata mode through the radio group.
+ * @param unused Triggering radio group
+ * @param checkedId ID of checked radio button
+ */
+ public void onRadioGroupChanged(@NonNull RadioGroup unused, int checkedId) {
+ refreshView(checkedId == R.id.user_agent_metadata_with_override_mode);
+ }
+}
diff --git a/webkit/integration-tests/testapp/src/main/res/layout/activity_user_agent_metadata.xml b/webkit/integration-tests/testapp/src/main/res/layout/activity_user_agent_metadata.xml
new file mode 100644
index 0000000..1223b4e
--- /dev/null
+++ b/webkit/integration-tests/testapp/src/main/res/layout/activity_user_agent_metadata.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright 2023 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.
+ -->
+
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/activity_user_agent_metadata"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <TextView
+ android:id="@+id/user_agent_metadata_radio_group_heading"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/user_agent_metadata_override_mode"
+ android:textColor="@color/colorPrimary"
+ android:layout_alignParentTop="true"/>
+ <RadioGroup
+ android:id="@+id/user_agent_metadata_radio_group"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/user_agent_metadata_radio_group_heading">
+ <RadioButton
+ android:id="@+id/user_agent_metadata_without_override_mode"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="48dp"
+ android:text="@string/user_agent_metadata_without_override"
+ android:textColor="@color/colorAccent"/>
+ <RadioButton
+ android:id="@+id/user_agent_metadata_with_override_mode"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="48dp"
+ android:textColor="@color/colorAccent"
+ android:text="@string/user_agent_metadata_with_override"/>
+ </RadioGroup>
+ <WebView
+ android:id="@+id/user_agent_metadata_webview"
+ android:layout_width="match_parent"
+ android:layout_below="@id/user_agent_metadata_radio_group"
+ android:layout_alignParentBottom="true"
+ android:layout_height="0dp"/>
+</RelativeLayout>
\ No newline at end of file
diff --git a/webkit/integration-tests/testapp/src/main/res/values/donottranslate-strings.xml b/webkit/integration-tests/testapp/src/main/res/values/donottranslate-strings.xml
index 9ac8144..fa1d0e1 100644
--- a/webkit/integration-tests/testapp/src/main/res/values/donottranslate-strings.xml
+++ b/webkit/integration-tests/testapp/src/main/res/values/donottranslate-strings.xml
@@ -109,4 +109,10 @@
<string name="requested_with_no_allow_list">Empty</string>
<string name="requested_with_use_allow_list">Containing the web server</string>
+ <!-- User Agent Metadata -->
+ <string name="user_agent_metadata_activity_title">Override User-Agent Metadata</string>
+ <string name="user_agent_metadata_override_mode">User-Agent Metadata</string>
+ <string name="user_agent_metadata_without_override">Without Overrides</string>
+ <string name="user_agent_metadata_with_override">With Overrides</string>
+
</resources>
diff --git a/webkit/webkit/api/current.txt b/webkit/webkit/api/current.txt
index b608615..ebb7e57 100644
--- a/webkit/webkit/api/current.txt
+++ b/webkit/webkit/api/current.txt
@@ -127,6 +127,41 @@
method public abstract boolean stop(java.io.OutputStream?, java.util.concurrent.Executor);
}
+ public final class UserAgentMetadata {
+ method public String? getArchitecture();
+ method public int getBitness();
+ method public java.util.List<androidx.webkit.UserAgentMetadata.BrandVersion!>? getBrandVersionList();
+ method public String? getFullVersion();
+ method public String? getModel();
+ method public String? getPlatform();
+ method public String? getPlatformVersion();
+ method public boolean isMobile();
+ method public boolean isWow64();
+ field public static final int BITNESS_DEFAULT = 0; // 0x0
+ }
+
+ public static final class UserAgentMetadata.BrandVersion {
+ ctor public UserAgentMetadata.BrandVersion(String, String, String);
+ method public String getBrand();
+ method public String getFullVersion();
+ method public String getMajorVersion();
+ }
+
+ public static final class UserAgentMetadata.Builder {
+ ctor public UserAgentMetadata.Builder();
+ ctor public UserAgentMetadata.Builder(androidx.webkit.UserAgentMetadata);
+ method public androidx.webkit.UserAgentMetadata build();
+ method public androidx.webkit.UserAgentMetadata.Builder setArchitecture(String);
+ method public androidx.webkit.UserAgentMetadata.Builder setBitness(int);
+ method public androidx.webkit.UserAgentMetadata.Builder setBrandVersionList(java.util.List<androidx.webkit.UserAgentMetadata.BrandVersion!>);
+ method public androidx.webkit.UserAgentMetadata.Builder setFullVersion(String);
+ method public androidx.webkit.UserAgentMetadata.Builder setMobile(boolean);
+ method public androidx.webkit.UserAgentMetadata.Builder setModel(String);
+ method public androidx.webkit.UserAgentMetadata.Builder setPlatform(String);
+ method public androidx.webkit.UserAgentMetadata.Builder setPlatformVersion(String);
+ method public androidx.webkit.UserAgentMetadata.Builder setWow64(boolean);
+ }
+
public class WebMessageCompat {
ctor @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_ARRAY_BUFFER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public WebMessageCompat(byte[]);
ctor @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_ARRAY_BUFFER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public WebMessageCompat(byte[], androidx.webkit.WebMessagePortCompat![]?);
@@ -169,6 +204,7 @@
method @RequiresFeature(name=androidx.webkit.WebViewFeature.OFF_SCREEN_PRERASTER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static boolean getOffscreenPreRaster(android.webkit.WebSettings);
method @RequiresFeature(name="REQUESTED_WITH_HEADER_ALLOW_LIST", enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static java.util.Set<java.lang.String!> getRequestedWithHeaderOriginAllowList(android.webkit.WebSettings);
method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_ENABLE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static boolean getSafeBrowsingEnabled(android.webkit.WebSettings);
+ method @RequiresFeature(name=androidx.webkit.WebViewFeature.USER_AGENT_METADATA, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.UserAgentMetadata getUserAgentMetadata(android.webkit.WebSettings);
method @RequiresFeature(name=androidx.webkit.WebViewFeature.ALGORITHMIC_DARKENING, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static boolean isAlgorithmicDarkeningAllowed(android.webkit.WebSettings);
method @RequiresFeature(name=androidx.webkit.WebViewFeature.ALGORITHMIC_DARKENING, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setAlgorithmicDarkeningAllowed(android.webkit.WebSettings, boolean);
method @RequiresFeature(name=androidx.webkit.WebViewFeature.DISABLED_ACTION_MODE_MENU_ITEMS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setDisabledActionModeMenuItems(android.webkit.WebSettings, int);
@@ -178,6 +214,7 @@
method @RequiresFeature(name=androidx.webkit.WebViewFeature.OFF_SCREEN_PRERASTER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setOffscreenPreRaster(android.webkit.WebSettings, boolean);
method @RequiresFeature(name="REQUESTED_WITH_HEADER_ALLOW_LIST", enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setRequestedWithHeaderOriginAllowList(android.webkit.WebSettings, java.util.Set<java.lang.String!>);
method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_ENABLE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setSafeBrowsingEnabled(android.webkit.WebSettings, boolean);
+ method @RequiresFeature(name=androidx.webkit.WebViewFeature.USER_AGENT_METADATA, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setUserAgentMetadata(android.webkit.WebSettings, androidx.webkit.UserAgentMetadata);
field @Deprecated public static final int DARK_STRATEGY_PREFER_WEB_THEME_OVER_USER_AGENT_DARKENING = 2; // 0x2
field @Deprecated public static final int DARK_STRATEGY_USER_AGENT_DARKENING_ONLY = 0; // 0x0
field @Deprecated public static final int DARK_STRATEGY_WEB_THEME_DARKENING_ONLY = 1; // 0x1
@@ -297,6 +334,7 @@
field public static final String STARTUP_FEATURE_SET_DIRECTORY_BASE_PATHS = "STARTUP_FEATURE_SET_DIRECTORY_BASE_PATHS";
field public static final String START_SAFE_BROWSING = "START_SAFE_BROWSING";
field public static final String TRACING_CONTROLLER_BASIC_USAGE = "TRACING_CONTROLLER_BASIC_USAGE";
+ field public static final String USER_AGENT_METADATA = "USER_AGENT_METADATA";
field public static final String VISUAL_STATE_CALLBACK = "VISUAL_STATE_CALLBACK";
field public static final String WEB_MESSAGE_ARRAY_BUFFER = "WEB_MESSAGE_ARRAY_BUFFER";
field public static final String WEB_MESSAGE_CALLBACK_ON_MESSAGE = "WEB_MESSAGE_CALLBACK_ON_MESSAGE";
diff --git a/webkit/webkit/api/restricted_current.txt b/webkit/webkit/api/restricted_current.txt
index b608615..ebb7e57 100644
--- a/webkit/webkit/api/restricted_current.txt
+++ b/webkit/webkit/api/restricted_current.txt
@@ -127,6 +127,41 @@
method public abstract boolean stop(java.io.OutputStream?, java.util.concurrent.Executor);
}
+ public final class UserAgentMetadata {
+ method public String? getArchitecture();
+ method public int getBitness();
+ method public java.util.List<androidx.webkit.UserAgentMetadata.BrandVersion!>? getBrandVersionList();
+ method public String? getFullVersion();
+ method public String? getModel();
+ method public String? getPlatform();
+ method public String? getPlatformVersion();
+ method public boolean isMobile();
+ method public boolean isWow64();
+ field public static final int BITNESS_DEFAULT = 0; // 0x0
+ }
+
+ public static final class UserAgentMetadata.BrandVersion {
+ ctor public UserAgentMetadata.BrandVersion(String, String, String);
+ method public String getBrand();
+ method public String getFullVersion();
+ method public String getMajorVersion();
+ }
+
+ public static final class UserAgentMetadata.Builder {
+ ctor public UserAgentMetadata.Builder();
+ ctor public UserAgentMetadata.Builder(androidx.webkit.UserAgentMetadata);
+ method public androidx.webkit.UserAgentMetadata build();
+ method public androidx.webkit.UserAgentMetadata.Builder setArchitecture(String);
+ method public androidx.webkit.UserAgentMetadata.Builder setBitness(int);
+ method public androidx.webkit.UserAgentMetadata.Builder setBrandVersionList(java.util.List<androidx.webkit.UserAgentMetadata.BrandVersion!>);
+ method public androidx.webkit.UserAgentMetadata.Builder setFullVersion(String);
+ method public androidx.webkit.UserAgentMetadata.Builder setMobile(boolean);
+ method public androidx.webkit.UserAgentMetadata.Builder setModel(String);
+ method public androidx.webkit.UserAgentMetadata.Builder setPlatform(String);
+ method public androidx.webkit.UserAgentMetadata.Builder setPlatformVersion(String);
+ method public androidx.webkit.UserAgentMetadata.Builder setWow64(boolean);
+ }
+
public class WebMessageCompat {
ctor @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_ARRAY_BUFFER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public WebMessageCompat(byte[]);
ctor @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_ARRAY_BUFFER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public WebMessageCompat(byte[], androidx.webkit.WebMessagePortCompat![]?);
@@ -169,6 +204,7 @@
method @RequiresFeature(name=androidx.webkit.WebViewFeature.OFF_SCREEN_PRERASTER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static boolean getOffscreenPreRaster(android.webkit.WebSettings);
method @RequiresFeature(name="REQUESTED_WITH_HEADER_ALLOW_LIST", enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static java.util.Set<java.lang.String!> getRequestedWithHeaderOriginAllowList(android.webkit.WebSettings);
method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_ENABLE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static boolean getSafeBrowsingEnabled(android.webkit.WebSettings);
+ method @RequiresFeature(name=androidx.webkit.WebViewFeature.USER_AGENT_METADATA, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.UserAgentMetadata getUserAgentMetadata(android.webkit.WebSettings);
method @RequiresFeature(name=androidx.webkit.WebViewFeature.ALGORITHMIC_DARKENING, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static boolean isAlgorithmicDarkeningAllowed(android.webkit.WebSettings);
method @RequiresFeature(name=androidx.webkit.WebViewFeature.ALGORITHMIC_DARKENING, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setAlgorithmicDarkeningAllowed(android.webkit.WebSettings, boolean);
method @RequiresFeature(name=androidx.webkit.WebViewFeature.DISABLED_ACTION_MODE_MENU_ITEMS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setDisabledActionModeMenuItems(android.webkit.WebSettings, int);
@@ -178,6 +214,7 @@
method @RequiresFeature(name=androidx.webkit.WebViewFeature.OFF_SCREEN_PRERASTER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setOffscreenPreRaster(android.webkit.WebSettings, boolean);
method @RequiresFeature(name="REQUESTED_WITH_HEADER_ALLOW_LIST", enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setRequestedWithHeaderOriginAllowList(android.webkit.WebSettings, java.util.Set<java.lang.String!>);
method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_ENABLE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setSafeBrowsingEnabled(android.webkit.WebSettings, boolean);
+ method @RequiresFeature(name=androidx.webkit.WebViewFeature.USER_AGENT_METADATA, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setUserAgentMetadata(android.webkit.WebSettings, androidx.webkit.UserAgentMetadata);
field @Deprecated public static final int DARK_STRATEGY_PREFER_WEB_THEME_OVER_USER_AGENT_DARKENING = 2; // 0x2
field @Deprecated public static final int DARK_STRATEGY_USER_AGENT_DARKENING_ONLY = 0; // 0x0
field @Deprecated public static final int DARK_STRATEGY_WEB_THEME_DARKENING_ONLY = 1; // 0x1
@@ -297,6 +334,7 @@
field public static final String STARTUP_FEATURE_SET_DIRECTORY_BASE_PATHS = "STARTUP_FEATURE_SET_DIRECTORY_BASE_PATHS";
field public static final String START_SAFE_BROWSING = "START_SAFE_BROWSING";
field public static final String TRACING_CONTROLLER_BASIC_USAGE = "TRACING_CONTROLLER_BASIC_USAGE";
+ field public static final String USER_AGENT_METADATA = "USER_AGENT_METADATA";
field public static final String VISUAL_STATE_CALLBACK = "VISUAL_STATE_CALLBACK";
field public static final String WEB_MESSAGE_ARRAY_BUFFER = "WEB_MESSAGE_ARRAY_BUFFER";
field public static final String WEB_MESSAGE_CALLBACK_ON_MESSAGE = "WEB_MESSAGE_CALLBACK_ON_MESSAGE";
diff --git a/webkit/webkit/src/main/java/androidx/webkit/UserAgentMetadata.java b/webkit/webkit/src/main/java/androidx/webkit/UserAgentMetadata.java
index cbcc3d3..f799f505 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/UserAgentMetadata.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/UserAgentMetadata.java
@@ -16,6 +16,8 @@
package androidx.webkit;
+import android.annotation.SuppressLint;
+
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
@@ -29,13 +31,8 @@
* <p>
* This class is functionally equivalent to
* <a href="https://wicg.github.io/ua-client-hints/#interface">UADataValues</a>.
- * <p>
- * TODO(b/294183509): unhide
- *
- * @hide
*/
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class UserAgentMetadata {
+public final class UserAgentMetadata {
/**
* Use this value for bitness to use the platform's default bitness value, which is an empty
* string for Android WebView.
@@ -52,7 +49,6 @@
private boolean mMobile = true;
private int mBitness = BITNESS_DEFAULT;
private boolean mWow64 = false;
- private final String mFormFactor;
@RestrictTo(RestrictTo.Scope.LIBRARY)
private UserAgentMetadata(@Nullable List<BrandVersion> brandVersionList,
@@ -60,7 +56,7 @@
@Nullable String platformVersion, @Nullable String architecture,
@Nullable String model,
boolean mobile,
- int bitness, boolean wow64, @Nullable String formFactor) {
+ int bitness, boolean wow64) {
mBrandVersionList = brandVersionList;
mFullVersion = fullVersion;
mPlatform = platform;
@@ -70,7 +66,6 @@
mMobile = mobile;
mBitness = bitness;
mWow64 = wow64;
- mFormFactor = formFactor;
}
/**
@@ -82,6 +77,7 @@
* @see Builder#setBrandVersionList
*
*/
+ @SuppressLint("NullableCollection")
@Nullable
public List<BrandVersion> getBrandVersionList() {
return mBrandVersionList;
@@ -172,24 +168,14 @@
* <p>
* @see Builder#setWow64
*
- * @return A boolean to indicate whether user-agent's binary is running in 64-bit Windows.
+ * @return A boolean to indicate whether user-agent's binary is running in 32-bit mode on
+ * 64-bit Windows.
*/
public boolean isWow64() {
return mWow64;
}
/**
- * Returns the value for the {@code sec-ch-ua-form-factor} client hint.
- * <p>
- * @see Builder#setFormFactor
- *
- */
- @Nullable
- public String getFormFactor() {
- return mFormFactor;
- }
-
- /**
* Two UserAgentMetadata objects are equal only if all the metadata values are equal.
*/
@Override
@@ -202,14 +188,13 @@
&& Objects.equals(mFullVersion, that.mFullVersion)
&& Objects.equals(mPlatform, that.mPlatform) && Objects.equals(
mPlatformVersion, that.mPlatformVersion) && Objects.equals(mArchitecture,
- that.mArchitecture) && Objects.equals(mModel, that.mModel)
- && Objects.equals(mFormFactor, that.mFormFactor);
+ that.mArchitecture) && Objects.equals(mModel, that.mModel);
}
@Override
public int hashCode() {
return Objects.hash(mBrandVersionList, mFullVersion, mPlatform, mPlatformVersion,
- mArchitecture, mModel, mMobile, mBitness, mWow64, mFormFactor);
+ mArchitecture, mModel, mMobile, mBitness, mWow64);
}
/**
@@ -221,12 +206,11 @@
* <a href="https://wicg.github.io/ua-client-hints/#interface">NavigatorUABrandVersion</a>.
*
*/
- public static class BrandVersion {
+ public static final class BrandVersion {
private final String mBrand;
private final String mMajorVersion;
private final String mFullVersion;
- @RestrictTo(RestrictTo.Scope.LIBRARY)
public BrandVersion(@NonNull String brand, @NonNull String majorVersion,
@NonNull String fullVersion) {
if (brand.trim().isEmpty() || majorVersion.trim().isEmpty()
@@ -323,7 +307,6 @@
private boolean mMobile = true;
private int mBitness = BITNESS_DEFAULT;
private boolean mWow64 = false;
- private String mFormFactor;
/**
* Create an empty UserAgentMetadata Builder.
@@ -344,7 +327,6 @@
mMobile = uaMetadata.isMobile();
mBitness = uaMetadata.getBitness();
mWow64 = uaMetadata.isWow64();
- mFormFactor = uaMetadata.getFormFactor();
}
/**
@@ -355,13 +337,14 @@
@NonNull
public UserAgentMetadata build() {
return new UserAgentMetadata(mBrandVersionList, mFullVersion, mPlatform,
- mPlatformVersion, mArchitecture, mModel, mMobile, mBitness, mWow64,
- mFormFactor);
+ mPlatformVersion, mArchitecture, mModel, mMobile, mBitness, mWow64);
}
/**
* Sets user-agent metadata brands and their versions. The brand name, major version and
- * full version should not be blank.
+ * full version should not be blank. The default value is null which means the system
+ * default user-agent metadata brands and versions will be used to generate the
+ * user-agent client hints.
*
* @param brandVersions a list of {@link BrandVersion} used to generated user-agent client
* hints {@code sec-cu-ua} and {@code sec-ch-ua-full-version-list}.
@@ -493,19 +476,5 @@
mWow64 = wow64;
return this;
}
-
- /**
- * Sets the user-agent metadata form factor. The value should not be null but can be
- * empty string.
- *
- * @param formFactor The form factor is used to generate user-agent client hint
- * {@code sec-ch-ua-form-factor}.
- *
- */
- @NonNull
- public Builder setFormFactor(@NonNull String formFactor) {
- mFormFactor = formFactor;
- return this;
- }
}
}
diff --git a/webkit/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java b/webkit/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java
index 9ce9e66..4a544da 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java
@@ -722,12 +722,9 @@
* {@link WebViewFeature#isFeatureSupported(String)}
* returns true for {@link WebViewFeature#USER_AGENT_METADATA}.
*
+ * @param settings Settings retrieved from {@link WebView#getSettings()}.
* @param metadata the WebView's user-agent metadata.
- *
- * TODO(b/294183509): unhide
- * @hide
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@RequiresFeature(name = WebViewFeature.USER_AGENT_METADATA,
enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
public static void setUserAgentMetadata(@NonNull WebSettings settings,
@@ -752,10 +749,8 @@
* {@link WebViewFeature#isFeatureSupported(String)}
* returns true for {@link WebViewFeature#USER_AGENT_METADATA}.
*
- * TODO(b/294183509): unhide
- * @hide
+ * @param settings Settings retrieved from {@link WebView#getSettings()}.
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@RequiresFeature(name = WebViewFeature.USER_AGENT_METADATA,
enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
@NonNull
diff --git a/webkit/webkit/src/main/java/androidx/webkit/WebViewFeature.java b/webkit/webkit/src/main/java/androidx/webkit/WebViewFeature.java
index 81ba5ed..11936ee 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/WebViewFeature.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/WebViewFeature.java
@@ -538,13 +538,9 @@
/**
* Feature for {@link #isFeatureSupported(String)}.
* This feature covers
- * {@link androidx.webkit.ServiceWorkerWebSettingsCompat#getUserAgentMetadata(WebSettings)}, and
- * {@link androidx.webkit.ServiceWorkerWebSettingsCompat#setUserAgentMetadata(WebSettings, UserAgentMetadata)}.
- *
- * TODO(b/294183509): unhide
- * @hide
+ * {@link androidx.webkit.WebSettingsCompat#getUserAgentMetadata(WebSettings)}, and
+ * {@link androidx.webkit.WebSettingsCompat#setUserAgentMetadata(WebSettings, UserAgentMetadata)}.
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public static final String USER_AGENT_METADATA = "USER_AGENT_METADATA";
/**
diff --git a/webkit/webkit/src/main/java/androidx/webkit/internal/UserAgentMetadataInternal.java b/webkit/webkit/src/main/java/androidx/webkit/internal/UserAgentMetadataInternal.java
index 96beee3..dfd9747 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/internal/UserAgentMetadataInternal.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/internal/UserAgentMetadataInternal.java
@@ -90,12 +90,6 @@
*/
private static final String WOW64 = "WOW64";
/**
- * Predefined set of name for user-agent metadata key.
- * Key name for user-agent metadata form_factor,
- * used to generate user-agent client hint {@code sec-ch-ua-form-factor}.
- */
- private static final String FORM_FACTOR = "FORM_FACTOR";
- /**
* each brand should contains brand, major version and full version.
*/
private static final int BRAND_VERSION_LENGTH = 3;
@@ -118,7 +112,6 @@
item.put(MOBILE, uaMetadata.isMobile());
item.put(BITNESS, uaMetadata.getBitness());
item.put(WOW64, uaMetadata.isWow64());
- item.put(FORM_FACTOR, uaMetadata.getFormFactor());
return item;
}
@@ -200,10 +193,6 @@
builder.setWow64(isWow64);
}
- String formFactor = (String) uaMetadataMap.get(FORM_FACTOR);
- if (formFactor != null) {
- builder.setFormFactor(formFactor);
- }
return builder.build();
}
}