Merge "Fix modifiers of properties & accessors" into androidx-master-dev
diff --git a/activity/activity/src/main/java/androidx/activity/result/ActivityResultLauncher.java b/activity/activity/src/main/java/androidx/activity/result/ActivityResultLauncher.java
index 84aa1bf..b903221 100644
--- a/activity/activity/src/main/java/androidx/activity/result/ActivityResultLauncher.java
+++ b/activity/activity/src/main/java/androidx/activity/result/ActivityResultLauncher.java
@@ -36,7 +36,12 @@
/**
* Executes an {@link ActivityResultContract}.
*
+ * <p>This method throws {@link android.content.ActivityNotFoundException}
+ * if there was no Activity found to run the given Intent.
+
* @param input the input required to execute an {@link ActivityResultContract}.
+ *
+ * @throws android.content.ActivityNotFoundException
*/
public void launch(@SuppressLint("UnknownNullness") I input) {
launch(input, null);
@@ -45,8 +50,13 @@
/**
* Executes an {@link ActivityResultContract}.
*
+ * <p>This method throws {@link android.content.ActivityNotFoundException}
+ * if there was no Activity found to run the given Intent.
+ *
* @param input the input required to execute an {@link ActivityResultContract}.
* @param options Additional options for how the Activity should be started.
+ *
+ * @throws android.content.ActivityNotFoundException
*/
public abstract void launch(@SuppressLint("UnknownNullness") I input,
@Nullable ActivityOptionsCompat options);
diff --git a/appsearch/appsearch/api/current.txt b/appsearch/appsearch/api/current.txt
index 8cfd64d..0670b58 100644
--- a/appsearch/appsearch/api/current.txt
+++ b/appsearch/appsearch/api/current.txt
@@ -54,7 +54,7 @@
public final class AppSearchSchema {
method public java.util.List<androidx.appsearch.app.AppSearchSchema.PropertyConfig!> getProperties();
- method public String getSchemaTypeName();
+ method public String getSchemaType();
}
public static final class AppSearchSchema.Builder {
@@ -226,7 +226,7 @@
method public int getNumPerPage();
method public int getOrder();
method public int getRankingStrategy();
- method public java.util.List<java.lang.String!> getSchemas();
+ method public java.util.List<java.lang.String!> getSchemaTypes();
method public int getSnippetCount();
method public int getSnippetCountPerProperty();
method public int getTermMatch();
@@ -243,10 +243,10 @@
ctor public SearchSpec.Builder();
method public androidx.appsearch.app.SearchSpec.Builder addNamespace(java.lang.String!...);
method public androidx.appsearch.app.SearchSpec.Builder addNamespace(java.util.Collection<java.lang.String!>);
- method public androidx.appsearch.app.SearchSpec.Builder addSchema(java.lang.String!...);
- method public androidx.appsearch.app.SearchSpec.Builder addSchema(java.util.Collection<java.lang.String!>);
method public androidx.appsearch.app.SearchSpec.Builder addSchemaByDataClass(java.util.Collection<java.lang.Class<?>!>) throws androidx.appsearch.exceptions.AppSearchException;
method public androidx.appsearch.app.SearchSpec.Builder addSchemaByDataClass(Class<?>!...) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.app.SearchSpec.Builder addSchemaType(java.lang.String!...);
+ method public androidx.appsearch.app.SearchSpec.Builder addSchemaType(java.util.Collection<java.lang.String!>);
method public androidx.appsearch.app.SearchSpec build();
method public androidx.appsearch.app.SearchSpec.Builder setMaxSnippetSize(int);
method public androidx.appsearch.app.SearchSpec.Builder setNumPerPage(int);
diff --git a/appsearch/appsearch/api/public_plus_experimental_current.txt b/appsearch/appsearch/api/public_plus_experimental_current.txt
index 8cfd64d..0670b58 100644
--- a/appsearch/appsearch/api/public_plus_experimental_current.txt
+++ b/appsearch/appsearch/api/public_plus_experimental_current.txt
@@ -54,7 +54,7 @@
public final class AppSearchSchema {
method public java.util.List<androidx.appsearch.app.AppSearchSchema.PropertyConfig!> getProperties();
- method public String getSchemaTypeName();
+ method public String getSchemaType();
}
public static final class AppSearchSchema.Builder {
@@ -226,7 +226,7 @@
method public int getNumPerPage();
method public int getOrder();
method public int getRankingStrategy();
- method public java.util.List<java.lang.String!> getSchemas();
+ method public java.util.List<java.lang.String!> getSchemaTypes();
method public int getSnippetCount();
method public int getSnippetCountPerProperty();
method public int getTermMatch();
@@ -243,10 +243,10 @@
ctor public SearchSpec.Builder();
method public androidx.appsearch.app.SearchSpec.Builder addNamespace(java.lang.String!...);
method public androidx.appsearch.app.SearchSpec.Builder addNamespace(java.util.Collection<java.lang.String!>);
- method public androidx.appsearch.app.SearchSpec.Builder addSchema(java.lang.String!...);
- method public androidx.appsearch.app.SearchSpec.Builder addSchema(java.util.Collection<java.lang.String!>);
method public androidx.appsearch.app.SearchSpec.Builder addSchemaByDataClass(java.util.Collection<java.lang.Class<?>!>) throws androidx.appsearch.exceptions.AppSearchException;
method public androidx.appsearch.app.SearchSpec.Builder addSchemaByDataClass(Class<?>!...) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.app.SearchSpec.Builder addSchemaType(java.lang.String!...);
+ method public androidx.appsearch.app.SearchSpec.Builder addSchemaType(java.util.Collection<java.lang.String!>);
method public androidx.appsearch.app.SearchSpec build();
method public androidx.appsearch.app.SearchSpec.Builder setMaxSnippetSize(int);
method public androidx.appsearch.app.SearchSpec.Builder setNumPerPage(int);
diff --git a/appsearch/appsearch/api/restricted_current.txt b/appsearch/appsearch/api/restricted_current.txt
index 8cfd64d..0670b58 100644
--- a/appsearch/appsearch/api/restricted_current.txt
+++ b/appsearch/appsearch/api/restricted_current.txt
@@ -54,7 +54,7 @@
public final class AppSearchSchema {
method public java.util.List<androidx.appsearch.app.AppSearchSchema.PropertyConfig!> getProperties();
- method public String getSchemaTypeName();
+ method public String getSchemaType();
}
public static final class AppSearchSchema.Builder {
@@ -226,7 +226,7 @@
method public int getNumPerPage();
method public int getOrder();
method public int getRankingStrategy();
- method public java.util.List<java.lang.String!> getSchemas();
+ method public java.util.List<java.lang.String!> getSchemaTypes();
method public int getSnippetCount();
method public int getSnippetCountPerProperty();
method public int getTermMatch();
@@ -243,10 +243,10 @@
ctor public SearchSpec.Builder();
method public androidx.appsearch.app.SearchSpec.Builder addNamespace(java.lang.String!...);
method public androidx.appsearch.app.SearchSpec.Builder addNamespace(java.util.Collection<java.lang.String!>);
- method public androidx.appsearch.app.SearchSpec.Builder addSchema(java.lang.String!...);
- method public androidx.appsearch.app.SearchSpec.Builder addSchema(java.util.Collection<java.lang.String!>);
method public androidx.appsearch.app.SearchSpec.Builder addSchemaByDataClass(java.util.Collection<java.lang.Class<?>!>) throws androidx.appsearch.exceptions.AppSearchException;
method public androidx.appsearch.app.SearchSpec.Builder addSchemaByDataClass(Class<?>!...) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.app.SearchSpec.Builder addSchemaType(java.lang.String!...);
+ method public androidx.appsearch.app.SearchSpec.Builder addSchemaType(java.util.Collection<java.lang.String!>);
method public androidx.appsearch.app.SearchSpec build();
method public androidx.appsearch.app.SearchSpec.Builder setMaxSnippetSize(int);
method public androidx.appsearch.app.SearchSpec.Builder setNumPerPage(int);
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AnnotationProcessorTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AnnotationProcessorTest.java
index 09ac4df..04ae3f3 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AnnotationProcessorTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AnnotationProcessorTest.java
@@ -279,7 +279,7 @@
SearchResults searchResults = mSession.query("",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
- .addSchema("Gift", AppSearchEmail.SCHEMA_TYPE)
+ .addSchemaType("Gift", AppSearchEmail.SCHEMA_TYPE)
.build());
List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
assertThat(documents).hasSize(3);
@@ -297,7 +297,7 @@
searchResults = mSession.query("",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
- .addSchema(AppSearchEmail.SCHEMA_TYPE)
+ .addSchemaType(AppSearchEmail.SCHEMA_TYPE)
.addSchemaByDataClass(Gift.class)
.build());
documents = convertSearchResultsToDocuments(searchResults);
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AppSearchSessionTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AppSearchSessionTest.java
index 48129c8..d97a706 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AppSearchSessionTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AppSearchSessionTest.java
@@ -516,7 +516,7 @@
// Query only for Document
searchResults = mDb1.query("body", new SearchSpec.Builder()
- .addSchema("Generic", "Generic") // duplicate type in filter won't matter.
+ .addSchemaType("Generic", "Generic") // duplicate type in filter won't matter.
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.build());
documents = convertSearchResultsToDocuments(searchResults);
@@ -647,7 +647,7 @@
// Query for the document
SearchResults searchResults = mDb1.query("foo",
new SearchSpec.Builder()
- .addSchema("Generic")
+ .addSchemaType("Generic")
.setSnippetCount(1)
.setSnippetCountPerProperty(1)
.setMaxSnippetSize(10)
@@ -851,7 +851,7 @@
checkIsResultSuccess(mDb1.removeByQuery("",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
- .addSchema(AppSearchEmail.SCHEMA_TYPE)
+ .addSchemaType(AppSearchEmail.SCHEMA_TYPE)
.build()));
// Make sure it's really gone
@@ -902,7 +902,7 @@
checkIsResultSuccess(mDb1.removeByQuery("",
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
- .addSchema(AppSearchEmail.SCHEMA_TYPE)
+ .addSchemaType(AppSearchEmail.SCHEMA_TYPE)
.build()));
// Make sure it's really gone in instance 1
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/GlobalSearchSessionTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/GlobalSearchSessionTest.java
index 2a60798..b06ccfa 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/GlobalSearchSessionTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/GlobalSearchSessionTest.java
@@ -234,7 +234,7 @@
// Query only for email documents
searchResults = mGlobalAppSearchManager.query("body", new SearchSpec.Builder()
- .addSchema(AppSearchEmail.SCHEMA_TYPE)
+ .addSchemaType(AppSearchEmail.SCHEMA_TYPE)
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.build());
documents = convertSearchResultsToDocuments(searchResults);
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/SearchSpecTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/SearchSpecTest.java
index 3945e06..0d2dae4 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/SearchSpecTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/SearchSpecTest.java
@@ -28,7 +28,7 @@
@Test
public void buildSearchSpecWithoutTermMatchType() {
assertThrows(RuntimeException.class, () -> new SearchSpec.Builder()
- .addSchema("testSchemaType")
+ .addSchemaType("testSchemaType")
.build());
}
@@ -37,7 +37,7 @@
SearchSpec searchSpec = new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
.addNamespace("namespace1", "namespace2")
- .addSchema("schemaTypes1", "schemaTypes2")
+ .addSchemaType("schemaTypes1", "schemaTypes2")
.setSnippetCount(5)
.setSnippetCountPerProperty(10)
.setMaxSnippetSize(15)
@@ -49,7 +49,7 @@
assertThat(searchSpec.getTermMatch()).isEqualTo(SearchSpec.TERM_MATCH_PREFIX);
assertThat(searchSpec.getNamespaces())
.containsExactly("namespace1", "namespace2").inOrder();
- assertThat(searchSpec.getSchemas())
+ assertThat(searchSpec.getSchemaTypes())
.containsExactly("schemaTypes1", "schemaTypes2").inOrder();
assertThat(searchSpec.getSnippetCount()).isEqualTo(5);
assertThat(searchSpec.getSnippetCountPerProperty()).isEqualTo(10);
@@ -65,7 +65,7 @@
SearchSpec searchSpec = new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
.addNamespace("namespace1", "namespace2")
- .addSchema("schemaTypes1", "schemaTypes2")
+ .addSchemaType("schemaTypes1", "schemaTypes2")
.setSnippetCount(5)
.setSnippetCountPerProperty(10)
.setMaxSnippetSize(15)
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 efc3db4..6db51e2 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchSchema.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchSchema.java
@@ -71,7 +71,7 @@
/** Returns the name of this schema type, e.g. Email. */
@NonNull
- public String getSchemaTypeName() {
+ public String getSchemaType() {
return mBundle.getString(SCHEMA_TYPE_FIELD, "");
}
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 8645d52..674b906 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/GenericDocument.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/GenericDocument.java
@@ -78,9 +78,8 @@
* The maximum number of indexed properties a document can have.
*
* <p>Indexed properties are properties where the
- * {@link androidx.appsearch.annotation.AppSearchDocument.Property#indexingType} constant is
- * anything other than {@link
- * androidx.appsearch.app.AppSearchSchema.PropertyConfig.IndexingType#INDEXING_TYPE_NONE}.
+ * {@link AppSearchSchema.PropertyConfig#getIndexingType()} constant is anything other than
+ * {@link AppSearchSchema.PropertyConfig.IndexingType#INDEXING_TYPE_NONE}.
*/
public static int getMaxIndexedProperties() {
return MAX_INDEXED_PROPERTIES;
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSpec.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSpec.java
index b8c5199..aa1493b 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSpec.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSpec.java
@@ -153,12 +153,12 @@
* <p>If empty, the query will search over all schema types.
*/
@NonNull
- public List<String> getSchemas() {
- List<String> schemas = mBundle.getStringArrayList(SCHEMA_TYPE_FIELD);
- if (schemas == null) {
+ public List<String> getSchemaTypes() {
+ List<String> schemaTypes = mBundle.getStringArrayList(SCHEMA_TYPE_FIELD);
+ if (schemaTypes == null) {
return Collections.emptyList();
}
- return Collections.unmodifiableList(schemas);
+ return Collections.unmodifiableList(schemaTypes);
}
/**
@@ -240,10 +240,10 @@
* <p>If unset, the query will search over all schema types.
*/
@NonNull
- public Builder addSchema(@NonNull String... schemaTypes) {
+ public Builder addSchemaType(@NonNull String... schemaTypes) {
Preconditions.checkNotNull(schemaTypes);
Preconditions.checkState(!mBuilt, "Builder has already been used");
- return addSchema(Arrays.asList(schemaTypes));
+ return addSchemaType(Arrays.asList(schemaTypes));
}
/**
@@ -253,7 +253,7 @@
* <p>If unset, the query will search over all schema types.
*/
@NonNull
- public Builder addSchema(@NonNull Collection<String> schemaTypes) {
+ public Builder addSchemaType(@NonNull Collection<String> schemaTypes) {
Preconditions.checkNotNull(schemaTypes);
Preconditions.checkState(!mBuilt, "Builder has already been used");
mSchemaTypes.addAll(schemaTypes);
@@ -269,7 +269,7 @@
* @param dataClasses classes annotated with
* {@link androidx.appsearch.annotation.AppSearchDocument}.
*/
- @SuppressLint("MissingGetterMatchingBuilder") // Merged list available from getSchemas()
+ @SuppressLint("MissingGetterMatchingBuilder") // Merged list available from getSchemaTypes
@NonNull
public Builder addSchemaByDataClass(@NonNull Collection<Class<?>> dataClasses)
throws AppSearchException {
@@ -281,7 +281,7 @@
DataClassFactory<?> factory = registry.getOrCreateFactory(dataClass);
schemaTypes.add(factory.getSchemaType());
}
- addSchema(schemaTypes);
+ addSchemaType(schemaTypes);
return this;
}
diff --git a/appsearch/local-storage/src/androidTest/java/androidx/appsearch/localstorage/AppSearchImplTest.java b/appsearch/local-storage/src/androidTest/java/androidx/appsearch/localstorage/AppSearchImplTest.java
index 82be96d..ae2427f 100644
--- a/appsearch/local-storage/src/androidTest/java/androidx/appsearch/localstorage/AppSearchImplTest.java
+++ b/appsearch/local-storage/src/androidTest/java/androidx/appsearch/localstorage/AppSearchImplTest.java
@@ -396,7 +396,7 @@
@Test
public void testRemoveEmptyDatabase_NoExceptionThrown() throws Exception {
SearchSpec searchSpec =
- new SearchSpec.Builder().addSchema("FakeType").setTermMatch(
+ new SearchSpec.Builder().addSchemaType("FakeType").setTermMatch(
TermMatchType.Code.PREFIX_VALUE).build();
mAppSearchImpl.removeByQuery("EmptyDatabase",
"", searchSpec);
diff --git a/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/SearchSessionImpl.java b/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/SearchSessionImpl.java
index 6a201f5..23583b0 100644
--- a/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/SearchSessionImpl.java
+++ b/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/SearchSessionImpl.java
@@ -89,7 +89,7 @@
Set<String> schemasHiddenFromPlatformSurfaces =
new ArraySet<>(appSearchSchemasHiddenFromPlatformSurfaces.size());
for (AppSearchSchema schema : appSearchSchemasHiddenFromPlatformSurfaces) {
- schemasHiddenFromPlatformSurfaces.add(schema.getSchemaTypeName());
+ schemasHiddenFromPlatformSurfaces.add(schema.getSchemaType());
}
mAppSearchImpl.setVisibility(mDatabaseName, schemasHiddenFromPlatformSurfaces);
diff --git a/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/converter/SchemaToProtoConverter.java b/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/converter/SchemaToProtoConverter.java
index 296cc59..86855e1 100644
--- a/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/converter/SchemaToProtoConverter.java
+++ b/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/converter/SchemaToProtoConverter.java
@@ -44,7 +44,7 @@
public static SchemaTypeConfigProto convert(@NonNull AppSearchSchema schema) {
Preconditions.checkNotNull(schema);
SchemaTypeConfigProto.Builder protoBuilder =
- SchemaTypeConfigProto.newBuilder().setSchemaType(schema.getSchemaTypeName());
+ SchemaTypeConfigProto.newBuilder().setSchemaType(schema.getSchemaType());
List<AppSearchSchema.PropertyConfig> properties = schema.getProperties();
for (int i = 0; i < properties.size(); i++) {
PropertyConfigProto propertyProto = convertProperty(properties.get(i));
diff --git a/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/converter/SearchSpecToProtoConverter.java b/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/converter/SearchSpecToProtoConverter.java
index 810b69d..0e1d933 100644
--- a/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/converter/SearchSpecToProtoConverter.java
+++ b/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/converter/SearchSpecToProtoConverter.java
@@ -39,7 +39,7 @@
public static SearchSpecProto toSearchSpecProto(@NonNull SearchSpec spec) {
Preconditions.checkNotNull(spec);
SearchSpecProto.Builder protoBuilder = SearchSpecProto.newBuilder()
- .addAllSchemaTypeFilters(spec.getSchemas())
+ .addAllSchemaTypeFilters(spec.getSchemaTypes())
.addAllNamespaceFilters(spec.getNamespaces());
@SearchSpec.TermMatch int termMatchCode = spec.getTermMatch();
diff --git a/benchmark/benchmark-macro-runtime/src/androidTest/java/androidx/benchmark/macro/test/ActionsTest.kt b/benchmark/benchmark-macro-runtime/src/androidTest/java/androidx/benchmark/macro/test/ActionsTest.kt
index f58c8ca..cee0b1f 100644
--- a/benchmark/benchmark-macro-runtime/src/androidTest/java/androidx/benchmark/macro/test/ActionsTest.kt
+++ b/benchmark/benchmark-macro-runtime/src/androidTest/java/androidx/benchmark/macro/test/ActionsTest.kt
@@ -16,7 +16,6 @@
package androidx.benchmark.macro.test
-import android.content.Intent
import androidx.benchmark.macro.CompilationMode
import androidx.benchmark.macro.MacrobenchmarkScope
import androidx.benchmark.macro.compile
@@ -40,9 +39,7 @@
fun killTest() {
val scope = MacrobenchmarkScope(PACKAGE_NAME)
scope.pressHome()
- scope.launchPackageAndWait { intent ->
- intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
- }
+ scope.launchPackageAndWait()
assertTrue(isProcessAlive(PACKAGE_NAME))
scope.killProcess()
assertFalse(isProcessAlive(PACKAGE_NAME))
@@ -58,9 +55,7 @@
compilation.compile(PACKAGE_NAME) {
executions += 1
scope.pressHome()
- scope.launchPackageAndWait { intent ->
- intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
- }
+ scope.launchPackageAndWait()
}
assertEquals(iterations, executions)
}
diff --git a/benchmark/benchmark-macro-runtime/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt b/benchmark/benchmark-macro-runtime/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt
index 12feb34..9ca2ce7 100644
--- a/benchmark/benchmark-macro-runtime/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt
+++ b/benchmark/benchmark-macro-runtime/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt
@@ -33,7 +33,11 @@
private val context = instrumentation.context
private val device = UiDevice.getInstance(instrumentation)
- fun launchPackageAndWait(block: (Intent) -> Unit) {
+ fun launchPackageAndWait(
+ block: (Intent) -> Unit = {
+ it.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
+ }
+ ) {
val intent = context.packageManager.getLaunchIntentForPackage(packageName)!!
block(intent)
context.startActivity(intent)
diff --git a/benchmark/benchmark-simple-macro-benchmark/src/androidTest/java/androidx/benchmark/macro/sample/MacroBenchmarkTest.kt b/benchmark/benchmark-simple-macro-benchmark/src/androidTest/java/androidx/benchmark/macro/sample/MacroBenchmarkTest.kt
index a7359f6..b1d3aae 100644
--- a/benchmark/benchmark-simple-macro-benchmark/src/androidTest/java/androidx/benchmark/macro/sample/MacroBenchmarkTest.kt
+++ b/benchmark/benchmark-simple-macro-benchmark/src/androidTest/java/androidx/benchmark/macro/sample/MacroBenchmarkTest.kt
@@ -16,7 +16,6 @@
package androidx.benchmark.macro.sample
-import android.content.Intent
import androidx.benchmark.macro.CompilationMode
import androidx.benchmark.macro.CpuUsageMetric
import androidx.benchmark.macro.MacrobenchmarkConfig
@@ -47,9 +46,6 @@
)
) {
pressHome()
- launchPackageAndWait { launchIntent ->
- // Clear out any previous instances
- launchIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
- }
+ launchPackageAndWait()
}
}
diff --git a/biometric/biometric/api/current.txt b/biometric/biometric/api/current.txt
index 2d2401f..5870bdd 100644
--- a/biometric/biometric/api/current.txt
+++ b/biometric/biometric/api/current.txt
@@ -94,3 +94,107 @@
}
+package androidx.biometric.auth {
+
+ public interface AuthPrompt {
+ method public void cancelAuthentication();
+ }
+
+ public abstract class AuthPromptCallback {
+ ctor public AuthPromptCallback();
+ method public void onAuthenticationError(androidx.fragment.app.FragmentActivity?, int, CharSequence);
+ method public void onAuthenticationFailed(androidx.fragment.app.FragmentActivity?);
+ method public void onAuthenticationSucceeded(androidx.fragment.app.FragmentActivity?, androidx.biometric.BiometricPrompt.AuthenticationResult);
+ }
+
+ public class AuthPromptHost {
+ ctor public AuthPromptHost(androidx.fragment.app.Fragment);
+ ctor public AuthPromptHost(androidx.fragment.app.FragmentActivity);
+ method public androidx.fragment.app.FragmentActivity? getActivity();
+ method public androidx.fragment.app.Fragment? getFragment();
+ }
+
+ public class Class2BiometricAuthPrompt {
+ method public CharSequence? getDescription();
+ method public CharSequence? getSubtitle();
+ method public boolean isConfirmationRequired();
+ method public androidx.biometric.auth.AuthPrompt startAuthentication();
+ }
+
+ public static final class Class2BiometricAuthPrompt.Builder {
+ ctor public Class2BiometricAuthPrompt.Builder(androidx.biometric.auth.AuthPromptHost, CharSequence, CharSequence, java.util.concurrent.Executor, androidx.biometric.auth.AuthPromptCallback);
+ ctor public Class2BiometricAuthPrompt.Builder(androidx.biometric.auth.AuthPromptHost, CharSequence, CharSequence, androidx.biometric.auth.AuthPromptCallback);
+ method public androidx.biometric.auth.Class2BiometricAuthPrompt build();
+ method public androidx.biometric.auth.Class2BiometricAuthPrompt.Builder setConfirmationRequired(boolean);
+ method public androidx.biometric.auth.Class2BiometricAuthPrompt.Builder setDescription(CharSequence);
+ method public androidx.biometric.auth.Class2BiometricAuthPrompt.Builder setSubtitle(CharSequence);
+ }
+
+ public class Class2BiometricOrCredentialAuthPrompt {
+ method public CharSequence? getDescription();
+ method public CharSequence? getSubtitle();
+ method public boolean isConfirmationRequired();
+ method public androidx.biometric.auth.AuthPrompt startAuthentication();
+ }
+
+ public static final class Class2BiometricOrCredentialAuthPrompt.Builder {
+ ctor public Class2BiometricOrCredentialAuthPrompt.Builder(androidx.biometric.auth.AuthPromptHost, CharSequence, java.util.concurrent.Executor, androidx.biometric.auth.AuthPromptCallback);
+ ctor public Class2BiometricOrCredentialAuthPrompt.Builder(androidx.biometric.auth.AuthPromptHost, CharSequence, androidx.biometric.auth.AuthPromptCallback);
+ method public androidx.biometric.auth.Class2BiometricOrCredentialAuthPrompt build();
+ method public androidx.biometric.auth.Class2BiometricOrCredentialAuthPrompt.Builder setConfirmationRequired(boolean);
+ method public androidx.biometric.auth.Class2BiometricOrCredentialAuthPrompt.Builder setDescription(CharSequence);
+ method public androidx.biometric.auth.Class2BiometricOrCredentialAuthPrompt.Builder setSubtitle(CharSequence);
+ }
+
+ public class Class3BiometricAuthPrompt {
+ method public androidx.biometric.BiometricPrompt.CryptoObject? getCrypto();
+ method public CharSequence? getDescription();
+ method public CharSequence? getSubtitle();
+ method public boolean isConfirmationRequired();
+ method public androidx.biometric.auth.AuthPrompt startAuthentication();
+ }
+
+ public static final class Class3BiometricAuthPrompt.Builder {
+ ctor public Class3BiometricAuthPrompt.Builder(androidx.biometric.auth.AuthPromptHost, CharSequence, CharSequence, java.util.concurrent.Executor, androidx.biometric.auth.AuthPromptCallback);
+ ctor public Class3BiometricAuthPrompt.Builder(androidx.biometric.auth.AuthPromptHost, CharSequence, CharSequence, androidx.biometric.auth.AuthPromptCallback);
+ method public androidx.biometric.auth.Class3BiometricAuthPrompt build();
+ method public androidx.biometric.auth.Class3BiometricAuthPrompt.Builder setConfirmationRequired(boolean);
+ method public androidx.biometric.auth.Class3BiometricAuthPrompt.Builder setCrypto(androidx.biometric.BiometricPrompt.CryptoObject);
+ method public androidx.biometric.auth.Class3BiometricAuthPrompt.Builder setDescription(CharSequence);
+ method public androidx.biometric.auth.Class3BiometricAuthPrompt.Builder setSubtitle(CharSequence);
+ }
+
+ public class Class3BiometricOrCredentialAuthPrompt {
+ method public androidx.biometric.BiometricPrompt.CryptoObject? getCrypto();
+ method public CharSequence? getDescription();
+ method public CharSequence? getSubtitle();
+ method public boolean isConfirmationRequired();
+ method public androidx.biometric.auth.AuthPrompt startAuthentication();
+ }
+
+ public static final class Class3BiometricOrCredentialAuthPrompt.Builder {
+ ctor public Class3BiometricOrCredentialAuthPrompt.Builder(androidx.biometric.auth.AuthPromptHost, CharSequence, java.util.concurrent.Executor, androidx.biometric.auth.AuthPromptCallback);
+ ctor public Class3BiometricOrCredentialAuthPrompt.Builder(androidx.biometric.auth.AuthPromptHost, CharSequence, androidx.biometric.auth.AuthPromptCallback);
+ method public androidx.biometric.auth.Class3BiometricOrCredentialAuthPrompt build();
+ method public androidx.biometric.auth.Class3BiometricOrCredentialAuthPrompt.Builder setConfirmationRequired(boolean);
+ method public androidx.biometric.auth.Class3BiometricOrCredentialAuthPrompt.Builder setCrypto(androidx.biometric.BiometricPrompt.CryptoObject);
+ method public androidx.biometric.auth.Class3BiometricOrCredentialAuthPrompt.Builder setDescription(CharSequence);
+ method public androidx.biometric.auth.Class3BiometricOrCredentialAuthPrompt.Builder setSubtitle(CharSequence);
+ }
+
+ public class CredentialAuthPrompt {
+ method public androidx.biometric.BiometricPrompt.CryptoObject? getCrypto();
+ method public CharSequence? getDescription();
+ method public androidx.biometric.auth.AuthPrompt startAuthentication();
+ }
+
+ public static final class CredentialAuthPrompt.Builder {
+ ctor public CredentialAuthPrompt.Builder(androidx.biometric.auth.AuthPromptHost, CharSequence, java.util.concurrent.Executor, androidx.biometric.auth.AuthPromptCallback);
+ ctor public CredentialAuthPrompt.Builder(androidx.biometric.auth.AuthPromptHost, CharSequence, androidx.biometric.auth.AuthPromptCallback);
+ method public androidx.biometric.auth.CredentialAuthPrompt build();
+ method public androidx.biometric.auth.CredentialAuthPrompt.Builder setCrypto(androidx.biometric.BiometricPrompt.CryptoObject);
+ method public androidx.biometric.auth.CredentialAuthPrompt.Builder setDescription(CharSequence);
+ }
+
+}
+
diff --git a/biometric/biometric/api/public_plus_experimental_current.txt b/biometric/biometric/api/public_plus_experimental_current.txt
index 2d2401f..5870bdd 100644
--- a/biometric/biometric/api/public_plus_experimental_current.txt
+++ b/biometric/biometric/api/public_plus_experimental_current.txt
@@ -94,3 +94,107 @@
}
+package androidx.biometric.auth {
+
+ public interface AuthPrompt {
+ method public void cancelAuthentication();
+ }
+
+ public abstract class AuthPromptCallback {
+ ctor public AuthPromptCallback();
+ method public void onAuthenticationError(androidx.fragment.app.FragmentActivity?, int, CharSequence);
+ method public void onAuthenticationFailed(androidx.fragment.app.FragmentActivity?);
+ method public void onAuthenticationSucceeded(androidx.fragment.app.FragmentActivity?, androidx.biometric.BiometricPrompt.AuthenticationResult);
+ }
+
+ public class AuthPromptHost {
+ ctor public AuthPromptHost(androidx.fragment.app.Fragment);
+ ctor public AuthPromptHost(androidx.fragment.app.FragmentActivity);
+ method public androidx.fragment.app.FragmentActivity? getActivity();
+ method public androidx.fragment.app.Fragment? getFragment();
+ }
+
+ public class Class2BiometricAuthPrompt {
+ method public CharSequence? getDescription();
+ method public CharSequence? getSubtitle();
+ method public boolean isConfirmationRequired();
+ method public androidx.biometric.auth.AuthPrompt startAuthentication();
+ }
+
+ public static final class Class2BiometricAuthPrompt.Builder {
+ ctor public Class2BiometricAuthPrompt.Builder(androidx.biometric.auth.AuthPromptHost, CharSequence, CharSequence, java.util.concurrent.Executor, androidx.biometric.auth.AuthPromptCallback);
+ ctor public Class2BiometricAuthPrompt.Builder(androidx.biometric.auth.AuthPromptHost, CharSequence, CharSequence, androidx.biometric.auth.AuthPromptCallback);
+ method public androidx.biometric.auth.Class2BiometricAuthPrompt build();
+ method public androidx.biometric.auth.Class2BiometricAuthPrompt.Builder setConfirmationRequired(boolean);
+ method public androidx.biometric.auth.Class2BiometricAuthPrompt.Builder setDescription(CharSequence);
+ method public androidx.biometric.auth.Class2BiometricAuthPrompt.Builder setSubtitle(CharSequence);
+ }
+
+ public class Class2BiometricOrCredentialAuthPrompt {
+ method public CharSequence? getDescription();
+ method public CharSequence? getSubtitle();
+ method public boolean isConfirmationRequired();
+ method public androidx.biometric.auth.AuthPrompt startAuthentication();
+ }
+
+ public static final class Class2BiometricOrCredentialAuthPrompt.Builder {
+ ctor public Class2BiometricOrCredentialAuthPrompt.Builder(androidx.biometric.auth.AuthPromptHost, CharSequence, java.util.concurrent.Executor, androidx.biometric.auth.AuthPromptCallback);
+ ctor public Class2BiometricOrCredentialAuthPrompt.Builder(androidx.biometric.auth.AuthPromptHost, CharSequence, androidx.biometric.auth.AuthPromptCallback);
+ method public androidx.biometric.auth.Class2BiometricOrCredentialAuthPrompt build();
+ method public androidx.biometric.auth.Class2BiometricOrCredentialAuthPrompt.Builder setConfirmationRequired(boolean);
+ method public androidx.biometric.auth.Class2BiometricOrCredentialAuthPrompt.Builder setDescription(CharSequence);
+ method public androidx.biometric.auth.Class2BiometricOrCredentialAuthPrompt.Builder setSubtitle(CharSequence);
+ }
+
+ public class Class3BiometricAuthPrompt {
+ method public androidx.biometric.BiometricPrompt.CryptoObject? getCrypto();
+ method public CharSequence? getDescription();
+ method public CharSequence? getSubtitle();
+ method public boolean isConfirmationRequired();
+ method public androidx.biometric.auth.AuthPrompt startAuthentication();
+ }
+
+ public static final class Class3BiometricAuthPrompt.Builder {
+ ctor public Class3BiometricAuthPrompt.Builder(androidx.biometric.auth.AuthPromptHost, CharSequence, CharSequence, java.util.concurrent.Executor, androidx.biometric.auth.AuthPromptCallback);
+ ctor public Class3BiometricAuthPrompt.Builder(androidx.biometric.auth.AuthPromptHost, CharSequence, CharSequence, androidx.biometric.auth.AuthPromptCallback);
+ method public androidx.biometric.auth.Class3BiometricAuthPrompt build();
+ method public androidx.biometric.auth.Class3BiometricAuthPrompt.Builder setConfirmationRequired(boolean);
+ method public androidx.biometric.auth.Class3BiometricAuthPrompt.Builder setCrypto(androidx.biometric.BiometricPrompt.CryptoObject);
+ method public androidx.biometric.auth.Class3BiometricAuthPrompt.Builder setDescription(CharSequence);
+ method public androidx.biometric.auth.Class3BiometricAuthPrompt.Builder setSubtitle(CharSequence);
+ }
+
+ public class Class3BiometricOrCredentialAuthPrompt {
+ method public androidx.biometric.BiometricPrompt.CryptoObject? getCrypto();
+ method public CharSequence? getDescription();
+ method public CharSequence? getSubtitle();
+ method public boolean isConfirmationRequired();
+ method public androidx.biometric.auth.AuthPrompt startAuthentication();
+ }
+
+ public static final class Class3BiometricOrCredentialAuthPrompt.Builder {
+ ctor public Class3BiometricOrCredentialAuthPrompt.Builder(androidx.biometric.auth.AuthPromptHost, CharSequence, java.util.concurrent.Executor, androidx.biometric.auth.AuthPromptCallback);
+ ctor public Class3BiometricOrCredentialAuthPrompt.Builder(androidx.biometric.auth.AuthPromptHost, CharSequence, androidx.biometric.auth.AuthPromptCallback);
+ method public androidx.biometric.auth.Class3BiometricOrCredentialAuthPrompt build();
+ method public androidx.biometric.auth.Class3BiometricOrCredentialAuthPrompt.Builder setConfirmationRequired(boolean);
+ method public androidx.biometric.auth.Class3BiometricOrCredentialAuthPrompt.Builder setCrypto(androidx.biometric.BiometricPrompt.CryptoObject);
+ method public androidx.biometric.auth.Class3BiometricOrCredentialAuthPrompt.Builder setDescription(CharSequence);
+ method public androidx.biometric.auth.Class3BiometricOrCredentialAuthPrompt.Builder setSubtitle(CharSequence);
+ }
+
+ public class CredentialAuthPrompt {
+ method public androidx.biometric.BiometricPrompt.CryptoObject? getCrypto();
+ method public CharSequence? getDescription();
+ method public androidx.biometric.auth.AuthPrompt startAuthentication();
+ }
+
+ public static final class CredentialAuthPrompt.Builder {
+ ctor public CredentialAuthPrompt.Builder(androidx.biometric.auth.AuthPromptHost, CharSequence, java.util.concurrent.Executor, androidx.biometric.auth.AuthPromptCallback);
+ ctor public CredentialAuthPrompt.Builder(androidx.biometric.auth.AuthPromptHost, CharSequence, androidx.biometric.auth.AuthPromptCallback);
+ method public androidx.biometric.auth.CredentialAuthPrompt build();
+ method public androidx.biometric.auth.CredentialAuthPrompt.Builder setCrypto(androidx.biometric.BiometricPrompt.CryptoObject);
+ method public androidx.biometric.auth.CredentialAuthPrompt.Builder setDescription(CharSequence);
+ }
+
+}
+
diff --git a/biometric/biometric/api/restricted_current.txt b/biometric/biometric/api/restricted_current.txt
index 2d2401f..5870bdd 100644
--- a/biometric/biometric/api/restricted_current.txt
+++ b/biometric/biometric/api/restricted_current.txt
@@ -94,3 +94,107 @@
}
+package androidx.biometric.auth {
+
+ public interface AuthPrompt {
+ method public void cancelAuthentication();
+ }
+
+ public abstract class AuthPromptCallback {
+ ctor public AuthPromptCallback();
+ method public void onAuthenticationError(androidx.fragment.app.FragmentActivity?, int, CharSequence);
+ method public void onAuthenticationFailed(androidx.fragment.app.FragmentActivity?);
+ method public void onAuthenticationSucceeded(androidx.fragment.app.FragmentActivity?, androidx.biometric.BiometricPrompt.AuthenticationResult);
+ }
+
+ public class AuthPromptHost {
+ ctor public AuthPromptHost(androidx.fragment.app.Fragment);
+ ctor public AuthPromptHost(androidx.fragment.app.FragmentActivity);
+ method public androidx.fragment.app.FragmentActivity? getActivity();
+ method public androidx.fragment.app.Fragment? getFragment();
+ }
+
+ public class Class2BiometricAuthPrompt {
+ method public CharSequence? getDescription();
+ method public CharSequence? getSubtitle();
+ method public boolean isConfirmationRequired();
+ method public androidx.biometric.auth.AuthPrompt startAuthentication();
+ }
+
+ public static final class Class2BiometricAuthPrompt.Builder {
+ ctor public Class2BiometricAuthPrompt.Builder(androidx.biometric.auth.AuthPromptHost, CharSequence, CharSequence, java.util.concurrent.Executor, androidx.biometric.auth.AuthPromptCallback);
+ ctor public Class2BiometricAuthPrompt.Builder(androidx.biometric.auth.AuthPromptHost, CharSequence, CharSequence, androidx.biometric.auth.AuthPromptCallback);
+ method public androidx.biometric.auth.Class2BiometricAuthPrompt build();
+ method public androidx.biometric.auth.Class2BiometricAuthPrompt.Builder setConfirmationRequired(boolean);
+ method public androidx.biometric.auth.Class2BiometricAuthPrompt.Builder setDescription(CharSequence);
+ method public androidx.biometric.auth.Class2BiometricAuthPrompt.Builder setSubtitle(CharSequence);
+ }
+
+ public class Class2BiometricOrCredentialAuthPrompt {
+ method public CharSequence? getDescription();
+ method public CharSequence? getSubtitle();
+ method public boolean isConfirmationRequired();
+ method public androidx.biometric.auth.AuthPrompt startAuthentication();
+ }
+
+ public static final class Class2BiometricOrCredentialAuthPrompt.Builder {
+ ctor public Class2BiometricOrCredentialAuthPrompt.Builder(androidx.biometric.auth.AuthPromptHost, CharSequence, java.util.concurrent.Executor, androidx.biometric.auth.AuthPromptCallback);
+ ctor public Class2BiometricOrCredentialAuthPrompt.Builder(androidx.biometric.auth.AuthPromptHost, CharSequence, androidx.biometric.auth.AuthPromptCallback);
+ method public androidx.biometric.auth.Class2BiometricOrCredentialAuthPrompt build();
+ method public androidx.biometric.auth.Class2BiometricOrCredentialAuthPrompt.Builder setConfirmationRequired(boolean);
+ method public androidx.biometric.auth.Class2BiometricOrCredentialAuthPrompt.Builder setDescription(CharSequence);
+ method public androidx.biometric.auth.Class2BiometricOrCredentialAuthPrompt.Builder setSubtitle(CharSequence);
+ }
+
+ public class Class3BiometricAuthPrompt {
+ method public androidx.biometric.BiometricPrompt.CryptoObject? getCrypto();
+ method public CharSequence? getDescription();
+ method public CharSequence? getSubtitle();
+ method public boolean isConfirmationRequired();
+ method public androidx.biometric.auth.AuthPrompt startAuthentication();
+ }
+
+ public static final class Class3BiometricAuthPrompt.Builder {
+ ctor public Class3BiometricAuthPrompt.Builder(androidx.biometric.auth.AuthPromptHost, CharSequence, CharSequence, java.util.concurrent.Executor, androidx.biometric.auth.AuthPromptCallback);
+ ctor public Class3BiometricAuthPrompt.Builder(androidx.biometric.auth.AuthPromptHost, CharSequence, CharSequence, androidx.biometric.auth.AuthPromptCallback);
+ method public androidx.biometric.auth.Class3BiometricAuthPrompt build();
+ method public androidx.biometric.auth.Class3BiometricAuthPrompt.Builder setConfirmationRequired(boolean);
+ method public androidx.biometric.auth.Class3BiometricAuthPrompt.Builder setCrypto(androidx.biometric.BiometricPrompt.CryptoObject);
+ method public androidx.biometric.auth.Class3BiometricAuthPrompt.Builder setDescription(CharSequence);
+ method public androidx.biometric.auth.Class3BiometricAuthPrompt.Builder setSubtitle(CharSequence);
+ }
+
+ public class Class3BiometricOrCredentialAuthPrompt {
+ method public androidx.biometric.BiometricPrompt.CryptoObject? getCrypto();
+ method public CharSequence? getDescription();
+ method public CharSequence? getSubtitle();
+ method public boolean isConfirmationRequired();
+ method public androidx.biometric.auth.AuthPrompt startAuthentication();
+ }
+
+ public static final class Class3BiometricOrCredentialAuthPrompt.Builder {
+ ctor public Class3BiometricOrCredentialAuthPrompt.Builder(androidx.biometric.auth.AuthPromptHost, CharSequence, java.util.concurrent.Executor, androidx.biometric.auth.AuthPromptCallback);
+ ctor public Class3BiometricOrCredentialAuthPrompt.Builder(androidx.biometric.auth.AuthPromptHost, CharSequence, androidx.biometric.auth.AuthPromptCallback);
+ method public androidx.biometric.auth.Class3BiometricOrCredentialAuthPrompt build();
+ method public androidx.biometric.auth.Class3BiometricOrCredentialAuthPrompt.Builder setConfirmationRequired(boolean);
+ method public androidx.biometric.auth.Class3BiometricOrCredentialAuthPrompt.Builder setCrypto(androidx.biometric.BiometricPrompt.CryptoObject);
+ method public androidx.biometric.auth.Class3BiometricOrCredentialAuthPrompt.Builder setDescription(CharSequence);
+ method public androidx.biometric.auth.Class3BiometricOrCredentialAuthPrompt.Builder setSubtitle(CharSequence);
+ }
+
+ public class CredentialAuthPrompt {
+ method public androidx.biometric.BiometricPrompt.CryptoObject? getCrypto();
+ method public CharSequence? getDescription();
+ method public androidx.biometric.auth.AuthPrompt startAuthentication();
+ }
+
+ public static final class CredentialAuthPrompt.Builder {
+ ctor public CredentialAuthPrompt.Builder(androidx.biometric.auth.AuthPromptHost, CharSequence, java.util.concurrent.Executor, androidx.biometric.auth.AuthPromptCallback);
+ ctor public CredentialAuthPrompt.Builder(androidx.biometric.auth.AuthPromptHost, CharSequence, androidx.biometric.auth.AuthPromptCallback);
+ method public androidx.biometric.auth.CredentialAuthPrompt build();
+ method public androidx.biometric.auth.CredentialAuthPrompt.Builder setCrypto(androidx.biometric.BiometricPrompt.CryptoObject);
+ method public androidx.biometric.auth.CredentialAuthPrompt.Builder setDescription(CharSequence);
+ }
+
+}
+
diff --git a/biometric/biometric/src/main/java/androidx/biometric/BiometricFragment.java b/biometric/biometric/src/main/java/androidx/biometric/BiometricFragment.java
index 5383e59..3b28c82 100644
--- a/biometric/biometric/src/main/java/androidx/biometric/BiometricFragment.java
+++ b/biometric/biometric/src/main/java/androidx/biometric/BiometricFragment.java
@@ -263,9 +263,8 @@
if (activity == null) {
return;
}
-
mViewModel = new ViewModelProvider(getActivity()).get(BiometricViewModel.class);
-
+ mViewModel.setClientActivity(activity);
mViewModel.getAuthenticationResult().observe(
this,
new Observer<BiometricPrompt.AuthenticationResult>() {
diff --git a/biometric/biometric/src/main/java/androidx/biometric/BiometricPrompt.java b/biometric/biometric/src/main/java/androidx/biometric/BiometricPrompt.java
index fea9966..8b7d3cb 100644
--- a/biometric/biometric/src/main/java/androidx/biometric/BiometricPrompt.java
+++ b/biometric/biometric/src/main/java/androidx/biometric/BiometricPrompt.java
@@ -25,6 +25,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
+import androidx.annotation.RestrictTo;
import androidx.biometric.BiometricManager.Authenticators;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
@@ -155,6 +156,7 @@
/**
* An error code that may be returned during authentication.
+ * @hide
*/
@IntDef({
ERROR_HW_UNAVAILABLE,
@@ -171,8 +173,9 @@
ERROR_NEGATIVE_BUTTON,
ERROR_NO_DEVICE_CREDENTIAL
})
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
@Retention(RetentionPolicy.SOURCE)
- @interface AuthenticationError {}
+ public @interface AuthenticationError {}
/**
* Authentication type reported by {@link AuthenticationResult} when the user authenticated via
diff --git a/biometric/biometric/src/main/java/androidx/biometric/BiometricViewModel.java b/biometric/biometric/src/main/java/androidx/biometric/BiometricViewModel.java
index 1d63e18..12ecf70 100644
--- a/biometric/biometric/src/main/java/androidx/biometric/BiometricViewModel.java
+++ b/biometric/biometric/src/main/java/androidx/biometric/BiometricViewModel.java
@@ -23,6 +23,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
+import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
@@ -151,6 +152,11 @@
@Nullable private BiometricPrompt.AuthenticationCallback mClientCallback;
/**
+ * Reference to latest {@link androidx.fragment.app.FragmentActivity} hosting BiometricPrompt
+ */
+ @NonNull private WeakReference<FragmentActivity> mClientActivity;
+
+ /**
* Info about the appearance and behavior of the prompt provided by the client application.
*/
@Nullable private BiometricPrompt.PromptInfo mPromptInfo;
@@ -286,6 +292,24 @@
mClientCallback = clientCallback;
}
+ /**
+ * Returns reference to latest activity hosting BiometricPrompt or null if activity has
+ * already been destroyed
+ * @return Reference to latest activity hosting BiometricPrompt
+ */
+ @Nullable
+ public FragmentActivity getClientActivity() {
+ return mClientActivity.get();
+ }
+
+ /**
+ * Updates reference to latest activity hosting BiometricPrompt
+ * @param clientActivity Reference to latest activity hosting BiometricPrompt
+ */
+ void setClientActivity(@NonNull FragmentActivity clientActivity) {
+ mClientActivity = new WeakReference<>(clientActivity);
+ }
+
void setPromptInfo(@Nullable BiometricPrompt.PromptInfo promptInfo) {
mPromptInfo = promptInfo;
}
diff --git a/biometric/biometric/src/main/java/androidx/biometric/auth/AuthPrompt.java b/biometric/biometric/src/main/java/androidx/biometric/auth/AuthPrompt.java
new file mode 100644
index 0000000..4b64ac4
--- /dev/null
+++ b/biometric/biometric/src/main/java/androidx/biometric/auth/AuthPrompt.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2020 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.biometric.auth;
+
+/**
+ * A wrapper class for {@link Class2BiometricAuthPrompt}, {@link Class3BiometricAuthPrompt}
+ * {@link Class2BiometricOrCredentialAuthPrompt}, {@link Class3BiometricOrCredentialAuthPrompt}
+ * and {@link CredentialAuthPrompt} that allows for cancellation and dismissal of the current
+ * authentication prompt.
+ */
+public interface AuthPrompt {
+ /**
+ * Cancels authentication of the current BiometricPrompt.
+ */
+ void cancelAuthentication();
+}
diff --git a/biometric/biometric/src/main/java/androidx/biometric/auth/AuthPromptCallback.java b/biometric/biometric/src/main/java/androidx/biometric/auth/AuthPromptCallback.java
new file mode 100644
index 0000000..68b4a80
--- /dev/null
+++ b/biometric/biometric/src/main/java/androidx/biometric/auth/AuthPromptCallback.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2020 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.biometric.auth;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.biometric.BiometricPrompt;
+import androidx.fragment.app.FragmentActivity;
+
+/**
+ * A collection of methods that may be invoked by {@link Class2BiometricAuthPrompt},
+ * {@link Class3BiometricAuthPrompt}, {@link Class2BiometricOrCredentialAuthPrompt},
+ * {@link Class3BiometricOrCredentialAuthPrompt}, or {@link CredentialAuthPrompt} during
+ * authentication, returning the {@link androidx.fragment.app.FragmentActivity} the
+ * prompt is attached to.
+ */
+public abstract class AuthPromptCallback {
+ /**
+ * Called when an unrecoverable error has been encountered and authentication has stopped.
+ *
+ * <p>After this method is called, no further events will be sent for the current
+ * authentication session.
+ *
+ * @param activity {@link androidx.fragment.app.FragmentActivity} the prompt is attached to
+ * @param errorCode An integer ID associated with the error.
+ * @param errString A human-readable string that describes the error.
+ */
+ public void onAuthenticationError(@Nullable FragmentActivity activity,
+ @BiometricPrompt.AuthenticationError int errorCode, @NonNull CharSequence errString) {}
+
+ /**
+ * Called when a biometric (e.g. fingerprint, face, etc.) is recognized, indicating that the
+ * user has successfully authenticated.
+ *
+ * <p>After this method is called, no further events will be sent for the current
+ * authentication session.
+ *
+ * @param activity {@link androidx.fragment.app.FragmentActivity} the prompt is attached to
+ * @param result An object containing authentication-related data.
+ */
+ public void onAuthenticationSucceeded(@Nullable FragmentActivity activity,
+ @NonNull BiometricPrompt.AuthenticationResult result) {}
+
+ /**
+ * Called when a biometric (e.g. fingerprint, face, etc.) is presented but not recognized as
+ * belonging to the user.
+ *
+ * @param activity {@link androidx.fragment.app.FragmentActivity} the prompt is attached to
+ */
+ public void onAuthenticationFailed(@Nullable FragmentActivity activity) {}
+}
diff --git a/biometric/biometric/src/main/java/androidx/biometric/auth/AuthPromptHost.java b/biometric/biometric/src/main/java/androidx/biometric/auth/AuthPromptHost.java
new file mode 100644
index 0000000..7d9eb49
--- /dev/null
+++ b/biometric/biometric/src/main/java/androidx/biometric/auth/AuthPromptHost.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2020 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.biometric.auth;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+
+/**
+ * A wrapper class for {@link FragmentActivity} and {@link Fragment} with the FragmentActivity or
+ * Fragment that hosts the AuthPrompt
+ */
+public class AuthPromptHost {
+ @Nullable private Fragment mFragment = null;
+ @Nullable private FragmentActivity mActivity = null;
+
+ public AuthPromptHost(@NonNull Fragment fragment) {
+ mFragment = fragment;
+ }
+
+ public AuthPromptHost(@NonNull FragmentActivity activity) {
+ mActivity = activity;
+ }
+
+ @Nullable
+ public Fragment getFragment() {
+ return mFragment;
+ }
+
+ @Nullable
+ public FragmentActivity getActivity() {
+ return mActivity;
+ }
+}
diff --git a/biometric/biometric/src/main/java/androidx/biometric/auth/Class2BiometricAuthPrompt.java b/biometric/biometric/src/main/java/androidx/biometric/auth/Class2BiometricAuthPrompt.java
new file mode 100644
index 0000000..75127bf
--- /dev/null
+++ b/biometric/biometric/src/main/java/androidx/biometric/auth/Class2BiometricAuthPrompt.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright 2020 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.biometric.auth;
+
+import android.os.Handler;
+import android.os.Looper;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.biometric.BiometricManager;
+import androidx.biometric.BiometricPrompt;
+import androidx.biometric.BiometricViewModel;
+import androidx.lifecycle.ViewModelProvider;
+
+import java.util.concurrent.Executor;
+
+/**
+ * This class is used to build and configure a {@link BiometricPrompt} for authentication that
+ * only permits Class 2 biometric modalities (fingerprint, iris, face, etc), and then start
+ * authentication.
+ *
+ * Class 2 (formerly known as Weak) refers to the strength of the biometric sensor, as specified
+ * in the Android 11 CDD. Class 2 authentication can be used for applications that don't require
+ * cryptographic operations.
+ */
+public class Class2BiometricAuthPrompt {
+
+ /**
+ * The default executor provided when not provided in the {@link Class2BiometricAuthPrompt}
+ * constructor.
+ */
+ private static class DefaultExecutor implements Executor {
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ DefaultExecutor() {}
+
+ @Override
+ public void execute(Runnable runnable) {
+ mHandler.post(runnable);
+ }
+ }
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ @NonNull BiometricPrompt mBiometricPrompt;
+ @NonNull private BiometricPrompt.PromptInfo mPromptInfo;
+ private boolean mIsConfirmationRequired;
+
+ @Nullable private CharSequence mSubtitle;
+ @Nullable private CharSequence mDescription;
+
+ /**
+ * Constructs a {@link Class2BiometricAuthPrompt}, which can be used to begin authentication.
+ * @param biometricPrompt Manages a system-provided biometric prompt for authentication
+ * @param promptInfo A set of configurable options for how the {@link BiometricPrompt}
+ * should appear and behave.
+ * @param subtitle The subtitle to be displayed on the prompt.
+ * @param description The description to be displayed on the prompt.
+ * @param confirmationRequired Whether explicit user confirmation is required after a
+ * passive biometric
+ */
+ Class2BiometricAuthPrompt(
+ @NonNull BiometricPrompt biometricPrompt,
+ @NonNull BiometricPrompt.PromptInfo promptInfo,
+ @NonNull CharSequence subtitle,
+ @NonNull CharSequence description,
+ boolean confirmationRequired) {
+ mBiometricPrompt = biometricPrompt;
+ mPromptInfo = promptInfo;
+ mSubtitle = subtitle;
+ mDescription = description;
+ mIsConfirmationRequired = confirmationRequired;
+ }
+
+ /**
+ * Begins authentication using the configured biometric prompt, and returns an
+ * {@link AuthPrompt} wrapper that can be used for cancellation and dismissal of the
+ * biometric prompt.
+ * @return {@link AuthPrompt} wrapper that can be used for cancellation and dismissal of the
+ * biometric prompt using {@link AuthPrompt#cancelAuthentication()}
+ */
+ @NonNull
+ public AuthPrompt startAuthentication() {
+ mBiometricPrompt.authenticate(mPromptInfo);
+ return new AuthPrompt() {
+ @Override
+ public void cancelAuthentication() {
+ mBiometricPrompt.cancelAuthentication();
+ }
+ };
+ }
+
+ /**
+ * Gets the subtitle for the prompt.
+ * @return subtitle for the prompt
+ */
+ @Nullable
+ public CharSequence getSubtitle() {
+ return mSubtitle;
+ }
+
+ /**
+ * Gets the description for the prompt. Defaults to null.
+ * @return description for the prompt
+ */
+ @Nullable
+ public CharSequence getDescription() {
+ return mDescription;
+ }
+
+ /**
+ * Indicates whether prompt requires explicit user confirmation after a passive biometric (e
+ * .g. iris or face) has been recognized but before
+ * {@link AuthPromptCallback#onAuthenticationSucceeded(androidx.fragment.app.FragmentActivity,
+ * BiometricPrompt.AuthenticationResult)} is called.
+ * @return whether prompt requires explicit user confirmation after a passive biometric.
+ */
+ public boolean isConfirmationRequired() {
+ return mIsConfirmationRequired;
+ }
+
+ /**
+ * Builds a {@link BiometricPrompt} object for Class 2 biometric only authentication with
+ * specified options.
+ */
+ public static final class Builder {
+
+ // Nullable options on the builder
+ @Nullable private CharSequence mSubtitle = null;
+ @Nullable private CharSequence mDescription = null;
+
+ // Non-null options on the builder.
+ @NonNull private final AuthPromptHost mAuthPromptHost;
+ @NonNull private final CharSequence mTitle;
+ @NonNull private final CharSequence mNegativeButtonText;
+ @NonNull private final Executor mClientExecutor;
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ @NonNull final AuthPromptCallback mClientCallback;
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ @NonNull final BiometricViewModel mViewModel;
+
+ private boolean mIsConfirmationRequired = true;
+
+ /**
+ * A builder used to set individual options for the {@link Class2BiometricAuthPrompt}
+ * class to construct a {@link BiometricPrompt} for class 2 biometric only authentication.
+ *
+ * @param authPromptHost Contains {@link androidx.fragment.app.Fragment} or
+ * {@link androidx.fragment.app.FragmentActivity} to host the authentication prompt
+ * @param title The title to be displayed on the prompt.
+ * @param negativeButtonText The label to be used for the negative button on the prompt.
+ * @param clientExecutor The executor that will run authentication callback methods.
+ * @param clientCallback The object that will receive and process authentication events.
+ */
+ public Builder(
+ @NonNull AuthPromptHost authPromptHost,
+ @NonNull CharSequence title,
+ @NonNull CharSequence negativeButtonText,
+ @NonNull Executor clientExecutor,
+ @NonNull AuthPromptCallback clientCallback) {
+ mAuthPromptHost = authPromptHost;
+ mTitle = title;
+ mNegativeButtonText = negativeButtonText;
+ mClientExecutor = clientExecutor;
+ mClientCallback = clientCallback;
+ mViewModel = new ViewModelProvider(mAuthPromptHost.getActivity())
+ .get(BiometricViewModel.class);
+ }
+
+ /**
+ * A builder used to set individual options for the {@link Class2BiometricAuthPrompt}
+ * class to construct a {@link BiometricPrompt} for class 2 biometric only authentication.
+ * Sets mClientExecutor to new DefaultExecutor() object.
+ *
+ * @param authPromptHost Contains {@link androidx.fragment.app.Fragment} or
+ * {@link androidx.fragment.app.FragmentActivity} to host the authentication prompt
+ * @param title The title to be displayed on the prompt.
+ * @param negativeButtonText The label to be used for the negative button on the prompt.
+ * @param clientCallback The object that will receive and process authentication events.
+ */
+ public Builder(
+ @NonNull AuthPromptHost authPromptHost,
+ @NonNull CharSequence title,
+ @NonNull CharSequence negativeButtonText,
+ @NonNull AuthPromptCallback clientCallback) {
+ mAuthPromptHost = authPromptHost;
+ mTitle = title;
+ mNegativeButtonText = negativeButtonText;
+ mClientExecutor = new DefaultExecutor();
+ mClientCallback = clientCallback;
+ mViewModel = new ViewModelProvider(mAuthPromptHost.getActivity())
+ .get(BiometricViewModel.class);
+ }
+
+ /**
+ * Optional: Sets the subtitle for the prompt. Defaults to null.
+ *
+ * @param subtitle The subtitle to be displayed on the prompt.
+ * @return This builder.
+ */
+ @NonNull
+ public Builder setSubtitle(@NonNull CharSequence subtitle) {
+ mSubtitle = subtitle;
+ return this;
+ }
+
+ /**
+ * Optional: Sets the description for the prompt. Defaults to null.
+ *
+ * @param description The description to be displayed on the prompt.
+ * @return This builder.
+ */
+ @NonNull
+ public Builder setDescription(@NonNull CharSequence description) {
+ mDescription = description;
+ return this;
+ }
+
+ /**
+ * Optional: Sets a system hint for whether to require explicit user confirmation after a
+ * passive biometric (e.g. iris or face) has been recognized but before {@link
+ * AuthPromptCallback#onAuthenticationSucceeded(androidx.fragment.app.FragmentActivity,
+ * BiometricPrompt.AuthenticationResult)} is called. Defaults to {@code true}.
+ *
+ * <p>Disabling this option is generally only appropriate for frequent, low-value
+ * transactions, such as re-authenticating for a previously authorized application.
+ *
+ * <p>Also note that, as it is merely a hint, this option may be ignored by the system.
+ * For example, the system may choose to instead always require confirmation if the user
+ * has disabled passive authentication for their device in Settings. Additionally, this
+ * option will be ignored on devices running OS versions prior to Android 10 (API 29).
+ *
+ * @param confirmationRequired Whether this option should be enabled.
+ * @return This builder.
+ */
+ @NonNull
+ public Builder setConfirmationRequired(boolean confirmationRequired) {
+ mIsConfirmationRequired = confirmationRequired;
+ return this;
+ }
+
+ /**
+ * Configures a {@link BiometricPrompt} object with the specified options, and returns a
+ * {@link Class2BiometricAuthPrompt} instance that can be used for starting authentication.
+ * @return {@link Class2BiometricAuthPrompt} instance for starting authentication.
+ */
+ @NonNull
+ public Class2BiometricAuthPrompt build() {
+ final BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo
+ .Builder()
+ .setTitle(mTitle)
+ .setSubtitle(mSubtitle)
+ .setDescription(mDescription)
+ .setNegativeButtonText(mNegativeButtonText)
+ .setConfirmationRequired(mIsConfirmationRequired)
+ .setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_WEAK)
+ .build();
+ final BiometricPrompt biometricPrompt;
+ BiometricPrompt.AuthenticationCallback wrappedCallback =
+ new BiometricPrompt.AuthenticationCallback() {
+ @Override
+ public void onAuthenticationError(int errorCode,
+ @NonNull CharSequence errString) {
+ mClientCallback.onAuthenticationError(
+ mViewModel.getClientActivity(),
+ errorCode,
+ errString
+ );
+ }
+
+ @Override
+ public void onAuthenticationSucceeded(
+ @NonNull BiometricPrompt.AuthenticationResult result) {
+ mClientCallback.onAuthenticationSucceeded(
+ mViewModel.getClientActivity(),
+ result
+ );
+ }
+
+ @Override
+ public void onAuthenticationFailed() {
+ mClientCallback.onAuthenticationFailed(
+ mViewModel.getClientActivity()
+ );
+ }
+ };
+ if (mAuthPromptHost.getActivity() != null) {
+ biometricPrompt = new BiometricPrompt(mAuthPromptHost.getActivity(),
+ mClientExecutor, wrappedCallback);
+ } else if (mAuthPromptHost.getFragment() != null) {
+ biometricPrompt = new BiometricPrompt(mAuthPromptHost.getFragment(),
+ mClientExecutor, wrappedCallback);
+ } else {
+ throw new IllegalArgumentException("Invalid AuthPromptHost provided. Must "
+ + "provide AuthPromptHost containing Fragment or FragmentActivity for"
+ + " hosting the BiometricPrompt.");
+ }
+
+ return new Class2BiometricAuthPrompt(biometricPrompt, promptInfo, mSubtitle,
+ mDescription, mIsConfirmationRequired);
+ }
+ }
+}
diff --git a/biometric/biometric/src/main/java/androidx/biometric/auth/Class2BiometricOrCredentialAuthPrompt.java b/biometric/biometric/src/main/java/androidx/biometric/auth/Class2BiometricOrCredentialAuthPrompt.java
new file mode 100644
index 0000000..a099eca
--- /dev/null
+++ b/biometric/biometric/src/main/java/androidx/biometric/auth/Class2BiometricOrCredentialAuthPrompt.java
@@ -0,0 +1,316 @@
+/*
+ * Copyright 2020 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.biometric.auth;
+
+import android.os.Handler;
+import android.os.Looper;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.biometric.BiometricManager;
+import androidx.biometric.BiometricPrompt;
+import androidx.biometric.BiometricViewModel;
+import androidx.lifecycle.ViewModelProvider;
+
+import java.util.concurrent.Executor;
+
+/**
+ * This class is used to build and configure a {@link BiometricPrompt} for authentication that
+ * permits Class 2 biometric modalities (fingerprint, iris, face, etc), or device credential
+ * modalities (device PIN, pattern, or password), and then start authentication.
+ *
+ * Class 2 (formerly known as Weak) refers to the strength of the biometric sensor, as specified
+ * in the Android 11 CDD. Class 2 authentication can be used for applications that don't require
+ * cryptographic operations.
+ */
+public class Class2BiometricOrCredentialAuthPrompt {
+
+ /**
+ * The default executor provided when not provided in the
+ * {@link Class2BiometricOrCredentialAuthPrompt} constructor.
+ */
+ private static class DefaultExecutor implements Executor {
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ DefaultExecutor() {}
+
+ @Override
+ public void execute(Runnable runnable) {
+ mHandler.post(runnable);
+ }
+ }
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ @NonNull BiometricPrompt mBiometricPrompt;
+ @NonNull private BiometricPrompt.PromptInfo mPromptInfo;
+ private boolean mIsConfirmationRequired;
+
+ @Nullable private CharSequence mSubtitle;
+ @Nullable private CharSequence mDescription;
+
+ /**
+ * Constructs a {@link Class2BiometricOrCredentialAuthPrompt}, which can be used to begin
+ * authentication.
+ * @param biometricPrompt Manages a system-provided biometric prompt for authentication
+ * @param promptInfo A set of configurable options for how the {@link BiometricPrompt}
+ * should appear and behave.
+ * @param subtitle The subtitle to be displayed on the prompt.
+ * @param description The description to be displayed on the prompt.
+ * @param confirmationRequired Whether explicit user confirmation is required after a
+ * passive biometric
+ */
+ Class2BiometricOrCredentialAuthPrompt(
+ @NonNull BiometricPrompt biometricPrompt,
+ @NonNull BiometricPrompt.PromptInfo promptInfo,
+ @NonNull CharSequence subtitle,
+ @NonNull CharSequence description,
+ boolean confirmationRequired) {
+ mBiometricPrompt = biometricPrompt;
+ mPromptInfo = promptInfo;
+ mSubtitle = subtitle;
+ mDescription = description;
+ mIsConfirmationRequired = confirmationRequired;
+ }
+
+ /**
+ * Begins authentication using the configured biometric prompt, and returns an
+ * {@link AuthPrompt} wrapper that can be used for cancellation and dismissal of the biometric
+ * prompt.
+ * @return {@link AuthPrompt} wrapper that can be used for cancellation and dismissal of the
+ * biometric prompt using {@link AuthPrompt#cancelAuthentication()}
+ */
+ @NonNull
+ public AuthPrompt startAuthentication() {
+ mBiometricPrompt.authenticate(mPromptInfo);
+ return new AuthPrompt() {
+ @Override
+ public void cancelAuthentication() {
+ mBiometricPrompt.cancelAuthentication();
+ }
+ };
+ }
+
+ /**
+ * Gets the subtitle for the prompt.
+ * @return subtitle for the prompt
+ */
+ @Nullable
+ public CharSequence getSubtitle() {
+ return mSubtitle;
+ }
+
+ /**
+ * Gets the description for the prompt. Defaults to null.
+ * @return description for the prompt
+ */
+ @Nullable
+ public CharSequence getDescription() {
+ return mDescription;
+ }
+
+ /**
+ * Indicates whether prompt requires explicit user confirmation after a passive biometric (e
+ * .g. iris or face) has been recognized but before
+ * {@link AuthPromptCallback#onAuthenticationSucceeded(androidx.fragment.app.FragmentActivity,
+ * BiometricPrompt.AuthenticationResult)} is called.
+ * @return whether prompt requires explicit user confirmation after a passive biometric.
+ */
+ public boolean isConfirmationRequired() {
+ return mIsConfirmationRequired;
+ }
+
+ /**
+ * Builds a {@link BiometricPrompt} object for class 2 biometric or device credential
+ * authentication with specified options.
+ */
+ public static final class Builder {
+ // Mutable options to be set on the builder.
+ @Nullable private CharSequence mSubtitle = null;
+ @Nullable private CharSequence mDescription = null;
+
+ @NonNull private final AuthPromptHost mAuthPromptHost;
+ @NonNull private final CharSequence mTitle;
+ @NonNull private final Executor mClientExecutor;
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ @NonNull final AuthPromptCallback mClientCallback;
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ @NonNull final BiometricViewModel mViewModel;
+
+ private boolean mIsConfirmationRequired = true;
+
+ /**
+ * A builder used to set individual options for the
+ * {@link Class2BiometricOrCredentialAuthPrompt} class to construct a
+ * {@link BiometricPrompt} for class 2 biometric or device credential authentication.
+ *
+ * @param authPromptHost {@link androidx.fragment.app.Fragment} or
+ * {@link androidx.fragment.app.FragmentActivity} to host the authentication prompt
+ * @param title The title to be displayed on the prompt.
+ * @param clientExecutor The executor that will run authentication callback methods.
+ * @param clientCallback The object that will receive and process authentication events.
+ */
+ public Builder(
+ @NonNull AuthPromptHost authPromptHost,
+ @NonNull CharSequence title,
+ @NonNull Executor clientExecutor,
+ @NonNull AuthPromptCallback clientCallback) {
+ mAuthPromptHost = authPromptHost;
+ mTitle = title;
+ mClientExecutor = clientExecutor;
+ mClientCallback = clientCallback;
+ mViewModel = new ViewModelProvider(mAuthPromptHost.getActivity())
+ .get(BiometricViewModel.class);
+ }
+
+ /**
+ * A builder used to set individual options for the
+ * {@link Class2BiometricOrCredentialAuthPrompt} class to construct a
+ * {@link BiometricPrompt} for class 2 biometric or device credential authentication.
+ * Sets mClientExecutor to new DefaultExecutor() object.
+ *
+ * @param authPromptHost {@link androidx.fragment.app.Fragment} or
+ * {@link androidx.fragment.app.FragmentActivity} to host the authentication prompt
+ * @param title The title to be displayed on the prompt.
+ * @param clientCallback The object that will receive and process authentication events.
+ */
+ public Builder(
+ @NonNull AuthPromptHost authPromptHost,
+ @NonNull CharSequence title,
+ @NonNull AuthPromptCallback clientCallback) {
+ mAuthPromptHost = authPromptHost;
+ mTitle = title;
+ mClientExecutor = new DefaultExecutor();
+ mClientCallback = clientCallback;
+ mViewModel = new ViewModelProvider(mAuthPromptHost.getActivity())
+ .get(BiometricViewModel.class);
+ }
+
+ /**
+ * Optional: Sets the subtitle for the prompt. Defaults to null.
+ *
+ * @param subtitle The subtitle to be displayed on the prompt.
+ * @return This builder.
+ */
+ @NonNull
+ public Builder setSubtitle(@NonNull CharSequence subtitle) {
+ mSubtitle = subtitle;
+ return this;
+ }
+
+ /**
+ * Optional: Sets the description for the prompt. Defaults to null.
+ *
+ * @param description The description to be displayed on the prompt.
+ * @return This builder.
+ */
+ @NonNull
+ public Builder setDescription(@NonNull CharSequence description) {
+ mDescription = description;
+ return this;
+ }
+
+ /**
+ * Optional: Sets a system hint for whether to require explicit user confirmation after a
+ * passive biometric (e.g. iris or face) has been recognized but before
+ * {@link AuthPromptCallback#onAuthenticationSucceeded(
+ * androidx.fragment.app.FragmentActivity, BiometricPrompt.AuthenticationResult)} is
+ * called. Defaults to {@code true}.
+ *
+ * <p>Disabling this option is generally only appropriate for frequent, low-value
+ * transactions, such as re-authenticating for a previously authorized application.
+ *
+ * <p>Also note that, as it is merely a hint, this option may be ignored by the system.
+ * For example, the system may choose to instead always require confirmation if the user
+ * has disabled passive authentication for their device in Settings. Additionally, this
+ * option will be ignored on devices running OS versions prior to Android 10 (API 29).
+ *
+ * @param confirmationRequired Whether this option should be enabled.
+ * @return This builder.
+ */
+ @NonNull
+ public Builder setConfirmationRequired(boolean confirmationRequired) {
+ mIsConfirmationRequired = confirmationRequired;
+ return this;
+ }
+
+ /**
+ * Configures a {@link BiometricPrompt} object with the specified options, and returns a
+ * {@link Class2BiometricOrCredentialAuthPrompt} instance that can be used to start
+ * authentication.
+ * @return {@link Class2BiometricOrCredentialAuthPrompt} instance for starting
+ * authentication.
+ */
+ @NonNull
+ public Class2BiometricOrCredentialAuthPrompt build() {
+ final BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo
+ .Builder()
+ .setTitle(mTitle)
+ .setSubtitle(mSubtitle)
+ .setDescription(mDescription)
+ .setConfirmationRequired(mIsConfirmationRequired)
+ .setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_WEAK
+ | BiometricManager.Authenticators.DEVICE_CREDENTIAL)
+ .build();
+
+ final BiometricPrompt biometricPrompt;
+ BiometricPrompt.AuthenticationCallback wrappedCallback =
+ new BiometricPrompt.AuthenticationCallback() {
+ @Override
+ public void onAuthenticationError(int errorCode,
+ @NonNull CharSequence errString) {
+ mClientCallback.onAuthenticationError(
+ mViewModel.getClientActivity(),
+ errorCode,
+ errString
+ );
+ }
+
+ @Override
+ public void onAuthenticationSucceeded(
+ @NonNull BiometricPrompt.AuthenticationResult result) {
+ mClientCallback.onAuthenticationSucceeded(
+ mViewModel.getClientActivity(),
+ result
+ );
+ }
+
+ @Override
+ public void onAuthenticationFailed() {
+ mClientCallback.onAuthenticationFailed(
+ mViewModel.getClientActivity()
+ );
+ }
+ };
+ if (mAuthPromptHost.getActivity() != null) {
+ biometricPrompt = new BiometricPrompt(mAuthPromptHost.getActivity(),
+ mClientExecutor, wrappedCallback);
+ } else if (mAuthPromptHost.getFragment() != null) {
+ biometricPrompt = new BiometricPrompt(mAuthPromptHost.getFragment(),
+ mClientExecutor, wrappedCallback);
+ } else {
+ throw new IllegalArgumentException("Invalid AuthPromptHost provided. Must "
+ + "provide AuthPromptHost containing Fragment or FragmentActivity for"
+ + " hosting the BiometricPrompt.");
+ }
+ return new Class2BiometricOrCredentialAuthPrompt(biometricPrompt, promptInfo,
+ mSubtitle, mDescription, mIsConfirmationRequired);
+ }
+ }
+}
diff --git a/biometric/biometric/src/main/java/androidx/biometric/auth/Class3BiometricAuthPrompt.java b/biometric/biometric/src/main/java/androidx/biometric/auth/Class3BiometricAuthPrompt.java
new file mode 100644
index 0000000..8d84caf
--- /dev/null
+++ b/biometric/biometric/src/main/java/androidx/biometric/auth/Class3BiometricAuthPrompt.java
@@ -0,0 +1,351 @@
+/*
+ * Copyright 2020 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.biometric.auth;
+
+import android.os.Handler;
+import android.os.Looper;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.biometric.BiometricManager;
+import androidx.biometric.BiometricPrompt;
+import androidx.biometric.BiometricViewModel;
+import androidx.lifecycle.ViewModelProvider;
+
+import java.util.concurrent.Executor;
+
+/**
+ * This class is used to build and configure a {@link BiometricPrompt} for authentication that
+ * only permits Class 3 biometric modalities (fingerprint, iris, face, etc), and then start
+ * authentication.
+ *
+ * Class 3 (formerly known as Strong) refers to the strength of the biometric sensor, as specified
+ * in the Android 11 CDD. Class 3 authentication can be used for applications that use
+ * cryptographic operations.
+ */
+public class Class3BiometricAuthPrompt {
+
+ /**
+ * The default executor provided when not provided in the {@link Class3BiometricAuthPrompt}
+ * constructor.
+ */
+ private static class DefaultExecutor implements Executor {
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ DefaultExecutor() {}
+
+ @Override
+ public void execute(Runnable runnable) {
+ mHandler.post(runnable);
+ }
+ }
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ @NonNull BiometricPrompt mBiometricPrompt;
+ @NonNull private BiometricPrompt.PromptInfo mPromptInfo;
+ private boolean mIsConfirmationRequired;
+
+ @Nullable private BiometricPrompt.CryptoObject mCrypto;
+ @Nullable private CharSequence mSubtitle;
+ @Nullable private CharSequence mDescription;
+
+ /**
+ * Constructs a {@link Class3BiometricAuthPrompt}, which can be used to begin authentication.
+ * @param biometricPrompt Manages a system-provided biometric prompt for authentication
+ * @param promptInfo A set of configurable options for how the {@link BiometricPrompt}
+ * should appear and behave.
+ * @param crypto A crypto object to be associated with this authentication.
+ * @param subtitle The subtitle to be displayed on the prompt.
+ * @param description The description to be displayed on the prompt.
+ * @param confirmationRequired Whether explicit user confirmation is required after a
+ * passive biometric
+ */
+ Class3BiometricAuthPrompt(
+ @NonNull BiometricPrompt biometricPrompt,
+ @NonNull BiometricPrompt.PromptInfo promptInfo,
+ @Nullable BiometricPrompt.CryptoObject crypto,
+ @NonNull CharSequence subtitle,
+ @NonNull CharSequence description,
+ boolean confirmationRequired) {
+ mBiometricPrompt = biometricPrompt;
+ mPromptInfo = promptInfo;
+ mCrypto = crypto;
+ mSubtitle = subtitle;
+ mDescription = description;
+ mIsConfirmationRequired = confirmationRequired;
+ }
+
+ /**
+ * Begins authentication using the configured biometric prompt, and returns an
+ * {@link AuthPrompt} wrapper that can be used for cancellation and dismissal of the biometric
+ * prompt.
+ * @return {@link AuthPrompt} wrapper that can be used for cancellation and dismissal of the
+ * biometric prompt using {@link AuthPrompt#cancelAuthentication()}
+ */
+ @NonNull
+ public AuthPrompt startAuthentication() {
+ if (mCrypto == null) {
+ mBiometricPrompt.authenticate(mPromptInfo);
+ } else {
+ mBiometricPrompt.authenticate(mPromptInfo, mCrypto);
+ }
+
+ return new AuthPrompt() {
+ @Override
+ public void cancelAuthentication() {
+ mBiometricPrompt.cancelAuthentication();
+ }
+ };
+ }
+
+ /**
+ * Gets the subtitle for the prompt.
+ * @return subtitle for the prompt
+ */
+ @Nullable
+ public CharSequence getSubtitle() {
+ return mSubtitle;
+ }
+
+ /**
+ * Gets the description for the prompt. Defaults to null.
+ * @return description for the prompt
+ */
+ @Nullable
+ public CharSequence getDescription() {
+ return mDescription;
+ }
+
+ /**
+ * Indicates whether prompt requires explicit user confirmation after a passive biometric (e.g.
+ * iris or face) has been recognized but before
+ * {@link AuthPromptCallback#onAuthenticationSucceeded(androidx.fragment.app.FragmentActivity,
+ * BiometricPrompt.AuthenticationResult)} is called.
+ * @return whether prompt requires explicit user confirmation after a passive biometric.
+ */
+ public boolean isConfirmationRequired() {
+ return mIsConfirmationRequired;
+ }
+
+ /**
+ * Gets the crypto object for the prompt authentication.
+ * @return Crypto object associated with this authentication.
+ */
+ @Nullable
+ public BiometricPrompt.CryptoObject getCrypto() {
+ return mCrypto;
+ }
+
+ /**
+ * Builds a {@link BiometricPrompt} object for class 3 biometric only authentication with
+ * specified options.
+ */
+ public static final class Builder {
+
+ // Nullable options on the builder
+ @Nullable private BiometricPrompt.CryptoObject mCrypto = null;
+ @Nullable private CharSequence mSubtitle = null;
+ @Nullable private CharSequence mDescription = null;
+
+ // Non-null options on the builder.
+ @NonNull private final AuthPromptHost mAuthPromptHost;
+ @NonNull private final CharSequence mTitle;
+ @NonNull private final CharSequence mNegativeButtonText;
+ @NonNull private final Executor mClientExecutor;
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ @NonNull final AuthPromptCallback mClientCallback;
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ @NonNull final BiometricViewModel mViewModel;
+
+ private boolean mIsConfirmationRequired = true;
+
+ /**
+ * A builder used to set individual options for the {@link Class3BiometricAuthPrompt}
+ * class to construct a {@link BiometricPrompt} for class 3 biometric only authentication.
+ *
+ * @param authPromptHost Contains {@link androidx.fragment.app.Fragment} or
+ * {@link androidx.fragment.app.FragmentActivity} to host the authentication prompt
+ * @param title The title to be displayed on the prompt.
+ * @param negativeButtonText The label to be used for the negative button on the prompt.
+ * @param clientExecutor The executor that will run authentication callback methods.
+ * @param clientCallback The object that will receive and process authentication events.
+ */
+ public Builder(
+ @NonNull AuthPromptHost authPromptHost,
+ @NonNull CharSequence title,
+ @NonNull CharSequence negativeButtonText,
+ @NonNull Executor clientExecutor,
+ @NonNull AuthPromptCallback clientCallback) {
+ mAuthPromptHost = authPromptHost;
+ mTitle = title;
+ mNegativeButtonText = negativeButtonText;
+ mClientExecutor = clientExecutor;
+ mClientCallback = clientCallback;
+ mViewModel = new ViewModelProvider(mAuthPromptHost.getActivity())
+ .get(BiometricViewModel.class);
+ }
+ /**
+ * A builder used to set individual options for the {@link Class3BiometricAuthPrompt}
+ * class to construct a {@link BiometricPrompt} for class 3 biometric only authentication.
+ * Sets mClientExecutor to new DefaultExecutor() object.
+ *
+ * @param authPromptHost Contains {@link androidx.fragment.app.Fragment} or
+ * {@link androidx.fragment.app.FragmentActivity} to host the authentication prompt
+ * @param title The title to be displayed on the prompt.
+ * @param negativeButtonText The label to be used for the negative button on the prompt.
+ * @param clientCallback The object that will receive and process authentication events.
+ */
+ public Builder(
+ @NonNull AuthPromptHost authPromptHost,
+ @NonNull CharSequence title,
+ @NonNull CharSequence negativeButtonText,
+ @NonNull AuthPromptCallback clientCallback) {
+ mAuthPromptHost = authPromptHost;
+ mTitle = title;
+ mNegativeButtonText = negativeButtonText;
+ mClientExecutor = new DefaultExecutor();
+ mClientCallback = clientCallback;
+ mViewModel = new ViewModelProvider(mAuthPromptHost.getActivity())
+ .get(BiometricViewModel.class);
+ }
+
+ /**
+ * Required: Sets the crypto object for the prompt authentication. Defaults to null.
+ *
+ * @param crypto A crypto object to be associated with this authentication.
+ */
+ @NonNull
+ public Builder setCrypto(@NonNull BiometricPrompt.CryptoObject crypto) {
+ mCrypto = crypto;
+ return this;
+ }
+
+ /**
+ * Optional: Sets the subtitle for the prompt. Defaults to null.
+ *
+ * @param subtitle The subtitle to be displayed on the prompt.
+ * @return This builder.
+ */
+ @NonNull
+ public Builder setSubtitle(@NonNull CharSequence subtitle) {
+ mSubtitle = subtitle;
+ return this;
+ }
+
+ /**
+ * Optional: Sets the description for the prompt. Defaults to null.
+ *
+ * @param description The description to be displayed on the prompt.
+ * @return This builder.
+ */
+ @NonNull
+ public Builder setDescription(@NonNull CharSequence description) {
+ mDescription = description;
+ return this;
+ }
+
+ /**
+ * Optional: Sets a system hint for whether to require explicit user confirmation after
+ * a passive biometric (e.g. iris or face) has been recognized but before
+ * {@link AuthPromptCallback#onAuthenticationSucceeded(
+ * androidx.fragment.app.FragmentActivity, BiometricPrompt.AuthenticationResult)} is
+ * called. Defaults to {@code true}.
+ *
+ * <p>Disabling this option is generally only appropriate for frequent, low-value
+ * transactions, such as re-authenticating for a previously authorized application.
+ *
+ * <p>Also note that, as it is merely a hint, this option may be ignored by the system.
+ * For example, the system may choose to instead always require confirmation if the user
+ * has disabled passive authentication for their device in Settings. Additionally, this
+ * option will be ignored on devices running OS versions prior to Android 10 (API 29).
+ *
+ * @param confirmationRequired Whether this option should be enabled.
+ * @return This builder.
+ */
+ @NonNull
+ public Builder setConfirmationRequired(boolean confirmationRequired) {
+ mIsConfirmationRequired = confirmationRequired;
+ return this;
+ }
+
+ /**
+ * Configures a {@link BiometricPrompt} object with the specified options, and returns
+ * a {@link Class3BiometricAuthPrompt} instance that can be used for starting
+ * authentication.
+ * @return {@link Class3BiometricAuthPrompt} instance for starting authentication.
+ */
+ @NonNull
+ public Class3BiometricAuthPrompt build() {
+ final BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo
+ .Builder()
+ .setTitle(mTitle)
+ .setSubtitle(mSubtitle)
+ .setDescription(mDescription)
+ .setNegativeButtonText(mNegativeButtonText)
+ .setConfirmationRequired(mIsConfirmationRequired)
+ .setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG)
+ .build();
+
+ final BiometricPrompt biometricPrompt;
+ BiometricPrompt.AuthenticationCallback wrappedCallback =
+ new BiometricPrompt.AuthenticationCallback() {
+ @Override
+ public void onAuthenticationError(int errorCode,
+ @NonNull CharSequence errString) {
+ mClientCallback.onAuthenticationError(
+ mViewModel.getClientActivity(),
+ errorCode,
+ errString
+ );
+ }
+
+ @Override
+ public void onAuthenticationSucceeded(
+ @NonNull BiometricPrompt.AuthenticationResult result) {
+ mClientCallback.onAuthenticationSucceeded(
+ mViewModel.getClientActivity(),
+ result
+ );
+ }
+
+ @Override
+ public void onAuthenticationFailed() {
+ mClientCallback.onAuthenticationFailed(
+ mViewModel.getClientActivity()
+ );
+ }
+ };
+ if (mAuthPromptHost.getActivity() != null) {
+ biometricPrompt = new BiometricPrompt(mAuthPromptHost.getActivity(),
+ mClientExecutor, wrappedCallback);
+ } else if (mAuthPromptHost.getFragment() != null) {
+ biometricPrompt = new BiometricPrompt(mAuthPromptHost.getFragment(),
+ mClientExecutor, wrappedCallback);
+ } else {
+ throw new IllegalArgumentException("Invalid AuthPromptHost provided. Must "
+ + "provide AuthPromptHost containing Fragment or FragmentActivity for"
+ + " hosting the BiometricPrompt.");
+ }
+
+ return new Class3BiometricAuthPrompt(biometricPrompt, promptInfo, mCrypto,
+ mSubtitle, mDescription, mIsConfirmationRequired);
+ }
+ }
+}
diff --git a/biometric/biometric/src/main/java/androidx/biometric/auth/Class3BiometricOrCredentialAuthPrompt.java b/biometric/biometric/src/main/java/androidx/biometric/auth/Class3BiometricOrCredentialAuthPrompt.java
new file mode 100644
index 0000000..abb8248
--- /dev/null
+++ b/biometric/biometric/src/main/java/androidx/biometric/auth/Class3BiometricOrCredentialAuthPrompt.java
@@ -0,0 +1,348 @@
+/*
+ * Copyright 2020 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.biometric.auth;
+
+import android.os.Handler;
+import android.os.Looper;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.biometric.BiometricManager;
+import androidx.biometric.BiometricPrompt;
+import androidx.biometric.BiometricViewModel;
+import androidx.lifecycle.ViewModelProvider;
+
+import java.util.concurrent.Executor;
+
+/**
+ * This class is used to build and configure a {@link BiometricPrompt} for authentication that
+ * permits Class 3 biometric modalities (fingerprint, iris, face, etc), or device credential
+ * modalities (device PIN, pattern, or password), and then start authentication.
+ *
+ * Class 3 (formerly known as Strong) refers to the strength of the biometric sensor, as specified
+ * in the Android 11 CDD. Class 3 authentication can be used for applications that use
+ * cryptographic operations.
+ */
+public class Class3BiometricOrCredentialAuthPrompt {
+
+ /**
+ * The default executor provided when not provided in the
+ * {@link Class3BiometricOrCredentialAuthPrompt} constructor.
+ */
+ private static class DefaultExecutor implements Executor {
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ DefaultExecutor() {}
+
+ @Override
+ public void execute(Runnable runnable) {
+ mHandler.post(runnable);
+ }
+ }
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ @NonNull BiometricPrompt mBiometricPrompt;
+ @NonNull private BiometricPrompt.PromptInfo mPromptInfo;
+ private boolean mIsConfirmationRequired;
+
+ @Nullable private BiometricPrompt.CryptoObject mCrypto;
+ @Nullable private CharSequence mSubtitle;
+ @Nullable private CharSequence mDescription;
+
+ /**
+ * Constructs a {@link Class3BiometricOrCredentialAuthPrompt}, which can be used to begin
+ * authentication.
+ * @param biometricPrompt Manages a system-provided biometric prompt for authentication
+ * @param promptInfo A set of configurable options for how the {@link BiometricPrompt}
+ * should appear and behave.
+ * @param crypto A crypto object to be associated with this authentication.
+ * @param subtitle The subtitle to be displayed on the prompt.
+ * @param description The description to be displayed on the prompt.
+ * @param confirmationRequired Whether explicit user confirmation is required after a
+ * passive biometric
+ */
+ Class3BiometricOrCredentialAuthPrompt(@NonNull BiometricPrompt biometricPrompt,
+ @NonNull BiometricPrompt.PromptInfo promptInfo,
+ @Nullable BiometricPrompt.CryptoObject crypto,
+ @NonNull CharSequence subtitle,
+ @NonNull CharSequence description,
+ boolean confirmationRequired) {
+ mBiometricPrompt = biometricPrompt;
+ mPromptInfo = promptInfo;
+ mCrypto = crypto;
+ mSubtitle = subtitle;
+ mDescription = description;
+ mIsConfirmationRequired = confirmationRequired;
+ }
+
+ /**
+ * Begins authentication using the configured biometric prompt, and returns an
+ * {@link AuthPrompt} wrapper that can be used for cancellation and dismissal of the biometric
+ * prompt.
+ * @return {@link AuthPrompt} wrapper that can be used for cancellation and dismissal of the
+ * biometric prompt using {@link AuthPrompt#cancelAuthentication()}
+ */
+ @NonNull
+ public AuthPrompt startAuthentication() {
+ if (mCrypto == null) {
+ mBiometricPrompt.authenticate(mPromptInfo);
+ } else {
+ mBiometricPrompt.authenticate(mPromptInfo, mCrypto);
+ }
+
+ return new AuthPrompt() {
+ @Override
+ public void cancelAuthentication() {
+ mBiometricPrompt.cancelAuthentication();
+ }
+ };
+ }
+
+ /**
+ * Gets the subtitle for the prompt.
+ * @return subtitle for the prompt
+ */
+ @Nullable
+ public CharSequence getSubtitle() {
+ return mSubtitle;
+ }
+
+ /**
+ * Gets the description for the prompt. Defaults to null.
+ * @return description for the prompt
+ */
+ @Nullable
+ public CharSequence getDescription() {
+ return mDescription;
+ }
+
+ /**
+ * Indicates whether prompt requires explicit user confirmation after a passive biometric (e.g.
+ * iris or face) has been recognized but before {@link
+ * AuthPromptCallback#onAuthenticationSucceeded(androidx.fragment.app.FragmentActivity,
+ * BiometricPrompt.AuthenticationResult)} is called.
+ * @return whether prompt requires explicit user confirmation after a passive biometric.
+ */
+ public boolean isConfirmationRequired() {
+ return mIsConfirmationRequired;
+ }
+
+ /**
+ * Gets the crypto object for the prompt authentication.
+ * @return Crypto object associated with this authentication.
+ */
+ @Nullable
+ public BiometricPrompt.CryptoObject getCrypto() {
+ return mCrypto;
+ }
+
+ /**
+ * Builder to configure a {@link BiometricPrompt} object for class 3 biometric or device
+ * credential authentication with specified options.
+ */
+ public static final class Builder {
+ // Nullable options on the builder
+ @Nullable private BiometricPrompt.CryptoObject mCrypto = null;
+ @Nullable private CharSequence mSubtitle = null;
+ @Nullable private CharSequence mDescription = null;
+
+ // Non-null options on the builder
+ @NonNull private final AuthPromptHost mAuthPromptHost;
+ @NonNull private final CharSequence mTitle;
+ @NonNull private final Executor mClientExecutor;
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ @NonNull final AuthPromptCallback mClientCallback;
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ @NonNull final BiometricViewModel mViewModel;
+
+ private boolean mIsConfirmationRequired = true;
+
+ /**
+ * A builder used to set individual options for the
+ * {@link Class3BiometricOrCredentialAuthPrompt} class to construct a
+ * {@link BiometricPrompt} for class 3 biometric or device credential authentication.
+ *
+ * @param authPromptHost {@link androidx.fragment.app.Fragment} or
+ * {@link androidx.fragment.app.FragmentActivity} to host the authentication prompt
+ * @param title The title to be displayed on the prompt.
+ * @param clientExecutor The executor that will run authentication callback methods.
+ * @param clientCallback The object that will receive and process authentication events.
+ */
+ public Builder(
+ @NonNull AuthPromptHost authPromptHost,
+ @NonNull CharSequence title,
+ @NonNull Executor clientExecutor,
+ @NonNull AuthPromptCallback clientCallback) {
+ mAuthPromptHost = authPromptHost;
+ mTitle = title;
+ mClientExecutor = clientExecutor;
+ mClientCallback = clientCallback;
+ mViewModel = new ViewModelProvider(mAuthPromptHost.getActivity())
+ .get(BiometricViewModel.class);
+ }
+
+ /**
+ * A builder used to set individual options for the
+ * {@link Class3BiometricOrCredentialAuthPrompt} class to construct a
+ * {@link BiometricPrompt} for class 3 biometric or device credential authentication.
+ * Sets mClientExecutor to new DefaultExecutor() object.
+ *
+ * @param authPromptHost {@link androidx.fragment.app.Fragment} or
+ * {@link androidx.fragment.app.FragmentActivity} to host the authentication prompt
+ * @param title The title to be displayed on the prompt.
+ * @param clientCallback The object that will receive and process authentication events.
+ */
+ public Builder(
+ @NonNull AuthPromptHost authPromptHost,
+ @NonNull CharSequence title,
+ @NonNull AuthPromptCallback clientCallback) {
+ mAuthPromptHost = authPromptHost;
+ mTitle = title;
+ mClientExecutor = new DefaultExecutor();
+ mClientCallback = clientCallback;
+ mViewModel = new ViewModelProvider(mAuthPromptHost.getActivity())
+ .get(BiometricViewModel.class);
+ }
+
+ /**
+ * Optional: Sets the crypto object for the prompt.
+ * @param crypto A crypto object to be associated with this authentication.
+ */
+ @NonNull
+ public Builder setCrypto(@NonNull BiometricPrompt.CryptoObject crypto) {
+ mCrypto = crypto;
+ return this;
+ }
+
+ /**
+ * Optional: Sets the subtitle for the prompt.
+ *
+ * @param subtitle The subtitle to be displayed on the prompt.
+ * @return This builder.
+ */
+ @NonNull
+ public Builder setSubtitle(
+ @NonNull CharSequence subtitle) {
+ mSubtitle = subtitle;
+ return this;
+ }
+
+ /**
+ * Optional: Sets the description for the prompt.
+ *
+ * @param description The description to be displayed on the prompt.
+ * @return This builder.
+ */
+ @NonNull
+ public Builder setDescription(
+ @NonNull CharSequence description) {
+ mDescription = description;
+ return this;
+ }
+
+ /**
+ * Optional: Sets a system hint for whether to require explicit user confirmation after
+ * a passive biometric (e.g. iris or face) has been recognized but before
+ * {@link AuthPromptCallback#onAuthenticationSucceeded(
+ * androidx.fragment.app.FragmentActivity, BiometricPrompt.AuthenticationResult)} is
+ * called. Defaults to {@code true}.
+ *
+ * <p>Disabling this option is generally only appropriate for frequent, low-value
+ * transactions, such as re-authenticating for a previously authorized application.
+ *
+ * <p>Also note that, as it is merely a hint, this option may be ignored by the system.
+ * For example, the system may choose to instead always require confirmation if the user
+ * has disabled passive authentication for their device in Settings. Additionally, this
+ * option will be ignored on devices running OS versions prior to Android 10 (API 29).
+ *
+ * @param confirmationRequired Whether this option should be enabled.
+ * @return This builder.
+ */
+ @NonNull
+ public Builder setConfirmationRequired(
+ boolean confirmationRequired) {
+ mIsConfirmationRequired = confirmationRequired;
+ return this;
+ }
+
+ /**
+ * Configures a {@link BiometricPrompt} object with the specified options, and returns a
+ * {@link Class3BiometricOrCredentialAuthPrompt} instance that can be used for starting
+ * authentication.
+ * @return {@link Class3BiometricOrCredentialAuthPrompt} instance for starting
+ * authentication.
+ */
+ @NonNull
+ public Class3BiometricOrCredentialAuthPrompt build() {
+ final BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo
+ .Builder()
+ .setTitle(mTitle)
+ .setSubtitle(mSubtitle)
+ .setDescription(mDescription)
+ .setConfirmationRequired(mIsConfirmationRequired)
+ .setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG
+ | BiometricManager.Authenticators.DEVICE_CREDENTIAL)
+ .build();
+
+ final BiometricPrompt biometricPrompt;
+ BiometricPrompt.AuthenticationCallback wrappedCallback =
+ new BiometricPrompt.AuthenticationCallback() {
+ @Override
+ public void onAuthenticationError(int errorCode,
+ @NonNull CharSequence errString) {
+ mClientCallback.onAuthenticationError(
+ mViewModel.getClientActivity(),
+ errorCode,
+ errString
+ );
+ }
+
+ @Override
+ public void onAuthenticationSucceeded(
+ @NonNull BiometricPrompt.AuthenticationResult result) {
+ mClientCallback.onAuthenticationSucceeded(
+ mViewModel.getClientActivity(),
+ result
+ );
+ }
+
+ @Override
+ public void onAuthenticationFailed() {
+ mClientCallback.onAuthenticationFailed(
+ mViewModel.getClientActivity()
+ );
+ }
+ };
+ if (mAuthPromptHost.getActivity() != null) {
+ biometricPrompt = new BiometricPrompt(mAuthPromptHost.getActivity(),
+ mClientExecutor, wrappedCallback);
+ } else if (mAuthPromptHost.getFragment() != null) {
+ biometricPrompt = new BiometricPrompt(mAuthPromptHost.getFragment(),
+ mClientExecutor, wrappedCallback);
+ } else {
+ throw new IllegalArgumentException("Invalid AuthPromptHost provided. Must "
+ + "provide AuthPromptHost containing Fragment or FragmentActivity for"
+ + " hosting the BiometricPrompt.");
+ }
+ return new Class3BiometricOrCredentialAuthPrompt(biometricPrompt, promptInfo, mCrypto,
+ mSubtitle, mDescription, mIsConfirmationRequired);
+ }
+ }
+}
diff --git a/biometric/biometric/src/main/java/androidx/biometric/auth/CredentialAuthPrompt.java b/biometric/biometric/src/main/java/androidx/biometric/auth/CredentialAuthPrompt.java
new file mode 100644
index 0000000..075d174
--- /dev/null
+++ b/biometric/biometric/src/main/java/androidx/biometric/auth/CredentialAuthPrompt.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright 2020 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.biometric.auth;
+
+import android.os.Handler;
+import android.os.Looper;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.biometric.BiometricManager;
+import androidx.biometric.BiometricPrompt;
+import androidx.biometric.BiometricViewModel;
+import androidx.lifecycle.ViewModelProvider;
+
+import java.util.concurrent.Executor;
+
+/**
+ * This class is used to build and configure a {@link BiometricPrompt} for authentication that
+ * only permits device credential modalities (device PIN, pattern, or password), and then start
+ * authentication.
+ */
+public class CredentialAuthPrompt {
+
+ /**
+ * The default executor provided when not provided in the {@link CredentialAuthPrompt}
+ * constructor.
+ */
+ private static class DefaultExecutor implements Executor {
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ DefaultExecutor() {}
+
+ @Override
+ public void execute(Runnable runnable) {
+ mHandler.post(runnable);
+ }
+ }
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ @NonNull BiometricPrompt mBiometricPrompt;
+ @NonNull private BiometricPrompt.PromptInfo mPromptInfo;
+
+ @Nullable private BiometricPrompt.CryptoObject mCrypto;
+ @Nullable private CharSequence mDescription;
+
+ /**
+ * Constructs a {@link CredentialAuthPrompt}, which can be used to begin authentication.
+ * @param biometricPrompt Manages a system-provided biometric prompt for authentication
+ * @param promptInfo A set of configurable options for how the {@link BiometricPrompt}
+ * should appear and behave.
+ * @param crypto A crypto object to be associated with this authentication.
+ * @param description The description to be displayed on the prompt.
+ */
+ CredentialAuthPrompt(@NonNull BiometricPrompt biometricPrompt,
+ @NonNull BiometricPrompt.PromptInfo promptInfo,
+ @Nullable BiometricPrompt.CryptoObject crypto,
+ @NonNull CharSequence description) {
+ mBiometricPrompt = biometricPrompt;
+ mPromptInfo = promptInfo;
+ mCrypto = crypto;
+ mDescription = description;
+ }
+
+ /**
+ * Begins authentication using the configured authentication prompt, and returns an
+ * {@link AuthPrompt} wrapper that can be used for cancellation and dismissal of the
+ * authentication prompt.
+ * @return {@link AuthPrompt} wrapper that can be used for cancellation and dismissal of the
+ * authentication prompt using {@link AuthPrompt#cancelAuthentication()}
+ */
+ @NonNull
+ public AuthPrompt startAuthentication() {
+ if (mCrypto == null) {
+ mBiometricPrompt.authenticate(mPromptInfo);
+ } else {
+ mBiometricPrompt.authenticate(mPromptInfo, mCrypto);
+ }
+ return new AuthPrompt() {
+ @Override
+ public void cancelAuthentication() {
+ mBiometricPrompt.cancelAuthentication();
+ }
+ };
+ }
+
+ /**
+ * Returns the crypto object for the prompt.
+ * @return mCrypto A crypto object to be associated with this authentication.
+ */
+ @Nullable
+ public BiometricPrompt.CryptoObject getCrypto() {
+ return mCrypto;
+ }
+
+ /**
+ * Returns the description for the prompt.
+ * @return mDescription The description to be displayed on the prompt.
+ */
+ @Nullable
+ public CharSequence getDescription() {
+ return mDescription;
+ }
+
+ /**
+ * Builder to configure a {@link BiometricPrompt} object for device credential only
+ * authentication with specified options.
+ */
+ public static final class Builder {
+
+ // Mutable options to be set on the builder.
+ @Nullable private BiometricPrompt.CryptoObject mCrypto = null;
+ @Nullable private CharSequence mDescription = null;
+
+ @NonNull private final AuthPromptHost mAuthPromptHost;
+ @NonNull private final CharSequence mTitle;
+ @NonNull private final Executor mClientExecutor;
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ @NonNull final AuthPromptCallback mClientCallback;
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ @NonNull final BiometricViewModel mViewModel;
+
+ /**
+ * A builder used to set individual options for the {@link CredentialAuthPrompt}
+ * class to construct a {@link BiometricPrompt} for device credential authentication.
+ *
+ * @param authPromptHost {@link androidx.fragment.app.Fragment} or
+ * {@link androidx.fragment.app.FragmentActivity} to host the authentication prompt
+ * @param title The title to be displayed on the prompt.
+ * @param clientExecutor The executor that will run authentication callback methods.
+ * @param clientCallback The object that will receive and process authentication events.
+ * @return Builder object for setting remaining optional variables of the
+ * {@link CredentialAuthPrompt} class.
+ */
+ public Builder(
+ @NonNull AuthPromptHost authPromptHost,
+ @NonNull CharSequence title,
+ @NonNull Executor clientExecutor,
+ @NonNull AuthPromptCallback clientCallback) {
+ mAuthPromptHost = authPromptHost;
+ mTitle = title;
+ mClientExecutor = clientExecutor;
+ mClientCallback = clientCallback;
+ mViewModel = new ViewModelProvider(mAuthPromptHost.getActivity())
+ .get(BiometricViewModel.class);
+ }
+
+ /**
+ * A builder used to set individual options for the {@link CredentialAuthPrompt}
+ * class to construct a {@link BiometricPrompt} for device credential authentication.
+ * Sets mClientExecutor to new DefaultExecutor() object.
+ *
+ * @param authPromptHost {@link androidx.fragment.app.Fragment} or
+ * {@link androidx.fragment.app.FragmentActivity} to host the authentication prompt
+ * @param title The title to be displayed on the prompt.
+ * @param clientCallback The object that will receive and process authentication events.
+ * @return Builder object for setting remaining optional variables of the
+ * {@link CredentialAuthPrompt} class.
+ */
+ public Builder(
+ @NonNull AuthPromptHost authPromptHost,
+ @NonNull CharSequence title,
+ @NonNull AuthPromptCallback clientCallback) {
+ mAuthPromptHost = authPromptHost;
+ mTitle = title;
+ mClientExecutor = new DefaultExecutor();
+ mClientCallback = clientCallback;
+ mViewModel = new ViewModelProvider(mAuthPromptHost.getActivity())
+ .get(BiometricViewModel.class);
+ }
+
+ /**
+ * Optional: Sets the crypto object for the prompt.
+ * @param crypto A crypto object to be associated with this authentication.
+ */
+ @NonNull
+ public CredentialAuthPrompt.Builder setCrypto(
+ @NonNull BiometricPrompt.CryptoObject crypto
+ ) {
+ mCrypto = crypto;
+ return this;
+ }
+
+ /**
+ * Optional: Sets the description for the prompt. Defaults to null.
+ *
+ * @param description The description to be displayed on the prompt.
+ * @return This builder.
+ */
+ @NonNull
+ public CredentialAuthPrompt.Builder setDescription(@NonNull CharSequence description) {
+ mDescription = description;
+ return this;
+ }
+
+ /**
+ * Configures a {@link BiometricPrompt} object with the specified options, and returns
+ * a {@link CredentialAuthPrompt} instance that can be used for starting authentication.
+ * @return {@link CredentialAuthPrompt} instance for starting authentication.
+ */
+ @NonNull
+ public CredentialAuthPrompt build() {
+ final BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo
+ .Builder()
+ .setTitle(mTitle)
+ .setDescription(mDescription)
+ .setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL)
+ .build();
+
+ final BiometricPrompt biometricPrompt;
+ BiometricPrompt.AuthenticationCallback wrappedCallback =
+ new BiometricPrompt.AuthenticationCallback() {
+ @Override
+ public void onAuthenticationError(int errorCode,
+ @NonNull CharSequence errString) {
+ mClientCallback.onAuthenticationError(
+ mViewModel.getClientActivity(),
+ errorCode,
+ errString
+ );
+ }
+
+ @Override
+ public void onAuthenticationSucceeded(
+ @NonNull BiometricPrompt.AuthenticationResult result) {
+ mClientCallback.onAuthenticationSucceeded(
+ mViewModel.getClientActivity(),
+ result
+ );
+ }
+
+ @Override
+ public void onAuthenticationFailed() {
+ mClientCallback.onAuthenticationFailed(
+ mViewModel.getClientActivity()
+ );
+ }
+ };
+ if (mAuthPromptHost.getActivity() != null) {
+ biometricPrompt = new BiometricPrompt(mAuthPromptHost.getActivity(),
+ mClientExecutor, wrappedCallback);
+ } else if (mAuthPromptHost.getFragment() != null) {
+ biometricPrompt = new BiometricPrompt(mAuthPromptHost.getFragment(),
+ mClientExecutor, wrappedCallback);
+ } else {
+ throw new IllegalArgumentException("Invalid AuthPromptHost provided. Must "
+ + "provide AuthPromptHost containing Fragment or FragmentActivity for"
+ + " hosting the BiometricPrompt.");
+ }
+ return new CredentialAuthPrompt(biometricPrompt, promptInfo, mCrypto, mDescription);
+ }
+ }
+}
diff --git a/buildSrc-tests/src/test/kotlin/androidx/build/XmlTestConfigVerificationTest.kt b/buildSrc-tests/src/test/kotlin/androidx/build/XmlTestConfigVerificationTest.kt
index 8011c6e..3017bed 100644
--- a/buildSrc-tests/src/test/kotlin/androidx/build/XmlTestConfigVerificationTest.kt
+++ b/buildSrc-tests/src/test/kotlin/androidx/build/XmlTestConfigVerificationTest.kt
@@ -41,4 +41,20 @@
val parser = SAXParserFactory.newInstance().newSAXParser()
parser.parse(InputSource(StringReader(SELF_INSTRUMENTING_TEMPLATE)), DefaultHandler())
}
+
+ @Test
+ fun testValidTestConfigXml_MEDIA_TEMPLATE() {
+ val parser = SAXParserFactory.newInstance().newSAXParser()
+ parser.parse(
+ InputSource(
+ StringReader(
+ MEDIA_TEMPLATE.replace(
+ "INSTRUMENTATION_ARGS",
+ CLIENT_PREVIOUS + SERVICE_PREVIOUS
+ )
+ )
+ ),
+ DefaultHandler()
+ )
+ }
}
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt b/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt
index e9f75e7..a7ae645 100644
--- a/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt
@@ -33,10 +33,6 @@
import androidx.build.checkapi.LibraryApiTaskConfig
import androidx.build.checkapi.configureProjectForApiTasks
import androidx.build.studio.StudioTask
-import com.android.build.api.artifact.ArtifactType
-import com.android.build.api.artifact.Artifacts
-import com.android.build.api.dsl.ApplicationExtension
-import com.android.build.api.dsl.CommonExtension
import com.android.build.gradle.AppExtension
import com.android.build.gradle.AppPlugin
import com.android.build.gradle.LibraryExtension
@@ -521,43 +517,7 @@
Jacoco.registerClassFilesTask(project, this)
}
- val commonExtension = project.extensions.getByType(CommonExtension::class.java)
- try {
- val androidComponentsExtensionType =
- Class.forName("com.android.build.api.extension.AndroidComponentsExtension")
- val androidComponentsExtension =
- project.extensions.getByType(androidComponentsExtensionType)
- val selectorType = Class.forName("com.android.build.api.extension.VariantSelector")
- val selector = androidComponentsExtensionType.getMethod("selector")
- .invoke(androidComponentsExtension)
- androidComponentsExtension.javaClass.getMethod(
- "androidTest",
- selectorType,
- Function1::class.java
- ).invoke(
- androidComponentsExtension,
- selector.javaClass.getMethod("all").invoke(selector),
- { androidTest: Any ->
- createTestConfigurationGenerationTask(
- project,
- androidTest.javaClass.getMethod(
- "getName"
- ).invoke(
- androidTest
- ) as String,
- androidTest.javaClass.getMethod(
- "getArtifacts"
- ).invoke(
- androidTest
- ) as Artifacts,
- defaultConfig.minSdk!!,
- defaultConfig.testInstrumentationRunner!!
- )
- }
- )
- } catch (cnfe: ClassNotFoundException) {
- commonExtension.configureTestConfigGeneration(project)
- }
+ this.configureTestConfigGenerationViaReflection(project)
val buildTestApksTask = project.rootProject.tasks.named(BUILD_TEST_APKS_TASK)
testVariants.all { variant ->
@@ -568,118 +528,6 @@
}
}
- private fun CommonExtension<*, *, *, *, *, *, *, *>
- .configureTestConfigGeneration(project: Project) {
- // old iteration of the new API.
- javaClass.getMethod("onVariants", Function1::class.java)
- .invoke(
- this,
- { variant: Any ->
- variant.javaClass.getMethod(
- "androidTestProperties",
- Function1::class.java
- ).invoke(
- variant,
- { androidTest: Any ->
- createTestConfigurationGenerationTask(
- project,
- androidTest.javaClass.getMethod(
- "getName"
- ).invoke(androidTest) as String,
- androidTest.javaClass.getMethod(
- "getArtifacts"
- ).invoke(androidTest) as Artifacts,
- defaultConfig.minSdk!!,
- defaultConfig.testInstrumentationRunner!!
- )
- }
- )
- } as Function1<Any, Any>
- )
- }
-
- private fun createTestConfigurationGenerationTask(
- project: Project,
- variantName: String,
- artifacts: Artifacts,
- minSdk: Int,
- testRunner: String
- ) {
- val generateTestConfigurationTask = project.tasks.register(
- "${GENERATE_TEST_CONFIGURATION_TASK}$variantName",
- GenerateTestConfigurationTask::class.java
- ) {
- it.testFolder.set(artifacts.get(ArtifactType.APK))
- it.testLoader.set(artifacts.getBuiltArtifactsLoader())
- it.outputXml.fileValue(
- File(
- project.getTestConfigDirectory(),
- "${project.path.asFilenamePrefix()}$variantName.xml"
- )
- )
- it.minSdk.set(minSdk)
- it.hasBenchmarkPlugin.set(project.hasBenchmarkPlugin())
- it.testRunner.set(testRunner)
- it.projectPath.set(project.path)
- AffectedModuleDetector.configureTaskGuard(it)
- }
- // Disable xml generation for projects that have no test sources
- project.afterEvaluate {
- generateTestConfigurationTask.configure {
- it.enabled = hasAndroidTestSourceCode(project)
- }
- }
- project.rootProject.tasks.findByName(ZIP_TEST_CONFIGS_WITH_APKS_TASK)!!
- .dependsOn(generateTestConfigurationTask)
- }
-
- private fun ApplicationExtension<*, *, *, *, *>
- .addAppApkToTestConfigGeneration(project: Project) {
- val allVariants = javaClass.getMethod("getOnVariantProperties")
- .invoke(this)
-
- allVariants.javaClass.getMethod("withBuildType", String::class.java, Function1::class.java)
- .invoke(
- allVariants,
- "debug",
- { debugVariant: Any ->
- val artifacts = debugVariant.javaClass.getMethod("getArtifacts")
- .invoke(debugVariant) as Artifacts
- project.tasks.withType(GenerateTestConfigurationTask::class.java) {
- it.appFolder.set(artifacts.get(ArtifactType.APK))
- it.appLoader.set(artifacts.getBuiltArtifactsLoader())
- }
- }
- )
- }
-
- /**
- * Expected to be called in afterEvaluate when all extensions are available
- */
- private fun hasAndroidTestSourceCode(project: Project): Boolean {
- // check Java androidTest source set
- project.extensions.findByType(TestedExtension::class.java)?.sourceSets
- ?.findByName("androidTest")?.let { sourceSet ->
- // using getSourceFiles() instead of sourceFiles due to b/150800094
- if (!sourceSet.java.getSourceFiles().isEmpty) return true
- }
-
- // check kotlin-android androidTest source set
- project.extensions.findByType(KotlinAndroidProjectExtension::class.java)
- ?.sourceSets?.findByName("androidTest")?.let {
- if (it.kotlin.files.isNotEmpty()) return true
- }
-
- // check kotlin-multiplatform androidAndroidTest source set
- project.multiplatformExtension?.apply {
- sourceSets.findByName("androidAndroidTest")?.let {
- if (it.kotlin.files.isNotEmpty()) return true
- }
- }
-
- return false
- }
-
private fun ApkVariant.configureApkCopy(
project: Project,
testApk: Boolean
@@ -687,7 +535,7 @@
packageApplicationProvider.get().let { packageTask ->
AffectedModuleDetector.configureTaskGuard(packageTask)
// Skip copying AndroidTest apks if they have no source code (no tests to run).
- if (testApk && !hasAndroidTestSourceCode(project)) {
+ if (testApk && !project.hasAndroidTestSourceCode()) {
return
}
@@ -777,38 +625,7 @@
}
}
- try {
- val androidComponentsExtensionType = Class.forName(
- "com.android.build.api.extension.ApplicationAndroidComponentsExtension"
- )
- val androidComponentsExtension =
- project.extensions.getByType(androidComponentsExtensionType)
- val selectorType = Class.forName(
- "com.android.build.api.extension.VariantSelector"
- )
- val selector = androidComponentsExtensionType.getMethod("selector")
- .invoke(androidComponentsExtension)
- androidComponentsExtensionType
- .getMethod("onVariants", selectorType, Function1::class.java)
- .invoke(
- androidComponentsExtension,
- selectorType.getMethod("withBuildType", String::class.java)
- .invoke(selector, "debug"),
- { debugVariant: Any ->
- val artifacts = debugVariant.javaClass.getMethod("getArtifacts")
- .invoke(debugVariant) as Artifacts
- project.tasks.withType(GenerateTestConfigurationTask::class.java) {
- it.appFolder.set(artifacts.get(ArtifactType.APK))
- it.appLoader.set(artifacts.getBuiltArtifactsLoader())
- }
- }
- )
- } catch (cnfe: ClassNotFoundException) {
- val applicationExtension = project.extensions.getByType(
- ApplicationExtension::class.java
- )
- applicationExtension.addAppApkToTestConfigGeneration(project)
- }
+ project.addAppApkToTestConfigGenerationViaReflection()
val buildTestApksTask = project.rootProject.tasks.named(BUILD_TEST_APKS_TASK)
applicationVariants.all { variant ->
@@ -1015,16 +832,18 @@
* to determine what gets run by our test runner
*/
fun String.renameApkForTesting(projectPath: String, hasBenchmarkPlugin: Boolean): String {
- return if (this.contains("media-test") || this.contains("media2-test")) {
- // Exclude media-test-* and media2-test-* modules from
- // existing support library presubmit tests.
- this.replace("-debug-androidTest", "")
- } else if (hasBenchmarkPlugin) {
- val name = this.replace("-androidTest", "-androidBenchmark")
- "${projectPath.asFilenamePrefix()}_$name"
- } else {
- "${projectPath.asFilenamePrefix()}_$this"
- }
+
+ val name =
+ if (projectPath.contains("media") && projectPath.contains("version-compat-tests")) {
+ // Exclude media*:version-compat-tests modules from
+ // existing support library presubmit tests.
+ this.replace("-debug-androidTest", "")
+ } else if (hasBenchmarkPlugin) {
+ this.replace("-androidTest", "-androidBenchmark")
+ } else {
+ this
+ }
+ return "${projectPath.asFilenamePrefix()}_$name"
}
fun Project.hasBenchmarkPlugin(): Boolean {
@@ -1048,3 +867,30 @@
it.dependsOn(task)
}
}
+
+/**
+ * Expected to be called in afterEvaluate when all extensions are available
+ */
+internal fun Project.hasAndroidTestSourceCode(): Boolean {
+ // check Java androidTest source set
+ this.extensions.findByType(TestedExtension::class.java)!!.sourceSets
+ .findByName("androidTest")?.let { sourceSet ->
+ // using getSourceFiles() instead of sourceFiles due to b/150800094
+ if (!sourceSet.java.getSourceFiles().isEmpty) return true
+ }
+
+ // check kotlin-android androidTest source set
+ this.extensions.findByType(KotlinAndroidProjectExtension::class.java)
+ ?.sourceSets?.findByName("androidTest")?.let {
+ if (it.kotlin.files.isNotEmpty()) return true
+ }
+
+ // check kotlin-multiplatform androidAndroidTest source set
+ this.multiplatformExtension?.apply {
+ sourceSets.findByName("androidAndroidTest")?.let {
+ if (it.kotlin.files.isNotEmpty()) return true
+ }
+ }
+
+ return false
+}
diff --git a/buildSrc/src/main/kotlin/androidx/build/AndroidXRootPlugin.kt b/buildSrc/src/main/kotlin/androidx/build/AndroidXRootPlugin.kt
index 309f9b4..9847fce 100644
--- a/buildSrc/src/main/kotlin/androidx/build/AndroidXRootPlugin.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/AndroidXRootPlugin.kt
@@ -161,8 +161,14 @@
subproject.name != "docs-tip-of-tree" &&
subproject.name != "camera-testapp-timing" &&
subproject.name != "room-testapp" &&
- subproject.name != "support-media2-test-client-previous" &&
- subproject.name != "support-media2-test-service-previous"
+ !(
+ subproject.path.contains
+ ("media2:media2-session:version-compat-tests:client-previous")
+ ) &&
+ !(
+ subproject.path.contains
+ ("media2:media2-session:version-compat-tests:service-previous")
+ )
) {
subproject.configurations.all { configuration ->
configuration.resolutionStrategy.dependencySubstitution.apply {
diff --git a/buildSrc/src/main/kotlin/androidx/build/GenerateMediaTestConfigurationTask.kt b/buildSrc/src/main/kotlin/androidx/build/GenerateMediaTestConfigurationTask.kt
new file mode 100644
index 0000000..0bda7fe
--- /dev/null
+++ b/buildSrc/src/main/kotlin/androidx/build/GenerateMediaTestConfigurationTask.kt
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2020 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.build
+
+import com.android.build.api.variant.BuiltArtifacts
+import com.android.build.api.variant.BuiltArtifactsLoader
+import org.gradle.api.DefaultTask
+import org.gradle.api.file.DirectoryProperty
+import org.gradle.api.file.RegularFileProperty
+import org.gradle.api.provider.Property
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.Internal
+import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.TaskAction
+import java.io.File
+
+const val MEDIA_TEMPLATE = """<?xml version="1.0" encoding="utf-8"?>
+ <!-- Copyright (C) 2019 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.
+ -->
+ <configuration description="Runs tests for the module">
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MinApiLevelModuleController">
+ <option name="min-api-level" value="MIN_SDK" />
+ </object>
+ <option name="test-suite-tag" value="androidx_unit_tests_suite" />
+ <option name="config-descriptor:metadata" key="applicationId"
+ value="CLIENT_APPLICATION_ID;SERVICE_APPLICATION_ID" />
+ <option name="wifi:disable" value="true" />
+ <include name="google/unbundled/common/setup" />
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CLIENT_FILE_NAME" />
+ <option name="test-file-name" value="SERVICE_FILE_NAME" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="runner" value="TEST_RUNNER"/>
+ <option name="package" value="CLIENT_APPLICATION_ID" />
+ INSTRUMENTATION_ARGS
+ </test>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="runner" value="TEST_RUNNER"/>
+ <option name="package" value="SERVICE_APPLICATION_ID" />
+ INSTRUMENTATION_ARGS
+ </test>
+ </configuration>"""
+
+const val CLIENT_PREVIOUS = """
+ <option name="instrumentation-arg" value="client_version=previous" />
+"""
+const val CLIENT_TOT = """
+ <option name="instrumentation-arg" value="client_version=tot" />
+"""
+const val SERVICE_PREVIOUS = """
+ <option name="instrumentation-arg" value="service_version=previous" />
+"""
+const val SERVICE_TOT = """
+ <option name="instrumentation-arg" value="service_version=tot" />
+"""
+
+/**
+ * Writes three configuration files to test combinations of media client & service in
+ * <a href=https://source.android.com/devices/tech/test_infra/tradefed/testing/through-suite/android-test-structure>AndroidTest.xml</a>
+ * format that gets zipped alongside the APKs to be tested. The combinations are of previous and
+ * tip-of-tree versions client and service. We want to test every possible pairing that includes
+ * tip-of-tree.
+ *
+ * This config gets ingested by Tradefed.
+ */
+abstract class GenerateMediaTestConfigurationTask : DefaultTask() {
+
+ @get:InputFiles
+ abstract val clientToTFolder: DirectoryProperty
+
+ @get:Internal
+ abstract val clientToTLoader: Property<BuiltArtifactsLoader>
+
+ @get:InputFiles
+ abstract val clientPreviousFolder: DirectoryProperty
+
+ @get:Internal
+ abstract val clientPreviousLoader: Property<BuiltArtifactsLoader>
+
+ @get:InputFiles
+ abstract val serviceToTFolder: DirectoryProperty
+
+ @get:Internal
+ abstract val serviceToTLoader: Property<BuiltArtifactsLoader>
+
+ @get:InputFiles
+ abstract val servicePreviousFolder: DirectoryProperty
+
+ @get:Internal
+ abstract val servicePreviousLoader: Property<BuiltArtifactsLoader>
+
+ @get:Input
+ abstract val clientToTPath: Property<String>
+
+ @get:Input
+ abstract val clientPreviousPath: Property<String>
+
+ @get:Input
+ abstract val serviceToTPath: Property<String>
+
+ @get:Input
+ abstract val servicePreviousPath: Property<String>
+
+ @get:Input
+ abstract val minSdk: Property<Int>
+
+ @get:Input
+ abstract val testRunner: Property<String>
+
+ @get:OutputFile
+ abstract val clientPreviousServiceToT: RegularFileProperty
+
+ @get:OutputFile
+ abstract val clientToTServicePrevious: RegularFileProperty
+
+ @get:OutputFile
+ abstract val clientToTServiceToT: RegularFileProperty
+
+ @TaskAction
+ fun generateAndroidTestZip() {
+ val clientToTApk = resolveApk(clientToTFolder, clientToTLoader)
+ val clientPreviousApk = resolveApk(clientPreviousFolder, clientPreviousLoader)
+ val serviceToTApk = resolveApk(serviceToTFolder, serviceToTLoader)
+ val servicePreviousApk = resolveApk(
+ servicePreviousFolder, servicePreviousLoader
+ )
+ writeConfigFileContent(
+ clientToTApk, serviceToTApk, clientToTPath.get(),
+ serviceToTPath.get(), clientToTServiceToT
+ )
+ writeConfigFileContent(
+ clientToTApk, servicePreviousApk, clientToTPath.get(),
+ servicePreviousPath.get(), clientToTServicePrevious
+ )
+ writeConfigFileContent(
+ clientPreviousApk, serviceToTApk, clientPreviousPath.get(),
+ serviceToTPath.get(), clientPreviousServiceToT
+ )
+ }
+
+ private fun resolveApk(
+ apkFolder: DirectoryProperty,
+ apkLoader: Property<BuiltArtifactsLoader>
+ ): BuiltArtifacts {
+ return apkLoader.get().load(apkFolder.get())
+ ?: throw RuntimeException("Cannot load APK for $name")
+ }
+
+ private fun resolveName(apk: BuiltArtifacts, path: String): String {
+ return apk.elements.single().outputFile.substringAfterLast("/")
+ .renameApkForTesting(path, false)
+ }
+
+ private fun writeConfigFileContent(
+ clientApk: BuiltArtifacts,
+ serviceApk: BuiltArtifacts,
+ clientPath: String,
+ servicePath: String,
+ outputFile: RegularFileProperty
+ ) {
+ val instrumentationArgs =
+ if (clientPath.contains("previous")) {
+ if (servicePath.contains("previous")) {
+ CLIENT_PREVIOUS + SERVICE_PREVIOUS
+ } else {
+ CLIENT_PREVIOUS + SERVICE_TOT
+ }
+ } else if (servicePath.contains("previous")) {
+ CLIENT_TOT + SERVICE_PREVIOUS
+ } else {
+ CLIENT_TOT + SERVICE_TOT
+ }
+ var configContent: String = MEDIA_TEMPLATE
+ configContent = configContent
+ .replace("CLIENT_FILE_NAME", resolveName(clientApk, clientPath))
+ .replace("SERVICE_FILE_NAME", resolveName(serviceApk, servicePath))
+ .replace("CLIENT_APPLICATION_ID", clientApk.applicationId)
+ .replace("SERVICE_APPLICATION_ID", serviceApk.applicationId)
+ .replace("MIN_SDK", minSdk.get().toString())
+ .replace("TEST_RUNNER", testRunner.get())
+ .replace("INSTRUMENTATION_ARGS", instrumentationArgs)
+ val resolvedOutputFile: File = outputFile.asFile.get()
+ if (!resolvedOutputFile.exists()) {
+ if (!resolvedOutputFile.createNewFile()) {
+ throw RuntimeException(
+ "Failed to create test configuration file: $outputFile"
+ )
+ }
+ }
+ resolvedOutputFile.writeText(configContent)
+ }
+}
diff --git a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
index 378205f..58d463f 100644
--- a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
@@ -82,7 +82,7 @@
val MEDIAROUTER = Version("1.3.0-alpha01")
val NAVIGATION = Version("2.4.0-alpha01")
val NAVIGATION_COMPOSE = Version("1.0.0-alpha03")
- val PAGING = Version("3.0.0-alpha09")
+ val PAGING = Version("3.0.0-alpha10")
val PAGING_COMPOSE = Version("1.0.0-alpha03")
val PALETTE = Version("1.1.0-alpha01")
val PRINT = Version("1.1.0-beta01")
diff --git a/buildSrc/src/main/kotlin/androidx/build/MavenUploadHelper.kt b/buildSrc/src/main/kotlin/androidx/build/MavenUploadHelper.kt
index e460eb0..be665d8 100644
--- a/buildSrc/src/main/kotlin/androidx/build/MavenUploadHelper.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/MavenUploadHelper.kt
@@ -150,7 +150,7 @@
?: throw Exception("You must specify mavenGroup for $name project")
val strippedGroupId = mavenGroup.substringAfterLast(".")
if (mavenGroup.startsWith("androidx") && !name.startsWith(strippedGroupId)) {
- throw Exception("Your artifactId must start with $strippedGroupId! (currently is $name)")
+ throw Exception("Your artifactId must start with '$strippedGroupId'. (currently is $name)")
}
return mavenGroup
}
diff --git a/buildSrc/src/main/kotlin/androidx/build/TestSuiteConfiguration.kt b/buildSrc/src/main/kotlin/androidx/build/TestSuiteConfiguration.kt
new file mode 100644
index 0000000..dea292a
--- /dev/null
+++ b/buildSrc/src/main/kotlin/androidx/build/TestSuiteConfiguration.kt
@@ -0,0 +1,342 @@
+/*
+ * Copyright 2020 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.build
+
+import androidx.build.dependencyTracker.AffectedModuleDetector
+import com.android.build.api.artifact.ArtifactType
+import com.android.build.api.artifact.Artifacts
+import com.android.build.api.dsl.ApplicationExtension
+import com.android.build.api.dsl.CommonExtension
+import com.android.build.gradle.TestedExtension
+import org.gradle.api.Project
+import org.gradle.api.tasks.TaskProvider
+import java.io.File
+
+fun Project.createTestConfigurationGenerationTask(
+ variantName: String,
+ artifacts: Artifacts,
+ minSdk: Int,
+ testRunner: String
+) {
+ val generateTestConfigurationTask = this.tasks.register(
+ "${AndroidXPlugin.GENERATE_TEST_CONFIGURATION_TASK}$variantName",
+ GenerateTestConfigurationTask::class.java
+ ) {
+ it.testFolder.set(artifacts.get(ArtifactType.APK))
+ it.testLoader.set(artifacts.getBuiltArtifactsLoader())
+ it.outputXml.fileValue(
+ File(
+ this.getTestConfigDirectory(),
+ "${this.path.asFilenamePrefix()}$variantName.xml"
+ )
+ )
+ it.minSdk.set(minSdk)
+ it.hasBenchmarkPlugin.set(this.hasBenchmarkPlugin())
+ it.testRunner.set(testRunner)
+ it.projectPath.set(this.path)
+ AffectedModuleDetector.configureTaskGuard(it)
+ }
+ // Disable xml generation for projects that have no test sources
+ this.afterEvaluate {
+ generateTestConfigurationTask.configure {
+ it.enabled = this.hasAndroidTestSourceCode()
+ }
+ }
+ this.rootProject.tasks.findByName(AndroidXPlugin.ZIP_TEST_CONFIGS_WITH_APKS_TASK)!!
+ .dependsOn(generateTestConfigurationTask)
+}
+
+fun Project.addAppApkToTestConfigGenerationViaReflection() {
+ try {
+ val androidComponentsExtensionType = Class.forName(
+ "com.android.build.api.extension.ApplicationAndroidComponentsExtension"
+ )
+ val androidComponentsExtension =
+ this.extensions.getByType(androidComponentsExtensionType)
+ val selectorType = Class.forName(
+ "com.android.build.api.extension.VariantSelector"
+ )
+ val selector = androidComponentsExtensionType.getMethod("selector")
+ .invoke(androidComponentsExtension)
+ androidComponentsExtensionType
+ .getMethod("onVariants", selectorType, Function1::class.java)
+ .invoke(
+ androidComponentsExtension,
+ selectorType.getMethod("withBuildType", String::class.java)
+ .invoke(selector, "debug"),
+ { debugVariant: Any ->
+ val artifacts = debugVariant.javaClass.getMethod("getArtifacts")
+ .invoke(debugVariant) as Artifacts
+ this.tasks.withType(GenerateTestConfigurationTask::class.java) {
+ it.appFolder.set(artifacts.get(ArtifactType.APK))
+ it.appLoader.set(artifacts.getBuiltArtifactsLoader())
+ }
+ }
+ )
+ } catch (cnfe: ClassNotFoundException) {
+ val applicationExtension = this.extensions.getByType(
+ ApplicationExtension::class.java
+ )
+ applicationExtension.addAppApkToTestConfigGeneration(this)
+ }
+}
+
+fun ApplicationExtension<*, *, *, *, *>
+.addAppApkToTestConfigGeneration(project: Project) {
+ val allVariants = javaClass.getMethod("getOnVariantProperties")
+ .invoke(this)
+
+ allVariants.javaClass.getMethod("withBuildType", String::class.java, Function1::class.java)
+ .invoke(
+ allVariants,
+ "debug",
+ { debugVariant: Any ->
+ val artifacts = debugVariant.javaClass.getMethod("getArtifacts")
+ .invoke(debugVariant) as Artifacts
+ project.tasks.withType(GenerateTestConfigurationTask::class.java) {
+ it.appFolder.set(artifacts.get(ArtifactType.APK))
+ it.appLoader.set(artifacts.getBuiltArtifactsLoader())
+ }
+ }
+ )
+}
+
+private fun getOrCreateMediaTestConfigTask(project: Project, isMedia2: Boolean):
+ TaskProvider<GenerateMediaTestConfigurationTask> {
+ val mediaPrefix = getMediaConfigTaskPrefix(isMedia2)
+ if (!project.parent!!.tasks.withType(GenerateMediaTestConfigurationTask::class.java)
+ .names.contains(
+ "support-$mediaPrefix-test${
+ AndroidXPlugin
+ .GENERATE_TEST_CONFIGURATION_TASK
+ }"
+ )
+ ) {
+ val task = project.parent!!.tasks.register(
+ "support-$mediaPrefix-test${AndroidXPlugin.GENERATE_TEST_CONFIGURATION_TASK}",
+ GenerateMediaTestConfigurationTask::class.java
+ )
+ project.rootProject.tasks.findByName(AndroidXPlugin.ZIP_TEST_CONFIGS_WITH_APKS_TASK)!!
+ .dependsOn(task)
+ return task
+ } else {
+ return project.parent!!.tasks.withType(GenerateMediaTestConfigurationTask::class.java)
+ .named(
+ "support-$mediaPrefix-test${
+ AndroidXPlugin
+ .GENERATE_TEST_CONFIGURATION_TASK
+ }"
+ )
+ }
+ }
+
+private fun getMediaConfigTaskPrefix(isMedia2: Boolean): String {
+ return if (isMedia2) "media2" else "media"
+}
+
+fun Project.createOrUpdateMediaTestConfigurationGenerationTask(
+ variantName: String,
+ artifacts: Artifacts,
+ minSdk: Int,
+ testRunner: String,
+ isMedia2: Boolean
+) {
+ val mediaPrefix = getMediaConfigTaskPrefix(isMedia2)
+ val mediaTask = getOrCreateMediaTestConfigTask(this, isMedia2)
+ mediaTask.configure {
+ it as GenerateMediaTestConfigurationTask
+ if (this.name.contains("client")) {
+ if (this.name.contains("previous")) {
+ it.clientPreviousFolder.set(artifacts.get(ArtifactType.APK))
+ it.clientPreviousLoader.set(artifacts.getBuiltArtifactsLoader())
+ it.clientPreviousPath.set(this.path)
+ } else {
+ it.clientToTFolder.set(artifacts.get(ArtifactType.APK))
+ it.clientToTLoader.set(artifacts.getBuiltArtifactsLoader())
+ it.clientToTPath.set(this.path)
+ }
+ } else {
+ if (this.name.contains("previous")) {
+ it.servicePreviousFolder.set(artifacts.get(ArtifactType.APK))
+ it.servicePreviousLoader.set(artifacts.getBuiltArtifactsLoader())
+ it.servicePreviousPath.set(this.path)
+ } else {
+ it.serviceToTFolder.set(artifacts.get(ArtifactType.APK))
+ it.serviceToTLoader.set(artifacts.getBuiltArtifactsLoader())
+ it.serviceToTPath.set(this.path)
+ }
+ }
+ it.clientPreviousServiceToT.fileValue(
+ File(
+ this.getTestConfigDirectory(),
+ "${mediaPrefix}ClientPreviousServiceToT$variantName.xml"
+ )
+ )
+ it.clientToTServicePrevious.fileValue(
+ File(
+ this.getTestConfigDirectory(),
+ "${mediaPrefix}ClientToTServicePrevious$variantName.xml"
+ )
+ )
+ it.clientToTServiceToT.fileValue(
+ File(
+ this.getTestConfigDirectory(),
+ "${mediaPrefix}ClientToTServiceToT$variantName.xml"
+ )
+ )
+ it.minSdk.set(minSdk)
+ it.testRunner.set(testRunner)
+ AffectedModuleDetector.configureTaskGuard(it)
+ }
+}
+
+fun CommonExtension<*, *, *, *, *, *, *, *>
+.configureTestConfigGeneration(project: Project) {
+ // old iteration of the new API.
+ javaClass.getMethod("onVariants", Function1::class.java)
+ .invoke(
+ this,
+ { variant: Any ->
+ variant.javaClass.getMethod(
+ "androidTestProperties",
+ Function1::class.java
+ ).invoke(
+ variant,
+ { androidTest: Any ->
+ when {
+ project.path.contains("media2:version-compat-tests") -> {
+ project.createOrUpdateMediaTestConfigurationGenerationTask(
+ androidTest.javaClass.getMethod(
+ "getName"
+ ).invoke(androidTest) as String,
+ androidTest.javaClass.getMethod(
+ "getArtifacts"
+ ).invoke(androidTest) as Artifacts,
+ defaultConfig.minSdk!!,
+ defaultConfig.testInstrumentationRunner!!,
+ true
+ )
+ }
+ project.path.contains("media:version-compat-tests") -> {
+ project.createOrUpdateMediaTestConfigurationGenerationTask(
+ androidTest.javaClass.getMethod(
+ "getName"
+ ).invoke(androidTest) as String,
+ androidTest.javaClass.getMethod(
+ "getArtifacts"
+ ).invoke(androidTest) as Artifacts,
+ defaultConfig.minSdk!!,
+ defaultConfig.testInstrumentationRunner!!,
+ false
+ )
+ }
+ else -> {
+ project.createTestConfigurationGenerationTask(
+ androidTest.javaClass.getMethod(
+ "getName"
+ ).invoke(androidTest) as String,
+ androidTest.javaClass.getMethod(
+ "getArtifacts"
+ ).invoke(androidTest) as Artifacts,
+ defaultConfig.minSdk!!,
+ defaultConfig.testInstrumentationRunner!!
+ )
+ }
+ }
+ }
+ )
+ } as Function1<Any, Any>
+ )
+}
+
+fun TestedExtension.configureTestConfigGenerationViaReflection(project: Project) {
+ val commonExtension = project.extensions.getByType(CommonExtension::class.java)
+ try {
+ val androidComponentsExtensionType =
+ Class.forName("com.android.build.api.extension.AndroidComponentsExtension")
+ val androidComponentsExtension =
+ project.extensions.getByType(androidComponentsExtensionType)
+ val selectorType = Class.forName("com.android.build.api.extension.VariantSelector")
+ val selector = androidComponentsExtensionType.getMethod("selector")
+ .invoke(androidComponentsExtension)
+ androidComponentsExtension.javaClass.getMethod(
+ "androidTest",
+ selectorType,
+ Function1::class.java
+ ).invoke(
+ androidComponentsExtension,
+ selector.javaClass.getMethod("all").invoke(selector),
+ { androidTest: Any ->
+ when {
+ // support-media tests are special cased
+ project.path.contains("media2:version-compat-tests") -> {
+ project.createOrUpdateMediaTestConfigurationGenerationTask(
+ androidTest.javaClass.getMethod(
+ "getName"
+ ).invoke(
+ androidTest
+ ) as String,
+ androidTest.javaClass.getMethod(
+ "getArtifacts"
+ ).invoke(
+ androidTest
+ ) as Artifacts,
+ defaultConfig.minSdk!!,
+ defaultConfig.testInstrumentationRunner!!,
+ true
+ )
+ }
+ project.path.contains("media:version-compat-tests") -> {
+ project.createOrUpdateMediaTestConfigurationGenerationTask(
+ androidTest.javaClass.getMethod(
+ "getName"
+ ).invoke(
+ androidTest
+ ) as String,
+ androidTest.javaClass.getMethod(
+ "getArtifacts"
+ ).invoke(
+ androidTest
+ ) as Artifacts,
+ defaultConfig.minSdk!!,
+ defaultConfig.testInstrumentationRunner!!,
+ false
+ )
+ }
+ else -> {
+ project.createTestConfigurationGenerationTask(
+ androidTest.javaClass.getMethod(
+ "getName"
+ ).invoke(
+ androidTest
+ ) as String,
+ androidTest.javaClass.getMethod(
+ "getArtifacts"
+ ).invoke(
+ androidTest
+ ) as Artifacts,
+ defaultConfig.minSdk!!,
+ defaultConfig.testInstrumentationRunner!!
+ )
+ }
+ }
+ }
+ )
+ } catch (cnfe: ClassNotFoundException) {
+ commonExtension.configureTestConfigGeneration(project)
+ }
+}
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt b/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
index 3e2d4a4..1afdfa8 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
@@ -452,16 +452,16 @@
private val COBUILT_TEST_PATHS = setOf(
// Install media tests together per b/128577735
setOf(
- ":support-media-test-client",
- ":support-media-test-service",
- ":support-media-test-client-previous",
- ":support-media-test-service-previous"
+ ":media:version-compat-tests:client",
+ ":media:version-compat-tests:service",
+ ":media:version-compat-tests:client-previous",
+ ":media:version-compat-tests:service-previous"
),
setOf(
- ":support-media2-test-client",
- ":support-media2-test-service",
- ":support-media2-test-client-previous",
- ":support-media2-test-service-previous"
+ ":media2:media2-session:version-compat-tests:client",
+ ":media2:media2-session:version-compat-tests:service",
+ ":media2:media2-session:version-compat-tests:client-previous",
+ ":media2:media2-session:version-compat-tests:service-previous"
), // Link graphics and material to always run @Large in presubmit per b/160624022
setOf(
":compose:ui:ui-graphics",
diff --git a/buildSrc/src/main/kotlin/androidx/build/studio/StudioPatcher.kt b/buildSrc/src/main/kotlin/androidx/build/studio/StudioPatcher.kt
index 976bf8d..5e51934 100644
--- a/buildSrc/src/main/kotlin/androidx/build/studio/StudioPatcher.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/studio/StudioPatcher.kt
@@ -31,15 +31,15 @@
val NoopStudioPatcher: StudioPatcher = fun (_: StudioTask, _: Project, _: File) {}
/**
- * Patch studio with the Kotlin 1.4.10 plugin, downloaded from Jetbrains
+ * Patch studio with the Performance Testing 202.6948.5 plugin, downloaded from Jetbrains
*/
-val KotlinStudioPatcher: StudioPatcher = fun (
+val PerformancePluginStudioPatcher: StudioPatcher = fun (
studioTask: StudioTask,
project: Project,
studioInstallationDir: File
) {
- val url = "https://plugins.jetbrains.com/plugin/download?rel=true&updateId=96650"
- val tmpZip = File("${studioInstallationDir.absolutePath}/kotlinPlugin.zip")
+ val url = "https://plugins.jetbrains.com/plugin/download?rel=true&updateId=94393"
+ val tmpZip = File("${studioInstallationDir.absolutePath}/performanceTestingPlugin.zip")
.absolutePath
println("Downloading $url to $tmpZip")
@@ -52,7 +52,7 @@
val platformUtilities = StudioPlatformUtilities.get(project.rootDir, studioInstallationDir)
val pluginsDir = with(platformUtilities) { studioTask.pluginsDirectory }
- File(pluginsDir, "Kotlin").deleteRecursively()
+ File(pluginsDir, "performanceTesting").deleteRecursively()
project.exec { execSpec ->
with(execSpec) {
diff --git a/buildSrc/src/main/kotlin/androidx/build/studio/StudioTask.kt b/buildSrc/src/main/kotlin/androidx/build/studio/StudioTask.kt
index 3315a32..049587e 100644
--- a/buildSrc/src/main/kotlin/androidx/build/studio/StudioTask.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/studio/StudioTask.kt
@@ -120,11 +120,6 @@
File("$studioInstallationDir/STUDIOW_LICENSE_ACCEPTED")
}
- /**
- * Allows for the patching of a Studio installation (including replacing plugins).
- * TODO: Consider removing after Studio has switched to Kotlin 1.4
- * b/162414740
- */
@get:Internal
protected open val studioPatcher = NoopStudioPatcher
@@ -145,7 +140,7 @@
// Finish install process
successfulInstallFile.createNewFile()
}
- val successfulStudioPatch = File("$studioInstallationDir/PATCH_SUCCESSFUL")
+ val successfulStudioPatch = File("$studioInstallationDir/PLUGIN_PATCH_SUCCESSFUL")
if (!successfulStudioPatch.exists()) {
studioPatcher(this, project, studioInstallationDir)
// Finish patch process
@@ -240,7 +235,7 @@
*/
open class RootStudioTask : StudioTask() {
override val studioArchiveCreator = UrlArchiveCreator
- override val studioPatcher: StudioPatcher = KotlinStudioPatcher
+ override val studioPatcher: StudioPatcher = PerformancePluginStudioPatcher
override val ideaProperties get() = projectRoot.resolve("development/studio/idea.properties")
}
diff --git a/busytown/androidx_host_tests.sh b/busytown/androidx_host_tests.sh
index c753202..c1bbc97 100755
--- a/busytown/androidx_host_tests.sh
+++ b/busytown/androidx_host_tests.sh
@@ -10,6 +10,7 @@
-Pandroidx.ignoreTestFailures \
-Pandroidx.displayTestOutput=false \
-Pandroidx.coverageEnabled=true \
+ -Pandroidx.validateNoUnrecognizedMessages \
-Pandroidx.allWarningsAsErrors "$@"
echo "Completing $0 at $(date)"
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Metadata.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Metadata.kt
index f9bcabe..28fe434 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Metadata.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Metadata.kt
@@ -85,6 +85,15 @@
}
/**
+ * A wrapper around [CaptureRequest]. This is useful in tests where we can't create an instance of
+ * [CaptureRequest] directly.
+ *
+ * TODO(codelogic, sushilnath): this wrapper can be removed if some of the methods in
+ * [Request.Listener] can be changed to not accept [CaptureRequest] directly.
+ */
+interface CaptureRequestWrapper : UnsafeWrapper<CaptureRequest>
+
+/**
* RequestMetadata wraps together all of the information about specific CaptureRequest that was
* submitted to Camera2.
*
@@ -92,7 +101,7 @@
* [CameraGraph]. This class will report the actual keys / values that were sent to camera2 (if
* different) from the request that was used to create the Camera2 [CaptureRequest].
*/
-interface RequestMetadata : Metadata, UnsafeWrapper<CaptureRequest> {
+interface RequestMetadata : Metadata, CaptureRequestWrapper {
operator fun <T> get(key: CaptureRequest.Key<T>): T?
fun <T> getOrDefault(key: CaptureRequest.Key<T>, default: T): T
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Request.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Request.kt
index 5a109cd..dbfdf3b 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Request.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Request.kt
@@ -174,7 +174,7 @@
fun onRequestSequenceCreated(
request: Request,
requestNumber: RequestNumber,
- captureRequest: CaptureRequest,
+ captureRequest: CaptureRequestWrapper,
streams: Map<StreamId, Surface>
) {
}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraGraphComponent.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraGraphComponent.kt
index c95132b..640fc72 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraGraphComponent.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraGraphComponent.kt
@@ -97,12 +97,18 @@
@Provides
@ForCameraGraph
fun provideRequestListeners(
- graphConfig: CameraGraph.Config
+ graphConfig: CameraGraph.Config,
+ listener3A: Listener3A
): java.util.ArrayList<Request.Listener> {
// TODO: Dagger doesn't appear to like standard kotlin lists. Replace this with a standard
// Kotlin list interfaces when dagger compiles with them.
- // TODO: Add internal listeners before adding external global listeners.
- return java.util.ArrayList(graphConfig.listeners)
+ val listeners = java.util.ArrayList<Request.Listener>()
+ listeners.add(listener3A)
+ // Listeners in CameraGraph.Config can de defined outside of the CameraPipe library,
+ // and since we iterate thought the listeners in order and invoke them, it appears
+ // beneficial to add the internal listeners first and then the graph config listeners.
+ listeners.addAll(graphConfig.listeners)
+ return listeners
}
}
}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/FrameMetadata.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/FrameMetadata.kt
index a65debf..b6a2236 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/FrameMetadata.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/FrameMetadata.kt
@@ -16,11 +16,13 @@
package androidx.camera.camera2.pipe.impl
+import android.hardware.camera2.CaptureRequest
import android.hardware.camera2.CaptureResult
import android.hardware.camera2.TotalCaptureResult
import android.os.Build
import android.util.ArrayMap
import androidx.camera.camera2.pipe.CameraId
+import androidx.camera.camera2.pipe.CaptureRequestWrapper
import androidx.camera.camera2.pipe.FrameInfo
import androidx.camera.camera2.pipe.FrameMetadata
import androidx.camera.camera2.pipe.FrameNumber
@@ -131,4 +133,14 @@
get() = result.frameNumber
override fun unwrap(): TotalCaptureResult? = totalCaptureResult
+}
+
+/**
+ * Implementation of [CaptureRequestWrapper] that returns the underlying [CaptureRequest].
+ */
+@Suppress("SyntheticAccessor") // Using an inline class generates a synthetic constructor
+class AndroidCaptureRequest(private val captureRequest: CaptureRequest) : CaptureRequestWrapper {
+ override fun unwrap(): CaptureRequest {
+ return captureRequest
+ }
}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/Listener3A.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/Listener3A.kt
new file mode 100644
index 0000000..f1a0394
--- /dev/null
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/Listener3A.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2020 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.impl
+
+import android.view.Surface
+import androidx.camera.camera2.pipe.CaptureRequestWrapper
+import androidx.camera.camera2.pipe.FrameInfo
+import androidx.camera.camera2.pipe.FrameMetadata
+import androidx.camera.camera2.pipe.FrameNumber
+import androidx.camera.camera2.pipe.Request
+import androidx.camera.camera2.pipe.RequestMetadata
+import androidx.camera.camera2.pipe.RequestNumber
+import androidx.camera.camera2.pipe.StreamId
+import java.util.concurrent.CopyOnWriteArrayList
+import javax.inject.Inject
+
+/**
+ * A [Request.Listener] to receive partial and final metadata for each request sent to the camera
+ * device. It maintains a list of [Result3AStateListener] to which it broadcasts the updates and
+ * removes them as they are completed. This listener is useful for implementing 3A methods to
+ * look for desired 3A state changes.
+ */
+@CameraGraphScope
+class Listener3A @Inject constructor() : Request.Listener {
+ private val listeners: CopyOnWriteArrayList<Result3AStateListener> = CopyOnWriteArrayList()
+
+ override fun onRequestSequenceCreated(
+ request: Request,
+ requestNumber: RequestNumber,
+ captureRequest: CaptureRequestWrapper,
+ streams: Map<StreamId, Surface>
+ ) {
+ var listenersCopy: List<Result3AStateListener>
+ synchronized(this) {
+ listenersCopy = listeners.toList()
+ }
+ for (listener in listenersCopy) {
+ listener.onRequestSequenceCreated(requestNumber)
+ }
+ }
+
+ override fun onPartialCaptureResult(
+ requestMetadata: RequestMetadata,
+ frameNumber: FrameNumber,
+ captureResult: FrameMetadata
+ ) {
+ updateListeners(requestMetadata.requestNumber, captureResult)
+ }
+
+ override fun onTotalCaptureResult(
+ requestMetadata: RequestMetadata,
+ frameNumber: FrameNumber,
+ totalCaptureResult: FrameInfo
+ ) {
+ updateListeners(requestMetadata.requestNumber, totalCaptureResult.metadata)
+ }
+
+ fun addListener(listener: Result3AStateListener) {
+ synchronized(this) {
+ listeners.add(listener)
+ }
+ }
+
+ private fun updateListeners(requestNumber: RequestNumber, metadata: FrameMetadata) {
+ for (listener in listeners) {
+ if (listener.update(requestNumber, metadata)) {
+ listeners.remove(listener)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/RequestProcessor.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/RequestProcessor.kt
index 573d89c..f8fbcc3 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/RequestProcessor.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/RequestProcessor.kt
@@ -627,7 +627,7 @@
listener.onRequestSequenceCreated(
request.request,
request.requestNumber,
- captureRequests[index],
+ AndroidCaptureRequest(captureRequests[index]),
streamMap
)
}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/Result3AStateListener.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/Result3AStateListener.kt
index 60f1c45..56182d6 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/Result3AStateListener.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/Result3AStateListener.kt
@@ -34,11 +34,16 @@
* This update method can be called multiple times as we get newer [CaptureResult]s from the camera
* device. This class also exposes a [Deferred] to query the status of desired state.
*/
-class Result3AStateListener(
+interface Result3AStateListener {
+ fun onRequestSequenceCreated(requestNumber: RequestNumber)
+ fun update(requestNumber: RequestNumber, frameMetadata: FrameMetadata): Boolean
+}
+
+class Result3AStateListenerImpl (
private val exitConditionForKeys: Map<CaptureResult.Key<*>, List<Any>>,
private val frameLimit: Int? = null,
private val timeLimitNs: Long? = null
-) {
+) : Result3AStateListener {
init {
require(exitConditionForKeys.isNotEmpty()) { "Exit condition map for keys is empty." }
@@ -51,7 +56,7 @@
@GuardedBy("this")
private var initialRequestNumber: RequestNumber? = null
- fun onRequestSequenceCreated(requestNumber: RequestNumber) {
+ override fun onRequestSequenceCreated(requestNumber: RequestNumber) {
synchronized(this) {
if (initialRequestNumber == null) {
initialRequestNumber = requestNumber
@@ -59,7 +64,7 @@
}
}
- fun update(requestNumber: RequestNumber, frameMetadata: FrameMetadata): Boolean {
+ override fun update(requestNumber: RequestNumber, frameMetadata: FrameMetadata): Boolean {
// Save some compute if the task is already complete or has been canceled.
if (deferred.isCompleted || deferred.isCancelled) {
return true
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/Listener3ATest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/Listener3ATest.kt
new file mode 100644
index 0000000..051dd1e
--- /dev/null
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/Listener3ATest.kt
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2020 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.impl
+
+import android.hardware.camera2.CaptureResult
+import android.os.Build
+import androidx.camera.camera2.pipe.FrameNumber
+import androidx.camera.camera2.pipe.Request
+import androidx.camera.camera2.pipe.RequestNumber
+import androidx.camera.camera2.pipe.testing.CameraPipeRobolectricTestRunner
+import androidx.camera.camera2.pipe.testing.FakeCaptureRequestWrapper
+import androidx.camera.camera2.pipe.testing.FakeFrameMetadata
+import androidx.camera.camera2.pipe.testing.FakeRequestMetadata
+import androidx.camera.camera2.pipe.testing.UpdateCounting3AStateListener
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.internal.DoNotInstrument
+
+@RunWith(CameraPipeRobolectricTestRunner::class)
+@DoNotInstrument
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+class Listener3ATest {
+ @Test
+ fun testListenersInvoked() {
+ val result3AStateListener = Result3AStateListenerImpl(
+ mapOf(
+ CaptureResult.CONTROL_AF_MODE to listOf(CaptureResult.CONTROL_AF_MODE_AUTO)
+ )
+ )
+ val listener3A = Listener3A()
+ listener3A.addListener(result3AStateListener)
+
+ // The deferred result of 3a state listener shouldn't be complete right now.
+ assertThat(result3AStateListener.getDeferredResult().isCompleted).isFalse()
+ listener3A.onRequestSequenceCreated(
+ Request(listOf()),
+ RequestNumber(1),
+ FakeCaptureRequestWrapper(),
+ mapOf()
+ )
+
+ val requestMetadata = FakeRequestMetadata(requestNumber = RequestNumber(1))
+ val frameNumber = FrameNumber(1L)
+ val captureResult = FakeFrameMetadata(
+ mapOf(
+ CaptureResult.CONTROL_AF_MODE to CaptureResult.CONTROL_AF_MODE_AUTO
+ )
+ )
+ // Once the correct metadata is updated the listener3A should broadcast it to the
+ // result3AState listener added to it, making the deferred result complete.
+ listener3A.onPartialCaptureResult(requestMetadata, frameNumber, captureResult)
+ assertThat(result3AStateListener.getDeferredResult().isCompleted).isTrue()
+ }
+
+ @Test
+ fun testListenersInvokedWithMultipleUpdates() {
+ val result3AStateListener = UpdateCounting3AStateListener(
+ Result3AStateListenerImpl(
+ mapOf(
+ CaptureResult.CONTROL_AF_MODE to listOf(CaptureResult.CONTROL_AF_MODE_AUTO)
+ )
+ )
+ )
+ val listener3A = Listener3A()
+ listener3A.addListener(result3AStateListener)
+
+ listener3A.onRequestSequenceCreated(
+ Request(listOf()),
+ RequestNumber(1),
+ FakeCaptureRequestWrapper(),
+ mapOf()
+ )
+
+ val requestMetadata = FakeRequestMetadata(requestNumber = RequestNumber(1))
+ val captureResult = FakeFrameMetadata(
+ mapOf(
+ CaptureResult.CONTROL_AF_MODE to CaptureResult.CONTROL_AF_MODE_CONTINUOUS_PICTURE
+ )
+ )
+ val captureResult1 = FakeFrameMetadata(
+ mapOf(
+ CaptureResult.CONTROL_AF_MODE to CaptureResult.CONTROL_AF_MODE_CONTINUOUS_PICTURE
+ )
+ )
+ listener3A.onPartialCaptureResult(requestMetadata, FrameNumber(1), captureResult)
+ assertThat(result3AStateListener.updateCount).isEqualTo(1)
+
+ // Since the first update didn't have the right key and it's desired value, the second
+ // update should also be supplies to the result3AListener.
+ listener3A.onPartialCaptureResult(requestMetadata, FrameNumber(2), captureResult1)
+ assertThat(result3AStateListener.updateCount).isEqualTo(2)
+ }
+
+ @Test
+ fun testListenersAreRemovedWhenDone() {
+ val result3AStateListener1 = UpdateCounting3AStateListener(
+ Result3AStateListenerImpl(
+ mapOf(
+ CaptureResult.CONTROL_AF_MODE to listOf(CaptureResult.CONTROL_AF_MODE_AUTO)
+ )
+ )
+ )
+ val result3AStateListener2 = UpdateCounting3AStateListener(
+ Result3AStateListenerImpl(
+ mapOf(
+ CaptureResult.CONTROL_AE_MODE to listOf(CaptureResult.CONTROL_AE_MODE_OFF)
+ )
+ )
+ )
+
+ val listener3A = Listener3A()
+ listener3A.addListener(result3AStateListener1)
+ listener3A.addListener(result3AStateListener2)
+
+ val requestMetadata = FakeRequestMetadata(requestNumber = RequestNumber(1))
+ val frameNumber = FrameNumber(1L)
+ val captureResult = FakeFrameMetadata(
+ mapOf(
+ CaptureResult.CONTROL_AF_MODE to CaptureResult.CONTROL_AF_MODE_AUTO
+ )
+ )
+
+ // There should be no update to either of the listeners right now.
+ assertThat(result3AStateListener1.updateCount).isEqualTo(0)
+ assertThat(result3AStateListener2.updateCount).isEqualTo(0)
+
+ listener3A.onRequestSequenceCreated(
+ Request(listOf()),
+ RequestNumber(1),
+ FakeCaptureRequestWrapper(),
+ mapOf()
+ )
+
+ // Once the metadata for correct AF mode is updated, the listener3A should broadcast it to
+ // the result3AState listeners added to it, making result3AStateListener1 complete.
+ listener3A.onPartialCaptureResult(requestMetadata, frameNumber, captureResult)
+ assertThat(result3AStateListener1.updateCount).isEqualTo(1)
+ assertThat(result3AStateListener2.updateCount).isEqualTo(1)
+
+ // Once the metadata for correct AE mode is updated, the listener3A should broadcast it to
+ // the result3AState listeners added to it, making result3AStateListener2 complete. Since
+ // result3AStateListener1 was already completed it will not be updated again.
+ val captureResult1 = FakeFrameMetadata(
+ mapOf(
+ CaptureResult.CONTROL_AF_MODE to CaptureResult.CONTROL_AF_MODE_AUTO,
+ CaptureResult.CONTROL_AE_MODE to CaptureResult.CONTROL_AE_MODE_OFF
+ )
+ )
+ listener3A.onPartialCaptureResult(requestMetadata, frameNumber, captureResult1)
+ assertThat(result3AStateListener1.updateCount).isEqualTo(1)
+ assertThat(result3AStateListener2.updateCount).isEqualTo(2)
+
+ // Since both result3AStateListener1 and result3AStateListener2 are complete, they will not
+ // receive further updates.
+ listener3A.onPartialCaptureResult(requestMetadata, frameNumber, captureResult1)
+ assertThat(result3AStateListener1.updateCount).isEqualTo(1)
+ assertThat(result3AStateListener2.updateCount).isEqualTo(2)
+ }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/Result3AStateListenerTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/Result3AStateListenerImplTest.kt
similarity index 95%
rename from camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/Result3AStateListenerTest.kt
rename to camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/Result3AStateListenerImplTest.kt
index fac4250..5013b65 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/Result3AStateListenerTest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/Result3AStateListenerImplTest.kt
@@ -32,10 +32,10 @@
@RunWith(CameraPipeRobolectricTestRunner::class)
@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
@OptIn(ExperimentalCoroutinesApi::class)
-class Result3AStateListenerTest {
+class Result3AStateListenerImplTest {
@Test
fun testWithNoUpdate() {
- val listenerForKeys = Result3AStateListener(
+ val listenerForKeys = Result3AStateListenerImpl(
mapOf(
CaptureResult.CONTROL_AF_STATE to
listOf(
@@ -48,7 +48,7 @@
@Test
fun testKeyWithUndesirableValueInFrameMetadata() {
- val listenerForKeys = Result3AStateListener(
+ val listenerForKeys = Result3AStateListenerImpl(
mapOf(
CaptureResult.CONTROL_AF_STATE to
listOf(
@@ -70,7 +70,7 @@
@Test
fun testKeyWithDesirableValueInFrameMetadata() {
- val listenerForKeys = Result3AStateListener(
+ val listenerForKeys = Result3AStateListenerImpl(
mapOf(
CaptureResult.CONTROL_AF_STATE to
listOf(
@@ -91,7 +91,7 @@
@Test
fun testKeyNotPresentInFrameMetadata() {
- val listenerForKeys = Result3AStateListener(
+ val listenerForKeys = Result3AStateListenerImpl(
mapOf(
CaptureResult.CONTROL_AF_STATE to
listOf(
@@ -112,7 +112,7 @@
@Test
fun testMultipleKeysWithDesiredValuesInFrameMetadata() {
- val listenerForKeys = Result3AStateListener(
+ val listenerForKeys = Result3AStateListenerImpl(
mapOf(
CaptureResult.CONTROL_AF_STATE to
listOf(
@@ -136,7 +136,7 @@
@Test
fun testMultipleKeysWithDesiredValuesInFrameMetadataForASubset() {
- val listenerForKeys = Result3AStateListener(
+ val listenerForKeys = Result3AStateListenerImpl(
mapOf(
CaptureResult.CONTROL_AF_STATE to
listOf(
@@ -159,7 +159,7 @@
@Test
fun testMultipleUpdates() {
- val listenerForKeys = Result3AStateListener(
+ val listenerForKeys = Result3AStateListenerImpl(
mapOf(
CaptureResult.CONTROL_AF_STATE to
listOf(
@@ -191,7 +191,7 @@
@Test
fun testTimeLimit() {
- val listenerForKeys = Result3AStateListener(
+ val listenerForKeys = Result3AStateListenerImpl(
exitConditionForKeys = mapOf(
CaptureResult.CONTROL_AF_STATE to
listOf(CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED)
@@ -232,7 +232,7 @@
@Test
fun testFrameLimit() {
- val listenerForKeys = Result3AStateListener(
+ val listenerForKeys = Result3AStateListenerImpl(
exitConditionForKeys = mapOf(
CaptureResult.CONTROL_AF_STATE to
listOf(CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED)
@@ -289,7 +289,7 @@
@Test
fun testIgnoreUpdatesFromEarlierRequests() {
- val listenerForKeys = Result3AStateListener(
+ val listenerForKeys = Result3AStateListenerImpl(
mapOf(
CaptureResult.CONTROL_AF_STATE to
listOf(
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeMetadata.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeMetadata.kt
index 53c8525..1d96974 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeMetadata.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeMetadata.kt
@@ -26,6 +26,7 @@
import android.view.Surface
import androidx.camera.camera2.pipe.CameraId
import androidx.camera.camera2.pipe.CameraMetadata
+import androidx.camera.camera2.pipe.CaptureRequestWrapper
import androidx.camera.camera2.pipe.FrameInfo
import androidx.camera.camera2.pipe.FrameNumber
import androidx.camera.camera2.pipe.Metadata
@@ -156,3 +157,14 @@
)
}
}
+
+/**
+ * A fake [CaptureRequestWrapper].
+ */
+class FakeCaptureRequestWrapper : CaptureRequestWrapper {
+ override fun unwrap(): CaptureRequest? {
+ throw UnsupportedOperationException(
+ "FakeCaptureRequestWrapper does not wrap a real CaptureRequest object"
+ )
+ }
+}
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/UpdateCounting3AStateListener.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/UpdateCounting3AStateListener.kt
new file mode 100644
index 0000000..9351405
--- /dev/null
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/UpdateCounting3AStateListener.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2020 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.testing
+
+import androidx.camera.camera2.pipe.FrameMetadata
+import androidx.camera.camera2.pipe.RequestNumber
+import androidx.camera.camera2.pipe.impl.Result3AStateListener
+
+/**
+ * Wrapper on Result3AStateListenerImpl to keep track of the number of times the update method is
+ * called.
+ */
+class UpdateCounting3AStateListener(
+ private val listener: Result3AStateListener
+) : Result3AStateListener {
+ var updateCount = 0
+
+ override fun onRequestSequenceCreated(requestNumber: RequestNumber) {
+ listener.onRequestSequenceCreated(requestNumber)
+ }
+
+ override fun update(requestNumber: RequestNumber, frameMetadata: FrameMetadata): Boolean {
+ updateCount++
+ return listener.update(requestNumber, frameMetadata)
+ }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/PreviewTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/PreviewTest.java
index 5d75838..c076f83 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/PreviewTest.java
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/PreviewTest.java
@@ -49,6 +49,7 @@
import androidx.camera.core.impl.utils.executor.CameraXExecutors;
import androidx.camera.core.internal.CameraUseCaseAdapter;
import androidx.camera.testing.CameraUtil;
+import androidx.camera.testing.GLUtil;
import androidx.camera.testing.SurfaceTextureProvider;
import androidx.core.util.Consumer;
import androidx.test.core.app.ApplicationProvider;
@@ -492,6 +493,62 @@
assertThat(preview.getTargetRotation()).isEqualTo(Surface.ROTATION_180);
}
+ @Test
+ public void setNullSurfaceProvider_shouldStopPreview() throws InterruptedException {
+ // Arrange.
+ final Preview preview = new Preview.Builder().build();
+
+ // Act.
+ // TODO(b/160261462) move off of main thread when setSurfaceProvider does not need to be
+ // done on the main thread
+ mInstrumentation.runOnMainSync(() ->
+ preview.setSurfaceProvider(CameraXExecutors.mainThreadExecutor(),
+ createSurfaceTextureProvider(
+ new SurfaceTextureProvider.SurfaceTextureCallback() {
+ private boolean mIsReleased = false;
+ @Override
+ public void onSurfaceTextureReady(
+ @NonNull SurfaceTexture surfaceTexture,
+ @NonNull Size resolution) {
+ surfaceTexture.attachToGLContext(
+ GLUtil.getTexIdFromGLContext());
+ surfaceTexture.setOnFrameAvailableListener(st -> {
+ mSurfaceFutureSemaphore.release();
+ synchronized (this) {
+ if (!mIsReleased) {
+ surfaceTexture.updateTexImage();
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onSafeToRelease(
+ @NonNull SurfaceTexture surfaceTexture) {
+ synchronized (this) {
+ mIsReleased = true;
+ surfaceTexture.release();
+ }
+ }
+ })));
+ mCamera = CameraUtil.createCameraAndAttachUseCase(mContext, mCameraSelector, preview);
+
+ // Assert.
+ // Wait until preview gets frame.
+ assertThat(mSurfaceFutureSemaphore.tryAcquire(10, TimeUnit.SECONDS)).isTrue();
+
+ // Act.
+ // TODO(b/160261462) move off of main thread when setSurfaceProvider does not need to be
+ // done on the main thread
+ mInstrumentation.runOnMainSync(() ->
+ preview.setSurfaceProvider(CameraXExecutors.mainThreadExecutor(), null)
+ );
+
+ // Assert.
+ // No frame coming for 3 seconds in 10 seconds timeout.
+ assertThat(noFrameCome(3000L, 10000L)).isTrue();
+ }
+
private Executor getWorkExecutorWithNamedThread() {
final ThreadFactory threadFactory = runnable -> new Thread(runnable, ANY_THREAD_NAME);
return Executors.newSingleThreadExecutor(threadFactory);
@@ -517,4 +574,39 @@
}
});
}
+
+ /*
+ * Check if there is no frame callback for `noFrameIntervalMs` milliseconds, then it will
+ * return true; If the total check time is over `timeoutMs` milliseconds, then it will return
+ * false.
+ */
+ private boolean noFrameCome(long noFrameIntervalMs, long timeoutMs)
+ throws InterruptedException {
+ if (noFrameIntervalMs <= 0 || timeoutMs <= 0) {
+ throw new IllegalArgumentException("Time can't be negative value.");
+ }
+ if (timeoutMs < noFrameIntervalMs) {
+ throw new IllegalArgumentException(
+ "timeoutMs should be larger than noFrameIntervalMs.");
+ }
+ final long checkFrequency = 200L;
+ long totalCheckTime = 0L;
+ long zeroFrameTimer = 0L;
+ do {
+ Thread.sleep(checkFrequency);
+ if (mSurfaceFutureSemaphore.availablePermits() > 0) {
+ // Has frame, reset timer and frame count.
+ zeroFrameTimer = 0;
+ mSurfaceFutureSemaphore.drainPermits();
+ } else {
+ zeroFrameTimer += checkFrequency;
+ }
+ if (zeroFrameTimer > noFrameIntervalMs) {
+ return true;
+ }
+ totalCheckTime += checkFrequency;
+ } while (totalCheckTime < timeoutMs);
+
+ return false;
+ }
}
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/CaptureSessionTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/CaptureSessionTest.java
index 26ffbd2..8a00e17 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/CaptureSessionTest.java
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/CaptureSessionTest.java
@@ -23,6 +23,7 @@
import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -1085,6 +1086,52 @@
assertThat(captureSession.getState()).isEqualTo(State.RELEASED);
}
+ @Test
+ public void setSessionConfigWithoutSurface_shouldStopRepeating()
+ throws ExecutionException, InterruptedException {
+ // Create Surface
+ ImageReader imageReader =
+ ImageReader.newInstance(640, 480, ImageFormat.YUV_420_888, /*maxImages*/ 2);
+ DeferrableSurface surface = new ImmediateSurface(imageReader.getSurface());
+
+ // Prepare SessionConfig builder
+ SessionConfig.Builder builder = new SessionConfig.Builder();
+ builder.setTemplateType(CameraDevice.TEMPLATE_PREVIEW);
+ CameraCaptureSession.StateCallback stateCallback =
+ Mockito.mock(CameraCaptureSession.StateCallback.class);
+ builder.addSessionStateCallback(stateCallback);
+ CameraCaptureCallback captureCallback =
+ Mockito.mock(CameraCaptureCallback.class);
+ builder.addRepeatingCameraCaptureCallback(captureCallback);
+
+ // Create SessionConfig without Surface
+ SessionConfig sessionConfigWithoutSurface = builder.build();
+
+ // Create SessionConfig with Surface
+ builder.addSurface(surface);
+ SessionConfig sessionConfigWithSurface = builder.build();
+
+ // Open CaptureSession
+ CaptureSession captureSession = createCaptureSession();
+ captureSession.open(sessionConfigWithSurface, mCameraDeviceHolder.get(),
+ mCaptureSessionOpenerBuilder.build()).get();
+
+ // Activate repeating request
+ captureSession.setSessionConfig(sessionConfigWithSurface);
+ verify(captureCallback, timeout(3000L).atLeast(3)).onCaptureCompleted(any());
+
+ // Deactivate repeating request
+ clearInvocations(stateCallback);
+ captureSession.setSessionConfig(sessionConfigWithoutSurface);
+
+ // Wait for #onReady which means there is no repeating request.
+ verify(stateCallback, timeout(3000L)).onReady(any());
+
+ // Clean up
+ surface.close();
+ surface.getTerminationFuture().addListener(() -> imageReader.close(),
+ CameraXExecutors.directExecutor());
+ }
/**
* A implementation to test {@link CameraEventCallback} on CaptureSession.
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CaptureSession.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CaptureSession.java
index 5885d6b..7418b99 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CaptureSession.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CaptureSession.java
@@ -553,7 +553,7 @@
/**
* Sets the {@link CaptureRequest} so that the camera will start producing data.
*
- * <p>Will skip setting requests if there are no surfaces since it is illegal to do so.
+ * <p>It will stop running repeating if there are no surfaces.
*/
@SuppressWarnings("WeakerAccess") /* synthetic accessor */
@GuardedBy("mStateLock")
@@ -566,6 +566,15 @@
CaptureConfig captureConfig = mSessionConfig.getRepeatingCaptureConfig();
if (captureConfig.getSurfaces().isEmpty()) {
Logger.d(TAG, "Skipping issueRepeatingCaptureRequests for no surface.");
+ try {
+ // At least from Android L, framework will ignore the stopRepeating() if
+ // there is no ongoing repeating request, so it should be safe to always call
+ // stopRepeating() without checking if there is a repeating request.
+ mSynchronizedCaptureSession.stopRepeating();
+ } catch (CameraAccessException e) {
+ Logger.e(TAG, "Unable to access camera: " + e.getMessage());
+ Thread.dumpStack();
+ }
return;
}
diff --git a/camera/camera-view/src/androidTest/java/androidx/camera/view/preview/transform/PreviewTransformTest.java b/camera/camera-view/src/androidTest/java/androidx/camera/view/preview/transform/PreviewTransformTest.java
deleted file mode 100644
index f3c94c7..0000000
--- a/camera/camera-view/src/androidTest/java/androidx/camera/view/preview/transform/PreviewTransformTest.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright 2020 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.view.preview.transform;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.Context;
-import android.util.Size;
-import android.view.View;
-import android.widget.FrameLayout;
-
-import androidx.camera.testing.fakes.FakeActivity;
-import androidx.camera.view.PreviewView;
-import androidx.core.content.ContextCompat;
-import androidx.test.annotation.UiThreadTest;
-import androidx.test.rule.ActivityTestRule;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-
-public class PreviewTransformTest {
-
- private static final Size CONTAINER = new Size(800, 450);
- private static final Size BUFFER = new Size(400, 300);
-
- @Rule
- public final ActivityTestRule<FakeActivity> mActivityTestRule = new ActivityTestRule<>(
- FakeActivity.class);
-
- private FrameLayout mContainer;
- private View mView;
-
- @Before
- public void setUp() throws Throwable {
- mActivityTestRule.runOnUiThread(() -> {
- final Context context = mActivityTestRule.getActivity();
-
- final FrameLayout root = new FrameLayout(context);
-
- mContainer = new FrameLayout(context);
- mContainer.setLayoutParams(
- new FrameLayout.LayoutParams(CONTAINER.getWidth(), CONTAINER.getHeight()));
-
- mView = new View(context);
- mView.setLayoutParams(
- new FrameLayout.LayoutParams(BUFFER.getWidth(), BUFFER.getHeight()));
- mView.setBackgroundColor(
- ContextCompat.getColor(context, android.R.color.holo_blue_dark));
-
- mContainer.addView(mView);
- root.addView(mContainer);
- mActivityTestRule.getActivity().setContentView(root);
- });
- }
-
- @Test
- @UiThreadTest
- public void fillStart() {
- final PreviewTransform previewTransform = new PreviewTransform();
- previewTransform.setScaleType(PreviewView.ScaleType.FILL_START);
- previewTransform.applyCurrentScaleType(mContainer, mView, BUFFER);
-
- // Assert the preview fills its container
- assertThat(mView.getWidth() * mView.getScaleX()).isAtLeast(mContainer.getWidth());
- assertThat(mView.getHeight() * mView.getScaleY()).isAtLeast(mContainer.getHeight());
- }
-
- @Test
- @UiThreadTest
- public void fillCenter() {
- final PreviewTransform previewTransform = new PreviewTransform();
- previewTransform.setScaleType(PreviewView.ScaleType.FILL_CENTER);
- previewTransform.applyCurrentScaleType(mContainer, mView, BUFFER);
-
- // Assert the preview fills its container
- assertThat(mView.getWidth() * mView.getScaleX()).isAtLeast(mContainer.getWidth());
- assertThat(mView.getHeight() * mView.getScaleY()).isAtLeast(mContainer.getHeight());
- }
-
- @Test
- @UiThreadTest
- public void fillEnd() {
- final PreviewTransform previewTransform = new PreviewTransform();
- previewTransform.setScaleType(PreviewView.ScaleType.FILL_END);
- previewTransform.applyCurrentScaleType(mContainer, mView, BUFFER);
-
- // Assert the preview fills its container
- assertThat(mView.getWidth() * mView.getScaleX()).isAtLeast(mContainer.getWidth());
- assertThat(mView.getHeight() * mView.getScaleY()).isAtLeast(mContainer.getHeight());
- }
-}
diff --git a/camera/camera-view/src/androidTest/java/androidx/camera/view/preview/transform/transformation/TransformationDeviceTest.java b/camera/camera-view/src/androidTest/java/androidx/camera/view/preview/transform/transformation/TransformationDeviceTest.java
deleted file mode 100644
index df86666..0000000
--- a/camera/camera-view/src/androidTest/java/androidx/camera/view/preview/transform/transformation/TransformationDeviceTest.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright 2020 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.view.preview.transform.transformation;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.app.Activity;
-import android.view.View;
-
-import androidx.camera.testing.fakes.FakeActivity;
-import androidx.test.annotation.UiThreadTest;
-import androidx.test.rule.ActivityTestRule;
-
-import org.junit.Rule;
-import org.junit.Test;
-
-public class TransformationDeviceTest {
-
- @Rule
- public final ActivityTestRule<FakeActivity> mRule = new ActivityTestRule<>(FakeActivity.class);
-
- @Test
- @UiThreadTest
- public void getTransformationFromView() {
- final Activity activity = mRule.getActivity();
- final View view = new View(activity);
- view.setScaleX(2F);
- view.setScaleY(0.5F);
- view.setTranslationX(100);
- view.setTranslationY(35.5F);
- view.setRotation(3.14F);
- activity.setContentView(view);
-
- final Transformation transformation = Transformation.getTransformation(view);
-
- assertThat(transformation.getScaleX()).isEqualTo(2F);
- assertThat(transformation.getScaleY()).isEqualTo(0.5F);
- assertThat(transformation.getTransX()).isEqualTo(100);
- assertThat(transformation.getTransY()).isEqualTo(35.5F);
- assertThat(transformation.getRotation()).isEqualTo(3.14F);
- }
-}
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/PreviewCorrector.java b/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/PreviewCorrector.java
deleted file mode 100644
index 2d7418a..0000000
--- a/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/PreviewCorrector.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright 2020 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.view.preview.transform;
-
-import android.util.Pair;
-import android.util.Size;
-import android.view.Surface;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.camera.view.preview.transform.transformation.PreviewCorrectionTransformation;
-
-/** Corrects a camera preview by scaling and/or rotating it so that it matches the display. */
-final class PreviewCorrector {
-
- private PreviewCorrector() {
- }
-
- /**
- * Corrects a camera preview by scaling and rotating it.
- *
- * @param container Preview container
- * @param preview Preview view (a {@link android.view.TextureView} or
- * {@link android.view.SurfaceView})
- * @param bufferSize Camera output size
- * @param sensorDimensionFlipNeeded True if the sensor x and y dimensions need to be flipped.
- * @param deviceRotation If the app is not running in remote display mode, set the
- * parameter as {@link RotationTransform#ROTATION_AUTOMATIC}.
- * Then, the rotation value queried from the preview will be
- * used to do the transformation calculations. If the app is
- * running in remote display mode, the device rotation value
- * needs to be provided to make the result be rotated into
- * correct orientation. The device rotation should be obtained
- * from {@link android.view.OrientationEventListener} and
- * needs to be converted into {@link Surface#ROTATION_0},
- * {@link Surface#ROTATION_90}, {@link Surface#ROTATION_180}
- * , or {@link Surface#ROTATION_270}.
- */
- @NonNull
- static PreviewCorrectionTransformation getCorrectionTransformation(
- @NonNull final View container, @NonNull final View preview,
- @NonNull final Size bufferSize, final boolean sensorDimensionFlipNeeded,
- final int deviceRotation) {
- final int rotation = (int) RotationTransform.getRotationDegrees(preview, deviceRotation);
- final Pair<Float, Float> scaleXY = getCorrectionScale(container, preview,
- bufferSize, sensorDimensionFlipNeeded);
- return new PreviewCorrectionTransformation(scaleXY.first, scaleXY.second, -rotation);
- }
-
- /**
- * Computes the scales on both the x and y axes so that the preview can be corrected.
- *
- * @param container Preview container
- * @param preview Preview view (a {@link android.view.TextureView} or
- * {@link android.view.SurfaceView})
- * @param bufferSize Camera output size
- * @param sensorDimensionFlipNeeded True if the sensor x and y dimensions need to be flipped.
- * @return The scales on both the x and y axes so that the preview can be corrected.
- */
- private static Pair<Float, Float> getCorrectionScale(@NonNull final View container,
- @NonNull final View preview, @NonNull final Size bufferSize,
- final boolean sensorDimensionFlipNeeded) {
-
- // Scaling only makes sense when none of the dimensions are equal to zero. In the
- // opposite case, a default scale of 1 is returned,
- if (container.getWidth() == 0 || container.getHeight() == 0 || preview.getWidth() == 0
- || preview.getHeight() == 0 || bufferSize.getWidth() == 0
- || bufferSize.getHeight() == 0) {
- return new Pair<>(1F, 1F);
- }
-
- final int bufferWidth;
- final int bufferHeight;
- if (sensorDimensionFlipNeeded) {
- bufferWidth = bufferSize.getHeight();
- bufferHeight = bufferSize.getWidth();
- } else {
- bufferWidth = bufferSize.getWidth();
- bufferHeight = bufferSize.getHeight();
- }
-
- // Scale the buffers back to the original output size.
- float scaleX = bufferWidth / (float) preview.getWidth();
- float scaleY = bufferHeight / (float) preview.getHeight();
- return new Pair<>(scaleX, scaleY);
- }
-}
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/PreviewTransform.java b/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/PreviewTransform.java
deleted file mode 100644
index aedad72..0000000
--- a/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/PreviewTransform.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright 2020 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.view.preview.transform;
-
-import static androidx.camera.view.preview.transform.transformation.Transformation.getTransformation;
-
-import android.util.Size;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-import androidx.camera.core.impl.ImageOutputConfig;
-import androidx.camera.view.PreviewView;
-import androidx.camera.view.preview.transform.transformation.Transformation;
-
-/**
- * Transforms the camera preview using a supported {@link PreviewView.ScaleType}.
- * <p>
- * Holds a reference to the {@link PreviewView.ScaleType} that's being applied to the preview.
- * This attribute is used by both {@link PreviewView} (see {@link PreviewView#getScaleType()} and
- * {@link PreviewView#setScaleType(PreviewView.ScaleType)}) and its implementation (when
- * correcting the preview and updating the {@link PreviewView.ScaleType}).
- *
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
-public final class PreviewTransform {
-
- private static final PreviewView.ScaleType DEFAULT_SCALE_TYPE =
- PreviewView.ScaleType.FILL_CENTER;
-
- @NonNull
- private PreviewView.ScaleType mScaleType = DEFAULT_SCALE_TYPE;
-
- @Nullable
- private Transformation mCurrentTransformation;
-
- private boolean mSensorDimensionFlipNeeded = true;
-
- private int mDeviceRotation = RotationTransform.ROTATION_AUTOMATIC;
-
- /** Returns the {@link PreviewView.ScaleType} currently applied to the preview. */
- @NonNull
- public PreviewView.ScaleType getScaleType() {
- return mScaleType;
- }
-
- /** Sets the {@link PreviewView.ScaleType} to be applied to the preview. */
- public void setScaleType(@NonNull PreviewView.ScaleType scaleType) {
- mScaleType = scaleType;
- }
-
- /**
- * Returns the current {@link Transformation} applied to the preview, or {@code null} if the
- * preview hasn't started yet.
- */
- @Nullable
- public Transformation getCurrentTransformation() {
- return mCurrentTransformation;
- }
-
- /** Returns whether the device sensor x and y dimensions need to be flipped. */
- public boolean isSensorDimensionFlipNeeded() {
- return mSensorDimensionFlipNeeded;
- }
-
- /**
- * Sets whether the target device sensor dimensions need to be flipped when calculating the
- * transform.
- */
- public void setSensorDimensionFlipNeeded(boolean sensorDimensionFlipNeeded) {
- mSensorDimensionFlipNeeded = sensorDimensionFlipNeeded;
- }
-
- /** Returns current device rotation value. */
- public int getDeviceRotation() {
- return mDeviceRotation;
- }
-
- /**
- * Sets the device rotation value that will affect the transform calculations.
- */
- public void setDeviceRotation(@ImageOutputConfig.RotationValue int deviceRotation) {
- mDeviceRotation = deviceRotation;
- }
-
- /** Applies the current {@link PreviewView.ScaleType} on the passed in preview. */
- public void applyCurrentScaleType(@NonNull final View container, @NonNull final View view,
- @NonNull final Size bufferSize) {
- resetPreview(view);
- correctPreview(container, view, bufferSize);
- applyScaleTypeInternal(container, view, mScaleType, mDeviceRotation);
- }
-
- private void resetPreview(@NonNull View view) {
- final Transformation reset = new Transformation();
- applyTransformation(view, reset);
- }
-
- /** Corrects the preview. */
- private void correctPreview(@NonNull final View container, @NonNull final View view,
- @NonNull final Size bufferSize) {
- final Transformation correct = PreviewCorrector.getCorrectionTransformation(container, view,
- bufferSize, mSensorDimensionFlipNeeded, mDeviceRotation);
- applyTransformation(view, correct);
- }
-
- /** Applies the specified {@link PreviewView.ScaleType} on top of the corrected preview. */
- private void applyScaleTypeInternal(@NonNull final View container,
- @NonNull final View view, @NonNull final PreviewView.ScaleType scaleType,
- final int deviceRotation) {
- final Transformation current = getTransformation(view);
- final Transformation transformation = ScaleTypeTransform.getTransformation(container, view,
- scaleType, deviceRotation);
- applyTransformation(view, current.add(transformation));
- }
-
- /**
- * Applies a {@link Transformation} on the passed in preview while overriding any previous
- * preview {@linkplain Transformation transformations}
- */
- private void applyTransformation(@NonNull final View view,
- @NonNull final Transformation transformation) {
- view.setX(0);
- view.setY(0);
- view.setScaleX(transformation.getScaleX());
- view.setScaleY(transformation.getScaleY());
- view.setTranslationX(transformation.getTransX());
- view.setTranslationY(transformation.getTransY());
- view.setRotation(transformation.getRotation());
-
- mCurrentTransformation = transformation;
- }
-}
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/RotationTransform.java b/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/RotationTransform.java
deleted file mode 100644
index 624c056..0000000
--- a/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/RotationTransform.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright 2020 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.view.preview.transform;
-
-import android.view.Display;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-
-final class RotationTransform {
- // Queries rotation value from view automatically.
- static final int ROTATION_AUTOMATIC = -1;
-
- private RotationTransform() {
- }
-
- /**
- * Computes the rotation in degrees from its natural orientation.
- *
- * <p>When a application is running in remote display, the correct rotation degrees should
- * be obtained from the device rotation. The rotation from the {@link View} may cause incorrect
- * transform calculation results.
- *
- * @param view The {@link View} used to show the preview.
- * @param deviceRotation The device rotation value provided by the developers.
- * @return The rotation in degrees from its natural orientation.
- */
- static float getRotationDegrees(@NonNull final View view, final int deviceRotation) {
- if (deviceRotation != ROTATION_AUTOMATIC) {
- return SurfaceRotation.rotationDegreesFromSurfaceRotation(deviceRotation);
- } else {
- return getRotationDegrees(view);
- }
- }
-
- /** Computes the rotation of a {@link View} in degrees from its natural orientation. */
- static float getRotationDegrees(@NonNull final View view) {
- final Display display = view.getDisplay();
- if (display == null) {
- return 0;
- }
- final int rotation = display.getRotation();
- return SurfaceRotation.rotationDegreesFromSurfaceRotation(rotation);
- }
-}
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/ScaleTransform.java b/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/ScaleTransform.java
deleted file mode 100644
index 2b2b6f4..0000000
--- a/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/ScaleTransform.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright 2020 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.view.preview.transform;
-
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.camera.view.preview.transform.transformation.ScaleTransformation;
-
-/**
- * Computes the scale on both the x and y axes to uniformly scale up or down a view inside its
- * container, so that it entirely fills it, or is entirely container within it.
- */
-final class ScaleTransform {
-
- private ScaleTransform() {
- }
-
- /**
- * Computes the scale on both the x and y axes so that the view can uniformly fill its
- * container.
- */
- static ScaleTransformation fill(@NonNull final View container, @NonNull final View view,
- final int deviceRotation) {
- return computeScale(container, view, Math::max, deviceRotation);
- }
-
- /**
- * Computes the scale on both the x and y axes so that the view can uniformly fit inside its
- * container.
- */
- static ScaleTransformation fit(@NonNull final View container, @NonNull final View view,
- final int deviceRotation) {
- return computeScale(container, view, Math::min, deviceRotation);
- }
-
- private static ScaleTransformation computeScale(@NonNull final View container,
- @NonNull final View view, @NonNull final FloatBiFunction function,
- final int deviceRotation) {
- // Scaling only makes sense when none of the dimensions are equal to zero. In the
- // opposite case, a default scale of 1 is returned,
- if (container.getWidth() == 0 || container.getHeight() == 0 || view.getWidth() == 0
- || view.getHeight() == 0) {
- return new ScaleTransformation(1);
- }
-
- final int rotationDegrees = (int) RotationTransform.getRotationDegrees(view,
- deviceRotation);
- float bufferRotatedWidth;
- float bufferRotatedHeight;
- if (rotationDegrees == 0 || rotationDegrees == 180) {
- bufferRotatedWidth = view.getWidth() * view.getScaleX();
- bufferRotatedHeight = view.getHeight() * view.getScaleY();
- } else {
- bufferRotatedWidth = view.getHeight() * view.getScaleY();
- bufferRotatedHeight = view.getWidth() * view.getScaleX();
- }
-
- final float scale = function.apply(container.getWidth() / bufferRotatedWidth,
- container.getHeight() / bufferRotatedHeight);
- return new ScaleTransformation(scale);
- }
-
- private interface FloatBiFunction {
- float apply(float a, float b);
- }
-}
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/ScaleTypeTransform.java b/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/ScaleTypeTransform.java
deleted file mode 100644
index 85bf472..0000000
--- a/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/ScaleTypeTransform.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright 2020 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.view.preview.transform;
-
-import android.util.Pair;
-import android.view.Surface;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.camera.view.PreviewView;
-import androidx.camera.view.preview.transform.transformation.ScaleTransformation;
-import androidx.camera.view.preview.transform.transformation.Transformation;
-import androidx.camera.view.preview.transform.transformation.TranslationTransformation;
-
-final class ScaleTypeTransform {
-
- private ScaleTypeTransform() {
- }
-
- /**
- * Converts a {@link PreviewView.ScaleType} to a {@link Transformation}.
- *
- * @param container Preview container
- * @param view Preview view (a {@link android.view.TextureView} or
- * {@link android.view.SurfaceView})
- * @param scaleType The desired {@link PreviewView.ScaleType}.
- * @param deviceRotation If the app is not running in remote display mode, set the parameter
- * as {@link RotationTransform#ROTATION_AUTOMATIC}. Then, the rotation
- * value queried from the preview will be used to do the transformation
- * calculations. If the app is running in remote display mode, the
- * device rotation value needs to be provided to make the result be
- * rotated into correct orientation. The device rotation should be
- * obtained from {@link android.view.OrientationEventListener} and
- * needs to be converted into {@link Surface#ROTATION_0},
- * {@link Surface#ROTATION_90}, {@link Surface#ROTATION_180}, or
- * {@link Surface#ROTATION_270}.
- */
- static Transformation getTransformation(@NonNull final View container, @NonNull final View view,
- @NonNull final PreviewView.ScaleType scaleType, final int deviceRotation) {
- final Transformation scale = getScale(container, view, scaleType, deviceRotation);
-
- // Use the current preview scale AND the scale that's about to be applied to it to figure
- // out how to position the preview in its container
- final Pair<Float, Float> scaleXY = new Pair<>(view.getScaleX() * scale.getScaleX(),
- view.getScaleY() * scale.getScaleY());
- final Transformation translation = getScaledTranslation(container, view, scaleType,
- scaleXY, deviceRotation);
-
- return scale.add(translation);
- }
-
- private static ScaleTransformation getScale(@NonNull final View container,
- @NonNull final View view, @NonNull final PreviewView.ScaleType scaleType,
- final int deviceRotation) {
- switch (scaleType) {
- case FILL_START:
- case FILL_CENTER:
- case FILL_END:
- return ScaleTransform.fill(container, view, deviceRotation);
- case FIT_START:
- case FIT_CENTER:
- case FIT_END:
- return ScaleTransform.fit(container, view, deviceRotation);
- default:
- throw new IllegalArgumentException("Unknown scale type " + scaleType);
- }
- }
-
- private static TranslationTransformation getScaledTranslation(@NonNull final View container,
- @NonNull final View view, @NonNull final PreviewView.ScaleType scaleType,
- @NonNull final Pair<Float, Float> scaleXY, final int deviceRotation) {
- switch (scaleType) {
- case FILL_START:
- case FIT_START:
- return TranslationTransform.start(view, scaleXY, deviceRotation);
- case FILL_CENTER:
- case FIT_CENTER:
- return TranslationTransform.center(container, view);
- case FILL_END:
- case FIT_END:
- return TranslationTransform.end(container, view, scaleXY, deviceRotation);
- default:
- throw new IllegalArgumentException("Unknown scale type " + scaleType);
- }
- }
-}
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/SurfaceRotation.java b/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/SurfaceRotation.java
deleted file mode 100644
index 6428107..0000000
--- a/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/SurfaceRotation.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright 2020 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.view.preview.transform;
-
-import android.view.Surface;
-
-final class SurfaceRotation {
-
- private SurfaceRotation() {
- }
-
- /**
- * Get the degrees of a rotation from the {@link Surface} constants.
- *
- * <p>
- * Valid values for the relative rotation are {@link Surface#ROTATION_0}, {@link
- * Surface#ROTATION_90}, {@link Surface#ROTATION_180},
- * {@link Surface#ROTATION_270}.
- * </p>
- */
- static int rotationDegreesFromSurfaceRotation(final int rotationConstant) {
- switch (rotationConstant) {
- case Surface.ROTATION_0:
- return 0;
- case Surface.ROTATION_90:
- return 90;
- case Surface.ROTATION_180:
- return 180;
- case Surface.ROTATION_270:
- return 270;
- default:
- throw new UnsupportedOperationException(
- "Unsupported surface rotation constant: " + rotationConstant);
- }
- }
-}
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/TranslationTransform.java b/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/TranslationTransform.java
deleted file mode 100644
index 0be399d..0000000
--- a/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/TranslationTransform.java
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright 2020 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.view.preview.transform;
-
-import android.util.Pair;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.camera.view.preview.transform.transformation.TranslationTransformation;
-
-/**
- * Computes the horizontal and vertical translations by which the preview needs to be translated
- * to position it at the start, center or end of its parent.
- * <p>
- * The start represents the top left corner in a left-to-right (LTR) layout, or the top right
- * corner in a right-to-left (RTL) layout.
- * <p>
- * The end represents the bottom right corner in a left-to-right (LTR) layout, or the bottom left
- * corner in a right-to-left (RTL) layout.
- */
-final class TranslationTransform {
-
- private TranslationTransform() {
- }
-
- /**
- * Computes the horizontal and vertical translations to set on {@code view} to align it to the
- * start of its parent {@code container}.
- * <p>
- * The start represents the top left corner in a left-to-right (LTR) layout, or the top right
- * corner in a right-to-left (RTL) layout.
- */
- static TranslationTransformation start(@NonNull final View view,
- @NonNull final Pair<Float, Float> scaleXY, final int deviceRotation) {
- if (view.getWidth() == 0 || view.getHeight() == 0) {
- return new TranslationTransformation(0, 0);
- }
-
- // Scaled width and height of the view
- final int scaledWidth = (int) (view.getWidth() * scaleXY.first);
- final int scaledHeight = (int) (view.getHeight() * scaleXY.second);
-
- final int viewRotationDegrees = (int) RotationTransform.getRotationDegrees(view,
- deviceRotation);
- final boolean isPortrait = viewRotationDegrees == 0 || viewRotationDegrees == 180;
-
- // Coordinates of the view's center after the `start` translation
- final int targetCenterX;
- final int targetCenterY;
- if (isPortrait) {
- targetCenterX = scaledWidth / 2;
- targetCenterY = scaledHeight / 2;
- } else {
- targetCenterX = scaledHeight / 2;
- targetCenterY = scaledWidth / 2;
- }
-
- // Current coordinates of the view's center
- final int currentCenterX = view.getWidth() / 2;
- final int currentCenterY = view.getHeight() / 2;
-
- final int transX = reverseIfRTLLayout(view, targetCenterX - currentCenterX);
- final int transY = targetCenterY - currentCenterY;
- return new TranslationTransformation(transX, transY);
- }
-
- /**
- * Computes the horizontal and vertical translations to set on {@code view} to center it in its
- * parent {@code container}.
- */
- static TranslationTransformation center(@NonNull final View container,
- @NonNull final View view) {
- if (view.getWidth() == 0 || view.getHeight() == 0) {
- return new TranslationTransformation(0, 0);
- }
-
- // Coordinates of the view's center after the `center` translation
- final int targetCenterX = container.getWidth() / 2;
- final int targetCenterY = container.getHeight() / 2;
-
- // Current coordinates of the view's center
- final int currentCenterX = view.getWidth() / 2;
- final int currentCenterY = view.getHeight() / 2;
-
- final int transX = reverseIfRTLLayout(view, targetCenterX - currentCenterX);
- final int transY = targetCenterY - currentCenterY;
- return new TranslationTransformation(transX, transY);
- }
-
- /**
- * Computes the horizontal and vertical translations to set on {@code view} to align it to the
- * end of its parent {@code container}.
- * <p>
- * The end represents the bottom right corner in a left-to-right (LTR) layout, or the bottom
- * left corner in a right-to-left (RTL) layout.
- */
- static TranslationTransformation end(@NonNull final View container, @NonNull final View view,
- @NonNull final Pair<Float, Float> scaleXY, final int deviceRotation) {
- if (view.getWidth() == 0 || view.getHeight() == 0) {
- return new TranslationTransformation(0, 0);
- }
-
- // Coordinates of the bottom right corner of the container
- final int endX = container.getWidth();
- final int endY = container.getHeight();
-
- // Scaled width and height of the view
- final int scaledWidth = (int) (view.getWidth() * scaleXY.first);
- final int scaledHeight = (int) (view.getHeight() * scaleXY.second);
-
- final int viewRotationDegrees = (int) RotationTransform.getRotationDegrees(view,
- deviceRotation);
- final boolean isPortrait = viewRotationDegrees == 0 || viewRotationDegrees == 180;
-
- // Coordinates of the view's center after the `end` translation
- final int targetCenterX;
- final int targetCenterY;
- if (isPortrait) {
- targetCenterX = endX - (scaledWidth / 2);
- targetCenterY = endY - (scaledHeight / 2);
- } else {
- targetCenterX = endX - (scaledHeight / 2);
- targetCenterY = endY - (scaledWidth / 2);
- }
-
- // Current coordinates of the view's center
- final int currentCenterX = view.getWidth() / 2;
- final int currentCenterY = view.getHeight() / 2;
-
- final int transX = reverseIfRTLLayout(view, targetCenterX - currentCenterX);
- final int transY = targetCenterY - currentCenterY;
- return new TranslationTransformation(transX, transY);
- }
-
- /**
- * Reverses a horizontal translation if the {@code view} is in a right-to-left (RTL) layout.
- *
- * @return The passed in horizontal translation if the layout is left-to-right (LTR), or its
- * reverse if the layout is right-to-left (RTL).
- */
- private static int reverseIfRTLLayout(@NonNull final View view, int transX) {
- final boolean isRTLDirection = view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
- return isRTLDirection ? -transX : transX;
- }
-}
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/transformation/PreviewCorrectionTransformation.java b/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/transformation/PreviewCorrectionTransformation.java
deleted file mode 100644
index 6ab11f7..0000000
--- a/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/transformation/PreviewCorrectionTransformation.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2020 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.view.preview.transform.transformation;
-
-
-import androidx.annotation.RestrictTo;
-
-/**
- * A {@link Transformation} used to correct the camera preview.
- *
- * It only allows setting the scale on the x and y axes and the rotation, since these are the
- * operations involved when correcting the preview.
- *
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
-public class PreviewCorrectionTransformation extends Transformation {
-
- /**
- * Creates a {@link Transformation} that corrects the preview. It doesn't translate the
- * preview, keeping it at its initial position of (0, 0).
- */
- public PreviewCorrectionTransformation(float scaleX, float scaleY, float rotation) {
- super(scaleX, scaleY, 0, 0, rotation);
- }
-}
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/transformation/ScaleTransformation.java b/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/transformation/ScaleTransformation.java
deleted file mode 100644
index da113f0..0000000
--- a/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/transformation/ScaleTransformation.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright 2020 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.view.preview.transform.transformation;
-
-import androidx.annotation.RestrictTo;
-
-/**
- * A {@link Transformation} used for scaling on the x and y axes.
- *
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
-public final class ScaleTransformation extends Transformation {
-
- public ScaleTransformation(float scale) {
- super(scale, scale, 0, 0, 0);
- }
-}
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/transformation/Transformation.java b/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/transformation/Transformation.java
deleted file mode 100644
index d3bf25c..0000000
--- a/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/transformation/Transformation.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright 2020 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.view.preview.transform.transformation;
-
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RestrictTo;
-
-/**
- * Contains the required information to transform a camera preview.
- *
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
-public class Transformation {
-
- private final float mScaleX;
- private final float mScaleY;
- private final float mTransX;
- private final float mTransY;
- private final float mRotation;
-
- public Transformation() {
- this(1, 1, 0, 0, 0);
- }
-
- public Transformation(final float scaleX, final float scaleY, final float transX,
- final float transY, final float rotation) {
- this.mScaleX = scaleX;
- this.mScaleY = scaleY;
- this.mTransX = transX;
- this.mTransY = transY;
- this.mRotation = rotation;
- }
-
- public float getScaleX() {
- return mScaleX;
- }
-
- public float getScaleY() {
- return mScaleY;
- }
-
- public float getTransX() {
- return mTransX;
- }
-
- public float getTransY() {
- return mTransY;
- }
-
- public float getRotation() {
- return mRotation;
- }
-
- /** Performs the `sum` of two {@link Transformation} instances. */
- @NonNull
- public Transformation add(@NonNull final Transformation other) {
- return new Transformation(mScaleX * other.mScaleX,
- mScaleY * other.mScaleY,
- mTransX + other.mTransX,
- mTransY + other.mTransY,
- mRotation + other.mRotation);
- }
-
- /** Performs the `subtraction` of two {@link Transformation} instances. */
- @NonNull
- public Transformation subtract(@NonNull final Transformation other) {
- return new Transformation(mScaleX / other.mScaleX,
- mScaleY / other.mScaleY,
- mTransX - other.mTransX,
- mTransY - other.mTransY,
- mRotation - other.mRotation);
- }
-
- /** Returns a {@link Transformation} that represents the current state of the {@link View}. */
- @NonNull
- public static Transformation getTransformation(@NonNull final View view) {
- return new Transformation(view.getScaleX(), view.getScaleY(), view.getTranslationX(),
- view.getTranslationY(), view.getRotation());
- }
-}
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/transformation/TranslationTransformation.java b/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/transformation/TranslationTransformation.java
deleted file mode 100644
index 4b214ab..0000000
--- a/camera/camera-view/src/main/java/androidx/camera/view/preview/transform/transformation/TranslationTransformation.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright 2020 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.view.preview.transform.transformation;
-
-
-import androidx.annotation.RestrictTo;
-
-/**
- * A {@link Transformation} used for positioning.
- *
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
-public final class TranslationTransformation extends Transformation {
-
- public TranslationTransformation(float transX, float transY) {
- super(1, 1, transX, transY, 0);
- }
-}
diff --git a/camera/camera-view/src/test/java/androidx/camera/view/PreviewViewTest.java b/camera/camera-view/src/test/java/androidx/camera/view/PreviewViewTest.java
deleted file mode 100644
index ee238c9..0000000
--- a/camera/camera-view/src/test/java/androidx/camera/view/PreviewViewTest.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright 2020 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.view;
-
-import static android.os.Looper.getMainLooper;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assume.assumeTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
-import static org.robolectric.Shadows.shadowOf;
-
-import android.content.Context;
-import android.os.Build;
-import android.util.Size;
-import android.view.WindowManager;
-
-import androidx.camera.core.CameraInfo;
-import androidx.camera.core.Preview;
-import androidx.camera.core.SurfaceRequest;
-import androidx.camera.core.impl.CameraInfoInternal;
-import androidx.camera.testing.fakes.FakeCamera;
-import androidx.test.core.app.ApplicationProvider;
-
-import org.junit.After;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.annotation.Config;
-import org.robolectric.annotation.internal.DoNotInstrument;
-import org.robolectric.shadows.ShadowDisplayManager;
-
-@RunWith(RobolectricTestRunner.class)
-@DoNotInstrument
-@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
-public class PreviewViewTest {
-
- private final Context mContext = ApplicationProvider.getApplicationContext();
-
- private SurfaceRequest mSurfaceRequest;
-
- private SurfaceRequest createSurfaceRequest(CameraInfo cameraInfo) {
- FakeCamera fakeCamera = spy(new FakeCamera());
- when(fakeCamera.getCameraInfo()).thenReturn(cameraInfo);
- return new SurfaceRequest(new Size(640, 480), fakeCamera, false);
- }
-
- @After
- public void tearDown() {
- if (mSurfaceRequest != null) {
- mSurfaceRequest.willNotProvideSurface();
- // Ensure all successful requests have their returned future finish.
- mSurfaceRequest.getDeferrableSurface().close();
- mSurfaceRequest = null;
- }
- }
-
- private CameraInfoInternal createCameraInfo(String implementationType) {
- final CameraInfoInternal cameraInfo = mock(CameraInfoInternal.class);
- when(cameraInfo.getImplementationType()).thenReturn(implementationType);
- return cameraInfo;
- }
-
- @Test
- public void forceUseTextureViewMode_whenNonLegacyDevice_andInRemoteDisplayMode() {
- // The remote display simulation can only work from sdk 21 to 25. Limit to 24 because the
- // test may be failed when running presubmit in API level 25.
- assumeTrue(Build.VERSION.SDK_INT <= 24);
- final PreviewView previewView = new PreviewView(mContext);
-
- // Provides mock CameraInfo to make the device return non-legacy type.
- final CameraInfo cameraInfo = createCameraInfo(CameraInfo.IMPLEMENTATION_TYPE_CAMERA2);
-
- // Simulates the remote display mode by adding an additional display and returns the
- // second display's id when PreviewView is querying the default display's id.
- int secondDisplayId = addNewDisplay();
- WindowManager windowManager =
- (WindowManager) ApplicationProvider.getApplicationContext().getSystemService(
- Context.WINDOW_SERVICE);
- shadowOf(windowManager.getDefaultDisplay()).setDisplayId(secondDisplayId);
-
- previewView.setImplementationMode(PreviewView.ImplementationMode.PERFORMANCE);
- Preview.SurfaceProvider surfaceProvider = previewView.getSurfaceProvider();
- mSurfaceRequest = createSurfaceRequest(cameraInfo);
- surfaceProvider.onSurfaceRequested(mSurfaceRequest);
- shadowOf(getMainLooper()).idle();
- assertThat(previewView.mImplementation).isInstanceOf(TextureViewImplementation.class);
- }
-
- @Test
- public void forceUseTextureViewMode_whenLegacyDevice_andInRemoteDisplayMode() {
- // The remote display simulation can only work from sdk 21 to 25. Limit to 24 because the
- // test may be failed when running presubmit in API level 25.
- assumeTrue(Build.VERSION.SDK_INT <= 24);
- final PreviewView previewView = new PreviewView(mContext);
-
- // Provides mock CameraInfo to make the device return legacy type.
- final CameraInfo cameraInfo = createCameraInfo(
- CameraInfo.IMPLEMENTATION_TYPE_CAMERA2_LEGACY);
-
- // Simulates the remote display mode by adding an additional display and returns the
- // second display's id when PreviewView is querying the default display's id.
- int secondDisplayId = addNewDisplay();
- WindowManager windowManager =
- (WindowManager) ApplicationProvider.getApplicationContext().getSystemService(
- Context.WINDOW_SERVICE);
- shadowOf(windowManager.getDefaultDisplay()).setDisplayId(secondDisplayId);
-
- previewView.setImplementationMode(PreviewView.ImplementationMode.PERFORMANCE);
- Preview.SurfaceProvider surfaceProvider = previewView.getSurfaceProvider();
- mSurfaceRequest = createSurfaceRequest(cameraInfo);
- surfaceProvider.onSurfaceRequested(mSurfaceRequest);
- shadowOf(getMainLooper()).idle();
- assertThat(previewView.mImplementation).isInstanceOf(TextureViewImplementation.class);
- }
-
- private int addNewDisplay() {
- String displayQualifierString = String.format("w%ddp-h%ddp", 480, 640);
- ShadowDisplayManager.addDisplay(displayQualifierString);
- return ShadowDisplayManager.addDisplay(displayQualifierString);
- }
-}
diff --git a/camera/camera-view/src/test/java/androidx/camera/view/preview/transform/RotationTransformTest.java b/camera/camera-view/src/test/java/androidx/camera/view/preview/transform/RotationTransformTest.java
deleted file mode 100644
index 6893175..0000000
--- a/camera/camera-view/src/test/java/androidx/camera/view/preview/transform/RotationTransformTest.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright 2020 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.view.preview.transform;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.os.Build;
-import android.view.Display;
-import android.view.Surface;
-import android.view.View;
-
-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)
-public class RotationTransformTest {
- private final int[] mPossibleRotations = {Surface.ROTATION_0, Surface.ROTATION_90,
- Surface.ROTATION_180, Surface.ROTATION_270};
-
- @Test
- public void rotation_0() {
- final View view = setUpView(Surface.ROTATION_0);
- assertThat(RotationTransform.getRotationDegrees(view)).isEqualTo(0);
- }
-
- @Test
- public void rotation_90() {
- final View view = setUpView(Surface.ROTATION_90);
- assertThat(RotationTransform.getRotationDegrees(view)).isEqualTo(90);
- }
-
- @Test
- public void rotation_180() {
- final View view = setUpView(Surface.ROTATION_180);
- assertThat(RotationTransform.getRotationDegrees(view)).isEqualTo(180);
- }
-
- @Test
- public void rotation_270() {
- final View view = setUpView(Surface.ROTATION_270);
- assertThat(RotationTransform.getRotationDegrees(view)).isEqualTo(270);
- }
-
- @Test
- public void viewAndDeviceRotationCombinations() {
- for (int viewRotation : mPossibleRotations) {
- final View view = setUpView(viewRotation);
-
- // If the device rotation value is available, the return degrees will be calculated
- // from the device rotation value.
- for (int deviceRotation : mPossibleRotations) {
- int deviceRotationDegrees =
- SurfaceRotation.rotationDegreesFromSurfaceRotation(deviceRotation);
- assertThat(
- RotationTransform.getRotationDegrees(view, deviceRotation)).isEqualTo(
- deviceRotationDegrees);
- }
-
- // If the device rotation value is ROTATION_AUTOMATIC, the return degrees will be
- // calculated from the view.
- int viewRotationDegrees =
- SurfaceRotation.rotationDegreesFromSurfaceRotation(viewRotation);
- assertThat(RotationTransform.getRotationDegrees(view,
- RotationTransform.ROTATION_AUTOMATIC)).isEqualTo(viewRotationDegrees);
- }
- }
-
- @Test(expected = UnsupportedOperationException.class)
- public void unknown_rotation() {
- final View view = setUpView(-1);
- RotationTransform.getRotationDegrees(view);
- }
-
- private View setUpView(int rotation) {
- final View view = mock(View.class);
- final Display display = mock(Display.class);
- when(view.getDisplay()).thenReturn(display);
- when(display.getRotation()).thenReturn(rotation);
- return view;
- }
-}
diff --git a/camera/camera-view/src/test/java/androidx/camera/view/preview/transform/ScaleTransformTest.java b/camera/camera-view/src/test/java/androidx/camera/view/preview/transform/ScaleTransformTest.java
deleted file mode 100644
index 5f416d6..0000000
--- a/camera/camera-view/src/test/java/androidx/camera/view/preview/transform/ScaleTransformTest.java
+++ /dev/null
@@ -1,306 +0,0 @@
-/*
- * Copyright 2020 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.view.preview.transform;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.os.Build;
-import android.view.Display;
-import android.view.Surface;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.camera.view.preview.transform.transformation.ScaleTransformation;
-
-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)
-public class ScaleTransformTest {
-
- @Test
- public void fill_viewNotScaled_rotation0() {
- final View container = setUpContainer(300, 400);
- final View view = setUpView(200, 400, 1F, 1F, Surface.ROTATION_0);
-
- final ScaleTransformation scale = ScaleTransform.fill(container, view,
- RotationTransform.ROTATION_AUTOMATIC);
-
- assertThat(scale.getScaleX()).isEqualTo(scale.getScaleY());
- assertThat(getScaledWidth(view) * scale.getScaleX()).isAtLeast(container.getWidth());
- assertThat(getScaledHeight(view) * scale.getScaleY()).isAtLeast(container.getHeight());
- assertThat(getScaledWidth(view) * scale.getScaleX() == container.getWidth()
- || getScaledHeight(view) * scale.getScaleY() == container.getHeight()).isTrue();
- }
-
- @Test
- public void fill_viewNotScaled_rotation90_deviceRotation0() {
- final View container = setUpContainer(300, 400);
- final View view = setUpView(200, 400, 1F, 1F, Surface.ROTATION_90);
-
- final ScaleTransformation scale = ScaleTransform.fill(container, view, Surface.ROTATION_0);
-
- assertThat(scale.getScaleX()).isEqualTo(scale.getScaleY());
- assertThat(getScaledWidth(view) * scale.getScaleX()).isAtLeast(container.getWidth());
- assertThat(getScaledHeight(view) * scale.getScaleY()).isAtLeast(container.getHeight());
- assertThat(getScaledWidth(view) * scale.getScaleX() == container.getWidth()
- || getScaledHeight(view) * scale.getScaleY() == container.getHeight()).isTrue();
- }
-
- @Test
- public void fill_viewNotScaled_rotation90() {
- final View container = setUpContainer(300, 400);
- final View view = setUpView(200, 400, 1F, 1F, Surface.ROTATION_90);
-
- final ScaleTransformation scale = ScaleTransform.fill(container, view,
- RotationTransform.ROTATION_AUTOMATIC);
-
- assertThat(scale.getScaleX()).isEqualTo(scale.getScaleY());
- assertThat(getScaledWidth(view) * scale.getScaleX()).isAtLeast(container.getHeight());
- assertThat(getScaledHeight(view) * scale.getScaleY()).isAtLeast(container.getWidth());
- assertThat(getScaledWidth(view) * scale.getScaleX() == container.getHeight()
- || getScaledHeight(view) * scale.getScaleY() == container.getWidth()).isTrue();
- }
-
- @Test
- public void fill_viewNotScaled_rotation0_deviceRotation90() {
- final View container = setUpContainer(300, 400);
- final View view = setUpView(200, 400, 1F, 1F, Surface.ROTATION_0);
-
- final ScaleTransformation scale = ScaleTransform.fill(container, view,
- Surface.ROTATION_90);
-
- assertThat(scale.getScaleX()).isEqualTo(scale.getScaleY());
- assertThat(getScaledWidth(view) * scale.getScaleX()).isAtLeast(container.getHeight());
- assertThat(getScaledHeight(view) * scale.getScaleY()).isAtLeast(container.getWidth());
- assertThat(getScaledWidth(view) * scale.getScaleX() == container.getHeight()
- || getScaledHeight(view) * scale.getScaleY() == container.getWidth()).isTrue();
- }
-
- @Test
- public void fill_viewScaled_rotation0() {
- final View container = setUpContainer(300, 400);
- final View view = setUpView(200, 400, 2F, 1.5F, Surface.ROTATION_0);
-
- final ScaleTransformation scale = ScaleTransform.fill(container, view,
- RotationTransform.ROTATION_AUTOMATIC);
-
- assertThat(scale.getScaleX()).isEqualTo(scale.getScaleY());
- assertThat(getScaledWidth(view) * scale.getScaleX()).isAtLeast(container.getWidth());
- assertThat(getScaledHeight(view) * scale.getScaleY()).isAtLeast(container.getHeight());
- assertThat(getScaledWidth(view) * scale.getScaleX() == container.getWidth()
- || getScaledHeight(view) * scale.getScaleY() == container.getHeight()).isTrue();
- }
-
- @Test
- public void fill_viewScaled_rotation90_deviceRotation0() {
- final View container = setUpContainer(300, 400);
- final View view = setUpView(200, 400, 2F, 1.5F, Surface.ROTATION_90);
-
- final ScaleTransformation scale = ScaleTransform.fill(container, view, Surface.ROTATION_0);
-
- assertThat(scale.getScaleX()).isEqualTo(scale.getScaleY());
- assertThat(getScaledWidth(view) * scale.getScaleX()).isAtLeast(container.getWidth());
- assertThat(getScaledHeight(view) * scale.getScaleY()).isAtLeast(container.getHeight());
- assertThat(getScaledWidth(view) * scale.getScaleX() == container.getWidth()
- || getScaledHeight(view) * scale.getScaleY() == container.getHeight()).isTrue();
- }
-
- @Test
- public void fill_viewScaled_rotation90() {
- final View container = setUpContainer(300, 400);
- final View view = setUpView(200, 400, 2F, 1.5F, Surface.ROTATION_90);
-
- final ScaleTransformation scale = ScaleTransform.fill(container, view,
- RotationTransform.ROTATION_AUTOMATIC);
-
- assertThat(scale.getScaleX()).isEqualTo(scale.getScaleY());
- assertThat(getScaledWidth(view) * scale.getScaleX()).isAtLeast(container.getHeight());
- assertThat(getScaledHeight(view) * scale.getScaleY()).isAtLeast(container.getWidth());
- assertThat(getScaledWidth(view) * scale.getScaleX() == container.getHeight()
- || getScaledHeight(view) * scale.getScaleY() == container.getWidth()).isTrue();
- }
-
- @Test
- public void fill_viewScaled_rotation0_deviceRotation90() {
- final View container = setUpContainer(300, 400);
- final View view = setUpView(200, 400, 2F, 1.5F, Surface.ROTATION_0);
-
- final ScaleTransformation scale = ScaleTransform.fill(container, view, Surface.ROTATION_90);
-
- assertThat(scale.getScaleX()).isEqualTo(scale.getScaleY());
- assertThat(getScaledWidth(view) * scale.getScaleX()).isAtLeast(container.getHeight());
- assertThat(getScaledHeight(view) * scale.getScaleY()).isAtLeast(container.getWidth());
- assertThat(getScaledWidth(view) * scale.getScaleX() == container.getHeight()
- || getScaledHeight(view) * scale.getScaleY() == container.getWidth()).isTrue();
- }
-
- @Test
- public void fit_viewNotScaled_rotation0() {
- final View container = setUpContainer(200, 500);
- final View view = setUpView(500, 1000, 1F, 1F, Surface.ROTATION_0);
-
- final ScaleTransformation scale = ScaleTransform.fit(container, view,
- RotationTransform.ROTATION_AUTOMATIC);
-
- assertThat(scale.getScaleX()).isEqualTo(scale.getScaleY());
- assertThat(getScaledWidth(view) * scale.getScaleX()).isAtMost(container.getWidth());
- assertThat(getScaledHeight(view) * scale.getScaleY()).isAtMost(container.getHeight());
- assertThat(getScaledWidth(view) * scale.getScaleX() == container.getWidth()
- || getScaledHeight(view) * scale.getScaleY() == container.getHeight()).isTrue();
- }
-
- @Test
- public void fit_viewNotScaled_rotation90_deviceRotation0() {
- final View container = setUpContainer(200, 500);
- final View view = setUpView(500, 1000, 1F, 1F, Surface.ROTATION_90);
-
- final ScaleTransformation scale = ScaleTransform.fit(container, view, Surface.ROTATION_0);
-
- assertThat(scale.getScaleX()).isEqualTo(scale.getScaleY());
- assertThat(getScaledWidth(view) * scale.getScaleX()).isAtMost(container.getWidth());
- assertThat(getScaledHeight(view) * scale.getScaleY()).isAtMost(container.getHeight());
- assertThat(getScaledWidth(view) * scale.getScaleX() == container.getWidth()
- || getScaledHeight(view) * scale.getScaleY() == container.getHeight()).isTrue();
- }
-
- @Test
- public void fit_viewNotScaled_rotation90() {
- final View container = setUpContainer(200, 500);
- final View view = setUpView(500, 1000, 1F, 1F, Surface.ROTATION_90);
-
- final ScaleTransformation scale = ScaleTransform.fit(container, view,
- RotationTransform.ROTATION_AUTOMATIC);
-
- assertThat(scale.getScaleX()).isEqualTo(scale.getScaleY());
- assertThat(getScaledWidth(view) * scale.getScaleX()).isAtMost(container.getHeight());
- assertThat(getScaledHeight(view) * scale.getScaleY()).isAtMost(container.getWidth());
- assertThat(getScaledWidth(view) * scale.getScaleX() == container.getHeight()
- || getScaledHeight(view) * scale.getScaleY() == container.getWidth()).isTrue();
- }
-
- @Test
- public void fit_viewNotScaled_rotation0_deviceRotation90() {
- final View container = setUpContainer(200, 500);
- final View view = setUpView(500, 1000, 1F, 1F, Surface.ROTATION_0);
-
- final ScaleTransformation scale = ScaleTransform.fit(container, view, Surface.ROTATION_90);
-
- assertThat(scale.getScaleX()).isEqualTo(scale.getScaleY());
- assertThat(getScaledWidth(view) * scale.getScaleX()).isAtMost(container.getHeight());
- assertThat(getScaledHeight(view) * scale.getScaleY()).isAtMost(container.getWidth());
- assertThat(getScaledWidth(view) * scale.getScaleX() == container.getHeight()
- || getScaledHeight(view) * scale.getScaleY() == container.getWidth()).isTrue();
- }
-
- @Test
- public void fit_viewScaled_rotation0() {
- final View container = setUpContainer(200, 500);
- final View view = setUpView(500, 1000, 0.5F, 2F, Surface.ROTATION_0);
-
- final ScaleTransformation scale = ScaleTransform.fit(container, view,
- RotationTransform.ROTATION_AUTOMATIC);
-
- assertThat(scale.getScaleX()).isEqualTo(scale.getScaleY());
- assertThat(getScaledWidth(view) * scale.getScaleX()).isAtMost(container.getWidth());
- assertThat(getScaledHeight(view) * scale.getScaleY()).isAtMost(container.getHeight());
- assertThat(getScaledWidth(view) * scale.getScaleX() == container.getWidth()
- || getScaledHeight(view) * scale.getScaleY() == container.getHeight()).isTrue();
- }
-
- @Test
- public void fit_viewScaled_rotation90_deviceRotation0() {
- final View container = setUpContainer(200, 500);
- final View view = setUpView(500, 1000, 0.5F, 2F, Surface.ROTATION_90);
-
- final ScaleTransformation scale = ScaleTransform.fit(container, view, Surface.ROTATION_0);
-
- assertThat(scale.getScaleX()).isEqualTo(scale.getScaleY());
- assertThat(getScaledWidth(view) * scale.getScaleX()).isAtMost(container.getWidth());
- assertThat(getScaledHeight(view) * scale.getScaleY()).isAtMost(container.getHeight());
- assertThat(getScaledWidth(view) * scale.getScaleX() == container.getWidth()
- || getScaledHeight(view) * scale.getScaleY() == container.getHeight()).isTrue();
- }
-
- @Test
- public void fit_viewScaled_rotation90() {
- final View container = setUpContainer(200, 500);
- final View view = setUpView(500, 1000, 0.5F, 2F, Surface.ROTATION_90);
-
- final ScaleTransformation scale = ScaleTransform.fit(container, view,
- RotationTransform.ROTATION_AUTOMATIC);
-
- assertThat(scale.getScaleX()).isEqualTo(scale.getScaleY());
- assertThat(getScaledWidth(view) * scale.getScaleX()).isAtMost(container.getHeight());
- assertThat(getScaledHeight(view) * scale.getScaleY()).isAtMost(container.getWidth());
- assertThat(getScaledWidth(view) * scale.getScaleX() == container.getHeight()
- || getScaledHeight(view) * scale.getScaleY() == container.getWidth()).isTrue();
- }
-
- @Test
- public void fit_viewScaled_rotation0_deviceRotation90() {
- final View container = setUpContainer(200, 500);
- final View view = setUpView(500, 1000, 0.5F, 2F, Surface.ROTATION_0);
-
- final ScaleTransformation scale = ScaleTransform.fit(container, view, Surface.ROTATION_90);
-
- assertThat(scale.getScaleX()).isEqualTo(scale.getScaleY());
- assertThat(getScaledWidth(view) * scale.getScaleX()).isAtMost(container.getHeight());
- assertThat(getScaledHeight(view) * scale.getScaleY()).isAtMost(container.getWidth());
- assertThat(getScaledWidth(view) * scale.getScaleX() == container.getHeight()
- || getScaledHeight(view) * scale.getScaleY() == container.getWidth()).isTrue();
- }
-
- @NonNull
- private View setUpContainer(int width, int height) {
- final View container = mock(View.class);
- when(container.getWidth()).thenReturn(width);
- when(container.getHeight()).thenReturn(height);
- return container;
- }
-
- @NonNull
- private View setUpView(int width, int height, float scaleX, float scaleY, int rotation) {
- final View view = mock(View.class);
- when(view.getWidth()).thenReturn(width);
- when(view.getHeight()).thenReturn(height);
- when(view.getScaleX()).thenReturn(scaleX);
- when(view.getScaleY()).thenReturn(scaleY);
-
- final Display display = mock(Display.class);
- when(view.getDisplay()).thenReturn(display);
- when(display.getRotation()).thenReturn(rotation);
-
- return view;
- }
-
- private float getScaledWidth(@NonNull final View view) {
- return view.getWidth() * view.getScaleX();
- }
-
- private float getScaledHeight(@NonNull final View view) {
- return view.getHeight() * view.getScaleY();
- }
-}
diff --git a/camera/camera-view/src/test/java/androidx/camera/view/preview/transform/TranslationTransformTest.java b/camera/camera-view/src/test/java/androidx/camera/view/preview/transform/TranslationTransformTest.java
deleted file mode 100644
index f35cbbb..0000000
--- a/camera/camera-view/src/test/java/androidx/camera/view/preview/transform/TranslationTransformTest.java
+++ /dev/null
@@ -1,414 +0,0 @@
-/*
- * Copyright 2020 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.view.preview.transform;
-
-import static android.view.View.LAYOUT_DIRECTION_LTR;
-import static android.view.View.LAYOUT_DIRECTION_RTL;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.os.Build;
-import android.util.Pair;
-import android.view.Display;
-import android.view.Surface;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.camera.view.preview.transform.transformation.TranslationTransformation;
-
-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)
-public class TranslationTransformTest {
-
- private static final int CONTAINER_WIDTH = 500;
- private static final int CONTAINER_HEIGHT = 800;
- private static final int VIEW_WIDTH = 200;
- private static final int VIEW_HEIGHT = 300;
-
- @Test
- public void start_viewNotScaled_rotation0() {
- final View view = setUpView(Surface.ROTATION_0);
- final Pair<Float, Float> scaleXY = new Pair<>(1F, 1F);
- final TranslationTransformation transformation = TranslationTransform.start(view, scaleXY,
- RotationTransform.ROTATION_AUTOMATIC);
-
- assertThat(transformation.getTransX()).isEqualTo(0);
- assertThat(transformation.getTransY()).isEqualTo(0);
- }
-
- @Test
- public void start_viewNotScaled_rotation90_deviceRotation0() {
- final View view = setUpView(Surface.ROTATION_90);
- final Pair<Float, Float> scaleXY = new Pair<>(1F, 1F);
- final TranslationTransformation transformation = TranslationTransform.start(view, scaleXY,
- Surface.ROTATION_0);
-
- assertThat(transformation.getTransX()).isEqualTo(0);
- assertThat(transformation.getTransY()).isEqualTo(0);
- }
-
- @Test
- public void start_viewNotScaled_rotation90() {
- final View view = setUpView(Surface.ROTATION_90);
- final Pair<Float, Float> scaleXY = new Pair<>(1F, 1F);
- final TranslationTransformation transformation = TranslationTransform.start(view, scaleXY,
- RotationTransform.ROTATION_AUTOMATIC);
-
- assertThat(transformation.getTransX()).isEqualTo(50);
- assertThat(transformation.getTransY()).isEqualTo(-50);
- }
-
- @Test
- public void start_viewNotScaled_rotation0_deviceRotation90() {
- final View view = setUpView(Surface.ROTATION_0);
- final Pair<Float, Float> scaleXY = new Pair<>(1F, 1F);
- final TranslationTransformation transformation = TranslationTransform.start(view, scaleXY,
- Surface.ROTATION_90);
-
- assertThat(transformation.getTransX()).isEqualTo(50);
- assertThat(transformation.getTransY()).isEqualTo(-50);
- }
-
- @Test
- public void start_viewScaled_rotation0() {
- final View view = setUpView(Surface.ROTATION_0);
- final Pair<Float, Float> scaleXY = new Pair<>(2F, 0.5F);
- final TranslationTransformation transformation = TranslationTransform.start(view, scaleXY,
- RotationTransform.ROTATION_AUTOMATIC);
-
- assertThat(transformation.getTransX()).isEqualTo(100);
- assertThat(transformation.getTransY()).isEqualTo(-75);
- }
-
- @Test
- public void start_viewScaled_rotation90_deviceRotation0() {
- final View view = setUpView(Surface.ROTATION_90);
- final Pair<Float, Float> scaleXY = new Pair<>(2F, 0.5F);
- final TranslationTransformation transformation = TranslationTransform.start(view, scaleXY,
- Surface.ROTATION_0);
-
- assertThat(transformation.getTransX()).isEqualTo(100);
- assertThat(transformation.getTransY()).isEqualTo(-75);
- }
-
- @Test
- public void start_viewScaled_rotation90() {
- final View view = setUpView(Surface.ROTATION_90);
- final Pair<Float, Float> scaleXY = new Pair<>(2F, 0.5F);
- final TranslationTransformation transformation = TranslationTransform.start(view, scaleXY,
- RotationTransform.ROTATION_AUTOMATIC);
-
- assertThat(transformation.getTransX()).isEqualTo(-25);
- assertThat(transformation.getTransY()).isEqualTo(50);
- }
-
- @Test
- public void start_viewScaled_rotation0_deviceRotation90() {
- final View view = setUpView(Surface.ROTATION_0);
- final Pair<Float, Float> scaleXY = new Pair<>(2F, 0.5F);
- final TranslationTransformation transformation = TranslationTransform.start(view, scaleXY,
- Surface.ROTATION_90);
-
- assertThat(transformation.getTransX()).isEqualTo(-25);
- assertThat(transformation.getTransY()).isEqualTo(50);
- }
-
- @Test
- public void start_rtlLayout_rotation0() {
- final View view = setUpView(Surface.ROTATION_0, LAYOUT_DIRECTION_RTL);
- final Pair<Float, Float> scaleXY = new Pair<>(2F, 0.5F);
- final TranslationTransformation transformation = TranslationTransform.start(view, scaleXY,
- Surface.ROTATION_0);
-
- assertThat(transformation.getTransX()).isEqualTo(-100);
- assertThat(transformation.getTransY()).isEqualTo(-75);
- }
-
- @Test
- public void start_rtlLayout_rotation90_deviceRotation0() {
- final View view = setUpView(Surface.ROTATION_90, LAYOUT_DIRECTION_RTL);
- final Pair<Float, Float> scaleXY = new Pair<>(2F, 0.5F);
- final TranslationTransformation transformation = TranslationTransform.start(view, scaleXY,
- Surface.ROTATION_0);
-
- assertThat(transformation.getTransX()).isEqualTo(-100);
- assertThat(transformation.getTransY()).isEqualTo(-75);
- }
-
- @Test
- public void start_rtlLayout_rotation90() {
- final View view = setUpView(Surface.ROTATION_90, LAYOUT_DIRECTION_RTL);
- final Pair<Float, Float> scaleXY = new Pair<>(2F, 0.5F);
- final TranslationTransformation transformation = TranslationTransform.start(view, scaleXY,
- RotationTransform.ROTATION_AUTOMATIC);
-
- assertThat(transformation.getTransX()).isEqualTo(25);
- assertThat(transformation.getTransY()).isEqualTo(50);
- }
-
- @Test
- public void start_rtlLayout_rotation0_deviceRotation90() {
- final View view = setUpView(Surface.ROTATION_0, LAYOUT_DIRECTION_RTL);
- final Pair<Float, Float> scaleXY = new Pair<>(2F, 0.5F);
- final TranslationTransformation transformation = TranslationTransform.start(view, scaleXY,
- Surface.ROTATION_90);
-
- assertThat(transformation.getTransX()).isEqualTo(25);
- assertThat(transformation.getTransY()).isEqualTo(50);
- }
-
- @Test
- public void center_viewNotScaled_rotation0() {
- final View container = setUpContainer();
- final View view = setUpView(Surface.ROTATION_0);
- final Pair<Float, Float> scaleXY = new Pair<>(1F, 1F);
- final TranslationTransformation transformation = TranslationTransform.center(container,
- view);
-
- assertThat(transformation.getTransX()).isEqualTo(150);
- assertThat(transformation.getTransY()).isEqualTo(250);
- }
-
- @Test
- public void center_viewNotScaled_rotation90() {
- final View container = setUpContainer();
- final View view = setUpView(Surface.ROTATION_90);
- final Pair<Float, Float> scaleXY = new Pair<>(1F, 1F);
- final TranslationTransformation transformation = TranslationTransform.center(container,
- view);
-
- assertThat(transformation.getTransX()).isEqualTo(150);
- assertThat(transformation.getTransY()).isEqualTo(250);
- }
-
- @Test
- public void center_viewScaled_rotation0() {
- final View container = setUpContainer();
- final View view = setUpView(Surface.ROTATION_0);
- final Pair<Float, Float> scaleXY = new Pair<>(2F, 0.5F);
- final TranslationTransformation transformation = TranslationTransform.center(container,
- view);
-
- assertThat(transformation.getTransX()).isEqualTo(150);
- assertThat(transformation.getTransY()).isEqualTo(250);
- }
-
- @Test
- public void center_viewScaled_rotation90() {
- final View container = setUpContainer();
- final View view = setUpView(Surface.ROTATION_90);
- final Pair<Float, Float> scaleXY = new Pair<>(2F, 0.5F);
- final TranslationTransformation transformation = TranslationTransform.center(container,
- view);
-
- assertThat(transformation.getTransX()).isEqualTo(150);
- assertThat(transformation.getTransY()).isEqualTo(250);
- }
-
- @Test
- public void center_rtlLayout() {
- final View container = setUpContainer();
- final View view = setUpView(Surface.ROTATION_0, LAYOUT_DIRECTION_RTL);
- final Pair<Float, Float> scaleXY = new Pair<>(1F, 1F);
- final TranslationTransformation transformation = TranslationTransform.center(container,
- view);
-
- assertThat(transformation.getTransX()).isEqualTo(-150);
- assertThat(transformation.getTransY()).isEqualTo(250);
- }
-
- @Test
- public void end_viewNotScaled_rotation0() {
- final View container = setUpContainer();
- final View view = setUpView(Surface.ROTATION_0);
- final Pair<Float, Float> scaleXY = new Pair<>(1F, 1F);
- final TranslationTransformation transformation = TranslationTransform.end(container, view,
- scaleXY, RotationTransform.ROTATION_AUTOMATIC);
-
- assertThat(transformation.getTransX()).isEqualTo(300);
- assertThat(transformation.getTransY()).isEqualTo(500);
- }
-
- @Test
- public void end_viewNotScaled_rotation90_deviceRotation0() {
- final View container = setUpContainer();
- final View view = setUpView(Surface.ROTATION_90);
- final Pair<Float, Float> scaleXY = new Pair<>(1F, 1F);
- final TranslationTransformation transformation = TranslationTransform.end(container, view,
- scaleXY, Surface.ROTATION_0);
-
- assertThat(transformation.getTransX()).isEqualTo(300);
- assertThat(transformation.getTransY()).isEqualTo(500);
- }
-
- @Test
- public void end_viewNotScaled_rotation90() {
- final View container = setUpContainer();
- final View view = setUpView(Surface.ROTATION_90);
- final Pair<Float, Float> scaleXY = new Pair<>(1F, 1F);
- final TranslationTransformation transformation = TranslationTransform.end(container, view,
- scaleXY, RotationTransform.ROTATION_AUTOMATIC);
-
- assertThat(transformation.getTransX()).isEqualTo(250);
- assertThat(transformation.getTransY()).isEqualTo(550);
- }
-
- @Test
- public void end_viewNotScaled_rotation0_deviceRotation90() {
- final View container = setUpContainer();
- final View view = setUpView(Surface.ROTATION_0);
- final Pair<Float, Float> scaleXY = new Pair<>(1F, 1F);
- final TranslationTransformation transformation = TranslationTransform.end(container, view,
- scaleXY, Surface.ROTATION_90);
-
- assertThat(transformation.getTransX()).isEqualTo(250);
- assertThat(transformation.getTransY()).isEqualTo(550);
- }
-
- @Test
- public void end_viewScaled_rotation0() {
- final View container = setUpContainer();
- final View view = setUpView(Surface.ROTATION_0);
- final Pair<Float, Float> scaleXY = new Pair<>(2F, 0.5F);
- final TranslationTransformation transformation = TranslationTransform.end(container, view,
- scaleXY, RotationTransform.ROTATION_AUTOMATIC);
-
- assertThat(transformation.getTransX()).isEqualTo(200);
- assertThat(transformation.getTransY()).isEqualTo(575);
- }
-
- @Test
- public void end_viewScaled_rotation90_deviceRotation0() {
- final View container = setUpContainer();
- final View view = setUpView(Surface.ROTATION_90);
- final Pair<Float, Float> scaleXY = new Pair<>(2F, 0.5F);
- final TranslationTransformation transformation = TranslationTransform.end(container, view,
- scaleXY, Surface.ROTATION_0);
-
- assertThat(transformation.getTransX()).isEqualTo(200);
- assertThat(transformation.getTransY()).isEqualTo(575);
- }
-
- @Test
- public void end_viewScaled_rotation90() {
- final View container = setUpContainer();
- final View view = setUpView(Surface.ROTATION_90);
- final Pair<Float, Float> scaleXY = new Pair<>(2F, 0.5F);
- final TranslationTransformation transformation = TranslationTransform.end(container, view,
- scaleXY, RotationTransform.ROTATION_AUTOMATIC);
-
- assertThat(transformation.getTransX()).isEqualTo(325);
- assertThat(transformation.getTransY()).isEqualTo(450);
- }
-
- @Test
- public void end_viewScaled_rotation0_deviceRotation90() {
- final View container = setUpContainer();
- final View view = setUpView(Surface.ROTATION_0);
- final Pair<Float, Float> scaleXY = new Pair<>(2F, 0.5F);
- final TranslationTransformation transformation = TranslationTransform.end(container, view,
- scaleXY, Surface.ROTATION_90);
-
- assertThat(transformation.getTransX()).isEqualTo(325);
- assertThat(transformation.getTransY()).isEqualTo(450);
- }
-
- @Test
- public void end_rtlLayout_rotation0() {
- final View container = setUpContainer();
- final View view = setUpView(Surface.ROTATION_0, LAYOUT_DIRECTION_RTL);
- final Pair<Float, Float> scaleXY = new Pair<>(1F, 1F);
- final TranslationTransformation transformation = TranslationTransform.end(container, view,
- scaleXY, RotationTransform.ROTATION_AUTOMATIC);
-
- assertThat(transformation.getTransX()).isEqualTo(-300);
- assertThat(transformation.getTransY()).isEqualTo(500);
- }
-
- @Test
- public void end_rtlLayout_rotation90_deviceRotation0() {
- final View container = setUpContainer();
- final View view = setUpView(Surface.ROTATION_90, LAYOUT_DIRECTION_RTL);
- final Pair<Float, Float> scaleXY = new Pair<>(1F, 1F);
- final TranslationTransformation transformation = TranslationTransform.end(container, view,
- scaleXY, Surface.ROTATION_0);
-
- assertThat(transformation.getTransX()).isEqualTo(-300);
- assertThat(transformation.getTransY()).isEqualTo(500);
- }
-
- @Test
- public void end_rtlLayout_rotation90() {
- final View container = setUpContainer();
- final View view = setUpView(Surface.ROTATION_90, LAYOUT_DIRECTION_RTL);
- final Pair<Float, Float> scaleXY = new Pair<>(1F, 1F);
- final TranslationTransformation transformation = TranslationTransform.end(container, view,
- scaleXY, RotationTransform.ROTATION_AUTOMATIC);
-
- assertThat(transformation.getTransX()).isEqualTo(-250);
- assertThat(transformation.getTransY()).isEqualTo(550);
- }
-
- @Test
- public void end_rtlLayout_rotation0_deviceRotation90() {
- final View container = setUpContainer();
- final View view = setUpView(Surface.ROTATION_0, LAYOUT_DIRECTION_RTL);
- final Pair<Float, Float> scaleXY = new Pair<>(1F, 1F);
- final TranslationTransformation transformation = TranslationTransform.end(container, view,
- scaleXY, Surface.ROTATION_90);
-
- assertThat(transformation.getTransX()).isEqualTo(-250);
- assertThat(transformation.getTransY()).isEqualTo(550);
- }
-
- @NonNull
- private View setUpView(final int rotation) {
- return setUpView(rotation, LAYOUT_DIRECTION_LTR);
- }
-
- @NonNull
- private View setUpView(final int rotation, final int layoutDirection) {
- final View view = mock(View.class);
- when(view.getWidth()).thenReturn(VIEW_WIDTH);
- when(view.getHeight()).thenReturn(VIEW_HEIGHT);
- when(view.getLayoutDirection()).thenReturn(layoutDirection);
-
- final Display display = mock(Display.class);
- when(view.getDisplay()).thenReturn(display);
- when(display.getRotation()).thenReturn(rotation);
-
- return view;
- }
-
- @NonNull
- private View setUpContainer() {
- final View container = mock(View.class);
- when(container.getWidth()).thenReturn(CONTAINER_WIDTH);
- when(container.getHeight()).thenReturn(CONTAINER_HEIGHT);
- return container;
- }
-}
diff --git a/camera/camera-view/src/test/java/androidx/camera/view/preview/transform/transformation/TransformationTest.java b/camera/camera-view/src/test/java/androidx/camera/view/preview/transform/transformation/TransformationTest.java
deleted file mode 100644
index fa4d437..0000000
--- a/camera/camera-view/src/test/java/androidx/camera/view/preview/transform/transformation/TransformationTest.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2020 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.view.preview.transform.transformation;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@RunWith(JUnit4.class)
-public class TransformationTest {
-
- @Test
- public void addTransformations() {
- final Transformation transformation = new Transformation(2.3F, 7.5F, 100F, 23.4F, 3.14F);
- final Transformation other = new Transformation(12.3F, 0.5F, 0F, 0.24F, 90F);
-
- final Transformation sum = transformation.add(other);
-
- assertThat(sum.getScaleX()).isEqualTo(transformation.getScaleX() * other.getScaleX());
- assertThat(sum.getScaleY()).isEqualTo(transformation.getScaleY() * other.getScaleY());
- assertThat(sum.getTransX()).isEqualTo(transformation.getTransX() + other.getTransX());
- assertThat(sum.getTransY()).isEqualTo(transformation.getTransY() + other.getTransY());
- assertThat(sum.getRotation()).isEqualTo(transformation.getRotation() + other.getRotation());
- }
-
- @Test
- public void subtractTransformations() {
- final Transformation transformation = new Transformation(2.3F, 7.5F, 100F, 23.4F, 3.14F);
- final Transformation other = new Transformation(12.3F, 0.5F, 0F, 0.24F, 90F);
-
- final Transformation sum = transformation.subtract(other);
-
- assertThat(sum.getScaleX()).isEqualTo(transformation.getScaleX() / other.getScaleX());
- assertThat(sum.getScaleY()).isEqualTo(transformation.getScaleY() / other.getScaleY());
- assertThat(sum.getTransX()).isEqualTo(transformation.getTransX() - other.getTransX());
- assertThat(sum.getTransY()).isEqualTo(transformation.getTransY() - other.getTransY());
- assertThat(sum.getRotation()).isEqualTo(transformation.getRotation() - other.getRotation());
- }
-}
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 50db5e9..0b27c15 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
@@ -46,6 +46,7 @@
import com.google.common.collect.ImmutableList
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
+import org.junit.After
import org.junit.Assume
import org.junit.Assume.assumeTrue
import org.junit.Before
@@ -89,19 +90,30 @@
)
private val instrumentation = InstrumentationRegistry.getInstrumentation()
+ private lateinit var fragment: CameraControllerFragment
+ private lateinit var fragmentScenario: FragmentScenario<CameraControllerFragment>
@Before
fun setup() {
// Clear the device UI and check if there is no dialog or lock screen on the top of the
// window before start the test.
CoreAppTestUtil.prepareDeviceUI(instrumentation)
+ fragmentScenario = createFragmentScenario()
+ fragment = fragmentScenario.getFragment()
+ }
+
+ @After
+ fun tearDown() {
+ if (::fragmentScenario.isInitialized) {
+ fragmentScenario.moveToState(Lifecycle.State.DESTROYED)
+ }
}
@Test
fun fragmentLaunch_cameraInitializationCompletes() {
val semaphore = Semaphore(0)
Futures.addCallback(
- createFragmentScenario().getFragment().cameraController.initializationFuture,
+ fragment.cameraController.initializationFuture,
object : FutureCallback<Void> {
override fun onSuccess(result: Void?) {
semaphore.release()
@@ -116,12 +128,11 @@
@Test
fun fragmentLaunch_receiveAnalysisFrames() {
- createFragmentScenario().getFragment().assertAnalysisStreaming(true)
+ fragment.assertAnalysisStreaming(true)
}
@Test
fun imageAnalysisDisabled_isNotStreaming() {
- val fragment = createFragmentScenario().getFragment()
fragment.assertAnalysisStreaming(true)
onView(withId(R.id.analysis_enabled)).perform(click())
@@ -131,7 +142,6 @@
@Test
fun imageAnalysisDisabledAndEnabled_isStreaming() {
- val fragment = createFragmentScenario().getFragment()
fragment.assertAnalysisStreaming(true)
onView(withId(R.id.analysis_enabled)).perform(click())
@@ -142,7 +152,6 @@
@Test
fun analyzerCleared_isNotStreaming() {
- val fragment = createFragmentScenario().getFragment()
fragment.assertAnalysisStreaming(true)
instrumentation.runOnMainSync {
@@ -154,8 +163,6 @@
@Test
fun canSetAnalysisImageDepth() {
- // Arrange.
- val fragment = createFragmentScenario().getFragment()
var currentDepth = 0
// Act.
@@ -174,9 +181,6 @@
@Test
fun canSetAnalysisBackpressureStrategy() {
- // Arrange.
- val fragment = createFragmentScenario().getFragment()
-
// Act.
instrumentation.runOnMainSync {
fragment.cameraController.imageAnalysisBackpressureStrategy =
@@ -200,7 +204,6 @@
)
// Arrange.
- val fragment = createFragmentScenario().getFragment()
fragment.assertPreviewIsStreaming()
// Scaled down images to 10x10 bitmap to normalize and reduce computation.
val width = 10
@@ -268,7 +271,6 @@
@Test
fun fragmentLaunched_canTakePicture() {
- val fragment = createFragmentScenario().getFragment()
fragment.assertPreviewIsStreaming()
fragment.assertCanTakePicture()
}
@@ -277,8 +279,6 @@
fun captureDisabled_cannotTakePicture() {
// Arrange.
thrown.expectMessage("ImageCapture disabled")
- val fragment = createFragmentScenario().getFragment()
- fragment.assertPreviewIsStreaming()
// Act.
onView(withId(R.id.capture_enabled)).perform(click())
@@ -290,7 +290,6 @@
@Test
fun captureDisabledAndEnabled_canTakePicture() {
// Arrange.
- val fragment = createFragmentScenario().getFragment()
fragment.assertPreviewIsStreaming()
// Act.
@@ -304,14 +303,12 @@
@Test
fun previewViewRemoved_previewIsIdle() {
- val fragment = createFragmentScenario().getFragment()
onView(withId(R.id.remove_or_add)).perform(click())
fragment.assertPreviewIsIdle()
}
@Test
fun previewViewRemovedAndAdded_previewIsStreaming() {
- val fragment = createFragmentScenario().getFragment()
onView(withId(R.id.remove_or_add)).perform(click())
onView(withId(R.id.remove_or_add)).perform(click())
fragment.assertPreviewIsStreaming()
@@ -319,14 +316,12 @@
@Test
fun cameraToggled_previewIsStreaming() {
- val fragment = createFragmentScenario().getFragment()
onView(withId(R.id.camera_toggle)).perform(click())
fragment.assertPreviewIsStreaming()
}
@Test
fun cameraToggled_canTakePicture() {
- val fragment = createFragmentScenario().getFragment()
onView(withId(R.id.camera_toggle)).perform(click())
fragment.assertPreviewIsStreaming()
fragment.assertCanTakePicture()
diff --git a/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/PreviewViewFragmentTest.java b/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/PreviewViewFragmentTest.java
index caae3ff..25d71b2 100644
--- a/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/PreviewViewFragmentTest.java
+++ b/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/PreviewViewFragmentTest.java
@@ -39,6 +39,7 @@
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.GrantPermissionRule;
+import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -67,6 +68,7 @@
GrantPermissionRule.grant(android.Manifest.permission.RECORD_AUDIO);
private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ private FragmentScenario<PreviewViewFragment> mScenario;
@Before
public void setup() throws CoreAppTestUtil.ForegroundOccupiedError {
@@ -75,40 +77,46 @@
// Clear the device UI and check if there is no dialog or lock screen on the top of the
// window before start the test.
CoreAppTestUtil.prepareDeviceUI(mInstrumentation);
+ mScenario = createScenario();
+ }
+
+ @After
+ public void tearDown() {
+ if (mScenario != null) {
+ mScenario.moveToState(Lifecycle.State.DESTROYED);
+ }
}
@Test
public void checkPreviewUpdatedAfterDestroyRecreate() {
- final FragmentScenario<PreviewViewFragment> scenario = createScenario();
- assertPreviewUpdating(scenario);
+ assertPreviewUpdating(mScenario);
// Recreate fragment
- scenario.recreate();
- assertPreviewUpdating(scenario);
+ mScenario.recreate();
+ assertPreviewUpdating(mScenario);
}
@Test
public void checkPreviewUpdatedAfterStopResume_3Times() {
- final FragmentScenario<PreviewViewFragment> scenario = createScenario();
- assertPreviewUpdating(scenario);
+ assertPreviewUpdating(mScenario);
// Stop the fragment
- scenario.moveToState(Lifecycle.State.CREATED);
+ mScenario.moveToState(Lifecycle.State.CREATED);
// Resume the fragment
- scenario.moveToState(Lifecycle.State.RESUMED);
- assertPreviewUpdating(scenario);
+ mScenario.moveToState(Lifecycle.State.RESUMED);
+ assertPreviewUpdating(mScenario);
// Stop the fragment
- scenario.moveToState(Lifecycle.State.CREATED);
+ mScenario.moveToState(Lifecycle.State.CREATED);
// Resume the fragment
- scenario.moveToState(Lifecycle.State.RESUMED);
- assertPreviewUpdating(scenario);
+ mScenario.moveToState(Lifecycle.State.RESUMED);
+ assertPreviewUpdating(mScenario);
// Stop the fragment
- scenario.moveToState(Lifecycle.State.CREATED);
+ mScenario.moveToState(Lifecycle.State.CREATED);
// Resume the fragment
- scenario.moveToState(Lifecycle.State.RESUMED);
- assertPreviewUpdating(scenario);
+ mScenario.moveToState(Lifecycle.State.RESUMED);
+ assertPreviewUpdating(mScenario);
}
@Test
@@ -116,17 +124,16 @@
assumeTrue(CameraUtil.hasCameraWithLensFacing(CameraSelector.LENS_FACING_FRONT));
assumeTrue(CameraUtil.hasCameraWithLensFacing(CameraSelector.LENS_FACING_BACK));
- final FragmentScenario<PreviewViewFragment> scenario = createScenario();
- assertPreviewUpdating(scenario);
+ assertPreviewUpdating(mScenario);
// Toggle camera
onView(withId(R.id.toggle_camera)).perform(click());
// Stop the fragment
- scenario.moveToState(Lifecycle.State.CREATED);
+ mScenario.moveToState(Lifecycle.State.CREATED);
// Resume the fragment
- scenario.moveToState(Lifecycle.State.RESUMED);
- assertPreviewUpdating(scenario);
+ mScenario.moveToState(Lifecycle.State.RESUMED);
+ assertPreviewUpdating(mScenario);
}
@Test
@@ -137,8 +144,6 @@
assumeTrue(CameraUtil.hasCameraWithLensFacing(CameraSelector.LENS_FACING_FRONT));
assumeTrue(CameraUtil.hasCameraWithLensFacing(CameraSelector.LENS_FACING_BACK));
- final FragmentScenario<PreviewViewFragment> scenario = createScenario();
-
// Toggle camera. This is to toggle the camera from back to front.
onView(withId(R.id.toggle_camera)).perform(click());
@@ -146,10 +151,10 @@
onView(withId(R.id.toggle_camera)).perform(click());
// Stop the fragment
- scenario.moveToState(Lifecycle.State.CREATED);
+ mScenario.moveToState(Lifecycle.State.CREATED);
// Resume the fragment
- scenario.moveToState(Lifecycle.State.RESUMED);
- assertPreviewUpdating(scenario);
+ mScenario.moveToState(Lifecycle.State.RESUMED);
+ assertPreviewUpdating(mScenario);
}
@Test
@@ -157,70 +162,65 @@
assumeTrue(CameraUtil.hasCameraWithLensFacing(CameraSelector.LENS_FACING_FRONT));
assumeTrue(CameraUtil.hasCameraWithLensFacing(CameraSelector.LENS_FACING_BACK));
- final FragmentScenario<PreviewViewFragment> scenario = createScenario();
- assertPreviewUpdating(scenario);
+ assertPreviewUpdating(mScenario);
for (int i = 0; i <= 10; i++) {
onView(withId(R.id.toggle_camera)).perform(click());
}
- assertPreviewUpdating(scenario);
+ assertPreviewUpdating(mScenario);
}
@Test
public void checkPreviewNotUpdatedAfterPreviewUnbound() {
- final FragmentScenario<PreviewViewFragment> scenario = createScenario();
- assertPreviewUpdating(scenario);
+ assertPreviewUpdating(mScenario);
// Toggle visibility to unbind preview
onView(withId(R.id.toggle_visibility)).perform(click());
- assertPreviewNotUpdating(scenario);
+ assertPreviewNotUpdating(mScenario);
}
@Test
public void checkPreviewUpdatedWhenSamePreviewViewAttachedToMultiplePreviewUseCases() {
- final FragmentScenario<PreviewViewFragment> scenario = createScenario();
- assertPreviewUpdating(scenario);
+ assertPreviewUpdating(mScenario);
// Toggle visibility to unbind preview
onView(withId(R.id.toggle_visibility)).perform(click());
// Toggle visibility to bind new preview
onView(withId(R.id.toggle_visibility)).perform(click());
- assertPreviewUpdating(scenario);
+ assertPreviewUpdating(mScenario);
}
@Test
public void checkSameScaleTypeAfterStopResume() {
- final FragmentScenario<PreviewViewFragment> scenario = createScenario();
- assertPreviewUpdating(scenario);
+ assertPreviewUpdating(mScenario);
mInstrumentation.runOnMainSync(
- () -> getPreviewView(scenario).setScaleType(PreviewView.ScaleType.FIT_END));
+ () -> getPreviewView(mScenario).setScaleType(PreviewView.ScaleType.FIT_END));
// Stop the fragment
- scenario.moveToState(Lifecycle.State.CREATED);
+ mScenario.moveToState(Lifecycle.State.CREATED);
// Resume the fragment
- scenario.moveToState(Lifecycle.State.RESUMED);
+ mScenario.moveToState(Lifecycle.State.RESUMED);
mInstrumentation.runOnMainSync(
- () -> assertThat(getPreviewView(scenario).getScaleType()).isEqualTo(
+ () -> assertThat(getPreviewView(mScenario).getScaleType()).isEqualTo(
PreviewView.ScaleType.FIT_END));
}
@Test
public void checkSameImplementationModeAfterStopResume() {
- final FragmentScenario<PreviewViewFragment> scenario = createScenario();
- assertPreviewUpdating(scenario);
+ assertPreviewUpdating(mScenario);
mInstrumentation.runOnMainSync(
- () -> getPreviewView(scenario).setImplementationMode(
+ () -> getPreviewView(mScenario).setImplementationMode(
PreviewView.ImplementationMode.COMPATIBLE));
// Stop the fragment
- scenario.moveToState(Lifecycle.State.CREATED);
+ mScenario.moveToState(Lifecycle.State.CREATED);
// Resume the fragment
- scenario.moveToState(Lifecycle.State.RESUMED);
+ mScenario.moveToState(Lifecycle.State.RESUMED);
mInstrumentation.runOnMainSync(
- () -> assertThat(getPreviewView(scenario).getImplementationMode()).isEqualTo(
+ () -> assertThat(getPreviewView(mScenario).getImplementationMode()).isEqualTo(
PreviewView.ImplementationMode.COMPATIBLE));
}
diff --git a/collection/OWNERS b/collection/OWNERS
index a02ebce..0e9710d 100644
--- a/collection/OWNERS
+++ b/collection/OWNERS
@@ -1,2 +1,3 @@
-jakew@google.com
-android@jakewharton.com
+lukhnos@google.com
+dustinlam@google.com
+yboyar@google.com
diff --git a/collection/collection/src/test/java/androidx/collection/LruCacheTest.java b/collection/collection/src/test/java/androidx/collection/LruCacheTest.java
new file mode 100644
index 0000000..ab173be
--- /dev/null
+++ b/collection/collection/src/test/java/androidx/collection/LruCacheTest.java
@@ -0,0 +1,512 @@
+/*
+ * Copyright 2020 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.collection;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+@RunWith(JUnit4.class)
+public class LruCacheTest {
+
+ private int mExpectedCreateCount;
+ private int mExpectedPutCount;
+ private int mExpectedHitCount;
+ private int mExpectedMissCount;
+ private int mExpectedEvictionCount;
+
+ @Test
+ public void testStatistics() {
+ LruCache<String, String> cache = new LruCache<String, String>(3);
+ assertStatistics(cache);
+ assertEquals(null, cache.put("a", "A"));
+ mExpectedPutCount++;
+ assertStatistics(cache);
+ assertHit(cache, "a", "A");
+ assertSnapshot(cache, "a", "A");
+ assertEquals(null, cache.put("b", "B"));
+ mExpectedPutCount++;
+ assertStatistics(cache);
+ assertHit(cache, "a", "A");
+ assertHit(cache, "b", "B");
+ assertSnapshot(cache, "a", "A", "b", "B");
+ assertEquals(null, cache.put("c", "C"));
+ mExpectedPutCount++;
+ assertStatistics(cache);
+ assertHit(cache, "a", "A");
+ assertHit(cache, "b", "B");
+ assertHit(cache, "c", "C");
+ assertSnapshot(cache, "a", "A", "b", "B", "c", "C");
+ assertEquals(null, cache.put("d", "D"));
+ mExpectedPutCount++;
+ mExpectedEvictionCount++; // a should have been evicted
+ assertStatistics(cache);
+ assertMiss(cache, "a");
+ assertHit(cache, "b", "B");
+ assertHit(cache, "c", "C");
+ assertHit(cache, "d", "D");
+ assertHit(cache, "b", "B");
+ assertHit(cache, "c", "C");
+ assertSnapshot(cache, "d", "D", "b", "B", "c", "C");
+ assertEquals(null, cache.put("e", "E"));
+ mExpectedPutCount++;
+ mExpectedEvictionCount++; // d should have been evicted
+ assertStatistics(cache);
+ assertMiss(cache, "d");
+ assertMiss(cache, "a");
+ assertHit(cache, "e", "E");
+ assertHit(cache, "b", "B");
+ assertHit(cache, "c", "C");
+ assertSnapshot(cache, "e", "E", "b", "B", "c", "C");
+ }
+
+ @Test
+ public void testStatisticsWithCreate() {
+ LruCache<String, String> cache = newCreatingCache();
+ assertStatistics(cache);
+ assertCreated(cache, "aa", "created-aa");
+ assertHit(cache, "aa", "created-aa");
+ assertSnapshot(cache, "aa", "created-aa");
+ assertCreated(cache, "bb", "created-bb");
+ assertMiss(cache, "c");
+ assertSnapshot(cache, "aa", "created-aa", "bb", "created-bb");
+ assertCreated(cache, "cc", "created-cc");
+ assertSnapshot(cache, "aa", "created-aa", "bb", "created-bb", "cc", "created-cc");
+ mExpectedEvictionCount++; // aa will be evicted
+ assertCreated(cache, "dd", "created-dd");
+ assertSnapshot(cache, "bb", "created-bb", "cc", "created-cc", "dd", "created-dd");
+ mExpectedEvictionCount++; // bb will be evicted
+ assertCreated(cache, "aa", "created-aa");
+ assertSnapshot(cache, "cc", "created-cc", "dd", "created-dd", "aa", "created-aa");
+ }
+
+ @Test
+ public void testCreateOnCacheMiss() {
+ LruCache<String, String> cache = newCreatingCache();
+ String created = cache.get("aa");
+ assertEquals("created-aa", created);
+ }
+
+ @Test
+ public void testNoCreateOnCacheHit() {
+ LruCache<String, String> cache = newCreatingCache();
+ cache.put("aa", "put-aa");
+ assertEquals("put-aa", cache.get("aa"));
+ }
+
+ @Test
+ public void testConstructorDoesNotAllowZeroCacheSize() {
+ try {
+ new LruCache<String, String>(0);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testCannotPutNullKey() {
+ LruCache<String, String> cache = new LruCache<String, String>(3);
+ try {
+ cache.put(null, "A");
+ fail();
+ } catch (NullPointerException expected) {
+ }
+ }
+
+ @Test
+ public void testCannotPutNullValue() {
+ LruCache<String, String> cache = new LruCache<String, String>(3);
+ try {
+ cache.put("a", null);
+ fail();
+ } catch (NullPointerException expected) {
+ }
+ }
+
+ @Test
+ public void testToString() {
+ LruCache<String, String> cache = new LruCache<String, String>(3);
+ assertEquals("LruCache[maxSize=3,hits=0,misses=0,hitRate=0%]", cache.toString());
+ cache.put("a", "A");
+ cache.put("b", "B");
+ cache.put("c", "C");
+ cache.put("d", "D");
+ cache.get("a"); // miss
+ cache.get("b"); // hit
+ cache.get("c"); // hit
+ cache.get("d"); // hit
+ cache.get("e"); // miss
+ assertEquals("LruCache[maxSize=3,hits=3,misses=2,hitRate=60%]", cache.toString());
+ }
+
+ @Test
+ public void testEvictionWithSingletonCache() {
+ LruCache<String, String> cache = new LruCache<String, String>(1);
+ cache.put("a", "A");
+ cache.put("b", "B");
+ assertSnapshot(cache, "b", "B");
+ }
+
+ @Test
+ public void testEntryEvictedWhenFull() {
+ List<String> log = new ArrayList<String>();
+ LruCache<String, String> cache = newRemovalLogCache(log);
+ cache.put("a", "A");
+ cache.put("b", "B");
+ cache.put("c", "C");
+ assertEquals(Collections.<String>emptyList(), log);
+ cache.put("d", "D");
+ assertEquals(Arrays.asList("a=A"), log);
+ }
+
+ /**
+ * Replacing the value for a key doesn't cause an eviction but it does bring
+ * the replaced entry to the front of the queue.
+ */
+ @Test
+ public void testPutCauseEviction() {
+ List<String> log = new ArrayList<String>();
+ LruCache<String, String> cache = newRemovalLogCache(log);
+ cache.put("a", "A");
+ cache.put("b", "B");
+ cache.put("c", "C");
+ cache.put("b", "B2");
+ assertEquals(Arrays.asList("b=B>B2"), log);
+ assertSnapshot(cache, "a", "A", "c", "C", "b", "B2");
+ }
+
+ @Test
+ public void testCustomSizesImpactsSize() {
+ LruCache<String, String> cache = new LruCache<String, String>(10) {
+ @Override protected int sizeOf(String key, String value) {
+ return key.length() + value.length();
+ }
+ };
+ assertEquals(0, cache.size());
+ cache.put("a", "AA");
+ assertEquals(3, cache.size());
+ cache.put("b", "BBBB");
+ assertEquals(8, cache.size());
+ cache.put("a", "");
+ assertEquals(6, cache.size());
+ }
+
+ @Test
+ public void testEvictionWithCustomSizes() {
+ LruCache<String, String> cache = new LruCache<String, String>(4) {
+ @Override protected int sizeOf(String key, String value) {
+ return value.length();
+ }
+ };
+ cache.put("a", "AAAA");
+ assertSnapshot(cache, "a", "AAAA");
+ cache.put("b", "BBBB"); // should evict a
+ assertSnapshot(cache, "b", "BBBB");
+ cache.put("c", "CC"); // should evict b
+ assertSnapshot(cache, "c", "CC");
+ cache.put("d", "DD");
+ assertSnapshot(cache, "c", "CC", "d", "DD");
+ cache.put("e", "E"); // should evict c
+ assertSnapshot(cache, "d", "DD", "e", "E");
+ cache.put("f", "F");
+ assertSnapshot(cache, "d", "DD", "e", "E", "f", "F");
+ cache.put("g", "G"); // should evict d
+ assertSnapshot(cache, "e", "E", "f", "F", "g", "G");
+ cache.put("h", "H");
+ assertSnapshot(cache, "e", "E", "f", "F", "g", "G", "h", "H");
+ cache.put("i", "III"); // should evict e, f, and g
+ assertSnapshot(cache, "h", "H", "i", "III");
+ cache.put("j", "JJJ"); // should evict h and i
+ assertSnapshot(cache, "j", "JJJ");
+ }
+
+ @Test
+ public void testEvictionThrowsWhenSizesAreInconsistent() {
+ LruCache<String, int[]> cache = new LruCache<String, int[]>(4) {
+ @Override protected int sizeOf(String key, int[] value) {
+ return value[0];
+ }
+ };
+ int[] a = { 4 };
+ cache.put("a", a);
+ // get the cache size out of sync
+ a[0] = 1;
+ assertEquals(4, cache.size());
+ // evict something
+ try {
+ cache.put("b", new int[] { 2 });
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ @Test
+ public void testEvictionThrowsWhenSizesAreNegative() {
+ LruCache<String, String> cache = new LruCache<String, String>(4) {
+ @Override protected int sizeOf(String key, String value) {
+ return -1;
+ }
+ };
+ try {
+ cache.put("a", "A");
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ /**
+ * Naive caches evict at most one element at a time. This is problematic
+ * because evicting a small element may be insufficient to make room for a
+ * large element.
+ */
+ @Test
+ public void testDifferentElementSizes() {
+ LruCache<String, String> cache = new LruCache<String, String>(10) {
+ @Override protected int sizeOf(String key, String value) {
+ return value.length();
+ }
+ };
+ cache.put("a", "1");
+ cache.put("b", "12345678");
+ cache.put("c", "1");
+ assertSnapshot(cache, "a", "1", "b", "12345678", "c", "1");
+ cache.put("d", "12345678"); // should evict a and b
+ assertSnapshot(cache, "c", "1", "d", "12345678");
+ cache.put("e", "12345678"); // should evict c and d
+ assertSnapshot(cache, "e", "12345678");
+ }
+
+ @Test
+ public void testEvictAll() {
+ List<String> log = new ArrayList<String>();
+ LruCache<String, String> cache = newRemovalLogCache(log);
+ cache.put("a", "A");
+ cache.put("b", "B");
+ cache.put("c", "C");
+ cache.evictAll();
+ assertEquals(0, cache.size());
+ assertEquals(Arrays.asList("a=A", "b=B", "c=C"), log);
+ }
+
+ @Test
+ public void testEvictAllEvictsSizeZeroElements() {
+ LruCache<String, String> cache = new LruCache<String, String>(10) {
+ @Override protected int sizeOf(String key, String value) {
+ return 0;
+ }
+ };
+ cache.put("a", "A");
+ cache.put("b", "B");
+ cache.evictAll();
+ assertSnapshot(cache);
+ }
+
+ @Test
+ public void testRemoveWithCustomSizes() {
+ LruCache<String, String> cache = new LruCache<String, String>(10) {
+ @Override protected int sizeOf(String key, String value) {
+ return value.length();
+ }
+ };
+ cache.put("a", "123456");
+ cache.put("b", "1234");
+ cache.remove("a");
+ assertEquals(4, cache.size());
+ }
+
+ @Test
+ public void testRemoveAbsentElement() {
+ LruCache<String, String> cache = new LruCache<String, String>(10);
+ cache.put("a", "A");
+ cache.put("b", "B");
+ assertEquals(null, cache.remove("c"));
+ assertEquals(2, cache.size());
+ }
+
+ @Test
+ public void testRemoveNullThrows() {
+ LruCache<String, String> cache = new LruCache<String, String>(10);
+ try {
+ cache.remove(null);
+ fail();
+ } catch (NullPointerException expected) {
+ }
+ }
+
+ @Test
+ public void testRemoveCallsEntryRemoved() {
+ List<String> log = new ArrayList<String>();
+ LruCache<String, String> cache = newRemovalLogCache(log);
+ cache.put("a", "A");
+ cache.remove("a");
+ assertEquals(Arrays.asList("a=A>null"), log);
+ }
+
+ @Test
+ public void testPutCallsEntryRemoved() {
+ List<String> log = new ArrayList<String>();
+ LruCache<String, String> cache = newRemovalLogCache(log);
+ cache.put("a", "A");
+ cache.put("a", "A2");
+ assertEquals(Arrays.asList("a=A>A2"), log);
+ }
+
+ @Test
+ public void testEntryRemovedIsCalledWithoutSynchronization() {
+ LruCache<String, String> cache = new LruCache<String, String>(3) {
+ @Override protected void entryRemoved(
+ boolean evicted, String key, String oldValue, String newValue) {
+ assertFalse(Thread.holdsLock(this));
+ }
+ };
+ cache.put("a", "A");
+ cache.put("a", "A2"); // replaced
+ cache.put("b", "B");
+ cache.put("c", "C");
+ cache.put("d", "D"); // single eviction
+ cache.remove("a"); // removed
+ cache.evictAll(); // multiple eviction
+ }
+
+ @Test
+ public void testCreateIsCalledWithoutSynchronization() {
+ LruCache<String, String> cache = new LruCache<String, String>(3) {
+ @Override protected String create(String key) {
+ assertFalse(Thread.holdsLock(this));
+ return null;
+ }
+ };
+ cache.get("a");
+ }
+
+ /**
+ * Test what happens when a value is added to the map while create is
+ * working. The map value should be returned by get(), and the created value
+ * should be released with entryRemoved().
+ */
+ @Test
+ public void testCreateWithConcurrentPut() {
+ final List<String> log = new ArrayList<String>();
+ LruCache<String, String> cache = new LruCache<String, String>(3) {
+ @Override protected String create(String key) {
+ put(key, "B");
+ return "A";
+ }
+ @Override protected void entryRemoved(
+ boolean evicted, String key, String oldValue, String newValue) {
+ log.add(key + "=" + oldValue + ">" + newValue);
+ }
+ };
+ assertEquals("B", cache.get("a"));
+ assertEquals(Arrays.asList("a=A>B"), log);
+ }
+
+ /**
+ * Test what happens when two creates happen concurrently. The result from
+ * the first create to return is returned by both gets. The other created
+ * values should be released with entryRemove().
+ */
+ @Test
+ public void testCreateWithConcurrentCreate() {
+ final List<String> log = new ArrayList<String>();
+ LruCache<String, Integer> cache = new LruCache<String, Integer>(3) {
+ int mCallCount = 0;
+ @Override protected Integer create(String key) {
+ if (mCallCount++ == 0) {
+ assertEquals(2, get(key).intValue());
+ return 1;
+ } else {
+ return 2;
+ }
+ }
+ @Override protected void entryRemoved(
+ boolean evicted, String key, Integer oldValue, Integer newValue) {
+ log.add(key + "=" + oldValue + ">" + newValue);
+ }
+ };
+ assertEquals(2, cache.get("a").intValue());
+ assertEquals(Arrays.asList("a=1>2"), log);
+ }
+
+ private LruCache<String, String> newCreatingCache() {
+ return new LruCache<String, String>(3) {
+ @Override protected String create(String key) {
+ return (key.length() > 1) ? ("created-" + key) : null;
+ }
+ };
+ }
+
+ private LruCache<String, String> newRemovalLogCache(final List<String> log) {
+ return new LruCache<String, String>(3) {
+ @Override protected void entryRemoved(
+ boolean evicted, String key, String oldValue, String newValue) {
+ String message = evicted
+ ? (key + "=" + oldValue)
+ : (key + "=" + oldValue + ">" + newValue);
+ log.add(message);
+ }
+ };
+ }
+
+ private void assertHit(LruCache<String, String> cache, String key, String value) {
+ assertEquals(value, cache.get(key));
+ mExpectedHitCount++;
+ assertStatistics(cache);
+ }
+
+ private void assertMiss(LruCache<String, String> cache, String key) {
+ assertEquals(null, cache.get(key));
+ mExpectedMissCount++;
+ assertStatistics(cache);
+ }
+
+ private void assertCreated(LruCache<String, String> cache, String key, String value) {
+ assertEquals(value, cache.get(key));
+ mExpectedMissCount++;
+ mExpectedCreateCount++;
+ assertStatistics(cache);
+ }
+
+ private void assertStatistics(LruCache<?, ?> cache) {
+ assertEquals("create count", mExpectedCreateCount, cache.createCount());
+ assertEquals("put count", mExpectedPutCount, cache.putCount());
+ assertEquals("hit count", mExpectedHitCount, cache.hitCount());
+ assertEquals("miss count", mExpectedMissCount, cache.missCount());
+ assertEquals("eviction count", mExpectedEvictionCount, cache.evictionCount());
+ }
+
+ @SuppressWarnings("unchecked")
+ private <T> void assertSnapshot(LruCache<T, T> cache, T... keysAndValues) {
+ List<T> actualKeysAndValues = new ArrayList<T>();
+ for (Map.Entry<T, T> entry : cache.snapshot().entrySet()) {
+ actualKeysAndValues.add(entry.getKey());
+ actualKeysAndValues.add(entry.getValue());
+ }
+ // assert using lists because order is important for LRUs
+ assertEquals(Arrays.asList(keysAndValues), actualKeysAndValues);
+ }
+}
diff --git a/compose/androidview/androidview/integration-tests/androidview-demos/src/main/java/androidx/compose/androidview/demos/AndroidViewDemos.kt b/compose/androidview/androidview/integration-tests/androidview-demos/src/main/java/androidx/compose/androidview/demos/AndroidViewDemos.kt
index 26b6d6a..26e38e4 100644
--- a/compose/androidview/androidview/integration-tests/androidview-demos/src/main/java/androidx/compose/androidview/demos/AndroidViewDemos.kt
+++ b/compose/androidview/androidview/integration-tests/androidview-demos/src/main/java/androidx/compose/androidview/demos/AndroidViewDemos.kt
@@ -17,6 +17,7 @@
package androidx.compose.androidview.demos
import androidx.compose.integration.demos.common.ActivityDemo
+import androidx.compose.integration.demos.common.ComposableDemo
import androidx.compose.integration.demos.common.DemoCategory
val AndroidViewDemos = DemoCategory(
@@ -25,6 +26,7 @@
ComposeInAndroidDemos,
AndroidInComposeDemos,
ComplexTouchInterop,
- ActivityDemo("WebComponent", WebComponentActivity::class)
+ ActivityDemo("WebComponent", WebComponentActivity::class),
+ ComposableDemo("TextField Interop") { EditTextInteropDemo() },
)
)
diff --git a/compose/androidview/androidview/integration-tests/androidview-demos/src/main/java/androidx/compose/androidview/demos/FocusInteropAndroidInCompose.kt b/compose/androidview/androidview/integration-tests/androidview-demos/src/main/java/androidx/compose/androidview/demos/FocusInteropAndroidInCompose.kt
new file mode 100644
index 0000000..be35f52
--- /dev/null
+++ b/compose/androidview/androidview/integration-tests/androidview-demos/src/main/java/androidx/compose/androidview/demos/FocusInteropAndroidInCompose.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2020 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.androidview.demos
+
+import android.annotation.SuppressLint
+import android.widget.EditText
+import android.widget.LinearLayout
+import android.widget.RelativeLayout
+import android.widget.TextView
+import androidx.compose.foundation.layout.Arrangement.SpaceEvenly
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.material.Text
+import androidx.compose.material.TextField
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Recomposer
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment.Companion.CenterVertically
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.ExperimentalFocus
+import androidx.compose.ui.platform.setContent
+import androidx.compose.ui.text.input.TextFieldValue
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.viewinterop.AndroidView
+
+@OptIn(ExperimentalFocus::class)
+@Composable
+fun EditTextInteropDemo() {
+ Column {
+ Row(horizontalArrangement = SpaceEvenly, verticalAlignment = CenterVertically) {
+ Text("TextField in Compose:")
+ val text = remember { mutableStateOf(TextFieldValue()) }
+ TextField(text.value, onValueChange = { text.value = it })
+ }
+ Spacer(Modifier.height(20.dp))
+ Row(horizontalArrangement = SpaceEvenly, verticalAlignment = CenterVertically) {
+ AndroidView({
+ LinearLayout(it).apply {
+ this.orientation = LinearLayout.VERTICAL
+ addView(
+ LinearLayout(it).apply {
+ addView(
+ TextView(it).apply {
+ @SuppressLint("SetTextI18n")
+ text = "EditText within AndroidView:"
+ }
+ )
+ addView(EditText(it).apply { width = 500 })
+ }
+ )
+ addView(
+ LinearLayout(it).apply {
+ addView(
+ TextView(it).apply {
+ @SuppressLint("SetTextI18n")
+ text = "TextField within AndroidView:"
+ }
+ )
+ addView(
+ RelativeLayout(it).apply {
+ setContent(Recomposer.current()) {
+ val text = remember { mutableStateOf(TextFieldValue()) }
+ TextField(text.value, onValueChange = { text.value = it })
+ }
+ }
+ )
+ }
+ )
+ }
+ })
+ }
+ }
+}
diff --git a/compose/foundation/foundation-layout/lint-baseline.xml b/compose/foundation/foundation-layout/lint-baseline.xml
index 7ee29fa..93c34dac 100644
--- a/compose/foundation/foundation-layout/lint-baseline.xml
+++ b/compose/foundation/foundation-layout/lint-baseline.xml
@@ -8,30 +8,8 @@
errorLine2=" ^">
<location
file="src/androidMain/kotlin/androidx/compose/foundation/layout/ConstraintLayout.kt"
- line="533"
+ line="534"
column="26"/>
</issue>
- <issue
- id="ModifierInspectorInfo"
- message="Modifier missing inspectorInfo"
- errorLine1="fun Modifier.preferredWidth(intrinsicSize: IntrinsicSize) = when (intrinsicSize) {"
- errorLine2=" ~~~~~~">
- <location
- file="src/commonMain/kotlin/androidx/compose/foundation/layout/Intrinsic.kt"
- line="47"
- column="61"/>
- </issue>
-
- <issue
- id="ModifierInspectorInfo"
- message="Modifier missing inspectorInfo"
- errorLine1="fun Modifier.preferredHeight(intrinsicSize: IntrinsicSize) = when (intrinsicSize) {"
- errorLine2=" ~~~~~~">
- <location
- file="src/commonMain/kotlin/androidx/compose/foundation/layout/Intrinsic.kt"
- line="68"
- column="62"/>
- </issue>
-
</issues>
diff --git a/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/LayoutAlignTest.kt b/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/LayoutAlignTest.kt
index 568d9d4..5e1e41f 100644
--- a/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/LayoutAlignTest.kt
+++ b/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/LayoutAlignTest.kt
@@ -274,7 +274,7 @@
).enforce(incomingConstraints)
val placeable = measurable.measure(constraints)
layout(placeable.width, placeable.height) {
- placeable.placeRelative(Offset.Zero)
+ placeable.placeRelative(IntOffset.Zero)
}
}
)
diff --git a/compose/foundation/foundation-text/lint-baseline.xml b/compose/foundation/foundation-text/lint-baseline.xml
deleted file mode 100644
index dcff3ff..0000000
--- a/compose/foundation/foundation-text/lint-baseline.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-alpha15" client="gradle" variant="debug" version="4.2.0-alpha15">
-
- <issue
- id="ModifierInspectorInfo"
- message="Modifier missing inspectorInfo"
- errorLine1=" return this"
- errorLine2=" ^">
- <location
- file="src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt"
- line="630"
- column="12"/>
- </issue>
-
- <issue
- id="ModifierInspectorInfo"
- message="Modifier missing inspectorInfo"
- errorLine1=") = composed {"
- errorLine2=" ~~~~~~~~">
- <location
- file="src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt"
- line="671"
- column="5"/>
- </issue>
-
-</issues>
diff --git a/compose/foundation/foundation-text/src/commonMain/kotlin/androidx/compose/foundation/text/CoreText.kt b/compose/foundation/foundation-text/src/commonMain/kotlin/androidx/compose/foundation/text/CoreText.kt
index c572b80..977e99c 100644
--- a/compose/foundation/foundation-text/src/commonMain/kotlin/androidx/compose/foundation/text/CoreText.kt
+++ b/compose/foundation/foundation-text/src/commonMain/kotlin/androidx/compose/foundation/text/CoreText.kt
@@ -65,6 +65,7 @@
import androidx.compose.ui.text.subSequence
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.util.annotation.VisibleForTesting
import androidx.compose.ui.util.fastForEach
import kotlin.math.floor
@@ -286,7 +287,7 @@
maxHeight = floor(it.height).toInt()
)
),
- Offset(it.left, it.top)
+ IntOffset(it.left.roundToInt(), it.top.roundToInt())
)
}
}
diff --git a/compose/foundation/foundation-text/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt b/compose/foundation/foundation-text/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt
index 16d719e..d4c105b 100644
--- a/compose/foundation/foundation-text/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt
+++ b/compose/foundation/foundation-text/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt
@@ -663,6 +663,7 @@
private val DefaultCursorThickness = 2.dp
@OptIn(InternalTextApi::class)
+@Suppress("ModifierInspectorInfo")
private fun Modifier.cursor(
state: TextFieldState,
value: TextFieldValue,
diff --git a/compose/foundation/foundation/api/current.txt b/compose/foundation/foundation/api/current.txt
index f2ecd13..db4d7902 100644
--- a/compose/foundation/foundation/api/current.txt
+++ b/compose/foundation/foundation/api/current.txt
@@ -59,13 +59,13 @@
}
public final class IconKt {
+ method @Deprecated @androidx.compose.runtime.Composable public static void Icon-1Wn-iBs(androidx.compose.ui.graphics.ImageBitmap bitmap, optional androidx.compose.ui.Modifier modifier, optional long tint);
method @Deprecated @androidx.compose.runtime.Composable public static void Icon-1eoVtfs(androidx.compose.ui.graphics.painter.Painter painter, optional androidx.compose.ui.Modifier modifier, optional long tint);
method @Deprecated @androidx.compose.runtime.Composable public static void Icon-QSAJrGw(androidx.compose.ui.graphics.vector.VectorAsset asset, optional androidx.compose.ui.Modifier modifier, optional long tint);
- method @Deprecated @androidx.compose.runtime.Composable public static void Icon-uUQrhdE(androidx.compose.ui.graphics.ImageAsset asset, optional androidx.compose.ui.Modifier modifier, optional long tint);
}
public final class ImageKt {
- method @androidx.compose.runtime.Composable public static inline void Image(androidx.compose.ui.graphics.ImageAsset asset, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter);
+ method @androidx.compose.runtime.Composable public static inline void Image(androidx.compose.ui.graphics.ImageBitmap bitmap, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter);
method @androidx.compose.runtime.Composable public static inline void Image(androidx.compose.ui.graphics.vector.VectorAsset asset, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter);
method @androidx.compose.runtime.Composable public static void Image(androidx.compose.ui.graphics.painter.Painter painter, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter);
}
@@ -142,6 +142,7 @@
method public float getMaxValue();
method public float getValue();
method public boolean isAnimationRunning();
+ method public suspend Object? scroll(kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
method public void scrollBy(float value);
method public void scrollTo(float value);
method public void smoothScrollBy(float value, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> spec, optional kotlin.jvm.functions.Function2<? super androidx.compose.animation.core.AnimationEndReason,? super java.lang.Float,kotlin.Unit> onEnd);
@@ -248,10 +249,16 @@
method public suspend Object? tryAwaitRelease(kotlin.coroutines.Continuation<? super java.lang.Boolean> p);
}
+ public interface ScrollScope {
+ method public float scrollBy(float pixels);
+ }
+
public final class ScrollableController {
ctor public ScrollableController(internal kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> consumeScrollDelta, internal androidx.compose.foundation.animation.FlingConfig flingConfig, androidx.compose.animation.core.AnimationClockObservable animationClock, internal androidx.compose.foundation.InteractionState? interactionState);
method public boolean isAnimationRunning();
+ method public suspend Object? scroll(kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
method public void smoothScrollBy(float value, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> spec, optional kotlin.jvm.functions.Function2<? super androidx.compose.animation.core.AnimationEndReason,? super java.lang.Float,kotlin.Unit> onEnd);
+ method public suspend Object? smoothScrollBy(float value, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> spec, optional kotlin.coroutines.Continuation<? super java.lang.Float> p);
method public void stopAnimation();
property public final boolean isAnimationRunning;
}
@@ -323,6 +330,9 @@
method public int getFirstVisibleItemIndex();
method public int getFirstVisibleItemScrollOffset();
method public boolean isAnimationRunning();
+ method public suspend Object? scroll(kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+ method public suspend Object? smoothScrollBy(float value, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> spec, optional kotlin.coroutines.Continuation<? super java.lang.Float> p);
+ method public suspend Object? snapToItemIndex(@IntRange(from=0) int index, optional @IntRange(from=0) int scrollOffset, optional kotlin.coroutines.Continuation<? super kotlin.Unit> p);
property public final int firstVisibleItemIndex;
property public final int firstVisibleItemScrollOffset;
property public final boolean isAnimationRunning;
diff --git a/compose/foundation/foundation/api/public_plus_experimental_current.txt b/compose/foundation/foundation/api/public_plus_experimental_current.txt
index f2ecd13..db4d7902 100644
--- a/compose/foundation/foundation/api/public_plus_experimental_current.txt
+++ b/compose/foundation/foundation/api/public_plus_experimental_current.txt
@@ -59,13 +59,13 @@
}
public final class IconKt {
+ method @Deprecated @androidx.compose.runtime.Composable public static void Icon-1Wn-iBs(androidx.compose.ui.graphics.ImageBitmap bitmap, optional androidx.compose.ui.Modifier modifier, optional long tint);
method @Deprecated @androidx.compose.runtime.Composable public static void Icon-1eoVtfs(androidx.compose.ui.graphics.painter.Painter painter, optional androidx.compose.ui.Modifier modifier, optional long tint);
method @Deprecated @androidx.compose.runtime.Composable public static void Icon-QSAJrGw(androidx.compose.ui.graphics.vector.VectorAsset asset, optional androidx.compose.ui.Modifier modifier, optional long tint);
- method @Deprecated @androidx.compose.runtime.Composable public static void Icon-uUQrhdE(androidx.compose.ui.graphics.ImageAsset asset, optional androidx.compose.ui.Modifier modifier, optional long tint);
}
public final class ImageKt {
- method @androidx.compose.runtime.Composable public static inline void Image(androidx.compose.ui.graphics.ImageAsset asset, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter);
+ method @androidx.compose.runtime.Composable public static inline void Image(androidx.compose.ui.graphics.ImageBitmap bitmap, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter);
method @androidx.compose.runtime.Composable public static inline void Image(androidx.compose.ui.graphics.vector.VectorAsset asset, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter);
method @androidx.compose.runtime.Composable public static void Image(androidx.compose.ui.graphics.painter.Painter painter, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter);
}
@@ -142,6 +142,7 @@
method public float getMaxValue();
method public float getValue();
method public boolean isAnimationRunning();
+ method public suspend Object? scroll(kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
method public void scrollBy(float value);
method public void scrollTo(float value);
method public void smoothScrollBy(float value, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> spec, optional kotlin.jvm.functions.Function2<? super androidx.compose.animation.core.AnimationEndReason,? super java.lang.Float,kotlin.Unit> onEnd);
@@ -248,10 +249,16 @@
method public suspend Object? tryAwaitRelease(kotlin.coroutines.Continuation<? super java.lang.Boolean> p);
}
+ public interface ScrollScope {
+ method public float scrollBy(float pixels);
+ }
+
public final class ScrollableController {
ctor public ScrollableController(internal kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> consumeScrollDelta, internal androidx.compose.foundation.animation.FlingConfig flingConfig, androidx.compose.animation.core.AnimationClockObservable animationClock, internal androidx.compose.foundation.InteractionState? interactionState);
method public boolean isAnimationRunning();
+ method public suspend Object? scroll(kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
method public void smoothScrollBy(float value, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> spec, optional kotlin.jvm.functions.Function2<? super androidx.compose.animation.core.AnimationEndReason,? super java.lang.Float,kotlin.Unit> onEnd);
+ method public suspend Object? smoothScrollBy(float value, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> spec, optional kotlin.coroutines.Continuation<? super java.lang.Float> p);
method public void stopAnimation();
property public final boolean isAnimationRunning;
}
@@ -323,6 +330,9 @@
method public int getFirstVisibleItemIndex();
method public int getFirstVisibleItemScrollOffset();
method public boolean isAnimationRunning();
+ method public suspend Object? scroll(kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+ method public suspend Object? smoothScrollBy(float value, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> spec, optional kotlin.coroutines.Continuation<? super java.lang.Float> p);
+ method public suspend Object? snapToItemIndex(@IntRange(from=0) int index, optional @IntRange(from=0) int scrollOffset, optional kotlin.coroutines.Continuation<? super kotlin.Unit> p);
property public final int firstVisibleItemIndex;
property public final int firstVisibleItemScrollOffset;
property public final boolean isAnimationRunning;
diff --git a/compose/foundation/foundation/api/restricted_current.txt b/compose/foundation/foundation/api/restricted_current.txt
index f2ecd13..db4d7902 100644
--- a/compose/foundation/foundation/api/restricted_current.txt
+++ b/compose/foundation/foundation/api/restricted_current.txt
@@ -59,13 +59,13 @@
}
public final class IconKt {
+ method @Deprecated @androidx.compose.runtime.Composable public static void Icon-1Wn-iBs(androidx.compose.ui.graphics.ImageBitmap bitmap, optional androidx.compose.ui.Modifier modifier, optional long tint);
method @Deprecated @androidx.compose.runtime.Composable public static void Icon-1eoVtfs(androidx.compose.ui.graphics.painter.Painter painter, optional androidx.compose.ui.Modifier modifier, optional long tint);
method @Deprecated @androidx.compose.runtime.Composable public static void Icon-QSAJrGw(androidx.compose.ui.graphics.vector.VectorAsset asset, optional androidx.compose.ui.Modifier modifier, optional long tint);
- method @Deprecated @androidx.compose.runtime.Composable public static void Icon-uUQrhdE(androidx.compose.ui.graphics.ImageAsset asset, optional androidx.compose.ui.Modifier modifier, optional long tint);
}
public final class ImageKt {
- method @androidx.compose.runtime.Composable public static inline void Image(androidx.compose.ui.graphics.ImageAsset asset, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter);
+ method @androidx.compose.runtime.Composable public static inline void Image(androidx.compose.ui.graphics.ImageBitmap bitmap, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter);
method @androidx.compose.runtime.Composable public static inline void Image(androidx.compose.ui.graphics.vector.VectorAsset asset, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter);
method @androidx.compose.runtime.Composable public static void Image(androidx.compose.ui.graphics.painter.Painter painter, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter);
}
@@ -142,6 +142,7 @@
method public float getMaxValue();
method public float getValue();
method public boolean isAnimationRunning();
+ method public suspend Object? scroll(kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
method public void scrollBy(float value);
method public void scrollTo(float value);
method public void smoothScrollBy(float value, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> spec, optional kotlin.jvm.functions.Function2<? super androidx.compose.animation.core.AnimationEndReason,? super java.lang.Float,kotlin.Unit> onEnd);
@@ -248,10 +249,16 @@
method public suspend Object? tryAwaitRelease(kotlin.coroutines.Continuation<? super java.lang.Boolean> p);
}
+ public interface ScrollScope {
+ method public float scrollBy(float pixels);
+ }
+
public final class ScrollableController {
ctor public ScrollableController(internal kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> consumeScrollDelta, internal androidx.compose.foundation.animation.FlingConfig flingConfig, androidx.compose.animation.core.AnimationClockObservable animationClock, internal androidx.compose.foundation.InteractionState? interactionState);
method public boolean isAnimationRunning();
+ method public suspend Object? scroll(kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
method public void smoothScrollBy(float value, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> spec, optional kotlin.jvm.functions.Function2<? super androidx.compose.animation.core.AnimationEndReason,? super java.lang.Float,kotlin.Unit> onEnd);
+ method public suspend Object? smoothScrollBy(float value, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> spec, optional kotlin.coroutines.Continuation<? super java.lang.Float> p);
method public void stopAnimation();
property public final boolean isAnimationRunning;
}
@@ -323,6 +330,9 @@
method public int getFirstVisibleItemIndex();
method public int getFirstVisibleItemScrollOffset();
method public boolean isAnimationRunning();
+ method public suspend Object? scroll(kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+ method public suspend Object? smoothScrollBy(float value, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> spec, optional kotlin.coroutines.Continuation<? super java.lang.Float> p);
+ method public suspend Object? snapToItemIndex(@IntRange(from=0) int index, optional @IntRange(from=0) int scrollOffset, optional kotlin.coroutines.Continuation<? super kotlin.Unit> p);
property public final int firstVisibleItemIndex;
property public final int firstVisibleItemScrollOffset;
property public final boolean isAnimationRunning;
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/ListDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/ListDemos.kt
index 49c6f1e..8c7e262 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/ListDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/ListDemos.kt
@@ -23,6 +23,7 @@
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayout
+import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
@@ -47,15 +48,19 @@
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.DensityAmbient
import androidx.compose.ui.platform.LayoutDirectionAmbient
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.paging.compose.demos.PagingDemos
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.launch
import kotlin.random.Random
val LazyListDemos = listOf(
@@ -113,13 +118,62 @@
private fun ListHoistedStateDemo() {
val interactionState = remember { InteractionState() }
val state = rememberLazyListState(interactionState = interactionState)
+ var lastScrollDescription: String by remember { mutableStateOf("") }
Column {
- Text(
- "First item: ${state.firstVisibleItemIndex}",
- fontSize = 30.sp
- )
- Text("Dragging: ${interactionState.contains(Interaction.Dragged)}", fontSize = 30.sp)
- Text("Flinging: ${state.isAnimationRunning}", fontSize = 30.sp)
+ FlowRow {
+ val buttonModifier = Modifier.padding(8.dp)
+ val density = DensityAmbient.current
+ val coroutineScope = rememberCoroutineScope()
+ Button(
+ modifier = buttonModifier,
+ onClick = {
+ coroutineScope.launch {
+ state.snapToItemIndex(state.firstVisibleItemIndex - 1)
+ }
+ }
+ ) {
+ Text("Previous")
+ }
+ Button(
+ modifier = buttonModifier,
+ onClick = {
+ coroutineScope.launch {
+ state.snapToItemIndex(state.firstVisibleItemIndex + 1)
+ }
+ }
+ ) {
+ Text("Next")
+ }
+ Button(
+ modifier = buttonModifier,
+ onClick = {
+ with(density) {
+ coroutineScope.launch {
+ val requestedScroll = 3000.dp.toPx()
+ lastScrollDescription = try {
+ val actualScroll = state.smoothScrollBy(requestedScroll)
+ "$actualScroll/$requestedScroll px"
+ } catch (_: CancellationException) {
+ "Interrupted!"
+ }
+ }
+ }
+ }
+ ) {
+ Text("Scroll")
+ }
+ }
+ Column {
+ Text(
+ "First item: ${state.firstVisibleItemIndex}, Last scroll: $lastScrollDescription",
+ fontSize = 20.sp
+ )
+ Text(
+ "Dragging: ${interactionState.contains(Interaction.Dragged)}, " +
+ "Flinging: ${state.isAnimationRunning}",
+ fontSize = 20.sp
+ )
+ }
LazyColumnFor(
(0..1000).toList(),
Modifier.fillMaxWidth(),
@@ -237,7 +291,10 @@
val items = listOf(Color.Cyan, Color.Blue, Color.Magenta)
itemsIndexed(items) { index, item ->
- Box(modifier = Modifier.background(item).size(40.dp), alignment = Alignment.Center) {
+ Box(
+ modifier = Modifier.background(item).size(40.dp),
+ alignment = Alignment.Center
+ ) {
Text("$index", fontSize = 30.sp)
}
}
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/ImageSamples.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/ImageSamples.kt
index 038f28f..d3cf9e1 100644
--- a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/ImageSamples.kt
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/ImageSamples.kt
@@ -27,7 +27,7 @@
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
-import androidx.compose.ui.graphics.ImageAsset
+import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.painter.ImagePainter
@@ -41,19 +41,19 @@
@Sampled
@Composable
fun ImageSample() {
- val imageAsset = createTestImage()
- // Lays out and draws an image sized to the dimensions of the ImageAsset
- Image(asset = imageAsset)
+ val ImageBitmap = createTestImage()
+ // Lays out and draws an image sized to the dimensions of the ImageBitmap
+ Image(bitmap = ImageBitmap)
}
@Sampled
@Composable
fun ImagePainterSubsectionSample() {
- val imageAsset = createTestImage()
- // Lays out and draws an image sized to the rectangular subsection of the ImageAsset
+ val ImageBitmap = createTestImage()
+ // Lays out and draws an image sized to the rectangular subsection of the ImageBitmap
Image(
painter = ImagePainter(
- imageAsset,
+ ImageBitmap,
IntOffset(10, 12),
IntSize(50, 60)
)
@@ -93,13 +93,13 @@
}
/**
- * Helper method to create an ImageAsset with some content in it
+ * Helper method to create an ImageBitmap with some content in it
*/
-private fun createTestImage(): ImageAsset {
- val imageAsset = ImageAsset(100, 100)
- Canvas(imageAsset).drawCircle(
+private fun createTestImage(): ImageBitmap {
+ val ImageBitmap = ImageBitmap(100, 100)
+ Canvas(ImageBitmap).drawCircle(
Offset(50.0f, 50.0f), 50.0f,
Paint().apply { this.color = Color.Cyan }
)
- return imageAsset
+ return ImageBitmap
}
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ImageTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ImageTest.kt
index 06ce451..96f64f1 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ImageTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ImageTest.kt
@@ -31,7 +31,7 @@
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.ImageAsset
+import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.asAndroidBitmap
@@ -78,8 +78,8 @@
@get:Rule
val rule = createComposeRule()
- private fun createImageAsset(): ImageAsset {
- val image = ImageAsset(imageWidth, imageHeight)
+ private fun createImageBitmap(): ImageBitmap {
+ val image = ImageBitmap(imageWidth, imageHeight)
val path = Path().apply {
lineTo(imageWidth.toFloat(), imageHeight.toFloat())
lineTo(0.0f, imageHeight.toFloat())
@@ -108,7 +108,7 @@
.background(color = Color.White)
.wrapContentSize(Alignment.Center)
) {
- Image(modifier = Modifier.testTag(contentTag), asset = createImageAsset())
+ Image(modifier = Modifier.testTag(contentTag), bitmap = createImageBitmap())
}
}
@@ -151,7 +151,7 @@
) {
Image(
ImagePainter(
- createImageAsset(),
+ createImageBitmap(),
IntOffset(
imageWidth / 2 - subsectionWidth / 2,
imageHeight / 2 - subsectionHeight / 2
@@ -249,10 +249,10 @@
.wrapContentSize(Alignment.Center)
) {
// The resultant Image composable should be twice the size of the underlying
- // ImageAsset that is to be drawn and will stretch the content to fit
+ // ImageBitmap that is to be drawn and will stretch the content to fit
// the bounds
Image(
- asset = createImageAsset(),
+ bitmap = createImageBitmap(),
modifier = Modifier
.testTag(contentTag)
.preferredSize(
@@ -303,11 +303,11 @@
rule.setContent {
val density = DensityAmbient.current
val size = (containerSize * 2 / density.density).dp
- val imageAsset = ImageAsset(imageWidth, imageHeight)
+ val ImageBitmap = ImageBitmap(imageWidth, imageHeight)
CanvasDrawScope().draw(
density,
LayoutDirection.Ltr,
- Canvas(imageAsset),
+ Canvas(ImageBitmap),
Size(imageWidth.toFloat(), imageHeight.toFloat())
) {
drawRect(color = Color.Blue)
@@ -318,7 +318,7 @@
.wrapContentSize(Alignment.Center)
) {
Image(
- asset = imageAsset,
+ bitmap = ImageBitmap,
modifier = Modifier
.testTag(contentTag)
.preferredSize(
@@ -348,16 +348,16 @@
.wrapContentSize(Alignment.Center)
) {
// The resultant Image composable should be twice the size of the underlying
- // ImageAsset that is to be drawn in the bottom end section of the composable
+ // ImageBitmap that is to be drawn in the bottom end section of the composable
Image(
- asset = createImageAsset(),
+ bitmap = createImageBitmap(),
modifier = Modifier
.testTag(contentTag)
.preferredSize(
(imageComposableWidth / density).dp,
(imageComposableHeight / density).dp
),
- // Intentionally do not scale up the contents of the ImageAsset
+ // Intentionally do not scale up the contents of the ImageBitmap
contentScale = ContentScale.Inside,
alignment = Alignment.BottomEnd
)
@@ -485,7 +485,7 @@
fun testContentScaleCropRespectsMaxDimension() {
val testTag = "testTag"
rule.setContent {
- val asset = with(ImageAsset(100, 100)) {
+ val asset = with(ImageBitmap(100, 100)) {
with(Canvas(this)) {
val paint = Paint().apply { this.color = Color.Blue }
drawRect(0f, 0f, 100f, 100f, paint)
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
index d180291..125537a 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
@@ -37,7 +37,6 @@
import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.ExperimentalTesting
-import androidx.compose.ui.test.TestUiDispatcher
import androidx.compose.ui.test.center
import androidx.compose.ui.test.down
import androidx.compose.ui.test.junit4.createComposeRule
@@ -55,7 +54,9 @@
import androidx.test.filters.LargeTest
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
-import kotlinx.coroutines.withContext
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
import org.junit.After
import org.junit.Before
import org.junit.Rule
@@ -106,7 +107,7 @@
duration = 100.milliseconds
)
}
- advanceClockAndAwaitAnimation(clock)
+ advanceClockWhileAwaitersExist(clock)
val lastTotal = rule.runOnIdle {
assertThat(total).isGreaterThan(0)
@@ -119,7 +120,7 @@
duration = 100.milliseconds
)
}
- advanceClockAndAwaitAnimation(clock)
+ advanceClockWhileAwaitersExist(clock)
rule.runOnIdle {
assertThat(total).isEqualTo(lastTotal)
@@ -131,7 +132,7 @@
duration = 100.milliseconds
)
}
- advanceClockAndAwaitAnimation(clock)
+ advanceClockWhileAwaitersExist(clock)
rule.runOnIdle {
assertThat(total).isLessThan(0.01f)
}
@@ -162,7 +163,7 @@
duration = 100.milliseconds
)
}
- advanceClockAndAwaitAnimation(clock)
+ advanceClockWhileAwaitersExist(clock)
val lastTotal = rule.runOnIdle {
assertThat(total).isGreaterThan(0)
@@ -175,7 +176,7 @@
duration = 100.milliseconds
)
}
- advanceClockAndAwaitAnimation(clock)
+ advanceClockWhileAwaitersExist(clock)
rule.runOnIdle {
assertThat(total).isEqualTo(lastTotal)
@@ -187,7 +188,7 @@
duration = 100.milliseconds
)
}
- advanceClockAndAwaitAnimation(clock)
+ advanceClockWhileAwaitersExist(clock)
rule.runOnIdle {
assertThat(total).isLessThan(0.01f)
}
@@ -231,7 +232,7 @@
assertThat(startTrigger).isEqualTo(1)
assertThat(stopTrigger).isEqualTo(0)
}
- advanceClockAndAwaitAnimation(clock)
+ advanceClockWhileAwaitersExist(clock)
// after wait we expect stop to trigger
rule.runOnIdle {
assertThat(startTrigger).isEqualTo(1)
@@ -266,7 +267,7 @@
duration = 100.milliseconds
)
}
- advanceClockAndAwaitAnimation(clock)
+ advanceClockWhileAwaitersExist(clock)
val prevTotal = rule.runOnIdle {
assertThat(total).isGreaterThan(0f)
enabled.value = false
@@ -279,7 +280,7 @@
duration = 100.milliseconds
)
}
- advanceClockAndAwaitAnimation(clock)
+ advanceClockWhileAwaitersExist(clock)
rule.runOnIdle {
assertThat(total).isEqualTo(prevTotal)
}
@@ -379,7 +380,7 @@
@Test
@OptIn(ExperimentalTesting::class)
- fun scrollable_cancel_callsDragStop() = runBlockingWithManualClock { clock ->
+ fun scrollable_cancel_callsDragStop() = runBlocking {
var total by mutableStateOf(0f)
var dragStopped = 0f
val controller = ScrollableController(
@@ -388,7 +389,7 @@
it
},
flingConfig = FlingConfig(decayAnimation = ExponentialDecay()),
- animationClock = monotonicFrameAnimationClockOf(coroutineContext, clock)
+ animationClock = monotonicFrameAnimationClockOf(coroutineContext)
)
setScrollableContent {
if (total < 20) {
@@ -418,7 +419,7 @@
@Test
@OptIn(ExperimentalTesting::class)
- fun scrollable_snappingScrolling() = runBlockingWithManualClock(true) { clock ->
+ fun scrollable_snappingScrolling() = runBlocking {
var total = 0f
val controller = ScrollableController(
consumeScrollDelta = {
@@ -426,33 +427,24 @@
it
},
flingConfig = FlingConfig(decayAnimation = ExponentialDecay()),
- animationClock = monotonicFrameAnimationClockOf(coroutineContext, clock)
+ animationClock = monotonicFrameAnimationClockOf(coroutineContext)
)
setScrollableContent {
Modifier.scrollable(orientation = Orientation.Vertical, controller = controller)
}
- rule.runOnIdle {
- assertThat(total).isEqualTo(0f)
- }
- rule.runOnIdle {
- controller.smoothScrollBy(1000f)
- }
- advanceClockAndAwaitAnimation(clock)
- rule.runOnIdle {
- assertThat(total).isEqualTo(1000f)
- }
- rule.runOnIdle {
- controller.smoothScrollBy(-200f)
- }
- advanceClockAndAwaitAnimation(clock)
- rule.runOnIdle {
- assertThat(total).isEqualTo(800f)
- }
+ rule.awaitIdle()
+ assertThat(total).isEqualTo(0f)
+
+ controller.smoothScrollBy(1000f)
+ assertThat(total).isWithin(0.001f).of(1000f)
+
+ controller.smoothScrollBy(-200f)
+ assertThat(total).isWithin(0.001f).of(800f)
}
@Test
@OptIn(ExperimentalTesting::class)
- fun scrollable_explicitDisposal() = runBlockingWithManualClock(true) { clock ->
+ fun scrollable_explicitDisposal() = runBlockingWithManualClock { clock ->
val disposed = mutableStateOf(false)
var total = 0f
val controller = ScrollableController(
@@ -471,25 +463,24 @@
Modifier
}
}
- rule.runOnIdle {
+ launch {
controller.smoothScrollBy(300f)
}
- advanceClockAndAwaitAnimation(clock)
- rule.runOnIdle {
- assertThat(total).isEqualTo(300f)
- }
- rule.runOnIdle {
+ advanceClockWhileAwaitersExist(clock)
+ assertThat(total).isEqualTo(300f)
+
+ launch {
controller.smoothScrollBy(200f)
}
// don't advance clocks yet, toggle disposed value
- rule.runOnUiThread {
- disposed.value = true
- }
- advanceClockAndAwaitAnimation(clock)
+ disposed.value = true
+
+ // Modifier should now have been disposed and cancelled the scroll, advance clocks to
+ // confirm that it does not animate (checked in consumeScrollDelta)
+ advanceClockWhileAwaitersExist(clock)
+
// still 300 and didn't fail in onScrollConsumptionRequested.. lambda
- rule.runOnIdle {
- assertThat(total).isEqualTo(300f)
- }
+ assertThat(total).isEqualTo(300f)
}
@Test
@@ -550,8 +541,8 @@
assertThat(outerDrag).isEqualTo(innerDrag)
innerDrag
}
- advanceClockAndAwaitAnimation(clock)
- advanceClockAndAwaitAnimation(clock)
+ advanceClockWhileAwaitersExist(clock)
+ advanceClockWhileAwaitersExist(clock)
// and nothing should change as we don't do nested fling
rule.runOnIdle {
assertThat(outerDrag).isEqualTo(lastEqualDrag)
@@ -560,7 +551,7 @@
@Test
@OptIn(ExperimentalTesting::class)
- fun scrollable_interactionState() = runBlockingWithManualClock { clock ->
+ fun scrollable_interactionState() = runBlocking {
val interactionState = InteractionState()
var total = 0f
val controller = ScrollableController(
@@ -569,7 +560,7 @@
it
},
flingConfig = FlingConfig(decayAnimation = ExponentialDecay()),
- animationClock = monotonicFrameAnimationClockOf(coroutineContext, clock),
+ animationClock = monotonicFrameAnimationClockOf(coroutineContext),
interactionState = interactionState
)
@@ -606,7 +597,7 @@
@Test
@OptIn(ExperimentalTesting::class)
- fun scrollable_interactionState_resetWhenDisposed() = runBlockingWithManualClock { clock ->
+ fun scrollable_interactionState_resetWhenDisposed() = runBlocking {
val interactionState = InteractionState()
var emitScrollableBox by mutableStateOf(true)
var total = 0f
@@ -616,7 +607,7 @@
it
},
flingConfig = FlingConfig(decayAnimation = ExponentialDecay()),
- animationClock = monotonicFrameAnimationClockOf(coroutineContext, clock),
+ animationClock = monotonicFrameAnimationClockOf(coroutineContext),
interactionState = interactionState
)
@@ -697,10 +688,13 @@
}
@ExperimentalTesting
- private suspend fun advanceClockAndAwaitAnimation(clock: ManualFrameClock) {
- rule.waitForIdle()
- withContext(TestUiDispatcher.Main) {
+ private suspend fun advanceClockWhileAwaitersExist(clock: ManualFrameClock) {
+ rule.awaitIdle()
+ yield()
+ while (clock.hasAwaiters) {
clock.advanceClockMillis(5000L)
+ // Give awaiters the chance to await again
+ yield()
}
}
}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/TextFieldCursorTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/TextFieldCursorTest.kt
index 929634e..dba6330 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/TextFieldCursorTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/TextFieldCursorTest.kt
@@ -31,7 +31,7 @@
import androidx.compose.ui.focus.isFocused
import androidx.compose.ui.focusObserver
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.ImageAsset
+import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.test.captureToImage
import androidx.compose.ui.test.hasInputMethodsSupport
@@ -254,7 +254,7 @@
}
}
- private fun ImageAsset.assertCursor(cursorWidth: Dp, density: Density) {
+ private fun ImageBitmap.assertCursor(cursorWidth: Dp, density: Density) {
val cursorWidthPx = (with(density) { cursorWidth.toIntPx() })
val width = width
val height = height
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyScrollTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyScrollTest.kt
new file mode 100644
index 0000000..2f40c7c
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyScrollTest.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2020 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.lazy
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.preferredHeight
+import androidx.compose.foundation.layout.preferredWidth
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.gesture.scrollorientationlocking.Orientation
+import androidx.compose.ui.test.ExperimentalTesting
+import androidx.compose.ui.test.TestUiDispatcher
+import androidx.compose.ui.unit.dp
+import androidx.test.filters.MediumTest
+import androidx.ui.test.createComposeRule
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import kotlin.math.roundToInt
+
+@MediumTest
+@OptIn(ExperimentalTesting::class)
+@RunWith(Parameterized::class)
+class LazyScrollTest(private val orientation: Orientation) {
+ private val LazyListTag = "LazyListTag"
+
+ @get:Rule
+ val rule = createComposeRule()
+
+ private val vertical: Boolean
+ get() = orientation == Orientation.Vertical
+
+ private val items = (1..20).toList()
+ private lateinit var state: LazyListState
+
+ @Before
+ fun setup() {
+ rule.setContent {
+ state = rememberLazyListState()
+ TestContent()
+ }
+ }
+
+ @Test
+ fun testSetupWorks() {
+ assertThat(state.firstVisibleItemIndex).isEqualTo(0)
+ assertThat(state.firstVisibleItemScrollOffset).isEqualTo(0)
+ }
+
+ @Test
+ fun snapToItemTest() = runBlocking {
+ withContext(TestUiDispatcher.Main) {
+ state.snapToItemIndex(3)
+ }
+ assertThat(state.firstVisibleItemIndex).isEqualTo(3)
+ assertThat(state.firstVisibleItemScrollOffset).isEqualTo(0)
+ }
+
+ @ExperimentalFoundationApi
+ @Test
+ fun smoothScrollByTest() = runBlocking {
+ withContext(TestUiDispatcher.Main) {
+ state.smoothScrollBy(with(rule.density) { 320.dp.toPx() })
+ }
+ assertThat(state.firstVisibleItemIndex).isEqualTo(3)
+ assertThat(state.firstVisibleItemScrollOffset)
+ .isEqualTo(with(rule.density) { 17.dp.toPx().roundToInt() })
+ }
+
+ @Composable
+ private fun TestContent() {
+ if (vertical) {
+ LazyColumnFor(items, Modifier.preferredHeight(300.dp), state) {
+ ItemContent()
+ }
+ } else {
+ LazyRowFor(items, Modifier.preferredWidth(300.dp), state) {
+ ItemContent()
+ }
+ }
+ }
+
+ @Composable
+ private fun LazyItemScope.ItemContent() {
+ val modifier = if (vertical) {
+ Modifier.preferredHeight(101.dp)
+ } else {
+ Modifier.preferredWidth(101.dp)
+ }
+ Spacer(modifier)
+ }
+
+ companion object {
+ @JvmStatic
+ @Parameterized.Parameters(name = "{0}")
+ fun params() = arrayOf(Orientation.Vertical, Orientation.Horizontal)
+ }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Icon.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Icon.kt
index 59b24a9..632b4c3 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Icon.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Icon.kt
@@ -27,7 +27,7 @@
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
-import androidx.compose.ui.graphics.ImageAsset
+import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.painter.ImagePainter
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.VectorAsset
@@ -61,11 +61,11 @@
}
/**
- * Icon component that draws [asset] using [tint], defaulting to [AmbientContentColor].
+ * Icon component that draws [bitmap] using [tint], defaulting to [AmbientContentColor].
*
- * @param asset [ImageAsset] to draw inside this Icon
+ * @param bitmap [ImageBitmap] to draw inside this Icon
* @param modifier optional [Modifier] for this Icon
- * @param tint tint to be applied to [asset]. If [Color.Unspecified] is provided, then no
+ * @param tint tint to be applied to [bitmap]. If [Color.Unspecified] is provided, then no
* tint is applied
*/
@Deprecated(
@@ -74,11 +74,11 @@
)
@Composable
fun Icon(
- asset: ImageAsset,
+ bitmap: ImageBitmap,
modifier: Modifier = Modifier,
tint: Color = AmbientContentColor.current
) {
- val painter = remember(asset) { ImagePainter(asset) }
+ val painter = remember(bitmap) { ImagePainter(bitmap) }
@Suppress("DEPRECATION")
Icon(
painter = painter,
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Image.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Image.kt
index 0dfd90e..1750ac9e 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Image.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Image.kt
@@ -26,7 +26,7 @@
import androidx.compose.ui.draw.paint
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.DefaultAlpha
-import androidx.compose.ui.graphics.ImageAsset
+import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.painter.ColorPainter
import androidx.compose.ui.graphics.painter.ImagePainter
import androidx.compose.ui.graphics.painter.Painter
@@ -35,42 +35,42 @@
import androidx.compose.ui.layout.ContentScale
/**
- * A composable that lays out and draws a given [ImageAsset]. This will attempt to
- * size the composable according to the [ImageAsset]'s given width and height. However, an
+ * A composable that lays out and draws a given [ImageBitmap]. This will attempt to
+ * size the composable according to the [ImageBitmap]'s given width and height. However, an
* optional [Modifier] parameter can be provided to adjust sizing or draw additional content (ex.
- * background). Any unspecified dimension will leverage the [ImageAsset]'s size as a minimum
+ * background). Any unspecified dimension will leverage the [ImageBitmap]'s size as a minimum
* constraint.
*
* The following sample shows basic usage of an Image composable to position and draw an
- * [ImageAsset] on screen
+ * [ImageBitmap] on screen
* @sample androidx.compose.foundation.samples.ImageSample
*
- * For use cases that require drawing a rectangular subset of the [ImageAsset] consumers can use
+ * For use cases that require drawing a rectangular subset of the [ImageBitmap] consumers can use
* overload that consumes a [Painter] parameter shown in this sample
* @sample androidx.compose.foundation.samples.ImagePainterSubsectionSample
*
- * @param asset The [ImageAsset] to draw.
+ * @param bitmap The [ImageBitmap] to draw.
* @param modifier Modifier used to adjust the layout algorithm or draw decoration content (ex.
* background)
- * @param alignment Optional alignment parameter used to place the [ImageAsset] in the given
+ * @param alignment Optional alignment parameter used to place the [ImageBitmap] in the given
* bounds defined by the width and height.
* @param contentScale Optional scale parameter used to determine the aspect ratio scaling to be used
- * if the bounds are a different size from the intrinsic size of the [ImageAsset].
- * @param alpha Optional opacity to be applied to the [ImageAsset] when it is rendered onscreen
- * @param colorFilter Optional ColorFilter to apply for the [ImageAsset] when it is rendered
+ * if the bounds are a different size from the intrinsic size of the [ImageBitmap].
+ * @param alpha Optional opacity to be applied to the [ImageBitmap] when it is rendered onscreen
+ * @param colorFilter Optional ColorFilter to apply for the [ImageBitmap] when it is rendered
* onscreen
*/
@Suppress("NOTHING_TO_INLINE")
@Composable
inline fun Image(
- asset: ImageAsset,
+ bitmap: ImageBitmap,
modifier: Modifier = Modifier,
alignment: Alignment = Alignment.Center,
contentScale: ContentScale = ContentScale.Fit,
alpha: Float = DefaultAlpha,
colorFilter: ColorFilter? = null
) {
- val imagePainter = remember(asset) { ImagePainter(asset) }
+ val imagePainter = remember(bitmap) { ImagePainter(bitmap) }
Image(
painter = imagePainter,
modifier = modifier,
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Scroll.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Scroll.kt
index 5c689ed..4ba5a84 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Scroll.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Scroll.kt
@@ -24,13 +24,14 @@
import androidx.compose.animation.core.SpringSpec
import androidx.compose.foundation.animation.FlingConfig
import androidx.compose.foundation.animation.defaultFlingConfig
+import androidx.compose.foundation.gestures.ScrollScope
import androidx.compose.foundation.gestures.ScrollableController
import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
-import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.InternalLayoutApi
+import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.padding
@@ -44,21 +45,21 @@
import androidx.compose.runtime.setValue
import androidx.compose.runtime.structuralEqualityPolicy
import androidx.compose.ui.Alignment
-import androidx.compose.ui.layout.LayoutModifier
-import androidx.compose.ui.layout.Measurable
-import androidx.compose.ui.layout.MeasureScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.gesture.scrollorientationlocking.Orientation
+import androidx.compose.ui.layout.LayoutModifier
+import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
import androidx.compose.ui.platform.AnimationClockAmbient
import androidx.compose.ui.platform.LayoutDirectionAmbient
import androidx.compose.ui.platform.debugInspectorInfo
import androidx.compose.ui.semantics.AccessibilityScrollState
+import androidx.compose.ui.semantics.horizontalAccessibilityScrollState
import androidx.compose.ui.semantics.scrollBy
import androidx.compose.ui.semantics.semantics
-import androidx.compose.ui.semantics.horizontalAccessibilityScrollState
import androidx.compose.ui.semantics.verticalAccessibilityScrollState
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.LayoutDirection
@@ -159,6 +160,22 @@
)
/**
+ * Call this function to take control of scrolling and gain the ability to send scroll events
+ * via [ScrollScope.scrollBy]. All actions that change the logical scroll position must be
+ * performed within a [scroll] block (even if they don't call any other methods on this
+ * object) in order to guarantee that mutual exclusion is enforced.
+ *
+ * Cancels the currently running scroll, if any, and suspends until the cancellation is
+ * complete.
+ *
+ * If [scroll] is called from elsewhere, this will be canceled.
+ */
+ @OptIn(ExperimentalFoundationApi::class)
+ suspend fun scroll(
+ block: suspend ScrollScope.() -> Unit
+ ): Unit = scrollableController.scroll(block)
+
+ /**
* Stop any ongoing animation, smooth scrolling or fling occurring on this [ScrollState]
*/
fun stopAnimation() {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
index 11506ee..f08153c 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
@@ -24,12 +24,17 @@
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.SpringSpec
+import androidx.compose.animation.core.VectorConverter
+import androidx.compose.animation.core.spring
+import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Interaction
import androidx.compose.foundation.InteractionState
import androidx.compose.foundation.animation.FlingConfig
import androidx.compose.foundation.animation.defaultFlingConfig
import androidx.compose.foundation.animation.fling
+import androidx.compose.runtime.AtomicReference
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.dispatch.withFrameMillis
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.onDispose
import androidx.compose.runtime.remember
@@ -41,6 +46,10 @@
import androidx.compose.ui.gesture.scrollorientationlocking.Orientation
import androidx.compose.ui.platform.AnimationClockAmbient
import androidx.compose.ui.platform.debugInspectorInfo
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
/**
* Create and remember [ScrollableController] for [scrollable] with default [FlingConfig] and
@@ -66,6 +75,18 @@
}
/**
+ * Scope used for suspending scroll blocks
+ */
+interface ScrollScope {
+ /**
+ * Attempts to scroll forward by [pixels] px.
+ *
+ * @return the amount of the requested scroll that was consumed (that is, how far it scrolled)
+ */
+ fun scrollBy(pixels: Float): Float
+}
+
+/**
* Controller to control the [scrollable] modifier with. Contains necessary information about the
* ongoing fling and provides smooth scrolling capabilities.
*
@@ -100,6 +121,90 @@
animatedFloat.animateTo(to, anim = spec, onEnd = onEnd)
}
+ /**
+ * Smooth scroll by [value] pixels.
+ *
+ * Cancels the currently running scroll, if any, and suspends until the cancellation is
+ * complete.
+ *
+ * @param value delta to scroll by
+ * @param spec [AnimationSpec] to be used for this smooth scrolling
+ *
+ * @return the amount of scroll consumed
+ */
+ @OptIn(ExperimentalFoundationApi::class)
+ suspend fun smoothScrollBy(
+ value: Float,
+ spec: AnimationSpec<Float> = spring()
+ ): Float {
+ val animSpec = spec.vectorize(Float.VectorConverter)
+ val conv = Float.VectorConverter
+ val zeroVector = conv.convertToVector(0f)
+ val targetVector = conv.convertToVector(value)
+ var previousValue = 0f
+
+ scroll {
+ val startTimeMillis = withFrameMillis { it }
+ do {
+ val finished = withFrameMillis { frameTimeMillis ->
+ val newValue = conv.convertFromVector(
+ animSpec.getValue(
+ playTime = frameTimeMillis - startTimeMillis,
+ start = zeroVector,
+ end = targetVector,
+ // TODO: figure out if/how we should incorporate existing velocity
+ startVelocity = zeroVector
+ )
+ )
+ val delta = newValue - previousValue
+ val consumed = scrollBy(delta)
+
+ if (consumed != delta) {
+ previousValue += consumed
+ true
+ } else {
+ previousValue = newValue
+ previousValue == value
+ }
+ }
+ } while (!finished)
+ }
+ return previousValue
+ }
+
+ private val scrollControlJob = AtomicReference<Job?>(null)
+ private val scrollControlMutex = Mutex()
+
+ private val scrollScope: ScrollScope = object : ScrollScope {
+ override fun scrollBy(pixels: Float): Float = consumeScrollDelta(pixels)
+ }
+
+ /**
+ * Call this function to take control of scrolling and gain the ability to send scroll events
+ * via [ScrollScope.scrollBy]. All actions that change the logical scroll position must be
+ * performed within a [scroll] block (even if they don't call any other methods on this
+ * object) in order to guarantee that mutual exclusion is enforced.
+ *
+ * Cancels the currently running scroll, if any, and suspends until the cancellation is
+ * complete.
+ *
+ * If [scroll] is called from elsewhere, this will be canceled.
+ */
+ suspend fun scroll(
+ block: suspend ScrollScope.() -> Unit
+ ): Unit = coroutineScope {
+ stopFlingAnimation()
+ val currentJob = coroutineContext[Job]
+ scrollControlJob.getAndSet(currentJob)?.cancel()
+ scrollControlMutex.withLock(currentJob) {
+ // TODO: this is a workaround to make isAnimationRunning work for now by considering all
+ // suspend scrolls to be animations
+ isAnimationRunningState.value = true
+ scrollScope.block()
+ isAnimationRunningState.value = false
+ }
+ }
+
private val isAnimationRunningState = mutableStateOf(false)
private val clocksProxy: AnimationClockObservable = object : AnimationClockObservable {
@@ -115,20 +220,43 @@
}
/**
- * whether this [ScrollableController] is currently animating/flinging
+ * whether this [ScrollableController] is currently scrolling via [scroll].
+ *
+ * Note: **all** scrolls initiated via [scroll] are considered to be animations, regardless of
+ * whether they are actually performing an animation. For instance, gestures that perform
+ * scrolls via `scroll
*/
val isAnimationRunning
get() = isAnimationRunningState.value
/**
+ * The current velocity of the fling animation.
+ *
+ * Useful for handoff between animations
+ */
+ internal val velocity: Float
+ get() = animatedFloat.velocity
+
+ /**
* Stop any ongoing animation, smooth scrolling or fling
*
* Call this to stop receiving scrollable deltas in [consumeScrollDelta]
*/
- fun stopAnimation() {
+ internal fun stopFlingAnimation() {
animatedFloat.stop()
}
+ /**
+ * Stop any ongoing animation, smooth scrolling, fling, or any other scroll occurring via
+ * [scroll].
+ *
+ * Call this to stop receiving scrollable deltas in [consumeScrollDelta]
+ */
+ fun stopAnimation() {
+ stopFlingAnimation()
+ scrollControlJob.getAndSet(null)?.cancel()
+ }
+
private val animatedFloat =
DeltaAnimatedFloat(0f, clocksProxy, consumeScrollDelta)
@@ -196,7 +324,7 @@
override fun onStart(downPosition: Offset) {
if (enabled) {
- controller.stopAnimation()
+ controller.stopFlingAnimation()
controller.interactionState?.addInteraction(Interaction.Dragged)
onScrollStarted(downPosition)
}
@@ -204,7 +332,7 @@
override fun onScroll(scrollDistance: Float): Float {
if (!enabled) return 0f
- controller.stopAnimation()
+ controller.stopFlingAnimation()
val toConsume = if (reverseDirection) scrollDistance * -1 else scrollDistance
val consumed = controller.consumeScrollDelta(toConsume)
controller.value = controller.value + consumed
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyDsl.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyDsl.kt
index db0e9e1..e3ebd07 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyDsl.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyDsl.kt
@@ -194,8 +194,7 @@
contentPadding = contentPadding,
verticalAlignment = verticalAlignment,
isVertical = false
- ) {
- index ->
+ ) { index ->
scope.contentFor(index, this)
}
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt
index 9c78a7a..31d7c21 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt
@@ -49,7 +49,8 @@
modifier
.scrollable(
orientation = if (isVertical) Orientation.Vertical else Orientation.Horizontal,
- reverseDirection = reverseDirection,
+ // reverse scroll by default, to have "natural" gesture that goes reversed to layout
+ reverseDirection = !reverseDirection,
controller = state.scrollableController
)
.clipToBounds()
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt
index c42dcb8..842c0e1 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt
@@ -18,11 +18,15 @@
import androidx.compose.animation.asDisposableClock
import androidx.compose.animation.core.AnimationClockObservable
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.spring
+import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Interaction
import androidx.compose.foundation.InteractionState
import androidx.compose.foundation.animation.FlingConfig
import androidx.compose.foundation.animation.defaultFlingConfig
import androidx.compose.foundation.assertNotNestingScrollableContainers
+import androidx.compose.foundation.gestures.ScrollScope
import androidx.compose.foundation.gestures.ScrollableController
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
@@ -137,7 +141,11 @@
val firstVisibleItemScrollOffset: Int get() = scrollPosition.observableScrollOffset
/**
- * whether the Lazy list with this state is currently animating/flinging
+ * whether this [LazyListState] is currently scrolling via [scroll] or via an
+ * animation/fling.
+ *
+ * Note: **all** scrolls initiated via [scroll] are considered to be animations, regardless of
+ * whether they are actually performing an animation.
*/
val isAnimationRunning
get() = scrollableController.isAnimationRunning
@@ -156,7 +164,7 @@
ScrollableController(
flingConfig = flingConfig,
animationClock = animationClock,
- consumeScrollDelta = { onScroll(it) },
+ consumeScrollDelta = { -onScroll(-it) },
interactionState = interactionState
)
@@ -181,21 +189,64 @@
}
}
- // currently used by the desktop for scrollbars. to be made public
- internal suspend fun snapToItemIndex(
- @IntRange(from = 0) index: Int,
- @IntRange(from = 0) scrollOffset: Int = 0
- ) {
+ /**
+ * Instantly brings the item at [index] to the top of the viewport, offset by [scrollOffset]
+ * pixels.
+ *
+ * Cancels the currently running scroll, if any, and suspends until the cancellation is
+ * complete.
+ *
+ * @param index the data index to snap to
+ * @param scrollOffset the number of pixels past the start of the item to snap to
+ */
+ @OptIn(ExperimentalFoundationApi::class)
+ suspend fun snapToItemIndex(
+ @IntRange(from = 0)
+ index: Int,
+ @IntRange(from = 0)
+ scrollOffset: Int = 0
+ ) = scrollableController.scroll {
scrollPosition.update(
index = DataIndex(index),
- // scrollOffset can only be positive
- scrollOffset = maxOf(scrollOffset, 0),
+ scrollOffset = scrollOffset,
// `true` will be replaced with the real value during the forceRemeasure() execution
canScrollForward = true
)
remeasurement.forceRemeasure()
}
+ /**
+ * Call this function to take control of scrolling and gain the ability to send scroll events
+ * via [ScrollScope.scrollBy]. All actions that change the logical scroll position must be
+ * performed within a [scroll] block (even if they don't call any other methods on this
+ * object) in order to guarantee that mutual exclusion is enforced.
+ *
+ * Cancels the currently running scroll, if any, and suspends until the cancellation is
+ * complete.
+ *
+ * If [scroll] is called from elsewhere, this will be canceled.
+ */
+ @OptIn(ExperimentalFoundationApi::class)
+ suspend fun scroll(
+ block: suspend ScrollScope.() -> Unit
+ ): Unit = scrollableController.scroll(block)
+
+ /**
+ * Smooth scroll by [value] pixels.
+ *
+ * Cancels the currently running scroll, if any, and suspends until the cancellation is
+ * complete.
+ *
+ * @param value delta to scroll by
+ * @param spec [AnimationSpec] to be used for this smooth scrolling
+ *
+ * @return the amount of scroll consumed
+ */
+ suspend fun smoothScrollBy(
+ value: Float,
+ spec: AnimationSpec<Float> = spring()
+ ): Float = scrollableController.smoothScrollBy(value, spec)
+
// TODO: Coroutine scrolling APIs will allow this to be private again once we have more
// fine-grained control over scrolling
@VisibleForTesting
@@ -227,7 +278,7 @@
// We did not consume all of it - return the rest to be consumed elsewhere (e.g.,
// nested scrolling)
scrollToBeConsumed = 0f // We're not consuming the rest, give it back
- scrollableController.stopAnimation()
+ scrollableController.stopFlingAnimation()
return scrollConsumed
}
}
diff --git a/compose/integration-tests/benchmark/src/androidTest/java/androidx/compose/ui/text/ParagraphBenchmark.kt b/compose/integration-tests/benchmark/src/androidTest/java/androidx/compose/ui/text/ParagraphBenchmark.kt
index bd15771..8f82350 100644
--- a/compose/integration-tests/benchmark/src/androidTest/java/androidx/compose/ui/text/ParagraphBenchmark.kt
+++ b/compose/integration-tests/benchmark/src/androidTest/java/androidx/compose/ui/text/ParagraphBenchmark.kt
@@ -25,7 +25,7 @@
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.sp
import androidx.compose.ui.graphics.Canvas
-import androidx.compose.ui.graphics.ImageAsset
+import androidx.compose.ui.graphics.ImageBitmap
import androidx.ui.integration.test.Alphabet
import androidx.ui.integration.test.RandomTextGenerator
import androidx.ui.integration.test.TextBenchmarkTestRule
@@ -184,7 +184,7 @@
val (text, style) = text(textGenerator)
val paragraph = paragraph(text, style, width)
val canvas = Canvas(
- ImageAsset(paragraph.width.roundToInt(), paragraph.height.roundToInt())
+ ImageBitmap(paragraph.width.roundToInt(), paragraph.height.roundToInt())
)
Pair(paragraph, canvas)
}
@@ -204,7 +204,7 @@
// some line breaking in the result
val paragraph = paragraph(text, style, width)
val canvas = Canvas(
- ImageAsset(paragraph.width.roundToInt(), paragraph.height.roundToInt())
+ ImageBitmap(paragraph.width.roundToInt(), paragraph.height.roundToInt())
)
// Paint for the first time, so that we only benchmark repaint.
paragraph.paint(canvas)
diff --git a/compose/integration-tests/benchmark/src/androidTest/java/androidx/compose/ui/text/TextDelegateBenchmark.kt b/compose/integration-tests/benchmark/src/androidTest/java/androidx/compose/ui/text/TextDelegateBenchmark.kt
index f948f8a..45ecf16 100644
--- a/compose/integration-tests/benchmark/src/androidTest/java/androidx/compose/ui/text/TextDelegateBenchmark.kt
+++ b/compose/integration-tests/benchmark/src/androidTest/java/androidx/compose/ui/text/TextDelegateBenchmark.kt
@@ -27,7 +27,7 @@
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.ImageAsset
+import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.Paint
import androidx.ui.integration.test.RandomTextGenerator
import androidx.ui.integration.test.TextBenchmarkTestRule
@@ -176,7 +176,7 @@
layoutDirection
)
val canvas = Canvas(
- ImageAsset(
+ ImageBitmap(
layoutResult.size.width,
layoutResult.size.height
)
@@ -201,7 +201,7 @@
layoutDirection
)
val canvas = Canvas(
- ImageAsset(layoutResult.size.width, layoutResult.size.height)
+ ImageBitmap(layoutResult.size.width, layoutResult.size.height)
)
benchmarkRule.measureRepeated {
diff --git a/compose/integration-tests/macro-benchmark/build.gradle b/compose/integration-tests/macro-benchmark/build.gradle
new file mode 100644
index 0000000..fd91ff0
--- /dev/null
+++ b/compose/integration-tests/macro-benchmark/build.gradle
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.Publish
+
+plugins {
+ id("AndroidXPlugin")
+ id("com.android.library")
+ id("kotlin-android")
+}
+
+android.defaultConfig.minSdkVersion 28
+
+dependencies {
+ androidTestImplementation(project(":benchmark:benchmark-macro-runtime"))
+ androidTestImplementation(ANDROIDX_TEST_RULES)
+ androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
+ androidTestImplementation(ANDROIDX_TEST_CORE)
+ androidTestImplementation(ANDROIDX_TEST_RUNNER)
+}
+
+// Define a task dependency so the app is installed before we run macro benchmarks.
+tasks.getByPath(':compose:integration-tests:macro-benchmark:connectedCheck')
+ .dependsOn(tasks.getByPath(':compose:integration-tests:demos:installRelease'))
diff --git a/compose/integration-tests/macro-benchmark/src/androidTest/AndroidManifest.xml b/compose/integration-tests/macro-benchmark/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..2c1e747
--- /dev/null
+++ b/compose/integration-tests/macro-benchmark/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="androidx.ui.macrobenchmark.test">
+
+ <!--
+ The Macro Benchmark Sample needs to launch activities in
+ `androidx.compose.integration.demos` APK.
+
+ The Macro Benchmark Library uses `PackageManager` to query for activities. This requires
+ the test APK to declare that `androidx.compose.integration.demos` be visible to
+ the APK (given Android 11's package visibility rules).
+ -->
+ <queries>
+ <package android:name="androidx.compose.integration.demos" />
+ </queries>
+</manifest>
diff --git a/compose/integration-tests/macro-benchmark/src/androidTest/java/androidx/ui/macrobenchmark/ProcessSpeedProfileValidation.kt b/compose/integration-tests/macro-benchmark/src/androidTest/java/androidx/ui/macrobenchmark/ProcessSpeedProfileValidation.kt
new file mode 100644
index 0000000..1fcb722
--- /dev/null
+++ b/compose/integration-tests/macro-benchmark/src/androidTest/java/androidx/ui/macrobenchmark/ProcessSpeedProfileValidation.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2020 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.ui.macrobenchmark
+
+import androidx.benchmark.macro.CompilationMode
+import androidx.benchmark.macro.CpuUsageMetric
+import androidx.benchmark.macro.MacrobenchmarkConfig
+import androidx.benchmark.macro.MacrobenchmarkRule
+import androidx.benchmark.macro.StartupTimingMetric
+import androidx.test.filters.LargeTest
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Macrobenchmark used for local validation of performance numbers coming from MacrobenchmarkRule.
+ */
+@LargeTest
+@RunWith(Parameterized::class)
+class ProcessSpeedProfileValidation(
+ private val compilationMode: CompilationMode,
+ private val killProcess: Boolean
+) {
+ @get:Rule
+ val benchmarkRule = MacrobenchmarkRule()
+
+ @Test
+ @Ignore("Not running the test in CI")
+ fun start() {
+ val config = MacrobenchmarkConfig(
+ packageName = PACKAGE_NAME,
+ metrics = listOf(CpuUsageMetric(), StartupTimingMetric()),
+ compilationMode = compilationMode,
+ killProcessEachIteration = killProcess,
+ iterations = 10
+ )
+ benchmarkRule.measureRepeated(config) {
+ pressHome()
+ launchPackageAndWait()
+ }
+ }
+
+ companion object {
+ private const val PACKAGE_NAME = "androidx.compose.integration.demos"
+
+ @Parameterized.Parameters(name = "compilation_mode={0}, kill_process={1}")
+ @JvmStatic
+ fun kilProcessParameters(): List<Array<Any>> {
+ val compilationModes = listOf(
+ CompilationMode.None,
+ CompilationMode.SpeedProfile(warmupIterations = 3)
+ )
+ val processKillOptions = listOf(true, false)
+ return compilationModes.zip(processKillOptions).map {
+ arrayOf(it.first, it.second)
+ }
+ }
+ }
+}
diff --git a/compose/integration-tests/macro-benchmark/src/androidTest/java/androidx/ui/macrobenchmark/StartupDemosMacrobenchmark.kt b/compose/integration-tests/macro-benchmark/src/androidTest/java/androidx/ui/macrobenchmark/StartupDemosMacrobenchmark.kt
new file mode 100644
index 0000000..f0c611b
--- /dev/null
+++ b/compose/integration-tests/macro-benchmark/src/androidTest/java/androidx/ui/macrobenchmark/StartupDemosMacrobenchmark.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2020 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.ui.macrobenchmark
+
+import androidx.benchmark.macro.MacrobenchmarkRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class StartupDemosMacrobenchmark {
+ @get:Rule
+ val benchmarkRule = MacrobenchmarkRule()
+
+ @Ignore("Not running the test in CI")
+ @Test
+ fun compiledColdStartup() = benchmarkRule.measureStartup(
+ profileCompiled = true,
+ coldLaunch = true
+ )
+
+ @Ignore("Not running the test in CI")
+ @Test
+ fun uncompiledColdStartup() = benchmarkRule.measureStartup(
+ profileCompiled = false,
+ coldLaunch = true
+ )
+}
diff --git a/compose/integration-tests/macro-benchmark/src/androidTest/java/androidx/ui/macrobenchmark/StartupUtils.kt b/compose/integration-tests/macro-benchmark/src/androidTest/java/androidx/ui/macrobenchmark/StartupUtils.kt
new file mode 100644
index 0000000..3bc5433
--- /dev/null
+++ b/compose/integration-tests/macro-benchmark/src/androidTest/java/androidx/ui/macrobenchmark/StartupUtils.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2020 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.ui.macrobenchmark
+
+import androidx.benchmark.macro.CompilationMode
+import androidx.benchmark.macro.MacrobenchmarkConfig
+import androidx.benchmark.macro.MacrobenchmarkRule
+import androidx.benchmark.macro.MacrobenchmarkScope
+import androidx.benchmark.macro.StartupTimingMetric
+
+/**
+ * Simplified interface for standardizing e.g. package,
+ * compilation types, and iteration count across project
+ */
+fun MacrobenchmarkRule.measureStartup(
+ profileCompiled: Boolean,
+ coldLaunch: Boolean,
+ block: MacrobenchmarkScope.() -> Unit = {
+ pressHome()
+ launchPackageAndWait()
+ }
+) = measureRepeated(
+ MacrobenchmarkConfig(
+ packageName = "androidx.compose.integration.demos",
+ metrics = listOf(StartupTimingMetric()),
+ compilationMode = if (profileCompiled) {
+ CompilationMode.SpeedProfile(warmupIterations = 3)
+ } else {
+ CompilationMode.None
+ },
+ killProcessEachIteration = coldLaunch,
+ iterations = 10
+ ),
+ block
+)
diff --git a/compose/integration-tests/macro-benchmark/src/main/AndroidManifest.xml b/compose/integration-tests/macro-benchmark/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..36da356
--- /dev/null
+++ b/compose/integration-tests/macro-benchmark/src/main/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+<manifest package="androidx.ui.macrobenchmark" />
diff --git a/compose/internal-lint-checks/src/main/java/androidx/compose/lint/ModifierInspectorInfoDetector.kt b/compose/internal-lint-checks/src/main/java/androidx/compose/lint/ModifierInspectorInfoDetector.kt
index f8b50a0..e385acc 100644
--- a/compose/internal-lint-checks/src/main/java/androidx/compose/lint/ModifierInspectorInfoDetector.kt
+++ b/compose/internal-lint-checks/src/main/java/androidx/compose/lint/ModifierInspectorInfoDetector.kt
@@ -56,7 +56,6 @@
import org.jetbrains.uast.USwitchClauseExpression
import org.jetbrains.uast.USwitchClauseExpressionWithBody
import org.jetbrains.uast.USwitchExpression
-import org.jetbrains.uast.UThisExpression
import org.jetbrains.uast.UYieldExpression
import org.jetbrains.uast.kotlin.KotlinStringTemplateUPolyadicExpression
import org.jetbrains.uast.kotlin.KotlinStringULiteralExpression
@@ -79,6 +78,7 @@
private const val RememberMethodName = "remember"
private const val ComposedMethodPackage = "androidx.compose.ui"
private const val RememberMethodPackage = "androidx.compose.runtime"
+private val DemosPackageRegEx = "androidx\\.compose\\..+\\.demos\\..+".toRegex()
/**
* Lint [Detector] to ensure that we are creating debug information for the layout inspector on
@@ -148,7 +148,8 @@
if (node.containingFile?.name == ModifierFile ||
node.containingFile?.name == ComposedModifierFile ||
firstParameterType(node) != ModifierClass ||
- node.returnType?.canonicalText != ModifierClass
+ node.returnType?.canonicalText != ModifierClass ||
+ DemosPackageRegEx.matches(node.containingClass?.qualifiedName ?: "")
) {
// Ignore the method if it isn't a method on Modifier returning a Modifier,
// or if the method is defined in Modifier.kt or ComposedModifier.kt
@@ -430,13 +431,12 @@
*
* The expression is known to be the return expression from a return statement.
*
- * Currently the only accepted expressions are of the form:
+ * Currently the only check the following expressions:
* - Modifier.then(Modifier)
* - Modifier.composed(InspectorInfoLambda,factory)
- * - synonymCall()
- * - everything else is an error
+ * - everything else is ignored
*/
- private inner class ModifierBuilderVisitor : UnexpectedVisitor({ wrongLambda(it) }) {
+ private inner class ModifierBuilderVisitor : AbstractUastVisitor() {
override fun visitQualifiedReferenceExpression(
node: UQualifiedReferenceExpression
): Boolean {
@@ -453,24 +453,15 @@
if (isComposeFunctionCall(node)) {
val inspectorInfo = node.valueArguments
.find { isInspectorInfoLambdaType(it.getExpressionType()) }
- ?: return super.visitCallExpression(node)
- inspectorInfo.accept(debugInspectorVisitor)
+ if (inspectorInfo == null) {
+ wrongLambda(node)
+ } else {
+ inspectorInfo.accept(debugInspectorVisitor)
+ }
return true
}
- // For now accept all other calls. Assume that the method being called
- // will add inspector information.
- return true
+ return super.visitCallExpression(node)
}
-
- override fun visitSimpleNameReferenceExpression(
- node: USimpleNameReferenceExpression
- ): Boolean {
- // Accept a simple reference to a different modifier definition
- return false
- }
-
- // Accept a single this expression, which essentially makes the modifier a noop
- override fun visitThisExpression(node: UThisExpression): Boolean = false
}
/**
diff --git a/compose/internal-lint-checks/src/test/java/androidx/compose/lint/ModifierInspectorInfoDetectorTest.kt b/compose/internal-lint-checks/src/test/java/androidx/compose/lint/ModifierInspectorInfoDetectorTest.kt
index 906edb9..1bc855c3 100644
--- a/compose/internal-lint-checks/src/test/java/androidx/compose/lint/ModifierInspectorInfoDetectorTest.kt
+++ b/compose/internal-lint-checks/src/test/java/androidx/compose/lint/ModifierInspectorInfoDetectorTest.kt
@@ -428,6 +428,9 @@
.then(if (bottom > 0) padding(bottom) else Modifier)
.then(if (top > 0) padding(top) else Modifier)
+ fun Modifier.paddingFromBaseline2(top: Int, bottom: Int) =
+ this.padding(bottom).padding(top)
+
private class PaddingModifier(
paddingSize: Int,
inspectorInfo: InspectorInfo.() -> Unit
@@ -514,6 +517,22 @@
}
)
+ fun Modifier.border2(width: Int, color: Int, shape: Shape): Modifier =
+ if (width > 0) {
+ composed(
+ inspectorInfo = debugInspectorInfo {
+ name = "border2"
+ properties["width"] = width
+ properties["color"] = color
+ properties["shape"] = shape
+ }
+ ) {
+ border(width, SolidColor(color), shape)
+ }
+ } else {
+ this
+ }
+
private class BorderModifier(shape: Shape, width: Int, brush: Brush)
"""
@@ -620,6 +639,33 @@
}
@Test
+ fun acceptMissingInspectorInfoInSamples() {
+ lint().files(
+ modifierFile,
+ inspectableInfoFile,
+ kotlin(
+ """
+ package androidx.compose.ui.demos.whatever
+
+ import androidx.compose.ui.Modifier
+ import androidx.compose.ui.platform.InspectorInfo
+ import androidx.compose.ui.platform.InspectorValueInfo
+ import androidx.compose.ui.platform.debugInspectorInfo
+
+ fun Modifier.preferredWidth2(width: Int) = this.then(SizeModifier2(width))
+
+ private data class SizeModifier2(
+ val width: Int,
+ ): Modifier.Element
+
+ """
+ ).indented()
+ )
+ .run()
+ .expectClean()
+ }
+
+ @Test
fun missingInspectorInfo() {
lint().files(
modifierFile,
@@ -1097,4 +1143,75 @@
"""
)
}
+
+ @Test
+ fun missingInfoInConditionals() {
+ lint().files(
+ modifierFile,
+ inspectableInfoFile,
+ kotlin(
+ """
+ package androidx.compose.ui
+
+ import androidx.compose.ui.Modifier
+ import androidx.compose.ui.platform.InspectorInfo
+ import androidx.compose.ui.platform.InspectorValueInfo
+ import androidx.compose.ui.platform.debugInspectorInfo
+
+ class Brush
+
+ class SolidColor(val color: Int): Brush()
+
+ fun Modifier.border(width: Int): Modifier = composed(
+ inspectorInfo = debugInspectorInfo {
+ name = "border"
+ value = width
+ }
+ ) {
+ BorderModifier(shape, width, brush)
+ }
+
+ fun Modifier.border2(width: Int): Modifier =
+ if (width > 0) {
+ border(width)
+ } else {
+ composed { BorderModifier(shape, width, brush) }
+ }
+
+ fun Modifier.border3(width: Int): Modifier =
+ when {
+ width < 0 -> this
+ width < 2 -> border(width)
+ width < 3 -> composed { BorderModifier(shape, width, brush) }
+ else -> this
+ }
+
+ fun Modifier.border4(width: Int): Modifier =
+ when {
+ width < 0 -> this
+ width < 2 -> border(width)
+ else -> this.then(BorderModifier(shape, width, brush))
+ }
+
+ private class BorderModifier(shape: Shape, width: Int, brush: Brush): Modifier
+
+ """
+ ).indented()
+ )
+ .run()
+ .expect(
+ """
+ src/androidx/compose/ui/Brush.kt:25: Error: Modifier missing inspectorInfo [ModifierInspectorInfo]
+ composed { BorderModifier(shape, width, brush) }
+ ~~~~~~~~
+ src/androidx/compose/ui/Brush.kt:32: Error: Modifier missing inspectorInfo [ModifierInspectorInfo]
+ width < 3 -> composed { BorderModifier(shape, width, brush) }
+ ~~~~~~~~
+ src/androidx/compose/ui/Brush.kt:40: Error: Modifier missing inspectorInfo [ModifierInspectorInfo]
+ else -> this.then(BorderModifier(shape, width, brush))
+ ~~~~~~~~~~~~~~
+ 3 errors, 0 warnings
+ """
+ )
+ }
}
diff --git a/compose/material/material/api/current.txt b/compose/material/material/api/current.txt
index e472fbe..9bd3ea4b 100644
--- a/compose/material/material/api/current.txt
+++ b/compose/material/material/api/current.txt
@@ -382,9 +382,9 @@
}
public final class IconKt {
+ method @androidx.compose.runtime.Composable public static void Icon-1Wn-iBs(androidx.compose.ui.graphics.ImageBitmap bitmap, optional androidx.compose.ui.Modifier modifier, optional long tint);
method @androidx.compose.runtime.Composable public static void Icon-1eoVtfs(androidx.compose.ui.graphics.painter.Painter painter, optional androidx.compose.ui.Modifier modifier, optional long tint);
method @androidx.compose.runtime.Composable public static void Icon-QSAJrGw(androidx.compose.ui.graphics.vector.VectorAsset asset, optional androidx.compose.ui.Modifier modifier, optional long tint);
- method @androidx.compose.runtime.Composable public static void Icon-uUQrhdE(androidx.compose.ui.graphics.ImageAsset asset, optional androidx.compose.ui.Modifier modifier, optional long tint);
}
public final class ListItemKt {
diff --git a/compose/material/material/api/public_plus_experimental_current.txt b/compose/material/material/api/public_plus_experimental_current.txt
index e472fbe..9bd3ea4b 100644
--- a/compose/material/material/api/public_plus_experimental_current.txt
+++ b/compose/material/material/api/public_plus_experimental_current.txt
@@ -382,9 +382,9 @@
}
public final class IconKt {
+ method @androidx.compose.runtime.Composable public static void Icon-1Wn-iBs(androidx.compose.ui.graphics.ImageBitmap bitmap, optional androidx.compose.ui.Modifier modifier, optional long tint);
method @androidx.compose.runtime.Composable public static void Icon-1eoVtfs(androidx.compose.ui.graphics.painter.Painter painter, optional androidx.compose.ui.Modifier modifier, optional long tint);
method @androidx.compose.runtime.Composable public static void Icon-QSAJrGw(androidx.compose.ui.graphics.vector.VectorAsset asset, optional androidx.compose.ui.Modifier modifier, optional long tint);
- method @androidx.compose.runtime.Composable public static void Icon-uUQrhdE(androidx.compose.ui.graphics.ImageAsset asset, optional androidx.compose.ui.Modifier modifier, optional long tint);
}
public final class ListItemKt {
diff --git a/compose/material/material/api/restricted_current.txt b/compose/material/material/api/restricted_current.txt
index e472fbe..9bd3ea4b 100644
--- a/compose/material/material/api/restricted_current.txt
+++ b/compose/material/material/api/restricted_current.txt
@@ -382,9 +382,9 @@
}
public final class IconKt {
+ method @androidx.compose.runtime.Composable public static void Icon-1Wn-iBs(androidx.compose.ui.graphics.ImageBitmap bitmap, optional androidx.compose.ui.Modifier modifier, optional long tint);
method @androidx.compose.runtime.Composable public static void Icon-1eoVtfs(androidx.compose.ui.graphics.painter.Painter painter, optional androidx.compose.ui.Modifier modifier, optional long tint);
method @androidx.compose.runtime.Composable public static void Icon-QSAJrGw(androidx.compose.ui.graphics.vector.VectorAsset asset, optional androidx.compose.ui.Modifier modifier, optional long tint);
- method @androidx.compose.runtime.Composable public static void Icon-uUQrhdE(androidx.compose.ui.graphics.ImageAsset asset, optional androidx.compose.ui.Modifier modifier, optional long tint);
}
public final class ListItemKt {
diff --git a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/ColorPickerDemo.kt b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/ColorPickerDemo.kt
index 6d1a995..22e72b7 100644
--- a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/ColorPickerDemo.kt
+++ b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/ColorPickerDemo.kt
@@ -59,7 +59,7 @@
import androidx.compose.ui.gesture.dragGestureFilter
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.ImageAsset
+import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.isSpecified
@@ -115,7 +115,7 @@
)
Box(Modifier.fillMaxSize()) {
- Image(modifier = inputModifier, asset = colorWheel.image)
+ Image(modifier = inputModifier, bitmap = colorWheel.image)
val color = colorWheel.colorForPosition(position)
if (color.isSpecified) {
Magnifier(visible = isDragging, position = position, color = color)
@@ -305,7 +305,7 @@
}
/**
- * A color wheel with an [ImageAsset] that draws a circular color wheel of the specified diameter.
+ * A color wheel with an [ImageBitmap] that draws a circular color wheel of the specified diameter.
*/
private class ColorWheel(diameter: Int) {
private val radius = diameter / 2f
@@ -326,8 +326,8 @@
null
)
- val image = ImageAsset(diameter, diameter).also { asset ->
- val canvas = Canvas(asset)
+ val image = ImageBitmap(diameter, diameter).also { imageBitmap ->
+ val canvas = Canvas(imageBitmap)
val center = Offset(radius, radius)
val paint = Paint().apply { shader = sweepShader }
canvas.drawCircle(center, radius, paint)
diff --git a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MenuDemo.kt b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MenuDemo.kt
index 24de963..ddbfc47 100644
--- a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MenuDemo.kt
+++ b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MenuDemo.kt
@@ -31,8 +31,11 @@
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.Position
import androidx.compose.ui.unit.dp
import kotlin.math.roundToInt
@@ -44,8 +47,12 @@
for (j in 0..10) {
MenuInstance(
Modifier.fillMaxSize().wrapContentSize(
- { _, space, _ ->
- IntOffset(
+ object : Alignment {
+ override fun align(
+ size: IntSize,
+ space: IntSize,
+ layoutDirection: LayoutDirection
+ ) = IntOffset(
(space.width * i / 10f).roundToInt(),
(space.height * j / 10f).roundToInt()
)
diff --git a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ListSamples.kt b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ListSamples.kt
index 0260073..b02eeb7 100644
--- a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ListSamples.kt
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ListSamples.kt
@@ -33,15 +33,15 @@
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
-import androidx.compose.ui.graphics.ImageAsset
+import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.vector.VectorAsset
@Sampled
@Composable
fun OneLineListItems(
- icon24x24: ImageAsset,
- icon40x40: ImageAsset,
- icon56x56: ImageAsset,
+ icon24x24: ImageBitmap,
+ icon40x40: ImageBitmap,
+ icon56x56: ImageBitmap,
vectorIcon: VectorAsset
) {
Column {
@@ -94,8 +94,8 @@
@Sampled
@Composable
-// TODO(popam, b/159689286): material icons instead of ImageAsset when they can have custom sizes
-fun TwoLineListItems(icon24x24: ImageAsset, icon40x40: ImageAsset) {
+// TODO(popam, b/159689286): material icons instead of ImageBitmap when they can have custom sizes
+fun TwoLineListItems(icon24x24: ImageBitmap, icon40x40: ImageBitmap) {
Column {
ListItem(
text = { Text("Two line list item") },
@@ -151,7 +151,7 @@
@Sampled
@Composable
-fun ThreeLineListItems(icon24x24: ImageAsset, vectorIcon: VectorAsset) {
+fun ThreeLineListItems(icon24x24: ImageBitmap, vectorIcon: VectorAsset) {
Column {
ListItem(
text = { Text("Three line list item") },
@@ -208,7 +208,7 @@
// Demos for mixing RTL and LTR ListItems:
@Composable
-fun OneLineRtlLtrListItems(icon24x24: ImageAsset, icon40x40: ImageAsset) {
+fun OneLineRtlLtrListItems(icon24x24: ImageBitmap, icon40x40: ImageBitmap) {
Column {
ListItem(text = { Text("One line list item with no icon") })
Divider()
@@ -236,7 +236,7 @@
}
@Composable
-fun TwoLineRtlLtrListItems(icon40x40: ImageAsset) {
+fun TwoLineRtlLtrListItems(icon40x40: ImageBitmap) {
Column {
ListItem(
text = { Text("Two line list item") },
@@ -297,7 +297,7 @@
}
@Composable
-fun ThreeLineRtlLtrListItems(icon40x40: ImageAsset) {
+fun ThreeLineRtlLtrListItems(icon40x40: ImageBitmap) {
Column {
ListItem(
text = { Text("Three line list item") },
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/IconTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/IconTest.kt
index abb9316..4f02a23 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/IconTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/IconTest.kt
@@ -22,7 +22,7 @@
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.ImageAsset
+import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.drawscope.CanvasDrawScope
import androidx.compose.ui.graphics.painter.ColorPainter
import androidx.compose.ui.graphics.painter.ImagePainter
@@ -85,7 +85,7 @@
rule
.setMaterialContentForSizeAssertions {
val image = with(DensityAmbient.current) {
- ImageAsset(width.toIntPx(), height.toIntPx())
+ ImageBitmap(width.toIntPx(), height.toIntPx())
}
Icon(image)
@@ -102,7 +102,7 @@
rule
.setMaterialContentForSizeAssertions {
val image = with(DensityAmbient.current) {
- ImageAsset(width.toIntPx(), height.toIntPx())
+ ImageBitmap(width.toIntPx(), height.toIntPx())
}
Icon(image)
@@ -132,7 +132,7 @@
rule
.setMaterialContentForSizeAssertions {
val image = with(DensityAmbient.current) {
- ImageAsset(width.toIntPx(), height.toIntPx())
+ ImageBitmap(width.toIntPx(), height.toIntPx())
}
val imagePainter = ImagePainter(image)
@@ -148,7 +148,7 @@
val height = 83.dp
val testTag = "testTag"
rule.setMaterialContentForSizeAssertions {
- val image: ImageAsset
+ val image: ImageBitmap
with(DensityAmbient.current) {
image = createBitmapWithColor(
this,
@@ -170,7 +170,7 @@
val height = 83.dp
val testTag = "testTag"
rule.setMaterialContentForSizeAssertions {
- val image: ImageAsset
+ val image: ImageBitmap
with(DensityAmbient.current) {
image = createBitmapWithColor(
this,
@@ -191,9 +191,9 @@
width: Int,
height: Int,
color: Color
- ): ImageAsset {
+ ): ImageBitmap {
val size = Size(width.toFloat(), height.toFloat())
- val image = ImageAsset(width, height)
+ val image = ImageBitmap(width, height)
CanvasDrawScope().draw(
density,
LayoutDirection.Ltr,
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ListItemTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ListItemTest.kt
index 459d809..00034d3 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ListItemTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ListItemTest.kt
@@ -20,7 +20,7 @@
import androidx.compose.foundation.layout.Box
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.graphics.ImageAsset
+import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.layout.FirstBaseline
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.onGloballyPositioned
@@ -45,9 +45,9 @@
@get:Rule
val rule = createComposeRule()
- val icon24x24 by lazy { ImageAsset(width = 24.dp.toIntPx(), height = 24.dp.toIntPx()) }
- val icon40x40 by lazy { ImageAsset(width = 40.dp.toIntPx(), height = 40.dp.toIntPx()) }
- val icon56x56 by lazy { ImageAsset(width = 56.dp.toIntPx(), height = 56.dp.toIntPx()) }
+ val icon24x24 by lazy { ImageBitmap(width = 24.dp.toIntPx(), height = 24.dp.toIntPx()) }
+ val icon40x40 by lazy { ImageBitmap(width = 40.dp.toIntPx(), height = 40.dp.toIntPx()) }
+ val icon56x56 by lazy { ImageBitmap(width = 56.dp.toIntPx(), height = 56.dp.toIntPx()) }
@Test
fun listItem_oneLine_size() {
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Icon.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Icon.kt
index 105e998..6b5fbf2 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Icon.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Icon.kt
@@ -25,7 +25,7 @@
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
-import androidx.compose.ui.graphics.ImageAsset
+import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.painter.ImagePainter
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.VectorAsset
@@ -55,21 +55,21 @@
}
/**
- * Icon component that draws [asset] using [tint], defaulting to [AmbientContentColor]. For a
+ * Icon component that draws [bitmap] using [tint], defaulting to [AmbientContentColor]. For a
* clickable icon, see [IconButton].
*
- * @param asset [ImageAsset] to draw inside this Icon
+ * @param bitmap [ImageBitmap] to draw inside this Icon
* @param modifier optional [Modifier] for this Icon
- * @param tint tint to be applied to [asset]. If [Color.Unspecified] is provided, then no
+ * @param tint tint to be applied to [bitmap]. If [Color.Unspecified] is provided, then no
* tint is applied
*/
@Composable
fun Icon(
- asset: ImageAsset,
+ bitmap: ImageBitmap,
modifier: Modifier = Modifier,
tint: Color = AmbientContentColor.current
) {
- val painter = remember(asset) { ImagePainter(asset) }
+ val painter = remember(bitmap) { ImagePainter(bitmap) }
Icon(
painter = painter,
modifier = modifier,
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextFieldImpl.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextFieldImpl.kt
index 94e17da..9dbb12d 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextFieldImpl.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextFieldImpl.kt
@@ -277,10 +277,17 @@
) {
val colorAndEmphasis = @Composable {
Providers(AmbientContentColor provides contentColor) {
- if (contentAlpha != null) Providers(
- AmbientContentAlpha provides contentAlpha,
- children = children
- ) else children()
+ if (contentAlpha != null) {
+ Providers(
+ AmbientContentAlpha provides contentAlpha,
+ children = children
+ )
+ } else {
+ Providers(
+ AmbientContentAlpha provides contentColor.alpha,
+ children = children
+ )
+ }
}
}
if (typography != null) ProvideTextStyle(typography, colorAndEmphasis) else colorAndEmphasis()
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt
index c4ece4d..15f2ae2 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt
@@ -2081,15 +2081,10 @@
private fun removeSlots(start: Int, len: Int, group: Int) {
if (len > 0) {
val gapLen = slotsGapLen
- if (gapLen == 0) {
- slotsGapStart = start
- slotsGapLen = len
- } else {
- val removeEnd = start + len
- moveSlotGapTo(removeEnd, group)
- slotsGapStart = start
- slotsGapLen = gapLen + len
- }
+ val removeEnd = start + len
+ moveSlotGapTo(removeEnd, group)
+ slotsGapStart = start
+ slotsGapLen = gapLen + len
slots.fill(null, start, start + len)
val currentDataEnd = currentSlotEnd
if (currentDataEnd >= start) this.currentSlotEnd = currentDataEnd - len
diff --git a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/SlotTableTests.kt b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/SlotTableTests.kt
index a561908..cf04643 100644
--- a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/SlotTableTests.kt
+++ b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/SlotTableTests.kt
@@ -2834,6 +2834,54 @@
}
@Test
+ fun testRemoveDataBoundaryCondition() {
+ // Remove when the slot table contains amount that would make the slotGapSize 0
+ // Test insert exactly 64 data slots.
+ val slots = SlotTable().also {
+ it.write { writer ->
+ writer.insert {
+ writer.group(treeRoot) {
+ repeat(4) {
+ writer.group(it * 10 + 100) {
+ repeat(8) { value ->
+ writer.update(value)
+ }
+ }
+ }
+ writer.group(1000) {
+ repeat(16) { value ->
+ writer.update(value)
+ }
+ }
+ repeat(2) {
+ writer.group(it * 10 + 200) {
+ repeat(8) { value ->
+ writer.update(value)
+ }
+ }
+ }
+ repeat(10) {
+ writer.group(300 + it) { }
+ }
+ }
+ }
+ }
+ }
+ slots.verifyWellFormed()
+
+ slots.write { writer ->
+ writer.group(treeRoot) {
+ repeat(4) { writer.skipGroup() }
+ writer.removeGroup()
+ writer.skipGroup()
+ writer.set(4, 100)
+ writer.skipToGroupEnd()
+ }
+ }
+ slots.verifyWellFormed()
+ }
+
+ @Test
fun testInsertDataBoundaryCondition() {
// Test insert exactly 64 data slots.
val slots = SlotTable().also {
diff --git a/compose/test-utils/src/androidMain/kotlin/androidx/compose/testutils/ImageAssertions.kt b/compose/test-utils/src/androidMain/kotlin/androidx/compose/testutils/ImageAssertions.kt
index db0e572..9df3255 100644
--- a/compose/test-utils/src/androidMain/kotlin/androidx/compose/testutils/ImageAssertions.kt
+++ b/compose/test-utils/src/androidMain/kotlin/androidx/compose/testutils/ImageAssertions.kt
@@ -20,12 +20,12 @@
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.ImageAsset
+import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.asAndroidBitmap
-import androidx.compose.ui.graphics.asImageAsset
+import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.test.assertContainsColor
import androidx.compose.ui.test.assertPixelColor
import androidx.compose.ui.test.assertPixels
@@ -46,7 +46,7 @@
* @throws AssertionError if size or colors don't match.
*/
@Suppress("DEPRECATION")
-fun ImageAsset.assertPixels(
+fun ImageBitmap.assertPixels(
expectedSize: IntSize? = null,
expectedColorProvider: (pos: IntOffset) -> Color?
) = asAndroidBitmap().assertPixels(expectedSize, expectedColorProvider)
@@ -55,7 +55,7 @@
* Asserts that the color at a specific pixel in the bitmap at ([x], [y]) is [expected].
*/
@Suppress("DEPRECATION")
-fun ImageAsset.assertPixelColor(
+fun ImageBitmap.assertPixelColor(
expected: Color,
x: Int,
y: Int,
@@ -68,9 +68,9 @@
* @throws AssertionError if the expected color is not present.
*/
@Suppress("DEPRECATION")
-fun ImageAsset.assertContainsColor(
+fun ImageBitmap.assertContainsColor(
expectedColor: Color
-) = asAndroidBitmap().assertContainsColor(expectedColor).asImageAsset()
+) = asAndroidBitmap().assertContainsColor(expectedColor).asImageBitmap()
/**
* Tests to see if the given point is within the path. (That is, whether the
@@ -106,7 +106,7 @@
*/
// TODO (mount, malkov) : to investigate why it flakes when shape is not rect
@Suppress("DEPRECATION")
-fun ImageAsset.assertShape(
+fun ImageBitmap.assertShape(
density: Density,
shape: Shape,
shapeColor: Color,
@@ -149,7 +149,7 @@
* untested as it is likely anti-aliased. The default is 1 pixel
*/
@Suppress("DEPRECATION")
-fun ImageAsset.assertShape(
+fun ImageBitmap.assertShape(
density: Density,
horizontalPadding: Dp,
verticalPadding: Dp,
diff --git a/compose/test-utils/src/androidMain/kotlin/androidx/compose/testutils/ScreenshotTestsUtils.kt b/compose/test-utils/src/androidMain/kotlin/androidx/compose/testutils/ScreenshotTestsUtils.kt
index b0da4ba4..79dc916 100644
--- a/compose/test-utils/src/androidMain/kotlin/androidx/compose/testutils/ScreenshotTestsUtils.kt
+++ b/compose/test-utils/src/androidMain/kotlin/androidx/compose/testutils/ScreenshotTestsUtils.kt
@@ -16,7 +16,7 @@
package androidx.compose.testutils
-import androidx.compose.ui.graphics.ImageAsset
+import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asAndroidBitmap
import androidx.test.screenshot.ScreenshotTestRule
import androidx.test.screenshot.assertAgainstGolden
@@ -35,7 +35,7 @@
* @param matcher The algorithm to be used to perform the matching. By default [MSSIMMatcher]
* is used.
*/
-fun ImageAsset.assertAgainstGolden(
+fun ImageBitmap.assertAgainstGolden(
rule: ScreenshotTestRule,
goldenIdentifier: String,
matcher: BitmapMatcher = MSSIMMatcher()
diff --git a/compose/ui/ui-graphics/api/current.txt b/compose/ui/ui-graphics/api/current.txt
index 82b07b5..97981c8 100644
--- a/compose/ui/ui-graphics/api/current.txt
+++ b/compose/ui/ui-graphics/api/current.txt
@@ -10,10 +10,11 @@
method public static android.graphics.Canvas getNativeCanvas(androidx.compose.ui.graphics.Canvas);
}
- public final class AndroidImageAssetKt {
- method public static android.graphics.Bitmap asAndroidBitmap(androidx.compose.ui.graphics.ImageAsset);
- method public static androidx.compose.ui.graphics.ImageAsset asImageAsset(android.graphics.Bitmap);
- method public static androidx.compose.ui.graphics.ImageAsset imageFromResource(android.content.res.Resources res, int resId);
+ public final class AndroidImageBitmapKt {
+ method public static android.graphics.Bitmap asAndroidBitmap(androidx.compose.ui.graphics.ImageBitmap);
+ method @Deprecated public static androidx.compose.ui.graphics.ImageBitmap asImageAsset(android.graphics.Bitmap);
+ method public static androidx.compose.ui.graphics.ImageBitmap asImageBitmap(android.graphics.Bitmap);
+ method public static androidx.compose.ui.graphics.ImageBitmap imageFromResource(android.content.res.Resources res, int resId);
}
public final class AndroidMatrixConversionsKt {
@@ -188,8 +189,8 @@
method public void drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, androidx.compose.ui.graphics.Paint paint);
method public default void drawArcRad(androidx.compose.ui.geometry.Rect rect, float startAngleRad, float sweepAngleRad, boolean useCenter, androidx.compose.ui.graphics.Paint paint);
method public void drawCircle-tVKstsI(long center, float radius, androidx.compose.ui.graphics.Paint paint);
- method public void drawImage-YdY9XZw(androidx.compose.ui.graphics.ImageAsset image, long topLeftOffset, androidx.compose.ui.graphics.Paint paint);
- method public void drawImageRect-iaf9M94(androidx.compose.ui.graphics.ImageAsset image, optional long srcOffset, optional long srcSize, optional long dstOffset, optional long dstSize, androidx.compose.ui.graphics.Paint paint);
+ method public void drawImage-uwcbMjI(androidx.compose.ui.graphics.ImageBitmap image, long topLeftOffset, androidx.compose.ui.graphics.Paint paint);
+ method public void drawImageRect-bgE79EM(androidx.compose.ui.graphics.ImageBitmap image, optional long srcOffset, optional long srcSize, optional long dstOffset, optional long dstSize, androidx.compose.ui.graphics.Paint paint);
method public void drawLine-bYPfCD8(long p1, long p2, androidx.compose.ui.graphics.Paint paint);
method public default void drawOval(androidx.compose.ui.geometry.Rect rect, androidx.compose.ui.graphics.Paint paint);
method public void drawOval(float left, float top, float right, float bottom, androidx.compose.ui.graphics.Paint paint);
@@ -217,7 +218,7 @@
}
public final class CanvasKt {
- method public static androidx.compose.ui.graphics.Canvas Canvas(androidx.compose.ui.graphics.ImageAsset image);
+ method public static androidx.compose.ui.graphics.Canvas Canvas(androidx.compose.ui.graphics.ImageBitmap image);
method public static void rotate(androidx.compose.ui.graphics.Canvas, float degrees, float pivotX, float pivotY);
method public static void rotateRad(androidx.compose.ui.graphics.Canvas, float radians, optional float pivotX, optional float pivotY);
method public static void scale(androidx.compose.ui.graphics.Canvas, float sx, optional float sy, float pivotX, float pivotY);
@@ -325,32 +326,33 @@
public final class Float16Kt {
}
- public interface ImageAsset {
+ public interface ImageBitmap {
method public androidx.compose.ui.graphics.colorspace.ColorSpace getColorSpace();
- method public androidx.compose.ui.graphics.ImageAssetConfig getConfig();
+ method public androidx.compose.ui.graphics.ImageBitmapConfig getConfig();
method public boolean getHasAlpha();
method public int getHeight();
method public int getWidth();
method public void prepareToDraw();
method public void readPixels(int[] buffer, optional int startX, optional int startY, optional int width, optional int height, optional int bufferOffset, optional int stride);
property public abstract androidx.compose.ui.graphics.colorspace.ColorSpace colorSpace;
- property public abstract androidx.compose.ui.graphics.ImageAssetConfig config;
+ property public abstract androidx.compose.ui.graphics.ImageBitmapConfig config;
property public abstract boolean hasAlpha;
property public abstract int height;
property public abstract int width;
}
- public enum ImageAssetConfig {
- enum_constant public static final androidx.compose.ui.graphics.ImageAssetConfig Alpha8;
- enum_constant public static final androidx.compose.ui.graphics.ImageAssetConfig Argb8888;
- enum_constant public static final androidx.compose.ui.graphics.ImageAssetConfig F16;
- enum_constant public static final androidx.compose.ui.graphics.ImageAssetConfig Gpu;
- enum_constant public static final androidx.compose.ui.graphics.ImageAssetConfig Rgb565;
+ public enum ImageBitmapConfig {
+ enum_constant public static final androidx.compose.ui.graphics.ImageBitmapConfig Alpha8;
+ enum_constant public static final androidx.compose.ui.graphics.ImageBitmapConfig Argb8888;
+ enum_constant public static final androidx.compose.ui.graphics.ImageBitmapConfig F16;
+ enum_constant public static final androidx.compose.ui.graphics.ImageBitmapConfig Gpu;
+ enum_constant public static final androidx.compose.ui.graphics.ImageBitmapConfig Rgb565;
}
- public final class ImageAssetKt {
- method public static androidx.compose.ui.graphics.ImageAsset ImageAsset(int width, int height, optional androidx.compose.ui.graphics.ImageAssetConfig config, optional boolean hasAlpha, optional androidx.compose.ui.graphics.colorspace.ColorSpace colorSpace);
- method public static androidx.compose.ui.graphics.PixelMap toPixelMap(androidx.compose.ui.graphics.ImageAsset, optional int startX, optional int startY, optional int width, optional int height, optional int[] buffer, optional int bufferOffset, optional int stride);
+ public final class ImageBitmapKt {
+ method @Deprecated public static androidx.compose.ui.graphics.ImageBitmap ImageAsset(int width, int height, optional androidx.compose.ui.graphics.ImageBitmapConfig config, optional boolean hasAlpha, optional androidx.compose.ui.graphics.colorspace.ColorSpace colorSpace);
+ method public static androidx.compose.ui.graphics.ImageBitmap ImageBitmap(int width, int height, optional androidx.compose.ui.graphics.ImageBitmapConfig config, optional boolean hasAlpha, optional androidx.compose.ui.graphics.colorspace.ColorSpace colorSpace);
+ method public static androidx.compose.ui.graphics.PixelMap toPixelMap(androidx.compose.ui.graphics.ImageBitmap, optional int startX, optional int startY, optional int width, optional int height, optional int[] buffer, optional int bufferOffset, optional int stride);
}
@androidx.compose.runtime.Immutable public final class LinearGradient extends androidx.compose.ui.graphics.ShaderBrush {
@@ -571,7 +573,7 @@
}
public final class ShaderKt {
- method public static android.graphics.Shader ImageShader(androidx.compose.ui.graphics.ImageAsset image, optional androidx.compose.ui.graphics.TileMode tileModeX, optional androidx.compose.ui.graphics.TileMode tileModeY);
+ method public static android.graphics.Shader ImageShader(androidx.compose.ui.graphics.ImageBitmap image, optional androidx.compose.ui.graphics.TileMode tileModeX, optional androidx.compose.ui.graphics.TileMode tileModeY);
method public static android.graphics.Shader LinearGradientShader-GfyHbQM(long from, long to, java.util.List<androidx.compose.ui.graphics.Color> colors, optional java.util.List<java.lang.Float>? colorStops, optional androidx.compose.ui.graphics.TileMode tileMode);
method public static android.graphics.Shader RadialGradientShader-cY6o93o(long center, float radius, java.util.List<androidx.compose.ui.graphics.Color> colors, optional java.util.List<java.lang.Float>? colorStops, optional androidx.compose.ui.graphics.TileMode tileMode);
}
@@ -854,8 +856,8 @@
method public void drawArc-C-Io9bM(androidx.compose.ui.graphics.Brush brush, float startAngle, float sweepAngle, boolean useCenter, long topLeft, long size, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
method public void drawCircle-NGaRamM(long color, float radius, long center, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
method public void drawCircle-m-UMHxE(androidx.compose.ui.graphics.Brush brush, float radius, long center, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
- method public void drawImage-WFpWo7g(androidx.compose.ui.graphics.ImageAsset image, long topLeft, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
- method public void drawImage-oX8chH0(androidx.compose.ui.graphics.ImageAsset image, long srcOffset, long srcSize, long dstOffset, long dstSize, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
+ method public void drawImage-JUiai_k(androidx.compose.ui.graphics.ImageBitmap image, long topLeft, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
+ method public void drawImage-Yc2aOMw(androidx.compose.ui.graphics.ImageBitmap image, long srcOffset, long srcSize, long dstOffset, long dstSize, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
method public void drawLine-1-s4MmQ(long color, long start, long end, float strokeWidth, androidx.compose.ui.graphics.StrokeCap cap, android.graphics.PathEffect? pathEffect, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
method public void drawLine-IQGCKvc(androidx.compose.ui.graphics.Brush brush, long start, long end, float strokeWidth, androidx.compose.ui.graphics.StrokeCap cap, android.graphics.PathEffect? pathEffect, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
method public void drawOval-0a6MmAQ(androidx.compose.ui.graphics.Brush brush, long topLeft, long size, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
@@ -896,8 +898,8 @@
method public void drawArc-C-Io9bM(androidx.compose.ui.graphics.Brush brush, float startAngle, float sweepAngle, boolean useCenter, optional long topLeft, optional long size, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
method public void drawCircle-NGaRamM(long color, optional float radius, optional long center, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
method public void drawCircle-m-UMHxE(androidx.compose.ui.graphics.Brush brush, optional float radius, optional long center, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
- method public void drawImage-WFpWo7g(androidx.compose.ui.graphics.ImageAsset image, optional long topLeft, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
- method public void drawImage-oX8chH0(androidx.compose.ui.graphics.ImageAsset image, optional long srcOffset, optional long srcSize, optional long dstOffset, optional long dstSize, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
+ method public void drawImage-JUiai_k(androidx.compose.ui.graphics.ImageBitmap image, optional long topLeft, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
+ method public void drawImage-Yc2aOMw(androidx.compose.ui.graphics.ImageBitmap image, optional long srcOffset, optional long srcSize, optional long dstOffset, optional long dstSize, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
method public void drawLine-1-s4MmQ(long color, long start, long end, optional float strokeWidth, optional androidx.compose.ui.graphics.StrokeCap cap, optional android.graphics.PathEffect? pathEffect, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
method public void drawLine-IQGCKvc(androidx.compose.ui.graphics.Brush brush, long start, long end, optional float strokeWidth, optional androidx.compose.ui.graphics.StrokeCap cap, optional android.graphics.PathEffect? pathEffect, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
method public void drawOval-0a6MmAQ(androidx.compose.ui.graphics.Brush brush, optional long topLeft, optional long size, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
@@ -1019,7 +1021,7 @@
}
public final class ImagePainter extends androidx.compose.ui.graphics.painter.Painter {
- method public androidx.compose.ui.graphics.painter.ImagePainter copy-rHaiP50(androidx.compose.ui.graphics.ImageAsset image, long srcOffset, long srcSize);
+ method public androidx.compose.ui.graphics.painter.ImagePainter copy-MHcbwps(androidx.compose.ui.graphics.ImageBitmap image, long srcOffset, long srcSize);
method public long getIntrinsicSize-NH-jbRc();
method protected void onDraw(androidx.compose.ui.graphics.drawscope.DrawScope);
property public long intrinsicSize;
diff --git a/compose/ui/ui-graphics/api/public_plus_experimental_current.txt b/compose/ui/ui-graphics/api/public_plus_experimental_current.txt
index 82b07b5..97981c8 100644
--- a/compose/ui/ui-graphics/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui-graphics/api/public_plus_experimental_current.txt
@@ -10,10 +10,11 @@
method public static android.graphics.Canvas getNativeCanvas(androidx.compose.ui.graphics.Canvas);
}
- public final class AndroidImageAssetKt {
- method public static android.graphics.Bitmap asAndroidBitmap(androidx.compose.ui.graphics.ImageAsset);
- method public static androidx.compose.ui.graphics.ImageAsset asImageAsset(android.graphics.Bitmap);
- method public static androidx.compose.ui.graphics.ImageAsset imageFromResource(android.content.res.Resources res, int resId);
+ public final class AndroidImageBitmapKt {
+ method public static android.graphics.Bitmap asAndroidBitmap(androidx.compose.ui.graphics.ImageBitmap);
+ method @Deprecated public static androidx.compose.ui.graphics.ImageBitmap asImageAsset(android.graphics.Bitmap);
+ method public static androidx.compose.ui.graphics.ImageBitmap asImageBitmap(android.graphics.Bitmap);
+ method public static androidx.compose.ui.graphics.ImageBitmap imageFromResource(android.content.res.Resources res, int resId);
}
public final class AndroidMatrixConversionsKt {
@@ -188,8 +189,8 @@
method public void drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, androidx.compose.ui.graphics.Paint paint);
method public default void drawArcRad(androidx.compose.ui.geometry.Rect rect, float startAngleRad, float sweepAngleRad, boolean useCenter, androidx.compose.ui.graphics.Paint paint);
method public void drawCircle-tVKstsI(long center, float radius, androidx.compose.ui.graphics.Paint paint);
- method public void drawImage-YdY9XZw(androidx.compose.ui.graphics.ImageAsset image, long topLeftOffset, androidx.compose.ui.graphics.Paint paint);
- method public void drawImageRect-iaf9M94(androidx.compose.ui.graphics.ImageAsset image, optional long srcOffset, optional long srcSize, optional long dstOffset, optional long dstSize, androidx.compose.ui.graphics.Paint paint);
+ method public void drawImage-uwcbMjI(androidx.compose.ui.graphics.ImageBitmap image, long topLeftOffset, androidx.compose.ui.graphics.Paint paint);
+ method public void drawImageRect-bgE79EM(androidx.compose.ui.graphics.ImageBitmap image, optional long srcOffset, optional long srcSize, optional long dstOffset, optional long dstSize, androidx.compose.ui.graphics.Paint paint);
method public void drawLine-bYPfCD8(long p1, long p2, androidx.compose.ui.graphics.Paint paint);
method public default void drawOval(androidx.compose.ui.geometry.Rect rect, androidx.compose.ui.graphics.Paint paint);
method public void drawOval(float left, float top, float right, float bottom, androidx.compose.ui.graphics.Paint paint);
@@ -217,7 +218,7 @@
}
public final class CanvasKt {
- method public static androidx.compose.ui.graphics.Canvas Canvas(androidx.compose.ui.graphics.ImageAsset image);
+ method public static androidx.compose.ui.graphics.Canvas Canvas(androidx.compose.ui.graphics.ImageBitmap image);
method public static void rotate(androidx.compose.ui.graphics.Canvas, float degrees, float pivotX, float pivotY);
method public static void rotateRad(androidx.compose.ui.graphics.Canvas, float radians, optional float pivotX, optional float pivotY);
method public static void scale(androidx.compose.ui.graphics.Canvas, float sx, optional float sy, float pivotX, float pivotY);
@@ -325,32 +326,33 @@
public final class Float16Kt {
}
- public interface ImageAsset {
+ public interface ImageBitmap {
method public androidx.compose.ui.graphics.colorspace.ColorSpace getColorSpace();
- method public androidx.compose.ui.graphics.ImageAssetConfig getConfig();
+ method public androidx.compose.ui.graphics.ImageBitmapConfig getConfig();
method public boolean getHasAlpha();
method public int getHeight();
method public int getWidth();
method public void prepareToDraw();
method public void readPixels(int[] buffer, optional int startX, optional int startY, optional int width, optional int height, optional int bufferOffset, optional int stride);
property public abstract androidx.compose.ui.graphics.colorspace.ColorSpace colorSpace;
- property public abstract androidx.compose.ui.graphics.ImageAssetConfig config;
+ property public abstract androidx.compose.ui.graphics.ImageBitmapConfig config;
property public abstract boolean hasAlpha;
property public abstract int height;
property public abstract int width;
}
- public enum ImageAssetConfig {
- enum_constant public static final androidx.compose.ui.graphics.ImageAssetConfig Alpha8;
- enum_constant public static final androidx.compose.ui.graphics.ImageAssetConfig Argb8888;
- enum_constant public static final androidx.compose.ui.graphics.ImageAssetConfig F16;
- enum_constant public static final androidx.compose.ui.graphics.ImageAssetConfig Gpu;
- enum_constant public static final androidx.compose.ui.graphics.ImageAssetConfig Rgb565;
+ public enum ImageBitmapConfig {
+ enum_constant public static final androidx.compose.ui.graphics.ImageBitmapConfig Alpha8;
+ enum_constant public static final androidx.compose.ui.graphics.ImageBitmapConfig Argb8888;
+ enum_constant public static final androidx.compose.ui.graphics.ImageBitmapConfig F16;
+ enum_constant public static final androidx.compose.ui.graphics.ImageBitmapConfig Gpu;
+ enum_constant public static final androidx.compose.ui.graphics.ImageBitmapConfig Rgb565;
}
- public final class ImageAssetKt {
- method public static androidx.compose.ui.graphics.ImageAsset ImageAsset(int width, int height, optional androidx.compose.ui.graphics.ImageAssetConfig config, optional boolean hasAlpha, optional androidx.compose.ui.graphics.colorspace.ColorSpace colorSpace);
- method public static androidx.compose.ui.graphics.PixelMap toPixelMap(androidx.compose.ui.graphics.ImageAsset, optional int startX, optional int startY, optional int width, optional int height, optional int[] buffer, optional int bufferOffset, optional int stride);
+ public final class ImageBitmapKt {
+ method @Deprecated public static androidx.compose.ui.graphics.ImageBitmap ImageAsset(int width, int height, optional androidx.compose.ui.graphics.ImageBitmapConfig config, optional boolean hasAlpha, optional androidx.compose.ui.graphics.colorspace.ColorSpace colorSpace);
+ method public static androidx.compose.ui.graphics.ImageBitmap ImageBitmap(int width, int height, optional androidx.compose.ui.graphics.ImageBitmapConfig config, optional boolean hasAlpha, optional androidx.compose.ui.graphics.colorspace.ColorSpace colorSpace);
+ method public static androidx.compose.ui.graphics.PixelMap toPixelMap(androidx.compose.ui.graphics.ImageBitmap, optional int startX, optional int startY, optional int width, optional int height, optional int[] buffer, optional int bufferOffset, optional int stride);
}
@androidx.compose.runtime.Immutable public final class LinearGradient extends androidx.compose.ui.graphics.ShaderBrush {
@@ -571,7 +573,7 @@
}
public final class ShaderKt {
- method public static android.graphics.Shader ImageShader(androidx.compose.ui.graphics.ImageAsset image, optional androidx.compose.ui.graphics.TileMode tileModeX, optional androidx.compose.ui.graphics.TileMode tileModeY);
+ method public static android.graphics.Shader ImageShader(androidx.compose.ui.graphics.ImageBitmap image, optional androidx.compose.ui.graphics.TileMode tileModeX, optional androidx.compose.ui.graphics.TileMode tileModeY);
method public static android.graphics.Shader LinearGradientShader-GfyHbQM(long from, long to, java.util.List<androidx.compose.ui.graphics.Color> colors, optional java.util.List<java.lang.Float>? colorStops, optional androidx.compose.ui.graphics.TileMode tileMode);
method public static android.graphics.Shader RadialGradientShader-cY6o93o(long center, float radius, java.util.List<androidx.compose.ui.graphics.Color> colors, optional java.util.List<java.lang.Float>? colorStops, optional androidx.compose.ui.graphics.TileMode tileMode);
}
@@ -854,8 +856,8 @@
method public void drawArc-C-Io9bM(androidx.compose.ui.graphics.Brush brush, float startAngle, float sweepAngle, boolean useCenter, long topLeft, long size, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
method public void drawCircle-NGaRamM(long color, float radius, long center, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
method public void drawCircle-m-UMHxE(androidx.compose.ui.graphics.Brush brush, float radius, long center, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
- method public void drawImage-WFpWo7g(androidx.compose.ui.graphics.ImageAsset image, long topLeft, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
- method public void drawImage-oX8chH0(androidx.compose.ui.graphics.ImageAsset image, long srcOffset, long srcSize, long dstOffset, long dstSize, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
+ method public void drawImage-JUiai_k(androidx.compose.ui.graphics.ImageBitmap image, long topLeft, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
+ method public void drawImage-Yc2aOMw(androidx.compose.ui.graphics.ImageBitmap image, long srcOffset, long srcSize, long dstOffset, long dstSize, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
method public void drawLine-1-s4MmQ(long color, long start, long end, float strokeWidth, androidx.compose.ui.graphics.StrokeCap cap, android.graphics.PathEffect? pathEffect, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
method public void drawLine-IQGCKvc(androidx.compose.ui.graphics.Brush brush, long start, long end, float strokeWidth, androidx.compose.ui.graphics.StrokeCap cap, android.graphics.PathEffect? pathEffect, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
method public void drawOval-0a6MmAQ(androidx.compose.ui.graphics.Brush brush, long topLeft, long size, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
@@ -896,8 +898,8 @@
method public void drawArc-C-Io9bM(androidx.compose.ui.graphics.Brush brush, float startAngle, float sweepAngle, boolean useCenter, optional long topLeft, optional long size, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
method public void drawCircle-NGaRamM(long color, optional float radius, optional long center, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
method public void drawCircle-m-UMHxE(androidx.compose.ui.graphics.Brush brush, optional float radius, optional long center, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
- method public void drawImage-WFpWo7g(androidx.compose.ui.graphics.ImageAsset image, optional long topLeft, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
- method public void drawImage-oX8chH0(androidx.compose.ui.graphics.ImageAsset image, optional long srcOffset, optional long srcSize, optional long dstOffset, optional long dstSize, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
+ method public void drawImage-JUiai_k(androidx.compose.ui.graphics.ImageBitmap image, optional long topLeft, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
+ method public void drawImage-Yc2aOMw(androidx.compose.ui.graphics.ImageBitmap image, optional long srcOffset, optional long srcSize, optional long dstOffset, optional long dstSize, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
method public void drawLine-1-s4MmQ(long color, long start, long end, optional float strokeWidth, optional androidx.compose.ui.graphics.StrokeCap cap, optional android.graphics.PathEffect? pathEffect, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
method public void drawLine-IQGCKvc(androidx.compose.ui.graphics.Brush brush, long start, long end, optional float strokeWidth, optional androidx.compose.ui.graphics.StrokeCap cap, optional android.graphics.PathEffect? pathEffect, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
method public void drawOval-0a6MmAQ(androidx.compose.ui.graphics.Brush brush, optional long topLeft, optional long size, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
@@ -1019,7 +1021,7 @@
}
public final class ImagePainter extends androidx.compose.ui.graphics.painter.Painter {
- method public androidx.compose.ui.graphics.painter.ImagePainter copy-rHaiP50(androidx.compose.ui.graphics.ImageAsset image, long srcOffset, long srcSize);
+ method public androidx.compose.ui.graphics.painter.ImagePainter copy-MHcbwps(androidx.compose.ui.graphics.ImageBitmap image, long srcOffset, long srcSize);
method public long getIntrinsicSize-NH-jbRc();
method protected void onDraw(androidx.compose.ui.graphics.drawscope.DrawScope);
property public long intrinsicSize;
diff --git a/compose/ui/ui-graphics/api/restricted_current.txt b/compose/ui/ui-graphics/api/restricted_current.txt
index 729264d..21672de 100644
--- a/compose/ui/ui-graphics/api/restricted_current.txt
+++ b/compose/ui/ui-graphics/api/restricted_current.txt
@@ -13,8 +13,8 @@
method public void disableZ();
method public void drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, androidx.compose.ui.graphics.Paint paint);
method public void drawCircle-tVKstsI(long center, float radius, androidx.compose.ui.graphics.Paint paint);
- method public void drawImage-YdY9XZw(androidx.compose.ui.graphics.ImageAsset image, long topLeftOffset, androidx.compose.ui.graphics.Paint paint);
- method public void drawImageRect-iaf9M94(androidx.compose.ui.graphics.ImageAsset image, long srcOffset, long srcSize, long dstOffset, long dstSize, androidx.compose.ui.graphics.Paint paint);
+ method public void drawImage-uwcbMjI(androidx.compose.ui.graphics.ImageBitmap image, long topLeftOffset, androidx.compose.ui.graphics.Paint paint);
+ method public void drawImageRect-bgE79EM(androidx.compose.ui.graphics.ImageBitmap image, long srcOffset, long srcSize, long dstOffset, long dstSize, androidx.compose.ui.graphics.Paint paint);
method public void drawLine-bYPfCD8(long p1, long p2, androidx.compose.ui.graphics.Paint paint);
method public void drawOval(float left, float top, float right, float bottom, androidx.compose.ui.graphics.Paint paint);
method public void drawPath(androidx.compose.ui.graphics.Path path, androidx.compose.ui.graphics.Paint paint);
@@ -40,10 +40,11 @@
method public static android.graphics.Canvas getNativeCanvas(androidx.compose.ui.graphics.Canvas);
}
- public final class AndroidImageAssetKt {
- method public static android.graphics.Bitmap asAndroidBitmap(androidx.compose.ui.graphics.ImageAsset);
- method public static androidx.compose.ui.graphics.ImageAsset asImageAsset(android.graphics.Bitmap);
- method public static androidx.compose.ui.graphics.ImageAsset imageFromResource(android.content.res.Resources res, int resId);
+ public final class AndroidImageBitmapKt {
+ method public static android.graphics.Bitmap asAndroidBitmap(androidx.compose.ui.graphics.ImageBitmap);
+ method @Deprecated public static androidx.compose.ui.graphics.ImageBitmap asImageAsset(android.graphics.Bitmap);
+ method public static androidx.compose.ui.graphics.ImageBitmap asImageBitmap(android.graphics.Bitmap);
+ method public static androidx.compose.ui.graphics.ImageBitmap imageFromResource(android.content.res.Resources res, int resId);
}
public final class AndroidMatrixConversionsKt {
@@ -218,8 +219,8 @@
method public void drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, androidx.compose.ui.graphics.Paint paint);
method public default void drawArcRad(androidx.compose.ui.geometry.Rect rect, float startAngleRad, float sweepAngleRad, boolean useCenter, androidx.compose.ui.graphics.Paint paint);
method public void drawCircle-tVKstsI(long center, float radius, androidx.compose.ui.graphics.Paint paint);
- method public void drawImage-YdY9XZw(androidx.compose.ui.graphics.ImageAsset image, long topLeftOffset, androidx.compose.ui.graphics.Paint paint);
- method public void drawImageRect-iaf9M94(androidx.compose.ui.graphics.ImageAsset image, optional long srcOffset, optional long srcSize, optional long dstOffset, optional long dstSize, androidx.compose.ui.graphics.Paint paint);
+ method public void drawImage-uwcbMjI(androidx.compose.ui.graphics.ImageBitmap image, long topLeftOffset, androidx.compose.ui.graphics.Paint paint);
+ method public void drawImageRect-bgE79EM(androidx.compose.ui.graphics.ImageBitmap image, optional long srcOffset, optional long srcSize, optional long dstOffset, optional long dstSize, androidx.compose.ui.graphics.Paint paint);
method public void drawLine-bYPfCD8(long p1, long p2, androidx.compose.ui.graphics.Paint paint);
method public default void drawOval(androidx.compose.ui.geometry.Rect rect, androidx.compose.ui.graphics.Paint paint);
method public void drawOval(float left, float top, float right, float bottom, androidx.compose.ui.graphics.Paint paint);
@@ -248,7 +249,7 @@
}
public final class CanvasKt {
- method public static androidx.compose.ui.graphics.Canvas Canvas(androidx.compose.ui.graphics.ImageAsset image);
+ method public static androidx.compose.ui.graphics.Canvas Canvas(androidx.compose.ui.graphics.ImageBitmap image);
method public static void rotate(androidx.compose.ui.graphics.Canvas, float degrees, float pivotX, float pivotY);
method public static void rotateRad(androidx.compose.ui.graphics.Canvas, float radians, optional float pivotX, optional float pivotY);
method public static void scale(androidx.compose.ui.graphics.Canvas, float sx, optional float sy, float pivotX, float pivotY);
@@ -357,32 +358,33 @@
public final class Float16Kt {
}
- public interface ImageAsset {
+ public interface ImageBitmap {
method public androidx.compose.ui.graphics.colorspace.ColorSpace getColorSpace();
- method public androidx.compose.ui.graphics.ImageAssetConfig getConfig();
+ method public androidx.compose.ui.graphics.ImageBitmapConfig getConfig();
method public boolean getHasAlpha();
method public int getHeight();
method public int getWidth();
method public void prepareToDraw();
method public void readPixels(int[] buffer, optional int startX, optional int startY, optional int width, optional int height, optional int bufferOffset, optional int stride);
property public abstract androidx.compose.ui.graphics.colorspace.ColorSpace colorSpace;
- property public abstract androidx.compose.ui.graphics.ImageAssetConfig config;
+ property public abstract androidx.compose.ui.graphics.ImageBitmapConfig config;
property public abstract boolean hasAlpha;
property public abstract int height;
property public abstract int width;
}
- public enum ImageAssetConfig {
- enum_constant public static final androidx.compose.ui.graphics.ImageAssetConfig Alpha8;
- enum_constant public static final androidx.compose.ui.graphics.ImageAssetConfig Argb8888;
- enum_constant public static final androidx.compose.ui.graphics.ImageAssetConfig F16;
- enum_constant public static final androidx.compose.ui.graphics.ImageAssetConfig Gpu;
- enum_constant public static final androidx.compose.ui.graphics.ImageAssetConfig Rgb565;
+ public enum ImageBitmapConfig {
+ enum_constant public static final androidx.compose.ui.graphics.ImageBitmapConfig Alpha8;
+ enum_constant public static final androidx.compose.ui.graphics.ImageBitmapConfig Argb8888;
+ enum_constant public static final androidx.compose.ui.graphics.ImageBitmapConfig F16;
+ enum_constant public static final androidx.compose.ui.graphics.ImageBitmapConfig Gpu;
+ enum_constant public static final androidx.compose.ui.graphics.ImageBitmapConfig Rgb565;
}
- public final class ImageAssetKt {
- method public static androidx.compose.ui.graphics.ImageAsset ImageAsset(int width, int height, optional androidx.compose.ui.graphics.ImageAssetConfig config, optional boolean hasAlpha, optional androidx.compose.ui.graphics.colorspace.ColorSpace colorSpace);
- method public static androidx.compose.ui.graphics.PixelMap toPixelMap(androidx.compose.ui.graphics.ImageAsset, optional int startX, optional int startY, optional int width, optional int height, optional int[] buffer, optional int bufferOffset, optional int stride);
+ public final class ImageBitmapKt {
+ method @Deprecated public static androidx.compose.ui.graphics.ImageBitmap ImageAsset(int width, int height, optional androidx.compose.ui.graphics.ImageBitmapConfig config, optional boolean hasAlpha, optional androidx.compose.ui.graphics.colorspace.ColorSpace colorSpace);
+ method public static androidx.compose.ui.graphics.ImageBitmap ImageBitmap(int width, int height, optional androidx.compose.ui.graphics.ImageBitmapConfig config, optional boolean hasAlpha, optional androidx.compose.ui.graphics.colorspace.ColorSpace colorSpace);
+ method public static androidx.compose.ui.graphics.PixelMap toPixelMap(androidx.compose.ui.graphics.ImageBitmap, optional int startX, optional int startY, optional int width, optional int height, optional int[] buffer, optional int bufferOffset, optional int stride);
}
@androidx.compose.runtime.Immutable public final class LinearGradient extends androidx.compose.ui.graphics.ShaderBrush {
@@ -603,7 +605,7 @@
}
public final class ShaderKt {
- method public static android.graphics.Shader ImageShader(androidx.compose.ui.graphics.ImageAsset image, optional androidx.compose.ui.graphics.TileMode tileModeX, optional androidx.compose.ui.graphics.TileMode tileModeY);
+ method public static android.graphics.Shader ImageShader(androidx.compose.ui.graphics.ImageBitmap image, optional androidx.compose.ui.graphics.TileMode tileModeX, optional androidx.compose.ui.graphics.TileMode tileModeY);
method public static android.graphics.Shader LinearGradientShader-GfyHbQM(long from, long to, java.util.List<androidx.compose.ui.graphics.Color> colors, optional java.util.List<java.lang.Float>? colorStops, optional androidx.compose.ui.graphics.TileMode tileMode);
method public static android.graphics.Shader RadialGradientShader-cY6o93o(long center, float radius, java.util.List<androidx.compose.ui.graphics.Color> colors, optional java.util.List<java.lang.Float>? colorStops, optional androidx.compose.ui.graphics.TileMode tileMode);
}
@@ -886,8 +888,8 @@
method public void drawArc-C-Io9bM(androidx.compose.ui.graphics.Brush brush, float startAngle, float sweepAngle, boolean useCenter, long topLeft, long size, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
method public void drawCircle-NGaRamM(long color, float radius, long center, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
method public void drawCircle-m-UMHxE(androidx.compose.ui.graphics.Brush brush, float radius, long center, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
- method public void drawImage-WFpWo7g(androidx.compose.ui.graphics.ImageAsset image, long topLeft, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
- method public void drawImage-oX8chH0(androidx.compose.ui.graphics.ImageAsset image, long srcOffset, long srcSize, long dstOffset, long dstSize, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
+ method public void drawImage-JUiai_k(androidx.compose.ui.graphics.ImageBitmap image, long topLeft, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
+ method public void drawImage-Yc2aOMw(androidx.compose.ui.graphics.ImageBitmap image, long srcOffset, long srcSize, long dstOffset, long dstSize, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
method public void drawLine-1-s4MmQ(long color, long start, long end, float strokeWidth, androidx.compose.ui.graphics.StrokeCap cap, android.graphics.PathEffect? pathEffect, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
method public void drawLine-IQGCKvc(androidx.compose.ui.graphics.Brush brush, long start, long end, float strokeWidth, androidx.compose.ui.graphics.StrokeCap cap, android.graphics.PathEffect? pathEffect, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
method public void drawOval-0a6MmAQ(androidx.compose.ui.graphics.Brush brush, long topLeft, long size, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, androidx.compose.ui.graphics.BlendMode blendMode);
@@ -952,8 +954,8 @@
method public void drawArc-C-Io9bM(androidx.compose.ui.graphics.Brush brush, float startAngle, float sweepAngle, boolean useCenter, optional long topLeft, optional long size, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
method public void drawCircle-NGaRamM(long color, optional float radius, optional long center, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
method public void drawCircle-m-UMHxE(androidx.compose.ui.graphics.Brush brush, optional float radius, optional long center, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
- method public void drawImage-WFpWo7g(androidx.compose.ui.graphics.ImageAsset image, optional long topLeft, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
- method public void drawImage-oX8chH0(androidx.compose.ui.graphics.ImageAsset image, optional long srcOffset, optional long srcSize, optional long dstOffset, optional long dstSize, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
+ method public void drawImage-JUiai_k(androidx.compose.ui.graphics.ImageBitmap image, optional long topLeft, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
+ method public void drawImage-Yc2aOMw(androidx.compose.ui.graphics.ImageBitmap image, optional long srcOffset, optional long srcSize, optional long dstOffset, optional long dstSize, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
method public void drawLine-1-s4MmQ(long color, long start, long end, optional float strokeWidth, optional androidx.compose.ui.graphics.StrokeCap cap, optional android.graphics.PathEffect? pathEffect, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
method public void drawLine-IQGCKvc(androidx.compose.ui.graphics.Brush brush, long start, long end, optional float strokeWidth, optional androidx.compose.ui.graphics.StrokeCap cap, optional android.graphics.PathEffect? pathEffect, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
method public void drawOval-0a6MmAQ(androidx.compose.ui.graphics.Brush brush, optional long topLeft, optional long size, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional androidx.compose.ui.graphics.BlendMode blendMode);
@@ -1075,7 +1077,7 @@
}
public final class ImagePainter extends androidx.compose.ui.graphics.painter.Painter {
- method public androidx.compose.ui.graphics.painter.ImagePainter copy-rHaiP50(androidx.compose.ui.graphics.ImageAsset image, long srcOffset, long srcSize);
+ method public androidx.compose.ui.graphics.painter.ImagePainter copy-MHcbwps(androidx.compose.ui.graphics.ImageBitmap image, long srcOffset, long srcSize);
method public long getIntrinsicSize-NH-jbRc();
method protected void onDraw(androidx.compose.ui.graphics.drawscope.DrawScope);
property public long intrinsicSize;
diff --git a/compose/ui/ui-graphics/lint-baseline.xml b/compose/ui/ui-graphics/lint-baseline.xml
index ada17b4..ac13d90 100644
--- a/compose/ui/ui-graphics/lint-baseline.xml
+++ b/compose/ui/ui-graphics/lint-baseline.xml
@@ -3,7 +3,7 @@
<issue
id="UnsafeNewApiCall"
- message="This call is to a method from API 26, the call containing class androidx.compose.ui.graphics.AndroidImageAssetKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
+ message="This call is to a method from API 26, the call containing class androidx.compose.ui.graphics.AndroidImageBitmapKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
errorLine1=" bitmap = Bitmap.createBitmap("
errorLine2=" ~~~~~~~~~~~~">
<location
@@ -14,7 +14,7 @@
<issue
id="UnsafeNewApiCall"
- message="This call is to a method from API 26, the call containing class androidx.compose.ui.graphics.AndroidImageAsset is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
+ message="This call is to a method from API 26, the call containing class androidx.compose.ui.graphics.AndroidImageBitmap is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
errorLine1=" bitmap.colorSpace?.toComposeColorSpace() ?: ColorSpaces.Srgb"
errorLine2=" ~~~~~~~~~~">
<location
@@ -25,7 +25,7 @@
<issue
id="UnsafeNewApiCall"
- message="This call is to a method from API 26, the call containing class androidx.compose.ui.graphics.AndroidImageAssetKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
+ message="This call is to a method from API 26, the call containing class androidx.compose.ui.graphics.AndroidImageBitmapKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
errorLine1=" return android.graphics.ColorSpace.get(frameworkNamedSpace)"
errorLine2=" ~~~">
<location
@@ -36,7 +36,7 @@
<issue
id="UnsafeNewApiCall"
- message="This call is to a method from API 26, the call containing class androidx.compose.ui.graphics.AndroidImageAssetKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
+ message="This call is to a method from API 26, the call containing class androidx.compose.ui.graphics.AndroidImageBitmapKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
errorLine1=" android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.SRGB)"
errorLine2=" ~~~">
<location
@@ -47,7 +47,7 @@
<issue
id="UnsafeNewApiCall"
- message="This call is to a method from API 26, the call containing class androidx.compose.ui.graphics.AndroidImageAssetKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
+ message="This call is to a method from API 26, the call containing class androidx.compose.ui.graphics.AndroidImageBitmapKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
errorLine1=" android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.ACES)"
errorLine2=" ~~~">
<location
@@ -58,7 +58,7 @@
<issue
id="UnsafeNewApiCall"
- message="This call is to a method from API 26, the call containing class androidx.compose.ui.graphics.AndroidImageAssetKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
+ message="This call is to a method from API 26, the call containing class androidx.compose.ui.graphics.AndroidImageBitmapKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
errorLine1=" android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.ACESCG)"
errorLine2=" ~~~">
<location
@@ -69,7 +69,7 @@
<issue
id="UnsafeNewApiCall"
- message="This call is to a method from API 26, the call containing class androidx.compose.ui.graphics.AndroidImageAssetKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
+ message="This call is to a method from API 26, the call containing class androidx.compose.ui.graphics.AndroidImageBitmapKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
errorLine1=" android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.ADOBE_RGB)"
errorLine2=" ~~~">
<location
@@ -80,7 +80,7 @@
<issue
id="UnsafeNewApiCall"
- message="This call is to a method from API 26, the call containing class androidx.compose.ui.graphics.AndroidImageAssetKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
+ message="This call is to a method from API 26, the call containing class androidx.compose.ui.graphics.AndroidImageBitmapKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
errorLine1=" android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.BT2020)"
errorLine2=" ~~~">
<location
@@ -91,7 +91,7 @@
<issue
id="UnsafeNewApiCall"
- message="This call is to a method from API 26, the call containing class androidx.compose.ui.graphics.AndroidImageAssetKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
+ message="This call is to a method from API 26, the call containing class androidx.compose.ui.graphics.AndroidImageBitmapKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
errorLine1=" android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.BT709)"
errorLine2=" ~~~">
<location
@@ -102,7 +102,7 @@
<issue
id="UnsafeNewApiCall"
- message="This call is to a method from API 26, the call containing class androidx.compose.ui.graphics.AndroidImageAssetKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
+ message="This call is to a method from API 26, the call containing class androidx.compose.ui.graphics.AndroidImageBitmapKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
errorLine1=" android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.CIE_LAB)"
errorLine2=" ~~~">
<location
@@ -113,7 +113,7 @@
<issue
id="UnsafeNewApiCall"
- message="This call is to a method from API 26, the call containing class androidx.compose.ui.graphics.AndroidImageAssetKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
+ message="This call is to a method from API 26, the call containing class androidx.compose.ui.graphics.AndroidImageBitmapKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
errorLine1=" android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.CIE_XYZ)"
errorLine2=" ~~~">
<location
@@ -124,7 +124,7 @@
<issue
id="UnsafeNewApiCall"
- message="This call is to a method from API 26, the call containing class androidx.compose.ui.graphics.AndroidImageAssetKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
+ message="This call is to a method from API 26, the call containing class androidx.compose.ui.graphics.AndroidImageBitmapKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
errorLine1=" android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.DCI_P3)"
errorLine2=" ~~~">
<location
@@ -135,7 +135,7 @@
<issue
id="UnsafeNewApiCall"
- message="This call is to a method from API 26, the call containing class androidx.compose.ui.graphics.AndroidImageAssetKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
+ message="This call is to a method from API 26, the call containing class androidx.compose.ui.graphics.AndroidImageBitmapKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
errorLine1=" android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.DISPLAY_P3)"
errorLine2=" ~~~">
<location
@@ -146,7 +146,7 @@
<issue
id="UnsafeNewApiCall"
- message="This call is to a method from API 26, the call containing class androidx.compose.ui.graphics.AndroidImageAssetKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
+ message="This call is to a method from API 26, the call containing class androidx.compose.ui.graphics.AndroidImageBitmapKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
errorLine1=" android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.EXTENDED_SRGB)"
errorLine2=" ~~~">
<location
@@ -157,7 +157,7 @@
<issue
id="UnsafeNewApiCall"
- message="This call is to a method from API 26, the call containing class androidx.compose.ui.graphics.AndroidImageAssetKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
+ message="This call is to a method from API 26, the call containing class androidx.compose.ui.graphics.AndroidImageBitmapKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
errorLine1=" android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.LINEAR_EXTENDED_SRGB)"
errorLine2=" ~~~">
<location
@@ -168,7 +168,7 @@
<issue
id="UnsafeNewApiCall"
- message="This call is to a method from API 26, the call containing class androidx.compose.ui.graphics.AndroidImageAssetKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
+ message="This call is to a method from API 26, the call containing class androidx.compose.ui.graphics.AndroidImageBitmapKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
errorLine1=" android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.LINEAR_SRGB)"
errorLine2=" ~~~">
<location
@@ -179,7 +179,7 @@
<issue
id="UnsafeNewApiCall"
- message="This call is to a method from API 26, the call containing class androidx.compose.ui.graphics.AndroidImageAssetKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
+ message="This call is to a method from API 26, the call containing class androidx.compose.ui.graphics.AndroidImageBitmapKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
errorLine1=" android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.NTSC_1953)"
errorLine2=" ~~~">
<location
@@ -190,7 +190,7 @@
<issue
id="UnsafeNewApiCall"
- message="This call is to a method from API 26, the call containing class androidx.compose.ui.graphics.AndroidImageAssetKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
+ message="This call is to a method from API 26, the call containing class androidx.compose.ui.graphics.AndroidImageBitmapKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
errorLine1=" android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.PRO_PHOTO_RGB)"
errorLine2=" ~~~">
<location
@@ -201,7 +201,7 @@
<issue
id="UnsafeNewApiCall"
- message="This call is to a method from API 26, the call containing class androidx.compose.ui.graphics.AndroidImageAssetKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
+ message="This call is to a method from API 26, the call containing class androidx.compose.ui.graphics.AndroidImageBitmapKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
errorLine1=" android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.SMPTE_C)"
errorLine2=" ~~~">
<location
diff --git a/compose/ui/ui-graphics/samples/src/main/java/androidx/compose/ui/graphics/samples/ImageAssetSample.kt b/compose/ui/ui-graphics/samples/src/main/java/androidx/compose/ui/graphics/samples/ImageAssetSample.kt
index cb699c3..a5bd091 100644
--- a/compose/ui/ui-graphics/samples/src/main/java/androidx/compose/ui/graphics/samples/ImageAssetSample.kt
+++ b/compose/ui/ui-graphics/samples/src/main/java/androidx/compose/ui/graphics/samples/ImageAssetSample.kt
@@ -20,22 +20,22 @@
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.ImageAsset
+import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.PixelMap
import androidx.compose.ui.graphics.toPixelMap
/**
* Sample showing how to obtain a [PixelMap] to query pixel information
- * from an underlying [ImageAsset]
+ * from an underlying [ImageBitmap]
*/
@Sampled
-fun ImageAssetToPixelMapSample() {
- val imageAsset = createImageAsset()
+fun ImageBitmapToPixelMapSample() {
+ val imageBitmap = createImageBitmap()
- // Sample a 3 by 2 subsection of the given ImageAsset
+ // Sample a 3 by 2 subsection of the given ImageBitmap
// starting at the coordinate (48, 49)
- val pixelmap = imageAsset.toPixelMap(
+ val pixelmap = imageBitmap.toPixelMap(
startX = 48,
startY = 49,
width = 3,
@@ -43,7 +43,7 @@
)
// create a histogram to count the number of occurrences of a color within the specified
- // subsection of the provided ImageAsset
+ // subsection of the provided ImageBitmap
val histogram = HashMap<Color, Int>()
for (x in 0 until pixelmap.width) {
for (y in 0 until pixelmap.height) {
@@ -55,16 +55,16 @@
}
/**
- * [ImageAsset.readPixels] sample that shows how to create a consumer defined
+ * [ImageBitmap.readPixels] sample that shows how to create a consumer defined
* IntArray to store pixel information and create a PixelMap for querying information
* within the buffer
*/
@Sampled
-fun ImageAssetReadPixelsSample() {
- val imageAsset = createImageAsset()
+fun ImageBitmapReadPixelsSample() {
+ val imageBitmap = createImageBitmap()
val buffer = IntArray(20 * 10)
- imageAsset.readPixels(
+ imageBitmap.readPixels(
buffer = buffer,
startX = 8,
startY = 9,
@@ -81,7 +81,7 @@
)
// create a histogram to count the number of occurrences of a color within the specified
- // subsection of the provided ImageAsset
+ // subsection of the provided ImageBitmap
val histogram = HashMap<Color, Int>()
for (x in 0 until pixelmap.width) {
for (y in 0 until pixelmap.height) {
@@ -92,9 +92,9 @@
}
}
-private fun createImageAsset(): ImageAsset {
- val imageAsset = ImageAsset(100, 100)
- val canvas = Canvas(imageAsset)
+private fun createImageBitmap(): ImageBitmap {
+ val imageBitmap = ImageBitmap(100, 100)
+ val canvas = Canvas(imageBitmap)
val paint = Paint()
// Draw 4 colored squares that are red, blue, green and yellow from the top left, top right
@@ -110,5 +110,5 @@
paint.color = Color.Yellow
canvas.drawRect(Rect(50.0f, 50.0f, 100.0f, 100.0f), paint)
- return imageAsset
+ return imageBitmap
}
diff --git a/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/AndroidCanvasTest.kt b/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/AndroidCanvasTest.kt
index 2e47f48..ecec048 100644
--- a/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/AndroidCanvasTest.kt
+++ b/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/AndroidCanvasTest.kt
@@ -89,8 +89,8 @@
val fg = androidx.compose.ui.graphics.Color.Blue
val width = 200
val height = 200
- val imageAsset = ImageAsset(width, height)
- val canvas = Canvas(imageAsset)
+ val imageBitmap = ImageBitmap(width, height)
+ val canvas = Canvas(imageBitmap)
val paint = Paint().apply { this.color = bg }
val rect = Rect(Offset.Zero, Size(width.toFloat(), height.toFloat()))
with(canvas) {
@@ -102,7 +102,7 @@
}
}
- val pixelMap = imageAsset.toPixelMap()
+ val pixelMap = imageBitmap.toPixelMap()
assertEquals(fg, pixelMap[0, 0])
assertEquals(fg, pixelMap[99, 0])
assertEquals(fg, pixelMap[0, 99])
@@ -121,8 +121,8 @@
val fg = androidx.compose.ui.graphics.Color.Blue
val width = 200
val height = 200
- val imageAsset = ImageAsset(width, height)
- val canvas = Canvas(imageAsset)
+ val imageBitmap = ImageBitmap(width, height)
+ val canvas = Canvas(imageBitmap)
val paint = Paint().apply { this.color = bg }
val rect = Rect(Offset(0.0f, 0.0f), Size(width.toFloat(), height.toFloat()))
with(canvas) {
@@ -134,7 +134,7 @@
}
}
- val pixelMap = imageAsset.toPixelMap()
+ val pixelMap = imageBitmap.toPixelMap()
val left = width / 2 - 50
val top = height / 2 - 50
val right = width / 2 + 50 - 1
@@ -167,8 +167,8 @@
val fg = androidx.compose.ui.graphics.Color.Blue
val width = 200
val height = 200
- val imageAsset = ImageAsset(width, height)
- val canvas = Canvas(imageAsset)
+ val imageBitmap = ImageBitmap(width, height)
+ val canvas = Canvas(imageBitmap)
val paint = Paint().apply { this.color = bg }
val rect = Rect(Offset(0.0f, 0.0f), Size(width.toFloat(), height.toFloat()))
with(canvas) {
@@ -180,7 +180,7 @@
}
}
- val pixelMap = imageAsset.toPixelMap()
+ val pixelMap = imageBitmap.toPixelMap()
val left = width - 100
val top = height - 100
@@ -205,8 +205,8 @@
val fg = androidx.compose.ui.graphics.Color.Blue
val width = 200
val height = 200
- val imageAsset = ImageAsset(width, height)
- val canvas = Canvas(imageAsset)
+ val imageBitmap = ImageBitmap(width, height)
+ val canvas = Canvas(imageBitmap)
val paint = Paint().apply { this.color = bg }
val rect = Rect(Offset(0.0f, 0.0f), Size(width.toFloat(), height.toFloat()))
with(canvas) {
@@ -221,7 +221,7 @@
}
}
- val pixelMap = imageAsset.toPixelMap()
+ val pixelMap = imageBitmap.toPixelMap()
assertEquals(fg, pixelMap[0, 0])
assertEquals(fg, pixelMap[99, 0])
assertEquals(fg, pixelMap[0, 99])
@@ -240,8 +240,8 @@
val fg = androidx.compose.ui.graphics.Color.Blue
val width = 200
val height = 200
- val imageAsset = ImageAsset(width, height)
- val canvas = Canvas(imageAsset)
+ val imageBitmap = ImageBitmap(width, height)
+ val canvas = Canvas(imageBitmap)
val paint = Paint().apply { this.color = bg }
val rect = Rect(Offset(0.0f, 0.0f), Size(width.toFloat(), height.toFloat()))
with(canvas) {
@@ -256,7 +256,7 @@
}
}
- val pixelMap = imageAsset.toPixelMap()
+ val pixelMap = imageBitmap.toPixelMap()
assertEquals(fg, pixelMap[2, 0])
assertEquals(fg, pixelMap[50, 49])
assertEquals(fg, pixelMap[70, 0])
diff --git a/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/ImageAssetTest.kt b/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/ImageBitmapTest.kt
similarity index 88%
rename from compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/ImageAssetTest.kt
rename to compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/ImageBitmapTest.kt
index b871b51..ab73407 100644
--- a/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/ImageAssetTest.kt
+++ b/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/ImageBitmapTest.kt
@@ -26,22 +26,22 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
-class ImageAssetTest {
+class ImageBitmapTest {
@Test
fun testCreatedImage() {
val cs = ColorSpaces.Srgb
- val image = ImageAsset(
+ val image = ImageBitmap(
width = 10,
height = 20,
- config = ImageAssetConfig.Argb8888,
+ config = ImageBitmapConfig.Argb8888,
hasAlpha = false,
colorSpace = cs
)
assertEquals(10, image.width)
assertEquals(20, image.height)
- assertEquals(ImageAssetConfig.Argb8888, image.config)
+ assertEquals(ImageBitmapConfig.Argb8888, image.config)
assertFalse(image.hasAlpha)
assertEquals(cs, image.colorSpace)
}
diff --git a/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/PathTest.kt b/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/PathTest.kt
index 6b208cc..dafe263 100644
--- a/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/PathTest.kt
+++ b/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/PathTest.kt
@@ -34,7 +34,7 @@
fun testAddArcPath() {
val width = 100
val height = 100
- val image = ImageAsset(width, height)
+ val image = ImageBitmap(width, height)
val canvas = Canvas(image)
val path1 = Path().apply {
addArcRad(
diff --git a/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/PixelMapTest.kt b/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/PixelMapTest.kt
index fbb2f5c..a733508 100644
--- a/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/PixelMapTest.kt
+++ b/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/PixelMapTest.kt
@@ -27,9 +27,9 @@
@RunWith(AndroidJUnit4::class)
class PixelMapTest {
- fun createImageAsset(): ImageAsset {
- val imageAsset = ImageAsset(100, 100)
- val canvas = Canvas(imageAsset)
+ fun createImageBitmap(): ImageBitmap {
+ val ImageBitmap = ImageBitmap(100, 100)
+ val canvas = Canvas(ImageBitmap)
val paint = Paint().apply { this.color = Color.Red }
canvas.drawRect(Rect(0.0f, 0.0f, 50.0f, 50.0f), paint)
@@ -42,13 +42,13 @@
paint.color = Color.Yellow
canvas.drawRect(Rect(50.0f, 50.0f, 100.0f, 100.0f), paint)
- return imageAsset
+ return ImageBitmap
}
@Test
- fun testImageAssetPixelMap() {
- val imageAsset = createImageAsset()
- val pixelmap = imageAsset.toPixelMap()
+ fun testImageBitmapPixelMap() {
+ val ImageBitmap = createImageBitmap()
+ val pixelmap = ImageBitmap.toPixelMap()
Assert.assertEquals(Color.Red, pixelmap[0, 0])
Assert.assertEquals(Color.Red, pixelmap[49, 0])
@@ -72,8 +72,8 @@
}
@Test
- fun testImageAssetSubsection() {
- val asset = createImageAsset()
+ fun testImageBitmapSubsection() {
+ val asset = createImageBitmap()
val subsectionWidth = 3
val subsectionHeight = 2
val bufferOffset = 3
diff --git a/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/drawscope/DrawScopeTest.kt b/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/drawscope/DrawScopeTest.kt
index c098d4f..b78b5f0 100644
--- a/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/drawscope/DrawScopeTest.kt
+++ b/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/drawscope/DrawScopeTest.kt
@@ -22,7 +22,7 @@
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.ClipOp
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.ImageAsset
+import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.PointMode
@@ -49,8 +49,8 @@
private val height: Int = 100
private val dstSize = Size(width.toFloat(), height.toFloat())
- private fun createTestDstImage(): ImageAsset {
- val dst = ImageAsset(width, height)
+ private fun createTestDstImage(): ImageBitmap {
+ val dst = ImageBitmap(width, height)
val dstCanvas = Canvas(dst)
val dstPaint = Paint().apply {
this.color = Color.White
@@ -398,16 +398,16 @@
val width = 200
val height = 200
val size = Size(width.toFloat(), height.toFloat())
- val imageAsset = ImageAsset(width, height)
+ val imageBitmap = ImageBitmap(width, height)
- canvasScope.draw(Canvas(imageAsset), size) {
+ canvasScope.draw(Canvas(imageBitmap), size) {
drawRect(color = Color.Red)
scale(0.5f, pivot = Offset.Zero) {
drawRect(color = Color.Blue)
}
}
- val pixelMap = imageAsset.toPixelMap()
+ val pixelMap = imageBitmap.toPixelMap()
assertEquals(Color.Blue, pixelMap[0, 0])
assertEquals(Color.Blue, pixelMap[99, 0])
assertEquals(Color.Blue, pixelMap[0, 99])
@@ -427,16 +427,16 @@
val width = 200
val height = 200
val size = Size(width.toFloat(), height.toFloat())
- val imageAsset = ImageAsset(width, height)
+ val imageBitmap = ImageBitmap(width, height)
- canvasScope.draw(Canvas(imageAsset), size) {
+ canvasScope.draw(Canvas(imageBitmap), size) {
drawRect(color = Color.Red)
scale(0.5f) {
drawRect(color = Color.Blue)
}
}
- val pixelMap = imageAsset.toPixelMap()
+ val pixelMap = imageBitmap.toPixelMap()
val left = width / 2 - 50
val top = height / 2 - 50
val right = width / 2 + 50 - 1
@@ -470,10 +470,10 @@
val width = 200
val height = 200
val size = Size(width.toFloat(), height.toFloat())
- val imageAsset = ImageAsset(width, height)
+ val imageBitmap = ImageBitmap(width, height)
try {
- canvasScope.draw(Canvas(imageAsset), size) {
+ canvasScope.draw(Canvas(imageBitmap), size) {
inset(100.0f, 0.0f, 101.0f, 0.0f) {
drawRect(color = Color.Red)
}
@@ -491,10 +491,10 @@
val width = 200
val height = 200
val size = Size(width.toFloat(), height.toFloat())
- val imageAsset = ImageAsset(width, height)
+ val imageBitmap = ImageBitmap(width, height)
try {
- canvasScope.draw(Canvas(imageAsset), size) {
+ canvasScope.draw(Canvas(imageBitmap), size) {
inset(0.0f, 100.0f, 0.0f, 101.0f) {
drawRect(color = Color.Red)
}
@@ -517,10 +517,10 @@
val width = 200
val height = 200
val size = Size(width.toFloat(), height.toFloat())
- val imageAsset = ImageAsset(width, height)
+ val imageBitmap = ImageBitmap(width, height)
try {
- canvasScope.draw(Canvas(imageAsset), size) {
+ canvasScope.draw(Canvas(imageBitmap), size) {
inset(0.0f, 100.0f, 0.0f, 100.0f) {
drawRect(color = Color.Red)
}
@@ -542,10 +542,10 @@
val width = 200
val height = 200
val size = Size(width.toFloat(), height.toFloat())
- val imageAsset = ImageAsset(width, height)
+ val imageBitmap = ImageBitmap(width, height)
try {
- canvasScope.draw(Canvas(imageAsset), size) {
+ canvasScope.draw(Canvas(imageBitmap), size) {
inset(100.0f, 0.0f, 100.0f, 0.0f) {
drawRect(color = Color.Red)
}
@@ -562,16 +562,16 @@
val width = 200
val height = 200
val size = Size(width.toFloat(), height.toFloat())
- val imageAsset = ImageAsset(width, height)
+ val imageBitmap = ImageBitmap(width, height)
- canvasScope.draw(Canvas(imageAsset), size) {
+ canvasScope.draw(Canvas(imageBitmap), size) {
drawRect(color = Color.Red)
scale(0.5f, 0.5f, Offset(width.toFloat(), height.toFloat())) {
drawRect(color = Color.Blue)
}
}
- val pixelMap = imageAsset.toPixelMap()
+ val pixelMap = imageBitmap.toPixelMap()
val left = width - 100
val top = height - 100
@@ -595,8 +595,8 @@
val width = 200
val height = 200
val size = Size(width.toFloat(), height.toFloat())
- val imageAsset = ImageAsset(width, height)
- CanvasDrawScope().draw(Canvas(imageAsset), size) {
+ val imageBitmap = ImageBitmap(width, height)
+ CanvasDrawScope().draw(Canvas(imageBitmap), size) {
drawRect(color = Color.Red)
rotate(180.0f) {
drawRect(
@@ -607,7 +607,7 @@
}
}
- val pixelMap = imageAsset.toPixelMap()
+ val pixelMap = imageBitmap.toPixelMap()
assertEquals(Color.Blue, pixelMap[0, 0])
assertEquals(Color.Blue, pixelMap[99, 0])
assertEquals(Color.Blue, pixelMap[0, 99])
@@ -625,8 +625,8 @@
val width = 200
val height = 200
val size = Size(width.toFloat(), height.toFloat())
- val imageAsset = ImageAsset(width, height)
- CanvasDrawScope().draw(Canvas(imageAsset), size) {
+ val imageBitmap = ImageBitmap(width, height)
+ CanvasDrawScope().draw(Canvas(imageBitmap), size) {
drawRect(color = Color.Red)
rotateRad(kotlin.math.PI.toFloat()) {
drawRect(
@@ -637,7 +637,7 @@
}
}
- val pixelMap = imageAsset.toPixelMap()
+ val pixelMap = imageBitmap.toPixelMap()
assertEquals(Color.Blue, pixelMap[0, 0])
assertEquals(Color.Blue, pixelMap[99, 0])
assertEquals(Color.Blue, pixelMap[0, 99])
@@ -655,8 +655,8 @@
val width = 200
val height = 200
val size = Size(width.toFloat(), height.toFloat())
- val imageAsset = ImageAsset(width, height)
- CanvasDrawScope().draw(Canvas(imageAsset), size) {
+ val imageBitmap = ImageBitmap(width, height)
+ CanvasDrawScope().draw(Canvas(imageBitmap), size) {
drawRect(color = Color.Red)
rotate(-45.0f, Offset.Zero) {
drawRect(
@@ -666,7 +666,7 @@
}
}
- val pixelMap = imageAsset.toPixelMap()
+ val pixelMap = imageBitmap.toPixelMap()
assertEquals(Color.Blue, pixelMap[2, 0])
assertEquals(Color.Blue, pixelMap[50, 49])
assertEquals(Color.Blue, pixelMap[70, 0])
@@ -682,8 +682,8 @@
val width = 200
val height = 200
val size = Size(width.toFloat(), height.toFloat())
- val imageAsset1 = ImageAsset(width, height)
- CanvasDrawScope().draw(Canvas(imageAsset1), size) {
+ val imageBitmap1 = ImageBitmap(width, height)
+ CanvasDrawScope().draw(Canvas(imageBitmap1), size) {
drawRect(color = Color.Red)
inset(20.0f, 12.0f, 10.0f, 8.0f) {
scale(2.0f, 0.5f) {
@@ -699,8 +699,8 @@
}
}
- val imageAsset2 = ImageAsset(width, height)
- val saveCountCanvas = SaveCountCanvas(Canvas(imageAsset2))
+ val imageBitmap2 = ImageBitmap(width, height)
+ val saveCountCanvas = SaveCountCanvas(Canvas(imageBitmap2))
CanvasDrawScope().draw(saveCountCanvas, size) {
drawRect(color = Color.Red)
withTransform({
@@ -722,8 +722,8 @@
assertEquals(1, saveCountCanvas.saveCount)
}
- val pixelMap1 = imageAsset1.toPixelMap()
- val pixelMap2 = imageAsset2.toPixelMap()
+ val pixelMap1 = imageBitmap1.toPixelMap()
+ val pixelMap2 = imageBitmap2.toPixelMap()
assertEquals(pixelMap1.width, pixelMap2.width)
assertEquals(pixelMap1.height, pixelMap2.height)
assertEquals(pixelMap1.stride, pixelMap2.stride)
@@ -868,7 +868,7 @@
@Test
fun testDensityAndLayoutDirectionConfigured() {
- val canvas = Canvas(ImageAsset(1, 1))
+ val canvas = Canvas(ImageBitmap(1, 1))
CanvasDrawScope().draw(
Density(density = 2.0f, fontScale = 3.0f),
LayoutDirection.Rtl,
@@ -884,8 +884,8 @@
@Test
fun testParametersRestoredAfterDraw() {
- val canvas1 = Canvas(ImageAsset(200, 300))
- val canvas2 = Canvas(ImageAsset(100, 200))
+ val canvas1 = Canvas(ImageBitmap(200, 300))
+ val canvas2 = Canvas(ImageBitmap(100, 200))
val size1 = Size(200f, 300f)
val size2 = Size(100f, 200f)
@@ -976,7 +976,7 @@
val width = 100
val height = 150
TestDrawScopeTransform().draw(
- Canvas(ImageAsset(width, height)),
+ Canvas(ImageBitmap(width, height)),
Size(width.toFloat(), height.toFloat())
) {
withWrappedTransform({
@@ -996,16 +996,16 @@
canvasBlock: (Canvas) -> Unit
) {
val size = Size(width.toFloat(), height.toFloat())
- val imageAsset1 = ImageAsset(width, height)
- CanvasDrawScope().draw(Canvas(imageAsset1), size) {
+ val imageBitmap1 = ImageBitmap(width, height)
+ CanvasDrawScope().draw(Canvas(imageBitmap1), size) {
drawScopeBlock()
}
- val imageAsset2 = ImageAsset(width, height)
- canvasBlock(Canvas(imageAsset2))
+ val imageBitmap2 = ImageBitmap(width, height)
+ canvasBlock(Canvas(imageBitmap2))
- val pixelMap1 = imageAsset1.toPixelMap()
- val pixelMap2 = imageAsset2.toPixelMap()
+ val pixelMap1 = imageBitmap1.toPixelMap()
+ val pixelMap2 = imageBitmap2.toPixelMap()
assertEquals(pixelMap1.width, pixelMap2.width)
assertEquals(pixelMap1.height, pixelMap2.height)
assertEquals(pixelMap1.stride, pixelMap2.stride)
diff --git a/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/painter/ImagePainterTest.kt b/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/painter/ImagePainterTest.kt
index 501f178..8d4d17f 100644
--- a/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/painter/ImagePainterTest.kt
+++ b/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/painter/ImagePainterTest.kt
@@ -23,7 +23,7 @@
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
-import androidx.compose.ui.graphics.ImageAsset
+import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.compositeOver
import androidx.compose.ui.graphics.toPixelMap
@@ -44,8 +44,8 @@
val white = Color.White
private val srcSize = Size(100.0f, 100.0f)
- private fun createTestSrcImage(): ImageAsset {
- val src = ImageAsset(100, 100)
+ private fun createTestSrcImage(): ImageBitmap {
+ val src = ImageBitmap(100, 100)
val canvas = Canvas(src)
val paint = Paint().apply {
this.color = Color.Blue
@@ -57,8 +57,8 @@
return src
}
- private fun createTestDstImage(): ImageAsset {
- val dst = ImageAsset(200, 200)
+ private fun createTestDstImage(): ImageBitmap {
+ val dst = ImageBitmap(200, 200)
val dstCanvas = Canvas(dst)
val dstPaint = Paint().apply {
this.color = Color.White
@@ -232,7 +232,7 @@
IntOffset(0, 0),
IntSize(image.width + 1, 10)
)
- fail("Right bound must be less than ImageAsset width")
+ fail("Right bound must be less than ImageBitmap width")
} catch (e: IllegalArgumentException) {
// no-op
}
@@ -247,7 +247,7 @@
IntOffset(0, 0),
IntSize(10, image.height + 1)
)
- fail("Bottom bound must be less than ImageAsset height")
+ fail("Bottom bound must be less than ImageBitmap height")
} catch (e: IllegalArgumentException) {
// no-op
}
diff --git a/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/painter/PainterTest.kt b/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/painter/PainterTest.kt
index 64ee193..16bc135 100644
--- a/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/painter/PainterTest.kt
+++ b/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/painter/PainterTest.kt
@@ -23,7 +23,7 @@
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
-import androidx.compose.ui.graphics.ImageAsset
+import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.compositeOver
import androidx.compose.ui.graphics.drawscope.DrawScope
@@ -62,7 +62,7 @@
drawPainter(
p,
- Canvas(ImageAsset(100, 100)),
+ Canvas(ImageBitmap(100, 100)),
Size(100f, 100f)
)
assertTrue(p.didDraw)
@@ -87,7 +87,7 @@
}
}
- val image = ImageAsset(100, 100)
+ val image = ImageBitmap(100, 100)
drawPainter(
p,
@@ -111,7 +111,7 @@
}
}
- val image = ImageAsset(100, 100)
+ val image = ImageBitmap(100, 100)
val canvas = Canvas(image)
val paint = Paint().apply { this.color = Color.White }
@@ -164,7 +164,7 @@
}
assertEquals(Color.Red, p.color)
- val image = ImageAsset(100, 100)
+ val image = ImageBitmap(100, 100)
val canvas = Canvas(image)
val paint = Paint().apply { this.color = Color.White }
@@ -210,7 +210,7 @@
}
}
- val image = ImageAsset(100, 100)
+ val image = ImageBitmap(100, 100)
drawPainter(
p,
diff --git a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidCanvas.kt b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidCanvas.kt
index 08f8162..970e993 100644
--- a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidCanvas.kt
+++ b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidCanvas.kt
@@ -26,9 +26,9 @@
/**
* Create a new Canvas instance that targets its drawing commands
- * to the provided [ImageAsset]
+ * to the provided [ImageBitmap]
*/
-internal actual fun ActualCanvas(image: ImageAsset): Canvas =
+internal actual fun ActualCanvas(image: ImageBitmap): Canvas =
AndroidCanvas().apply {
internalCanvas = android.graphics.Canvas(image.asAndroidBitmap())
}
@@ -249,7 +249,7 @@
/**
* @see Canvas.drawImage
*/
- override fun drawImage(image: ImageAsset, topLeftOffset: Offset, paint: Paint) {
+ override fun drawImage(image: ImageBitmap, topLeftOffset: Offset, paint: Paint) {
internalCanvas.drawBitmap(
image.asAndroidBitmap(),
topLeftOffset.x,
@@ -262,7 +262,7 @@
* @See Canvas.drawImageRect
*/
override fun drawImageRect(
- image: ImageAsset,
+ image: ImageBitmap,
srcOffset: IntOffset,
srcSize: IntSize,
dstOffset: IntOffset,
diff --git a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidImageAsset.kt b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidImageAsset.kt
deleted file mode 100644
index ab6e9e1..0000000
--- a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidImageAsset.kt
+++ /dev/null
@@ -1,262 +0,0 @@
-/*
- * Copyright 2019 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.ui.graphics
-
-import android.content.res.Resources
-import android.graphics.Bitmap
-import android.graphics.BitmapFactory
-import android.os.Build
-import android.util.DisplayMetrics
-import androidx.annotation.RequiresApi
-import androidx.compose.ui.graphics.colorspace.ColorSpace
-import androidx.compose.ui.graphics.colorspace.ColorSpaces
-
-/**
- * Create an [ImageAsset] from an image file stored in resources for the application
- *
- * @param res Resources object to query the image file from
- * @param resId Identifier for the image asset to query from [res]
- *
- * @return Loaded image file represented as an [ImageAsset]
- */
-fun imageFromResource(res: Resources, resId: Int): ImageAsset {
- return AndroidImageAsset(BitmapFactory.decodeResource(res, resId))
-}
-
-/**
- * Create an [ImageAsset] from the given [Bitmap]. Note this does
- * not create a copy of the original [Bitmap] and changes to it
- * will modify the returned [ImageAsset]
- */
-fun Bitmap.asImageAsset(): ImageAsset = AndroidImageAsset(this)
-
-internal actual fun ActualImageAsset(
- width: Int,
- height: Int,
- config: ImageAssetConfig,
- hasAlpha: Boolean,
- colorSpace: ColorSpace
-): ImageAsset {
- val bitmapConfig = config.toBitmapConfig()
- val bitmap: Bitmap
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- // Note intentionally ignoring density in all cases
- bitmap = Bitmap.createBitmap(
- null,
- width,
- height,
- bitmapConfig,
- hasAlpha,
- colorSpace.toFrameworkColorSpace()
- )
- } else {
- bitmap = Bitmap.createBitmap(
- null as DisplayMetrics?,
- width,
- height,
- bitmapConfig
- )
- bitmap.setHasAlpha(hasAlpha)
- }
- return AndroidImageAsset(bitmap)
-}
-
-/**
- * @Throws UnsupportedOperationException if this [ImageAsset] is not backed by an
- * android.graphics.Bitmap
- */
-fun ImageAsset.asAndroidBitmap(): Bitmap =
- when (this) {
- is AndroidImageAsset -> bitmap
- else -> throw UnsupportedOperationException("Unable to obtain android.graphics.Bitmap")
- }
-
-internal class AndroidImageAsset(internal val bitmap: Bitmap) : ImageAsset {
-
- override val width: Int
- get() = bitmap.width
-
- override val height: Int
- get() = bitmap.height
-
- override val config: ImageAssetConfig
- get() = bitmap.config.toImageConfig()
-
- override val colorSpace: ColorSpace
- get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- bitmap.colorSpace?.toComposeColorSpace() ?: ColorSpaces.Srgb
- } else {
- ColorSpaces.Srgb
- }
-
- override fun readPixels(
- buffer: IntArray,
- startX: Int,
- startY: Int,
- width: Int,
- height: Int,
- bufferOffset: Int,
- stride: Int
- ) {
- // Internal Android implementation that copies the pixels from the underlying
- // android.graphics.Bitmap if the configuration supports it
- val androidBitmap = asAndroidBitmap()
- var recycleTarget = false
- val targetBitmap =
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O ||
- androidBitmap.config != Bitmap.Config.HARDWARE
- ) {
- androidBitmap
- } else {
- // Because we are creating a copy for the purposes of reading pixels out of it
- // be sure to recycle this temporary bitmap when we are finished with it.
- recycleTarget = true
-
- // Pixels of a hardware bitmap cannot be queried directly so make a copy
- // of it into a configuration that can be queried
- // Passing in false for the isMutable parameter as we only intend to read pixel
- // information from the bitmap
- androidBitmap.copy(Bitmap.Config.ARGB_8888, false)
- }
-
- targetBitmap.getPixels(
- buffer,
- bufferOffset,
- stride,
- startX,
- startY,
- width,
- height
- )
- // Recycle the target if we are done with it
- if (recycleTarget) {
- targetBitmap.recycle()
- }
- }
-
- override val hasAlpha: Boolean
- get() = bitmap.hasAlpha()
-
- override fun prepareToDraw() {
- bitmap.prepareToDraw()
- }
-}
-
-internal fun ImageAssetConfig.toBitmapConfig(): Bitmap.Config {
- // Cannot utilize when statements with enums that may have different sets of supported
- // values between the compiled SDK and the platform version of the device.
- // As a workaround use if/else statements
- // See https://youtrack.jetbrains.com/issue/KT-30473 for details
- return if (this == ImageAssetConfig.Argb8888) {
- Bitmap.Config.ARGB_8888
- } else if (this == ImageAssetConfig.Alpha8) {
- Bitmap.Config.ALPHA_8
- } else if (this == ImageAssetConfig.Rgb565) {
- Bitmap.Config.RGB_565
- } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && this == ImageAssetConfig.F16) {
- Bitmap.Config.RGBA_F16
- } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && this == ImageAssetConfig.Gpu) {
- Bitmap.Config.HARDWARE
- } else {
- Bitmap.Config.ARGB_8888
- }
-}
-
-internal fun Bitmap.Config.toImageConfig(): ImageAssetConfig {
- // Cannot utilize when statements with enums that may have different sets of supported
- // values between the compiled SDK and the platform version of the device.
- // As a workaround use if/else statements
- // See https://youtrack.jetbrains.com/issue/KT-30473 for details
- @Suppress("DEPRECATION")
- return if (this == Bitmap.Config.ALPHA_8) {
- ImageAssetConfig.Alpha8
- } else if (this == Bitmap.Config.RGB_565) {
- ImageAssetConfig.Rgb565
- } else if (this == Bitmap.Config.ARGB_4444) {
- ImageAssetConfig.Argb8888 // Always upgrade to Argb_8888
- } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && this == Bitmap.Config.RGBA_F16) {
- ImageAssetConfig.F16
- } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && this == Bitmap.Config.HARDWARE) {
- ImageAssetConfig.Gpu
- } else {
- ImageAssetConfig.Argb8888
- }
-}
-
-@RequiresApi(Build.VERSION_CODES.O)
-internal fun ColorSpace.toFrameworkColorSpace(): android.graphics.ColorSpace {
- val frameworkNamedSpace = when (this) {
- ColorSpaces.Srgb -> android.graphics.ColorSpace.Named.SRGB
- ColorSpaces.Aces -> android.graphics.ColorSpace.Named.ACES
- ColorSpaces.Acescg -> android.graphics.ColorSpace.Named.ACESCG
- ColorSpaces.AdobeRgb -> android.graphics.ColorSpace.Named.ADOBE_RGB
- ColorSpaces.Bt2020 -> android.graphics.ColorSpace.Named.BT2020
- ColorSpaces.Bt709 -> android.graphics.ColorSpace.Named.BT709
- ColorSpaces.CieLab -> android.graphics.ColorSpace.Named.CIE_LAB
- ColorSpaces.CieXyz -> android.graphics.ColorSpace.Named.CIE_XYZ
- ColorSpaces.DciP3 -> android.graphics.ColorSpace.Named.DCI_P3
- ColorSpaces.DisplayP3 -> android.graphics.ColorSpace.Named.DISPLAY_P3
- ColorSpaces.ExtendedSrgb -> android.graphics.ColorSpace.Named.EXTENDED_SRGB
- ColorSpaces.LinearExtendedSrgb ->
- android.graphics.ColorSpace.Named.LINEAR_EXTENDED_SRGB
- ColorSpaces.LinearSrgb -> android.graphics.ColorSpace.Named.LINEAR_SRGB
- ColorSpaces.Ntsc1953 -> android.graphics.ColorSpace.Named.NTSC_1953
- ColorSpaces.ProPhotoRgb -> android.graphics.ColorSpace.Named.PRO_PHOTO_RGB
- ColorSpaces.SmpteC -> android.graphics.ColorSpace.Named.SMPTE_C
- else -> android.graphics.ColorSpace.Named.SRGB
- }
- return android.graphics.ColorSpace.get(frameworkNamedSpace)
-}
-
-@RequiresApi(Build.VERSION_CODES.O)
-internal fun android.graphics.ColorSpace.toComposeColorSpace(): ColorSpace {
- return when (this) {
- android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.SRGB)
- -> ColorSpaces.Srgb
- android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.ACES)
- -> ColorSpaces.Aces
- android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.ACESCG)
- -> ColorSpaces.Acescg
- android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.ADOBE_RGB)
- -> ColorSpaces.AdobeRgb
- android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.BT2020)
- -> ColorSpaces.Bt2020
- android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.BT709)
- -> ColorSpaces.Bt709
- android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.CIE_LAB)
- -> ColorSpaces.CieLab
- android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.CIE_XYZ)
- -> ColorSpaces.CieXyz
- android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.DCI_P3)
- -> ColorSpaces.DciP3
- android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.DISPLAY_P3)
- -> ColorSpaces.DisplayP3
- android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.EXTENDED_SRGB)
- -> ColorSpaces.ExtendedSrgb
- android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.LINEAR_EXTENDED_SRGB)
- -> ColorSpaces.LinearExtendedSrgb
- android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.LINEAR_SRGB)
- -> ColorSpaces.LinearSrgb
- android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.NTSC_1953)
- -> ColorSpaces.Ntsc1953
- android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.PRO_PHOTO_RGB)
- -> ColorSpaces.ProPhotoRgb
- android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.SMPTE_C)
- -> ColorSpaces.SmpteC
- else -> ColorSpaces.Srgb
- }
-}
\ No newline at end of file
diff --git a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidImageBitmap.kt b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidImageBitmap.kt
new file mode 100644
index 0000000..eab26b4
--- /dev/null
+++ b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidImageBitmap.kt
@@ -0,0 +1,300 @@
+/*
+ * Copyright 2019 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.ui.graphics
+
+import android.content.res.Resources
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.os.Build
+import android.util.DisplayMetrics
+import androidx.annotation.RequiresApi
+import androidx.compose.ui.graphics.colorspace.ColorSpace
+import androidx.compose.ui.graphics.colorspace.ColorSpaces
+
+/**
+ * Create an [ImageBitmap] from an image file stored in resources for the application
+ *
+ * @param res Resources object to query the image file from
+ * @param resId Identifier for the image asset to query from [res]
+ *
+ * @return Loaded image file represented as an [ImageBitmap]
+ */
+fun imageFromResource(res: Resources, resId: Int): ImageBitmap {
+ return AndroidImageBitmap(BitmapFactory.decodeResource(res, resId))
+}
+
+/**
+ * Create an [ImageBitmap] from the given [Bitmap]. Note this does
+ * not create a copy of the original [Bitmap] and changes to it
+ * will modify the returned [ImageBitmap]
+ */
+fun Bitmap.asImageBitmap(): ImageBitmap = AndroidImageBitmap(this)
+
+/**
+ * Create an [ImageBitmap] from the given [Bitmap]. Note this does
+ * not create a copy of the original [Bitmap] and changes to it
+ * will modify the returned [ImageBitmap]
+ */
+@Deprecated(
+ "Use asImageBitmap instead",
+ ReplaceWith("asImageBitmap()", "androidx.compose.ui.graphics")
+)
+fun Bitmap.asImageAsset(): ImageBitmap = asImageBitmap()
+
+internal actual fun ActualImageBitmap(
+ width: Int,
+ height: Int,
+ config: ImageBitmapConfig,
+ hasAlpha: Boolean,
+ colorSpace: ColorSpace
+): ImageBitmap {
+ val bitmapConfig = config.toBitmapConfig()
+ val bitmap: Bitmap
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ bitmap = Api26Bitmap.createBitmap(width, height, config, hasAlpha, colorSpace)
+ } else {
+ bitmap = Bitmap.createBitmap(
+ null as DisplayMetrics?,
+ width,
+ height,
+ bitmapConfig
+ )
+ bitmap.setHasAlpha(hasAlpha)
+ }
+ return AndroidImageBitmap(bitmap)
+}
+
+/**
+ * @Throws UnsupportedOperationException if this [ImageBitmap] is not backed by an
+ * android.graphics.Bitmap
+ */
+fun ImageBitmap.asAndroidBitmap(): Bitmap =
+ when (this) {
+ is AndroidImageBitmap -> bitmap
+ else -> throw UnsupportedOperationException("Unable to obtain android.graphics.Bitmap")
+ }
+
+internal class AndroidImageBitmap(internal val bitmap: Bitmap) : ImageBitmap {
+
+ override val width: Int
+ get() = bitmap.width
+
+ override val height: Int
+ get() = bitmap.height
+
+ override val config: ImageBitmapConfig
+ get() = bitmap.config.toImageConfig()
+
+ override val colorSpace: ColorSpace
+ get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ with (Api26Bitmap) {
+ bitmap.composeColorSpace()
+ }
+ } else {
+ ColorSpaces.Srgb
+ }
+
+ override fun readPixels(
+ buffer: IntArray,
+ startX: Int,
+ startY: Int,
+ width: Int,
+ height: Int,
+ bufferOffset: Int,
+ stride: Int
+ ) {
+ // Internal Android implementation that copies the pixels from the underlying
+ // android.graphics.Bitmap if the configuration supports it
+ val androidBitmap = asAndroidBitmap()
+ var recycleTarget = false
+ val targetBitmap =
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O ||
+ androidBitmap.config != Bitmap.Config.HARDWARE
+ ) {
+ androidBitmap
+ } else {
+ // Because we are creating a copy for the purposes of reading pixels out of it
+ // be sure to recycle this temporary bitmap when we are finished with it.
+ recycleTarget = true
+
+ // Pixels of a hardware bitmap cannot be queried directly so make a copy
+ // of it into a configuration that can be queried
+ // Passing in false for the isMutable parameter as we only intend to read pixel
+ // information from the bitmap
+ androidBitmap.copy(Bitmap.Config.ARGB_8888, false)
+ }
+
+ targetBitmap.getPixels(
+ buffer,
+ bufferOffset,
+ stride,
+ startX,
+ startY,
+ width,
+ height
+ )
+ // Recycle the target if we are done with it
+ if (recycleTarget) {
+ targetBitmap.recycle()
+ }
+ }
+
+ override val hasAlpha: Boolean
+ get() = bitmap.hasAlpha()
+
+ override fun prepareToDraw() {
+ bitmap.prepareToDraw()
+ }
+}
+
+internal fun ImageBitmapConfig.toBitmapConfig(): Bitmap.Config {
+ // Cannot utilize when statements with enums that may have different sets of supported
+ // values between the compiled SDK and the platform version of the device.
+ // As a workaround use if/else statements
+ // See https://youtrack.jetbrains.com/issue/KT-30473 for details
+ return if (this == ImageBitmapConfig.Argb8888) {
+ Bitmap.Config.ARGB_8888
+ } else if (this == ImageBitmapConfig.Alpha8) {
+ Bitmap.Config.ALPHA_8
+ } else if (this == ImageBitmapConfig.Rgb565) {
+ Bitmap.Config.RGB_565
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && this == ImageBitmapConfig.F16) {
+ Bitmap.Config.RGBA_F16
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && this == ImageBitmapConfig.Gpu) {
+ Bitmap.Config.HARDWARE
+ } else {
+ Bitmap.Config.ARGB_8888
+ }
+}
+
+internal fun Bitmap.Config.toImageConfig(): ImageBitmapConfig {
+ // Cannot utilize when statements with enums that may have different sets of supported
+ // values between the compiled SDK and the platform version of the device.
+ // As a workaround use if/else statements
+ // See https://youtrack.jetbrains.com/issue/KT-30473 for details
+ @Suppress("DEPRECATION")
+ return if (this == Bitmap.Config.ALPHA_8) {
+ ImageBitmapConfig.Alpha8
+ } else if (this == Bitmap.Config.RGB_565) {
+ ImageBitmapConfig.Rgb565
+ } else if (this == Bitmap.Config.ARGB_4444) {
+ ImageBitmapConfig.Argb8888 // Always upgrade to Argb_8888
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && this == Bitmap.Config.RGBA_F16) {
+ ImageBitmapConfig.F16
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && this == Bitmap.Config.HARDWARE) {
+ ImageBitmapConfig.Gpu
+ } else {
+ ImageBitmapConfig.Argb8888
+ }
+}
+
+/**
+ * Make Lint happy
+ * Separate class to contain all API calls that require API level 26 to assist in dead code
+ * elimination during compilation time
+ */
+private class Api26Bitmap {
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ companion object {
+
+ internal fun createBitmap(
+ width: Int,
+ height: Int,
+ bitmapConfig: ImageBitmapConfig,
+ hasAlpha: Boolean,
+ colorSpace: ColorSpace
+ ): Bitmap {
+ // Note intentionally ignoring density in all cases
+ return Bitmap.createBitmap(
+ null,
+ width,
+ height,
+ bitmapConfig.toBitmapConfig(),
+ hasAlpha,
+ colorSpace.toFrameworkColorSpace()
+ )
+ }
+
+ internal fun Bitmap.composeColorSpace() =
+ colorSpace?.composeColorSpace() ?: ColorSpaces.Srgb
+
+ internal fun ColorSpace.toFrameworkColorSpace(): android.graphics.ColorSpace {
+ val frameworkNamedSpace = when (this) {
+ ColorSpaces.Srgb -> android.graphics.ColorSpace.Named.SRGB
+ ColorSpaces.Aces -> android.graphics.ColorSpace.Named.ACES
+ ColorSpaces.Acescg -> android.graphics.ColorSpace.Named.ACESCG
+ ColorSpaces.AdobeRgb -> android.graphics.ColorSpace.Named.ADOBE_RGB
+ ColorSpaces.Bt2020 -> android.graphics.ColorSpace.Named.BT2020
+ ColorSpaces.Bt709 -> android.graphics.ColorSpace.Named.BT709
+ ColorSpaces.CieLab -> android.graphics.ColorSpace.Named.CIE_LAB
+ ColorSpaces.CieXyz -> android.graphics.ColorSpace.Named.CIE_XYZ
+ ColorSpaces.DciP3 -> android.graphics.ColorSpace.Named.DCI_P3
+ ColorSpaces.DisplayP3 -> android.graphics.ColorSpace.Named.DISPLAY_P3
+ ColorSpaces.ExtendedSrgb -> android.graphics.ColorSpace.Named.EXTENDED_SRGB
+ ColorSpaces.LinearExtendedSrgb ->
+ android.graphics.ColorSpace.Named.LINEAR_EXTENDED_SRGB
+ ColorSpaces.LinearSrgb -> android.graphics.ColorSpace.Named.LINEAR_SRGB
+ ColorSpaces.Ntsc1953 -> android.graphics.ColorSpace.Named.NTSC_1953
+ ColorSpaces.ProPhotoRgb -> android.graphics.ColorSpace.Named.PRO_PHOTO_RGB
+ ColorSpaces.SmpteC -> android.graphics.ColorSpace.Named.SMPTE_C
+ else -> android.graphics.ColorSpace.Named.SRGB
+ }
+ return android.graphics.ColorSpace.get(frameworkNamedSpace)
+ }
+
+ internal fun android.graphics.ColorSpace.composeColorSpace(): ColorSpace {
+ return when (this) {
+ android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.SRGB) ->
+ ColorSpaces.Srgb
+ android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.ACES) ->
+ ColorSpaces.Aces
+ android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.ACESCG) ->
+ ColorSpaces.Acescg
+ android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.ADOBE_RGB) ->
+ ColorSpaces.AdobeRgb
+ android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.BT2020) ->
+ ColorSpaces.Bt2020
+ android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.BT709) ->
+ ColorSpaces.Bt709
+ android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.CIE_LAB) ->
+ ColorSpaces.CieLab
+ android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.CIE_XYZ) ->
+ ColorSpaces.CieXyz
+ android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.DCI_P3) ->
+ ColorSpaces.DciP3
+ android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.DISPLAY_P3) ->
+ ColorSpaces.DisplayP3
+ android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.EXTENDED_SRGB) ->
+ ColorSpaces.ExtendedSrgb
+ android.graphics.ColorSpace.get(
+ android.graphics.ColorSpace.Named.LINEAR_EXTENDED_SRGB
+ ) ->
+ ColorSpaces.LinearExtendedSrgb
+ android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.LINEAR_SRGB) ->
+ ColorSpaces.LinearSrgb
+ android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.NTSC_1953) ->
+ ColorSpaces.Ntsc1953
+ android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.PRO_PHOTO_RGB) ->
+ ColorSpaces.ProPhotoRgb
+ android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.SMPTE_C) ->
+ ColorSpaces.SmpteC
+ else -> ColorSpaces.Srgb
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidShader.kt b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidShader.kt
index 7704d47..f0918e2b 100644
--- a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidShader.kt
+++ b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidShader.kt
@@ -61,7 +61,7 @@
}
internal actual fun ActualImageShader(
- image: ImageAsset,
+ image: ImageBitmap,
tileModeX: TileMode,
tileModeY: TileMode
): Shader {
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Canvas.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Canvas.kt
index 5f181b5..1cd50b9 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Canvas.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Canvas.kt
@@ -23,11 +23,11 @@
/**
* Create a new Canvas instance that targets its drawing commands
- * to the provided [ImageAsset]
+ * to the provided [ImageBitmap]
*/
-fun Canvas(image: ImageAsset): Canvas = ActualCanvas(image)
+fun Canvas(image: ImageBitmap): Canvas = ActualCanvas(image)
-internal expect fun ActualCanvas(image: ImageAsset): Canvas
+internal expect fun ActualCanvas(image: ImageBitmap): Canvas
expect class NativeCanvas
@@ -535,10 +535,10 @@
fun drawPath(path: Path, paint: Paint)
/**
- * Draws the given [ImageAsset] into the canvas with its top-left corner at the
+ * Draws the given [ImageBitmap] into the canvas with its top-left corner at the
* given [Offset]. The image is composited into the canvas using the given [Paint].
*/
- fun drawImage(image: ImageAsset, topLeftOffset: Offset, paint: Paint)
+ fun drawImage(image: ImageBitmap, topLeftOffset: Offset, paint: Paint)
/**
* Draws the subset of the given image described by the `src` argument into
@@ -547,7 +547,7 @@
* This might sample from outside the `src` rect by up to half the width of
* an applied filter.
*
- * @param image ImageAsset to draw
+ * @param image ImageBitmap to draw
* @param srcOffset: Optional offset representing the top left offset of the source image
* to draw, this defaults to the origin of [image]
* @param srcSize: Optional dimensions of the source image to draw relative to [srcOffset],
@@ -555,10 +555,10 @@
* @param dstOffset: Offset representing the top left offset of the destination image
* to draw
* @param dstSize: Dimensions of the destination to draw
- * @param paint Paint used to composite the [ImageAsset] pixels into the canvas
+ * @param paint Paint used to composite the [ImageBitmap] pixels into the canvas
*/
fun drawImageRect(
- image: ImageAsset,
+ image: ImageBitmap,
srcOffset: IntOffset = IntOffset.Zero,
srcSize: IntSize = IntSize(image.width, image.height),
dstOffset: IntOffset = IntOffset.Zero,
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/ImageAsset.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/ImageBitmap.kt
similarity index 74%
rename from compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/ImageAsset.kt
rename to compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/ImageBitmap.kt
index 8b051d3..2029713 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/ImageAsset.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/ImageBitmap.kt
@@ -23,49 +23,49 @@
* Graphics object that represents a 2 dimensional array of pixel information represented
* as ARGB values
*/
-interface ImageAsset {
+interface ImageBitmap {
- /** The number of image pixels along the ImageAssets's horizontal axis. */
+ /** The number of image pixels along the ImageBitmap's horizontal axis. */
val width: Int
- /** The number of image pixels along the ImageAssets's vertical axis. */
+ /** The number of image pixels along the ImageBitmap's vertical axis. */
val height: Int
/** ColorSpace the Image renders in **/
val colorSpace: ColorSpace
- /** Determines whether or not the ImageAsset contains an alpha channel **/
+ /** Determines whether or not the ImageBitmap contains an alpha channel **/
val hasAlpha: Boolean
/**
* Returns the current configuration of this Image, either:
- * @see ImageAssetConfig.Argb8888
- * @see ImageAssetConfig.Rgb565
- * @see ImageAssetConfig.Alpha8
- * @see ImageAssetConfig.Gpu
+ * @see ImageBitmapConfig.Argb8888
+ * @see ImageBitmapConfig.Rgb565
+ * @see ImageBitmapConfig.Alpha8
+ * @see ImageBitmapConfig.Gpu
*/
- val config: ImageAssetConfig
+ val config: ImageBitmapConfig
/**
- * Copies the pixel data within the ImageAsset into the given array. Each value is
+ * Copies the pixel data within the ImageBitmap into the given array. Each value is
* represented as ARGB values packed into an Int.
* The stride parameter allows the caller to allow for gaps in the returned pixels array
* between rows. For normal packed, results, the stride value is equivalent to the width of
- * the [ImageAsset]. The returned colors are non-premultiplied ARGB values in the
+ * the [ImageBitmap]. The returned colors are non-premultiplied ARGB values in the
* [ColorSpaces.Srgb] color space.
*
* Note this method can block so it is recommended to not invoke this method in performance
* critical code paths
*
- * @sample androidx.compose.ui.graphics.samples.ImageAssetReadPixelsSample
+ * @sample androidx.compose.ui.graphics.samples.ImageBitmapReadPixelsSample
*
- * @param buffer The array to store the [ImageAsset]'s colors. By default this allocates an
+ * @param buffer The array to store the [ImageBitmap]'s colors. By default this allocates an
* [IntArray] large enough to store all the pixel information. Consumers of this API are
* advised to use the smallest [IntArray] necessary to extract relevant pixel information, that
- * is the 2 dimensional area of the section of the [ImageAsset] to be queried.
+ * is the 2 dimensional area of the section of the [ImageBitmap] to be queried.
*
- * @param startX The x-coordinate of the first pixel to read from the [ImageAsset]
- * @param startY The y-coordinate of the first pixel to read from the [ImageAsset]
+ * @param startX The x-coordinate of the first pixel to read from the [ImageBitmap]
+ * @param startY The y-coordinate of the first pixel to read from the [ImageBitmap]
* @param width The number of pixels to read from each row
* @param height The number of rows to read
* @param bufferOffset The first index to write into the buffer array, this defaults to 0
@@ -82,34 +82,34 @@
)
/**
- * Builds caches associated with the ImageAsset that are used for drawing it. This method can
+ * Builds caches associated with the ImageBitmap that are used for drawing it. This method can
* be used as a signal to upload textures to the GPU to eventually be rendered
*/
fun prepareToDraw()
}
/**
- * Convenience method to extract pixel information from the given ImageAsset into a [PixelMap]
+ * Convenience method to extract pixel information from the given ImageBitmap into a [PixelMap]
* that supports for querying pixel information based on
*
* Note this method can block so it is recommended to not invoke this method in performance
* critical code paths
*
- * @sample androidx.compose.ui.graphics.samples.ImageAssetToPixelMapSample
+ * @sample androidx.compose.ui.graphics.samples.ImageBitmapToPixelMapSample
*
- * @param startX The x-coordinate of the first pixel to read from the [ImageAsset]
- * @param startY The y-coordinate of the first pixel to read from the [ImageAsset]
+ * @param startX The x-coordinate of the first pixel to read from the [ImageBitmap]
+ * @param startY The y-coordinate of the first pixel to read from the [ImageBitmap]
* @param width The number of pixels to read from each row
* @param height The number of rows to read
- * @param buffer The array to store the [ImageAsset]'s colors. By default this allocates an
+ * @param buffer The array to store the [ImageBitmap]'s colors. By default this allocates an
* [IntArray] large enough to store all the pixel information. Consumers of this API are
* advised to use the smallest [IntArray] necessary to extract relevant pixel information
* @param bufferOffset The first index to write into the buffer array, this defaults to 0
* @param stride The number of entries in [buffer] to skip between rows (must be >= [width]
*
- * @see ImageAsset.readPixels
+ * @see ImageBitmap.readPixels
*/
-fun ImageAsset.toPixelMap(
+fun ImageBitmap.toPixelMap(
startX: Int = 0,
startY: Int = 0,
width: Int = this.width,
@@ -131,11 +131,11 @@
}
/**
- * Possible ImageAsset configurations. An ImageAsset configuration describes
+ * Possible ImageBitmap configurations. An ImageBitmap configuration describes
* how pixels are stored. This affects the quality (color depth) as
* well as the ability to display transparent/translucent colors.
*/
-enum class ImageAssetConfig {
+enum class ImageBitmapConfig {
/**
* Each pixel is stored on 4 bytes. Each channel (RGB and alpha
* for translucency) is stored with 8 bits of precision (256
@@ -208,30 +208,51 @@
F16,
/**
- * Special configuration, when an ImageAsset is stored only in graphic memory.
- * ImageAssets in this configuration are always immutable.
+ * Special configuration, when an ImageBitmap is stored only in graphic memory.
+ * ImageBitmaps in this configuration are always immutable.
*
- * It is optimal for cases, when the only operation with the ImageAsset is to draw it on a
+ * It is optimal for cases, when the only operation with the ImageBitmap is to draw it on a
* screen.
*/
Gpu
}
-internal expect fun ActualImageAsset(
+internal expect fun ActualImageBitmap(
width: Int,
height: Int,
- config: ImageAssetConfig,
+ config: ImageBitmapConfig,
hasAlpha: Boolean,
colorSpace: ColorSpace
-): ImageAsset
+): ImageBitmap
+fun ImageBitmap(
+ width: Int,
+ height: Int,
+ config: ImageBitmapConfig = ImageBitmapConfig.Argb8888,
+ hasAlpha: Boolean = true,
+ colorSpace: ColorSpace = ColorSpaces.Srgb
+): ImageBitmap = ActualImageBitmap(
+ width,
+ height,
+ config,
+ hasAlpha,
+ colorSpace
+)
+
+@Deprecated(
+ "Use ImageBitmap instead",
+ ReplaceWith(
+ "ImageBitmap(width, height, config, hasAlpha, colorSpace)",
+ "androidx.compose.ui.graphics"
+ )
+)
fun ImageAsset(
width: Int,
height: Int,
- config: ImageAssetConfig = ImageAssetConfig.Argb8888,
+ config: ImageBitmapConfig = ImageBitmapConfig.Argb8888,
hasAlpha: Boolean = true,
colorSpace: ColorSpace = ColorSpaces.Srgb
-): ImageAsset = ActualImageAsset(
+): ImageBitmap = ActualImageBitmap(
width,
height,
config,
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/PixelMap.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/PixelMap.kt
index e6f3cb1..b155107 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/PixelMap.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/PixelMap.kt
@@ -19,21 +19,21 @@
import androidx.compose.ui.util.annotation.IntRange
/**
- * Result of a pixel read operation. This contains the [ImageAsset] pixel information represented
+ * Result of a pixel read operation. This contains the [ImageBitmap] pixel information represented
* as a 1 dimensional array of values that supports queries of pixel values based on the 2
- * dimensional coordinates of the corresponding [ImageAsset] this was obtained from
+ * dimensional coordinates of the corresponding [ImageBitmap] this was obtained from
*
- * @sample androidx.compose.ui.graphics.samples.ImageAssetReadPixelsSample
+ * @sample androidx.compose.ui.graphics.samples.ImageBitmapReadPixelsSample
*
* @param buffer IntArray where pixel information is stored as an ARGB value packed into an Int
- * @param bufferOffset first index in the buffer where pixel information for the [ImageAsset] is
+ * @param bufferOffset first index in the buffer where pixel information for the [ImageBitmap] is
* stored
- * @param width Width of the subsection of the [ImageAsset] this buffer represents
- * @param height Height of the subsection of the [ImageAsset] this buffer represents
+ * @param width Width of the subsection of the [ImageBitmap] this buffer represents
+ * @param height Height of the subsection of the [ImageBitmap] this buffer represents
* @param stride Number of entries to skip between rows
*
- * @see ImageAsset.readPixels
- * @See ImageAsset.toPixelMap
+ * @see ImageBitmap.readPixels
+ * @See ImageBitmap.toPixelMap
*/
class PixelMap(
val buffer: IntArray,
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Shader.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Shader.kt
index c24b2ae..ed91332 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Shader.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Shader.kt
@@ -89,13 +89,13 @@
): Shader
fun ImageShader(
- image: ImageAsset,
+ image: ImageBitmap,
tileModeX: TileMode = TileMode.Clamp,
tileModeY: TileMode = TileMode.Clamp
): Shader = ActualImageShader(image, tileModeX, tileModeY)
internal expect fun ActualImageShader(
- image: ImageAsset,
+ image: ImageBitmap,
tileModeX: TileMode,
tileModeY: TileMode
): Shader
\ No newline at end of file
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/CanvasDrawScope.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/CanvasDrawScope.kt
index 8b0f29c..a73b4a4 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/CanvasDrawScope.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/CanvasDrawScope.kt
@@ -25,7 +25,7 @@
import androidx.compose.ui.graphics.ClipOp
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
-import androidx.compose.ui.graphics.ImageAsset
+import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.Matrix
import androidx.compose.ui.graphics.NativePathEffect
import androidx.compose.ui.graphics.Paint
@@ -193,7 +193,7 @@
* @see [DrawScope.drawImage]
*/
override fun drawImage(
- image: ImageAsset,
+ image: ImageBitmap,
topLeft: Offset,
@FloatRange(from = 0.0, to = 1.0) alpha: Float,
style: DrawStyle,
@@ -209,7 +209,7 @@
* @see [DrawScope.drawImage]
*/
override fun drawImage(
- image: ImageAsset,
+ image: ImageBitmap,
srcOffset: IntOffset,
srcSize: IntSize,
dstOffset: IntOffset,
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/DrawScope.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/DrawScope.kt
index f04d876..16f8b4f 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/DrawScope.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/DrawScope.kt
@@ -25,7 +25,7 @@
import androidx.compose.ui.graphics.ClipOp
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
-import androidx.compose.ui.graphics.ImageAsset
+import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.NativePathEffect
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.Path
@@ -409,10 +409,10 @@
)
/**
- * Draws the given [ImageAsset] into the canvas with its top-left corner at the
+ * Draws the given [ImageBitmap] into the canvas with its top-left corner at the
* given [Offset]. The image is composited into the canvas using the given [Paint].
*
- * @param image The [ImageAsset] to draw
+ * @param image The [ImageBitmap] to draw
* @param topLeft Offset from the local origin of 0, 0 relative to the current translation
* @param alpha Opacity to be applied to [image] from 0.0f to 1.0f representing
* fully transparent to fully opaque respectively
@@ -421,7 +421,7 @@
* @param blendMode Blending algorithm to apply to destination
*/
fun drawImage(
- image: ImageAsset,
+ image: ImageBitmap,
topLeft: Offset = Offset.Zero,
@FloatRange(from = 0.0, to = 1.0) alpha: Float = 1.0f,
style: DrawStyle = Fill,
@@ -452,7 +452,7 @@
* @param blendMode Blending algorithm to apply to destination
*/
fun drawImage(
- image: ImageAsset,
+ image: ImageBitmap,
srcOffset: IntOffset = IntOffset.Zero,
srcSize: IntSize = IntSize(image.width, image.height),
dstOffset: IntOffset = IntOffset.Zero,
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/EmptyCanvas.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/EmptyCanvas.kt
index fa7014a..e09df38 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/EmptyCanvas.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/EmptyCanvas.kt
@@ -21,7 +21,7 @@
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.ClipOp
-import androidx.compose.ui.graphics.ImageAsset
+import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.PointMode
@@ -126,12 +126,12 @@
throw UnsupportedOperationException()
}
- override fun drawImage(image: ImageAsset, topLeftOffset: Offset, paint: Paint) {
+ override fun drawImage(image: ImageBitmap, topLeftOffset: Offset, paint: Paint) {
throw UnsupportedOperationException()
}
override fun drawImageRect(
- image: ImageAsset,
+ image: ImageBitmap,
srcOffset: IntOffset,
srcSize: IntSize,
dstOffset: IntOffset,
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/painter/ImagePainter.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/painter/ImagePainter.kt
index 9211d42..d0223d1 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/painter/ImagePainter.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/painter/ImagePainter.kt
@@ -18,7 +18,7 @@
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.ColorFilter
-import androidx.compose.ui.graphics.ImageAsset
+import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
@@ -26,12 +26,12 @@
import kotlin.math.roundToInt
/**
- * [Painter] implementation used to draw an [ImageAsset] into the provided canvas
+ * [Painter] implementation used to draw an [ImageBitmap] into the provided canvas
* This implementation can handle applying alpha and [ColorFilter] to it's drawn result
*
- * @param image The [ImageAsset] to draw
+ * @param image The [ImageBitmap] to draw
* @param srcOffset Optional offset relative to [image] used to draw a subsection of the
- * [ImageAsset]. By default this uses the origin of [image]
+ * [ImageBitmap]. By default this uses the origin of [image]
* @param srcSize Optional dimensions representing size of the subsection of [image] to draw
* Both the offset and size must have the following requirements:
*
@@ -40,7 +40,7 @@
* 3) Source size must be less than or equal to the dimensions of [image]
*/
data class ImagePainter(
- private val image: ImageAsset,
+ private val image: ImageBitmap,
private val srcOffset: IntOffset = IntOffset.Zero,
private val srcSize: IntSize = IntSize(image.width, image.height)
) : Painter() {
@@ -66,7 +66,7 @@
}
/**
- * Return the dimension of the underlying [ImageAsset] as it's intrinsic width and height
+ * Return the dimension of the underlying [ImageBitmap] as it's intrinsic width and height
*/
override val intrinsicSize: Size get() = size.toSize()
diff --git a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopCanvas.kt b/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopCanvas.kt
index 2432a0d..43b19d1 100644
--- a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopCanvas.kt
+++ b/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopCanvas.kt
@@ -30,10 +30,10 @@
actual typealias NativeCanvas = org.jetbrains.skija.Canvas
-internal actual fun ActualCanvas(image: ImageAsset): Canvas {
+internal actual fun ActualCanvas(image: ImageBitmap): Canvas {
val skijaBitmap = image.asDesktopBitmap()
require(!skijaBitmap.isImmutable) {
- "Cannot draw on immutable ImageAsset"
+ "Cannot draw on immutable ImageBitmap"
}
return DesktopCanvas(org.jetbrains.skija.Canvas(skijaBitmap))
}
@@ -162,7 +162,7 @@
skija.drawPath(path.asDesktopPath(), paint.skija)
}
- override fun drawImage(image: ImageAsset, topLeftOffset: Offset, paint: Paint) {
+ override fun drawImage(image: ImageBitmap, topLeftOffset: Offset, paint: Paint) {
skija.drawBitmapRect(
image.asDesktopBitmap(),
SkijaRect.makeXYWH(
@@ -182,7 +182,7 @@
}
override fun drawImageRect(
- image: ImageAsset,
+ image: ImageBitmap,
srcOffset: IntOffset,
srcSize: IntSize,
dstOffset: IntOffset,
diff --git a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopImageAsset.kt b/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopImageAsset.kt
index 30cbcba..f67add9 100644
--- a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopImageAsset.kt
+++ b/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopImageAsset.kt
@@ -29,16 +29,16 @@
import kotlin.math.abs
/**
- * Create an [ImageAsset] from the given [Bitmap]. Note this does
+ * Create an [ImageBitmap] from the given [Bitmap]. Note this does
* not create a copy of the original [Bitmap] and changes to it
- * will modify the returned [ImageAsset]
+ * will modify the returned [ImageBitmap]
*/
-fun Bitmap.asImageAsset(): ImageAsset = DesktopImageAsset(this)
+fun Bitmap.asImageBitmap(): ImageBitmap = DesktopImageBitmap(this)
/**
- * Create an [ImageAsset] from the given [Image].
+ * Create an [ImageBitmap] from the given [Image].
*/
-fun Image.asImageAsset(): ImageAsset = DesktopImageAsset(toBitmap())
+fun Image.asImageBitmap(): ImageBitmap = DesktopImageBitmap(toBitmap())
private fun Image.toBitmap(): Bitmap {
val bitmap = Bitmap()
@@ -49,13 +49,13 @@
return bitmap
}
-internal actual fun ActualImageAsset(
+internal actual fun ActualImageBitmap(
width: Int,
height: Int,
- config: ImageAssetConfig,
+ config: ImageBitmapConfig,
hasAlpha: Boolean,
colorSpace: ColorSpace
-): ImageAsset {
+): ImageBitmap {
val colorType = config.toSkijaColorType()
val alphaType = if (hasAlpha) ColorAlphaType.PREMUL else ColorAlphaType.OPAQUE
val skijaColorSpace = colorSpace.toSkijaColorSpace()
@@ -63,18 +63,18 @@
val imageInfo = ImageInfo(colorInfo, width, height)
val bitmap = Bitmap()
bitmap.allocPixels(imageInfo)
- return DesktopImageAsset(bitmap)
+ return DesktopImageBitmap(bitmap)
}
/**
- * Create an [ImageAsset] from an image file stored in resources for the application
+ * Create an [ImageBitmap] from an image file stored in resources for the application
*
* @param path path to the image file
*
- * @return Loaded image file represented as an [ImageAsset]
+ * @return Loaded image file represented as an [ImageBitmap]
*/
-fun imageFromResource(path: String): ImageAsset =
- Image.makeFromEncoded(loadResource(path)).asImageAsset()
+fun imageFromResource(path: String): ImageBitmap =
+ Image.makeFromEncoded(loadResource(path)).asImageBitmap()
private fun loadResource(path: String): ByteArray {
val resource = Thread.currentThread().contextClassLoader.getResource(path)
@@ -83,16 +83,16 @@
}
/**
- * @Throws UnsupportedOperationException if this [ImageAsset] is not backed by an
+ * @Throws UnsupportedOperationException if this [ImageBitmap] is not backed by an
* org.jetbrains.skija.Image
*/
-fun ImageAsset.asDesktopBitmap(): Bitmap =
+fun ImageBitmap.asDesktopBitmap(): Bitmap =
when (this) {
- is DesktopImageAsset -> bitmap
+ is DesktopImageBitmap -> bitmap
else -> throw UnsupportedOperationException("Unable to obtain org.jetbrains.skija.Image")
}
-private class DesktopImageAsset(val bitmap: Bitmap) : ImageAsset {
+private class DesktopImageBitmap(val bitmap: Bitmap) : ImageBitmap {
override val colorSpace = bitmap.colorSpace.toComposeColorSpace()
override val config = bitmap.colorType.toComposeConfig()
override val hasAlpha = !bitmap.isOpaque
@@ -140,20 +140,20 @@
// in toSkijaColorType/toComposeConfig/toComposeColorSpace/toSkijaColorSpace
// see [https://android-review.googlesource.com/c/platform/frameworks/support/+/1429835/comment/c219501b_63c3d1fe/]
-private fun ImageAssetConfig.toSkijaColorType() = when (this) {
- ImageAssetConfig.Argb8888 -> ColorType.N32
- ImageAssetConfig.Alpha8 -> ColorType.ALPHA_8
- ImageAssetConfig.Rgb565 -> ColorType.RGB_565
- ImageAssetConfig.F16 -> ColorType.RGBA_F16
+private fun ImageBitmapConfig.toSkijaColorType() = when (this) {
+ ImageBitmapConfig.Argb8888 -> ColorType.N32
+ ImageBitmapConfig.Alpha8 -> ColorType.ALPHA_8
+ ImageBitmapConfig.Rgb565 -> ColorType.RGB_565
+ ImageBitmapConfig.F16 -> ColorType.RGBA_F16
else -> ColorType.N32
}
private fun ColorType.toComposeConfig() = when (this) {
- ColorType.N32 -> ImageAssetConfig.Argb8888
- ColorType.ALPHA_8 -> ImageAssetConfig.Alpha8
- ColorType.RGB_565 -> ImageAssetConfig.Rgb565
- ColorType.RGBA_F16 -> ImageAssetConfig.F16
- else -> ImageAssetConfig.Argb8888
+ ColorType.N32 -> ImageBitmapConfig.Argb8888
+ ColorType.ALPHA_8 -> ImageBitmapConfig.Alpha8
+ ColorType.RGB_565 -> ImageBitmapConfig.Rgb565
+ ColorType.RGBA_F16 -> ImageBitmapConfig.F16
+ else -> ImageBitmapConfig.Argb8888
}
private fun org.jetbrains.skija.ColorSpace?.toComposeColorSpace(): ColorSpace {
diff --git a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopShader.kt b/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopShader.kt
index ccd6fb3..0287398 100644
--- a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopShader.kt
+++ b/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopShader.kt
@@ -55,7 +55,7 @@
}
internal actual fun ActualImageShader(
- image: ImageAsset,
+ image: ImageBitmap,
tileModeX: TileMode,
tileModeY: TileMode
): Shader {
diff --git a/compose/ui/ui-graphics/src/desktopTest/kotlin/androidx/compose/ui/graphics/DesktopImageAssetTest.kt b/compose/ui/ui-graphics/src/desktopTest/kotlin/androidx/compose/ui/graphics/DesktopImageBitmapTest.kt
similarity index 87%
rename from compose/ui/ui-graphics/src/desktopTest/kotlin/androidx/compose/ui/graphics/DesktopImageAssetTest.kt
rename to compose/ui/ui-graphics/src/desktopTest/kotlin/androidx/compose/ui/graphics/DesktopImageBitmapTest.kt
index 830c669..30da869 100644
--- a/compose/ui/ui-graphics/src/desktopTest/kotlin/androidx/compose/ui/graphics/DesktopImageAssetTest.kt
+++ b/compose/ui/ui-graphics/src/desktopTest/kotlin/androidx/compose/ui/graphics/DesktopImageBitmapTest.kt
@@ -23,12 +23,12 @@
import org.junit.Assert.assertTrue
import org.junit.Test
-class DesktopImageAssetTest : DesktopGraphicsTest() {
+class DesktopImageBitmapTest : DesktopGraphicsTest() {
private val canvas: Canvas = initCanvas(widthPx = 16, heightPx = 16)
@Test
- fun drawOnImageAssetAlpha8() {
- val asset = ImageAsset(width = 8, height = 8, config = ImageAssetConfig.Alpha8)
+ fun drawOnImageBitmapAlpha8() {
+ val asset = ImageBitmap(width = 8, height = 8, config = ImageBitmapConfig.Alpha8)
val assetCanvas = Canvas(asset)
assetCanvas.drawImage(
imageFromResource("androidx/compose/desktop/test.png"),
@@ -41,8 +41,8 @@
}
@Test
- fun drawOnImageAssetDisplayP3() {
- val asset = ImageAsset(width = 8, height = 8, colorSpace = ColorSpaces.DisplayP3)
+ fun drawOnImageBitmapDisplayP3() {
+ val asset = ImageBitmap(width = 8, height = 8, colorSpace = ColorSpaces.DisplayP3)
val assetCanvas = Canvas(asset)
assetCanvas.drawImage(
imageFromResource("androidx/compose/desktop/test.png"),
@@ -55,8 +55,8 @@
}
@Test
- fun drawOnImageAsset() {
- val asset = ImageAsset(width = 8, height = 8)
+ fun drawOnImageBitmap() {
+ val asset = ImageBitmap(width = 8, height = 8)
val assetCanvas = Canvas(asset)
assetCanvas.drawImage(
imageFromResource("androidx/compose/desktop/test.png"),
@@ -69,7 +69,7 @@
}
@Test(expected = RuntimeException::class)
- fun `cannot draw on loaded ImageAsset`() {
+ fun `cannot draw on loaded ImageBitmap`() {
val asset = imageFromResource("androidx/compose/desktop/test.png")
Canvas(asset)
}
@@ -80,7 +80,7 @@
assertEquals(8, asset.width)
assertEquals(8, asset.height)
assertTrue(asset.hasAlpha)
- assertEquals(ImageAssetConfig.Argb8888, asset.config)
+ assertEquals(ImageBitmapConfig.Argb8888, asset.config)
assertEquals(ColorSpaces.Srgb, asset.colorSpace)
}
diff --git a/compose/ui/ui-test/api/current.txt b/compose/ui/ui-test/api/current.txt
index 2216d25..c4d58cd 100644
--- a/compose/ui/ui-test/api/current.txt
+++ b/compose/ui/ui-test/api/current.txt
@@ -20,8 +20,8 @@
method @Deprecated public static void assertShape-WOPiG5A(android.graphics.Bitmap, androidx.compose.ui.unit.Density density, float horizontalPadding, float verticalPadding, long backgroundColor, long shapeColor, optional androidx.compose.ui.graphics.Shape shape, optional float shapeOverlapPixelCount);
method @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.O) public static android.graphics.Bitmap captureToBitmap(androidx.compose.ui.test.SemanticsNodeInteraction);
method @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.O) public static android.graphics.Bitmap captureToBitmap(android.view.View);
- method @RequiresApi(android.os.Build.VERSION_CODES.O) public static androidx.compose.ui.graphics.ImageAsset captureToImage(androidx.compose.ui.test.SemanticsNodeInteraction);
- method @RequiresApi(android.os.Build.VERSION_CODES.O) public static androidx.compose.ui.graphics.ImageAsset captureToImage(android.view.View);
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) public static androidx.compose.ui.graphics.ImageBitmap captureToImage(androidx.compose.ui.test.SemanticsNodeInteraction);
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) public static androidx.compose.ui.graphics.ImageBitmap captureToImage(android.view.View);
method @Deprecated public static boolean contains-ej0GBII(androidx.compose.ui.graphics.Path, long offset);
}
@@ -33,6 +33,7 @@
public final class AnimationClocksKt {
method @androidx.compose.ui.test.ExperimentalTesting public static androidx.compose.animation.core.MonotonicFrameAnimationClock monotonicFrameAnimationClockOf(kotlin.coroutines.CoroutineContext coroutineContext, androidx.compose.runtime.dispatch.MonotonicFrameClock clock);
+ method @androidx.compose.ui.test.ExperimentalTesting public static androidx.compose.animation.core.MonotonicFrameAnimationClock monotonicFrameAnimationClockOf(kotlin.coroutines.CoroutineContext coroutineContext);
}
public final class AssertionsKt {
diff --git a/compose/ui/ui-test/api/public_plus_experimental_current.txt b/compose/ui/ui-test/api/public_plus_experimental_current.txt
index 2216d25..c4d58cd 100644
--- a/compose/ui/ui-test/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui-test/api/public_plus_experimental_current.txt
@@ -20,8 +20,8 @@
method @Deprecated public static void assertShape-WOPiG5A(android.graphics.Bitmap, androidx.compose.ui.unit.Density density, float horizontalPadding, float verticalPadding, long backgroundColor, long shapeColor, optional androidx.compose.ui.graphics.Shape shape, optional float shapeOverlapPixelCount);
method @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.O) public static android.graphics.Bitmap captureToBitmap(androidx.compose.ui.test.SemanticsNodeInteraction);
method @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.O) public static android.graphics.Bitmap captureToBitmap(android.view.View);
- method @RequiresApi(android.os.Build.VERSION_CODES.O) public static androidx.compose.ui.graphics.ImageAsset captureToImage(androidx.compose.ui.test.SemanticsNodeInteraction);
- method @RequiresApi(android.os.Build.VERSION_CODES.O) public static androidx.compose.ui.graphics.ImageAsset captureToImage(android.view.View);
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) public static androidx.compose.ui.graphics.ImageBitmap captureToImage(androidx.compose.ui.test.SemanticsNodeInteraction);
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) public static androidx.compose.ui.graphics.ImageBitmap captureToImage(android.view.View);
method @Deprecated public static boolean contains-ej0GBII(androidx.compose.ui.graphics.Path, long offset);
}
@@ -33,6 +33,7 @@
public final class AnimationClocksKt {
method @androidx.compose.ui.test.ExperimentalTesting public static androidx.compose.animation.core.MonotonicFrameAnimationClock monotonicFrameAnimationClockOf(kotlin.coroutines.CoroutineContext coroutineContext, androidx.compose.runtime.dispatch.MonotonicFrameClock clock);
+ method @androidx.compose.ui.test.ExperimentalTesting public static androidx.compose.animation.core.MonotonicFrameAnimationClock monotonicFrameAnimationClockOf(kotlin.coroutines.CoroutineContext coroutineContext);
}
public final class AssertionsKt {
diff --git a/compose/ui/ui-test/api/restricted_current.txt b/compose/ui/ui-test/api/restricted_current.txt
index 2216d25..c4d58cd 100644
--- a/compose/ui/ui-test/api/restricted_current.txt
+++ b/compose/ui/ui-test/api/restricted_current.txt
@@ -20,8 +20,8 @@
method @Deprecated public static void assertShape-WOPiG5A(android.graphics.Bitmap, androidx.compose.ui.unit.Density density, float horizontalPadding, float verticalPadding, long backgroundColor, long shapeColor, optional androidx.compose.ui.graphics.Shape shape, optional float shapeOverlapPixelCount);
method @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.O) public static android.graphics.Bitmap captureToBitmap(androidx.compose.ui.test.SemanticsNodeInteraction);
method @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.O) public static android.graphics.Bitmap captureToBitmap(android.view.View);
- method @RequiresApi(android.os.Build.VERSION_CODES.O) public static androidx.compose.ui.graphics.ImageAsset captureToImage(androidx.compose.ui.test.SemanticsNodeInteraction);
- method @RequiresApi(android.os.Build.VERSION_CODES.O) public static androidx.compose.ui.graphics.ImageAsset captureToImage(android.view.View);
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) public static androidx.compose.ui.graphics.ImageBitmap captureToImage(androidx.compose.ui.test.SemanticsNodeInteraction);
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) public static androidx.compose.ui.graphics.ImageBitmap captureToImage(android.view.View);
method @Deprecated public static boolean contains-ej0GBII(androidx.compose.ui.graphics.Path, long offset);
}
@@ -33,6 +33,7 @@
public final class AnimationClocksKt {
method @androidx.compose.ui.test.ExperimentalTesting public static androidx.compose.animation.core.MonotonicFrameAnimationClock monotonicFrameAnimationClockOf(kotlin.coroutines.CoroutineContext coroutineContext, androidx.compose.runtime.dispatch.MonotonicFrameClock clock);
+ method @androidx.compose.ui.test.ExperimentalTesting public static androidx.compose.animation.core.MonotonicFrameAnimationClock monotonicFrameAnimationClockOf(kotlin.coroutines.CoroutineContext coroutineContext);
}
public final class AssertionsKt {
diff --git a/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/AndroidBitmapHelpers.kt b/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/AndroidBitmapHelpers.kt
index 6e27460..4060bdc 100644
--- a/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/AndroidBitmapHelpers.kt
+++ b/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/AndroidBitmapHelpers.kt
@@ -25,13 +25,13 @@
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.ImageAsset
+import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.addOutline
import androidx.compose.ui.graphics.asAndroidPath
-import androidx.compose.ui.graphics.asImageAsset
+import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.node.ExperimentalLayoutNodeApi
import androidx.compose.ui.platform.AndroidOwner
import androidx.compose.ui.semantics.SemanticsProperties
@@ -55,8 +55,8 @@
*/
@Suppress("DEPRECATION")
@RequiresApi(Build.VERSION_CODES.O)
-fun SemanticsNodeInteraction.captureToImage(): ImageAsset =
- captureToBitmap().asImageAsset()
+fun SemanticsNodeInteraction.captureToImage(): ImageBitmap =
+ captureToBitmap().asImageBitmap()
/**
* Captures the underlying semantics node's surface into bitmap.
@@ -148,7 +148,7 @@
*/
@Suppress("DEPRECATION")
@RequiresApi(Build.VERSION_CODES.O)
-fun View.captureToImage(): ImageAsset = captureToBitmap().asImageAsset()
+fun View.captureToImage(): ImageBitmap = captureToBitmap().asImageBitmap()
/**
* Captures the underlying view's surface into bitmap.
diff --git a/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/CoroutineBuilders.kt b/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/CoroutineBuilders.kt
index 50d732d..4a03271 100644
--- a/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/CoroutineBuilders.kt
+++ b/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/CoroutineBuilders.kt
@@ -21,7 +21,7 @@
import androidx.compose.animation.core.advanceClockMillis
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.withContext
+import kotlinx.coroutines.yield
/**
* Runs a new coroutine and blocks the current thread interruptibly until it completes, passing a
@@ -75,15 +75,15 @@
fun <R> runBlockingWithManualClock(
compatibleWithManualAnimationClock: Boolean = false,
block: suspend CoroutineScope.(clock: ManualFrameClock) -> R
-) = runBlocking {
+) {
@Suppress("DEPRECATION")
val clock = ManualFrameClock(0L, compatibleWithManualAnimationClock)
- block(clock)
- while (clock.hasAwaiters) {
- withContext(TestUiDispatcher.Main) {
+ return runBlocking(clock) {
+ block(clock)
+ while (clock.hasAwaiters) {
clock.advanceClockMillis(10_000L)
+ // Give awaiters the chance to await again
+ yield()
}
- // Give awaiters the chance to await again
- withContext(TestUiDispatcher.Main) {}
}
-}
+}
\ No newline at end of file
diff --git a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/AnimationClocks.kt b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/AnimationClocks.kt
index e4039ed..11ea390 100644
--- a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/AnimationClocks.kt
+++ b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/AnimationClocks.kt
@@ -78,5 +78,19 @@
clock: MonotonicFrameClock
): MonotonicFrameAnimationClock =
MonotonicFrameAnimationClock(
- CoroutineScope(coroutineContext + TestUiDispatcher.Main + clock)
+ CoroutineScope(coroutineContext + clock)
+ )
+
+/**
+ * Creates a [MonotonicFrameAnimationClock] from the given [coroutineContext]'s clock. A new
+ * coroutine scope is created from the [coroutineContext].
+ *
+ * @see MonotonicFrameAnimationClock
+ */
+@ExperimentalTesting
+fun monotonicFrameAnimationClockOf(
+ coroutineContext: CoroutineContext
+): MonotonicFrameAnimationClock =
+ MonotonicFrameAnimationClock(
+ CoroutineScope(coroutineContext)
)
diff --git a/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/ParagraphIntegrationTest.kt b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/ParagraphIntegrationTest.kt
index 65cbb2c..f499d3a 100644
--- a/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/ParagraphIntegrationTest.kt
+++ b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/ParagraphIntegrationTest.kt
@@ -19,8 +19,8 @@
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.ImageAsset
-import androidx.compose.ui.graphics.ImageAssetConfig
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.ImageBitmapConfig
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.PathOperation
import androidx.compose.ui.graphics.Shadow
@@ -1874,16 +1874,16 @@
// Make sure the maxLine is applied correctly
assertThat(paragraphNoMaxLine.height).isGreaterThan(paragraphWithMaxLine.height)
- val imageNoMaxLine = ImageAsset(
+ val imageNoMaxLine = ImageBitmap(
paragraphNoMaxLine.width.roundToInt(),
paragraphNoMaxLine.height.roundToInt(),
- ImageAssetConfig.Argb8888
+ ImageBitmapConfig.Argb8888
)
// Same size with imageNoMaxLine for comparison
- val imageWithMaxLine = ImageAsset(
+ val imageWithMaxLine = ImageBitmap(
paragraphNoMaxLine.width.roundToInt(),
paragraphNoMaxLine.height.roundToInt(),
- ImageAssetConfig.Argb8888
+ ImageBitmapConfig.Argb8888
)
paragraphNoMaxLine.paint(Canvas(imageNoMaxLine))
diff --git a/compose/ui/ui-tooling/src/androidTest/AndroidManifest.xml b/compose/ui/ui-tooling/src/androidTest/AndroidManifest.xml
index 9740903..faa9b77 100644
--- a/compose/ui/ui-tooling/src/androidTest/AndroidManifest.xml
+++ b/compose/ui/ui-tooling/src/androidTest/AndroidManifest.xml
@@ -16,12 +16,12 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
- package="androidx.compose.ui.tooling.test">
+ package="androidx.compose.ui.tooling">
<application
android:debuggable="true"
tools:ignore="HardcodedDebugMode"
tools:replace="android:debuggable">
- <activity android:name="androidx.compose.ui.tooling.ComposeViewAdapterTest$Companion$TestActivity"
+ <activity android:name="androidx.compose.ui.tooling.preview.ComposeViewAdapterTest$Companion$TestActivity"
android:theme="@style/TestTheme"/>
<activity android:name="androidx.compose.ui.tooling.TestActivity"
android:theme="@style/TestTheme"/>
diff --git a/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/ComposeViewAdapterTest.kt b/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/preview/ComposeViewAdapterTest.kt
similarity index 97%
rename from compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/ComposeViewAdapterTest.kt
rename to compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/preview/ComposeViewAdapterTest.kt
index 135fc39..79d7ae1 100644
--- a/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/ComposeViewAdapterTest.kt
+++ b/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/preview/ComposeViewAdapterTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 The Android Open Source Project
+ * Copyright 2020 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.
@@ -14,15 +14,14 @@
* limitations under the License.
*/
-package androidx.compose.ui.tooling
+package androidx.compose.ui.tooling.preview
import android.app.Activity
import android.os.Bundle
import android.view.ViewTreeObserver
import androidx.compose.animation.core.InternalAnimationApi
import androidx.compose.animation.core.TransitionAnimation
-import androidx.compose.ui.tooling.preview.ComposeViewAdapter
-import androidx.compose.ui.tooling.preview.ViewInfo
+import androidx.compose.ui.tooling.compositionCount
import androidx.compose.ui.tooling.preview.animation.PreviewAnimationClock
import androidx.compose.ui.tooling.test.R
import org.junit.Assert.assertArrayEquals
diff --git a/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/PreviewActivityTest.kt b/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/preview/PreviewActivityTest.kt
similarity index 94%
rename from compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/PreviewActivityTest.kt
rename to compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/preview/PreviewActivityTest.kt
index 24768cf..fba78f7 100644
--- a/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/PreviewActivityTest.kt
+++ b/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/preview/PreviewActivityTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 The Android Open Source Project
+ * Copyright 2020 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.
@@ -14,11 +14,10 @@
* limitations under the License.
*/
-package androidx.compose.ui.tooling
+package androidx.compose.ui.tooling.preview
import android.content.Intent
import android.view.ViewGroup
-import androidx.compose.ui.tooling.preview.PreviewActivity
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Rule
diff --git a/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/preview/PreviewParameterTest.kt b/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/preview/PreviewParameterTest.kt
index 8a89c33..c74b740 100644
--- a/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/preview/PreviewParameterTest.kt
+++ b/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/preview/PreviewParameterTest.kt
@@ -19,7 +19,6 @@
import androidx.compose.material.Colors
import androidx.compose.material.darkColors
import androidx.compose.material.lightColors
-import androidx.compose.ui.tooling.ComposeViewAdapterTest
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import androidx.compose.ui.tooling.preview.datasource.LoremIpsum
import androidx.compose.ui.tooling.test.R
diff --git a/compose/ui/ui/api/current.txt b/compose/ui/ui/api/current.txt
index d485aa7..cd671d4 100644
--- a/compose/ui/ui/api/current.txt
+++ b/compose/ui/ui/api/current.txt
@@ -1773,7 +1773,7 @@
method protected final long getMeasuredSize-YbymL2g();
method protected final long getMeasurementConstraints-msEJaDk();
method public final int getWidth();
- method protected abstract void placeAt--gyyYBs(long position);
+ method protected abstract void placeAt-iGa6aRc(long position, float zIndex);
method protected final void setMeasuredSize-ozmzZPI(long value);
method protected final void setMeasurementConstraints-BRTryo0(long p);
property protected final long apparentToRealOffset;
@@ -1787,12 +1787,12 @@
ctor public Placeable.PlacementScope();
method protected abstract androidx.compose.ui.unit.LayoutDirection getParentLayoutDirection();
method protected abstract int getParentWidth();
- method public final void place(androidx.compose.ui.layout.Placeable, int x, int y);
- method public final void place-I1SjWws(androidx.compose.ui.layout.Placeable, long position);
- method public final void place-KmjayG8(androidx.compose.ui.layout.Placeable, long position);
- method public final void placeRelative(androidx.compose.ui.layout.Placeable, int x, int y);
- method public final void placeRelative-I1SjWws(androidx.compose.ui.layout.Placeable, long position);
- method public final void placeRelative-KmjayG8(androidx.compose.ui.layout.Placeable, long position);
+ method public final void place(androidx.compose.ui.layout.Placeable, int x, int y, optional float zIndex);
+ method @Deprecated public final void place-I1SjWws(androidx.compose.ui.layout.Placeable, long position);
+ method public final void place-z2mxYZE(androidx.compose.ui.layout.Placeable, long position, optional float zIndex);
+ method public final void placeRelative(androidx.compose.ui.layout.Placeable, int x, int y, optional float zIndex);
+ method @Deprecated public final void placeRelative-I1SjWws(androidx.compose.ui.layout.Placeable, long position);
+ method public final void placeRelative-z2mxYZE(androidx.compose.ui.layout.Placeable, long position, optional float zIndex);
property protected abstract androidx.compose.ui.unit.LayoutDirection parentLayoutDirection;
property protected abstract int parentWidth;
}
@@ -2308,8 +2308,8 @@
}
public final class ImageResourcesKt {
- method @androidx.compose.runtime.Composable public static androidx.compose.ui.graphics.ImageAsset imageResource(@DrawableRes int id);
- method @androidx.compose.runtime.Composable public static androidx.compose.ui.res.DeferredResource<androidx.compose.ui.graphics.ImageAsset> loadImageResource(int id, optional androidx.compose.ui.graphics.ImageAsset? pendingImage, optional androidx.compose.ui.graphics.ImageAsset? failedImage);
+ method @androidx.compose.runtime.Composable public static androidx.compose.ui.graphics.ImageBitmap imageResource(@DrawableRes int id);
+ method @androidx.compose.runtime.Composable public static androidx.compose.ui.res.DeferredResource<androidx.compose.ui.graphics.ImageBitmap> loadImageResource(int id, optional androidx.compose.ui.graphics.ImageBitmap? pendingImage, optional androidx.compose.ui.graphics.ImageBitmap? failedImage);
}
public final class LoadedResource<T> extends androidx.compose.ui.res.Resource<T> {
diff --git a/compose/ui/ui/api/public_plus_experimental_current.txt b/compose/ui/ui/api/public_plus_experimental_current.txt
index d485aa7..cd671d4 100644
--- a/compose/ui/ui/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui/api/public_plus_experimental_current.txt
@@ -1773,7 +1773,7 @@
method protected final long getMeasuredSize-YbymL2g();
method protected final long getMeasurementConstraints-msEJaDk();
method public final int getWidth();
- method protected abstract void placeAt--gyyYBs(long position);
+ method protected abstract void placeAt-iGa6aRc(long position, float zIndex);
method protected final void setMeasuredSize-ozmzZPI(long value);
method protected final void setMeasurementConstraints-BRTryo0(long p);
property protected final long apparentToRealOffset;
@@ -1787,12 +1787,12 @@
ctor public Placeable.PlacementScope();
method protected abstract androidx.compose.ui.unit.LayoutDirection getParentLayoutDirection();
method protected abstract int getParentWidth();
- method public final void place(androidx.compose.ui.layout.Placeable, int x, int y);
- method public final void place-I1SjWws(androidx.compose.ui.layout.Placeable, long position);
- method public final void place-KmjayG8(androidx.compose.ui.layout.Placeable, long position);
- method public final void placeRelative(androidx.compose.ui.layout.Placeable, int x, int y);
- method public final void placeRelative-I1SjWws(androidx.compose.ui.layout.Placeable, long position);
- method public final void placeRelative-KmjayG8(androidx.compose.ui.layout.Placeable, long position);
+ method public final void place(androidx.compose.ui.layout.Placeable, int x, int y, optional float zIndex);
+ method @Deprecated public final void place-I1SjWws(androidx.compose.ui.layout.Placeable, long position);
+ method public final void place-z2mxYZE(androidx.compose.ui.layout.Placeable, long position, optional float zIndex);
+ method public final void placeRelative(androidx.compose.ui.layout.Placeable, int x, int y, optional float zIndex);
+ method @Deprecated public final void placeRelative-I1SjWws(androidx.compose.ui.layout.Placeable, long position);
+ method public final void placeRelative-z2mxYZE(androidx.compose.ui.layout.Placeable, long position, optional float zIndex);
property protected abstract androidx.compose.ui.unit.LayoutDirection parentLayoutDirection;
property protected abstract int parentWidth;
}
@@ -2308,8 +2308,8 @@
}
public final class ImageResourcesKt {
- method @androidx.compose.runtime.Composable public static androidx.compose.ui.graphics.ImageAsset imageResource(@DrawableRes int id);
- method @androidx.compose.runtime.Composable public static androidx.compose.ui.res.DeferredResource<androidx.compose.ui.graphics.ImageAsset> loadImageResource(int id, optional androidx.compose.ui.graphics.ImageAsset? pendingImage, optional androidx.compose.ui.graphics.ImageAsset? failedImage);
+ method @androidx.compose.runtime.Composable public static androidx.compose.ui.graphics.ImageBitmap imageResource(@DrawableRes int id);
+ method @androidx.compose.runtime.Composable public static androidx.compose.ui.res.DeferredResource<androidx.compose.ui.graphics.ImageBitmap> loadImageResource(int id, optional androidx.compose.ui.graphics.ImageBitmap? pendingImage, optional androidx.compose.ui.graphics.ImageBitmap? failedImage);
}
public final class LoadedResource<T> extends androidx.compose.ui.res.Resource<T> {
diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt
index b87c61e..ecb8dd6 100644
--- a/compose/ui/ui/api/restricted_current.txt
+++ b/compose/ui/ui/api/restricted_current.txt
@@ -1820,7 +1820,7 @@
method protected final long getMeasuredSize-YbymL2g();
method protected final long getMeasurementConstraints-msEJaDk();
method public final int getWidth();
- method protected abstract void placeAt--gyyYBs(long position);
+ method protected abstract void placeAt-iGa6aRc(long position, float zIndex);
method protected final void setMeasuredSize-ozmzZPI(long value);
method protected final void setMeasurementConstraints-BRTryo0(long p);
property protected final long apparentToRealOffset;
@@ -1834,12 +1834,12 @@
ctor public Placeable.PlacementScope();
method protected abstract androidx.compose.ui.unit.LayoutDirection getParentLayoutDirection();
method protected abstract int getParentWidth();
- method public final void place(androidx.compose.ui.layout.Placeable, int x, int y);
- method public final void place-I1SjWws(androidx.compose.ui.layout.Placeable, long position);
- method public final void place-KmjayG8(androidx.compose.ui.layout.Placeable, long position);
- method public final void placeRelative(androidx.compose.ui.layout.Placeable, int x, int y);
- method public final void placeRelative-I1SjWws(androidx.compose.ui.layout.Placeable, long position);
- method public final void placeRelative-KmjayG8(androidx.compose.ui.layout.Placeable, long position);
+ method public final void place(androidx.compose.ui.layout.Placeable, int x, int y, optional float zIndex);
+ method @Deprecated public final void place-I1SjWws(androidx.compose.ui.layout.Placeable, long position);
+ method public final void place-z2mxYZE(androidx.compose.ui.layout.Placeable, long position, optional float zIndex);
+ method public final void placeRelative(androidx.compose.ui.layout.Placeable, int x, int y, optional float zIndex);
+ method @Deprecated public final void placeRelative-I1SjWws(androidx.compose.ui.layout.Placeable, long position);
+ method public final void placeRelative-z2mxYZE(androidx.compose.ui.layout.Placeable, long position, optional float zIndex);
property protected abstract androidx.compose.ui.unit.LayoutDirection parentLayoutDirection;
property protected abstract int parentWidth;
}
@@ -2385,8 +2385,8 @@
}
public final class ImageResourcesKt {
- method @androidx.compose.runtime.Composable public static androidx.compose.ui.graphics.ImageAsset imageResource(@DrawableRes int id);
- method @androidx.compose.runtime.Composable public static androidx.compose.ui.res.DeferredResource<androidx.compose.ui.graphics.ImageAsset> loadImageResource(int id, optional androidx.compose.ui.graphics.ImageAsset? pendingImage, optional androidx.compose.ui.graphics.ImageAsset? failedImage);
+ method @androidx.compose.runtime.Composable public static androidx.compose.ui.graphics.ImageBitmap imageResource(@DrawableRes int id);
+ method @androidx.compose.runtime.Composable public static androidx.compose.ui.res.DeferredResource<androidx.compose.ui.graphics.ImageBitmap> loadImageResource(int id, optional androidx.compose.ui.graphics.ImageBitmap? pendingImage, optional androidx.compose.ui.graphics.ImageBitmap? failedImage);
}
public final class LoadedResource<T> extends androidx.compose.ui.res.Resource<T> {
diff --git a/compose/ui/ui/integration-tests/ui-demos/lint-baseline.xml b/compose/ui/ui/integration-tests/ui-demos/lint-baseline.xml
index a3129e5..aedc407 100644
--- a/compose/ui/ui/integration-tests/ui-demos/lint-baseline.xml
+++ b/compose/ui/ui/integration-tests/ui-demos/lint-baseline.xml
@@ -24,17 +24,6 @@
</issue>
<issue
- id="ModifierInspectorInfo"
- message="Modifier missing inspectorInfo"
- errorLine1=" composed {"
- errorLine2=" ~~~~~~~~">
- <location
- file="src/main/java/androidx/compose/ui/demos/gestures/PointerInputDuringSubCompDemo.kt"
- line="92"
- column="5"/>
- </issue>
-
- <issue
id="SetTextI18n"
message="String literal in `setText` can not be translated. Use Android resources instead."
errorLine1=" text1.text = "Text updated""
diff --git a/compose/ui/ui/lint-baseline.xml b/compose/ui/ui/lint-baseline.xml
index aad399c..2595fa3 100644
--- a/compose/ui/ui/lint-baseline.xml
+++ b/compose/ui/ui/lint-baseline.xml
@@ -686,83 +686,6 @@
<issue
id="ModifierInspectorInfo"
message="Modifier missing inspectorInfo"
- errorLine1=") = if (elevation > 0.dp || clip) {"
- errorLine2=" ^">
- <location
- file="src/commonMain/kotlin/androidx/compose/ui/draw/DrawShadow.kt"
- line="50"
- column="5"/>
- </issue>
-
- <issue
- id="ModifierInspectorInfo"
- message="Modifier missing inspectorInfo"
- errorLine1=" return this.then(FocusObserverModifierImpl(onFocusChange))"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/commonMain/kotlin/androidx/compose/ui/FocusObserverModifier.kt"
- line="43"
- column="22"/>
- </issue>
-
- <issue
- id="ModifierInspectorInfo"
- message="Modifier missing inspectorInfo"
- errorLine1=" return this.then(FocusRequesterModifierImpl(focusRequester))"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/commonMain/kotlin/androidx/compose/ui/FocusRequesterModifier.kt"
- line="46"
- column="22"/>
- </issue>
-
- <issue
- id="ModifierInspectorInfo"
- message="Modifier missing inspectorInfo"
- errorLine1="fun Modifier.keyInputFilter(onKeyEvent: (KeyEvent) -> Boolean): Modifier = composed {"
- errorLine2=" ~~~~~~~~">
- <location
- file="src/commonMain/kotlin/androidx/compose/ui/input/key/KeyInputModifier.kt"
- line="32"
- column="76"/>
- </issue>
-
- <issue
- id="ModifierInspectorInfo"
- message="Modifier missing inspectorInfo"
- errorLine1="fun Modifier.previewKeyInputFilter(onPreviewKeyEvent: (KeyEvent) -> Boolean): Modifier = composed {"
- errorLine2=" ~~~~~~~~">
- <location
- file="src/commonMain/kotlin/androidx/compose/ui/input/key/KeyInputModifier.kt"
- line="47"
- column="90"/>
- </issue>
-
- <issue
- id="ModifierInspectorInfo"
- message="Modifier missing inspectorInfo"
- errorLine1="): Modifier = composed {"
- errorLine2=" ~~~~~~~~">
- <location
- file="src/commonMain/kotlin/androidx/compose/ui/gesture/LongPressDragGestureFilter.kt"
- line="121"
- column="15"/>
- </issue>
-
- <issue
- id="ModifierInspectorInfo"
- message="Modifier missing inspectorInfo"
- errorLine1="): Modifier = composed {"
- errorLine2=" ~~~~~~~~">
- <location
- file="src/commonMain/kotlin/androidx/compose/ui/gesture/LongPressGestureFilter.kt"
- line="58"
- column="15"/>
- </issue>
-
- <issue
- id="ModifierInspectorInfo"
- message="Modifier missing inspectorInfo"
errorLine1=") = this.then(object : OnGloballyPositionedModifier {"
errorLine2=" ^">
<location
@@ -788,94 +711,6 @@
errorLine1="): Modifier = composed {"
errorLine2=" ~~~~~~~~">
<location
- file="src/androidMain/kotlin/androidx/compose/ui/input/pointer/PointerInteropFilter.kt"
- line="62"
- column="15"/>
- </issue>
-
- <issue
- id="ModifierInspectorInfo"
- message="Modifier missing inspectorInfo"
- errorLine1="): Modifier = composed {"
- errorLine2=" ~~~~~~~~">
- <location
- file="src/commonMain/kotlin/androidx/compose/ui/gesture/PressIndicatorGestureFilter.kt"
- line="57"
- column="15"/>
- </issue>
-
- <issue
- id="ModifierInspectorInfo"
- message="Modifier missing inspectorInfo"
- errorLine1="): Modifier = composed {"
- errorLine2=" ~~~~~~~~">
- <location
- file="src/commonMain/kotlin/androidx/compose/ui/gesture/RawDragGestureFilter.kt"
- line="153"
- column="15"/>
- </issue>
-
- <issue
- id="ModifierInspectorInfo"
- message="Modifier missing inspectorInfo"
- errorLine1="): Modifier = composed {"
- errorLine2=" ~~~~~~~~">
- <location
- file="src/commonMain/kotlin/androidx/compose/ui/gesture/RawPressStartGestureFilter.kt"
- line="57"
- column="15"/>
- </issue>
-
- <issue
- id="ModifierInspectorInfo"
- message="Modifier missing inspectorInfo"
- errorLine1="): Modifier = composed {"
- errorLine2=" ~~~~~~~~">
- <location
- file="src/commonMain/kotlin/androidx/compose/ui/gesture/RawScaleGestureFilter.kt"
- line="122"
- column="15"/>
- </issue>
-
- <issue
- id="ModifierInspectorInfo"
- message="Modifier missing inspectorInfo"
- errorLine1="): Modifier = composed {"
- errorLine2=" ~~~~~~~~">
- <location
- file="src/commonMain/kotlin/androidx/compose/ui/gesture/ScaleGestureFilter.kt"
- line="106"
- column="15"/>
- </issue>
-
- <issue
- id="ModifierInspectorInfo"
- message="Modifier missing inspectorInfo"
- errorLine1="): Modifier = composed {"
- errorLine2=" ~~~~~~~~">
- <location
- file="src/commonMain/kotlin/androidx/compose/ui/gesture/ScaleSlopExceededGestureFilter.kt"
- line="51"
- column="15"/>
- </issue>
-
- <issue
- id="ModifierInspectorInfo"
- message="Modifier missing inspectorInfo"
- errorLine1="): Modifier = composed {"
- errorLine2=" ~~~~~~~~">
- <location
- file="src/commonMain/kotlin/androidx/compose/ui/gesture/ScrollGestureFilter.kt"
- line="120"
- column="15"/>
- </issue>
-
- <issue
- id="ModifierInspectorInfo"
- message="Modifier missing inspectorInfo"
- errorLine1="): Modifier = composed {"
- errorLine2=" ~~~~~~~~">
- <location
file="src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsModifier.kt"
line="85"
column="15"/>
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt
index e3e2fb1..f02a9de 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt
@@ -54,7 +54,7 @@
import androidx.compose.ui.graphics.Outline
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.Shape
-import androidx.compose.ui.graphics.asImageAsset
+import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.drawscope.clipRect
import androidx.compose.ui.graphics.drawscope.translate
@@ -91,12 +91,14 @@
import androidx.compose.ui.test.TestActivity
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.constrainHeight
import androidx.compose.ui.unit.constrainWidth
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.offset
+import androidx.compose.ui.unit.toOffset
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import androidx.test.filters.SdkSuppress
@@ -2200,7 +2202,7 @@
@Test
fun layoutModifier_convenienceApi() {
val size = 100
- val offset = 15f
+ val offset = 15
val latch = CountDownLatch(1)
var resultCoordinates: LayoutCoordinates? = null
@@ -2212,7 +2214,7 @@
.layout { measurable, constraints ->
val placeable = measurable.measure(constraints)
layout(placeable.width, placeable.height) {
- placeable.place(Offset(offset, offset))
+ placeable.place(offset, offset)
}
}.onGloballyPositioned {
resultCoordinates = it
@@ -2227,14 +2229,14 @@
activity.runOnUiThread {
assertEquals(size, resultCoordinates?.size?.height)
assertEquals(size, resultCoordinates?.size?.width)
- assertEquals(Offset(offset, offset), resultCoordinates?.positionInRoot)
+ assertEquals(IntOffset(offset, offset).toOffset(), resultCoordinates?.positionInRoot)
}
}
@Test
fun layoutModifier_convenienceApi_equivalent() {
val size = 100
- val offset = 15f
+ val offset = 15
val latch = CountDownLatch(2)
var convenienceCoordinates: LayoutCoordinates? = null
@@ -2248,7 +2250,7 @@
.layout { measurable, constraints ->
val placeable = measurable.measure(constraints)
layout(placeable.width, placeable.height) {
- placeable.place(Offset(offset, offset))
+ placeable.place(offset, offset)
}
}.onGloballyPositioned {
convenienceCoordinates = it
@@ -2263,7 +2265,7 @@
): MeasureResult {
val placeable = measurable.measure(constraints)
return layout(placeable.width, placeable.height) {
- placeable.place(Offset(offset, offset))
+ placeable.place(offset, offset)
}
}
}
@@ -3180,7 +3182,7 @@
)
}
- activityTestRule.waitAndScreenShot(frameLayout).asImageAsset()
+ activityTestRule.waitAndScreenShot(frameLayout).asImageBitmap()
.assertPixels(expectedSize = IntSize(size, size)) {
Color.Red
}
@@ -3375,7 +3377,7 @@
offset: Int = 0,
totalSize: Int = size * 3
) {
- assertTrue("drawLatch timed out", drawLatch.await(10000, TimeUnit.SECONDS))
+ assertTrue("drawLatch timed out", drawLatch.await(1, TimeUnit.SECONDS))
val bitmap = waitAndScreenShot()
assertEquals(totalSize, bitmap.width)
assertEquals(totalSize, bitmap.height)
@@ -3694,6 +3696,8 @@
fun PaddingModifier(padding: Int) = PaddingModifier(padding, padding, padding, padding)
+fun Modifier.padding(padding: Int) = this.then(PaddingModifier(padding))
+
data class PaddingModifier(
val left: Int = 0,
val top: Int = 0,
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/DrawReorderingTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/DrawReorderingTest.kt
index 44c7ffb..a8034fc 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/DrawReorderingTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/DrawReorderingTest.kt
@@ -23,18 +23,20 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.emptyContent
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
import androidx.compose.ui.AtLeastSize
import androidx.compose.ui.DrawLayerModifier
import androidx.compose.ui.FixedSize
import androidx.compose.ui.Modifier
-import androidx.compose.ui.PaddingModifier
-import androidx.compose.ui.ZIndexModifier
import androidx.compose.ui.background
import androidx.compose.ui.drawBehind
import androidx.compose.ui.drawLayer
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.layout
+import androidx.compose.ui.padding
import androidx.compose.ui.platform.AndroidOwnerExtraAssertionsRule
import androidx.compose.ui.platform.setContent
import androidx.compose.ui.test.TestActivity
@@ -44,7 +46,6 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import androidx.test.filters.SdkSuppress
-import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Before
@@ -84,7 +85,7 @@
children = {
FixedSize(
10,
- PaddingModifier(10)
+ Modifier.padding(10)
.background(Color.White)
)
FixedSize(
@@ -124,7 +125,7 @@
children = {
FixedSize(
10,
- PaddingModifier(10)
+ Modifier.padding(10)
.background(Color.White)
)
FixedSize(
@@ -157,14 +158,14 @@
@Test
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
- fun testDrawingOrderIsOverriddenWithZIndexWhenWePlaceItemsInTheReverseOrder() {
+ fun testDrawingOrderIsOverriddenWithZIndexModifierWhenWePlaceItemsInTheReverseOrder() {
rule.runOnUiThread {
activity.setContent {
Layout(
children = {
FixedSize(
10,
- PaddingModifier(10)
+ Modifier.padding(10)
.background(Color.White)
)
FixedSize(
@@ -198,6 +199,47 @@
@Test
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ fun testDrawingOrderIsOverriddenWithZIndexWhenWePlaceItemsInTheReverseOrder() {
+ rule.runOnUiThread {
+ activity.setContent {
+ Layout(
+ children = {
+ FixedSize(
+ 10,
+ Modifier.padding(10)
+ .background(Color.White)
+ )
+ FixedSize(
+ 30,
+ Modifier.drawLayer()
+ .background(Color.Red)
+ .zIndex(1f)
+ .drawLatchModifier()
+ )
+ }
+ ) { measurables, _ ->
+ val newConstraints = Constraints.fixed(30, 30)
+ val placeables = measurables.map { m ->
+ m.measure(newConstraints)
+ }
+ layout(newConstraints.maxWidth, newConstraints.maxWidth) {
+ placeables.reversed().forEach { child ->
+ child.place(0, 0, zIndex = placeables.indexOf(child).toFloat())
+ }
+ }
+ }
+ }
+ }
+ rule.validateSquareColors(
+ outerColor = Color.Red,
+ innerColor = Color.Red,
+ size = 10,
+ drawLatch = drawLatch
+ )
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
fun testCustomDrawingOrderForThreeItems() {
rule.runOnUiThread {
activity.setContent {
@@ -211,7 +253,7 @@
)
FixedSize(
10,
- PaddingModifier(10)
+ Modifier.padding(10)
.background(Color.White)
)
FixedSize(
@@ -281,7 +323,7 @@
) {
FixedSize(
10,
- PaddingModifier(10)
+ Modifier.padding(10)
.zIndex(1f)
.background(Color.White)
)
@@ -312,7 +354,7 @@
) {
FixedSize(
10,
- PaddingModifier(10)
+ Modifier.padding(10)
.zIndex(1f)
.background(Color.White)
)
@@ -340,7 +382,7 @@
FixedSize(
size = 30
) {
- FixedSize(10, PaddingModifier(10)) {
+ FixedSize(10, Modifier.padding(10)) {
FixedSize(
10,
Modifier.zIndex(1f)
@@ -348,7 +390,7 @@
)
}
FixedSize(30, Modifier.background(Color.Red))
- FixedSize(10, PaddingModifier(10)) {
+ FixedSize(10, Modifier.padding(10)) {
FixedSize(
10,
Modifier.background(Color.White)
@@ -374,7 +416,7 @@
FixedSize(
size = 30
) {
- FixedSize(10, PaddingModifier(10)) {
+ FixedSize(10, Modifier.padding(10)) {
FixedSize(
10,
Modifier.zIndex(1f)
@@ -409,7 +451,7 @@
) {
FixedSize(
10,
- PaddingModifier(10)
+ Modifier.padding(10)
.zIndex(state.value)
.background(Color.Black)
)
@@ -420,7 +462,7 @@
)
FixedSize(
10,
- PaddingModifier(10).background(Color.White)
+ Modifier.padding(10).background(Color.White)
)
}
}
@@ -469,11 +511,13 @@
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
fun testChangingZOrderReusingModifiers() {
val state = mutableStateOf(0f)
- val zIndex = object : ZIndexModifier {
- override val zIndex: Float
- get() = state.value
+ val zIndex = Modifier.layout { measurable, constraints ->
+ val placeable = measurable.measure(constraints)
+ layout(placeable.width, placeable.height) {
+ placeable.place(0, 0, zIndex = state.value)
+ }
}
- val modifier1 = PaddingModifier(10)
+ val modifier1 = Modifier.padding(10)
.then(zIndex)
.background(Color.White)
val modifier2 = Modifier.background(Color.Red)
@@ -545,7 +589,7 @@
FixedSize(30) {
FixedSize(
10,
- PaddingModifier(10).then(elevation).background(Color.Black)
+ Modifier.padding(10).then(elevation).background(Color.Black)
)
}
FixedSize(
@@ -555,7 +599,7 @@
)
FixedSize(
10,
- PaddingModifier(10)
+ Modifier.padding(10)
.background(Color.White)
)
}
@@ -601,7 +645,7 @@
) {
FixedSize(
size,
- PaddingModifier(10)
+ Modifier.padding(10)
.zIndex(1f)
.background(Color.White)
)
@@ -650,7 +694,7 @@
FixedSize(size = 30) {
FixedSize(
10,
- PaddingModifier(10)
+ Modifier.padding(10)
.zIndex(1f)
.background(Color.White)
)
@@ -690,7 +734,7 @@
) {
FixedSize(
10,
- PaddingModifier(10)
+ Modifier.padding(10)
.zIndex(2f)
.zIndex(2f)
.background(Color.White)
@@ -714,77 +758,6 @@
}
@Test
- fun elevationWithinModifier() {
- val elevation = mutableStateOf(0f)
- val color = mutableStateOf(Color.Blue)
- val underColor = mutableStateOf(Color.Transparent)
- val modifier = Modifier.drawLayer()
- .background(underColor)
- .drawLatchModifier()
- .then(object : DrawLayerModifier {
- override val shadowElevation: Float
- get() {
- return elevation.value
- }
- })
- .background(color)
-
- rule.runOnUiThread {
- activity.setContent {
- FixedSize(30, modifier)
- }
- }
-
- assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
-
- drawLatch = CountDownLatch(1)
-
- rule.runOnUiThread {
- color.value = Color.Red
- }
-
- assertFalse(drawLatch.await(200, TimeUnit.MILLISECONDS))
-
- drawLatch = CountDownLatch(1)
- rule.runOnUiThread {
- elevation.value = 1f
- }
-
- assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
-
- drawLatch = CountDownLatch(1)
-
- rule.runOnUiThread {
- elevation.value = 2f // elevation was already 1, so it doesn't need to enableZ again
- }
- assertFalse(drawLatch.await(200, TimeUnit.MILLISECONDS))
-
- rule.runOnUiThread {
- elevation.value = 0f // going to 0 doesn't trigger invalidation
- }
- assertFalse(drawLatch.await(200, TimeUnit.MILLISECONDS))
-
- rule.runOnUiThread {
- elevation.value = 1f // going to 1 won't invalidate because it was last drawn with Z
- }
- assertFalse(drawLatch.await(200, TimeUnit.MILLISECONDS))
-
- rule.runOnUiThread {
- elevation.value = 0f
- underColor.value = Color.Black
- }
-
- assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
-
- drawLatch = CountDownLatch(1)
-
- rule.runOnUiThread {
- elevation.value = 1f
- }
- assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
- }
-
- @Test
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
fun testInvalidateParentOfReorderedChild() {
val color = mutableStateOf(Color.Red)
@@ -793,7 +766,7 @@
FixedSize(size = 30) {
FixedSize(
10,
- PaddingModifier(10)
+ Modifier.padding(10)
.zIndex(1f)
.background(Color.White)
)
@@ -829,7 +802,7 @@
) {
FixedSize(
10,
- PaddingModifier(10)
+ Modifier.padding(10)
.drawLayer(shadowElevation = 1f)
.background(Color.White)
)
@@ -850,15 +823,226 @@
)
}
- fun Modifier.drawLatchModifier() = drawBehind { drawLatch.countDown() }
-}
-
-private fun Modifier.background(
- color: State<Color>
-) = drawBehind {
- if (color.value != Color.Transparent) {
- drawRect(color.value)
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ fun placeOrderIsUsedWhenParentProvidedSameZIndex() {
+ rule.runOnUiThread {
+ activity.setContent {
+ Layout(
+ children = {
+ FixedSize(30) {
+ FixedSize(
+ 10,
+ Modifier.padding(10)
+ .background(Color.White)
+ )
+ }
+ FixedSize(30) {
+ FixedSize(
+ 30,
+ Modifier.background(Color.Red)
+ .drawLatchModifier()
+ )
+ }
+ }
+ ) { measurables, _ ->
+ val newConstraints = Constraints.fixed(30, 30)
+ val placeables = measurables.map { m ->
+ m.measure(newConstraints)
+ }
+ layout(newConstraints.maxWidth, newConstraints.maxWidth) {
+ placeables[0].place(0, 0, zIndex = 1f)
+ placeables[1].place(0, 0, zIndex = 1f)
+ }
+ }
+ }
+ }
+ rule.validateSquareColors(
+ outerColor = Color.Red,
+ innerColor = Color.Red,
+ size = 10,
+ drawLatch = drawLatch
+ )
}
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ fun placeOrderIsUsedWhenParentProvidedSameZIndex_reversePlaceOrder() {
+ rule.runOnUiThread {
+ activity.setContent {
+ Layout(
+ children = {
+ FixedSize(30) {
+ FixedSize(
+ 10,
+ Modifier.padding(10)
+ .background(Color.White)
+ )
+ }
+ FixedSize(30) {
+ FixedSize(
+ 30,
+ Modifier.background(Color.Red)
+ .drawLatchModifier()
+ )
+ }
+ }
+ ) { measurables, _ ->
+ val newConstraints = Constraints.fixed(30, 30)
+ val placeables = measurables.map { m ->
+ m.measure(newConstraints)
+ }
+ layout(newConstraints.maxWidth, newConstraints.maxWidth) {
+ placeables[1].place(0, 0, zIndex = 1f)
+ placeables[0].place(0, 0, zIndex = 1f)
+ }
+ }
+ }
+ }
+ rule.validateSquareColors(
+ outerColor = Color.Red,
+ innerColor = Color.White,
+ size = 10,
+ drawLatch = drawLatch
+ )
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ fun parentProvidedZIndexSummedWithTheOneFromModifier() {
+ rule.runOnUiThread {
+ activity.setContent {
+ Layout(
+ children = {
+ FixedSize(30, Modifier.zIndex(2f)) {
+ FixedSize(
+ 10,
+ Modifier.padding(10)
+ .background(Color.White)
+ )
+ }
+ FixedSize(30) {
+ FixedSize(
+ 30,
+ Modifier.background(Color.Red)
+ .drawLatchModifier()
+ )
+ }
+ }
+ ) { measurables, _ ->
+ val newConstraints = Constraints.fixed(30, 30)
+ val placeables = measurables.map { m ->
+ m.measure(newConstraints)
+ }
+ layout(newConstraints.maxWidth, newConstraints.maxWidth) {
+ placeables[0].place(0, 0, zIndex = 1f)
+ placeables[1].place(0, 0, zIndex = 2f)
+ }
+ }
+ }
+ }
+ rule.validateSquareColors(
+ outerColor = Color.Red,
+ innerColor = Color.White,
+ size = 10,
+ drawLatch = drawLatch
+ )
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ fun placeRelativePassesZIndex() {
+ rule.runOnUiThread {
+ activity.setContent {
+ Layout(
+ children = {
+ FixedSize(30) {
+ FixedSize(
+ 10,
+ Modifier.padding(10)
+ .background(Color.White)
+ )
+ }
+ FixedSize(30) {
+ FixedSize(
+ 30,
+ Modifier.background(Color.Red)
+ .drawLatchModifier()
+ )
+ }
+ }
+ ) { measurables, _ ->
+ val newConstraints = Constraints.fixed(30, 30)
+ val placeables = measurables.map { m ->
+ m.measure(newConstraints)
+ }
+ layout(newConstraints.maxWidth, newConstraints.maxWidth) {
+ placeables[0].placeRelative(0, 0, zIndex = 1f)
+ placeables[1].placeRelative(0, 0, zIndex = -1f)
+ }
+ }
+ }
+ }
+ rule.validateSquareColors(
+ outerColor = Color.Red,
+ innerColor = Color.White,
+ size = 10,
+ drawLatch = drawLatch
+ )
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ fun whenSecondChildAddedLaterDrawingOrderIsStillCorrect() {
+ var needSecondChild by mutableStateOf(false)
+ rule.runOnUiThread {
+ activity.setContent {
+ Layout(
+ children = {
+ FixedSize(30) {
+ FixedSize(
+ 10,
+ Modifier.padding(10)
+ .background(Color.White)
+ .drawLatchModifier()
+ )
+ }
+ if (needSecondChild) {
+ FixedSize(30) {
+ FixedSize(
+ 30,
+ Modifier.background(Color.Red)
+ )
+ }
+ }
+ }
+ ) { measurables, _ ->
+ val newConstraints = Constraints.fixed(30, 30)
+ val placeables = measurables.map { m ->
+ m.measure(newConstraints)
+ }
+ layout(newConstraints.maxWidth, newConstraints.maxWidth) {
+ placeables[0].placeRelative(0, 0, zIndex = 1f)
+ placeables.getOrNull(1)?.placeRelative(0, 0)
+ }
+ }
+ }
+ }
+ assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
+ rule.runOnUiThread {
+ drawLatch = CountDownLatch(1)
+ needSecondChild = true
+ }
+
+ rule.validateSquareColors(
+ outerColor = Color.Red,
+ innerColor = Color.White,
+ size = 10,
+ drawLatch = drawLatch
+ )
+ }
+
+ fun Modifier.drawLatchModifier() = drawBehind { drawLatch.countDown() }
}
@Composable
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/DrawShadowTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/DrawShadowTest.kt
index 0bc2cec..66ec42a 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/DrawShadowTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/DrawShadowTest.kt
@@ -22,6 +22,7 @@
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.AtLeastSize
+import androidx.compose.ui.DrawLayerModifier
import androidx.compose.ui.Modifier
import androidx.compose.ui.drawBehind
import androidx.compose.ui.drawLayer
@@ -29,9 +30,13 @@
import androidx.compose.ui.geometry.toRect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Outline
+import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.luminance
import androidx.compose.ui.platform.DensityAmbient
+import androidx.compose.ui.platform.InspectableValue
+import androidx.compose.ui.platform.ValueElement
+import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
import androidx.compose.ui.platform.setContent
import androidx.compose.ui.runOnUiThreadIR
import androidx.compose.ui.test.TestActivity
@@ -42,6 +47,9 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import androidx.test.filters.SdkSuppress
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Assert
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertTrue
@@ -72,6 +80,12 @@
activity = rule.activity
activity.hasFocusLatch.await(5, TimeUnit.SECONDS)
drawLatch = CountDownLatch(1)
+ isDebugInspectorInfoEnabled = true
+ }
+
+ @After
+ fun tearDown() {
+ isDebugInspectorInfoEnabled = false
}
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
@@ -207,6 +221,91 @@
}
}
+ @Test
+ fun testInspectorValue() {
+ rule.runOnUiThreadIR {
+ val modifier = Modifier.drawShadow(4.0.dp) as InspectableValue
+ assertThat(modifier.nameFallback).isEqualTo("drawShadow")
+ assertThat(modifier.valueOverride).isNull()
+ assertThat(modifier.inspectableElements.asIterable()).containsExactly(
+ ValueElement("elevation", 4.0.dp),
+ ValueElement("shape", RectangleShape),
+ ValueElement("clip", true)
+ )
+ }
+ }
+
+ @Test
+ fun elevationWithinModifier() {
+ val elevation = mutableStateOf(0f)
+ val color = mutableStateOf(Color.Blue)
+ val underColor = mutableStateOf(Color.Transparent)
+ val modifier = Modifier.drawLayer()
+ .background(underColor)
+ .drawLatchModifier()
+ .then(object : DrawLayerModifier {
+ override val shadowElevation: Float
+ get() {
+ return elevation.value
+ }
+ })
+ .background(color)
+
+ rule.runOnUiThread {
+ activity.setContent {
+ androidx.compose.ui.FixedSize(30, modifier)
+ }
+ }
+
+ assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
+
+ drawLatch = CountDownLatch(1)
+
+ rule.runOnUiThread {
+ color.value = Color.Red
+ }
+
+ Assert.assertFalse(drawLatch.await(200, TimeUnit.MILLISECONDS))
+
+ drawLatch = CountDownLatch(1)
+ rule.runOnUiThread {
+ elevation.value = 1f
+ }
+
+ assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
+
+ drawLatch = CountDownLatch(1)
+
+ rule.runOnUiThread {
+ elevation.value = 2f // elevation was already 1, so it doesn't need to enableZ again
+ }
+ Assert.assertFalse(drawLatch.await(200, TimeUnit.MILLISECONDS))
+
+ rule.runOnUiThread {
+ elevation.value = 0f // going to 0 doesn't trigger invalidation
+ }
+ Assert.assertFalse(drawLatch.await(200, TimeUnit.MILLISECONDS))
+
+ rule.runOnUiThread {
+ elevation.value = 1f // going to 1 won't invalidate because it was last drawn with Z
+ }
+ Assert.assertFalse(drawLatch.await(200, TimeUnit.MILLISECONDS))
+
+ rule.runOnUiThread {
+ elevation.value = 0f
+ underColor.value = Color.Black
+ }
+
+ assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
+
+ drawLatch = CountDownLatch(1)
+
+ rule.runOnUiThread {
+ elevation.value = 1f
+ }
+ assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
+ }
+
@Composable
private fun ShadowContainer(
elevation: State<Dp> = mutableStateOf(8.dp),
@@ -230,6 +329,16 @@
drawLatch.countDown()
}
+ fun Modifier.drawLatchModifier() = drawBehind { drawLatch.countDown() }
+
+ private fun Modifier.background(
+ color: State<Color>
+ ) = drawBehind {
+ if (color.value != Color.Transparent) {
+ drawRect(color.value)
+ }
+ }
+
private fun takeScreenShot(width: Int, height: Int = width): Bitmap {
val bitmap = rule.waitAndScreenShot()
assertEquals(width, bitmap.width)
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/PainterModifierTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/PainterModifierTest.kt
index b5ef674..09458a6 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/PainterModifierTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/PainterModifierTest.kt
@@ -41,11 +41,11 @@
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.DefaultAlpha
-import androidx.compose.ui.graphics.ImageAsset
+import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.asAndroidBitmap
-import androidx.compose.ui.graphics.asImageAsset
+import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.compositeOver
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.painter.ImagePainter
@@ -528,7 +528,7 @@
// height and width centered within the bounds of the composable
val boxWidth = 600
val boxHeight = 400
- val srcImage = ImageAsset(100, 200)
+ val srcImage = ImageBitmap(100, 200)
val canvas = Canvas(srcImage)
val paint = Paint().apply { this.color = Color.Red }
canvas.drawRect(0f, 0f, 400f, 200f, paint)
@@ -568,12 +568,12 @@
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
@Test
fun testImagePainterScalesNonUniformly() {
- // The composable dimensions are larger than the ImageAsset. By not passing in
- // a ContentScale parameter to the painter, the ImageAsset should be stretched
+ // The composable dimensions are larger than the ImageBitmap. By not passing in
+ // a ContentScale parameter to the painter, the ImageBitmap should be stretched
// non-uniformly to fully occupy the bounds of the composable
val boxWidth = 60
val boxHeight = 40
- val srcImage = ImageAsset(10, 20)
+ val srcImage = ImageBitmap(10, 20)
val canvas = Canvas(srcImage)
val paint = Paint().apply { this.color = Color.Red }
canvas.drawRect(0f, 0f, 40f, 20f, paint)
@@ -591,7 +591,7 @@
)
}
- rule.obtainScreenshotBitmap(boxWidth, boxHeight).asImageAsset().assertPixels { Color.Red }
+ rule.obtainScreenshotBitmap(boxWidth, boxHeight).asImageBitmap().assertPixels { Color.Red }
}
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/gesture/LongPressDragGestureFilterTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/gesture/LongPressDragGestureFilterTest.kt
index 9cdd663..482bbac 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/gesture/LongPressDragGestureFilterTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/gesture/LongPressDragGestureFilterTest.kt
@@ -23,14 +23,19 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.platform.InspectableValue
+import androidx.compose.ui.platform.ValueElement
+import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
import androidx.compose.ui.platform.setContent
import androidx.compose.ui.test.TestActivity
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
+import com.google.common.truth.Truth.assertThat
import com.nhaarman.mockitokotlin2.inOrder
import com.nhaarman.mockitokotlin2.spy
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions
+import org.junit.After
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
@@ -94,6 +99,16 @@
)
}
+ @Before
+ fun before() {
+ isDebugInspectorInfoEnabled = true
+ }
+
+ @After
+ fun after() {
+ isDebugInspectorInfoEnabled = false
+ }
+
@Test
fun ui_downMoveUpBeforeLongPressTimeout_noCallbacksCalled() {
@@ -383,6 +398,20 @@
verifyNoMoreInteractions(longPressDragObserver)
}
+ @Test
+ fun testInspectorValue() {
+ val onLongPress: () -> Unit = {}
+ val observer = MyLongPressDragObserver(onLongPress)
+ activityTestRule.runOnUiThreadIR {
+ val modifier = Modifier.longPressDragGestureFilter(observer) as InspectableValue
+ assertThat(modifier.nameFallback).isEqualTo("longPressDragGestureFilter")
+ assertThat(modifier.valueOverride).isNull()
+ assertThat(modifier.inspectableElements.asIterable()).containsExactly(
+ ValueElement("longPressDragObserver", observer)
+ )
+ }
+ }
+
private fun waitForLongPress(block: () -> Unit) {
longPressCountDownLatch = CountDownLatch(1)
activityTestRule.runOnUiThreadIR(block)
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/gesture/LongPressGestureFilterComposeTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/gesture/LongPressGestureFilterComposeTest.kt
new file mode 100644
index 0000000..508a0b2
--- /dev/null
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/gesture/LongPressGestureFilterComposeTest.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2020 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.ui.gesture
+
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.platform.InspectableValue
+import androidx.compose.ui.platform.ValueElement
+import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
+import androidx.compose.ui.test.junit4.createComposeRule
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+class LongPressGestureFilterComposeTest {
+ @get:Rule
+ val rule = createComposeRule()
+
+ @Before
+ fun before() {
+ isDebugInspectorInfoEnabled = true
+ }
+
+ @After
+ fun after() {
+ isDebugInspectorInfoEnabled = false
+ }
+
+ @Test
+ fun testInspectorValue() {
+ val onLongPress: (Offset) -> Unit = {}
+ rule.setContent {
+ val modifier = Modifier.longPressGestureFilter(onLongPress) as InspectableValue
+ assertThat(modifier.nameFallback).isEqualTo("longPressGestureFilter")
+ assertThat(modifier.valueOverride).isNull()
+ assertThat(modifier.inspectableElements.asIterable()).containsExactly(
+ ValueElement("onLongPress", onLongPress)
+ )
+ }
+ }
+}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/gesture/PressIndicatorGestureFilterComposeTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/gesture/PressIndicatorGestureFilterComposeTest.kt
new file mode 100644
index 0000000..18ed376
--- /dev/null
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/gesture/PressIndicatorGestureFilterComposeTest.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2020 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.ui.gesture
+
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.platform.InspectableValue
+import androidx.compose.ui.platform.ValueElement
+import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
+import androidx.compose.ui.test.junit4.createComposeRule
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+class PressIndicatorGestureFilterComposeTest {
+ @get:Rule
+ val rule = createComposeRule()
+
+ @Before
+ fun before() {
+ isDebugInspectorInfoEnabled = true
+ }
+
+ @After
+ fun after() {
+ isDebugInspectorInfoEnabled = false
+ }
+
+ @Test
+ fun testInspectorValue() {
+ val onStart: (Offset) -> Unit = {}
+ val onStop: () -> Unit = {}
+ val onCancel: () -> Unit = {}
+ val enabled = true
+ rule.setContent {
+ val modifier = Modifier.pressIndicatorGestureFilter(onStart, onStop, onCancel, enabled)
+ as InspectableValue
+ assertThat(modifier.nameFallback).isEqualTo("pressIndicatorGestureFilter")
+ assertThat(modifier.valueOverride).isNull()
+ assertThat(modifier.inspectableElements.asIterable()).containsExactly(
+ ValueElement("onStart", onStart),
+ ValueElement("onStop", onStop),
+ ValueElement("onCancel", onCancel),
+ ValueElement("enabled", enabled)
+ )
+ }
+ }
+}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/gesture/RawDragGestureFilterComposeTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/gesture/RawDragGestureFilterComposeTest.kt
new file mode 100644
index 0000000..1c831a9
--- /dev/null
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/gesture/RawDragGestureFilterComposeTest.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2020 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.ui.gesture
+
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.gesture.scrollorientationlocking.Orientation
+import androidx.compose.ui.platform.InspectableValue
+import androidx.compose.ui.platform.ValueElement
+import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
+import androidx.compose.ui.test.junit4.createComposeRule
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+class RawDragGestureFilterComposeTest {
+ @get:Rule
+ val rule = createComposeRule()
+
+ @Before
+ fun before() {
+ isDebugInspectorInfoEnabled = true
+ }
+
+ @After
+ fun after() {
+ isDebugInspectorInfoEnabled = false
+ }
+
+ @Test
+ fun testInspectorValue() {
+ val dragObserver = object : DragObserver {}
+ val canStartDragging: () -> Boolean = { true }
+ rule.setContent {
+ val modifier = Modifier.rawDragGestureFilter(
+ dragObserver,
+ canStartDragging,
+ Orientation.Horizontal
+ ) as InspectableValue
+
+ assertThat(modifier.nameFallback).isEqualTo("rawDragGestureFilter")
+ assertThat(modifier.valueOverride).isNull()
+ assertThat(modifier.inspectableElements.asIterable()).containsExactly(
+ ValueElement("dragObserver", dragObserver),
+ ValueElement("canStartDragging", canStartDragging),
+ ValueElement("orientation", Orientation.Horizontal)
+ )
+ }
+ }
+}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/gesture/RawPressStartGestureFilterComposeTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/gesture/RawPressStartGestureFilterComposeTest.kt
new file mode 100644
index 0000000..17cfbce
--- /dev/null
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/gesture/RawPressStartGestureFilterComposeTest.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2020 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.ui.gesture
+
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.platform.InspectableValue
+import androidx.compose.ui.platform.ValueElement
+import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
+import androidx.compose.ui.test.junit4.createComposeRule
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+class RawPressStartGestureFilterComposeTest {
+ @get:Rule
+ val rule = createComposeRule()
+
+ @Before
+ fun before() {
+ isDebugInspectorInfoEnabled = true
+ }
+
+ @After
+ fun after() {
+ isDebugInspectorInfoEnabled = false
+ }
+
+ @Test
+ fun testInspectorValue() {
+ val onPressStart: (Offset) -> Unit = {}
+ PointerEventPass.Main
+ rule.setContent {
+ val modifier = Modifier.rawPressStartGestureFilter(
+ onPressStart,
+ true,
+ PointerEventPass.Main
+ ) as InspectableValue
+
+ assertThat(modifier.nameFallback).isEqualTo("rawPressStartGestureFilter")
+ assertThat(modifier.valueOverride).isNull()
+ assertThat(modifier.inspectableElements.asIterable()).containsExactly(
+ ValueElement("onPressStart", onPressStart),
+ ValueElement("enabled", true),
+ ValueElement("executionPass", PointerEventPass.Main)
+ )
+ }
+ }
+}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/gesture/RawScaleGestureFilterComposeTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/gesture/RawScaleGestureFilterComposeTest.kt
new file mode 100644
index 0000000..0b5dafc
--- /dev/null
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/gesture/RawScaleGestureFilterComposeTest.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2020 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.ui.gesture
+
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.InspectableValue
+import androidx.compose.ui.platform.ValueElement
+import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
+import androidx.compose.ui.test.junit4.createComposeRule
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+class RawScaleGestureFilterComposeTest {
+ @get:Rule
+ val rule = createComposeRule()
+
+ @Before
+ fun before() {
+ isDebugInspectorInfoEnabled = true
+ }
+
+ @After
+ fun after() {
+ isDebugInspectorInfoEnabled = false
+ }
+
+ @Test
+ fun testInspectorValue() {
+ val observer = object : RawScaleObserver {}
+ val canStartScaling: () -> Boolean = { true }
+ rule.setContent {
+ val modifier = Modifier.rawScaleGestureFilter(observer, canStartScaling)
+ as InspectableValue
+
+ assertThat(modifier.nameFallback).isEqualTo("rawScaleGestureFilter")
+ assertThat(modifier.valueOverride).isNull()
+ assertThat(modifier.inspectableElements.asIterable()).containsExactly(
+ ValueElement("scaleObserver", observer),
+ ValueElement("canStartScaling", canStartScaling)
+ )
+ }
+ }
+}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/gesture/ScaleGestureFilterTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/gesture/ScaleGestureFilterTest.kt
index a1c156c..5e7a49b 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/gesture/ScaleGestureFilterTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/gesture/ScaleGestureFilterTest.kt
@@ -23,15 +23,20 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.platform.DensityAmbient
+import androidx.compose.ui.platform.InspectableValue
+import androidx.compose.ui.platform.ValueElement
+import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
import androidx.compose.ui.platform.setContent
import androidx.compose.ui.test.TestActivity
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
+import com.google.common.truth.Truth.assertThat
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.inOrder
import com.nhaarman.mockitokotlin2.spy
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions
+import org.junit.After
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
@@ -91,6 +96,16 @@
assertTrue(setupLatch.await(1000, TimeUnit.SECONDS))
}
+ @Before
+ fun before() {
+ isDebugInspectorInfoEnabled = true
+ }
+
+ @After
+ fun after() {
+ isDebugInspectorInfoEnabled = false
+ }
+
@Test
fun ui_pointerMovementWithinTouchSlop_noCallbacksCalled() {
@@ -434,6 +449,21 @@
verify(scaleObserver).onScale(.2f)
}
+
+ @Test
+ fun testInspectorValue() {
+ val observer = MyScaleObserver()
+ activityTestRule.runOnUiThread {
+ val modifier = Modifier.scaleGestureFilter(observer)
+ as InspectableValue
+
+ assertThat(modifier.nameFallback).isEqualTo("scaleGestureFilter")
+ assertThat(modifier.valueOverride).isNull()
+ assertThat(modifier.inspectableElements.asIterable()).containsExactly(
+ ValueElement("scaleObserver", observer),
+ )
+ }
+ }
}
@Suppress("RedundantOverride")
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/gesture/ScaleSlopExceededGestureFilterComposeTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/gesture/ScaleSlopExceededGestureFilterComposeTest.kt
new file mode 100644
index 0000000..a2c3669
--- /dev/null
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/gesture/ScaleSlopExceededGestureFilterComposeTest.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2020 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.ui.gesture
+
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.InspectableValue
+import androidx.compose.ui.platform.ValueElement
+import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
+import androidx.compose.ui.test.junit4.createComposeRule
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+class ScaleSlopExceededGestureFilterComposeTest {
+ @get:Rule
+ val rule = createComposeRule()
+
+ @Before
+ fun before() {
+ isDebugInspectorInfoEnabled = true
+ }
+
+ @After
+ fun after() {
+ isDebugInspectorInfoEnabled = false
+ }
+
+ @Test
+ fun testInspectorValue() {
+ val onScaleSlopExceeded: () -> Unit = {}
+ rule.setContent {
+ val modifier = Modifier.scaleSlopExceededGestureFilter(onScaleSlopExceeded)
+ as InspectableValue
+
+ assertThat(modifier.nameFallback).isEqualTo("scaleSlopExceededGestureFilter")
+ assertThat(modifier.valueOverride).isNull()
+ assertThat(modifier.inspectableElements.asIterable()).containsExactly(
+ ValueElement("onScaleSlopExceeded", onScaleSlopExceeded)
+ )
+ }
+ }
+}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/gesture/ScrollGestureFilterComposeTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/gesture/ScrollGestureFilterComposeTest.kt
new file mode 100644
index 0000000..20ae38b
--- /dev/null
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/gesture/ScrollGestureFilterComposeTest.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2020 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.ui.gesture
+
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.gesture.scrollorientationlocking.Orientation
+import androidx.compose.ui.platform.InspectableValue
+import androidx.compose.ui.platform.ValueElement
+import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
+import androidx.compose.ui.test.junit4.createComposeRule
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+class ScrollGestureFilterComposeTest {
+ @get:Rule
+ val rule = createComposeRule()
+
+ @Before
+ fun before() {
+ isDebugInspectorInfoEnabled = true
+ }
+
+ @After
+ fun after() {
+ isDebugInspectorInfoEnabled = false
+ }
+
+ @Test
+ fun testInspectorValue() {
+ val scrollCallback = object : ScrollCallback {}
+ val canDrag: (Direction) -> Boolean = { true }
+ rule.setContent {
+ val modifier = Modifier.scrollGestureFilter(
+ scrollCallback,
+ Orientation.Vertical,
+ canDrag,
+ true
+ ) as InspectableValue
+
+ assertThat(modifier.nameFallback).isEqualTo("scrollGestureFilter")
+ assertThat(modifier.valueOverride).isNull()
+ assertThat(modifier.inspectableElements.asIterable()).containsExactly(
+ ValueElement("scrollCallback", scrollCallback),
+ ValueElement("orientation", Orientation.Vertical),
+ ValueElement("canDrag", canDrag),
+ ValueElement("startDragImmediately", true),
+ )
+ }
+ }
+}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerInteropFilterTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerInteropFilterTest.kt
index 091e54b..03e43b3 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerInteropFilterTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerInteropFilterTest.kt
@@ -25,18 +25,23 @@
import android.view.MotionEvent.ACTION_UP
import android.view.MotionEvent.TOOL_TYPE_UNKNOWN
import androidx.activity.ComponentActivity
+import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.gesture.PointerCoords
import androidx.compose.ui.gesture.PointerProperties
import androidx.compose.ui.layout.AlignmentLine
import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.platform.InspectableValue
+import androidx.compose.ui.platform.ValueElement
+import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.milliseconds
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.google.common.truth.Truth.assertThat
+import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -65,6 +70,16 @@
pointerInteropFilter.requestDisallowInterceptTouchEvent = disallowInterceptRequester
}
+ @Before
+ fun before() {
+ isDebugInspectorInfoEnabled = true
+ }
+
+ @After
+ fun after() {
+ isDebugInspectorInfoEnabled = false
+ }
+
// Verification of correct MotionEvents being dispatched (when no events are cancel)
@Test
@@ -4329,6 +4344,21 @@
assertThat(dispatchedMotionEvents).hasSize(1)
}
+ @Test
+ fun testInspectorValue() {
+ val onTouchEvent: (MotionEvent) -> Boolean = { true }
+ rule.setContent {
+ val modifier = Modifier.pointerInteropFilter(disallowInterceptRequester, onTouchEvent)
+ as InspectableValue
+ assertThat(modifier.nameFallback).isEqualTo("pointerInteropFilter")
+ assertThat(modifier.valueOverride).isNull()
+ assertThat(modifier.inspectableElements.asIterable()).containsExactly(
+ ValueElement("requestDisallowInterceptTouchEvent", disallowInterceptRequester),
+ ValueElement("onTouchEvent", onTouchEvent)
+ )
+ }
+ }
+
private class MockCoordinates : LayoutCoordinates {
override val size: IntSize
get() = IntSize.Zero
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/RtlLayoutTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/RtlLayoutTest.kt
index 8abc165..5adf0f7 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/RtlLayoutTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/RtlLayoutTest.kt
@@ -212,16 +212,16 @@
val width = placeables.fold(0) { sum, p -> sum + p.width }
val height = placeables.fold(0) { sum, p -> sum + p.height }
layout(width, height) {
- var x = 0f
- var y = 0f
+ var x = 0
+ var y = 0
for (placeable in placeables) {
if (absolutePositioning) {
- placeable.place(Offset(x, y))
+ placeable.place(x, y)
} else {
- placeable.placeRelative(Offset(x, y))
+ placeable.placeRelative(x, y)
}
- x += placeable.width.toFloat()
- y += placeable.height.toFloat()
+ x += placeable.width
+ y += placeable.height
}
}
}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/SubcomposeLayoutTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/SubcomposeLayoutTest.kt
index da1aae6..02a55a7 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/SubcomposeLayoutTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/SubcomposeLayoutTest.kt
@@ -32,7 +32,7 @@
import androidx.compose.ui.background
import androidx.compose.ui.draw.assertColor
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.ImageAsset
+import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asAndroidBitmap
import androidx.compose.ui.platform.AndroidOwnerExtraAssertionsRule
import androidx.compose.ui.platform.DensityAmbient
@@ -552,6 +552,6 @@
}
}
-fun ImageAsset.assertCenterPixelColor(expectedColor: Color) {
+fun ImageBitmap.assertCenterPixelColor(expectedColor: Color) {
asAndroidBitmap().assertColor(expectedColor, width / 2, height / 2)
}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/WithConstraintsTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/WithConstraintsTest.kt
index ffb2010..9a7ab5d 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/WithConstraintsTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/WithConstraintsTest.kt
@@ -35,7 +35,6 @@
import androidx.compose.ui.assertRect
import androidx.compose.ui.draw.paint
import androidx.compose.ui.drawBehind
-import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.node.Ref
@@ -45,6 +44,7 @@
import androidx.compose.ui.runOnUiThreadIR
import androidx.compose.ui.test.TestActivity
import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.waitAndScreenShot
@@ -700,7 +700,7 @@
minHeight = minHeightConstraint.toIntPx(),
maxHeight = maxHeightConstraint.toIntPx()
)
- ).place(Offset.Zero)
+ ).place(IntOffset.Zero)
}
}
}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/res/ResourcesTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/res/ResourcesTest.kt
index 2d724d6..b47ba46 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/res/ResourcesTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/res/ResourcesTest.kt
@@ -18,7 +18,7 @@
import android.util.LruCache
import androidx.compose.runtime.Providers
-import androidx.compose.ui.graphics.ImageAsset
+import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asAndroidBitmap
import androidx.compose.ui.graphics.imageFromResource
import androidx.compose.ui.platform.ContextAmbient
@@ -67,7 +67,7 @@
val failedImage = imageFromResource(resource, R.drawable.failed_image)
var uiThreadWork: (() -> Unit)? = null
- var res: DeferredResource<ImageAsset>? = null
+ var res: DeferredResource<ImageBitmap>? = null
rule.setContent {
Providers(ContextAmbient provides context) {
@@ -130,7 +130,7 @@
val failedImage = imageFromResource(resource, R.drawable.failed_image)
var uiThreadWork: (() -> Unit)? = null
- var res: DeferredResource<ImageAsset>? = null
+ var res: DeferredResource<ImageBitmap>? = null
rule.setContent {
Providers(ContextAmbient provides context) {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/viewinterop/EditTextInteropTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/viewinterop/EditTextInteropTest.kt
new file mode 100644
index 0000000..b000a0a
--- /dev/null
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/viewinterop/EditTextInteropTest.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2020 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.ui.viewinterop
+
+import android.view.KeyEvent
+import android.view.View
+import android.widget.EditText
+import androidx.activity.ComponentActivity
+import androidx.compose.ui.focus.ExperimentalFocus
+import androidx.compose.ui.platform.ViewAmbient
+import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.text.InternalTextApi
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@OptIn(ExperimentalFocus::class, InternalTextApi::class)
+@RunWith(AndroidJUnit4::class)
+class EditTextInteropTest {
+ @get:Rule
+ val rule = createAndroidComposeRule<ComponentActivity>()
+
+ @Test
+ fun hardwareKeyInEmbeddedView() {
+ // Arrange.
+ lateinit var editText: EditText
+ lateinit var ownerView: View
+ rule.setContent {
+ ownerView = ViewAmbient.current
+ AndroidView({
+ EditText(it).apply { width = 500; editText = this }
+ })
+ }
+ rule.runOnIdle { editText.requestFocus() }
+
+ // Act.
+ val keyConsumed = rule.runOnIdle {
+ ownerView.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_A))
+ }
+
+ // Assert.
+ // TODO(b/171997891): Right now we just assert that we reach here without crashing. Once we
+ // propagate hardware keys correctly, assert that the EditText receives the entered value.
+ assertThat(keyConsumed).isFalse()
+ }
+}
\ No newline at end of file
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/PointerInteropFilter.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/PointerInteropFilter.kt
index 6e1811f..d637e37 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/PointerInteropFilter.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/PointerInteropFilter.kt
@@ -25,6 +25,7 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.platform.debugInspectorInfo
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.milliseconds
import androidx.compose.ui.util.fastAny
@@ -59,7 +60,13 @@
fun Modifier.pointerInteropFilter(
requestDisallowInterceptTouchEvent: (RequestDisallowInterceptTouchEvent)? = null,
onTouchEvent: (MotionEvent) -> Boolean
-): Modifier = composed {
+): Modifier = composed(
+ inspectorInfo = debugInspectorInfo {
+ name = "pointerInteropFilter"
+ properties["requestDisallowInterceptTouchEvent"] = requestDisallowInterceptTouchEvent
+ properties["onTouchEvent"] = onTouchEvent
+ }
+) {
val filter = remember { PointerInteropFilter() }
filter.onTouchEvent = onTouchEvent
filter.requestDisallowInterceptTouchEvent = requestDisallowInterceptTouchEvent
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.kt
index 0efb821..c525d28 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.kt
@@ -326,9 +326,17 @@
return keyInputModifier.processKeyInput(keyEvent)
}
- override fun dispatchKeyEvent(event: AndroidKeyEvent): Boolean {
- return sendKeyEvent(KeyEventAndroid(event))
- }
+ override fun dispatchKeyEvent(event: AndroidKeyEvent) =
+ if (isFocused) {
+ // Focus lies within the Compose hierarchy, so we dispatch the key event to the
+ // appropriate place.
+ sendKeyEvent(KeyEventAndroid(event))
+ } else {
+ // This Owner has a focused child view, which is a view interop use case,
+ // so we use the default ViewGroup behavior which will route tke key event to the
+ // focused view.
+ super.dispatchKeyEvent(event)
+ }
override fun pauseModelReadObserveration(block: () -> Unit) =
snapshotObserver.pauseObservingReads(block)
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/res/ImageResources.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/res/ImageResources.kt
index 584b134..1a0fe6b 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/res/ImageResources.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/res/ImageResources.kt
@@ -20,7 +20,7 @@
import androidx.annotation.DrawableRes
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
-import androidx.compose.ui.graphics.ImageAsset
+import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.imageFromResource
import androidx.compose.ui.platform.ContextAmbient
import androidx.compose.ui.util.trace
@@ -34,7 +34,7 @@
* @return the decoded image data associated with the resource
*/
@Composable
-fun imageResource(@DrawableRes id: Int): ImageAsset {
+fun imageResource(@DrawableRes id: Int): ImageBitmap {
val context = ContextAmbient.current
val value = remember { TypedValue() }
context.resources.getValue(id, value, true)
@@ -59,9 +59,9 @@
@Composable
fun loadImageResource(
id: Int,
- pendingImage: ImageAsset? = null,
- failedImage: ImageAsset? = null
-): DeferredResource<ImageAsset> {
+ pendingImage: ImageBitmap? = null,
+ failedImage: ImageBitmap? = null
+): DeferredResource<ImageBitmap> {
val context = ContextAmbient.current
val res = context.resources
val value = remember { TypedValue() }
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/TextInputServiceAndroid.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/TextInputServiceAndroid.kt
index 28d0a29..a5f2405 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/TextInputServiceAndroid.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/TextInputServiceAndroid.kt
@@ -16,7 +16,9 @@
package androidx.compose.ui.text.input
+import android.annotation.SuppressLint
import android.content.Context
+import android.os.Build
import android.text.InputType
import android.view.View
import android.view.ViewTreeObserver
@@ -245,6 +247,14 @@
}
}
+ outInfo.initialSelStart = state.selection.start
+ outInfo.initialSelEnd = state.selection.end
+
+ @SuppressLint("UnsafeNewApiCall")
+ if (Build.VERSION.SDK_INT >= 30) {
+ outInfo.setInitialSurroundingText(state.text)
+ }
+
outInfo.imeOptions = outInfo.imeOptions or EditorInfo.IME_FLAG_NO_FULLSCREEN
}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/FocusObserverModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/FocusObserverModifier.kt
index 7758df1..4546434 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/FocusObserverModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/FocusObserverModifier.kt
@@ -18,6 +18,9 @@
import androidx.compose.ui.focus.ExperimentalFocus
import androidx.compose.ui.focus.FocusState
+import androidx.compose.ui.platform.InspectorInfo
+import androidx.compose.ui.platform.InspectorValueInfo
+import androidx.compose.ui.platform.debugInspectorInfo
/**
* A [modifier][Modifier.Element] that can be used to observe focus state changes.
@@ -32,13 +35,22 @@
@OptIn(ExperimentalFocus::class)
internal class FocusObserverModifierImpl(
- override val onFocusChange: (FocusState) -> Unit
-) : FocusObserverModifier
+ override val onFocusChange: (FocusState) -> Unit,
+ inspectorInfo: InspectorInfo.() -> Unit
+) : FocusObserverModifier, InspectorValueInfo(inspectorInfo)
/**
* Add this modifier to a component to observe focus state changes.
*/
@ExperimentalFocus
fun Modifier.focusObserver(onFocusChange: (FocusState) -> Unit): Modifier {
- return this.then(FocusObserverModifierImpl(onFocusChange))
+ return this.then(
+ FocusObserverModifierImpl(
+ onFocusChange = onFocusChange,
+ inspectorInfo = debugInspectorInfo {
+ name = "focusObserver"
+ properties["onFocusChange"] = onFocusChange
+ }
+ )
+ )
}
\ No newline at end of file
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/FocusRequesterModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/FocusRequesterModifier.kt
index acfbf3c..bd049b0 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/FocusRequesterModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/FocusRequesterModifier.kt
@@ -18,6 +18,9 @@
import androidx.compose.ui.focus.ExperimentalFocus
import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.platform.InspectorInfo
+import androidx.compose.ui.platform.InspectorValueInfo
+import androidx.compose.ui.platform.debugInspectorInfo
/**
* A [modifier][Modifier.Element] that can be used to pass in a [FocusRequester] that can be used
@@ -35,13 +38,22 @@
@OptIn(ExperimentalFocus::class)
internal class FocusRequesterModifierImpl(
- override val focusRequester: FocusRequester
-) : FocusRequesterModifier
+ override val focusRequester: FocusRequester,
+ inspectorInfo: InspectorInfo.() -> Unit
+) : FocusRequesterModifier, InspectorValueInfo(inspectorInfo)
/**
* Add this modifier to a component to observe changes to focus state.
*/
@ExperimentalFocus
fun Modifier.focusRequester(focusRequester: FocusRequester): Modifier {
- return this.then(FocusRequesterModifierImpl(focusRequester))
+ return this.then(
+ FocusRequesterModifierImpl(
+ focusRequester = focusRequester,
+ inspectorInfo = debugInspectorInfo {
+ name = "focusRequester"
+ properties["focusRequester"] = focusRequester
+ }
+ )
+ )
}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/ZIndexModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/ZIndexModifier.kt
index 92fe7b5..80850d8 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/ZIndexModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/ZIndexModifier.kt
@@ -17,34 +17,20 @@
package androidx.compose.ui
import androidx.compose.runtime.Stable
+import androidx.compose.ui.layout.LayoutModifier
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
import androidx.compose.ui.platform.InspectorInfo
import androidx.compose.ui.platform.InspectorValueInfo
import androidx.compose.ui.platform.debugInspectorInfo
-
-/**
- * A [Modifier.Element] that controls the drawing order for the children of the same layout
- * parent. A child with larger [zIndex] will be drawn on top of all the children with smaller
- * [zIndex]. When children have the same [zIndex] the original order in which the items were
- * added into the parent layout is applied.
- *
- * Note that if there would be multiple [ZIndexModifier] modifiers applied for the same layout
- * the sum of their values will be used as the final zIndex. If no [ZIndexModifier]s applied for the
- * layout then zIndex for this Layout is 0.
- *
- * @see [Modifier.zIndex]
- */
-// TODO("Made it internal to be able to later migrate Modifier.zIndex() implementation without
-// the breaking change to work as LayoutModifier where we provide zIndex right as a param for
-// placeable.place() call. Tracked in b/171493718")
-internal interface ZIndexModifier : Modifier.Element {
- val zIndex: Float
-}
+import androidx.compose.ui.unit.Constraints
/**
* Creates a modifier that controls the drawing order for the children of the same layout parent.
* A child with larger [zIndex] will be drawn on top of all the children with smaller [zIndex].
- * When children have the same [zIndex] the original order in which the items were added into the
- * parent layout is applied.
+ * When children have the same [zIndex] the original order in which the parent placed the
+ * children is used.
*
* Note that if there would be multiple [zIndex] modifiers applied for the same layout
* the sum of their values will be used as the final zIndex. If no [zIndex] were applied for the
@@ -54,7 +40,7 @@
*/
@Stable
fun Modifier.zIndex(zIndex: Float): Modifier = this.then(
- SimpleZIndexModifier(
+ ZIndexModifier(
zIndex = zIndex,
inspectorInfo = debugInspectorInfo {
name = "zIndex"
@@ -63,19 +49,27 @@
)
)
-private class SimpleZIndexModifier(
- override val zIndex: Float,
+private class ZIndexModifier(
+ private val zIndex: Float,
inspectorInfo: InspectorInfo.() -> Unit
-) : ZIndexModifier, InspectorValueInfo(inspectorInfo) {
+) : LayoutModifier, InspectorValueInfo(inspectorInfo) {
- override fun hashCode(): Int =
- zIndex.hashCode()
+ override fun MeasureScope.measure(
+ measurable: Measurable,
+ constraints: Constraints
+ ): MeasureResult {
+ val placeable = measurable.measure(constraints)
+ return layout(placeable.width, placeable.height) {
+ placeable.place(0, 0, zIndex = zIndex)
+ }
+ }
+
+ override fun hashCode(): Int = zIndex.hashCode()
override fun equals(other: Any?): Boolean {
- val otherModifier = other as? SimpleZIndexModifier ?: return false
+ val otherModifier = other as? ZIndexModifier ?: return false
return zIndex == otherModifier.zIndex
}
- override fun toString(): String =
- "SimpleZIndexModifier(zIndex=$zIndex)"
+ override fun toString(): String = "ZIndexModifier(zIndex=$zIndex)"
}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draw/DrawShadow.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draw/DrawShadow.kt
index 231f86f..0cfff37 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draw/DrawShadow.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draw/DrawShadow.kt
@@ -23,6 +23,7 @@
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.platform.DensityAmbient
+import androidx.compose.ui.platform.debugInspectorInfo
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@@ -48,7 +49,14 @@
shape: Shape = RectangleShape,
clip: Boolean = elevation > 0.dp
) = if (elevation > 0.dp || clip) {
- composed {
+ composed(
+ inspectorInfo = debugInspectorInfo {
+ name = "drawShadow"
+ properties["elevation"] = elevation
+ properties["shape"] = shape
+ properties["clip"] = clip
+ }
+ ) {
drawLayer(
shadowElevation = with(DensityAmbient.current) { elevation.toPx() },
shape = shape,
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/LongPressDragGestureFilter.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/LongPressDragGestureFilter.kt
index c725a4e..dd36b98 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/LongPressDragGestureFilter.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/LongPressDragGestureFilter.kt
@@ -24,6 +24,7 @@
import androidx.compose.ui.input.pointer.PointerEventPass
import androidx.compose.ui.input.pointer.PointerInputFilter
import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed
+import androidx.compose.ui.platform.debugInspectorInfo
import androidx.compose.ui.unit.IntSize
interface LongPressDragObserver {
@@ -118,7 +119,12 @@
*/
fun Modifier.longPressDragGestureFilter(
longPressDragObserver: LongPressDragObserver
-): Modifier = composed {
+): Modifier = composed(
+ inspectorInfo = debugInspectorInfo {
+ name = "longPressDragGestureFilter"
+ properties["longPressDragObserver"] = longPressDragObserver
+ }
+) {
val glue = remember { LongPressDragGestureDetectorGlue() }
glue.longPressDragObserver = longPressDragObserver
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/LongPressGestureFilter.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/LongPressGestureFilter.kt
index 0f655f6..7619155 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/LongPressGestureFilter.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/LongPressGestureFilter.kt
@@ -33,6 +33,7 @@
import androidx.compose.ui.input.pointer.changedToUp
import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed
import androidx.compose.ui.input.pointer.consumeDownChange
+import androidx.compose.ui.platform.debugInspectorInfo
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.inMilliseconds
import androidx.compose.ui.util.annotation.VisibleForTesting
@@ -55,7 +56,12 @@
*/
fun Modifier.longPressGestureFilter(
onLongPress: (Offset) -> Unit
-): Modifier = composed {
+): Modifier = composed(
+ inspectorInfo = debugInspectorInfo {
+ name = "longPressGestureFilter"
+ properties["onLongPress"] = onLongPress
+ }
+) {
@Suppress("DEPRECATION")
val scope = rememberCoroutineScope()
val filter = remember { LongPressGestureFilter(scope) }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/PressIndicatorGestureFilter.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/PressIndicatorGestureFilter.kt
index 077c908..ad7dc32 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/PressIndicatorGestureFilter.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/PressIndicatorGestureFilter.kt
@@ -28,6 +28,7 @@
import androidx.compose.ui.input.pointer.changedToDown
import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed
import androidx.compose.ui.input.pointer.consumeDownChange
+import androidx.compose.ui.platform.debugInspectorInfo
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.util.fastAny
import androidx.compose.ui.util.fastForEach
@@ -54,7 +55,15 @@
onStop: (() -> Unit)? = null,
onCancel: (() -> Unit)? = null,
enabled: Boolean = true
-): Modifier = composed {
+): Modifier = composed(
+ inspectorInfo = debugInspectorInfo {
+ name = "pressIndicatorGestureFilter"
+ properties["onStart"] = onStart
+ properties["onStop"] = onStop
+ properties["onCancel"] = onCancel
+ properties["enabled"] = enabled
+ }
+) {
val filter = remember { PressIndicatorGestureFilter() }
filter.onStart = onStart
filter.onStop = onStop
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/RawDragGestureFilter.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/RawDragGestureFilter.kt
index 784548b..250512b 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/RawDragGestureFilter.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/RawDragGestureFilter.kt
@@ -38,6 +38,7 @@
import androidx.compose.ui.input.pointer.consumeDownChange
import androidx.compose.ui.input.pointer.consumePositionChange
import androidx.compose.ui.input.pointer.positionChange
+import androidx.compose.ui.platform.debugInspectorInfo
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.util.fastAny
import androidx.compose.ui.util.fastForEach
@@ -150,7 +151,14 @@
dragObserver: DragObserver,
canStartDragging: (() -> Boolean)? = null,
orientation: Orientation? = null
-): Modifier = composed {
+): Modifier = composed(
+ inspectorInfo = debugInspectorInfo {
+ name = "rawDragGestureFilter"
+ properties["dragObserver"] = dragObserver
+ properties["canStartDragging"] = canStartDragging
+ properties["orientation"] = orientation
+ }
+) {
val filter = remember { RawDragGestureFilter() }
filter.dragObserver = dragObserver
filter.canStartDragging = canStartDragging
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/RawPressStartGestureFilter.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/RawPressStartGestureFilter.kt
index 543e36a..7efd427 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/RawPressStartGestureFilter.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/RawPressStartGestureFilter.kt
@@ -26,6 +26,7 @@
import androidx.compose.ui.input.pointer.changedToDown
import androidx.compose.ui.input.pointer.changedToUp
import androidx.compose.ui.input.pointer.consumeDownChange
+import androidx.compose.ui.platform.debugInspectorInfo
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.util.fastForEach
@@ -54,7 +55,14 @@
onPressStart: (Offset) -> Unit,
enabled: Boolean = false,
executionPass: PointerEventPass = PointerEventPass.Main
-): Modifier = composed {
+): Modifier = composed(
+ inspectorInfo = debugInspectorInfo {
+ name = "rawPressStartGestureFilter"
+ properties["onPressStart"] = onPressStart
+ properties["enabled"] = enabled
+ properties["executionPass"] = executionPass
+ }
+) {
val filter = remember { RawPressStartGestureFilter() }
filter.onPressStart = onPressStart
filter.setEnabled(enabled = enabled)
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/RawScaleGestureFilter.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/RawScaleGestureFilter.kt
index abf751b..b4c1257 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/RawScaleGestureFilter.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/RawScaleGestureFilter.kt
@@ -24,6 +24,7 @@
import androidx.compose.ui.input.pointer.PointerInputFilter
import androidx.compose.ui.input.pointer.consumeDownChange
import androidx.compose.ui.input.pointer.consumePositionChange
+import androidx.compose.ui.platform.debugInspectorInfo
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.util.fastForEach
@@ -119,7 +120,13 @@
fun Modifier.rawScaleGestureFilter(
scaleObserver: RawScaleObserver,
canStartScaling: (() -> Boolean)? = null
-): Modifier = composed {
+): Modifier = composed(
+ inspectorInfo = debugInspectorInfo {
+ name = "rawScaleGestureFilter"
+ properties["scaleObserver"] = scaleObserver
+ properties["canStartScaling"] = canStartScaling
+ }
+) {
val filter = remember { RawScaleGestureFilter() }
// TODO(b/129784010): Consider also allowing onStart, onScale, and onEnd to be set individually.
filter.scaleObserver = scaleObserver
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/ScaleGestureFilter.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/ScaleGestureFilter.kt
index 0e44d48..3c91cde 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/ScaleGestureFilter.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/ScaleGestureFilter.kt
@@ -19,6 +19,7 @@
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
+import androidx.compose.ui.platform.debugInspectorInfo
/**
* Observes various events sent by [scaleGestureFilter]. Implement and pass into
@@ -103,7 +104,12 @@
*/
fun Modifier.scaleGestureFilter(
scaleObserver: ScaleObserver
-): Modifier = composed {
+): Modifier = composed(
+ inspectorInfo = debugInspectorInfo {
+ name = "scaleGestureFilter"
+ properties["scaleObserver"] = scaleObserver
+ }
+) {
val glue = remember { TouchSlopScaleGestureDetectorGlue() }
glue.scaleObserver = scaleObserver
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/ScaleSlopExceededGestureFilter.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/ScaleSlopExceededGestureFilter.kt
index e7e1820..e3243d4 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/ScaleSlopExceededGestureFilter.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/ScaleSlopExceededGestureFilter.kt
@@ -24,6 +24,7 @@
import androidx.compose.ui.input.pointer.PointerInputFilter
import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed
import androidx.compose.ui.platform.DensityAmbient
+import androidx.compose.ui.platform.debugInspectorInfo
import androidx.compose.ui.unit.IntSize
import kotlin.math.absoluteValue
@@ -48,7 +49,12 @@
*/
fun Modifier.scaleSlopExceededGestureFilter(
onScaleSlopExceeded: () -> Unit
-): Modifier = composed {
+): Modifier = composed(
+ inspectorInfo = debugInspectorInfo {
+ name = "scaleSlopExceededGestureFilter"
+ properties["onScaleSlopExceeded"] = onScaleSlopExceeded
+ }
+) {
val scaleSlop = with(DensityAmbient.current) { ScaleSlop.toPx() }
val filter = remember { ScaleSlopExceededGestureFilter(scaleSlop) }
// TODO(b/129784010): Consider also allowing onStart, onScale, and onEnd to be set individually.
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/ScrollGestureFilter.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/ScrollGestureFilter.kt
index 0c0e60b..0e569c1 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/ScrollGestureFilter.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/ScrollGestureFilter.kt
@@ -23,6 +23,7 @@
import androidx.compose.ui.gesture.scrollorientationlocking.Orientation
import androidx.compose.ui.gesture.scrollorientationlocking.ScrollOrientationLocker
import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.platform.debugInspectorInfo
/**
* Defines the callbacks associated with scrolling.
@@ -117,7 +118,15 @@
orientation: Orientation,
canDrag: ((Direction) -> Boolean)? = null,
startDragImmediately: Boolean = false
-): Modifier = composed {
+): Modifier = composed(
+ inspectorInfo = debugInspectorInfo {
+ name = "scrollGestureFilter"
+ properties["scrollCallback"] = scrollCallback
+ properties["orientation"] = orientation
+ properties["canDrag"] = canDrag
+ properties["startDragImmediately"] = startDragImmediately
+ }
+) {
val coordinator = remember { ScrollGestureFilterCoordinator() }
coordinator.scrollCallback = scrollCallback
coordinator.orientation = orientation
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/DrawCache.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/DrawCache.kt
index 397678e..99a0886 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/DrawCache.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/DrawCache.kt
@@ -21,7 +21,7 @@
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
-import androidx.compose.ui.graphics.ImageAsset
+import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.drawscope.CanvasDrawScope
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.unit.Density
@@ -30,7 +30,7 @@
import androidx.compose.ui.unit.toSize
/**
- * Creates a drawing environment that directs its drawing commands to an [ImageAsset]
+ * Creates a drawing environment that directs its drawing commands to an [ImageBitmap]
* which can be drawn directly in another [DrawScope] instance. This is useful to cache
* complicated drawing commands across frames especially if the content has not changed.
* Additionally some drawing operations such as rendering paths are done purely in
@@ -39,7 +39,7 @@
*/
internal class DrawCache {
- @PublishedApi internal var cachedImage: ImageAsset? = null
+ @PublishedApi internal var mCachedImage: ImageBitmap? = null
private var cachedCanvas: Canvas? = null
private var scopeDensity: Density? = null
private var layoutDirection: LayoutDirection = LayoutDirection.Ltr
@@ -47,8 +47,8 @@
private val cacheScope = CanvasDrawScope()
/**
- * Draw the contents of the lambda with receiver scope into an [ImageAsset] with the provided
- * size. If the same size is provided across calls, the same [ImageAsset] instance is
+ * Draw the contents of the lambda with receiver scope into an [ImageBitmap] with the provided
+ * size. If the same size is provided across calls, the same [ImageBitmap] instance is
* re-used and the contents are cleared out before drawing content in it again
*/
fun drawCachedImage(
@@ -59,17 +59,17 @@
) {
this.scopeDensity = density
this.layoutDirection = layoutDirection
- var targetImage = cachedImage
+ var targetImage = mCachedImage
var targetCanvas = cachedCanvas
if (targetImage == null ||
targetCanvas == null ||
size.width > targetImage.width ||
size.height > targetImage.height
) {
- targetImage = ImageAsset(size.width, size.height)
+ targetImage = ImageBitmap(size.width, size.height)
targetCanvas = Canvas(targetImage)
- cachedImage = targetImage
+ mCachedImage = targetImage
cachedCanvas = targetCanvas
}
cacheScope.draw(density, layoutDirection, targetCanvas, size.toSize()) {
@@ -87,7 +87,7 @@
alpha: Float = 1.0f,
colorFilter: ColorFilter? = null
) {
- val targetImage = cachedImage
+ val targetImage = mCachedImage
check(targetImage != null) {
"drawCachedImage must be invoked first before attempting to draw the result " +
"into another destination"
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/key/KeyInputModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/key/KeyInputModifier.kt
index 40050aa..dc18697 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/key/KeyInputModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/key/KeyInputModifier.kt
@@ -19,6 +19,7 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.node.ModifiedKeyInputNode
+import androidx.compose.ui.platform.debugInspectorInfo
/**
* Adding this [modifier][Modifier] to the [modifier][Modifier] parameter of a component will
@@ -29,7 +30,12 @@
* false, the key event will be sent to this [keyInputFilter]'s parent.
*/
@ExperimentalKeyInput
-fun Modifier.keyInputFilter(onKeyEvent: (KeyEvent) -> Boolean): Modifier = composed {
+fun Modifier.keyInputFilter(onKeyEvent: (KeyEvent) -> Boolean): Modifier = composed(
+ inspectorInfo = debugInspectorInfo {
+ name = "keyInputFilter"
+ properties["onKeyEvent"] = onKeyEvent
+ }
+) {
KeyInputModifier(onKeyEvent = onKeyEvent, onPreviewKeyEvent = null)
}
@@ -44,7 +50,12 @@
* sent back up to the root [keyInputFilter] using the onKeyEvent callback.
*/
@ExperimentalKeyInput
-fun Modifier.previewKeyInputFilter(onPreviewKeyEvent: (KeyEvent) -> Boolean): Modifier = composed {
+fun Modifier.previewKeyInputFilter(onPreviewKeyEvent: (KeyEvent) -> Boolean): Modifier = composed(
+ inspectorInfo = debugInspectorInfo {
+ name = "previewKeyInputFilter"
+ properties["onPreviewKeyEvent"] = onPreviewKeyEvent
+ }
+) {
KeyInputModifier(onKeyEvent = null, onPreviewKeyEvent = onPreviewKeyEvent)
}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Layout.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Layout.kt
index 1b64b0c..3401337 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Layout.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Layout.kt
@@ -290,7 +290,7 @@
measuredSize = IntSize(width, height)
}
override fun get(line: AlignmentLine): Int = AlignmentLine.Unspecified
- override fun placeAt(position: IntOffset) { }
+ override fun placeAt(position: IntOffset, zIndex: Float) { }
}
/**
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/LayoutModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/LayoutModifier.kt
index 77d7ff5..e2146fe 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/LayoutModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/LayoutModifier.kt
@@ -228,7 +228,7 @@
measuredSize = IntSize(width, height)
}
override fun get(line: AlignmentLine): Int = AlignmentLine.Unspecified
- override fun placeAt(position: IntOffset) { }
+ override fun placeAt(position: IntOffset, zIndex: Float) { }
}
private enum class IntrinsicMinMax { Min, Max }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Placeable.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Placeable.kt
index 28297cd..62ecf52 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Placeable.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Placeable.kt
@@ -76,8 +76,12 @@
/**
* Positions the [Placeable] at [position] in its parent's coordinate system.
+ *
+ * @param zIndex controls the drawing order for the [Placeable]. A [Placeable] with larger
+ * [zIndex] will be drawn on top of all the children with smaller [zIndex]. When children
+ * have the same [zIndex] the order in which the items were placed is used.
*/
- protected abstract fun placeAt(position: IntOffset)
+ protected abstract fun placeAt(position: IntOffset, zIndex: Float)
/**
* The constraints used for the measurement made to obtain this [Placeable].
@@ -128,8 +132,13 @@
* If this method is used outside the [MeasureScope.layout] positioning block, the
* automatic position mirroring will not happen and the [Placeable] will be placed at the
* given [position], similar to the [place] method.
+ *
+ * @param zIndex controls the drawing order for the [Placeable]. A [Placeable] with larger
+ * [zIndex] will be drawn on top of all the children with smaller [zIndex]. When children
+ * have the same [zIndex] the order in which the items were placed is used.
*/
- fun Placeable.placeRelative(position: IntOffset) = placeAutoMirrored(position)
+ fun Placeable.placeRelative(position: IntOffset, zIndex: Float = 0f) =
+ placeAutoMirrored(position, zIndex)
/**
* Place a [Placeable] at [position] in its parent's coordinate system.
@@ -140,7 +149,11 @@
* automatic position mirroring will not happen and the [Placeable] will be placed at the
* given [position], similar to the [place] method.
*/
- fun Placeable.placeRelative(position: Offset) = placeAutoMirrored(position.round())
+ @Deprecated(
+ "Use the overloads with IntOffset instead",
+ ReplaceWith("placeRelative(position.round())", "androidx.compose.ui.unit.round")
+ )
+ fun Placeable.placeRelative(position: Offset) = placeRelative(position.round())
/**
* Place a [Placeable] at [x], [y] in its parent's coordinate system.
@@ -150,36 +163,53 @@
* If this method is used outside the [MeasureScope.layout] positioning block, the
* automatic position mirroring will not happen and the [Placeable] will be placed at the
* given position, similar to the [place] method.
+ *
+ * @param zIndex controls the drawing order for the [Placeable]. A [Placeable] with larger
+ * [zIndex] will be drawn on top of all the children with smaller [zIndex]. When children
+ * have the same [zIndex] the order in which the items were placed is used.
*/
- fun Placeable.placeRelative(x: Int, y: Int) = placeAutoMirrored(IntOffset(x, y))
+ fun Placeable.placeRelative(x: Int, y: Int, zIndex: Float = 0f) =
+ placeAutoMirrored(IntOffset(x, y), zIndex)
/**
* Place a [Placeable] at [position] in its parent's coordinate system.
* Unlike [placeRelative], the given [position] will not implicitly react in RTL layout direction
* contexts.
*/
+ @Deprecated(
+ "Use the overloads with IntOffset instead",
+ ReplaceWith("place(position.round())", "androidx.compose.ui.unit.round")
+ )
fun Placeable.place(position: Offset) = place(position.round())
/**
* Place a [Placeable] at [x], [y] in its parent's coordinate system.
* Unlike [placeRelative], the given position will not implicitly react in RTL layout direction
* contexts.
+ *
+ * @param zIndex controls the drawing order for the [Placeable]. A [Placeable] with larger
+ * [zIndex] will be drawn on top of all the children with smaller [zIndex]. When children
+ * have the same [zIndex] the order in which the items were placed is used.
*/
- fun Placeable.place(x: Int, y: Int) = place(IntOffset(x, y))
+ fun Placeable.place(x: Int, y: Int, zIndex: Float = 0f) = placeAt(IntOffset(x, y), zIndex)
/**
* Place a [Placeable] at [position] in its parent's coordinate system.
* Unlike [placeRelative], the given [position] will not implicitly react in RTL layout direction
* contexts.
+ *
+ * @param zIndex controls the drawing order for the [Placeable]. A [Placeable] with larger
+ * [zIndex] will be drawn on top of all the children with smaller [zIndex]. When children
+ * have the same [zIndex] the order in which the items were placed is used.
*/
- fun Placeable.place(position: IntOffset) =
- placeAt(position + apparentToRealOffset)
+ fun Placeable.place(position: IntOffset, zIndex: Float = 0f) =
+ placeAt(position + apparentToRealOffset, zIndex)
- private fun Placeable.placeAutoMirrored(position: IntOffset) {
+ private fun Placeable.placeAutoMirrored(position: IntOffset, zIndex: Float) {
if (parentLayoutDirection == LayoutDirection.Ltr || parentWidth == 0) {
- place(position)
+ place(position, zIndex)
} else {
- place(IntOffset(parentWidth - measuredSize.width - position.x, position.y))
+ place(IntOffset(parentWidth - measuredSize.width - position.x, position.y), zIndex)
}
}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DelegatingLayoutNodeWrapper.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DelegatingLayoutNodeWrapper.kt
index dc8701d..a60d0c2 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DelegatingLayoutNodeWrapper.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DelegatingLayoutNodeWrapper.kt
@@ -83,8 +83,9 @@
override fun get(line: AlignmentLine): Int = wrapped[line]
- override fun placeAt(position: IntOffset) {
+ override fun placeAt(position: IntOffset, zIndex: Float) {
this.position = position
+ this.zIndex = zIndex
// The wrapper only runs their placement block to obtain our position, which allows them
// to calculate the offset of an alignment line we have already provided a position for.
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/InnerPlaceable.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/InnerPlaceable.kt
index 19e75a9..57f0705 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/InnerPlaceable.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/InnerPlaceable.kt
@@ -105,8 +105,9 @@
)
}
- override fun placeAt(position: IntOffset) {
+ override fun placeAt(position: IntOffset, zIndex: Float) {
this.position = position
+ this.zIndex = zIndex
// The wrapper only runs their placement block to obtain our position, which allows them
// to calculate the offset of an alignment line we have already provided a position for.
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayerWrapper.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayerWrapper.kt
index 90fde06..0833a6a 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayerWrapper.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayerWrapper.kt
@@ -69,8 +69,8 @@
return placeable
}
- override fun placeAt(position: IntOffset) {
- super.placeAt(position)
+ override fun placeAt(position: IntOffset, zIndex: Float) {
+ super.placeAt(position, zIndex)
layer.move(position)
}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
index 421abd5..1b2604e 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
@@ -31,7 +31,6 @@
import androidx.compose.ui.layout.Placeable
import androidx.compose.ui.layout.Remeasurement
import androidx.compose.ui.layout.RemeasurementModifier
-import androidx.compose.ui.ZIndexModifier
import androidx.compose.ui.focus.ExperimentalFocus
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
@@ -207,6 +206,7 @@
instance.parent = this
_foldedChildren.add(index, instance)
+ zSortedChildrenInvalidated = true
if (instance.isVirtual) {
require(!isVirtual) { "Virtual LayoutNode can't be added into a virtual parent" }
@@ -232,6 +232,7 @@
val attached = owner != null
for (i in index + count - 1 downTo index) {
val child = _foldedChildren.removeAt(i)
+ zSortedChildrenInvalidated = true
if (DebugChanges) {
println("$child removed from $this at index $i")
}
@@ -261,6 +262,7 @@
child.parent = null
}
_foldedChildren.clear()
+ zSortedChildrenInvalidated = true
virtualChildrenCount = 0
invalidateUnfoldedVirtualChildren()
@@ -290,6 +292,7 @@
_foldedChildren.add(toIndex, child)
}
+ zSortedChildrenInvalidated = true
invalidateUnfoldedVirtualChildren()
requestRemeasure()
@@ -363,6 +366,7 @@
}
private val _zSortedChildren = mutableVectorOf<LayoutNode>()
+ private var zSortedChildrenInvalidated = true
/**
* Returns the children list sorted by their [LayoutNode.zIndex] first (smaller first) and the
@@ -375,9 +379,11 @@
@PublishedApi
internal val zSortedChildren: MutableVector<LayoutNode>
get() {
- _zSortedChildren.clear()
- _zSortedChildren.addAll(_children)
- _zSortedChildren.sortWith(ZComparator)
+ if (zSortedChildrenInvalidated) {
+ _zSortedChildren.clear()
+ _zSortedChildren.addAll(_children)
+ _zSortedChildren.sortWith(ZComparator)
+ }
return _zSortedChildren
}
@@ -616,22 +622,11 @@
/**
* zIndex defines the drawing order of the LayoutNode. Children with larger zIndex are drawn
- * after others (the original order is used for the nodes with the same zIndex).
- * Default zIndex is 0. We use sum of the values of all [ZIndexModifier] as a zIndex.
+ * on top of others (the original order is used for the nodes with the same zIndex).
+ * Default zIndex is 0. We use sum of the values passed as zIndex to place() by the
+ * parent layout and all the applied modifiers.
*/
- private val zIndex: Float
- get() = if (zIndexModifiers.isEmpty()) {
- 0f
- } else {
- zIndexModifiers.fold(0f) { acc, item ->
- acc + item.zIndex
- }
- }
-
- /**
- * All [ZIndexModifier]s added to the node.
- */
- private val zIndexModifiers = mutableVectorOf<ZIndexModifier>()
+ private var zIndex: Float = 0f
/**
* The inner-most layer wrapper. Used for performance for LayoutNodeWrapper.findLayer().
@@ -665,7 +660,6 @@
field = value
val invalidateParentLayer = shouldInvalidateParentLayer()
- val startZIndex = zIndex
copyWrappersToCache()
@@ -677,7 +671,6 @@
val addedCallback = hasNewPositioningCallback()
onPositionedCallbacks.clear()
onRemeasuredCallbacks.clear()
- zIndexModifiers.clear()
innerLayerWrapper = null
// Create a new chain of LayoutNodeWrappers, reusing existing ones from wrappers
@@ -690,9 +683,6 @@
if (mod is OnRemeasuredModifier) {
onRemeasuredCallbacks += mod
}
- if (mod is ZIndexModifier) {
- zIndexModifiers += mod
- }
if (mod is RemeasurementModifier) {
mod.onRemeasurementAvailable(this)
}
@@ -781,9 +771,7 @@
if (oldParentData != parentData) {
parent?.requestRemeasure()
}
- if (invalidateParentLayer || startZIndex != zIndex ||
- shouldInvalidateParentLayer()
- ) {
+ if (invalidateParentLayer || shouldInvalidateParentLayer()) {
parent?.invalidateLayer()
}
}
@@ -896,6 +884,16 @@
internal fun onNodePlaced() {
val parent = parent
+ var newZIndex = outerMeasurablePlaceable.lastZIndex + innerLayoutNodeWrapper.zIndex
+ forEachDelegate {
+ newZIndex += it.zIndex
+ }
+ if (newZIndex != zIndex) {
+ zIndex = newZIndex
+ zSortedChildrenInvalidated = true
+ parent?.invalidateLayer()
+ }
+
if (!isPlaced) {
isPlaced = true
// when the visibility of a child has been changed we need to invalidate
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt
index fe8c3f8..b71473c 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt
@@ -82,6 +82,9 @@
field = value
}
+ var zIndex: Float = 0f
+ protected set
+
override val parentCoordinates: LayoutCoordinates?
get() {
check(isAttached) { ExpectAttachedLayoutCoordinates }
@@ -127,7 +130,7 @@
/**
* Places the modified child.
*/
- abstract override fun placeAt(position: IntOffset)
+ abstract override fun placeAt(position: IntOffset, zIndex: Float)
/**
* Draws the content of the LayoutNode
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedLayoutNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedLayoutNode.kt
index 3b30a8f..dab373c 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedLayoutNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedLayoutNode.kt
@@ -67,7 +67,7 @@
}
// Place our wrapped to obtain their position inside ourselves.
isShallowPlacing = true
- placeAt(this.position)
+ placeAt(this.position, zIndex = 0f)
isShallowPlacing = false
return if (line is HorizontalAlignmentLine) {
positionInWrapped + wrapped.position.y
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OuterMeasurablePlaceable.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OuterMeasurablePlaceable.kt
index d806e34..79f7564 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OuterMeasurablePlaceable.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OuterMeasurablePlaceable.kt
@@ -46,6 +46,9 @@
override var parentData: Any? = null
private set
+ var lastZIndex: Float = 0f
+ private set
+
/**
* The function to be executed when the parent layout measures its children.
*/
@@ -109,8 +112,9 @@
override fun get(line: AlignmentLine): Int = outerWrapper[line]
- override fun placeAt(position: IntOffset) {
+ override fun placeAt(position: IntOffset, zIndex: Float) {
lastPosition = position
+ lastZIndex = zIndex
with(PlacementScope) {
outerWrapper.place(position)
}
@@ -120,7 +124,7 @@
* Calls [placeAt] with the same position used during the last [placeAt] call
*/
fun replace() {
- placeAt(checkNotNull(lastPosition))
+ placeAt(checkNotNull(lastPosition), lastZIndex)
}
override fun minIntrinsicWidth(height: Int): Int = outerWrapper.minIntrinsicWidth(height)
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/res/ImageResources.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/res/ImageResources.kt
index 2fe8874..7cc8516 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/res/ImageResources.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/res/ImageResources.kt
@@ -18,7 +18,7 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
-import androidx.compose.ui.graphics.ImageAsset
+import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.imageFromResource
/**
@@ -30,6 +30,6 @@
* @return the decoded image data associated with the resource
*/
@Composable
-fun imageResource(path: String): ImageAsset {
+fun imageResource(path: String): ImageBitmap {
return remember(path) { imageFromResource(path) }
}
\ No newline at end of file
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/FocusObserverModifierTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/FocusObserverModifierTest.kt
new file mode 100644
index 0000000..682eed0
--- /dev/null
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/FocusObserverModifierTest.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2020 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.ui
+
+import androidx.compose.ui.focus.ExperimentalFocus
+import androidx.compose.ui.focus.FocusState
+import androidx.compose.ui.platform.InspectableValue
+import androidx.compose.ui.platform.ValueElement
+import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+
+class FocusObserverModifierTest {
+ @Before
+ fun before() {
+ isDebugInspectorInfoEnabled = true
+ }
+
+ @After
+ fun after() {
+ isDebugInspectorInfoEnabled = false
+ }
+
+ @OptIn(ExperimentalFocus::class)
+ @Test
+ fun testInspectorValue() {
+ val onFocusChange: (FocusState) -> Unit = {}
+ val modifier = Modifier.focusObserver(onFocusChange) as InspectableValue
+ assertThat(modifier.nameFallback).isEqualTo("focusObserver")
+ assertThat(modifier.valueOverride).isNull()
+ assertThat(modifier.inspectableElements.asIterable()).containsExactly(
+ ValueElement("onFocusChange", onFocusChange)
+ )
+ }
+}
\ No newline at end of file
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/FocusRequesterModifierTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/FocusRequesterModifierTest.kt
new file mode 100644
index 0000000..d54d1d1
--- /dev/null
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/FocusRequesterModifierTest.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2020 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.ui
+
+import androidx.compose.ui.focus.ExperimentalFocus
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.platform.InspectableValue
+import androidx.compose.ui.platform.ValueElement
+import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+
+class FocusRequesterModifierTest {
+ @Before
+ fun before() {
+ isDebugInspectorInfoEnabled = true
+ }
+
+ @After
+ fun after() {
+ isDebugInspectorInfoEnabled = false
+ }
+
+ @OptIn(ExperimentalFocus::class)
+ @Test
+ fun testInspectorValue() {
+ val focusRequester = FocusRequester()
+ val modifier = Modifier.focusRequester(focusRequester) as InspectableValue
+ assertThat(modifier.nameFallback).isEqualTo("focusRequester")
+ assertThat(modifier.valueOverride).isNull()
+ assertThat(modifier.inspectableElements.asIterable()).containsExactly(
+ ValueElement("focusRequester", focusRequester)
+ )
+ }
+}
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/input/TextInputServiceAndroidTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/input/TextInputServiceAndroidTest.kt
index 7dd46f2..3d7118f 100644
--- a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/input/TextInputServiceAndroidTest.kt
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/input/TextInputServiceAndroidTest.kt
@@ -22,17 +22,19 @@
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import androidx.compose.ui.text.ExperimentalTextApi
+import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.ImeAction
-import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.input.ImeOptions
+import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.input.TextInputServiceAndroid
import com.nhaarman.mockitokotlin2.eq
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.whenever
-import org.junit.Assert.assertTrue
+import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -638,4 +640,37 @@
assertTrue((InputType.TYPE_TEXT_FLAG_AUTO_CORRECT and info.inputType) == 0)
}
}
+
+ @Test
+ fun initial_default_selection_info_is_set() {
+ textInputService.startInput(
+ value = TextFieldValue(),
+ imeOptions = ImeOptions.Default,
+ onEditCommand = {},
+ onImeActionPerformed = {}
+ )
+
+ EditorInfo().let { info ->
+ textInputService.createInputConnection(info)
+ assertEquals(info.initialSelStart, 0)
+ assertEquals(info.initialSelEnd, 0)
+ }
+ }
+
+ @Test
+ fun initial_selection_info_is_set() {
+ val selection = TextRange(1, 2)
+ textInputService.startInput(
+ value = TextFieldValue("abc", selection),
+ imeOptions = ImeOptions.Default,
+ onEditCommand = {},
+ onImeActionPerformed = {}
+ )
+
+ EditorInfo().let { info ->
+ textInputService.createInputConnection(info)
+ assertEquals(info.initialSelStart, selection.start)
+ assertEquals(info.initialSelEnd, selection.end)
+ }
+ }
}
\ No newline at end of file
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/input/key/KeyInputModifierTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/input/key/KeyInputModifierTest.kt
new file mode 100644
index 0000000..d072a85
--- /dev/null
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/input/key/KeyInputModifierTest.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2020 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.ui.input.key
+
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.InspectableValue
+import androidx.compose.ui.platform.ValueElement
+import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+
+class KeyInputModifierTest {
+ @Before
+ fun before() {
+ isDebugInspectorInfoEnabled = true
+ }
+
+ @After
+ fun after() {
+ isDebugInspectorInfoEnabled = false
+ }
+
+ @OptIn(ExperimentalKeyInput::class)
+ @Test
+ fun testInspectorValueForKeyInputFilter() {
+ val onKeyEvent: (KeyEvent) -> Boolean = { true }
+ val modifier = Modifier.keyInputFilter(onKeyEvent) as InspectableValue
+ assertThat(modifier.nameFallback).isEqualTo("keyInputFilter")
+ assertThat(modifier.valueOverride).isNull()
+ assertThat(modifier.inspectableElements.asIterable()).containsExactly(
+ ValueElement("onKeyEvent", onKeyEvent)
+ )
+ }
+
+ @OptIn(ExperimentalKeyInput::class)
+ @Test
+ fun testInspectorValueForPreviewKeyInputFilter() {
+ val onPreviewKeyEvent: (KeyEvent) -> Boolean = { true }
+ val modifier = Modifier.previewKeyInputFilter(onPreviewKeyEvent) as InspectableValue
+ assertThat(modifier.nameFallback).isEqualTo("previewKeyInputFilter")
+ assertThat(modifier.valueOverride).isNull()
+ assertThat(modifier.inspectableElements.asIterable()).containsExactly(
+ ValueElement("onPreviewKeyEvent", onPreviewKeyEvent)
+ )
+ }
+}
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt
index ec5f1c7..6b57e64 100644
--- a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt
@@ -18,8 +18,6 @@
import androidx.compose.ui.ContentDrawScope
import androidx.compose.ui.DrawLayerModifier
import androidx.compose.ui.DrawModifier
-import androidx.compose.ui.layout.Measurable
-import androidx.compose.ui.layout.MeasureScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.autofill.Autofill
import androidx.compose.ui.autofill.AutofillTree
@@ -35,7 +33,9 @@
import androidx.compose.ui.input.key.KeyEvent
import androidx.compose.ui.input.pointer.PointerInputFilter
import androidx.compose.ui.input.pointer.PointerInputModifier
+import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
import androidx.compose.ui.layout.positionInRoot
import androidx.compose.ui.platform.ClipboardManager
import androidx.compose.ui.platform.TextToolbar
@@ -65,7 +65,6 @@
import org.junit.rules.ExpectedException
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
-import org.mockito.Mockito.times
@RunWith(JUnit4::class)
@OptIn(ExperimentalLayoutNodeApi::class)
@@ -1576,7 +1575,11 @@
val parent = LayoutNode(
0, 0, 2, 2
).apply {
- attach(MockOwner())
+ attach(
+ MockOwner().apply {
+ measureIteration = 1L
+ }
+ )
}
parent.insertAt(
0,
@@ -1596,6 +1599,8 @@
)
)
)
+ parent.remeasure()
+ parent.replace()
val hit = mutableListOf<PointerInputFilter>()
@@ -1703,9 +1708,11 @@
override fun onRequestMeasure(layoutNode: LayoutNode) {
onRequestMeasureParams += layoutNode
+ layoutNode.layoutState = LayoutNode.LayoutState.NeedsRemeasure
}
override fun onRequestRelayout(layoutNode: LayoutNode) {
+ layoutNode.layoutState = LayoutNode.LayoutState.NeedsRelayout
}
override val hasPendingMeasureOrLayout = false
@@ -1794,7 +1801,7 @@
override fun onSemanticsChange() {
}
- override val measureIteration: Long = 0
+ override var measureIteration: Long = 0
override val viewConfiguration: ViewConfiguration
get() = TODO("Not yet implemented")
}
@@ -1809,7 +1816,9 @@
measurables: List<Measurable>,
constraints: Constraints
): MeasureResult =
- measureScope.layout(x2 - x, y2 - y) {}
+ measureScope.layout(x2 - x, y2 - y) {
+ measurables.forEach { it.measure(constraints).place(0, 0) }
+ }
}
attach(MockOwner())
layoutState = LayoutNode.LayoutState.NeedsRemeasure
diff --git a/core/core-appdigest/build.gradle b/core/core-appdigest/build.gradle
index 1625b7e..8270040 100644
--- a/core/core-appdigest/build.gradle
+++ b/core/core-appdigest/build.gradle
@@ -32,8 +32,8 @@
}
dependencies {
- api("androidx.annotation:annotation:1.1.0")
- implementation project(path: ':core:core')
+ api("androidx.annotation:annotation:1.0.0")
+ api("androidx.core:core:1.0.0")
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
androidTestImplementation(ANDROIDX_TEST_CORE)
androidTestImplementation(ANDROIDX_TEST_RUNNER)
diff --git a/docs-public/build.gradle b/docs-public/build.gradle
index c8aa644..90dc4a5 100644
--- a/docs-public/build.gradle
+++ b/docs-public/build.gradle
@@ -202,8 +202,6 @@
stubs(fileTree(dir: '../wear/wear_stubs/', include: ['com.google.android.wearable-stubs.jar']))
docs("androidx.wear:wear-complications-data:1.0.0-alpha02")
docs("androidx.wear:wear-complications-provider:1.0.0-alpha02")
- docs("androidx.wear:wear-tiles:1.0.0-alpha01")
- docs("androidx.wear:wear-tiles-data:1.0.0-alpha01")
docs("androidx.wear:wear-watchface:1.0.0-alpha02")
docs("androidx.wear:wear-watchface-client:1.0.0-alpha02")
docs("androidx.wear:wear-watchface-complications-rendering:1.0.0-alpha02")
diff --git a/docs-tip-of-tree/build.gradle b/docs-tip-of-tree/build.gradle
index b26415e..ec1c352 100644
--- a/docs-tip-of-tree/build.gradle
+++ b/docs-tip-of-tree/build.gradle
@@ -247,3 +247,10 @@
docs(project(":work:work-rxjava3"))
docs(project(":work:work-testing"))
}
+
+afterEvaluate {
+ // b/172817047 : exploratory workaround to avoid running two dokkaKotlinDocs tasks at once
+ tasks["dokkaKotlinDocs"].configure { task ->
+ task.mustRunAfter(":docs-public:dokkaKotlinDocs")
+ }
+}
diff --git a/leanback/leanback/lint-baseline.xml b/leanback/leanback/lint-baseline.xml
index 05b53f7..7989b71 100644
--- a/leanback/leanback/lint-baseline.xml
+++ b/leanback/leanback/lint-baseline.xml
@@ -2,534 +2,6 @@
<issues format="5" by="lint 4.2.0-alpha15" client="gradle" variant="debug" version="4.2.0-alpha15">
<issue
- id="CustomViewStyleable"
- message="By convention, the custom view (`BaseCardView`) and the declare-styleable (`lbBaseCardView`) should have the same name (various editor features rely on this convention)"
- errorLine1=" TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbBaseCardView,"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/leanback/widget/BaseCardView.java"
- line="159"
- column="62"/>
- </issue>
-
- <issue
- id="CustomViewStyleable"
- message="By convention, the declare-styleable (`lbBaseCardView_Layout`) for a layout parameter class (`LayoutParams`) is expected to be the surrounding class (`BaseCardView`) plus "`_Layout`", e.g. `BaseCardView_Layout`. (Various editor features rely on this convention.)"
- errorLine1=" TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.lbBaseCardView_Layout);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/leanback/widget/BaseCardView.java"
- line="866"
- column="60"/>
- </issue>
-
- <issue
- id="CustomViewStyleable"
- message="By convention, the custom view (`BaseGridView`) and the declare-styleable (`lbBaseGridView`) should have the same name (various editor features rely on this convention)"
- errorLine1=" TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbBaseGridView);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/leanback/widget/BaseGridView.java"
- line="285"
- column="62"/>
- </issue>
-
- <issue
- id="CustomViewStyleable"
- message="By convention, the custom view (`DatePicker`) and the declare-styleable (`lbDatePicker`) should have the same name (various editor features rely on this convention)"
- errorLine1=" R.styleable.lbDatePicker);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/leanback/widget/picker/DatePicker.java"
- line="78"
- column="17"/>
- </issue>
-
- <issue
- id="CustomViewStyleable"
- message="By convention, the custom view (`HorizontalGridView`) and the declare-styleable (`lbHorizontalGridView`) should have the same name (various editor features rely on this convention)"
- errorLine1=" TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbHorizontalGridView);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/leanback/widget/HorizontalGridView.java"
- line="84"
- column="62"/>
- </issue>
-
- <issue
- id="CustomViewStyleable"
- message="By convention, the custom view (`ImageCardView`) and the declare-styleable (`lbImageCardView`) should have the same name (various editor features rely on this convention)"
- errorLine1=" R.styleable.lbImageCardView, defStyleAttr, defStyle);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/leanback/widget/ImageCardView.java"
- line="173"
- column="17"/>
- </issue>
-
- <issue
- id="CustomViewStyleable"
- message="By convention, the custom view (`Picker`) and the declare-styleable (`lbPicker`) should have the same name (various editor features rely on this convention)"
- errorLine1=" attrs, R.styleable.lbPicker, defStyleAttr, 0);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/leanback/widget/picker/Picker.java"
- line="196"
- column="24"/>
- </issue>
-
- <issue
- id="CustomViewStyleable"
- message="By convention, the custom view (`PinPicker`) and the declare-styleable (`lbPinPicker`) should have the same name (various editor features rely on this convention)"
- errorLine1=" attrs, R.styleable.lbPinPicker, defStyleAttr, 0);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/leanback/widget/picker/PinPicker.java"
- line="47"
- column="24"/>
- </issue>
-
- <issue
- id="CustomViewStyleable"
- message="By convention, the custom view (`ResizingTextView`) and the declare-styleable (`lbResizingTextView`) should have the same name (various editor features rely on this convention)"
- errorLine1=" TypedArray a = ctx.obtainStyledAttributes(attrs, R.styleable.lbResizingTextView,"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/leanback/widget/ResizingTextView.java"
- line="58"
- column="58"/>
- </issue>
-
- <issue
- id="CustomViewStyleable"
- message="By convention, the custom view (`SearchOrbView`) and the declare-styleable (`lbSearchOrbView`) should have the same name (various editor features rely on this convention)"
- errorLine1=" TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbSearchOrbView,"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/leanback/widget/SearchOrbView.java"
- line="185"
- column="62"/>
- </issue>
-
- <issue
- id="CustomViewStyleable"
- message="By convention, the custom view (`TimePicker`) and the declare-styleable (`lbTimePicker`) should have the same name (various editor features rely on this convention)"
- errorLine1=" R.styleable.lbTimePicker);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/leanback/widget/picker/TimePicker.java"
- line="110"
- column="17"/>
- </issue>
-
- <issue
- id="CustomViewStyleable"
- message="By convention, the custom view (`VerticalGridView`) and the declare-styleable (`lbVerticalGridView`) should have the same name (various editor features rely on this convention)"
- errorLine1=" TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbVerticalGridView);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/leanback/widget/VerticalGridView.java"
- line="60"
- column="62"/>
- </issue>
-
- <issue
- id="DefaultLocale"
- message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
- errorLine1=" String dateFieldsPattern = datePickerFormat.toUpperCase();"
- errorLine2=" ~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/leanback/widget/picker/DatePicker.java"
- line="244"
- column="53"/>
- </issue>
-
- <issue
- id="DefaultLocale"
- message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
- errorLine1=" timeFieldsPattern = timeFieldsPattern.toUpperCase();"
- errorLine2=" ~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/leanback/widget/picker/TimePicker.java"
- line="281"
- column="47"/>
- </issue>
-
- <issue
- id="InlinedApi"
- message="Field requires API level 26 (current min is 17): `android.view.View#AUTOFILL_TYPE_TEXT`"
- errorLine1=" return AUTOFILL_TYPE_TEXT;"
- errorLine2=" ~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/leanback/widget/GuidedActionEditText.java"
- line="133"
- column="16"/>
- </issue>
-
- <issue
- id="SimpleDateFormat"
- message="To get local formatting use `getDateInstance()`, `getDateTimeInstance()`, or `getTimeInstance()`, or use `new SimpleDateFormat(String template, Locale locale)` with for example `Locale.US` for ASCII dates."
- errorLine1=" private final DateFormat mDateFormat = new SimpleDateFormat(DATE_FORMAT);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/leanback/widget/picker/DatePicker.java"
- line="60"
- column="44"/>
- </issue>
-
- <issue
- id="UnusedAttribute"
- message="Attribute `elevation` is only used in API level 21 and higher (current min is 17)"
- errorLine1=" android:elevation="@dimen/lb_details_overview_z""
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/res/layout/lb_details_overview.xml"
- line="37"
- column="9"/>
- </issue>
-
- <issue
- id="UnusedAttribute"
- message="Attribute `elevation` is only used in API level 21 and higher (current min is 17)"
- errorLine1=" android:elevation="@dimen/lb_details_overview_z""
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/res/layout/lb_fullwidth_details_overview.xml"
- line="38"
- column="9"/>
- </issue>
-
- <issue
- id="UnusedAttribute"
- message="Attribute `elevation` is only used in API level 21 and higher (current min is 17)"
- errorLine1=" android:elevation="@dimen/lb_details_overview_z""
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/res/layout/lb_fullwidth_details_overview_logo.xml"
- line="23"
- column="5"/>
- </issue>
-
- <issue
- id="UnusedAttribute"
- message="Attribute `transitionName` is only used in API level 21 and higher (current min is 17)"
- errorLine1=" android:transitionName="guidedactions_root""
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/res/layout/lb_guidedactions.xml"
- line="20"
- column="5"/>
- </issue>
-
- <issue
- id="UnusedAttribute"
- message="Attribute `transitionGroup` is only used in API level 21 and higher (current min is 17)"
- errorLine1=" android:transitionGroup="false""
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/res/layout/lb_guidedactions.xml"
- line="21"
- column="5"/>
- </issue>
-
- <issue
- id="UnusedAttribute"
- message="Attribute `transitionName` is only used in API level 21 and higher (current min is 17)"
- errorLine1=" android:transitionName="guidedactions_list_background""
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/res/layout/lb_guidedactions.xml"
- line="28"
- column="9"/>
- </issue>
-
- <issue
- id="UnusedAttribute"
- message="Attribute `transitionName` is only used in API level 21 and higher (current min is 17)"
- errorLine1=" android:transitionName="guidedactions_content""
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/res/layout/lb_guidedactions.xml"
- line="38"
- column="9"/>
- </issue>
-
- <issue
- id="UnusedAttribute"
- message="Attribute `transitionGroup` is only used in API level 21 and higher (current min is 17)"
- errorLine1=" android:transitionGroup="false""
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/res/layout/lb_guidedactions.xml"
- line="39"
- column="9"/>
- </issue>
-
- <issue
- id="UnusedAttribute"
- message="Attribute `transitionGroup` is only used in API level 21 and higher (current min is 17)"
- errorLine1=" android:transitionGroup="true""
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/res/layout/lb_guidedactions.xml"
- line="43"
- column="13"/>
- </issue>
-
- <issue
- id="UnusedAttribute"
- message="Attribute `transitionName` is only used in API level 21 and higher (current min is 17)"
- errorLine1=" android:transitionName="guidedactions_sub_list_background""
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/res/layout/lb_guidedactions.xml"
- line="48"
- column="13"/>
- </issue>
-
- <issue
- id="UnusedAttribute"
- message="Attribute `transitionGroup` is only used in API level 21 and higher (current min is 17)"
- errorLine1=" android:transitionGroup="true""
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/res/layout/lb_guidedactions.xml"
- line="58"
- column="13"/>
- </issue>
-
- <issue
- id="UnusedAttribute"
- message="Attribute `transitionName` is only used in API level 21 and higher (current min is 17)"
- errorLine1=" android:transitionName="guidedactions_root2""
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/res/layout/lb_guidedbuttonactions.xml"
- line="20"
- column="5"/>
- </issue>
-
- <issue
- id="UnusedAttribute"
- message="Attribute `transitionGroup` is only used in API level 21 and higher (current min is 17)"
- errorLine1=" android:transitionGroup="false""
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/res/layout/lb_guidedbuttonactions.xml"
- line="21"
- column="5"/>
- </issue>
-
- <issue
- id="UnusedAttribute"
- message="Attribute `transitionName` is only used in API level 21 and higher (current min is 17)"
- errorLine1=" android:transitionName="guidedactions_list_background2""
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/res/layout/lb_guidedbuttonactions.xml"
- line="28"
- column="9"/>
- </issue>
-
- <issue
- id="UnusedAttribute"
- message="Attribute `transitionName` is only used in API level 21 and higher (current min is 17)"
- errorLine1=" android:transitionName="guidedactions_content2""
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/res/layout/lb_guidedbuttonactions.xml"
- line="35"
- column="9"/>
- </issue>
-
- <issue
- id="UnusedAttribute"
- message="Attribute `transitionGroup` is only used in API level 21 and higher (current min is 17)"
- errorLine1=" android:transitionGroup="false""
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/res/layout/lb_guidedbuttonactions.xml"
- line="36"
- column="9"/>
- </issue>
-
- <issue
- id="UnusedAttribute"
- message="Attribute `transitionGroup` is only used in API level 21 and higher (current min is 17)"
- errorLine1=" android:transitionGroup="true""
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/res/layout/lb_guidedbuttonactions.xml"
- line="40"
- column="13"/>
- </issue>
-
- <issue
- id="UnusedAttribute"
- message="Attribute `transitionName` is only used in API level 21 and higher (current min is 17)"
- errorLine1=" android:transitionName="guidedstep_background""
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/res/layout/lb_guidedstep_background.xml"
- line="20"
- column="5"/>
- </issue>
-
- <issue
- id="UnusedAttribute"
- message="Attribute `transitionName` is only used in API level 21 and higher (current min is 17)"
- errorLine1=" android:transitionName="action_fragment_root""
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/res/layout/lb_guidedstep_fragment.xml"
- line="48"
- column="17"/>
- </issue>
-
- <issue
- id="UnusedAttribute"
- message="Attribute `transitionGroup` is only used in API level 21 and higher (current min is 17)"
- errorLine1=" android:transitionGroup="false""
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/res/layout/lb_guidedstep_fragment.xml"
- line="49"
- column="17"/>
- </issue>
-
- <issue
- id="UnusedAttribute"
- message="Attribute `transitionName` is only used in API level 21 and higher (current min is 17)"
- errorLine1=" android:transitionName="action_fragment_background""
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/res/layout/lb_guidedstep_fragment.xml"
- line="60"
- column="21"/>
- </issue>
-
- <issue
- id="UnusedAttribute"
- message="Attribute `outlineProvider` is only used in API level 21 and higher (current min is 17)"
- errorLine1=" android:outlineProvider="paddedBounds""
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/res/layout/lb_guidedstep_fragment.xml"
- line="62"
- column="21"/>
- </issue>
-
- <issue
- id="UnusedAttribute"
- message="Attribute `elevation` is only used in API level 21 and higher (current min is 17)"
- errorLine1=" android:elevation="?attr/guidedActionsElevation" />"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/res/layout/lb_guidedstep_fragment.xml"
- line="66"
- column="21"/>
- </issue>
-
- <issue
- id="UnusedAttribute"
- message="Attribute `transitionName` is only used in API level 21 and higher (current min is 17)"
- errorLine1=" android:transitionName="action_fragment""
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/res/layout/lb_guidedstep_fragment.xml"
- line="72"
- column="21"/>
- </issue>
-
- <issue
- id="UnusedAttribute"
- message="Attribute `transitionGroup` is only used in API level 21 and higher (current min is 17)"
- errorLine1=" android:transitionGroup="false""
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/res/layout/lb_guidedstep_fragment.xml"
- line="73"
- column="21"/>
- </issue>
-
- <issue
- id="UnusedAttribute"
- message="Attribute `elevation` is only used in API level 21 and higher (current min is 17)"
- errorLine1=" android:elevation="?attr/guidedActionsElevation" />"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/res/layout/lb_guidedstep_fragment.xml"
- line="77"
- column="21"/>
- </issue>
-
- <issue
- id="UnusedAttribute"
- message="Attribute `transitionGroup` is only used in API level 21 and higher (current min is 17)"
- errorLine1=" android:transitionGroup="true""
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/res/layout/lb_headers_fragment.xml"
- line="20"
- column="5"/>
- </issue>
-
- <issue
- id="UnusedAttribute"
- message="Attribute `elevation` is only used in API level 21 and higher (current min is 17)"
- errorLine1=" android:elevation="@dimen/lb_browse_headers_z""
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/res/layout/lb_headers_fragment.xml"
- line="24"
- column="5"/>
- </issue>
-
- <issue
- id="UnusedAttribute"
- message="Attribute `transitionGroup` is only used in API level 21 and higher (current min is 17)"
- errorLine1=" android:transitionGroup="false""
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/res/layout/lb_playback_fragment.xml"
- line="21"
- column="9"/>
- </issue>
-
- <issue
- id="UnusedAttribute"
- message="Attribute `transitionGroup` is only used in API level 21 and higher (current min is 17)"
- errorLine1=" android:transitionGroup="false""
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/res/layout/lb_playback_fragment.xml"
- line="26"
- column="9"/>
- </issue>
-
- <issue
- id="UnusedAttribute"
- message="Attribute `transitionGroup` is only used in API level 21 and higher (current min is 17)"
- errorLine1=" android:transitionGroup="true""
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/res/layout/lb_playback_fragment.xml"
- line="32"
- column="13"/>
- </issue>
-
- <issue
- id="UnusedAttribute"
- message="Attribute `transitionGroup` is only used in API level 21 and higher (current min is 17)"
- errorLine1=" android:transitionGroup="true""
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/res/layout/lb_title_view.xml"
- line="41"
- column="9"/>
- </issue>
-
- <issue
id="PrivateConstructorForUtilityClass"
message="Utility class with non private constructor"
errorLine1="public class FocusHighlightHelper {"
@@ -569,7 +41,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/picker/DatePicker.java"
- line="142"
+ line="144"
column="58"/>
</issue>
@@ -943,7 +415,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/picker/TimePicker.java"
- line="157"
+ line="159"
column="38"/>
</issue>
@@ -1625,7 +1097,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/layout/lb_guidedstep_background.xml"
- line="23"
+ line="25"
column="5"/>
</issue>
@@ -1636,7 +1108,7 @@
errorLine2=" ~~~~~~~~~~~">
<location
file="src/main/res/layout/lb_details_overview.xml"
- line="32"
+ line="33"
column="6"/>
</issue>
@@ -1647,7 +1119,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/res/layout/lb_details_overview.xml"
- line="41"
+ line="43"
column="6"/>
</issue>
@@ -1669,7 +1141,7 @@
errorLine2=" ~~~~~~~~~~~">
<location
file="src/main/res/layout/lb_fullwidth_details_overview.xml"
- line="31"
+ line="32"
column="6"/>
</issue>
@@ -1680,7 +1152,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/res/layout/lb_fullwidth_details_overview.xml"
- line="41"
+ line="43"
column="6"/>
</issue>
@@ -1691,7 +1163,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/res/layout/lb_guidedstep_fragment.xml"
- line="33"
+ line="34"
column="10"/>
</issue>
@@ -1734,7 +1206,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/GuidedActionEditText.java"
- line="160"
+ line="162"
column="20"/>
</issue>
@@ -1789,7 +1261,7 @@
errorLine2=" ~~~~~~~~~">
<location
file="src/main/res/layout/lb_details_overview.xml"
- line="48"
+ line="50"
column="10"/>
</issue>
@@ -1976,7 +1448,7 @@
errorLine2=" ~~~~~~~~~">
<location
file="src/main/res/layout/lb_title_view.xml"
- line="19"
+ line="20"
column="6"/>
</issue>
@@ -2094,7 +1566,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/picker/TimePicker.java"
- line="434"
+ line="436"
column="20"/>
</issue>
@@ -2116,7 +1588,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/GuidedActionAdapter.java"
- line="144"
+ line="145"
column="74"/>
</issue>
@@ -3051,7 +2523,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/BaseCardView.java"
- line="148"
+ line="149"
column="25"/>
</issue>
@@ -3062,7 +2534,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/BaseCardView.java"
- line="152"
+ line="153"
column="25"/>
</issue>
@@ -3073,7 +2545,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/BaseCardView.java"
- line="152"
+ line="153"
column="42"/>
</issue>
@@ -3084,7 +2556,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/BaseCardView.java"
- line="156"
+ line="158"
column="25"/>
</issue>
@@ -3095,7 +2567,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/BaseCardView.java"
- line="156"
+ line="158"
column="42"/>
</issue>
@@ -3106,7 +2578,7 @@
errorLine2=" ~~~~~">
<location
file="src/main/java/androidx/leanback/widget/BaseCardView.java"
- line="598"
+ line="600"
column="15"/>
</issue>
@@ -3117,7 +2589,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/BaseCardView.java"
- line="819"
+ line="821"
column="12"/>
</issue>
@@ -3128,7 +2600,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/BaseCardView.java"
- line="819"
+ line="821"
column="46"/>
</issue>
@@ -3139,7 +2611,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/BaseCardView.java"
- line="824"
+ line="826"
column="15"/>
</issue>
@@ -3150,7 +2622,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/BaseCardView.java"
- line="830"
+ line="832"
column="15"/>
</issue>
@@ -3161,7 +2633,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/BaseCardView.java"
- line="830"
+ line="832"
column="49"/>
</issue>
@@ -3172,7 +2644,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/BaseCardView.java"
- line="839"
+ line="841"
column="41"/>
</issue>
@@ -3183,7 +2655,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/BaseCardView.java"
- line="864"
+ line="867"
column="29"/>
</issue>
@@ -3194,7 +2666,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/BaseCardView.java"
- line="864"
+ line="867"
column="40"/>
</issue>
@@ -3205,7 +2677,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/BaseCardView.java"
- line="884"
+ line="887"
column="29"/>
</issue>
@@ -3216,7 +2688,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/BaseCardView.java"
- line="894"
+ line="897"
column="29"/>
</issue>
@@ -3315,7 +2787,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/BaseGridView.java"
- line="663"
+ line="664"
column="43"/>
</issue>
@@ -3326,7 +2798,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/BaseGridView.java"
- line="676"
+ line="677"
column="44"/>
</issue>
@@ -3337,7 +2809,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/BaseGridView.java"
- line="708"
+ line="709"
column="54"/>
</issue>
@@ -3348,7 +2820,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/BaseGridView.java"
- line="720"
+ line="721"
column="54"/>
</issue>
@@ -3359,7 +2831,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/BaseGridView.java"
- line="729"
+ line="730"
column="57"/>
</issue>
@@ -3370,7 +2842,7 @@
errorLine2=" ~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/BaseGridView.java"
- line="794"
+ line="795"
column="69"/>
</issue>
@@ -3381,7 +2853,7 @@
errorLine2=" ~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/BaseGridView.java"
- line="820"
+ line="821"
column="63"/>
</issue>
@@ -3392,7 +2864,7 @@
errorLine2=" ~~~~">
<location
file="src/main/java/androidx/leanback/widget/BaseGridView.java"
- line="899"
+ line="900"
column="63"/>
</issue>
@@ -3403,7 +2875,7 @@
errorLine2=" ~~~~">
<location
file="src/main/java/androidx/leanback/widget/BaseGridView.java"
- line="915"
+ line="916"
column="40"/>
</issue>
@@ -3414,7 +2886,7 @@
errorLine2=" ~~~~~">
<location
file="src/main/java/androidx/leanback/widget/BaseGridView.java"
- line="915"
+ line="916"
column="51"/>
</issue>
@@ -3425,7 +2897,7 @@
errorLine2=" ~~~~">
<location
file="src/main/java/androidx/leanback/widget/BaseGridView.java"
- line="929"
+ line="930"
column="12"/>
</issue>
@@ -3436,7 +2908,7 @@
errorLine2=" ~~~~">
<location
file="src/main/java/androidx/leanback/widget/BaseGridView.java"
- line="943"
+ line="944"
column="69"/>
</issue>
@@ -3447,7 +2919,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/BaseGridView.java"
- line="1039"
+ line="1040"
column="45"/>
</issue>
@@ -3458,7 +2930,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/BaseGridView.java"
- line="1047"
+ line="1048"
column="46"/>
</issue>
@@ -3469,7 +2941,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/BaseGridView.java"
- line="1055"
+ line="1056"
column="43"/>
</issue>
@@ -3480,7 +2952,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/BaseGridView.java"
- line="1063"
+ line="1064"
column="43"/>
</issue>
@@ -3491,7 +2963,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/BaseGridView.java"
- line="1071"
+ line="1072"
column="12"/>
</issue>
@@ -3502,7 +2974,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/BaseGridView.java"
- line="1076"
+ line="1077"
column="37"/>
</issue>
@@ -3513,7 +2985,7 @@
errorLine2=" ~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/BaseGridView.java"
- line="1087"
+ line="1088"
column="39"/>
</issue>
@@ -3524,7 +2996,7 @@
errorLine2=" ~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/BaseGridView.java"
- line="1097"
+ line="1098"
column="51"/>
</issue>
@@ -3535,7 +3007,7 @@
errorLine2=" ~~~~">
<location
file="src/main/java/androidx/leanback/widget/BaseGridView.java"
- line="1347"
+ line="1348"
column="28"/>
</issue>
@@ -6241,7 +5713,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/picker/DatePicker.java"
- line="68"
+ line="69"
column="23"/>
</issue>
@@ -6252,7 +5724,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/picker/DatePicker.java"
- line="68"
+ line="69"
column="40"/>
</issue>
@@ -6263,7 +5735,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/picker/DatePicker.java"
- line="72"
+ line="74"
column="23"/>
</issue>
@@ -6274,7 +5746,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/picker/DatePicker.java"
- line="72"
+ line="74"
column="40"/>
</issue>
@@ -6285,7 +5757,7 @@
errorLine2=" ~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/picker/DatePicker.java"
- line="227"
+ line="229"
column="37"/>
</issue>
@@ -6296,7 +5768,7 @@
errorLine2=" ~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/picker/DatePicker.java"
- line="285"
+ line="287"
column="12"/>
</issue>
@@ -9970,7 +9442,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/GuidedActionAdapter.java"
- line="143"
+ line="144"
column="32"/>
</issue>
@@ -9981,7 +9453,7 @@
errorLine2=" ~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/GuidedActionAdapter.java"
- line="143"
+ line="144"
column="60"/>
</issue>
@@ -9992,7 +9464,7 @@
errorLine2=" ~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/GuidedActionAdapter.java"
- line="144"
+ line="145"
column="13"/>
</issue>
@@ -10003,7 +9475,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/GuidedActionAdapter.java"
- line="144"
+ line="145"
column="42"/>
</issue>
@@ -10014,7 +9486,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/GuidedActionAdapter.java"
- line="166"
+ line="169"
column="33"/>
</issue>
@@ -10025,7 +9497,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/GuidedActionAdapter.java"
- line="175"
+ line="178"
column="34"/>
</issue>
@@ -10036,7 +9508,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/GuidedActionAdapter.java"
- line="242"
+ line="245"
column="12"/>
</issue>
@@ -10047,7 +9519,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/GuidedActionAdapter.java"
- line="251"
+ line="254"
column="24"/>
</issue>
@@ -10058,7 +9530,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/GuidedActionAdapter.java"
- line="258"
+ line="261"
column="12"/>
</issue>
@@ -10069,7 +9541,7 @@
errorLine2=" ~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/GuidedActionAdapter.java"
- line="266"
+ line="269"
column="34"/>
</issue>
@@ -10080,7 +9552,7 @@
errorLine2=" ~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/GuidedActionAdapter.java"
- line="274"
+ line="277"
column="34"/>
</issue>
@@ -10091,7 +9563,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/GuidedActionAdapter.java"
- line="283"
+ line="286"
column="12"/>
</issue>
@@ -10102,7 +9574,7 @@
errorLine2=" ~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/GuidedActionAdapter.java"
- line="303"
+ line="302"
column="12"/>
</issue>
@@ -10113,7 +9585,7 @@
errorLine2=" ~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/GuidedActionAdapter.java"
- line="303"
+ line="302"
column="42"/>
</issue>
@@ -10124,7 +9596,7 @@
errorLine2=" ~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/GuidedActionAdapter.java"
- line="334"
+ line="333"
column="34"/>
</issue>
@@ -10135,7 +9607,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/GuidedActionAdapter.java"
- line="401"
+ line="400"
column="12"/>
</issue>
@@ -10146,7 +9618,7 @@
errorLine2=" ~~~~">
<location
file="src/main/java/androidx/leanback/widget/GuidedActionAdapter.java"
- line="401"
+ line="400"
column="67"/>
</issue>
@@ -10157,7 +9629,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/GuidedActionAdapter.java"
- line="418"
+ line="416"
column="38"/>
</issue>
@@ -10168,7 +9640,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/GuidedActionAdapter.java"
- line="451"
+ line="449"
column="38"/>
</issue>
@@ -10344,7 +9816,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/GuidedActionEditText.java"
- line="76"
+ line="77"
column="33"/>
</issue>
@@ -10355,7 +9827,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/GuidedActionEditText.java"
- line="80"
+ line="81"
column="33"/>
</issue>
@@ -10366,7 +9838,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/GuidedActionEditText.java"
- line="80"
+ line="81"
column="46"/>
</issue>
@@ -10377,7 +9849,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/GuidedActionEditText.java"
- line="84"
+ line="85"
column="33"/>
</issue>
@@ -10388,7 +9860,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/GuidedActionEditText.java"
- line="84"
+ line="85"
column="46"/>
</issue>
@@ -10399,7 +9871,7 @@
errorLine2=" ~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/GuidedActionEditText.java"
- line="92"
+ line="93"
column="35"/>
</issue>
@@ -10410,7 +9882,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/GuidedActionEditText.java"
- line="97"
+ line="98"
column="45"/>
</issue>
@@ -10421,7 +9893,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/GuidedActionEditText.java"
- line="109"
+ line="110"
column="51"/>
</issue>
@@ -10432,7 +9904,7 @@
errorLine2=" ~~~~">
<location
file="src/main/java/androidx/leanback/widget/GuidedActionEditText.java"
- line="115"
+ line="116"
column="67"/>
</issue>
@@ -10443,7 +9915,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/GuidedActionEditText.java"
- line="137"
+ line="139"
column="39"/>
</issue>
@@ -10454,7 +9926,7 @@
errorLine2=" ~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/GuidedActionEditText.java"
- line="142"
+ line="144"
column="26"/>
</issue>
@@ -10465,7 +9937,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/GuidedActionEditText.java"
- line="154"
+ line="156"
column="54"/>
</issue>
@@ -12555,7 +12027,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/HorizontalGridView.java"
- line="68"
+ line="69"
column="31"/>
</issue>
@@ -12566,7 +12038,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/HorizontalGridView.java"
- line="72"
+ line="73"
column="31"/>
</issue>
@@ -12577,7 +12049,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/HorizontalGridView.java"
- line="72"
+ line="73"
column="48"/>
</issue>
@@ -12588,7 +12060,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/HorizontalGridView.java"
- line="76"
+ line="77"
column="31"/>
</issue>
@@ -12599,7 +12071,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/HorizontalGridView.java"
- line="76"
+ line="77"
column="48"/>
</issue>
@@ -12610,7 +12082,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/HorizontalGridView.java"
- line="82"
+ line="84"
column="35"/>
</issue>
@@ -12621,7 +12093,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/HorizontalGridView.java"
- line="82"
+ line="84"
column="52"/>
</issue>
@@ -12632,7 +12104,7 @@
errorLine2=" ~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/HorizontalGridView.java"
- line="313"
+ line="315"
column="22"/>
</issue>
@@ -12698,7 +12170,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/ImageCardView.java"
- line="153"
+ line="154"
column="26"/>
</issue>
@@ -12709,7 +12181,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/ImageCardView.java"
- line="160"
+ line="161"
column="26"/>
</issue>
@@ -12720,7 +12192,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/ImageCardView.java"
- line="160"
+ line="161"
column="43"/>
</issue>
@@ -12731,7 +12203,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/ImageCardView.java"
- line="287"
+ line="289"
column="26"/>
</issue>
@@ -12742,7 +12214,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/ImageCardView.java"
- line="294"
+ line="296"
column="26"/>
</issue>
@@ -12753,7 +12225,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/ImageCardView.java"
- line="294"
+ line="296"
column="43"/>
</issue>
@@ -12764,7 +12236,7 @@
errorLine2=" ~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/ImageCardView.java"
- line="301"
+ line="303"
column="18"/>
</issue>
@@ -12775,7 +12247,7 @@
errorLine2=" ~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/ImageCardView.java"
- line="317"
+ line="319"
column="39"/>
</issue>
@@ -12786,7 +12258,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/ImageCardView.java"
- line="326"
+ line="328"
column="30"/>
</issue>
@@ -12797,7 +12269,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/ImageCardView.java"
- line="333"
+ line="335"
column="30"/>
</issue>
@@ -12808,7 +12280,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/ImageCardView.java"
- line="367"
+ line="369"
column="12"/>
</issue>
@@ -12819,7 +12291,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/ImageCardView.java"
- line="378"
+ line="380"
column="12"/>
</issue>
@@ -12830,7 +12302,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/ImageCardView.java"
- line="388"
+ line="390"
column="39"/>
</issue>
@@ -12841,7 +12313,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/ImageCardView.java"
- line="406"
+ line="408"
column="30"/>
</issue>
@@ -12852,7 +12324,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/ImageCardView.java"
- line="416"
+ line="418"
column="12"/>
</issue>
@@ -12863,7 +12335,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/ImageCardView.java"
- line="427"
+ line="429"
column="32"/>
</issue>
@@ -12874,7 +12346,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/ImageCardView.java"
- line="437"
+ line="439"
column="12"/>
</issue>
@@ -12885,7 +12357,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/ImageCardView.java"
- line="448"
+ line="450"
column="31"/>
</issue>
@@ -12896,7 +12368,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/ImageCardView.java"
- line="463"
+ line="465"
column="12"/>
</issue>
@@ -16240,7 +15712,7 @@
errorLine2=" ~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/picker/Picker.java"
- line="73"
+ line="74"
column="29"/>
</issue>
@@ -16251,7 +15723,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/picker/Picker.java"
- line="102"
+ line="103"
column="18"/>
</issue>
@@ -16262,7 +15734,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/picker/Picker.java"
- line="111"
+ line="112"
column="36"/>
</issue>
@@ -16273,7 +15745,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/picker/Picker.java"
- line="120"
+ line="121"
column="18"/>
</issue>
@@ -16284,7 +15756,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/picker/Picker.java"
- line="136"
+ line="137"
column="37"/>
</issue>
@@ -16295,7 +15767,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/picker/Picker.java"
- line="186"
+ line="187"
column="19"/>
</issue>
@@ -16306,7 +15778,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/picker/Picker.java"
- line="186"
+ line="187"
column="36"/>
</issue>
@@ -16317,7 +15789,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/picker/Picker.java"
- line="193"
+ line="195"
column="19"/>
</issue>
@@ -16328,7 +15800,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/picker/Picker.java"
- line="193"
+ line="195"
column="36"/>
</issue>
@@ -16339,7 +15811,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/picker/Picker.java"
- line="228"
+ line="230"
column="12"/>
</issue>
@@ -16350,7 +15822,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/picker/Picker.java"
- line="254"
+ line="256"
column="28"/>
</issue>
@@ -16361,7 +15833,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/picker/Picker.java"
- line="329"
+ line="331"
column="46"/>
</issue>
@@ -16372,7 +15844,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/picker/Picker.java"
- line="376"
+ line="378"
column="43"/>
</issue>
@@ -16383,7 +15855,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/picker/Picker.java"
- line="388"
+ line="390"
column="46"/>
</issue>
@@ -16394,7 +15866,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/picker/Picker.java"
- line="541"
+ line="543"
column="37"/>
</issue>
@@ -16405,7 +15877,7 @@
errorLine2=" ~~~~">
<location
file="src/main/java/androidx/leanback/widget/picker/Picker.java"
- line="560"
+ line="562"
column="66"/>
</issue>
@@ -16416,7 +15888,7 @@
errorLine2=" ~~~~">
<location
file="src/main/java/androidx/leanback/widget/picker/Picker.java"
- line="696"
+ line="698"
column="35"/>
</issue>
@@ -16427,7 +15899,7 @@
errorLine2=" ~~~~">
<location
file="src/main/java/androidx/leanback/widget/picker/Picker.java"
- line="696"
+ line="698"
column="47"/>
</issue>
@@ -16493,7 +15965,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/picker/PinPicker.java"
- line="40"
+ line="41"
column="22"/>
</issue>
@@ -16504,7 +15976,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/picker/PinPicker.java"
- line="40"
+ line="41"
column="39"/>
</issue>
@@ -16515,7 +15987,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/picker/PinPicker.java"
- line="44"
+ line="46"
column="22"/>
</issue>
@@ -16526,7 +15998,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/picker/PinPicker.java"
- line="44"
+ line="46"
column="39"/>
</issue>
@@ -16537,7 +16009,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/picker/PinPicker.java"
- line="87"
+ line="89"
column="37"/>
</issue>
@@ -16548,7 +16020,7 @@
errorLine2=" ~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/picker/PinPicker.java"
- line="104"
+ line="106"
column="12"/>
</issue>
@@ -21696,7 +21168,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/SearchOrbView.java"
- line="155"
+ line="156"
column="26"/>
</issue>
@@ -21707,7 +21179,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/SearchOrbView.java"
- line="159"
+ line="160"
column="26"/>
</issue>
@@ -21718,7 +21190,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/SearchOrbView.java"
- line="159"
+ line="160"
column="43"/>
</issue>
@@ -21729,7 +21201,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/SearchOrbView.java"
- line="163"
+ line="165"
column="26"/>
</issue>
@@ -21740,7 +21212,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/SearchOrbView.java"
- line="163"
+ line="165"
column="43"/>
</issue>
@@ -21751,7 +21223,7 @@
errorLine2=" ~~~~">
<location
file="src/main/java/androidx/leanback/widget/SearchOrbView.java"
- line="228"
+ line="230"
column="25"/>
</issue>
@@ -21762,7 +21234,7 @@
errorLine2=" ~~~~">
<location
file="src/main/java/androidx/leanback/widget/SearchOrbView.java"
- line="248"
+ line="250"
column="69"/>
</issue>
@@ -21773,7 +21245,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/SearchOrbView.java"
- line="264"
+ line="266"
column="28"/>
</issue>
@@ -21784,7 +21256,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/SearchOrbView.java"
- line="273"
+ line="275"
column="12"/>
</issue>
@@ -21795,7 +21267,7 @@
errorLine2=" ~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/SearchOrbView.java"
- line="281"
+ line="283"
column="41"/>
</issue>
@@ -21806,7 +21278,7 @@
errorLine2=" ~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/SearchOrbView.java"
- line="317"
+ line="319"
column="30"/>
</issue>
@@ -21817,7 +21289,7 @@
errorLine2=" ~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/SearchOrbView.java"
- line="331"
+ line="333"
column="12"/>
</issue>
@@ -23137,7 +22609,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/picker/TimePicker.java"
- line="89"
+ line="90"
column="23"/>
</issue>
@@ -23148,7 +22620,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/picker/TimePicker.java"
- line="89"
+ line="90"
column="40"/>
</issue>
@@ -23159,7 +22631,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/picker/TimePicker.java"
- line="103"
+ line="105"
column="23"/>
</issue>
@@ -23170,7 +22642,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/picker/TimePicker.java"
- line="103"
+ line="105"
column="40"/>
</issue>
@@ -24985,7 +24457,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/VerticalGridView.java"
- line="44"
+ line="45"
column="29"/>
</issue>
@@ -24996,7 +24468,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/VerticalGridView.java"
- line="48"
+ line="49"
column="29"/>
</issue>
@@ -25007,7 +24479,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/VerticalGridView.java"
- line="48"
+ line="49"
column="46"/>
</issue>
@@ -25018,7 +24490,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/VerticalGridView.java"
- line="52"
+ line="53"
column="29"/>
</issue>
@@ -25029,7 +24501,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/VerticalGridView.java"
- line="52"
+ line="53"
column="46"/>
</issue>
@@ -25040,7 +24512,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/VerticalGridView.java"
- line="58"
+ line="60"
column="35"/>
</issue>
@@ -25051,7 +24523,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/androidx/leanback/widget/VerticalGridView.java"
- line="58"
+ line="60"
column="52"/>
</issue>
diff --git a/leanback/leanback/src/main/java/androidx/leanback/widget/BaseCardView.java b/leanback/leanback/src/main/java/androidx/leanback/widget/BaseCardView.java
index 911a36d..d1c0738 100644
--- a/leanback/leanback/src/main/java/androidx/leanback/widget/BaseCardView.java
+++ b/leanback/leanback/src/main/java/androidx/leanback/widget/BaseCardView.java
@@ -16,6 +16,7 @@
package androidx.leanback.widget;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
@@ -153,6 +154,7 @@
this(context, attrs, R.attr.baseCardViewStyle);
}
+ @SuppressLint("CustomViewStyleable")
public BaseCardView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
@@ -861,6 +863,7 @@
/**
* {@inheritDoc}
*/
+ @SuppressLint("CustomViewStyleable")
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.lbBaseCardView_Layout);
diff --git a/leanback/leanback/src/main/java/androidx/leanback/widget/BaseGridView.java b/leanback/leanback/src/main/java/androidx/leanback/widget/BaseGridView.java
index 16083c8..82864ee 100644
--- a/leanback/leanback/src/main/java/androidx/leanback/widget/BaseGridView.java
+++ b/leanback/leanback/src/main/java/androidx/leanback/widget/BaseGridView.java
@@ -281,6 +281,7 @@
});
}
+ @SuppressLint("CustomViewStyleable")
void initBaseGridViewAttributes(Context context, AttributeSet attrs) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbBaseGridView);
boolean throughFront = a.getBoolean(R.styleable.lbBaseGridView_focusOutFront, false);
diff --git a/leanback/leanback/src/main/java/androidx/leanback/widget/GuidedActionAdapter.java b/leanback/leanback/src/main/java/androidx/leanback/widget/GuidedActionAdapter.java
index b2ec182..fa28365 100644
--- a/leanback/leanback/src/main/java/androidx/leanback/widget/GuidedActionAdapter.java
+++ b/leanback/leanback/src/main/java/androidx/leanback/widget/GuidedActionAdapter.java
@@ -98,6 +98,7 @@
void onImeClose();
}
+ final RecyclerView mRecyclerView;
private final boolean mIsSubAdapter;
private final ActionOnKeyListener mActionOnKeyListener;
private final ActionOnFocusListener mActionOnFocusListener;
@@ -113,9 +114,9 @@
private final View.OnClickListener mOnClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
- if (v != null && v.getWindowToken() != null && getRecyclerView() != null) {
+ if (v != null && v.getWindowToken() != null && mRecyclerView.isAttachedToWindow()) {
GuidedActionsStylist.ViewHolder avh = (GuidedActionsStylist.ViewHolder)
- getRecyclerView().getChildViewHolder(v);
+ mRecyclerView.getChildViewHolder(v);
GuidedAction action = avh.getAction();
if (action.hasTextEditable()) {
if (DEBUG_EDIT) Log.v(TAG_EDIT, "openIme by click");
@@ -155,6 +156,8 @@
if (!isSubAdapter) {
mDiffCallback = GuidedActionDiffCallback.getInstance();
}
+ mRecyclerView = isSubAdapter ? mStylist.getSubActionsGridView() :
+ mStylist.getActionsGridView();
}
/**
@@ -292,10 +295,6 @@
return mStylist.getItemViewType(mActions.get(position));
}
- RecyclerView getRecyclerView() {
- return mIsSubAdapter ? mStylist.getSubActionsGridView() : mStylist.getActionsGridView();
- }
-
/**
* {@inheritDoc}
*/
@@ -362,8 +361,8 @@
}
public void unFocus() {
- if (mSelectedView != null && getRecyclerView() != null) {
- ViewHolder vh = getRecyclerView().getChildViewHolder(mSelectedView);
+ if (mSelectedView != null && mRecyclerView.isAttachedToWindow()) {
+ ViewHolder vh = mRecyclerView.getChildViewHolder(mSelectedView);
if (vh != null) {
GuidedActionsStylist.ViewHolder avh = (GuidedActionsStylist.ViewHolder)vh;
mStylist.onAnimateItemFocused(avh, false);
@@ -376,11 +375,11 @@
@Override
public void onFocusChange(View v, boolean hasFocus) {
- if (getRecyclerView() == null) {
+ if (!mRecyclerView.isAttachedToWindow()) {
return;
}
GuidedActionsStylist.ViewHolder avh = (GuidedActionsStylist.ViewHolder)
- getRecyclerView().getChildViewHolder(v);
+ mRecyclerView.getChildViewHolder(v);
if (hasFocus) {
mSelectedView = v;
if (mFocusListener != null) {
@@ -399,18 +398,17 @@
}
public GuidedActionsStylist.ViewHolder findSubChildViewHolder(View v) {
- // Needed because RecyclerView.getChildViewHolder does not traverse the hierarchy
- if (getRecyclerView() == null) {
+ if (!mRecyclerView.isAttachedToWindow()) {
return null;
}
GuidedActionsStylist.ViewHolder result = null;
ViewParent parent = v.getParent();
- while (parent != getRecyclerView() && parent != null && v != null) {
+ while (parent != mRecyclerView && parent != null && v != null) {
v = (View)parent;
parent = parent.getParent();
}
if (parent != null && v != null) {
- result = (GuidedActionsStylist.ViewHolder)getRecyclerView().getChildViewHolder(v);
+ result = (GuidedActionsStylist.ViewHolder) mRecyclerView.getChildViewHolder(v);
}
return result;
}
@@ -418,7 +416,7 @@
public void handleCheckedActions(GuidedActionsStylist.ViewHolder avh) {
GuidedAction action = avh.getAction();
int actionCheckSetId = action.getCheckSetId();
- if (getRecyclerView() != null && actionCheckSetId != GuidedAction.NO_CHECK_SET) {
+ if (mRecyclerView.isAttachedToWindow() && actionCheckSetId != GuidedAction.NO_CHECK_SET) {
// Find any actions that are checked and are in the same group
// as the selected action. Fade their checkmarks out.
if (actionCheckSetId != GuidedAction.CHECKBOX_CHECK_SET_ID) {
@@ -427,7 +425,7 @@
if (a != action && a.getCheckSetId() == actionCheckSetId && a.isChecked()) {
a.setChecked(false);
GuidedActionsStylist.ViewHolder vh = (GuidedActionsStylist.ViewHolder)
- getRecyclerView().findViewHolderForPosition(i);
+ mRecyclerView.findViewHolderForPosition(i);
if (vh != null) {
mStylist.onAnimateItemChecked(vh, false);
}
@@ -466,7 +464,7 @@
*/
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
- if (v == null || event == null || getRecyclerView() == null) {
+ if (v == null || event == null || !mRecyclerView.isAttachedToWindow()) {
return false;
}
boolean handled = false;
@@ -478,7 +476,7 @@
case KeyEvent.KEYCODE_ENTER:
GuidedActionsStylist.ViewHolder avh = (GuidedActionsStylist.ViewHolder)
- getRecyclerView().getChildViewHolder(v);
+ mRecyclerView.getChildViewHolder(v);
GuidedAction action = avh.getAction();
if (!action.isEnabled() || action.infoOnly()) {
diff --git a/leanback/leanback/src/main/java/androidx/leanback/widget/GuidedActionEditText.java b/leanback/leanback/src/main/java/androidx/leanback/widget/GuidedActionEditText.java
index 04717c0..5f661df 100644
--- a/leanback/leanback/src/main/java/androidx/leanback/widget/GuidedActionEditText.java
+++ b/leanback/leanback/src/main/java/androidx/leanback/widget/GuidedActionEditText.java
@@ -30,6 +30,7 @@
import android.widget.TextView;
import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
import androidx.core.widget.TextViewCompat;
/**
@@ -126,6 +127,7 @@
}
}
+ @RequiresApi(26)
@Override
public int getAutofillType() {
// make it always autofillable as Guided fragment switches InputType when user clicks
diff --git a/leanback/leanback/src/main/java/androidx/leanback/widget/HorizontalGridView.java b/leanback/leanback/src/main/java/androidx/leanback/widget/HorizontalGridView.java
index 1fe9378..6bda0d3 100644
--- a/leanback/leanback/src/main/java/androidx/leanback/widget/HorizontalGridView.java
+++ b/leanback/leanback/src/main/java/androidx/leanback/widget/HorizontalGridView.java
@@ -13,6 +13,7 @@
*/
package androidx.leanback.widget;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
@@ -79,6 +80,7 @@
initAttributes(context, attrs);
}
+ @SuppressLint("CustomViewStyleable")
protected void initAttributes(Context context, AttributeSet attrs) {
initBaseGridViewAttributes(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbHorizontalGridView);
diff --git a/leanback/leanback/src/main/java/androidx/leanback/widget/ImageCardView.java b/leanback/leanback/src/main/java/androidx/leanback/widget/ImageCardView.java
index b70fc03..88f961e 100644
--- a/leanback/leanback/src/main/java/androidx/leanback/widget/ImageCardView.java
+++ b/leanback/leanback/src/main/java/androidx/leanback/widget/ImageCardView.java
@@ -14,6 +14,7 @@
package androidx.leanback.widget;
import android.animation.ObjectAnimator;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
@@ -162,6 +163,7 @@
buildImageCardView(attrs, defStyleAttr, R.style.Widget_Leanback_ImageCardView);
}
+ @SuppressLint("CustomViewStyleable")
private void buildImageCardView(AttributeSet attrs, int defStyleAttr, int defStyle) {
// Make sure the ImageCardView is focusable.
setFocusable(true);
diff --git a/leanback/leanback/src/main/java/androidx/leanback/widget/ResizingTextView.java b/leanback/leanback/src/main/java/androidx/leanback/widget/ResizingTextView.java
index 805b1b2..e2cc560 100644
--- a/leanback/leanback/src/main/java/androidx/leanback/widget/ResizingTextView.java
+++ b/leanback/leanback/src/main/java/androidx/leanback/widget/ResizingTextView.java
@@ -53,6 +53,7 @@
private int mDefaultPaddingTop;
private int mDefaultPaddingBottom;
+ @SuppressLint("CustomViewStyleable")
public ResizingTextView(Context ctx, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(ctx, attrs, defStyleAttr);
TypedArray a = ctx.obtainStyledAttributes(attrs, R.styleable.lbResizingTextView,
diff --git a/leanback/leanback/src/main/java/androidx/leanback/widget/SearchOrbView.java b/leanback/leanback/src/main/java/androidx/leanback/widget/SearchOrbView.java
index 84e7957..b54540f 100644
--- a/leanback/leanback/src/main/java/androidx/leanback/widget/SearchOrbView.java
+++ b/leanback/leanback/src/main/java/androidx/leanback/widget/SearchOrbView.java
@@ -18,6 +18,7 @@
import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -160,6 +161,7 @@
this(context, attrs, R.attr.searchOrbViewStyle);
}
+ @SuppressLint("CustomViewStyleable")
public SearchOrbView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
diff --git a/leanback/leanback/src/main/java/androidx/leanback/widget/VerticalGridView.java b/leanback/leanback/src/main/java/androidx/leanback/widget/VerticalGridView.java
index 981d13a..44a38d6 100644
--- a/leanback/leanback/src/main/java/androidx/leanback/widget/VerticalGridView.java
+++ b/leanback/leanback/src/main/java/androidx/leanback/widget/VerticalGridView.java
@@ -13,6 +13,7 @@
*/
package androidx.leanback.widget;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
@@ -55,6 +56,7 @@
initAttributes(context, attrs);
}
+ @SuppressLint("CustomViewStyleable")
protected void initAttributes(Context context, AttributeSet attrs) {
initBaseGridViewAttributes(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbVerticalGridView);
diff --git a/leanback/leanback/src/main/java/androidx/leanback/widget/picker/DatePicker.java b/leanback/leanback/src/main/java/androidx/leanback/widget/picker/DatePicker.java
index f2d25af..7ce9b57 100644
--- a/leanback/leanback/src/main/java/androidx/leanback/widget/picker/DatePicker.java
+++ b/leanback/leanback/src/main/java/androidx/leanback/widget/picker/DatePicker.java
@@ -14,6 +14,7 @@
package androidx.leanback.widget.picker;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.text.TextUtils;
@@ -57,7 +58,7 @@
private int mColYearIndex;
private static final String DATE_FORMAT = "MM/dd/yyyy";
- private final DateFormat mDateFormat = new SimpleDateFormat(DATE_FORMAT);
+ private final DateFormat mDateFormat = new SimpleDateFormat(DATE_FORMAT, Locale.getDefault());
private PickerUtility.DateConstant mConstant;
private Calendar mMinDate;
@@ -69,6 +70,7 @@
this(context, attrs, R.attr.datePickerStyle);
}
+ @SuppressLint("CustomViewStyleable")
public DatePicker(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
@@ -241,7 +243,7 @@
setSeparators(separators);
mYearColumn = mMonthColumn = mDayColumn = null;
mColYearIndex = mColDayIndex = mColMonthIndex = -1;
- String dateFieldsPattern = datePickerFormat.toUpperCase();
+ String dateFieldsPattern = datePickerFormat.toUpperCase(mConstant.locale);
ArrayList<PickerColumn> columns = new ArrayList<>(3);
for (int i = 0; i < dateFieldsPattern.length(); i++) {
switch (dateFieldsPattern.charAt(i)) {
diff --git a/leanback/leanback/src/main/java/androidx/leanback/widget/picker/Picker.java b/leanback/leanback/src/main/java/androidx/leanback/widget/picker/Picker.java
index 521ddbd..bb159d9 100644
--- a/leanback/leanback/src/main/java/androidx/leanback/widget/picker/Picker.java
+++ b/leanback/leanback/src/main/java/androidx/leanback/widget/picker/Picker.java
@@ -14,6 +14,7 @@
package androidx.leanback.widget.picker;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
@@ -190,6 +191,7 @@
/**
* Creates a Picker widget.
*/
+ @SuppressLint("CustomViewStyleable")
public Picker(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
final TypedArray a = context.obtainStyledAttributes(
diff --git a/leanback/leanback/src/main/java/androidx/leanback/widget/picker/PinPicker.java b/leanback/leanback/src/main/java/androidx/leanback/widget/picker/PinPicker.java
index 864eda9..b865af8 100644
--- a/leanback/leanback/src/main/java/androidx/leanback/widget/picker/PinPicker.java
+++ b/leanback/leanback/src/main/java/androidx/leanback/widget/picker/PinPicker.java
@@ -16,6 +16,7 @@
package androidx.leanback.widget.picker;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
@@ -41,6 +42,7 @@
this(context, attrs, R.attr.pinPickerStyle);
}
+ @SuppressLint("CustomViewStyleable")
public PinPicker(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
final TypedArray a = context.obtainStyledAttributes(
diff --git a/leanback/leanback/src/main/java/androidx/leanback/widget/picker/TimePicker.java b/leanback/leanback/src/main/java/androidx/leanback/widget/picker/TimePicker.java
index faf7ecb..3cc9f88 100644
--- a/leanback/leanback/src/main/java/androidx/leanback/widget/picker/TimePicker.java
+++ b/leanback/leanback/src/main/java/androidx/leanback/widget/picker/TimePicker.java
@@ -16,6 +16,7 @@
package androidx.leanback.widget.picker;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.text.TextUtils;
@@ -100,6 +101,7 @@
* resource that supplies default values for the widget. Can be 0 to not
* look for defaults.
*/
+ @SuppressLint("CustomViewStyleable")
public TimePicker(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
@@ -278,7 +280,7 @@
+ " the size of timeFieldsPattern: " + timeFieldsPattern.length() + " + 1");
}
setSeparators(separators);
- timeFieldsPattern = timeFieldsPattern.toUpperCase();
+ timeFieldsPattern = timeFieldsPattern.toUpperCase(mConstant.locale);
mHourColumn = mMinuteColumn = mAmPmColumn = null;
mColHourIndex = mColMinuteIndex = mColAmPmIndex = -1;
diff --git a/leanback/leanback/src/main/res/layout/lb_details_overview.xml b/leanback/leanback/src/main/res/layout/lb_details_overview.xml
index 22d72dc..3ca9e61 100644
--- a/leanback/leanback/src/main/res/layout/lb_details_overview.xml
+++ b/leanback/leanback/src/main/res/layout/lb_details_overview.xml
@@ -17,6 +17,7 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:lb="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="@dimen/lb_details_overview_margin_start"
@@ -35,6 +36,7 @@
android:layout_height="@dimen/lb_details_overview_height_large"
android:foreground="#ffffff"
android:elevation="@dimen/lb_details_overview_z"
+ tools:ignore="UnusedAttribute"
>
<!-- Background is applied to this inner layout -->
diff --git a/leanback/leanback/src/main/res/layout/lb_fullwidth_details_overview.xml b/leanback/leanback/src/main/res/layout/lb_fullwidth_details_overview.xml
index b6bc902..2e60acc 100644
--- a/leanback/leanback/src/main/res/layout/lb_fullwidth_details_overview.xml
+++ b/leanback/leanback/src/main/res/layout/lb_fullwidth_details_overview.xml
@@ -17,6 +17,7 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:lb="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/details_root"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -36,6 +37,7 @@
android:clipToPadding="false"
android:background="?attr/defaultBrandColor"
android:elevation="@dimen/lb_details_overview_z"
+ tools:ignore="UnusedAttribute"
>
<LinearLayout
diff --git a/leanback/leanback/src/main/res/layout/lb_fullwidth_details_overview_logo.xml b/leanback/leanback/src/main/res/layout/lb_fullwidth_details_overview_logo.xml
index 3b4cd7e..fa24c51 100644
--- a/leanback/leanback/src/main/res/layout/lb_fullwidth_details_overview_logo.xml
+++ b/leanback/leanback/src/main/res/layout/lb_fullwidth_details_overview_logo.xml
@@ -16,10 +16,12 @@
-->
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/details_overview_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:elevation="@dimen/lb_details_overview_z"
+ tools:ignore="UnusedAttribute"
android:maxWidth="@dimen/lb_details_v2_logo_max_width"
android:maxHeight="@dimen/lb_details_v2_logo_max_height" />
diff --git a/leanback/leanback/src/main/res/layout/lb_guidedactions.xml b/leanback/leanback/src/main/res/layout/lb_guidedactions.xml
index 53122a4..2314005 100644
--- a/leanback/leanback/src/main/res/layout/lb_guidedactions.xml
+++ b/leanback/leanback/src/main/res/layout/lb_guidedactions.xml
@@ -16,9 +16,11 @@
-->
<!-- Layout for the settings list fragment -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/guidedactions_root"
android:transitionName="guidedactions_root"
android:transitionGroup="false"
+ tools:ignore="UnusedAttribute"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent">
@@ -26,6 +28,7 @@
<androidx.leanback.widget.NonOverlappingView
android:id="@+id/guidedactions_list_background"
android:transitionName="guidedactions_list_background"
+ tools:ignore="UnusedAttribute"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
@@ -37,15 +40,18 @@
android:id="@+id/guidedactions_content"
android:transitionName="guidedactions_content"
android:transitionGroup="false"
+ tools:ignore="UnusedAttribute"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.leanback.widget.VerticalGridView
android:transitionGroup="true"
+ tools:ignore="UnusedAttribute"
android:id="@+id/guidedactions_list"
style="?attr/guidedActionsListStyle" />
<androidx.leanback.widget.NonOverlappingView
android:id="@+id/guidedactions_sub_list_background"
android:transitionName="guidedactions_sub_list_background"
+ tools:ignore="UnusedAttribute"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignTop="@+id/guidedactions_sub_list"
@@ -56,6 +62,7 @@
android:background="?attr/guidedActionsBackgroundDark" />
<androidx.leanback.widget.VerticalGridView
android:transitionGroup="true"
+ tools:ignore="UnusedAttribute"
android:id="@+id/guidedactions_sub_list"
android:layout_width="match_parent"
android:layout_height="0dp"
diff --git a/leanback/leanback/src/main/res/layout/lb_guidedbuttonactions.xml b/leanback/leanback/src/main/res/layout/lb_guidedbuttonactions.xml
index 5678fdf..fe91320 100644
--- a/leanback/leanback/src/main/res/layout/lb_guidedbuttonactions.xml
+++ b/leanback/leanback/src/main/res/layout/lb_guidedbuttonactions.xml
@@ -17,8 +17,10 @@
<!-- Layout for the settings list fragment -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/guidedactions_root2"
+ xmlns:tools="http://schemas.android.com/tools"
android:transitionName="guidedactions_root2"
android:transitionGroup="false"
+ tools:ignore="UnusedAttribute"
android:layout_width="0dp"
android:layout_weight="?attr/guidedButtonActionsWidthWeight"
android:layout_height="match_parent">
@@ -26,6 +28,7 @@
<androidx.leanback.widget.NonOverlappingView
android:id="@+id/guidedactions_list_background2"
android:transitionName="guidedactions_list_background2"
+ tools:ignore="UnusedAttribute"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/guidedActionsBackgroundDark" />
@@ -34,6 +37,7 @@
android:id="@+id/guidedactions_content2"
android:transitionName="guidedactions_content2"
android:transitionGroup="false"
+ tools:ignore="UnusedAttribute"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.leanback.widget.VerticalGridView
diff --git a/leanback/leanback/src/main/res/layout/lb_guidedstep_background.xml b/leanback/leanback/src/main/res/layout/lb_guidedstep_background.xml
index 7332f9c..1ab32dd 100644
--- a/leanback/leanback/src/main/res/layout/lb_guidedstep_background.xml
+++ b/leanback/leanback/src/main/res/layout/lb_guidedstep_background.xml
@@ -16,8 +16,10 @@
-->
<androidx.leanback.widget.NonOverlappingView
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/guidedstep_background"
android:transitionName="guidedstep_background"
+ tools:ignore="UnusedAttribute"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/guidedStepBackground" />
diff --git a/leanback/leanback/src/main/res/layout/lb_guidedstep_fragment.xml b/leanback/leanback/src/main/res/layout/lb_guidedstep_fragment.xml
index 0df9991..766dbcc 100644
--- a/leanback/leanback/src/main/res/layout/lb_guidedstep_fragment.xml
+++ b/leanback/leanback/src/main/res/layout/lb_guidedstep_fragment.xml
@@ -17,6 +17,7 @@
<!-- Layout for the frame of a 2 pane actions fragment. -->
<androidx.leanback.app.GuidedStepRootLayout
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/guidedstep_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -47,6 +48,7 @@
android:id="@+id/action_fragment_root"
android:transitionName="action_fragment_root"
android:transitionGroup="false"
+ tools:ignore="UnusedAttribute"
android:orientation="horizontal"
android:clipToPadding="false"
android:clipChildren="false"
@@ -58,6 +60,7 @@
<androidx.leanback.widget.NonOverlappingView
android:id="@+id/action_fragment_background"
android:transitionName="action_fragment_background"
+ tools:ignore="UnusedAttribute"
android:orientation="horizontal"
android:outlineProvider="paddedBounds"
android:layout_width="match_parent"
@@ -71,6 +74,7 @@
android:descendantFocusability="afterDescendants"
android:transitionName="action_fragment"
android:transitionGroup="false"
+ tools:ignore="UnusedAttribute"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
diff --git a/leanback/leanback/src/main/res/layout/lb_headers_fragment.xml b/leanback/leanback/src/main/res/layout/lb_headers_fragment.xml
index 5876a36..8f15719d 100644
--- a/leanback/leanback/src/main/res/layout/lb_headers_fragment.xml
+++ b/leanback/leanback/src/main/res/layout/lb_headers_fragment.xml
@@ -16,8 +16,10 @@
-->
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/browse_headers_root"
android:transitionGroup="true"
+ tools:ignore="UnusedAttribute"
android:layout_width="@dimen/lb_browse_headers_width"
android:layout_height="match_parent"
android:paddingEnd="@dimen/lb_browse_header_padding_end"
diff --git a/leanback/leanback/src/main/res/layout/lb_playback_fragment.xml b/leanback/leanback/src/main/res/layout/lb_playback_fragment.xml
index 19ebe90..f116b5f 100644
--- a/leanback/leanback/src/main/res/layout/lb_playback_fragment.xml
+++ b/leanback/leanback/src/main/res/layout/lb_playback_fragment.xml
@@ -16,20 +16,24 @@
-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/playback_fragment_root"
android:layout_width="match_parent"
android:transitionGroup="false"
+ tools:ignore="UnusedAttribute"
android:layout_height="match_parent">
<androidx.leanback.widget.NonOverlappingFrameLayout
android:id="@+id/playback_fragment_background"
android:transitionGroup="false"
+ tools:ignore="UnusedAttribute"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<androidx.leanback.widget.NonOverlappingFrameLayout
android:id="@+id/playback_controls_dock"
android:transitionGroup="true"
+ tools:ignore="UnusedAttribute"
android:layout_height="match_parent"
android:layout_width="match_parent"/>
</FrameLayout>
diff --git a/leanback/leanback/src/main/res/layout/lb_title_view.xml b/leanback/leanback/src/main/res/layout/lb_title_view.xml
index ae439d3..2971ebc 100644
--- a/leanback/leanback/src/main/res/layout/lb_title_view.xml
+++ b/leanback/leanback/src/main/res/layout/lb_title_view.xml
@@ -14,7 +14,8 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<merge xmlns:android="http://schemas.android.com/apk/res/android" >
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools">
<ImageView
android:id="@+id/title_badge"
@@ -39,6 +40,7 @@
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:transitionGroup="true"
+ tools:ignore="UnusedAttribute"
android:layout_gravity="center_vertical|start"
android:visibility="invisible" />
diff --git a/media/version-compat-tests/current/client/build.gradle b/media/version-compat-tests/current/client/build.gradle
index d6925c2..8961d3a 100644
--- a/media/version-compat-tests/current/client/build.gradle
+++ b/media/version-compat-tests/current/client/build.gradle
@@ -23,7 +23,7 @@
dependencies {
androidTestImplementation(project(":media:media"))
- androidTestImplementation(project(":support-media-test-lib"))
+ androidTestImplementation(project(":media:version-compat-tests:lib"))
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
androidTestImplementation(ANDROIDX_TEST_CORE)
diff --git a/media/version-compat-tests/current/service/build.gradle b/media/version-compat-tests/current/service/build.gradle
index d6925c2..8961d3a 100644
--- a/media/version-compat-tests/current/service/build.gradle
+++ b/media/version-compat-tests/current/service/build.gradle
@@ -23,7 +23,7 @@
dependencies {
androidTestImplementation(project(":media:media"))
- androidTestImplementation(project(":support-media-test-lib"))
+ androidTestImplementation(project(":media:version-compat-tests:lib"))
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
androidTestImplementation(ANDROIDX_TEST_CORE)
diff --git a/media/version-compat-tests/previous/client/build.gradle b/media/version-compat-tests/previous/client/build.gradle
index b0055a4..f32aaa4 100644
--- a/media/version-compat-tests/previous/client/build.gradle
+++ b/media/version-compat-tests/previous/client/build.gradle
@@ -22,7 +22,7 @@
}
dependencies {
- androidTestImplementation project(':support-media-test-lib')
+ androidTestImplementation project(':media:version-compat-tests:lib')
androidTestImplementation "com.android.support:support-media-compat:27.1.0"
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
diff --git a/media/version-compat-tests/previous/service/build.gradle b/media/version-compat-tests/previous/service/build.gradle
index e089e44..9edfd60 100644
--- a/media/version-compat-tests/previous/service/build.gradle
+++ b/media/version-compat-tests/previous/service/build.gradle
@@ -22,7 +22,7 @@
}
dependencies {
- androidTestImplementation(project(":support-media-test-lib"))
+ androidTestImplementation(project(":media:version-compat-tests:lib"))
androidTestImplementation "com.android.support:support-media-compat:27.1.0"
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
diff --git a/media2/common/src/main/java/androidx/media2/common/SessionPlayer.java b/media2/common/src/main/java/androidx/media2/common/SessionPlayer.java
index 5eecc32..f54cc41 100644
--- a/media2/common/src/main/java/androidx/media2/common/SessionPlayer.java
+++ b/media2/common/src/main/java/androidx/media2/common/SessionPlayer.java
@@ -602,6 +602,8 @@
* completed.
* <p>
* On success, a {@link PlayerResult} should be returned with {@code item} removed.
+ * <p>
+ * If the last item is removed, the player should be moved to {@link #PLAYER_STATE_IDLE}.
*
* @param index the index of the item you want to remove in the playlist
* @see PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)
diff --git a/media2/player/src/androidTest/java/androidx/media2/player/MediaPlayerTest.java b/media2/player/src/androidTest/java/androidx/media2/player/MediaPlayerTest.java
index d822f5e..b61ea0b 100644
--- a/media2/player/src/androidTest/java/androidx/media2/player/MediaPlayerTest.java
+++ b/media2/player/src/androidTest/java/androidx/media2/player/MediaPlayerTest.java
@@ -1596,6 +1596,37 @@
@Test
@MediumTest
+ public void setPlaylistAndRemoveAll_playerShouldMoveIdleState() throws Exception {
+ List<MediaItem> playlist = new ArrayList<>();
+ playlist.add(createMediaItem(R.raw.number1));
+
+ final TestUtils.Monitor onPlayerStateChangedCalled = new TestUtils.Monitor();
+ final AtomicInteger playerState = new AtomicInteger();
+
+ MediaPlayer.PlayerCallback callback = new MediaPlayer.PlayerCallback() {
+ @Override
+ public void onPlayerStateChanged(@NonNull SessionPlayer player, int state) {
+ playerState.set(state);
+ onPlayerStateChangedCalled.signal();
+ }
+ };
+
+ mPlayer.registerPlayerCallback(mExecutor, callback);
+
+ assertNotNull(mPlayer.setPlaylist(playlist, null));
+ assertNotNull(mPlayer.prepare());
+ do {
+ assertTrue(onPlayerStateChangedCalled.waitForSignal(1000));
+ } while (playerState.get() != MediaPlayer.PLAYER_STATE_PAUSED);
+
+ mPlayer.removePlaylistItem(0);
+ do {
+ assertTrue(onPlayerStateChangedCalled.waitForSignal(1000));
+ } while (playerState.get() != MediaPlayer.PLAYER_STATE_IDLE);
+ }
+
+ @Test
+ @MediumTest
public void skipToPlaylistItems() throws Exception {
int listSize = 5;
List<MediaItem> playlist = createPlaylist(listSize);
diff --git a/media2/player/src/main/java/androidx/media2/player/MediaPlayer.java b/media2/player/src/main/java/androidx/media2/player/MediaPlayer.java
index 87a5e81..9a44910 100644
--- a/media2/player/src/main/java/androidx/media2/player/MediaPlayer.java
+++ b/media2/player/src/main/java/androidx/media2/player/MediaPlayer.java
@@ -1375,7 +1375,16 @@
});
ArrayList<ResolvableFuture<PlayerResult>> futures = new ArrayList<>();
- if (updatedCurNextItem != null) {
+ if (curItem == null) {
+ resetInternal();
+ notifySessionPlayerCallback(new SessionPlayerCallbackNotifier() {
+ @Override
+ public void callCallback(SessionPlayer.PlayerCallback callback) {
+ callback.onPlayerStateChanged(MediaPlayer.this, PLAYER_STATE_IDLE);
+ }
+ });
+ futures.add(createFutureForResultCode(RESULT_SUCCESS));
+ } else if (updatedCurNextItem != null) {
if (updatedCurNextItem.first != null) {
futures.addAll(setMediaItemsInternal(curItem, nextItem));
} else if (updatedCurNextItem.second != null) {
@@ -2006,20 +2015,7 @@
}
mPendingFutures.clear();
}
- synchronized (mStateLock) {
- mState = PLAYER_STATE_IDLE;
- mMediaItemToBuffState.clear();
- }
- synchronized (mPlaylistLock) {
- mPlaylist.clear();
- mShuffledList.clear();
- mCurPlaylistItem = null;
- mNextPlaylistItem = null;
- mCurrentShuffleIdx = END_OF_PLAYLIST;
- mSetMediaItemCalled = false;
- }
- mAudioFocusHandler.onReset();
- mPlayer.reset();
+ resetInternal();
}
/**
@@ -2158,9 +2154,6 @@
return new VideoSize(mPlayer.getVideoWidth(), mPlayer.getVideoHeight());
}
-
-
-
/**
* @return a {@link PersistableBundle} containing the set of attributes and values
* available for the media being handled by this player instance.
@@ -3026,6 +3019,24 @@
return futures;
}
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ void resetInternal() {
+ synchronized (mStateLock) {
+ mState = PLAYER_STATE_IDLE;
+ mMediaItemToBuffState.clear();
+ }
+ synchronized (mPlaylistLock) {
+ mPlaylist.clear();
+ mShuffledList.clear();
+ mCurPlaylistItem = null;
+ mNextPlaylistItem = null;
+ mCurrentShuffleIdx = END_OF_PLAYLIST;
+ mSetMediaItemCalled = false;
+ }
+ mAudioFocusHandler.onReset();
+ mPlayer.reset();
+ }
+
private ResolvableFuture<PlayerResult> setMediaItemInternal(MediaItem item) {
ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
synchronized (mPendingCommands) {
@@ -3126,7 +3137,7 @@
Pair<MediaItem, MediaItem> updateAndGetCurrentNextItemIfNeededLocked() {
MediaItem changedCurItem = null;
MediaItem changedNextItem = null;
- if (mCurrentShuffleIdx < 0) {
+ if (mCurrentShuffleIdx < 0 || mPlaylist.isEmpty()) {
if (mCurPlaylistItem == null && mNextPlaylistItem == null) {
return null;
}
diff --git a/media2/session/version-compat-tests/current/client/build.gradle b/media2/session/version-compat-tests/current/client/build.gradle
index 51b16b8..a71c527 100644
--- a/media2/session/version-compat-tests/current/client/build.gradle
+++ b/media2/session/version-compat-tests/current/client/build.gradle
@@ -23,7 +23,7 @@
dependencies {
androidTestImplementation(project(":media2:media2-session"))
- androidTestImplementation(project(":support-media2-test-common"))
+ androidTestImplementation(project(":media2:media2-session:version-compat-tests:common"))
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
androidTestImplementation(ANDROIDX_TEST_CORE)
diff --git a/media2/session/version-compat-tests/current/service/build.gradle b/media2/session/version-compat-tests/current/service/build.gradle
index 6b66631..cebe889 100644
--- a/media2/session/version-compat-tests/current/service/build.gradle
+++ b/media2/session/version-compat-tests/current/service/build.gradle
@@ -23,7 +23,7 @@
dependencies {
androidTestImplementation(project(":media2:media2-session"))
- androidTestImplementation(project(":support-media2-test-common"))
+ androidTestImplementation(project(":media2:media2-session:version-compat-tests:common"))
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
androidTestImplementation(ANDROIDX_TEST_CORE)
diff --git a/media2/session/version-compat-tests/previous/client/build.gradle b/media2/session/version-compat-tests/previous/client/build.gradle
index b36a3c0..3fd0d17 100644
--- a/media2/session/version-compat-tests/previous/client/build.gradle
+++ b/media2/session/version-compat-tests/previous/client/build.gradle
@@ -23,7 +23,7 @@
dependencies {
androidTestImplementation("androidx.media2:media2-session:1.0.0-beta02")
- androidTestImplementation(project(":support-media2-test-common"))
+ androidTestImplementation(project(":media2:media2-session:version-compat-tests:common"))
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
androidTestImplementation(ANDROIDX_TEST_CORE)
diff --git a/media2/session/version-compat-tests/previous/service/build.gradle b/media2/session/version-compat-tests/previous/service/build.gradle
index cb5a4cf..aa5cba0 100644
--- a/media2/session/version-compat-tests/previous/service/build.gradle
+++ b/media2/session/version-compat-tests/previous/service/build.gradle
@@ -23,7 +23,7 @@
dependencies {
androidTestImplementation("androidx.media2:media2-session:1.0.0-beta02")
- androidTestImplementation(project(":support-media2-test-common"))
+ androidTestImplementation(project(":media2:media2-session:version-compat-tests:common"))
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
androidTestImplementation(ANDROIDX_TEST_CORE)
diff --git a/mediarouter/mediarouter/build.gradle b/mediarouter/mediarouter/build.gradle
index 44ed352..1b27ddf 100644
--- a/mediarouter/mediarouter/build.gradle
+++ b/mediarouter/mediarouter/build.gradle
@@ -37,7 +37,7 @@
androidTestImplementation(ANDROIDX_TEST_RUNNER)
androidTestImplementation(ANDROIDX_TEST_RULES)
androidTestImplementation(ESPRESSO_CORE, libs.exclude_for_espresso)
- androidTestImplementation project(':support-media-test-lib')
+ androidTestImplementation project(':media:version-compat-tests:lib')
}
android {
diff --git a/navigation/navigation-common/build.gradle b/navigation/navigation-common/build.gradle
index a290ab4..52998f0 100644
--- a/navigation/navigation-common/build.gradle
+++ b/navigation/navigation-common/build.gradle
@@ -31,6 +31,7 @@
implementation("androidx.core:core:1.1.0")
implementation("androidx.collection:collection:1.1.0")
+ api(KOTLIN_STDLIB)
testImplementation(JUNIT)
testImplementation(MOCKITO_CORE)
testImplementation(TRUTH)
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/FloatingWindow.java b/navigation/navigation-common/src/main/java/androidx/navigation/FloatingWindow.kt
similarity index 63%
rename from navigation/navigation-common/src/main/java/androidx/navigation/FloatingWindow.java
rename to navigation/navigation-common/src/main/java/androidx/navigation/FloatingWindow.kt
index 9ce6e88..834bc9d 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/FloatingWindow.java
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/FloatingWindow.kt
@@ -14,17 +14,18 @@
* limitations under the License.
*/
-package androidx.navigation;
+package androidx.navigation
/**
- * A marker interface for {@link NavDestination} subclasses that float above the view of other
- * destinations (i.e. {@link androidx.navigation.fragment.DialogFragmentNavigator.Destination}).
+ * A marker interface for [NavDestination] subclasses that float above the view of other
+ * destinations (i.e. [androidx.navigation.fragment.DialogFragmentNavigator.Destination]).
*
- * <p>Destinations that implement this interface will automatically be popped off the back
+ *
+ * Destinations that implement this interface will automatically be popped off the back
* stack when you navigate to a new destination.
*
- * <p>{@link androidx.navigation.NavController.OnDestinationChangedListener} instances can also
+ *
+ * [androidx.navigation.NavController.OnDestinationChangedListener] instances can also
* customize their behavior based on whether the destination is a FloatingWindow.
*/
-public interface FloatingWindow {
-}
+public interface FloatingWindow
diff --git a/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/DialogFragmentNavigator.java b/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/DialogFragmentNavigator.java
index 3505e37..d199c52 100644
--- a/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/DialogFragmentNavigator.java
+++ b/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/DialogFragmentNavigator.java
@@ -38,6 +38,8 @@
import androidx.navigation.Navigator;
import androidx.navigation.NavigatorProvider;
+import java.util.HashSet;
+
/**
* Navigator that uses {@link DialogFragment#show(FragmentManager, String)}. Every
* destination using this Navigator must set a valid DialogFragment class name with
@@ -52,6 +54,7 @@
private final Context mContext;
private final FragmentManager mFragmentManager;
private int mDialogCount = 0;
+ private final HashSet<String> mRestoredTagsAwaitingAttach = new HashSet<>();
private LifecycleEventObserver mObserver = new LifecycleEventObserver() {
@Override
@@ -145,13 +148,20 @@
if (fragment != null) {
fragment.getLifecycle().addObserver(mObserver);
} else {
- throw new IllegalStateException("DialogFragment " + index
- + " doesn't exist in the FragmentManager");
+ mRestoredTagsAwaitingAttach.add(DIALOG_TAG + index);
}
}
}
}
+ // TODO: Switch to FragmentOnAttachListener once we depend on Fragment 1.3
+ void onAttachFragment(@NonNull Fragment childFragment) {
+ boolean needToAddObserver = mRestoredTagsAwaitingAttach.remove(childFragment.getTag());
+ if (needToAddObserver) {
+ childFragment.getLifecycle().addObserver(mObserver);
+ }
+ }
+
/**
* NavDestination specific to {@link DialogFragmentNavigator}.
*/
diff --git a/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/NavHostFragment.java b/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/NavHostFragment.java
index 34c3abf..744d5f4 100644
--- a/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/NavHostFragment.java
+++ b/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/NavHostFragment.java
@@ -306,6 +306,16 @@
navController.getNavigatorProvider().addNavigator(createFragmentNavigator());
}
+ // TODO: DialogFragmentNavigator should use FragmentOnAttachListener from Fragment 1.3
+ @SuppressWarnings("deprecation")
+ @Override
+ public void onAttachFragment(@NonNull Fragment childFragment) {
+ super.onAttachFragment(childFragment);
+ DialogFragmentNavigator dialogFragmentNavigator =
+ mNavController.getNavigatorProvider().getNavigator(DialogFragmentNavigator.class);
+ dialogFragmentNavigator.onAttachFragment(childFragment);
+ }
+
@CallSuper
@Override
public void onPrimaryNavigationFragmentChanged(boolean isPrimaryNavigationFragment) {
diff --git a/navigation/navigation-runtime/src/main/java/androidx/navigation/ActivityNavigator.java b/navigation/navigation-runtime/src/main/java/androidx/navigation/ActivityNavigator.java
index b15a912..96500ef 100644
--- a/navigation/navigation-runtime/src/main/java/androidx/navigation/ActivityNavigator.java
+++ b/navigation/navigation-runtime/src/main/java/androidx/navigation/ActivityNavigator.java
@@ -21,11 +21,13 @@
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
+import android.content.res.Resources;
import android.content.res.TypedArray;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.AttributeSet;
+import android.util.Log;
import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
@@ -50,6 +52,8 @@
private static final String EXTRA_POP_EXIT_ANIM =
"android-support-navigation:ActivityNavigator:popExitAnim";
+ private static final String LOG_TAG = "ActivityNavigator";
+
private Context mContext;
private Activity mHostActivity;
@@ -160,10 +164,23 @@
}
final int destId = destination.getId();
intent.putExtra(EXTRA_NAV_CURRENT, destId);
+ Resources resources = getContext().getResources();
if (navOptions != null) {
- // For use in applyPopAnimationsToPendingTransition()
- intent.putExtra(EXTRA_POP_ENTER_ANIM, navOptions.getPopEnterAnim());
- intent.putExtra(EXTRA_POP_EXIT_ANIM, navOptions.getPopExitAnim());
+ int popEnterAnim = navOptions.getPopEnterAnim();
+ int popExitAnim = navOptions.getPopExitAnim();
+ if ((popEnterAnim != -1
+ && resources.getResourceTypeName(popEnterAnim).equals("animator"))
+ || (popExitAnim != -1
+ && resources.getResourceTypeName(popExitAnim).equals("animator"))) {
+ Log.w(LOG_TAG, "Activity destinations do not support Animator resource. Ignoring "
+ + "popEnter resource " + resources.getResourceName(popEnterAnim) + " and "
+ + "popExit resource " + resources.getResourceName(popExitAnim) + "when "
+ + "launching " + destination);
+ } else {
+ // For use in applyPopAnimationsToPendingTransition()
+ intent.putExtra(EXTRA_POP_ENTER_ANIM, popEnterAnim);
+ intent.putExtra(EXTRA_POP_EXIT_ANIM, popExitAnim);
+ }
}
if (navigatorExtras instanceof Extras) {
Extras extras = (Extras) navigatorExtras;
@@ -180,9 +197,18 @@
int enterAnim = navOptions.getEnterAnim();
int exitAnim = navOptions.getExitAnim();
if (enterAnim != -1 || exitAnim != -1) {
- enterAnim = enterAnim != -1 ? enterAnim : 0;
- exitAnim = exitAnim != -1 ? exitAnim : 0;
- mHostActivity.overridePendingTransition(enterAnim, exitAnim);
+ if (resources.getResourceTypeName(enterAnim).equals("animator")
+ || resources.getResourceTypeName(exitAnim).equals("animator")
+ ) {
+ Log.w(LOG_TAG, "Activity destinations do not support Animator resource. "
+ + "Ignoring " + "enter resource " + resources.getResourceName(enterAnim)
+ + " and exit resource " + resources.getResourceName(exitAnim) + "when "
+ + "launching " + destination);
+ } else {
+ enterAnim = enterAnim != -1 ? enterAnim : 0;
+ exitAnim = exitAnim != -1 ? exitAnim : 0;
+ mHostActivity.overridePendingTransition(enterAnim, exitAnim);
+ }
}
}
diff --git a/navigation/navigation-safe-args-generator/build.gradle b/navigation/navigation-safe-args-generator/build.gradle
index 3eb4daf..54cdb37 100644
--- a/navigation/navigation-safe-args-generator/build.gradle
+++ b/navigation/navigation-safe-args-generator/build.gradle
@@ -32,7 +32,7 @@
implementation(KOTLIN_STDLIB)
implementation(JAVAPOET)
- implementation "com.squareup:kotlinpoet:1.6.0"
+ implementation "com.squareup:kotlinpoet:1.7.2"
testImplementation(JUNIT)
testImplementation(GOOGLE_COMPILE_TESTING)
diff --git a/navigation/navigation-safe-args-generator/src/test/test-data/expected/kotlin_nav_writer_test/FunFragmentDirections.kt b/navigation/navigation-safe-args-generator/src/test/test-data/expected/kotlin_nav_writer_test/FunFragmentDirections.kt
index af9c0b8..4e1ef8c 100644
--- a/navigation/navigation-safe-args-generator/src/test/test-data/expected/kotlin_nav_writer_test/FunFragmentDirections.kt
+++ b/navigation/navigation-safe-args-generator/src/test/test-data/expected/kotlin_nav_writer_test/FunFragmentDirections.kt
@@ -4,8 +4,8 @@
import androidx.navigation.ActionOnlyNavDirections
import androidx.navigation.NavDirections
-class FunFragmentDirections private constructor() {
- companion object {
- fun next(): NavDirections = ActionOnlyNavDirections(R.id.next)
+public class FunFragmentDirections private constructor() {
+ public companion object {
+ public fun next(): NavDirections = ActionOnlyNavDirections(R.id.next)
}
}
diff --git a/navigation/navigation-safe-args-generator/src/test/test-data/expected/kotlin_nav_writer_test/LongPackageFragmentDirections.kt b/navigation/navigation-safe-args-generator/src/test/test-data/expected/kotlin_nav_writer_test/LongPackageFragmentDirections.kt
index f9cd0ae..d244af3 100644
--- a/navigation/navigation-safe-args-generator/src/test/test-data/expected/kotlin_nav_writer_test/LongPackageFragmentDirections.kt
+++ b/navigation/navigation-safe-args-generator/src/test/test-data/expected/kotlin_nav_writer_test/LongPackageFragmentDirections.kt
@@ -4,8 +4,8 @@
import androidx.navigation.ActionOnlyNavDirections
import androidx.navigation.NavDirections
-class LongPackageFragmentDirections private constructor() {
- companion object {
- fun next(): NavDirections = ActionOnlyNavDirections(R.id.next)
+public class LongPackageFragmentDirections private constructor() {
+ public companion object {
+ public fun next(): NavDirections = ActionOnlyNavDirections(R.id.next)
}
}
diff --git a/navigation/navigation-safe-args-generator/src/test/test-data/expected/kotlin_nav_writer_test/MainFragmentArgs.kt b/navigation/navigation-safe-args-generator/src/test/test-data/expected/kotlin_nav_writer_test/MainFragmentArgs.kt
index 447ecf2..379e28a 100644
--- a/navigation/navigation-safe-args-generator/src/test/test-data/expected/kotlin_nav_writer_test/MainFragmentArgs.kt
+++ b/navigation/navigation-safe-args-generator/src/test/test-data/expected/kotlin_nav_writer_test/MainFragmentArgs.kt
@@ -7,7 +7,7 @@
import java.io.Serializable
import java.lang.IllegalArgumentException
import java.lang.UnsupportedOperationException
-import java.nio.file.AccessMode
+import java.nio.`file`.AccessMode
import kotlin.Array
import kotlin.Boolean
import kotlin.Float
@@ -17,20 +17,20 @@
import kotlin.Suppress
import kotlin.jvm.JvmStatic
-data class MainFragmentArgs(
- val main: String,
- val optional: Int = -1,
- val reference: Int = R.drawable.background,
- val referenceZeroDefaultValue: Int = 0,
- val floatArg: Float = 1F,
- val floatArrayArg: FloatArray,
- val objectArrayArg: Array<ActivityInfo>,
- val boolArg: Boolean = true,
- val optionalParcelable: ActivityInfo? = null,
- val enumArg: AccessMode = AccessMode.READ
+public data class MainFragmentArgs(
+ public val main: String,
+ public val optional: Int = -1,
+ public val reference: Int = R.drawable.background,
+ public val referenceZeroDefaultValue: Int = 0,
+ public val floatArg: Float = 1F,
+ public val floatArrayArg: FloatArray,
+ public val objectArrayArg: Array<ActivityInfo>,
+ public val boolArg: Boolean = true,
+ public val optionalParcelable: ActivityInfo? = null,
+ public val enumArg: AccessMode = AccessMode.READ
) : NavArgs {
@Suppress("CAST_NEVER_SUCCEEDS")
- fun toBundle(): Bundle {
+ public fun toBundle(): Bundle {
val result = Bundle()
result.putString("main", this.main)
result.putInt("optional", this.optional)
@@ -53,10 +53,10 @@
return result
}
- companion object {
+ public companion object {
@JvmStatic
@Suppress("UNCHECKED_CAST")
- fun fromBundle(bundle: Bundle): MainFragmentArgs {
+ public fun fromBundle(bundle: Bundle): MainFragmentArgs {
bundle.setClassLoader(MainFragmentArgs::class.java.classLoader)
val __main : String?
if (bundle.containsKey("main")) {
diff --git a/navigation/navigation-safe-args-generator/src/test/test-data/expected/kotlin_nav_writer_test/MainFragmentDirections.kt b/navigation/navigation-safe-args-generator/src/test/test-data/expected/kotlin_nav_writer_test/MainFragmentDirections.kt
index e5912fb..70eb55c 100644
--- a/navigation/navigation-safe-args-generator/src/test/test-data/expected/kotlin_nav_writer_test/MainFragmentDirections.kt
+++ b/navigation/navigation-safe-args-generator/src/test/test-data/expected/kotlin_nav_writer_test/MainFragmentDirections.kt
@@ -6,14 +6,14 @@
import kotlin.Int
import kotlin.String
-class MainFragmentDirections private constructor() {
+public class MainFragmentDirections private constructor() {
private data class Next(
- val main: String,
- val optional: String = "bla"
+ public val main: String,
+ public val optional: String = "bla"
) : NavDirections {
- override fun getActionId(): Int = R.id.next
+ public override fun getActionId(): Int = R.id.next
- override fun getArguments(): Bundle {
+ public override fun getArguments(): Bundle {
val result = Bundle()
result.putString("main", this.main)
result.putString("optional", this.optional)
@@ -21,9 +21,9 @@
}
}
- companion object {
- fun previous(): NavDirections = ActionOnlyNavDirections(R.id.previous)
+ public companion object {
+ public fun previous(): NavDirections = ActionOnlyNavDirections(R.id.previous)
- fun next(main: String, optional: String = "bla"): NavDirections = Next(main, optional)
+ public fun next(main: String, optional: String = "bla"): NavDirections = Next(main, optional)
}
}
diff --git a/navigation/navigation-safe-args-generator/src/test/test-data/expected/kotlin_nav_writer_test/Next.kt b/navigation/navigation-safe-args-generator/src/test/test-data/expected/kotlin_nav_writer_test/Next.kt
index 272d94c..0358452 100644
--- a/navigation/navigation-safe-args-generator/src/test/test-data/expected/kotlin_nav_writer_test/Next.kt
+++ b/navigation/navigation-safe-args-generator/src/test/test-data/expected/kotlin_nav_writer_test/Next.kt
@@ -11,18 +11,18 @@
import kotlin.Suppress
private data class Next(
- val main: String,
- val mainInt: Int,
- val optional: String = "bla",
- val optionalInt: Int = 239,
- val optionalParcelable: ActivityInfo? = null,
- val parcelable: ActivityInfo,
- val innerData: ActivityInfo.WindowLayout
+ public val main: String,
+ public val mainInt: Int,
+ public val optional: String = "bla",
+ public val optionalInt: Int = 239,
+ public val optionalParcelable: ActivityInfo? = null,
+ public val parcelable: ActivityInfo,
+ public val innerData: ActivityInfo.WindowLayout
) : NavDirections {
- override fun getActionId(): Int = R.id.next
+ public override fun getActionId(): Int = R.id.next
@Suppress("CAST_NEVER_SUCCEEDS")
- override fun getArguments(): Bundle {
+ public override fun getArguments(): Bundle {
val result = Bundle()
result.putString("main", this.main)
result.putInt("mainInt", this.mainInt)
diff --git a/navigation/navigation-safe-args-generator/src/test/test-data/expected/kotlin_nav_writer_test/ReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongNameMainFragmentArgs.kt b/navigation/navigation-safe-args-generator/src/test/test-data/expected/kotlin_nav_writer_test/ReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongNameMainFragmentArgs.kt
index 6208f47..ab530ca 100644
--- a/navigation/navigation-safe-args-generator/src/test/test-data/expected/kotlin_nav_writer_test/ReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongNameMainFragmentArgs.kt
+++ b/navigation/navigation-safe-args-generator/src/test/test-data/expected/kotlin_nav_writer_test/ReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongNameMainFragmentArgs.kt
@@ -4,17 +4,17 @@
import androidx.navigation.NavArgs
import kotlin.jvm.JvmStatic
-data class
+public data class
ReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongNameFragmentArgs
: NavArgs {
- fun toBundle(): Bundle {
+ public fun toBundle(): Bundle {
val result = Bundle()
return result
}
- companion object {
+ public companion object {
@JvmStatic
- fun fromBundle(bundle: Bundle):
+ public fun fromBundle(bundle: Bundle):
ReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongNameFragmentArgs {
bundle.setClassLoader(ReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongNameFragmentArgs::class.java.classLoader)
return ReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongNameFragmentArgs()
diff --git a/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested/LoginDirections.kt b/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested/LoginDirections.kt
index 8c5779c..9db5a05 100644
--- a/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested/LoginDirections.kt
+++ b/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested/LoginDirections.kt
@@ -3,8 +3,8 @@
import androidx.navigation.ActionOnlyNavDirections
import androidx.navigation.NavDirections
-class LoginDirections private constructor() {
- companion object {
- fun actionDone(): NavDirections = ActionOnlyNavDirections(R.id.action_done)
+public class LoginDirections private constructor() {
+ public companion object {
+ public fun actionDone(): NavDirections = ActionOnlyNavDirections(R.id.action_done)
}
}
diff --git a/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested/LoginFragmentDirections.kt b/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested/LoginFragmentDirections.kt
index 2f8e844..bf2e570 100644
--- a/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested/LoginFragmentDirections.kt
+++ b/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested/LoginFragmentDirections.kt
@@ -5,10 +5,10 @@
import foo.LoginDirections
import foo.R
-class LoginFragmentDirections private constructor() {
- companion object {
- fun register(): NavDirections = ActionOnlyNavDirections(R.id.register)
+public class LoginFragmentDirections private constructor() {
+ public companion object {
+ public fun register(): NavDirections = ActionOnlyNavDirections(R.id.register)
- fun actionDone(): NavDirections = LoginDirections.actionDone()
+ public fun actionDone(): NavDirections = LoginDirections.actionDone()
}
}
diff --git a/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested/MainFragmentDirections.kt b/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested/MainFragmentDirections.kt
index 392b4a0..12006eb 100644
--- a/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested/MainFragmentDirections.kt
+++ b/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested/MainFragmentDirections.kt
@@ -4,8 +4,8 @@
import androidx.navigation.NavDirections
import foo.R
-class MainFragmentDirections private constructor() {
- companion object {
- fun startLogin(): NavDirections = ActionOnlyNavDirections(R.id.start_login)
+public class MainFragmentDirections private constructor() {
+ public companion object {
+ public fun startLogin(): NavDirections = ActionOnlyNavDirections(R.id.start_login)
}
}
diff --git a/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested/RegisterFragmentDirections.kt b/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested/RegisterFragmentDirections.kt
index fa5ad14..35314de 100644
--- a/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested/RegisterFragmentDirections.kt
+++ b/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested/RegisterFragmentDirections.kt
@@ -3,8 +3,8 @@
import androidx.navigation.NavDirections
import foo.LoginDirections
-class RegisterFragmentDirections private constructor() {
- companion object {
- fun actionDone(): NavDirections = LoginDirections.actionDone()
+public class RegisterFragmentDirections private constructor() {
+ public companion object {
+ public fun actionDone(): NavDirections = LoginDirections.actionDone()
}
}
diff --git a/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested_overridden_action/InnerSettingsDirections.kt b/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested_overridden_action/InnerSettingsDirections.kt
index 66ea217..1790afe 100644
--- a/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested_overridden_action/InnerSettingsDirections.kt
+++ b/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested_overridden_action/InnerSettingsDirections.kt
@@ -5,22 +5,23 @@
import kotlin.Int
import kotlin.String
-class InnerSettingsDirections private constructor() {
+public class InnerSettingsDirections private constructor() {
private data class Exit(
- val exitReason: Int
+ public val exitReason: Int
) : NavDirections {
- override fun getActionId(): Int = R.id.exit
+ public override fun getActionId(): Int = R.id.exit
- override fun getArguments(): Bundle {
+ public override fun getArguments(): Bundle {
val result = Bundle()
result.putInt("exitReason", this.exitReason)
return result
}
}
- companion object {
- fun exit(exitReason: Int): NavDirections = Exit(exitReason)
+ public companion object {
+ public fun exit(exitReason: Int): NavDirections = Exit(exitReason)
- fun main(enterReason: String = "DEFAULT"): NavDirections = SettingsDirections.main(enterReason)
+ public fun main(enterReason: String = "DEFAULT"): NavDirections =
+ SettingsDirections.main(enterReason)
}
}
diff --git a/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested_overridden_action/InnerSettingsFragmentDirections.kt b/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested_overridden_action/InnerSettingsFragmentDirections.kt
index 0a9b8b4..af5d028 100644
--- a/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested_overridden_action/InnerSettingsFragmentDirections.kt
+++ b/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested_overridden_action/InnerSettingsFragmentDirections.kt
@@ -5,11 +5,11 @@
import kotlin.Int
import kotlin.String
-class InnerSettingsFragmentDirections private constructor() {
- companion object {
- fun exit(exitReason: Int): NavDirections = InnerSettingsDirections.exit(exitReason)
+public class InnerSettingsFragmentDirections private constructor() {
+ public companion object {
+ public fun exit(exitReason: Int): NavDirections = InnerSettingsDirections.exit(exitReason)
- fun main(enterReason: String = "DEFAULT"): NavDirections =
+ public fun main(enterReason: String = "DEFAULT"): NavDirections =
InnerSettingsDirections.main(enterReason)
}
}
diff --git a/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested_overridden_action/MainFragmentDirections.kt b/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested_overridden_action/MainFragmentDirections.kt
index 392b4a0..12006eb 100644
--- a/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested_overridden_action/MainFragmentDirections.kt
+++ b/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested_overridden_action/MainFragmentDirections.kt
@@ -4,8 +4,8 @@
import androidx.navigation.NavDirections
import foo.R
-class MainFragmentDirections private constructor() {
- companion object {
- fun startLogin(): NavDirections = ActionOnlyNavDirections(R.id.start_login)
+public class MainFragmentDirections private constructor() {
+ public companion object {
+ public fun startLogin(): NavDirections = ActionOnlyNavDirections(R.id.start_login)
}
}
diff --git a/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested_overridden_action/SettingsDirections.kt b/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested_overridden_action/SettingsDirections.kt
index 3697f81..25f9192 100644
--- a/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested_overridden_action/SettingsDirections.kt
+++ b/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested_overridden_action/SettingsDirections.kt
@@ -5,13 +5,13 @@
import kotlin.Int
import kotlin.String
-class SettingsDirections private constructor() {
+public class SettingsDirections private constructor() {
private data class Main(
- val enterReason: String = "DEFAULT"
+ public val enterReason: String = "DEFAULT"
) : NavDirections {
- override fun getActionId(): Int = R.id.main
+ public override fun getActionId(): Int = R.id.main
- override fun getArguments(): Bundle {
+ public override fun getArguments(): Bundle {
val result = Bundle()
result.putString("enterReason", this.enterReason)
return result
@@ -19,20 +19,20 @@
}
private data class Exit(
- val exitReason: String = "DEFAULT"
+ public val exitReason: String = "DEFAULT"
) : NavDirections {
- override fun getActionId(): Int = R.id.exit
+ public override fun getActionId(): Int = R.id.exit
- override fun getArguments(): Bundle {
+ public override fun getArguments(): Bundle {
val result = Bundle()
result.putString("exitReason", this.exitReason)
return result
}
}
- companion object {
- fun main(enterReason: String = "DEFAULT"): NavDirections = Main(enterReason)
+ public companion object {
+ public fun main(enterReason: String = "DEFAULT"): NavDirections = Main(enterReason)
- fun exit(exitReason: String = "DEFAULT"): NavDirections = Exit(exitReason)
+ public fun exit(exitReason: String = "DEFAULT"): NavDirections = Exit(exitReason)
}
}
diff --git a/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested_overridden_action/SettingsFragmentDirections.kt b/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested_overridden_action/SettingsFragmentDirections.kt
index f1f0ab7..0e0f0ad 100644
--- a/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested_overridden_action/SettingsFragmentDirections.kt
+++ b/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested_overridden_action/SettingsFragmentDirections.kt
@@ -4,10 +4,12 @@
import foo.SettingsDirections
import kotlin.String
-class SettingsFragmentDirections private constructor() {
- companion object {
- fun main(enterReason: String = "DEFAULT"): NavDirections = SettingsDirections.main(enterReason)
+public class SettingsFragmentDirections private constructor() {
+ public companion object {
+ public fun main(enterReason: String = "DEFAULT"): NavDirections =
+ SettingsDirections.main(enterReason)
- fun exit(exitReason: String = "DEFAULT"): NavDirections = SettingsDirections.exit(exitReason)
+ public fun exit(exitReason: String = "DEFAULT"): NavDirections =
+ SettingsDirections.exit(exitReason)
}
}
diff --git a/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested_same_action/MainFragmentDirections.kt b/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested_same_action/MainFragmentDirections.kt
index 392b4a0..12006eb 100644
--- a/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested_same_action/MainFragmentDirections.kt
+++ b/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested_same_action/MainFragmentDirections.kt
@@ -4,8 +4,8 @@
import androidx.navigation.NavDirections
import foo.R
-class MainFragmentDirections private constructor() {
- companion object {
- fun startLogin(): NavDirections = ActionOnlyNavDirections(R.id.start_login)
+public class MainFragmentDirections private constructor() {
+ public companion object {
+ public fun startLogin(): NavDirections = ActionOnlyNavDirections(R.id.start_login)
}
}
diff --git a/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested_same_action/SettingsDirections.kt b/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested_same_action/SettingsDirections.kt
index 11801b9..e8c8d17 100644
--- a/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested_same_action/SettingsDirections.kt
+++ b/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested_same_action/SettingsDirections.kt
@@ -5,20 +5,20 @@
import kotlin.Int
import kotlin.String
-class SettingsDirections private constructor() {
+public class SettingsDirections private constructor() {
private data class Exit(
- val exitReason: String = "DEFAULT"
+ public val exitReason: String = "DEFAULT"
) : NavDirections {
- override fun getActionId(): Int = R.id.exit
+ public override fun getActionId(): Int = R.id.exit
- override fun getArguments(): Bundle {
+ public override fun getArguments(): Bundle {
val result = Bundle()
result.putString("exitReason", this.exitReason)
return result
}
}
- companion object {
- fun exit(exitReason: String = "DEFAULT"): NavDirections = Exit(exitReason)
+ public companion object {
+ public fun exit(exitReason: String = "DEFAULT"): NavDirections = Exit(exitReason)
}
}
diff --git a/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested_same_action/SettingsFragmentDirections.kt b/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested_same_action/SettingsFragmentDirections.kt
index 458d1e5..dee9a87 100644
--- a/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested_same_action/SettingsFragmentDirections.kt
+++ b/navigation/navigation-safe-args-generator/src/test/test-data/expected/nav_generator_test/kotlin/nested_same_action/SettingsFragmentDirections.kt
@@ -6,20 +6,20 @@
import kotlin.Int
import kotlin.String
-class SettingsFragmentDirections private constructor() {
+public class SettingsFragmentDirections private constructor() {
private data class Exit(
- val exitReason: String = "DIFFERENT"
+ public val exitReason: String = "DIFFERENT"
) : NavDirections {
- override fun getActionId(): Int = R.id.exit
+ public override fun getActionId(): Int = R.id.exit
- override fun getArguments(): Bundle {
+ public override fun getArguments(): Bundle {
val result = Bundle()
result.putString("exitReason", this.exitReason)
return result
}
}
- companion object {
- fun exit(exitReason: String = "DIFFERENT"): NavDirections = Exit(exitReason)
+ public companion object {
+ public fun exit(exitReason: String = "DIFFERENT"): NavDirections = Exit(exitReason)
}
}
diff --git a/navigation/navigation-ui/src/main/java/androidx/navigation/ui/NavigationUI.java b/navigation/navigation-ui/src/main/java/androidx/navigation/ui/NavigationUI.java
index e08f76a..4609bef 100644
--- a/navigation/navigation-ui/src/main/java/androidx/navigation/ui/NavigationUI.java
+++ b/navigation/navigation-ui/src/main/java/androidx/navigation/ui/NavigationUI.java
@@ -30,6 +30,7 @@
import androidx.appcompat.widget.Toolbar;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.customview.widget.Openable;
+import androidx.navigation.ActivityNavigator;
import androidx.navigation.NavController;
import androidx.navigation.NavDestination;
import androidx.navigation.NavGraph;
@@ -73,11 +74,20 @@
public static boolean onNavDestinationSelected(@NonNull MenuItem item,
@NonNull NavController navController) {
NavOptions.Builder builder = new NavOptions.Builder()
- .setLaunchSingleTop(true)
- .setEnterAnim(R.animator.nav_default_enter_anim)
- .setExitAnim(R.animator.nav_default_exit_anim)
- .setPopEnterAnim(R.animator.nav_default_pop_enter_anim)
- .setPopExitAnim(R.animator.nav_default_pop_exit_anim);
+ .setLaunchSingleTop(true);
+ if (navController.getCurrentDestination().getParent().findNode(item.getItemId())
+ instanceof ActivityNavigator.Destination) {
+ builder.setEnterAnim(R.anim.nav_default_enter_anim)
+ .setExitAnim(R.anim.nav_default_exit_anim)
+ .setPopEnterAnim(R.anim.nav_default_pop_enter_anim)
+ .setPopExitAnim(R.anim.nav_default_pop_exit_anim);
+
+ } else {
+ builder.setEnterAnim(R.animator.nav_default_enter_anim)
+ .setExitAnim(R.animator.nav_default_exit_anim)
+ .setPopEnterAnim(R.animator.nav_default_pop_enter_anim)
+ .setPopExitAnim(R.animator.nav_default_pop_exit_anim);
+ }
if ((item.getOrder() & Menu.CATEGORY_SECONDARY) == 0) {
builder.setPopUpTo(findStartDestination(navController.getGraph()).getId(), false);
}
diff --git a/paging/common/src/main/kotlin/androidx/paging/PageFetcher.kt b/paging/common/src/main/kotlin/androidx/paging/PageFetcher.kt
index bdd752a..376fb76 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PageFetcher.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PageFetcher.kt
@@ -65,42 +65,61 @@
@OptIn(ExperimentalPagingApi::class)
emit(remoteMediatorAccessor?.initialize() == LAUNCH_INITIAL_REFRESH)
}
- .scan(null) {
- previousGeneration: PageFetcherSnapshot<Key, Value>?, triggerRemoteRefresh ->
- var pagingSource = generateNewPagingSource(previousGeneration?.pagingSource)
+ .scan(null) { previousGeneration: GenerationInfo<Key, Value>?,
+ triggerRemoteRefresh: Boolean ->
+ var pagingSource = generateNewPagingSource(
+ previousGeneration?.snapshot?.pagingSource
+ )
while (pagingSource.invalid) {
- pagingSource = generateNewPagingSource(previousGeneration?.pagingSource)
+ pagingSource = generateNewPagingSource(
+ previousGeneration?.snapshot?.pagingSource
+ )
+ }
+
+ var previousPagingState = previousGeneration?.snapshot?.refreshKeyInfo()
+ // If previous generation was invalidated before anchorPosition was established,
+ // re-use last PagingState that successfully loaded pages and has an anchorPosition.
+ // This prevents rapid invalidation from deleting the anchorPosition if the
+ // previous generation didn't have time to load before getting invalidated. We
+ // check for anchorPosition before overriding to prevent empty PagingState from
+ // overriding a PagingState with pages loaded, but no anchorPosition.
+ if (previousPagingState?.anchorPosition == null &&
+ previousGeneration?.state?.anchorPosition != null
+ ) {
+ previousPagingState = previousGeneration.state
}
@OptIn(ExperimentalPagingApi::class)
- val initialKey: Key? = previousGeneration?.refreshKeyInfo()
- ?.let { pagingSource.getRefreshKey(it) }
+ val initialKey: Key? = previousPagingState?.let { pagingSource.getRefreshKey(it) }
?: initialKey
- previousGeneration?.close()
+ previousGeneration?.snapshot?.close()
- PageFetcherSnapshot<Key, Value>(
- initialKey = initialKey,
- pagingSource = pagingSource,
- config = config,
- retryFlow = retryChannel.asFlow(),
- // Only trigger remote refresh on refresh signals that do not originate from
- // initialization or PagingSource invalidation.
- triggerRemoteRefresh = triggerRemoteRefresh,
- remoteMediatorConnection = remoteMediatorAccessor,
- invalidate = this@PageFetcher::refresh
+ GenerationInfo(
+ snapshot = PageFetcherSnapshot<Key, Value>(
+ initialKey = initialKey,
+ pagingSource = pagingSource,
+ config = config,
+ retryFlow = retryChannel.asFlow(),
+ // Only trigger remote refresh on refresh signals that do not originate from
+ // initialization or PagingSource invalidation.
+ triggerRemoteRefresh = triggerRemoteRefresh,
+ remoteMediatorConnection = remoteMediatorAccessor,
+ invalidate = this@PageFetcher::refresh
+ ),
+ state = previousPagingState,
)
}
.filterNotNull()
.mapLatest { generation ->
val downstreamFlow = if (remoteMediatorAccessor == null) {
- generation.pageEventFlow
+ generation.snapshot.pageEventFlow
} else {
- generation.injectRemoteEvents(remoteMediatorAccessor)
+ generation.snapshot.injectRemoteEvents(remoteMediatorAccessor)
}
PagingData(
flow = downstreamFlow,
- receiver = PagerUiReceiver(generation, retryChannel)
+ receiver = PagerUiReceiver(generation.snapshot, retryChannel)
)
}
.collect { send(it) }
@@ -196,4 +215,9 @@
override fun refresh() = this@PageFetcher.refresh()
}
+
+ private class GenerationInfo<Key : Any, Value : Any>(
+ val snapshot: PageFetcherSnapshot<Key, Value>,
+ val state: PagingState<Key, Value>?
+ )
}
diff --git a/paging/common/src/main/kotlin/androidx/paging/PagingState.kt b/paging/common/src/main/kotlin/androidx/paging/PagingState.kt
index 053cb41..df2899d 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PagingState.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PagingState.kt
@@ -126,6 +126,11 @@
*/
fun lastItemOrNull(): Value? = pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
+ override fun toString(): String {
+ return "PagingState(pages=$pages, anchorPosition=$anchorPosition, config=$config, " +
+ "leadingPlaceholderCount=$leadingPlaceholderCount)"
+ }
+
internal inline fun <T> anchorPositionToPagedIndices(
anchorPosition: Int,
block: (pageIndex: Int, index: Int) -> T
diff --git a/paging/common/src/test/kotlin/androidx/paging/PageFetcherTest.kt b/paging/common/src/test/kotlin/androidx/paging/PageFetcherTest.kt
index 85855ef..49de1d5 100644
--- a/paging/common/src/test/kotlin/androidx/paging/PageFetcherTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/PageFetcherTest.kt
@@ -432,6 +432,128 @@
assertEquals(1, invalidatesFromAdapter)
job.cancel()
}
+
+ @Test
+ fun invalidateBeforeAccessPreservesPagingState() = testScope.runBlockingTest {
+ pauseDispatcher {
+ val config = PagingConfig(
+ pageSize = 1,
+ prefetchDistance = 1,
+ enablePlaceholders = true,
+ initialLoadSize = 3,
+ )
+ val pagingSources = mutableListOf<TestPagingSource>()
+ val pageFetcher = PageFetcher(
+ pagingSourceFactory = {
+ TestPagingSource(loadDelay = 1000).also {
+ pagingSources.add(it)
+ }
+ },
+ initialKey = 50,
+ config = config,
+ )
+
+ lateinit var pagingData: PagingData<Int>
+ val job = launch() {
+ pageFetcher.flow.collectLatest {
+ pagingData = it
+ it.flow.collect { }
+ }
+ }
+
+ advanceUntilIdle()
+
+ // Trigger access to allow PagingState to get populated for next generation.
+ pagingData.receiver.accessHint(
+ ViewportHint(
+ pageOffset = 0,
+ indexInPage = 1,
+ presentedItemsBefore = 1,
+ presentedItemsAfter = 1,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0,
+ )
+ )
+ advanceUntilIdle()
+
+ // Invalidate first generation, instantiating second generation.
+ pagingSources[0].invalidate()
+
+ // Invalidate second generation before it has a chance to complete initial load.
+ advanceTimeBy(500)
+ pagingSources[1].invalidate()
+
+ // Wait for all non-canceled loads to complete.
+ advanceUntilIdle()
+
+ // Verify 3 generations were instantiated.
+ assertThat(pagingSources.size).isEqualTo(3)
+
+ // First generation should use initialKey.
+ assertThat(pagingSources[0].getRefreshKeyCalls).isEmpty()
+
+ // Second generation should receive getRefreshKey call with state from first generation.
+ assertThat(pagingSources[1].getRefreshKeyCalls).isEqualTo(
+ listOf(
+ PagingState(
+ pages = pagingSources[0].loadedPages,
+ anchorPosition = 51,
+ config = config,
+ leadingPlaceholderCount = 50,
+ )
+ )
+ )
+
+ // Verify second generation was invalidated before any pages loaded.
+ assertThat(pagingSources[1].loadedPages).isEmpty()
+
+ // Third generation should receive getRefreshKey call with state from first generation.
+ assertThat(pagingSources[0].loadedPages.size).isEqualTo(1)
+ assertThat(pagingSources[2].getRefreshKeyCalls).isEqualTo(
+ listOf(
+ PagingState(
+ pages = pagingSources[0].loadedPages,
+ anchorPosition = 51,
+ config = config,
+ leadingPlaceholderCount = 50,
+ )
+ )
+ )
+
+ advanceUntilIdle()
+ // Trigger APPEND in third generation.
+ pagingData.receiver.accessHint(
+ ViewportHint(
+ pageOffset = 0,
+ indexInPage = 2,
+ presentedItemsBefore = 2,
+ presentedItemsAfter = 0,
+ originalPageOffsetFirst = 0,
+ originalPageOffsetLast = 0,
+ )
+ )
+ advanceUntilIdle()
+
+ // Invalidate third generation, instantiating fourth generation with new PagingState.
+ pagingSources[2].invalidate()
+ advanceUntilIdle()
+
+ // Fourth generation should receive getRefreshKey call with state from third generation.
+ assertThat(pagingSources[2].loadedPages.size).isEqualTo(2)
+ assertThat(pagingSources[3].getRefreshKeyCalls).isEqualTo(
+ listOf(
+ PagingState(
+ pages = pagingSources[2].loadedPages,
+ anchorPosition = 53,
+ config = config,
+ leadingPlaceholderCount = 51,
+ )
+ )
+ )
+
+ job.cancel()
+ }
+ }
}
internal class FetcherState<T : Any>(
diff --git a/paging/compose/build.gradle b/paging/compose/build.gradle
index c32f2f1..58ff167 100644
--- a/paging/compose/build.gradle
+++ b/paging/compose/build.gradle
@@ -38,6 +38,7 @@
api("androidx.paging:paging-common-ktx:3.0.0-alpha06")
androidTestImplementation project(':ui:ui-test')
+ androidTestImplementation project(':internal-testutils-paging')
androidTestImplementation(ANDROIDX_TEST_RUNNER)
androidTestImplementation(JUNIT)
androidTestImplementation(TRUTH)
diff --git a/paging/compose/src/androidTest/java/androidx/paging/compose/LazyPagingItemsTest.kt b/paging/compose/src/androidTest/java/androidx/paging/compose/LazyPagingItemsTest.kt
index bee1d72..dc85a99 100644
--- a/paging/compose/src/androidTest/java/androidx/paging/compose/LazyPagingItemsTest.kt
+++ b/paging/compose/src/androidTest/java/androidx/paging/compose/LazyPagingItemsTest.kt
@@ -30,8 +30,10 @@
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingSource
+import androidx.paging.TestPagingSource
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
+import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -42,47 +44,30 @@
@get:Rule
val rule = createComposeRule()
- val items = (1..10).toList().map { "$it" }
+ val items = (1..10).toList().map { it }
- val pagingSource = object : PagingSource<Int, String>() {
- override suspend fun load(params: LoadParams<Int>): LoadResult<Int, String> {
- val pageNumber = params.key ?: 0
- val prevKey = if (pageNumber > 0) pageNumber - 1 else null
- val nextKey = if (pageNumber < items.size) pageNumber + 1 else null
- val subList = if (pageNumber < items.size) {
- items.subList(pageNumber, pageNumber + 1)
- } else {
- emptyList()
- }
-
- return LoadResult.Page(
- data = subList,
- prevKey = prevKey,
- nextKey = nextKey
- )
- }
- }
-
- val pager = Pager(
- PagingConfig(
+ private fun createPager(
+ config: PagingConfig = PagingConfig(
pageSize = 1,
- enablePlaceholders = true,
- maxSize = 200
- )
- ) {
- pagingSource
+ enablePlaceholders = false,
+ maxSize = 200,
+ initialLoadSize = 3,
+ prefetchDistance = 1,
+ ),
+ pagingSourceFactory: () -> PagingSource<Int, Int> = {
+ TestPagingSource(items = items, loadDelay = 0)
+ }
+ ): Pager<Int, Int> {
+ return Pager(config = config, pagingSourceFactory = pagingSourceFactory)
}
@Test
fun lazyPagingColumnShowsItems() {
rule.setContent {
- val lazyPagingItems = pager.flow.collectAsLazyPagingItems()
+ val lazyPagingItems = createPager().flow.collectAsLazyPagingItems()
LazyColumn(Modifier.preferredHeight(200.dp)) {
items(lazyPagingItems) {
- Spacer(
- Modifier.preferredHeight(101.dp).fillParentMaxWidth()
- .testTag("$it")
- )
+ Spacer(Modifier.preferredHeight(101.dp).fillParentMaxWidth().testTag("$it"))
}
}
}
@@ -105,7 +90,7 @@
@Test
fun lazyPagingColumnShowsIndexedItems() {
rule.setContent {
- val lazyPagingItems = pager.flow.collectAsLazyPagingItems()
+ val lazyPagingItems = createPager().flow.collectAsLazyPagingItems()
LazyColumn(Modifier.preferredHeight(200.dp)) {
itemsIndexed(lazyPagingItems) { index, item ->
Spacer(
@@ -134,13 +119,10 @@
@Test
fun lazyPagingRowShowsItems() {
rule.setContent {
- val lazyPagingItems = pager.flow.collectAsLazyPagingItems()
+ val lazyPagingItems = createPager().flow.collectAsLazyPagingItems()
LazyRow(Modifier.preferredWidth(200.dp)) {
items(lazyPagingItems) {
- Spacer(
- Modifier.preferredWidth(101.dp).fillParentMaxHeight()
- .testTag("$it")
- )
+ Spacer(Modifier.preferredWidth(101.dp).fillParentMaxHeight().testTag("$it"))
}
}
}
@@ -163,7 +145,7 @@
@Test
fun lazyPagingRowShowsIndexedItems() {
rule.setContent {
- val lazyPagingItems = pager.flow.collectAsLazyPagingItems()
+ val lazyPagingItems = createPager().flow.collectAsLazyPagingItems()
LazyRow(Modifier.preferredWidth(200.dp)) {
itemsIndexed(lazyPagingItems) { index, item ->
Spacer(
@@ -188,4 +170,96 @@
rule.onNodeWithTag("3-4")
.assertDoesNotExist()
}
+
+ @Test
+ fun snapshot() {
+ lateinit var lazyPagingItems: LazyPagingItems<Int>
+ rule.setContent {
+ lazyPagingItems = createPager().flow.collectAsLazyPagingItems()
+ }
+
+ // Trigger page fetch until all items are loaded
+ for (i in items.indices) {
+ lazyPagingItems.get(i)
+ rule.waitForIdle()
+ }
+
+ assertThat(lazyPagingItems.snapshot()).isEqualTo(items)
+ }
+
+ @Test
+ fun peek() {
+ lateinit var lazyPagingItems: LazyPagingItems<Int>
+ rule.setContent {
+ lazyPagingItems = createPager().flow.collectAsLazyPagingItems()
+ }
+
+ // Trigger page fetch until all items 0-6 are loaded
+ for (i in 0..4) {
+ lazyPagingItems.get(i)
+ rule.waitForIdle()
+ }
+
+ assertThat(lazyPagingItems.itemCount).isEqualTo(6)
+ for (i in 0..4) {
+ assertThat(lazyPagingItems.peek(i)).isEqualTo(items[i])
+ }
+ // Verify peek does not trigger page fetch.
+ assertThat(lazyPagingItems.itemCount).isEqualTo(6)
+ }
+
+ @Test
+ fun retry() {
+ var factoryCallCount = 0
+ lateinit var pagingSource: TestPagingSource
+ val pager = createPager {
+ factoryCallCount++
+ pagingSource = TestPagingSource(items = items, loadDelay = 0)
+ if (factoryCallCount == 1) {
+ pagingSource.errorNextLoad = true
+ }
+ pagingSource
+ }
+
+ lateinit var lazyPagingItems: LazyPagingItems<Int>
+ rule.setContent {
+ lazyPagingItems = pager.flow.collectAsLazyPagingItems()
+ }
+
+ assertThat(lazyPagingItems.snapshot()).isEmpty()
+
+ lazyPagingItems.retry()
+ rule.waitForIdle()
+
+ assertThat(lazyPagingItems.snapshot()).isNotEmpty()
+ // Verify retry does not invalidate.
+ assertThat(factoryCallCount).isEqualTo(1)
+ }
+
+ @Test
+ fun refresh() {
+ var factoryCallCount = 0
+ lateinit var pagingSource: TestPagingSource
+ val pager = createPager {
+ factoryCallCount++
+ pagingSource = TestPagingSource(items = items, loadDelay = 0)
+ if (factoryCallCount == 1) {
+ pagingSource.errorNextLoad = true
+ }
+ pagingSource
+ }
+
+ lateinit var lazyPagingItems: LazyPagingItems<Int>
+ rule.setContent {
+ lazyPagingItems = pager.flow.collectAsLazyPagingItems()
+ }
+
+ assertThat(lazyPagingItems.snapshot()).isEmpty()
+
+ lazyPagingItems.refresh()
+ rule.waitForIdle()
+
+ assertThat(lazyPagingItems.snapshot()).isNotEmpty()
+ assertThat(factoryCallCount).isEqualTo(2)
+ }
}
\ No newline at end of file
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/JavaPoetExt.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/JavaPoetExt.kt
index 8b3859b..268a5f7 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/JavaPoetExt.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/JavaPoetExt.kt
@@ -63,6 +63,28 @@
}
/**
+ * Returns the unboxed TypeName for this if it can be unboxed, otherwise, returns this.
+ */
+internal fun TypeName.tryUnbox(): TypeName {
+ return if (isBoxedPrimitive) {
+ unbox()
+ } else {
+ this
+ }
+}
+
+/**
+ * Returns the boxed TypeName for this if it can be unboxed, otherwise, returns this.
+ */
+internal fun TypeName.tryBox(): TypeName {
+ return try {
+ box()
+ } catch (err: AssertionError) {
+ this
+ }
+}
+
+/**
* Helper class to create overrides for XExecutableElements with final parameters and correct
* parameter names read from Kotlin Metadata.
*/
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/kotlin/JvmDescriptorUtils.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/kotlin/JvmDescriptorUtils.kt
index 2105173..05f2e59 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/kotlin/JvmDescriptorUtils.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/kotlin/JvmDescriptorUtils.kt
@@ -17,6 +17,9 @@
package androidx.room.compiler.processing.javac.kotlin
import com.google.auto.common.MoreTypes
+import com.squareup.javapoet.ArrayTypeName
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.TypeName
import javax.lang.model.element.Element
import javax.lang.model.element.ExecutableElement
import javax.lang.model.element.NestingKind
@@ -95,6 +98,53 @@
else -> error("Unknown primitive type $this")
}
+// see https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3.2-200
+internal fun String.typeNameFromJvmSignature(): TypeName {
+ check(isNotEmpty())
+ return when (this[0]) {
+ 'B' -> TypeName.BYTE
+ 'C' -> TypeName.CHAR
+ 'D' -> TypeName.DOUBLE
+ 'F' -> TypeName.FLOAT
+ 'I' -> TypeName.INT
+ 'J' -> TypeName.LONG
+ 'S' -> TypeName.SHORT
+ 'Z' -> TypeName.BOOLEAN
+ 'L' -> {
+ val end = lastIndexOf(";")
+ check(end > 0) {
+ "invalid input $this"
+ }
+ val simpleNamesSeparator = lastIndexOf('/')
+ val simpleNamesStart = if (simpleNamesSeparator < 0) {
+ 1 // first char is 'L'
+ } else {
+ simpleNamesSeparator + 1
+ }
+ val packageName = if (simpleNamesSeparator < 0) {
+ // no package name
+ ""
+ } else {
+ substring(1, simpleNamesSeparator).replace('/', '.')
+ }
+ val firstSimpleNameSeparator = indexOf('$', startIndex = simpleNamesStart)
+ return if (firstSimpleNameSeparator < 0) {
+ // not nested
+ ClassName.get(packageName, substring(simpleNamesStart, end))
+ } else {
+ // nested class
+ val firstSimpleName = substring(simpleNamesStart, firstSimpleNameSeparator)
+ val restOfSimpleNames = substring(firstSimpleNameSeparator + 1, end)
+ .split('$')
+ .toTypedArray()
+ ClassName.get(packageName, firstSimpleName, *restOfSimpleNames)
+ }
+ }
+ '[' -> ArrayTypeName.of(substring(1).typeNameFromJvmSignature())
+ else -> error("unexpected jvm signature $this")
+ }
+}
+
internal fun TypeMirror.descriptor(): String = accept(JvmDescriptorTypeVisitor, Unit)
@Suppress("unused")
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeExt.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeExt.kt
index fe0ba08..7a39e00 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeExt.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeExt.kt
@@ -118,4 +118,8 @@
return checkNotNull(type?.resolve()) {
"KSTypeArgument.type should not have been null, please file a bug. $this"
}
+}
+
+internal fun KSTypeReference.isTypeParameterReference(): Boolean {
+ return this.resolve().declaration is KSTypeParameter
}
\ No newline at end of file
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspAnnotationBox.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspAnnotationBox.kt
index 6df5430..4e308fb 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspAnnotationBox.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspAnnotationBox.kt
@@ -30,12 +30,12 @@
) : XAnnotationBox<T> {
override fun getAsType(methodName: String): XType? {
val value = getFieldValue<KSType>(methodName)
- return value?.let(env::wrap)
+ return value?.let(env::wrapDeclared)
}
override fun getAsTypeList(methodName: String): List<XType> {
val values = getFieldValue<List<KSType>>(methodName) ?: return emptyList()
- return values.map(env::wrap)
+ return values.map(env::wrapDeclared)
}
override fun <R : Annotation> getAsAnnotationBox(methodName: String): XAnnotationBox<R> {
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspDeclaredType.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspDeclaredType.kt
index 8b630ed..85752cd 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspDeclaredType.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspDeclaredType.kt
@@ -18,8 +18,8 @@
import androidx.room.compiler.processing.XDeclaredType
import androidx.room.compiler.processing.XType
-import com.squareup.javapoet.TypeName
import com.google.devtools.ksp.symbol.KSType
+import com.squareup.javapoet.TypeName
internal open class KspDeclaredType(
env: KspProcessingEnv,
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspExecutableParameterElement.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspExecutableParameterElement.kt
index 7e2d647..728033e 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspExecutableParameterElement.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspExecutableParameterElement.kt
@@ -43,7 +43,12 @@
resolver = env.resolver,
functionDeclaration = method.declaration,
ksType = method.containing.declaration.asStarProjectedType()
- ).let(env::wrap)
+ ).let {
+ env.wrap(
+ originatingReference = parameter.type!!, // as member of doesn't allow nulls
+ ksType = it
+ )
+ }
}
override fun asMemberOf(other: XDeclaredType): XType {
@@ -55,7 +60,12 @@
resolver = env.resolver,
functionDeclaration = method.declaration,
ksType = other.ksType
- ).let(env::wrap)
+ ).let {
+ env.wrap(
+ originatingReference = parameter.type!!, // as member of doesn't allow nulls
+ ksType = it
+ )
+ }
}
override fun kindName(): String {
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspFieldElement.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspFieldElement.kt
index deb3153..8f8e32d 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspFieldElement.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspFieldElement.kt
@@ -47,7 +47,10 @@
}
override val type: XType by lazy {
- env.wrap(declaration.typeAsMemberOf(env.resolver, containing.type.ksType))
+ env.wrap(
+ originatingReference = declaration.type,
+ ksType = declaration.typeAsMemberOf(env.resolver, containing.type.ksType)
+ )
}
override fun asMemberOf(other: XDeclaredType): XType {
@@ -56,7 +59,10 @@
}
check(other is KspType)
val asMember = declaration.typeAsMemberOf(env.resolver, other.ksType)
- return env.wrap(asMember)
+ return env.wrap(
+ originatingReference = declaration.type,
+ ksType = asMember
+ )
}
fun copyTo(newContaining: KspTypeElement) = KspFieldElement(
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspMethodElement.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspMethodElement.kt
index 74c2e38..ab01a0b 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspMethodElement.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspMethodElement.kt
@@ -114,7 +114,7 @@
override fun isSuspendFunction() = true
override val returnType: XType by lazy {
- env.wrap(env.resolver.builtIns.anyType.makeNullable())
+ env.wrapDeclared(env.resolver.builtIns.anyType.makeNullable())
}
override val parameters: List<XExecutableParameterElement>
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspMethodType.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspMethodType.kt
index cca29f31..218643a 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspMethodType.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspMethodType.kt
@@ -52,7 +52,8 @@
) : KspMethodType(env, origin, containing) {
override val returnType: XType by lazy {
env.wrap(
- origin.declaration.returnTypeAsMemberOf(
+ originatingReference = origin.declaration.returnType!!,
+ ksType = origin.declaration.returnTypeAsMemberOf(
resolver = env.resolver,
ksType = containing.ksType
)
@@ -70,8 +71,9 @@
get() = origin.returnType
override fun getSuspendFunctionReturnType(): XType {
- return env.wrap(
- origin.declaration.returnTypeAsMemberOf(
+ // suspend functions work w/ continuation so it is always declared
+ return env.wrapDeclared(
+ ksType = origin.declaration.returnTypeAsMemberOf(
resolver = env.resolver,
ksType = containing.ksType
)
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspPrimitiveType.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspPrimitiveType.kt
new file mode 100644
index 0000000..21af989
--- /dev/null
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspPrimitiveType.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2020 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.room.compiler.processing.ksp
+
+import com.google.devtools.ksp.symbol.KSType
+import com.squareup.javapoet.TypeName
+
+/**
+ * This tries to mimic primitive types in Kotlin.
+ *
+ * Primitiveness of a type cannot always be driven from itself (e.g. its nullability).
+ * For instance, a kotlin.Int might be non-null but still be non primitive if it is derived from a
+ * generic type argument or is part of type parameters.
+ */
+internal class KspPrimitiveType(
+ env: KspProcessingEnv,
+ ksType: KSType
+) : KspType(env, ksType) {
+ override val typeName: TypeName by lazy {
+ // TODO once we generate type names in java realm, this custom conversion won't be necessary
+ // for now, temporarily, we only do conversion here in place
+ return@lazy checkNotNull(
+ KspTypeMapper.getPrimitiveJavaTypeName(
+ ksType.declaration.qualifiedName!!.asString()
+ )
+ ) {
+ "Internal error. Should've found a java primitive for " +
+ "${ksType.declaration.qualifiedName}"
+ }
+ }
+}
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspProcessingEnv.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspProcessingEnv.kt
index 41d2747..c752c97 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspProcessingEnv.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspProcessingEnv.kt
@@ -24,6 +24,7 @@
import androidx.room.compiler.processing.XType
import androidx.room.compiler.processing.XTypeElement
import androidx.room.compiler.processing.javac.XTypeElementStore
+import com.google.devtools.ksp.getClassDeclarationByName
import com.google.devtools.ksp.processing.CodeGenerator
import com.google.devtools.ksp.processing.KSPLogger
import com.google.devtools.ksp.processing.Resolver
@@ -32,6 +33,7 @@
import com.google.devtools.ksp.symbol.KSTypeArgument
import com.google.devtools.ksp.symbol.KSTypeParameter
import com.google.devtools.ksp.symbol.KSTypeReference
+import com.google.devtools.ksp.symbol.Nullability
import com.google.devtools.ksp.symbol.Variance
internal class KspProcessingEnv(
@@ -44,7 +46,7 @@
private val typeElementStore =
XTypeElementStore { qName ->
resolver.getClassDeclarationByName(
- resolver.getKSNameFromString(qName)
+ KspTypeMapper.swapWithKotlinType(qName)
)?.let {
KspTypeElement(
env = this,
@@ -64,8 +66,12 @@
}
override fun findType(qName: String): XType? {
- return resolver.findClass(qName)?.let {
- wrap(it.asStarProjectedType())
+ val kotlinTypeName = KspTypeMapper.swapWithKotlinType(qName)
+ return resolver.findClass(kotlinTypeName)?.let {
+ wrap(
+ allowPrimitives = KspTypeMapper.isJavaPrimitiveType(qName),
+ ksType = it.asStarProjectedType()
+ )
}
}
@@ -90,7 +96,7 @@
variance = Variance.INVARIANT
)
}
- return wrap(
+ return wrapDeclared(
type.declaration.asType(typeArguments)
)
}
@@ -112,21 +118,44 @@
)
}
- fun wrap(ksType: KSType): KspDeclaredType {
- return if (ksType.declaration.qualifiedName?.asString() == KOTLIN_ARRAY_Q_NAME) {
- KspArrayType(
- env = this,
- ksType = ksType
- )
- } else {
- KspDeclaredType(this, ksType)
- }
+ /**
+ * Wraps the given `ksType` as a [KspDeclaredType].
+ * For the edge cases where `ksType` potentially represents a java primitive, it is always
+ * boxed.
+ */
+ fun wrapDeclared(ksType: KSType): KspDeclaredType {
+ return wrap(
+ ksType = ksType,
+ allowPrimitives = false
+ ) as KspDeclaredType
}
- fun wrap(ksTypeReference: KSTypeReference): KspDeclaredType {
- return wrap(ksTypeReference.resolve())
+ /**
+ * Wraps the given `ksType`.
+ *
+ * The [originatingReference] is used to calculate whether the given [ksType] can be a
+ * primitive or not.
+ */
+ fun wrap(
+ originatingReference: KSTypeReference,
+ ksType: KSType
+ ): KspType {
+ return wrap(
+ ksType = ksType,
+ allowPrimitives = !originatingReference.isTypeParameterReference()
+ )
}
+ /**
+ * Wraps the given [typeReference] in to a [KspType].
+ */
+ fun wrap(
+ typeReference: KSTypeReference
+ ) = wrap(
+ originatingReference = typeReference,
+ ksType = typeReference.resolve()
+ )
+
fun wrap(ksTypeParam: KSTypeParameter, ksTypeArgument: KSTypeArgument): KspTypeArgumentType {
return KspTypeArgumentType(
env = this,
@@ -135,6 +164,33 @@
)
}
+ /**
+ * Wraps the given KSType into a KspType.
+ *
+ * Certain Kotlin types might be primitives in Java but such information cannot be derived
+ * just by looking at the type itself.
+ * Instead, it is passed in an argument to this function and public wrap functions make that
+ * decision.
+ */
+ private fun wrap(ksType: KSType, allowPrimitives: Boolean): KspType {
+ val qName = ksType.declaration.qualifiedName?.asString()
+ if (allowPrimitives && qName != null && ksType.nullability == Nullability.NOT_NULL) {
+ // check for primitives
+ val javaPrimitive = KspTypeMapper.getPrimitiveJavaTypeName(qName)
+ if (javaPrimitive != null) {
+ return KspPrimitiveType(this, ksType)
+ }
+ }
+ return if (qName == KOTLIN_ARRAY_Q_NAME) {
+ KspArrayType(
+ env = this,
+ ksType = ksType
+ )
+ } else {
+ KspDeclaredType(this, ksType)
+ }
+ }
+
fun wrapClassDeclaration(declaration: KSClassDeclaration): KspTypeElement {
return KspTypeElement(
env = this,
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspType.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspType.kt
index fecc613..3b825dd 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspType.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspType.kt
@@ -118,7 +118,17 @@
override fun isTypeOf(other: KClass<*>): Boolean {
// closest to what MoreTypes#isTypeOf does.
- return rawType.typeName.toString() == other.qualifiedName
+ // TODO once we move TypeNames to java realm, we will be able to check just using that
+ // (+ boxing). Fow now, this code stays flexible and checks for all variations as
+ // only primitives use java types now.
+ val rawTypeName = rawType.typeName
+ // other might be something like Kotlin.Int which would map to primitive int (when
+ // invoked with .java) hence we need to check the qualified name for both.
+ // similar case for lists.
+ val javaQName = other.java.canonicalName
+ val kotlinQName = other.qualifiedName
+ val myQName = rawTypeName.toString()
+ return myQName == javaQName || myQName == kotlinQName
}
override fun isSameType(other: XType): Boolean {
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt
index 91f4c17..699dd95 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt
@@ -26,7 +26,6 @@
import androidx.room.compiler.processing.ksp.KspAnnotated.UseSiteFilter.Companion.NO_USE_SITE
import androidx.room.compiler.processing.ksp.synthetic.KspSyntheticConstructorForJava
import androidx.room.compiler.processing.ksp.synthetic.KspSyntheticPropertyMethodElement
-import com.squareup.javapoet.ClassName
import com.google.devtools.ksp.getAllSuperTypes
import com.google.devtools.ksp.getDeclaredFunctions
import com.google.devtools.ksp.getDeclaredProperties
@@ -37,6 +36,7 @@
import com.google.devtools.ksp.symbol.KSPropertyDeclaration
import com.google.devtools.ksp.symbol.Modifier
import com.google.devtools.ksp.symbol.Origin
+import com.squareup.javapoet.ClassName
internal class KspTypeElement(
env: KspProcessingEnv,
@@ -72,7 +72,7 @@
}
override val type: KspDeclaredType by lazy {
- env.wrap(declaration.asStarProjectedType())
+ env.wrapDeclared(declaration.asStarProjectedType())
}
override val superType: XType? by lazy {
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeMapper.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeMapper.kt
new file mode 100644
index 0000000..2ffa3e5
--- /dev/null
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeMapper.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2020 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.room.compiler.processing.ksp
+
+import com.squareup.javapoet.TypeName
+
+/**
+ * Maps java specific types to their kotlin counterparts.
+ * see: https://github.com/google/ksp/issues/126
+ * see: https://github.com/google/ksp/issues/125
+ *
+ * `Resolver.getClassDeclarationByName` returns the java representation of a class even when a
+ * kotlin version of the same class exists. e.g. It returns a KSClassDeclaration representing
+ * `java.lang.String` if queried with `java.lang.String`. Even though this makes sense by itself,
+ * it is inconsistent with the kotlin compiler which will resolve all instances of
+ * `java.lang.String` to `kotlin.String` (even if it is in Java source code).
+ *
+ * Until KSP provides compiler consistent behavior, this helper utility does the mapping for us.
+ *
+ * This list is built from https://kotlinlang.org/docs/reference/java-interop.html#mapped-types.
+ * Hopefully, it will be temporary until KSP provides a utility to do the same conversion.
+ */
+object KspTypeMapper {
+ private val mapping = mutableMapOf<String, String>()
+ private val kotlinTypeToJavaPrimitiveMapping = mapOf(
+ "kotlin.Byte" to TypeName.BYTE,
+ "kotlin.Short" to TypeName.SHORT,
+ "kotlin.Int" to TypeName.INT,
+ "kotlin.Long" to TypeName.LONG,
+ "kotlin.Char" to TypeName.CHAR,
+ "kotlin.Float" to TypeName.FLOAT,
+ "kotlin.Double" to TypeName.DOUBLE,
+ "kotlin.Boolean" to TypeName.BOOLEAN
+ )
+
+ init {
+ // https://kotlinlang.org/docs/reference/java-interop.html#mapped-types
+ kotlinTypeToJavaPrimitiveMapping.forEach {
+ mapping[it.value.toString()] = it.key
+ }
+ // TODO Add non primitives after TypeNames move to the java type realm.
+ }
+
+ fun swapWithKotlinType(javaType: String): String = mapping[javaType] ?: javaType
+
+ fun getPrimitiveJavaTypeName(kotlinType: String) = kotlinTypeToJavaPrimitiveMapping[kotlinType]
+
+ fun isJavaPrimitiveType(qName: String) = mapping[qName]?.let {
+ kotlinTypeToJavaPrimitiveMapping[it]
+ } != null
+}
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticContinuationParameterElement.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticContinuationParameterElement.kt
index 1b5f8f4..42deaa39 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticContinuationParameterElement.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticContinuationParameterElement.kt
@@ -65,7 +65,7 @@
)
)
)
- env.wrap(contType)
+ env.wrapDeclared(contType)
}
override fun asMemberOf(other: XDeclaredType): XType {
@@ -83,7 +83,7 @@
Variance.CONTRAVARIANT
)
val contType = continuation.asType(listOf(returnTypeAsTypeArgument))
- return env.wrap(contType)
+ return env.wrapDeclared(contType)
}
override fun kindName(): String {
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticPropertyMethodElement.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticPropertyMethodElement.kt
index 97c42f8..a8932b4 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticPropertyMethodElement.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticPropertyMethodElement.kt
@@ -179,7 +179,7 @@
}
override val returnType: XType by lazy {
- env.wrap(
+ env.wrapDeclared(
env.resolver.builtIns.unitType
)
}
diff --git a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableElementTest.kt b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableElementTest.kt
index bb3b019..cbce886 100644
--- a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableElementTest.kt
+++ b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableElementTest.kt
@@ -25,6 +25,7 @@
import com.google.common.truth.Truth
import com.google.common.truth.Truth.assertThat
import com.squareup.javapoet.ParameterizedTypeName
+import com.squareup.javapoet.TypeName
import com.squareup.javapoet.WildcardTypeName
import org.junit.Test
import org.junit.runner.RunWith
@@ -202,7 +203,7 @@
invocation.types.string
)
assertThat(method.parameters[1].type.typeName).isEqualTo(
- invocation.types.int
+ TypeName.INT
)
assertThat(method.isSuspendFunction()).isTrue()
assertThat(method.returnType.typeName).isEqualTo(invocation.types.objectOrAny)
diff --git a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XProcessingEnvTest.kt b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XProcessingEnvTest.kt
index 2ea0df4..a5a7bc6 100644
--- a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XProcessingEnvTest.kt
+++ b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XProcessingEnvTest.kt
@@ -139,13 +139,16 @@
}
""".trimIndent()
)
- runProcessorTest(
+ runProcessorTestIncludingKsp(
listOf(source)
) { invocation ->
PRIMITIVE_TYPES.forEach {
val targetType = invocation.processingEnv.findType(it.key)
assertThat(targetType?.typeName).isEqualTo(it.value)
- assertThat(targetType?.boxed()?.typeName).isEqualTo(it.value.box())
+ if (!invocation.isKsp) {
+ // TODO re-enable once we move typenames to the java realm
+ assertThat(targetType?.boxed()?.typeName).isEqualTo(it.value.box())
+ }
}
}
}
diff --git a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/javac/kotlin/JvmDescriptorUtilsTest.kt b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/javac/kotlin/JvmDescriptorUtilsTest.kt
index 8a3c47f..2c297e0 100644
--- a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/javac/kotlin/JvmDescriptorUtilsTest.kt
+++ b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/javac/kotlin/JvmDescriptorUtilsTest.kt
@@ -19,9 +19,11 @@
import com.google.auto.common.MoreElements
import com.google.common.truth.Truth
import com.google.common.truth.Truth.assertThat
-import com.google.testing.compile.CompileTester
import com.google.testing.compile.JavaFileObjects
import com.google.testing.compile.JavaSourcesSubjectFactory
+import com.squareup.javapoet.ArrayTypeName
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.TypeName
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@@ -96,7 +98,7 @@
"field4:Ljava/util/List;"
)
)
- }.compilesWithoutError()
+ }
}
@Test
@@ -153,7 +155,7 @@
"method9()Ljava/lang/String;"
)
)
- }.compilesWithoutError()
+ }
}
@Test
@@ -179,7 +181,7 @@
) { descriptors ->
assertThat(descriptors)
.isEqualTo(setOf("method1(ZI)V", "method2(C)B", "method3(DF)V", "method4(JS)V"))
- }.compilesWithoutError()
+ }
}
@Test
@@ -215,7 +217,7 @@
"method4()Ljava/util/Map;"
)
)
- }.compilesWithoutError()
+ }
}
@Test
@@ -247,7 +249,7 @@
"method2()Landroidx/room/test/DataClass;"
)
)
- }.compilesWithoutError()
+ }
}
@Test
@@ -296,7 +298,7 @@
"method4()Landroidx/room/test/DataClass\$StaticInnerData;"
)
)
- }.compilesWithoutError()
+ }
}
@Test
@@ -336,7 +338,72 @@
"method4([I)V"
)
)
- }.compilesWithoutError()
+ }
+ }
+
+ @Test
+ fun typeNameFromDescriptor() {
+ val extraJfo =
+ """
+ import androidx.room.test.Describe;
+ class Custom {
+ static class Nested1 {
+ static class Nested2 {
+ @Describe
+ Custom c;
+ @Describe
+ Custom.Nested1 n1;
+ @Describe
+ Custom.Nested1.Nested2 n2;
+ }
+ }
+ }
+ """.toJFO("Custom")
+ singleRun(
+ """
+ package androidx.room.test;
+
+ class Foo {
+ @Describe
+ int x;
+
+ @Describe
+ java.util.Map map;
+
+ @Describe
+ java.util.Map.Entry entry;
+
+ @Describe
+ int[] intArray;
+
+ @Describe
+ int[][] intArrayOfArray;
+ }
+ """.toJFO("androidx.room.test.Foo"),
+ extraJfo
+ ) {
+ assertThat(
+ it.map {
+ // the format is name:type so we strip everything before `:`
+ it.split(':')[1].typeNameFromJvmSignature()
+ }
+ ).containsExactly(
+ TypeName.INT,
+ ClassName.get(Map::class.java),
+ ClassName.get(Map.Entry::class.java),
+ ArrayTypeName.of(
+ TypeName.INT
+ ),
+ ArrayTypeName.of(
+ ArrayTypeName.of(
+ TypeName.INT
+ )
+ ),
+ ClassName.get("", "Custom"),
+ ClassName.get("", "Custom", "Nested1"),
+ ClassName.get("", "Custom", "Nested1", "Nested2"),
+ )
+ }
}
private fun String.toJFO(qName: String): JavaFileObject =
@@ -346,27 +413,31 @@
private fun singleRun(
vararg jfo: JavaFileObject,
handler: (Set<String>) -> Unit
- ): CompileTester {
- return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
+ ) {
+ Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
.that(listOf(describeAnnotation) + jfo)
.processedWith(object : AbstractProcessor() {
override fun process(
annotations: Set<TypeElement>,
roundEnv: RoundEnvironment
): Boolean {
- roundEnv.getElementsAnnotatedWith(annotations.first()).map { element ->
- when (element.kind) {
- FIELD -> MoreElements.asVariable(element).descriptor()
- METHOD, CONSTRUCTOR -> MoreElements.asExecutable(element).descriptor()
- else -> error("Unsupported element to describe.")
- }
- }.toSet().let(handler)
+ if (annotations.isNotEmpty()) {
+ roundEnv.getElementsAnnotatedWith(annotations.first()).map { element ->
+ when (element.kind) {
+ FIELD ->
+ MoreElements.asVariable(element).descriptor()
+ METHOD, CONSTRUCTOR ->
+ MoreElements.asExecutable(element).descriptor()
+ else -> error("Unsupported element to describe.")
+ }
+ }.toSet().let(handler)
+ }
return true
}
- override fun getSupportedOptions(): Set<String> {
+ override fun getSupportedAnnotationTypes(): Set<String> {
return setOf("androidx.room.test.Describe")
}
- })
+ }).compilesWithoutError()
}
}
\ No newline at end of file
diff --git a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeTest.kt b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeTest.kt
index 3944482..5b6e65f 100644
--- a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeTest.kt
+++ b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeTest.kt
@@ -27,7 +27,7 @@
import com.google.devtools.ksp.getClassDeclarationByName
import com.google.devtools.ksp.getDeclaredFunctions
import com.google.devtools.ksp.symbol.KSPropertyDeclaration
-import com.google.devtools.ksp.symbol.KSTypeReference
+import com.squareup.javapoet.TypeName
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@@ -88,14 +88,14 @@
listOf(src),
succeed = false
) { invocation ->
- invocation.requirePropertyType("errorType").let { type ->
+ invocation.requireDeclaredPropertyType("errorType").let { type ->
assertThat(type.isError()).isTrue()
assertThat(type.typeArguments).isEmpty()
assertThat(type.typeName).isEqualTo(ERROR_TYPE_NAME)
assertThat(type.asTypeElement().className).isEqualTo(ERROR_TYPE_NAME)
}
- invocation.requirePropertyType("listOfErrorType").let { type ->
+ invocation.requireDeclaredPropertyType("listOfErrorType").let { type ->
assertThat(type.isError()).isFalse()
assertThat(type.typeArguments).hasSize(1)
type.typeArguments.single().let { typeArg ->
@@ -120,7 +120,7 @@
listOf(src),
succeed = true
) { invocation ->
- invocation.requirePropertyType("listOfNullableStrings").let { type ->
+ invocation.requireDeclaredPropertyType("listOfNullableStrings").let { type ->
assertThat(type.nullability).isEqualTo(NONNULL)
assertThat(type.typeArguments).hasSize(1)
assertThat(type.asTypeElement().className).isEqualTo(
@@ -136,7 +136,7 @@
}
}
- invocation.requirePropertyType("listOfInts").let { type ->
+ invocation.requireDeclaredPropertyType("listOfInts").let { type ->
assertThat(type.nullability).isEqualTo(NONNULL)
assertThat(type.typeArguments).hasSize(1)
type.typeArguments.single().let { typeArg ->
@@ -172,13 +172,17 @@
listOf(src),
succeed = true
) { invocation ->
- val nullableStringList = invocation.requirePropertyType("listOfNullableStrings")
- val nonNullStringList = invocation.requirePropertyType("listOfNonNullStrings")
+ val nullableStringList = invocation
+ .requireDeclaredPropertyType("listOfNullableStrings")
+ val nonNullStringList = invocation
+ .requireDeclaredPropertyType("listOfNonNullStrings")
assertThat(nullableStringList).isNotEqualTo(nonNullStringList)
assertThat(nonNullStringList).isNotEqualTo(nullableStringList)
- val nullableStringList_2 = invocation.requirePropertyType("listOfNullableStrings_2")
- val nonNullStringList_2 = invocation.requirePropertyType("listOfNonNullStrings_2")
+ val nullableStringList_2 = invocation
+ .requireDeclaredPropertyType("listOfNullableStrings_2")
+ val nonNullStringList_2 = invocation
+ .requireDeclaredPropertyType("listOfNonNullStrings_2")
assertThat(nullableStringList).isEqualTo(nullableStringList_2)
assertThat(nonNullStringList).isEqualTo(nonNullStringList_2)
@@ -216,21 +220,21 @@
succeed = true
) { invocation ->
invocation.requirePropertyType("simple").let {
- assertThat(it.rawType.typeName).isEqualTo(ClassName.get("kotlin", "Int"))
+ assertThat(it.rawType.typeName).isEqualTo(TypeName.INT)
}
- invocation.requirePropertyType("list").let { list ->
+ invocation.requireDeclaredPropertyType("list").let { list ->
assertThat(list.rawType).isNotEqualTo(list)
assertThat(list.typeArguments).isNotEmpty()
assertThat(list.rawType.typeName)
.isEqualTo(ClassName.get("kotlin.collections", "List"))
}
- invocation.requirePropertyType("map").let { map ->
+ invocation.requireDeclaredPropertyType("map").let { map ->
assertThat(map.rawType).isNotEqualTo(map)
assertThat(map.typeArguments).hasSize(2)
assertThat(map.rawType.typeName)
.isEqualTo(ClassName.get("kotlin.collections", "Map"))
}
- invocation.requirePropertyType("listOfMaps").let { listOfMaps ->
+ invocation.requireDeclaredPropertyType("listOfMaps").let { listOfMaps ->
assertThat(listOfMaps.rawType).isNotEqualTo(listOfMaps)
assertThat(listOfMaps.typeArguments).hasSize(1)
}
@@ -450,7 +454,7 @@
.single()
.type
.let {
- invocation.wrap(typeRef = it!!)
+ invocation.processingEnv.wrapDeclared(it!!.resolve())
}
}
assertThat(typeArgs["Bar"]!!.typeName)
@@ -486,7 +490,9 @@
?.first {
it.simpleName.asString() == "wildcardMethod"
} ?: throw AssertionError("cannot find test method")
- val paramType = invocation.wrap(method.parameters.first().type!!)
+ val paramType = invocation.processingEnv.wrapDeclared(
+ method.parameters.first().type!!.resolve()
+ )
val arg1 = paramType.typeArguments.single()
assertThat(arg1.typeName)
.isEqualTo(
@@ -498,17 +504,22 @@
}
}
- private fun TestInvocation.requirePropertyType(name: String): KspDeclaredType {
- (processingEnv as KspProcessingEnv).resolver.getAllFiles().forEach { file ->
- val prop = file.declarations.first {
- it.simpleName.asString() == name
- } as KSPropertyDeclaration
- return wrap(prop.type)
- }
- throw IllegalStateException("cannot find any property with name $name")
+ private fun TestInvocation.requirePropertyType(name: String): KspType {
+ val prop = requireProperty(name)
+ return (processingEnv as KspProcessingEnv).wrap(prop.type)
}
- private fun TestInvocation.wrap(typeRef: KSTypeReference): KspDeclaredType {
- return (processingEnv as KspProcessingEnv).wrap(typeRef)
+ private fun TestInvocation.requireDeclaredPropertyType(name: String): KspDeclaredType {
+ val prop = requireProperty(name)
+ return (processingEnv as KspProcessingEnv).wrapDeclared(prop.type.resolve())
+ }
+
+ private fun TestInvocation.requireProperty(name: String): KSPropertyDeclaration {
+ kspResolver.getAllFiles().forEach { file ->
+ return file.declarations.first {
+ it.simpleName.asString() == name
+ } as KSPropertyDeclaration
+ }
+ throw IllegalStateException("cannot find any property with name $name")
}
}
\ No newline at end of file
diff --git a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/util/TestInvocation.kt b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/util/TestInvocation.kt
index 5e0d428..ab5c68a 100644
--- a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/util/TestInvocation.kt
+++ b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/util/TestInvocation.kt
@@ -42,8 +42,6 @@
voidOrUnit = KotlinTypeNames.UNIT_CLASS_NAME,
objectOrAny = KotlinTypeNames.ANY_CLASS_NAME,
boxedInt = KotlinTypeNames.INT_CLASS_NAME,
- int = KotlinTypeNames.INT_CLASS_NAME,
- long = KotlinTypeNames.LONG_CLASS_NAME,
list = KotlinTypeNames.LIST_CLASS_NAME,
mutableSet = KotlinTypeNames.MUTABLESET_CLASS_NAME
)
@@ -53,8 +51,6 @@
voidOrUnit = TypeName.VOID,
objectOrAny = TypeName.OBJECT,
boxedInt = TypeName.INT.box(),
- int = TypeName.INT,
- long = TypeName.LONG,
list = ClassName.get("java.util", "List"),
mutableSet = ClassName.get("java.util", "Set")
)
@@ -70,8 +66,8 @@
val voidOrUnit: TypeName,
val objectOrAny: ClassName,
val boxedInt: TypeName,
- val int: TypeName,
- val long: TypeName,
+ val int: TypeName = TypeName.INT,
+ val long: TypeName = TypeName.LONG,
val list: ClassName,
val mutableSet: TypeName
)
diff --git a/settings.gradle b/settings.gradle
index 64e1358..719a0b6 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -214,6 +214,7 @@
includeProject(":compose:foundation:foundation:foundation-samples", "compose/foundation/foundation/samples", [BuildType.COMPOSE])
includeProject(":compose:integration-tests", "compose/integration-tests", [BuildType.COMPOSE])
includeProject(":compose:integration-tests:benchmark", "compose/integration-tests/benchmark", [BuildType.COMPOSE])
+includeProject(":compose:integration-tests:macro-benchmark", "compose/integration-tests/macro-benchmark", [BuildType.COMPOSE])
includeProject(":compose:integration-tests:demos", "compose/integration-tests/demos", [BuildType.COMPOSE])
includeProject(":compose:integration-tests:demos:common", "compose/integration-tests/demos/common", [BuildType.COMPOSE])
includeProject(":compose:internal-lint-checks", "compose/internal-lint-checks", [BuildType.COMPOSE])
@@ -385,7 +386,7 @@
includeProject(":navigation:navigation-ui", "navigation/navigation-ui", [BuildType.MAIN, BuildType.FLAN])
includeProject(":navigation:navigation-ui-ktx", "navigation/navigation-ui-ktx", [BuildType.MAIN, BuildType.FLAN])
includeProject(":paging:integration-tests:testapp", "paging/integration-tests/testapp", [BuildType.MAIN])
-includeProject(":paging:paging-common", "paging/common", [BuildType.MAIN])
+includeProject(":paging:paging-common", "paging/common", [BuildType.MAIN, BuildType.COMPOSE])
includeProject(":paging:paging-common-ktx", "paging/common/ktx", [BuildType.MAIN])
includeProject(":paging:paging-compose", "paging/compose", [BuildType.COMPOSE])
includeProject(":paging:paging-compose:paging-compose-samples", "paging/compose/samples", [BuildType.COMPOSE])
@@ -551,14 +552,14 @@
//
/////////////////////////////
-includeProject(":internal-testutils-common", "testutils/testutils-common", [BuildType.MAIN])
+includeProject(":internal-testutils-common", "testutils/testutils-common", [BuildType.MAIN, BuildType.COMPOSE])
includeProject(":internal-testutils-runtime", "testutils/testutils-runtime", [BuildType.MAIN, BuildType.FLAN])
includeProject(":internal-testutils-appcompat", "testutils/testutils-appcompat", [BuildType.MAIN])
includeProject(":internal-testutils-espresso", "testutils/testutils-espresso", [BuildType.MAIN])
includeProject(":internal-testutils-truth", "testutils/testutils-truth", [BuildType.MAIN, BuildType.FLAN])
-includeProject(":internal-testutils-ktx", "testutils/testutils-ktx", [BuildType.MAIN])
+includeProject(":internal-testutils-ktx", "testutils/testutils-ktx", [BuildType.MAIN, BuildType.COMPOSE])
includeProject(":internal-testutils-navigation", "testutils/testutils-navigation", [BuildType.MAIN, BuildType.COMPOSE, BuildType.FLAN])
-includeProject(":internal-testutils-paging", "testutils/testutils-paging", [BuildType.MAIN])
+includeProject(":internal-testutils-paging", "testutils/testutils-paging", [BuildType.MAIN, BuildType.COMPOSE])
includeProject(":internal-testutils-gradle-plugin", "testutils/testutils-gradle-plugin", [BuildType.MAIN, BuildType.FLAN])
/////////////////////////////
@@ -566,18 +567,27 @@
// Applications and libraries for tests
//
/////////////////////////////
+includeProject(":media:version-compat-tests:client",
+ "media/version-compat-tests/current/client", [BuildType.MAIN])
+includeProject(":media:version-compat-tests:client-previous",
+ "media/version-compat-tests/previous/client", [BuildType.MAIN])
+includeProject(":media:version-compat-tests:service",
+ "media/version-compat-tests/current/service", [BuildType.MAIN])
+includeProject(":media:version-compat-tests:service-previous",
+ "media/version-compat-tests/previous/service", [BuildType.MAIN])
+includeProject(":media:version-compat-tests:lib", "media/version-compat-tests/lib",
+ [BuildType.MAIN])
-includeProject(":support-media-test-client", "media/version-compat-tests/current/client", [BuildType.MAIN])
-includeProject(":support-media-test-client-previous", "media/version-compat-tests/previous/client", [BuildType.MAIN])
-includeProject(":support-media-test-service", "media/version-compat-tests/current/service", [BuildType.MAIN])
-includeProject(":support-media-test-service-previous", "media/version-compat-tests/previous/service", [BuildType.MAIN])
-includeProject(":support-media-test-lib", "media/version-compat-tests/lib", [BuildType.MAIN])
-
-includeProject(":support-media2-test-client", "media2/session/version-compat-tests/current/client", [BuildType.MAIN])
-includeProject(":support-media2-test-client-previous", "media2/session/version-compat-tests/previous/client", [BuildType.MAIN])
-includeProject(":support-media2-test-service", "media2/session/version-compat-tests/current/service", [BuildType.MAIN])
-includeProject(":support-media2-test-service-previous", "media2/session/version-compat-tests/previous/service", [BuildType.MAIN])
-includeProject(":support-media2-test-common", "media2/session/version-compat-tests/common", [BuildType.MAIN])
+includeProject(":media2:media2-session:version-compat-tests:client",
+ "media2/session/version-compat-tests/current/client", [BuildType.MAIN])
+includeProject(":media2:media2-session:version-compat-tests:client-previous",
+ "media2/session/version-compat-tests/previous/client", [BuildType.MAIN])
+includeProject(":media2:media2-session:version-compat-tests:service",
+ "media2/session/version-compat-tests/current/service", [BuildType.MAIN])
+includeProject(":media2:media2-session:version-compat-tests:service-previous",
+ "media2/session/version-compat-tests/previous/service", [BuildType.MAIN])
+includeProject(":media2:media2-session:version-compat-tests:common",
+ "media2/session/version-compat-tests/common", [BuildType.MAIN])
/////////////////////////////
//
diff --git a/testutils/testutils-paging/src/main/java/androidx/paging/TestPagingSource.kt b/testutils/testutils-paging/src/main/java/androidx/paging/TestPagingSource.kt
index e0216e4..d0113b5 100644
--- a/testutils/testutils-paging/src/main/java/androidx/paging/TestPagingSource.kt
+++ b/testutils/testutils-paging/src/main/java/androidx/paging/TestPagingSource.kt
@@ -29,11 +29,14 @@
counted: Boolean = true,
override val jumpingSupported: Boolean = true,
val items: List<Int> = ITEMS,
- private val loadDelay: Long = 1000
+ private val loadDelay: Long = 1000,
) : PagingSource<Int, Int>() {
var errorNextLoad = false
var nextLoadResult: LoadResult<Int, Int>? = null
+ val getRefreshKeyCalls = mutableListOf<PagingState<Int, Int>>()
+ val loadedPages = mutableListOf<LoadResult.Page<Int, Int>>()
+
init {
if (!counted) {
throw NotImplementedError(
@@ -74,11 +77,14 @@
if (end < items.size) end else null,
start,
items.size - end
- )
+ ).also {
+ loadedPages.add(it)
+ }
}
@OptIn(ExperimentalPagingApi::class)
override fun getRefreshKey(state: PagingState<Int, Int>): Int? {
+ getRefreshKeyCalls.add(state)
return state.anchorPosition
}
diff --git a/wear/wear-complications-data/api/restricted_current.txt b/wear/wear-complications-data/api/restricted_current.txt
index 304c31b..83e4f49 100644
--- a/wear/wear-complications-data/api/restricted_current.txt
+++ b/wear/wear-complications-data/api/restricted_current.txt
@@ -167,11 +167,11 @@
}
public final class DefaultComplicationProviderPolicy {
- ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public DefaultComplicationProviderPolicy(java.util.List<android.content.ComponentName> providers, int systemProviderFallback);
+ ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public DefaultComplicationProviderPolicy(java.util.List<android.content.ComponentName> providers, @androidx.wear.complications.SystemProviders.ProviderId int systemProviderFallback);
ctor public DefaultComplicationProviderPolicy();
- ctor public DefaultComplicationProviderPolicy(int systemProvider);
- ctor public DefaultComplicationProviderPolicy(android.content.ComponentName provider, int systemProviderFallback);
- ctor public DefaultComplicationProviderPolicy(android.content.ComponentName primaryProvider, android.content.ComponentName secondaryProvider, int systemProviderFallback);
+ ctor public DefaultComplicationProviderPolicy(@androidx.wear.complications.SystemProviders.ProviderId int systemProvider);
+ ctor public DefaultComplicationProviderPolicy(android.content.ComponentName provider, @androidx.wear.complications.SystemProviders.ProviderId int systemProviderFallback);
+ ctor public DefaultComplicationProviderPolicy(android.content.ComponentName primaryProvider, android.content.ComponentName secondaryProvider, @androidx.wear.complications.SystemProviders.ProviderId int systemProviderFallback);
method public android.content.ComponentName? getPrimaryProvider();
method public android.content.ComponentName? getSecondaryProvider();
method public int getSystemProviderFallback();
diff --git a/wear/wear-complications-data/src/main/java/androidx/wear/complications/DefaultComplicationProviderPolicy.kt b/wear/wear-complications-data/src/main/java/androidx/wear/complications/DefaultComplicationProviderPolicy.kt
index d053729..81e61f9 100644
--- a/wear/wear-complications-data/src/main/java/androidx/wear/complications/DefaultComplicationProviderPolicy.kt
+++ b/wear/wear-complications-data/src/main/java/androidx/wear/complications/DefaultComplicationProviderPolicy.kt
@@ -44,7 +44,7 @@
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public constructor(
providers: List<ComponentName>,
- systemProviderFallback: Int
+ @SystemProviders.ProviderId systemProviderFallback: Int
) {
this.primaryProvider = if (providers.isNotEmpty()) providers[0] else null
this.secondaryProvider = if (providers.size >= 2) providers[1] else null
@@ -61,7 +61,7 @@
/**
* Uses systemProvider as the default complication provider.
*/
- public constructor(systemProvider: Int) {
+ public constructor(@SystemProviders.ProviderId systemProvider: Int) {
primaryProvider = null
secondaryProvider = null
systemProviderFallback = systemProvider
@@ -71,7 +71,10 @@
* Attempts to use provider as the default complication provider, if not present then
* systemProviderFallback will be used instead.
*/
- public constructor(provider: ComponentName, systemProviderFallback: Int) {
+ public constructor(
+ provider: ComponentName,
+ @SystemProviders.ProviderId systemProviderFallback: Int
+ ) {
primaryProvider = provider
secondaryProvider = null
this.systemProviderFallback = systemProviderFallback
@@ -85,7 +88,7 @@
public constructor(
primaryProvider: ComponentName,
secondaryProvider: ComponentName,
- systemProviderFallback: Int
+ @SystemProviders.ProviderId systemProviderFallback: Int
) {
this.primaryProvider = primaryProvider
this.secondaryProvider = secondaryProvider
diff --git a/wear/wear-complications-provider/samples/src/main/java/androidx/wear/complications/samples/AsynchronousProviderService.kt b/wear/wear-complications-provider/samples/src/main/java/androidx/wear/complications/samples/AsynchronousProviderService.kt
index cf5a586..59dc0de 100644
--- a/wear/wear-complications-provider/samples/src/main/java/androidx/wear/complications/samples/AsynchronousProviderService.kt
+++ b/wear/wear-complications-provider/samples/src/main/java/androidx/wear/complications/samples/AsynchronousProviderService.kt
@@ -35,12 +35,12 @@
executor.execute {
callback.onUpdateComplication(
when (type) {
- ShortTextComplicationData.TYPE ->
+ ComplicationType.SHORT_TEXT ->
ShortTextComplicationData.Builder(
ComplicationText.plain("# $complicationId")
).build()
- LongTextComplicationData.TYPE ->
+ ComplicationType.LONG_TEXT ->
LongTextComplicationData.Builder(
ComplicationText.plain("hello $complicationId")
).build()
@@ -52,11 +52,11 @@
}
override fun getPreviewData(type: ComplicationType) = when (type) {
- ShortTextComplicationData.TYPE ->
+ ComplicationType.SHORT_TEXT ->
ShortTextComplicationData.Builder(ComplicationText.plain("# 123"))
.build()
- LongTextComplicationData.TYPE ->
+ ComplicationType.LONG_TEXT ->
LongTextComplicationData.Builder(ComplicationText.plain("hello 123"))
.build()
diff --git a/wear/wear-complications-provider/samples/src/main/java/androidx/wear/complications/samples/BackgroundProviderService.kt b/wear/wear-complications-provider/samples/src/main/java/androidx/wear/complications/samples/BackgroundProviderService.kt
index 85caa05..a4486e1 100644
--- a/wear/wear-complications-provider/samples/src/main/java/androidx/wear/complications/samples/BackgroundProviderService.kt
+++ b/wear/wear-complications-provider/samples/src/main/java/androidx/wear/complications/samples/BackgroundProviderService.kt
@@ -63,12 +63,12 @@
) {
callback.onUpdateComplication(
when (type) {
- ShortTextComplicationData.TYPE ->
+ ComplicationType.SHORT_TEXT ->
ShortTextComplicationData.Builder(
ComplicationText.plain("# $counter")
).build()
- LongTextComplicationData.TYPE ->
+ ComplicationType.LONG_TEXT ->
LongTextComplicationData.Builder(
ComplicationText.plain("Count $counter")
).build()
@@ -79,12 +79,12 @@
}
override fun getPreviewData(type: ComplicationType) = when (type) {
- ShortTextComplicationData.TYPE ->
+ ComplicationType.SHORT_TEXT ->
ShortTextComplicationData.Builder(
ComplicationText.plain("# 123")
).build()
- LongTextComplicationData.TYPE ->
+ ComplicationType.LONG_TEXT ->
LongTextComplicationData.Builder(
ComplicationText.plain("Count 123")
).build()
diff --git a/wear/wear-complications-provider/samples/src/main/java/androidx/wear/complications/samples/SynchronousProviderService.kt b/wear/wear-complications-provider/samples/src/main/java/androidx/wear/complications/samples/SynchronousProviderService.kt
index 6138270..4b9e243 100644
--- a/wear/wear-complications-provider/samples/src/main/java/androidx/wear/complications/samples/SynchronousProviderService.kt
+++ b/wear/wear-complications-provider/samples/src/main/java/androidx/wear/complications/samples/SynchronousProviderService.kt
@@ -32,12 +32,12 @@
) {
callback.onUpdateComplication(
when (type) {
- ShortTextComplicationData.TYPE ->
+ ComplicationType.SHORT_TEXT ->
ShortTextComplicationData.Builder(
ComplicationText.plain("# $complicationId")
).build()
- LongTextComplicationData.TYPE ->
+ ComplicationType.LONG_TEXT ->
LongTextComplicationData.Builder(
ComplicationText.plain("hello $complicationId")
).build()
@@ -48,11 +48,11 @@
}
override fun getPreviewData(type: ComplicationType) = when (type) {
- ShortTextComplicationData.TYPE ->
+ ComplicationType.SHORT_TEXT ->
ShortTextComplicationData.Builder(ComplicationText.plain("# 123"))
.build()
- LongTextComplicationData.TYPE ->
+ ComplicationType.LONG_TEXT ->
LongTextComplicationData.Builder(ComplicationText.plain("hello 123"))
.build()
diff --git a/wear/wear-watchface-client/api/current.txt b/wear/wear-watchface-client/api/current.txt
index 97c2972..4e593ce 100644
--- a/wear/wear-watchface-client/api/current.txt
+++ b/wear/wear-watchface-client/api/current.txt
@@ -113,12 +113,16 @@
property public final int interruptionFilter;
}
- public final class WatchFaceControlClient implements java.lang.AutoCloseable {
- ctor public WatchFaceControlClient(android.content.Context context, String watchFacePackageName);
- method public void close();
+ public interface WatchFaceControlClient extends java.lang.AutoCloseable {
method public com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.HeadlessWatchFaceClient> createHeadlessWatchFaceClient(android.content.ComponentName watchFaceName, androidx.wear.watchface.client.DeviceConfig deviceConfig, int surfaceWidth, int surfaceHeight);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.InteractiveWatchFaceSysUiClient> getInteractiveWatchFaceInstanceSysUi(String instanceId);
+ method public default static androidx.wear.watchface.client.WatchFaceControlClient createWatchFaceControlClient(android.content.Context context, String watchFacePackageName);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.InteractiveWatchFaceSysUiClient> getInteractiveWatchFaceSysUiClientInstance(String instanceId);
method public com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.InteractiveWatchFaceWcsClient> getOrCreateWallpaperServiceBackedInteractiveWatchFaceWcsClient(String id, androidx.wear.watchface.client.DeviceConfig deviceConfig, androidx.wear.watchface.client.SystemState systemState, androidx.wear.watchface.style.UserStyle? userStyle, java.util.Map<java.lang.Integer,? extends androidx.wear.complications.data.ComplicationData>? idToComplicationData);
+ field public static final androidx.wear.watchface.client.WatchFaceControlClient.Companion Companion;
+ }
+
+ public static final class WatchFaceControlClient.Companion {
+ method public androidx.wear.watchface.client.WatchFaceControlClient createWatchFaceControlClient(android.content.Context context, String watchFacePackageName);
}
public static final class WatchFaceControlClient.ServiceNotBoundException extends java.lang.Exception {
diff --git a/wear/wear-watchface-client/api/public_plus_experimental_current.txt b/wear/wear-watchface-client/api/public_plus_experimental_current.txt
index a37e9cb..8b42a9b 100644
--- a/wear/wear-watchface-client/api/public_plus_experimental_current.txt
+++ b/wear/wear-watchface-client/api/public_plus_experimental_current.txt
@@ -113,12 +113,16 @@
property public final int interruptionFilter;
}
- public final class WatchFaceControlClient implements java.lang.AutoCloseable {
- ctor public WatchFaceControlClient(android.content.Context context, String watchFacePackageName);
- method public void close();
+ public interface WatchFaceControlClient extends java.lang.AutoCloseable {
method public com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.HeadlessWatchFaceClient> createHeadlessWatchFaceClient(android.content.ComponentName watchFaceName, androidx.wear.watchface.client.DeviceConfig deviceConfig, int surfaceWidth, int surfaceHeight);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.InteractiveWatchFaceSysUiClient> getInteractiveWatchFaceInstanceSysUi(String instanceId);
+ method public default static androidx.wear.watchface.client.WatchFaceControlClient createWatchFaceControlClient(android.content.Context context, String watchFacePackageName);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.InteractiveWatchFaceSysUiClient> getInteractiveWatchFaceSysUiClientInstance(String instanceId);
method public com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.InteractiveWatchFaceWcsClient> getOrCreateWallpaperServiceBackedInteractiveWatchFaceWcsClient(String id, androidx.wear.watchface.client.DeviceConfig deviceConfig, androidx.wear.watchface.client.SystemState systemState, androidx.wear.watchface.style.UserStyle? userStyle, java.util.Map<java.lang.Integer,? extends androidx.wear.complications.data.ComplicationData>? idToComplicationData);
+ field public static final androidx.wear.watchface.client.WatchFaceControlClient.Companion Companion;
+ }
+
+ public static final class WatchFaceControlClient.Companion {
+ method public androidx.wear.watchface.client.WatchFaceControlClient createWatchFaceControlClient(android.content.Context context, String watchFacePackageName);
}
public static final class WatchFaceControlClient.ServiceNotBoundException extends java.lang.Exception {
diff --git a/wear/wear-watchface-client/api/restricted_current.txt b/wear/wear-watchface-client/api/restricted_current.txt
index de254fe..62a3fbc 100644
--- a/wear/wear-watchface-client/api/restricted_current.txt
+++ b/wear/wear-watchface-client/api/restricted_current.txt
@@ -116,12 +116,16 @@
@IntDef({androidx.wear.watchface.client.InteractiveWatchFaceSysUiClient.TAP_TYPE_TOUCH, androidx.wear.watchface.client.InteractiveWatchFaceSysUiClient.TAP_TYPE_TOUCH_CANCEL, androidx.wear.watchface.client.InteractiveWatchFaceSysUiClient.TAP_TYPE_TAP}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public @interface TapType {
}
- public final class WatchFaceControlClient implements java.lang.AutoCloseable {
- ctor public WatchFaceControlClient(android.content.Context context, String watchFacePackageName);
- method public void close();
+ public interface WatchFaceControlClient extends java.lang.AutoCloseable {
method public com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.HeadlessWatchFaceClient> createHeadlessWatchFaceClient(android.content.ComponentName watchFaceName, androidx.wear.watchface.client.DeviceConfig deviceConfig, int surfaceWidth, int surfaceHeight);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.InteractiveWatchFaceSysUiClient> getInteractiveWatchFaceInstanceSysUi(String instanceId);
+ method public default static androidx.wear.watchface.client.WatchFaceControlClient createWatchFaceControlClient(android.content.Context context, String watchFacePackageName);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.InteractiveWatchFaceSysUiClient> getInteractiveWatchFaceSysUiClientInstance(String instanceId);
method public com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.InteractiveWatchFaceWcsClient> getOrCreateWallpaperServiceBackedInteractiveWatchFaceWcsClient(String id, androidx.wear.watchface.client.DeviceConfig deviceConfig, androidx.wear.watchface.client.SystemState systemState, androidx.wear.watchface.style.UserStyle? userStyle, java.util.Map<java.lang.Integer,? extends androidx.wear.complications.data.ComplicationData>? idToComplicationData);
+ field public static final androidx.wear.watchface.client.WatchFaceControlClient.Companion Companion;
+ }
+
+ public static final class WatchFaceControlClient.Companion {
+ method public androidx.wear.watchface.client.WatchFaceControlClient createWatchFaceControlClient(android.content.Context context, String watchFacePackageName);
}
public static final class WatchFaceControlClient.ServiceNotBoundException extends java.lang.Exception {
diff --git a/wear/wear-watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/WatchFaceControlClientTest.kt b/wear/wear-watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/WatchFaceControlClientTest.kt
index 264a729..a2304a8 100644
--- a/wear/wear-watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/WatchFaceControlClientTest.kt
+++ b/wear/wear-watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/WatchFaceControlClientTest.kt
@@ -37,7 +37,7 @@
import androidx.wear.watchface.RenderParameters
import androidx.wear.watchface.client.DeviceConfig
import androidx.wear.watchface.client.SystemState
-import androidx.wear.watchface.client.WatchFaceControlClient
+import androidx.wear.watchface.client.WatchFaceControlClientImpl
import androidx.wear.watchface.control.WatchFaceControlService
import androidx.wear.watchface.data.ComplicationBoundsType
import androidx.wear.watchface.samples.EXAMPLE_CANVAS_WATCHFACE_LEFT_COMPLICATION_ID
@@ -62,7 +62,7 @@
@MediumTest
class WatchFaceControlClientTest {
private val context = ApplicationProvider.getApplicationContext<Context>()
- private val service = WatchFaceControlClient(
+ private val service = WatchFaceControlClientImpl(
context,
Intent(context, WatchFaceControlTestService::class.java).apply {
action = WatchFaceControlService.ACTION_WATCHFACE_CONTROL_SERVICE
@@ -350,7 +350,7 @@
// Wait for the instance to be created.
interactiveInstanceFuture.get(CONNECT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)!!
- val sysUiInterface = service.getInteractiveWatchFaceInstanceSysUi("testId")
+ val sysUiInterface = service.getInteractiveWatchFaceSysUiClientInstance("testId")
.get(CONNECT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)!!
try {
diff --git a/wear/wear-watchface-client/src/main/java/androidx/wear/watchface/client/WatchFaceControlClient.kt b/wear/wear-watchface-client/src/main/java/androidx/wear/watchface/client/WatchFaceControlClient.kt
index eda8a77..c2c798f 100644
--- a/wear/wear-watchface-client/src/main/java/androidx/wear/watchface/client/WatchFaceControlClient.kt
+++ b/wear/wear-watchface-client/src/main/java/androidx/wear/watchface/client/WatchFaceControlClient.kt
@@ -37,40 +37,22 @@
* Connects to a watch face's WatchFaceControlService which allows the user to control the
* watch face.
*/
-public class WatchFaceControlClient internal constructor(
- private val context: Context,
- serviceIntent: Intent
-) : AutoCloseable {
+public interface WatchFaceControlClient : AutoCloseable {
- /** Constructs a client which connects to a watch face in the given android package. */
- public constructor(
- /** Calling application's [Context]. */
- context: Context,
- /** The name of the package containing the watch face control service to bind to. */
- watchFacePackageName: String
- ) : this(
- context,
- Intent(WatchFaceControlService.ACTION_WATCHFACE_CONTROL_SERVICE).apply {
- this.setPackage(watchFacePackageName)
- }
- )
-
- internal var serviceFuture = ResolvableFuture.create<IWatchFaceControlService?>()
-
- private val serviceConnection = object : ServiceConnection {
- override fun onServiceConnected(name: ComponentName, binder: IBinder) {
- serviceFuture.set(IWatchFaceControlService.Stub.asInterface(binder))
- }
-
- override fun onServiceDisconnected(name: ComponentName) {
- serviceFuture.set(null)
- }
- }
-
- init {
- if (!context.bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE)) {
- serviceFuture.set(null)
- }
+ public companion object {
+ /** Constructs a client which connects to a watch face in the given android package. */
+ @JvmStatic
+ public fun createWatchFaceControlClient(
+ /** Calling application's [Context]. */
+ context: Context,
+ /** The name of the package containing the watch face control service to bind to. */
+ watchFacePackageName: String
+ ): WatchFaceControlClient = WatchFaceControlClientImpl(
+ context,
+ Intent(WatchFaceControlService.ACTION_WATCHFACE_CONTROL_SERVICE).apply {
+ this.setPackage(watchFacePackageName)
+ }
+ )
}
/**
@@ -89,27 +71,9 @@
* [instanceId] is unrecognized, or [ServiceNotBoundException] if the
* WatchFaceControlService is not bound.
*/
- public fun getInteractiveWatchFaceInstanceSysUi(
+ public fun getInteractiveWatchFaceSysUiClientInstance(
instanceId: String
- ): ListenableFuture<InteractiveWatchFaceSysUiClient?> {
- val resultFuture = ResolvableFuture.create<InteractiveWatchFaceSysUiClient>()
- serviceFuture.addListener(
- {
- val service = serviceFuture.get()
- if (service == null) {
- resultFuture.setException(ServiceNotBoundException())
- } else {
- resultFuture.set(
- InteractiveWatchFaceSysUiClientImpl(
- service.getInteractiveWatchFaceInstanceSysUI(instanceId)
- )
- )
- }
- },
- { runnable -> runnable.run() }
- )
- return resultFuture
- }
+ ): ListenableFuture<InteractiveWatchFaceSysUiClient?>
/**
* Creates a [HeadlessWatchFaceClient] with the specified [DeviceConfig]. Screenshots made with
@@ -133,13 +97,88 @@
deviceConfig: DeviceConfig,
surfaceWidth: Int,
surfaceHeight: Int
+ ): ListenableFuture<HeadlessWatchFaceClient?>
+
+ /**
+ * Requests either an existing [InteractiveWatchFaceWcsClient] with the specified [id] or
+ * schedules creation of an [InteractiveWatchFaceWcsClient] for the next time the
+ * WallpaperService creates an engine.
+ *
+ * NOTE that currently only one [InteractiveWatchFaceWcsClient] per process can exist at a time.
+ *
+ * @param id The ID for the requested [InteractiveWatchFaceWcsClient].
+ * @param deviceConfig The [DeviceConfig] for the wearable.
+ * @param systemState The initial [SystemState] for the wearable.
+ * @param userStyle The initial [UserStyle], or null if the default should be used.
+ * @param idToComplicationData The initial complication data, or null if unavailable.
+ * @return a [ListenableFuture] for a [InteractiveWatchFaceWcsClient]
+ */
+ public fun getOrCreateWallpaperServiceBackedInteractiveWatchFaceWcsClient(
+ id: String,
+ deviceConfig: DeviceConfig,
+ systemState: SystemState,
+ userStyle: UserStyle?,
+ idToComplicationData: Map<Int, ComplicationData>?
+ ): ListenableFuture<InteractiveWatchFaceWcsClient>
+}
+
+internal class WatchFaceControlClientImpl internal constructor(
+ private val context: Context,
+ serviceIntent: Intent
+) : WatchFaceControlClient {
+
+ internal var serviceFuture = ResolvableFuture.create<IWatchFaceControlService?>()
+
+ private val serviceConnection = object : ServiceConnection {
+ override fun onServiceConnected(name: ComponentName, binder: IBinder) {
+ serviceFuture.set(IWatchFaceControlService.Stub.asInterface(binder))
+ }
+
+ override fun onServiceDisconnected(name: ComponentName) {
+ serviceFuture.set(null)
+ }
+ }
+
+ init {
+ if (!context.bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE)) {
+ serviceFuture.set(null)
+ }
+ }
+
+ override fun getInteractiveWatchFaceSysUiClientInstance(
+ instanceId: String
+ ): ListenableFuture<InteractiveWatchFaceSysUiClient?> {
+ val resultFuture = ResolvableFuture.create<InteractiveWatchFaceSysUiClient>()
+ serviceFuture.addListener(
+ {
+ val service = serviceFuture.get()
+ if (service == null) {
+ resultFuture.setException(WatchFaceControlClient.ServiceNotBoundException())
+ } else {
+ resultFuture.set(
+ InteractiveWatchFaceSysUiClientImpl(
+ service.getInteractiveWatchFaceInstanceSysUI(instanceId)
+ )
+ )
+ }
+ },
+ { runnable -> runnable.run() }
+ )
+ return resultFuture
+ }
+
+ override fun createHeadlessWatchFaceClient(
+ watchFaceName: ComponentName,
+ deviceConfig: DeviceConfig,
+ surfaceWidth: Int,
+ surfaceHeight: Int
): ListenableFuture<HeadlessWatchFaceClient?> {
val resultFuture = ResolvableFuture.create<HeadlessWatchFaceClient?>()
serviceFuture.addListener(
{
val service = serviceFuture.get()
if (service == null) {
- resultFuture.setException(ServiceNotBoundException())
+ resultFuture.setException(WatchFaceControlClient.ServiceNotBoundException())
} else {
resultFuture.set(
HeadlessWatchFaceClientImpl(
@@ -166,21 +205,7 @@
return resultFuture
}
- /**
- * Requests either an existing [InteractiveWatchFaceWcsClient] with the specified [id] or
- * schedules creation of an [InteractiveWatchFaceWcsClient] for the next time the
- * WallpaperService creates an engine.
- *
- * NOTE that currently only one [InteractiveWatchFaceWcsClient] per process can exist at a time.
- *
- * @param id The ID for the requested [InteractiveWatchFaceWcsClient].
- * @param deviceConfig The [DeviceConfig] for the wearable.
- * @param systemState The initial [SystemState] for the wearable.
- * @param userStyle The initial [UserStyle], or null if the default should be used.
- * @param idToComplicationData The initial complication data, or null if unavailable.
- * @return a [ListenableFuture] for a [InteractiveWatchFaceWcsClient]
- */
- public fun getOrCreateWallpaperServiceBackedInteractiveWatchFaceWcsClient(
+ override fun getOrCreateWallpaperServiceBackedInteractiveWatchFaceWcsClient(
id: String,
deviceConfig: DeviceConfig,
systemState: SystemState,
@@ -192,7 +217,7 @@
{
val service = serviceFuture.get()
if (service == null) {
- resultFuture.setException(ServiceNotBoundException())
+ resultFuture.setException(WatchFaceControlClient.ServiceNotBoundException())
} else {
val existingInstance = service.getOrCreateInteractiveWatchFaceWCS(
WallpaperInteractiveWatchFaceInstanceParams(
@@ -239,10 +264,6 @@
return resultFuture
}
- /**
- * Releases the controls service. It is an error to issue any further commands on this
- * interface.
- */
override fun close() {
context.unbindService(serviceConnection)
serviceFuture.set(null)
diff --git a/wear/wear-watchface-style/api/current.txt b/wear/wear-watchface-style/api/current.txt
index 1c063e1..4f45dac 100644
--- a/wear/wear-watchface-style/api/current.txt
+++ b/wear/wear-watchface-style/api/current.txt
@@ -69,6 +69,7 @@
}
public static final class UserStyleSetting.ComplicationsUserStyleSetting.ComplicationOverlay {
+ ctor public UserStyleSetting.ComplicationsUserStyleSetting.ComplicationOverlay(int complicationId, Boolean? enabled, android.graphics.RectF? bounds, java.util.List<? extends androidx.wear.complications.data.ComplicationType>? supportedTypes, androidx.wear.complications.DefaultComplicationProviderPolicy? defaultProviderPolicy, androidx.wear.complications.data.ComplicationType? defaultProviderType);
method public android.graphics.RectF? getBounds();
method public int getComplicationId();
method public androidx.wear.complications.DefaultComplicationProviderPolicy? getDefaultProviderPolicy();
diff --git a/wear/wear-watchface-style/api/public_plus_experimental_current.txt b/wear/wear-watchface-style/api/public_plus_experimental_current.txt
index 677a590..c223ee5 100644
--- a/wear/wear-watchface-style/api/public_plus_experimental_current.txt
+++ b/wear/wear-watchface-style/api/public_plus_experimental_current.txt
@@ -72,6 +72,7 @@
}
public static final class UserStyleSetting.ComplicationsUserStyleSetting.ComplicationOverlay {
+ ctor public UserStyleSetting.ComplicationsUserStyleSetting.ComplicationOverlay(int complicationId, Boolean? enabled, android.graphics.RectF? bounds, java.util.List<? extends androidx.wear.complications.data.ComplicationType>? supportedTypes, androidx.wear.complications.DefaultComplicationProviderPolicy? defaultProviderPolicy, androidx.wear.complications.data.ComplicationType? defaultProviderType);
method public android.graphics.RectF? getBounds();
method public int getComplicationId();
method public androidx.wear.complications.DefaultComplicationProviderPolicy? getDefaultProviderPolicy();
diff --git a/wear/wear-watchface-style/api/restricted_current.txt b/wear/wear-watchface-style/api/restricted_current.txt
index 41ff9a5..d43ffd7 100644
--- a/wear/wear-watchface-style/api/restricted_current.txt
+++ b/wear/wear-watchface-style/api/restricted_current.txt
@@ -78,6 +78,7 @@
}
public static final class UserStyleSetting.ComplicationsUserStyleSetting.ComplicationOverlay {
+ ctor public UserStyleSetting.ComplicationsUserStyleSetting.ComplicationOverlay(int complicationId, Boolean? enabled, android.graphics.RectF? bounds, java.util.List<? extends androidx.wear.complications.data.ComplicationType>? supportedTypes, androidx.wear.complications.DefaultComplicationProviderPolicy? defaultProviderPolicy, androidx.wear.complications.data.ComplicationType? defaultProviderType);
method public android.graphics.RectF? getBounds();
method public int getComplicationId();
method public androidx.wear.complications.DefaultComplicationProviderPolicy? getDefaultProviderPolicy();
diff --git a/wear/wear-watchface-style/src/main/java/androidx/wear/watchface/style/UserStyleSetting.kt b/wear/wear-watchface-style/src/main/java/androidx/wear/watchface/style/UserStyleSetting.kt
index 43451a91..7037d18 100644
--- a/wear/wear-watchface-style/src/main/java/androidx/wear/watchface/style/UserStyleSetting.kt
+++ b/wear/wear-watchface-style/src/main/java/androidx/wear/watchface/style/UserStyleSetting.kt
@@ -278,7 +278,7 @@
* Overrides to be applied to the corresponding complication's initial config (as specified
* in [androidx.wear.watchface.Complication]) when the setting is selected.
*/
- public class ComplicationOverlay internal constructor(
+ public class ComplicationOverlay constructor(
/** The id of the complication to configure. */
public val complicationId: Int,
@@ -287,31 +287,31 @@
* null then no changes are made.
*/
@get:JvmName("isEnabled")
- public val enabled: Boolean?,
+ public val enabled: Boolean? = null,
/**
* If non null, the new unit square screen space complication bounds for this
* configuration. If null then no changes are made.
*/
- public val bounds: RectF?,
+ public val bounds: RectF? = null,
/**
* If non null, the new types of complication supported by this complication for this
* configuration. If null then no changes are made.
*/
- public val supportedTypes: List<ComplicationType>?,
+ public val supportedTypes: List<ComplicationType>? = null,
/**
* If non null, the new default complication provider for this configuration. If null
* then no changes are made.
*/
- public val defaultProviderPolicy: DefaultComplicationProviderPolicy?,
+ public val defaultProviderPolicy: DefaultComplicationProviderPolicy? = null,
/**
* If non null, the new default complication provider data type. If null then no changes
* are made.
*/
- public val defaultProviderType: ComplicationType?
+ public val defaultProviderType: ComplicationType? = null
) {
public class Builder(
/** The id of the complication to configure. */
diff --git a/wear/wear-watchface/api/current.txt b/wear/wear-watchface/api/current.txt
index 406bd24..89192c1 100644
--- a/wear/wear-watchface/api/current.txt
+++ b/wear/wear-watchface/api/current.txt
@@ -78,17 +78,17 @@
public final class ComplicationsManager {
ctor public ComplicationsManager(java.util.Collection<androidx.wear.watchface.Complication> complicationCollection, androidx.wear.watchface.style.UserStyleRepository userStyleRepository);
- method @UiThread public void addTapListener(androidx.wear.watchface.ComplicationsManager.TapListener tapListener);
+ method @UiThread public void addTapListener(androidx.wear.watchface.ComplicationsManager.TapCallback tapCallback);
method @UiThread public void bringAttentionToComplication(int complicationId);
method public operator androidx.wear.watchface.Complication? get(int id);
method public androidx.wear.watchface.Complication? getBackgroundComplication();
method public androidx.wear.watchface.Complication? getComplicationAt(int x, int y);
method public java.util.Map<java.lang.Integer,androidx.wear.watchface.Complication> getComplications();
- method @UiThread public void removeTapListener(androidx.wear.watchface.ComplicationsManager.TapListener tapListener);
+ method @UiThread public void removeTapListener(androidx.wear.watchface.ComplicationsManager.TapCallback tapCallback);
property public final java.util.Map<java.lang.Integer,androidx.wear.watchface.Complication> complications;
}
- public static interface ComplicationsManager.TapListener {
+ public static interface ComplicationsManager.TapCallback {
method public default void onComplicationDoubleTapped(int complicationId);
method public default void onComplicationSingleTapped(int complicationId);
}
@@ -186,18 +186,38 @@
}
public final class WatchFace {
+ method public androidx.wear.watchface.WatchFace.LegacyWatchFaceOverlayStyle getLegacyWatchFaceStyle();
+ method public long getPreviewReferenceTimeMillis();
method @UiThread public void invalidate();
+ method public static boolean isLegacyWatchFaceOverlayStyleSupported();
method public void postInvalidate();
+ property public final androidx.wear.watchface.WatchFace.LegacyWatchFaceOverlayStyle legacyWatchFaceStyle;
+ property public final long previewReferenceTimeMillis;
+ field public static final androidx.wear.watchface.WatchFace.Companion Companion;
}
public static final class WatchFace.Builder {
ctor public WatchFace.Builder(int watchFaceType, long interactiveUpdateRateMillis, internal androidx.wear.watchface.style.UserStyleRepository userStyleRepository, internal androidx.wear.watchface.ComplicationsManager complicationsManager, internal androidx.wear.watchface.Renderer renderer, androidx.wear.watchface.WatchFaceHost watchFaceHost, androidx.wear.watchface.WatchState watchState);
method public androidx.wear.watchface.WatchFace build();
+ method public androidx.wear.watchface.WatchFace.Builder setLegacyWatchFaceStyle(androidx.wear.watchface.WatchFace.LegacyWatchFaceOverlayStyle legacyWatchFaceStyle);
method public androidx.wear.watchface.WatchFace.Builder setPreviewReferenceTimeMillis(long previewReferenceTimeMillis);
- method public androidx.wear.watchface.WatchFace.Builder setWear2AccentColor(@ColorInt int accentColor);
- method public androidx.wear.watchface.WatchFace.Builder setWear2AcceptsTapEvents(boolean acceptsTapEvents);
- method public androidx.wear.watchface.WatchFace.Builder setWear2StatusBarGravity(int statusBarGravity);
- method public androidx.wear.watchface.WatchFace.Builder setWear2ViewProtectionMode(int viewProtectionMode);
+ }
+
+ public static final class WatchFace.Companion {
+ method public boolean isLegacyWatchFaceOverlayStyleSupported();
+ }
+
+ public static final class WatchFace.LegacyWatchFaceOverlayStyle {
+ ctor public WatchFace.LegacyWatchFaceOverlayStyle(int viewProtectionMode, int statusBarGravity, boolean tapEventsAccepted, @ColorInt int accentColor);
+ ctor public WatchFace.LegacyWatchFaceOverlayStyle(int viewProtectionMode, int statusBarGravity, boolean tapEventsAccepted);
+ method public int getAccentColor();
+ method public int getStatusBarGravity();
+ method public int getViewProtectionMode();
+ method public boolean isTapEventsAccepted();
+ property public final int accentColor;
+ property public final int statusBarGravity;
+ property public final boolean tapEventsAccepted;
+ property public final int viewProtectionMode;
}
public final class WatchFaceHost {
@@ -220,10 +240,10 @@
ctor public WatchState(androidx.wear.watchface.ObservableWatchData<java.lang.Integer> interruptionFilter, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isAmbient, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isBatteryLowAndNotCharging, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isVisible, boolean hasLowBitAmbient, boolean hasBurnInProtection, int screenShape, long analogPreviewReferenceTimeMillis, long digitalPreviewReferenceTimeMillis);
method public long getAnalogPreviewReferenceTimeMillis();
method public long getDigitalPreviewReferenceTimeMillis();
- method public boolean getHasBurnInProtection();
- method public boolean getHasLowBitAmbient();
method public androidx.wear.watchface.ObservableWatchData<java.lang.Integer> getInterruptionFilter();
method public int getScreenShape();
+ method public boolean hasBurnInProtection();
+ method public boolean hasLowBitAmbient();
method public androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isAmbient();
method public androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isVisible();
property public final long analogPreviewReferenceTimeMillis;
diff --git a/wear/wear-watchface/api/public_plus_experimental_current.txt b/wear/wear-watchface/api/public_plus_experimental_current.txt
index 406bd24..89192c1 100644
--- a/wear/wear-watchface/api/public_plus_experimental_current.txt
+++ b/wear/wear-watchface/api/public_plus_experimental_current.txt
@@ -78,17 +78,17 @@
public final class ComplicationsManager {
ctor public ComplicationsManager(java.util.Collection<androidx.wear.watchface.Complication> complicationCollection, androidx.wear.watchface.style.UserStyleRepository userStyleRepository);
- method @UiThread public void addTapListener(androidx.wear.watchface.ComplicationsManager.TapListener tapListener);
+ method @UiThread public void addTapListener(androidx.wear.watchface.ComplicationsManager.TapCallback tapCallback);
method @UiThread public void bringAttentionToComplication(int complicationId);
method public operator androidx.wear.watchface.Complication? get(int id);
method public androidx.wear.watchface.Complication? getBackgroundComplication();
method public androidx.wear.watchface.Complication? getComplicationAt(int x, int y);
method public java.util.Map<java.lang.Integer,androidx.wear.watchface.Complication> getComplications();
- method @UiThread public void removeTapListener(androidx.wear.watchface.ComplicationsManager.TapListener tapListener);
+ method @UiThread public void removeTapListener(androidx.wear.watchface.ComplicationsManager.TapCallback tapCallback);
property public final java.util.Map<java.lang.Integer,androidx.wear.watchface.Complication> complications;
}
- public static interface ComplicationsManager.TapListener {
+ public static interface ComplicationsManager.TapCallback {
method public default void onComplicationDoubleTapped(int complicationId);
method public default void onComplicationSingleTapped(int complicationId);
}
@@ -186,18 +186,38 @@
}
public final class WatchFace {
+ method public androidx.wear.watchface.WatchFace.LegacyWatchFaceOverlayStyle getLegacyWatchFaceStyle();
+ method public long getPreviewReferenceTimeMillis();
method @UiThread public void invalidate();
+ method public static boolean isLegacyWatchFaceOverlayStyleSupported();
method public void postInvalidate();
+ property public final androidx.wear.watchface.WatchFace.LegacyWatchFaceOverlayStyle legacyWatchFaceStyle;
+ property public final long previewReferenceTimeMillis;
+ field public static final androidx.wear.watchface.WatchFace.Companion Companion;
}
public static final class WatchFace.Builder {
ctor public WatchFace.Builder(int watchFaceType, long interactiveUpdateRateMillis, internal androidx.wear.watchface.style.UserStyleRepository userStyleRepository, internal androidx.wear.watchface.ComplicationsManager complicationsManager, internal androidx.wear.watchface.Renderer renderer, androidx.wear.watchface.WatchFaceHost watchFaceHost, androidx.wear.watchface.WatchState watchState);
method public androidx.wear.watchface.WatchFace build();
+ method public androidx.wear.watchface.WatchFace.Builder setLegacyWatchFaceStyle(androidx.wear.watchface.WatchFace.LegacyWatchFaceOverlayStyle legacyWatchFaceStyle);
method public androidx.wear.watchface.WatchFace.Builder setPreviewReferenceTimeMillis(long previewReferenceTimeMillis);
- method public androidx.wear.watchface.WatchFace.Builder setWear2AccentColor(@ColorInt int accentColor);
- method public androidx.wear.watchface.WatchFace.Builder setWear2AcceptsTapEvents(boolean acceptsTapEvents);
- method public androidx.wear.watchface.WatchFace.Builder setWear2StatusBarGravity(int statusBarGravity);
- method public androidx.wear.watchface.WatchFace.Builder setWear2ViewProtectionMode(int viewProtectionMode);
+ }
+
+ public static final class WatchFace.Companion {
+ method public boolean isLegacyWatchFaceOverlayStyleSupported();
+ }
+
+ public static final class WatchFace.LegacyWatchFaceOverlayStyle {
+ ctor public WatchFace.LegacyWatchFaceOverlayStyle(int viewProtectionMode, int statusBarGravity, boolean tapEventsAccepted, @ColorInt int accentColor);
+ ctor public WatchFace.LegacyWatchFaceOverlayStyle(int viewProtectionMode, int statusBarGravity, boolean tapEventsAccepted);
+ method public int getAccentColor();
+ method public int getStatusBarGravity();
+ method public int getViewProtectionMode();
+ method public boolean isTapEventsAccepted();
+ property public final int accentColor;
+ property public final int statusBarGravity;
+ property public final boolean tapEventsAccepted;
+ property public final int viewProtectionMode;
}
public final class WatchFaceHost {
@@ -220,10 +240,10 @@
ctor public WatchState(androidx.wear.watchface.ObservableWatchData<java.lang.Integer> interruptionFilter, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isAmbient, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isBatteryLowAndNotCharging, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isVisible, boolean hasLowBitAmbient, boolean hasBurnInProtection, int screenShape, long analogPreviewReferenceTimeMillis, long digitalPreviewReferenceTimeMillis);
method public long getAnalogPreviewReferenceTimeMillis();
method public long getDigitalPreviewReferenceTimeMillis();
- method public boolean getHasBurnInProtection();
- method public boolean getHasLowBitAmbient();
method public androidx.wear.watchface.ObservableWatchData<java.lang.Integer> getInterruptionFilter();
method public int getScreenShape();
+ method public boolean hasBurnInProtection();
+ method public boolean hasLowBitAmbient();
method public androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isAmbient();
method public androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isVisible();
property public final long analogPreviewReferenceTimeMillis;
diff --git a/wear/wear-watchface/api/restricted_current.txt b/wear/wear-watchface/api/restricted_current.txt
index 3128c72..96c9dce 100644
--- a/wear/wear-watchface/api/restricted_current.txt
+++ b/wear/wear-watchface/api/restricted_current.txt
@@ -78,17 +78,17 @@
public final class ComplicationsManager {
ctor public ComplicationsManager(java.util.Collection<androidx.wear.watchface.Complication> complicationCollection, androidx.wear.watchface.style.UserStyleRepository userStyleRepository);
- method @UiThread public void addTapListener(androidx.wear.watchface.ComplicationsManager.TapListener tapListener);
+ method @UiThread public void addTapListener(androidx.wear.watchface.ComplicationsManager.TapCallback tapCallback);
method @UiThread public void bringAttentionToComplication(int complicationId);
method public operator androidx.wear.watchface.Complication? get(int id);
method public androidx.wear.watchface.Complication? getBackgroundComplication();
method public androidx.wear.watchface.Complication? getComplicationAt(int x, int y);
method public java.util.Map<java.lang.Integer,androidx.wear.watchface.Complication> getComplications();
- method @UiThread public void removeTapListener(androidx.wear.watchface.ComplicationsManager.TapListener tapListener);
+ method @UiThread public void removeTapListener(androidx.wear.watchface.ComplicationsManager.TapCallback tapCallback);
property public final java.util.Map<java.lang.Integer,androidx.wear.watchface.Complication> complications;
}
- public static interface ComplicationsManager.TapListener {
+ public static interface ComplicationsManager.TapCallback {
method public default void onComplicationDoubleTapped(int complicationId);
method public default void onComplicationSingleTapped(int complicationId);
}
@@ -216,19 +216,39 @@
}
public final class WatchFace {
+ method public androidx.wear.watchface.WatchFace.LegacyWatchFaceOverlayStyle getLegacyWatchFaceStyle();
+ method public long getPreviewReferenceTimeMillis();
method @UiThread public void invalidate();
+ method public static boolean isLegacyWatchFaceOverlayStyleSupported();
method public void postInvalidate();
+ property public final androidx.wear.watchface.WatchFace.LegacyWatchFaceOverlayStyle legacyWatchFaceStyle;
+ property public final long previewReferenceTimeMillis;
+ field public static final androidx.wear.watchface.WatchFace.Companion Companion;
}
public static final class WatchFace.Builder {
ctor public WatchFace.Builder(int watchFaceType, long interactiveUpdateRateMillis, internal androidx.wear.watchface.style.UserStyleRepository userStyleRepository, internal androidx.wear.watchface.ComplicationsManager complicationsManager, internal androidx.wear.watchface.Renderer renderer, androidx.wear.watchface.WatchFaceHost watchFaceHost, androidx.wear.watchface.WatchState watchState);
method public androidx.wear.watchface.WatchFace build();
+ method public androidx.wear.watchface.WatchFace.Builder setLegacyWatchFaceStyle(androidx.wear.watchface.WatchFace.LegacyWatchFaceOverlayStyle legacyWatchFaceStyle);
method public androidx.wear.watchface.WatchFace.Builder setPreviewReferenceTimeMillis(long previewReferenceTimeMillis);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public androidx.wear.watchface.WatchFace.Builder setSystemTimeProvider(androidx.wear.watchface.WatchFace.SystemTimeProvider systemTimeProvider);
- method public androidx.wear.watchface.WatchFace.Builder setWear2AccentColor(@ColorInt int accentColor);
- method public androidx.wear.watchface.WatchFace.Builder setWear2AcceptsTapEvents(boolean acceptsTapEvents);
- method public androidx.wear.watchface.WatchFace.Builder setWear2StatusBarGravity(int statusBarGravity);
- method public androidx.wear.watchface.WatchFace.Builder setWear2ViewProtectionMode(int viewProtectionMode);
+ }
+
+ public static final class WatchFace.Companion {
+ method public boolean isLegacyWatchFaceOverlayStyleSupported();
+ }
+
+ public static final class WatchFace.LegacyWatchFaceOverlayStyle {
+ ctor public WatchFace.LegacyWatchFaceOverlayStyle(int viewProtectionMode, int statusBarGravity, boolean tapEventsAccepted, @ColorInt int accentColor);
+ ctor public WatchFace.LegacyWatchFaceOverlayStyle(int viewProtectionMode, int statusBarGravity, boolean tapEventsAccepted);
+ method public int getAccentColor();
+ method public int getStatusBarGravity();
+ method public int getViewProtectionMode();
+ method public boolean isTapEventsAccepted();
+ property public final int accentColor;
+ property public final int statusBarGravity;
+ property public final boolean tapEventsAccepted;
+ property public final int viewProtectionMode;
}
@RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static interface WatchFace.SystemTimeProvider {
@@ -268,10 +288,10 @@
ctor public WatchState(androidx.wear.watchface.ObservableWatchData<java.lang.Integer> interruptionFilter, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isAmbient, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isBatteryLowAndNotCharging, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isVisible, boolean hasLowBitAmbient, boolean hasBurnInProtection, int screenShape, long analogPreviewReferenceTimeMillis, long digitalPreviewReferenceTimeMillis);
method public long getAnalogPreviewReferenceTimeMillis();
method public long getDigitalPreviewReferenceTimeMillis();
- method public boolean getHasBurnInProtection();
- method public boolean getHasLowBitAmbient();
method public androidx.wear.watchface.ObservableWatchData<java.lang.Integer> getInterruptionFilter();
method public int getScreenShape();
+ method public boolean hasBurnInProtection();
+ method public boolean hasLowBitAmbient();
method public androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isAmbient();
method public androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isVisible();
property public final long analogPreviewReferenceTimeMillis;
diff --git a/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/ExampleOpenGLWatchFaceService.kt b/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/ExampleOpenGLWatchFaceService.kt
index 6c7faff..2e66b98 100644
--- a/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/ExampleOpenGLWatchFaceService.kt
+++ b/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/ExampleOpenGLWatchFaceService.kt
@@ -133,7 +133,13 @@
renderer,
watchFaceHost,
watchState
- ).setWear2StatusBarGravity(Gravity.RIGHT or Gravity.TOP).build()
+ ).setLegacyWatchFaceStyle(
+ WatchFace.LegacyWatchFaceOverlayStyle(
+ 0,
+ Gravity.RIGHT or Gravity.TOP,
+ true
+ )
+ ).build()
}
}
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/Complication.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/Complication.kt
index 59b6ac5..02185b38 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/Complication.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/Complication.kt
@@ -304,14 +304,14 @@
canvasComplication.onAttach(this)
}
- internal interface InvalidateCallback {
+ internal interface InvalidateListener {
/** Requests redraw. */
@UiThread
fun onInvalidate()
}
private lateinit var complicationsManager: ComplicationsManager
- private lateinit var invalidateCallback: InvalidateCallback
+ private lateinit var invalidateListener: InvalidateListener
private var _unitSquareBounds = unitSquareBounds
internal var unitSquareBoundsDirty = true
@@ -486,15 +486,15 @@
* loading a [Drawable].
*/
public fun invalidate() {
- invalidateCallback.onInvalidate()
+ invalidateListener.onInvalidate()
}
internal fun init(
complicationsManager: ComplicationsManager,
- invalidateCallback: InvalidateCallback
+ invalidateListener: InvalidateListener
) {
this.complicationsManager = complicationsManager
- this.invalidateCallback = invalidateCallback
+ this.invalidateListener = invalidateListener
}
internal fun scheduleUpdateComplications() {
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/ComplicationsManager.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/ComplicationsManager.kt
index da6da1e..e97f277 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/ComplicationsManager.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/ComplicationsManager.kt
@@ -59,7 +59,7 @@
*/
private val userStyleRepository: UserStyleRepository
) {
- public interface TapListener {
+ public interface TapCallback {
/**
* Called when the user single taps on a complication.
*
@@ -112,7 +112,7 @@
}
)
- private val complicationListeners = HashSet<TapListener>()
+ private val complicationListeners = HashSet<TapCallback>()
@VisibleForTesting
internal constructor(
@@ -127,7 +127,7 @@
watchFaceHostApi: WatchFaceHostApi,
calendar: Calendar,
renderer: Renderer,
- complicationInvalidateCallback: Complication.InvalidateCallback
+ complicationInvalidateListener: Complication.InvalidateListener
) {
this.watchFaceHostApi = watchFaceHostApi
this.calendar = calendar
@@ -135,7 +135,7 @@
pendingUpdate = CancellableUniqueTask(watchFaceHostApi.getHandler())
for ((_, complication) in complications) {
- complication.init(this, complicationInvalidateCallback)
+ complication.init(this, complicationInvalidateListener)
}
val complicationsStyleCategory =
@@ -408,20 +408,20 @@
}
/**
- * Adds a [TapListener] which is called whenever the user interacts with a
+ * Adds a [TapCallback] which is called whenever the user interacts with a
* complication.
*/
@UiThread
@SuppressLint("ExecutorRegistration")
- public fun addTapListener(tapListener: TapListener) {
- complicationListeners.add(tapListener)
+ public fun addTapListener(tapCallback: TapCallback) {
+ complicationListeners.add(tapCallback)
}
/**
- * Removes a [TapListener] previously added by [addTapListener].
+ * Removes a [TapCallback] previously added by [addTapListener].
*/
@UiThread
- public fun removeTapListener(tapListener: TapListener) {
- complicationListeners.remove(tapListener)
+ public fun removeTapListener(tapCallback: TapCallback) {
+ complicationListeners.remove(tapCallback)
}
}
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFace.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
index e49580f..1b6cc48 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
@@ -37,6 +37,7 @@
import androidx.annotation.VisibleForTesting
import androidx.wear.complications.SystemProviders
import androidx.wear.complications.data.ComplicationData
+import androidx.wear.watchface.control.IInteractiveWatchFaceSysUI
import androidx.wear.watchface.data.RenderParametersWireFormat
import androidx.wear.watchface.style.UserStyle
import androidx.wear.watchface.style.UserStyleRepository
@@ -130,6 +131,62 @@
}
/**
+ * Legacy Wear 2.0 watch face styling. These settings will be ignored on Wear 3.0 devices.
+ *
+ * @throws IllegalArgumentException if [viewProtectionMode] has an unexpected value
+ */
+ public class LegacyWatchFaceOverlayStyle @JvmOverloads constructor(
+ /**
+ * The view protection mode bit field, must be a combination of
+ * zero or more of [PROTECT_STATUS_BAR], [PROTECT_HOTWORD_INDICATOR],
+ * [PROTECT_WHOLE_SCREEN].
+ */
+ public val viewProtectionMode: Int,
+
+ /**
+ * Controls the position of status icons (battery state, lack of connection) on the screen.
+ *
+ * This must be any combination of horizontal Gravity constant
+ * ([Gravity.LEFT], [Gravity.CENTER_HORIZONTAL], [Gravity.RIGHT])
+ * and vertical Gravity constants ([Gravity.TOP], [Gravity,CENTER_VERTICAL},
+ * [Gravity,BOTTOM]), e.g. {@code Gravity.LEFT | Gravity.BOTTOM}. On circular screens,
+ * only the vertical gravity is respected.
+ */
+ public val statusBarGravity: Int,
+
+ /**
+ * Controls whether this watch face accepts tap events.
+ *
+ * Watchfaces that set this {@code true} are indicating they are prepared to receive
+ * [IInteractiveWatchFaceSysUI.TAP_TYPE_TOUCH],
+ * [IInteractiveWatchFaceSysUI.TAP_TYPE_TOUCH_CANCEL], and
+ * [IInteractiveWatchFaceSysUI.TAP_TYPE_TAP] events.
+ */
+ @get:JvmName("isTapEventsAccepted")
+ public val tapEventsAccepted: Boolean,
+
+ /**
+ * The accent color which will be used when drawing the unread notification indicator.
+ * Default color is white.
+ */
+ @ColorInt
+ public val accentColor: Int = WatchFaceStyle.DEFAULT_ACCENT_COLOR
+ ) {
+ init {
+ if (viewProtectionMode < 0 ||
+ viewProtectionMode >
+ WatchFaceStyle.PROTECT_STATUS_BAR + WatchFaceStyle.PROTECT_HOTWORD_INDICATOR +
+ WatchFaceStyle.PROTECT_WHOLE_SCREEN
+ ) {
+ throw IllegalArgumentException(
+ "View protection must be combination " +
+ "PROTECT_STATUS_BAR, PROTECT_HOTWORD_INDICATOR or PROTECT_WHOLE_SCREEN"
+ )
+ }
+ }
+ }
+
+ /**
* Builder for a [WatchFace].
*
* If unreadCountIndicator or notificationIndicator are hidden then the WatchState class will
@@ -167,13 +224,14 @@
*/
private val watchState: WatchState
) {
- private var viewProtectionMode: Int = 0
- private var statusBarGravity: Int = 0
private var overridePreviewReferenceTimeMillis: Long? = null
- @ColorInt
- private var accentColor: Int = WatchFaceStyle.DEFAULT_ACCENT_COLOR
- private var acceptsTapEvents: Boolean = true
+ private var legacyWatchFaceStyle = LegacyWatchFaceOverlayStyle(
+ 0,
+ 0,
+ true
+ )
+
private var systemTimeProvider: SystemTimeProvider = object : SystemTimeProvider {
override fun getSystemTimeMillis() = System.currentTimeMillis()
}
@@ -190,70 +248,12 @@
}
/**
- * Only has an impact on devices running Wear 2.x, on other devices this is a no-op and the
- * functionality is replaced by... TODO(alexclarke): Design the replacement.
- *
- * @param viewProtectionMode The view protection mode bit field, must be a combination of
- * zero or more of [PROTECT_STATUS_BAR], [PROTECT_HOTWORD_INDICATOR],
- * [PROTECT_WHOLE_SCREEN].
- * @throws IllegalArgumentException if viewProtectionMode has an unexpected value
+ * Sets the legacy [LegacyWatchFaceOverlayStyle] which only affects Wear 2.0 devices.
*/
- public fun setWear2ViewProtectionMode(viewProtectionMode: Int): Builder = apply {
- if (viewProtectionMode < 0 ||
- viewProtectionMode >
- WatchFaceStyle.PROTECT_STATUS_BAR + WatchFaceStyle.PROTECT_HOTWORD_INDICATOR +
- WatchFaceStyle.PROTECT_WHOLE_SCREEN
- ) {
- throw IllegalArgumentException(
- "View protection must be combination " +
- "PROTECT_STATUS_BAR, PROTECT_HOTWORD_INDICATOR or PROTECT_WHOLE_SCREEN"
- )
- }
- this.viewProtectionMode = viewProtectionMode
- }
-
- /**
- * Sets position of status icons (battery state, lack of connection) on the screen.
- *
- * <p>Only has an impact on devices running Wear 2.x, on other devices this is a no-op and
- * the functionality is replaced by... TODO(alexclarke): Design the replacement.
- *
- * @param statusBarGravity This must be any combination of horizontal Gravity constant
- * ([Gravity.LEFT], [Gravity.CENTER_HORIZONTAL], [Gravity.RIGHT])
- * and vertical Gravity constants ([Gravity.TOP], [Gravity,CENTER_VERTICAL},
- * [Gravity,BOTTOM]), e.g. {@code Gravity.LEFT | Gravity.BOTTOM}. On circular screens,
- * only the vertical gravity is respected.
- */
- public fun setWear2StatusBarGravity(statusBarGravity: Int): Builder = apply {
- this.statusBarGravity = statusBarGravity
- }
-
- /**
- * Sets the accent color which can be set by developers to customise watch face. It will be
- * used when drawing the unread notification indicator. Default color is white.
- *
- * <p>Only has an impact on devices running Wear 2.x, on other devices this is a no-op and
- * the functionality is replaced by... TODO(alexclarke): Design the replacement.
- */
- public fun setWear2AccentColor(@ColorInt accentColor: Int): Builder = apply {
- this.accentColor = accentColor
- }
-
- /**
- * Sets whether this watchface accepts tap events. The default is false.
- *
- * <p>Only has an impact on devices running Wear 2.x, on other devices this is a no-op and
- * the functionality is replaced by... TODO(alexclarke): Design the replacement.
- *
- * <p>Watchfaces that set this {@code true} are indicating they are prepared to receive
- * [android.support.wearable.watchface.WatchFaceService.TAP_TYPE_TOUCH],
- * [android.support.wearable.watchface.WatchFaceService.TAP_TYPE_TOUCH_CANCEL], and
- * [android.support.wearable.watchface.WatchFaceService.TAP_TYPE_TAP] events.
- *
- * @param acceptsTapEvents whether to receive touch events.
- */
- public fun setWear2AcceptsTapEvents(acceptsTapEvents: Boolean): Builder = apply {
- this.acceptsTapEvents = acceptsTapEvents
+ public fun setLegacyWatchFaceStyle(
+ legacyWatchFaceStyle: LegacyWatchFaceOverlayStyle
+ ): Builder = apply {
+ this.legacyWatchFaceStyle = legacyWatchFaceStyle
}
/** @hide */
@@ -280,12 +280,12 @@
watchState,
WatchFaceStyle(
componentName,
- viewProtectionMode,
- statusBarGravity,
- accentColor,
+ legacyWatchFaceStyle.viewProtectionMode,
+ legacyWatchFaceStyle.statusBarGravity,
+ legacyWatchFaceStyle.accentColor,
false,
false,
- acceptsTapEvents
+ legacyWatchFaceStyle.tapEventsAccepted
),
componentName,
systemTimeProvider
@@ -293,7 +293,12 @@
}
}
- internal companion object {
+ public companion object {
+ /** Returns whether [LegacyWatchFaceOverlayStyle] is supported on this device. */
+ @JvmStatic
+ public fun isLegacyWatchFaceOverlayStyleSupported(): Boolean =
+ android.os.Build.VERSION.SDK_INT <= 27
+
internal const val NO_DEFAULT_PROVIDER = SystemProviders.NO_PROVIDER
internal const val MOCK_TIME_INTENT = "androidx.wear.watchface.MockTime"
@@ -401,13 +406,25 @@
}
}
- internal val previewReferenceTimeMillis =
+ /** The UTC reference time for editor preview images in milliseconds since the epoch. */
+ public val previewReferenceTimeMillis: Long =
overridePreviewReferenceTimeMillis ?: when (watchFaceType) {
WatchFaceType.ANALOG -> watchState.analogPreviewReferenceTimeMillis
WatchFaceType.DIGITAL -> watchState.digitalPreviewReferenceTimeMillis
else -> throw InvalidParameterException("Unrecognized watchFaceType")
}
+ /**
+ * The legacy Wear 2.0 [LegacyWatchFaceOverlayStyle] for this watch face. Only affects Wear 2.0 devices.
+ */
+ public val legacyWatchFaceStyle: LegacyWatchFaceOverlayStyle
+ get() = LegacyWatchFaceOverlayStyle(
+ watchFaceStyle.viewProtectionMode,
+ watchFaceStyle.statusBarGravity,
+ watchFaceStyle.acceptsTapEvents,
+ watchFaceStyle.accentColor
+ )
+
init {
// If the system has a stored user style then Home/SysUI is in charge of style
// persistence, otherwise we need to do our own.
@@ -470,7 +487,7 @@
var initFinished = false
complicationsManager.init(
watchFaceHostApi, calendar, renderer,
- object : Complication.InvalidateCallback {
+ object : Complication.InvalidateListener {
@SuppressWarnings("SyntheticAccessor")
override fun onInvalidate() {
// Ensure we render a frame if the Complication needs rendering, e.g. because it
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceHostApi.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceHostApi.kt
index 554507e..a1d4d01 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceHostApi.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceHostApi.kt
@@ -58,18 +58,19 @@
* Sets ContentDescriptionLabels for text-to-speech screen readers to make your
* complications, buttons, and any other text on your watchface accessible.
*
- * <p>Each label is a region of the screen in absolute coordinates, along with
+ * Each label is a region of the screen in absolute coordinates, along with
* time-dependent text. The regions must not overlap.
*
- * <p>You must set all labels at the same time; previous labels will be cleared. An empty
+ * You must set all labels at the same time; previous labels will be cleared. An empty
* array clears all labels.
*
- * <p>In addition to labeling your complications, please include a label that will read the
+ * In addition to labeling your complications, please include a label that will read the
* current time. You can use [android.support.wearable.watchface.accessibility
- * .AccessibilityUtils.makeTimeAsComplicationText] to generate the proper ComplicationText.
+ * .AccessibilityUtils.makeTimeAsComplicationText] to generate the proper
+ * [android.support.wearable.complications.ComplicationText].
*
- * <p>This is a fairly expensive operation so use it sparingly (e.g. do not call it in
- * onDraw()).
+ * This is a fairly expensive operation so use it sparingly (e.g. do not call it in
+ * `onDraw()`).
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public fun setContentDescriptionLabels(labels: Array<ContentDescriptionLabel>)
@@ -78,13 +79,13 @@
* Sets the complications which are active in the watchface. Complication data will be
* received for these ids.
*
- * <p>Any ids not in the provided {@code ids} will be considered inactive.
+ * Any ids not in the provided [watchFaceComplicationIds] will be considered inactive.
*
- * <p>If providers and complication data types have been configured, the data received will
+ * If providers and complication data types have been configured, the data received will
* match the type chosen by the user. If no provider has been configured, data of type
* [ComplicationData.TYPE_NOT_CONFIGURED] will be received.
*
- * <p>Ids here are chosen by the watch face to represent each complication and can be any
+ * Ids here are chosen by the watch face to represent each complication and can be any
* integer.
*/
public fun setActiveComplications(watchFaceComplicationIds: IntArray)
@@ -95,18 +96,18 @@
* first doesn't exist then the next one is tried and so on. If none of them exist then the
* specified system provider is set as the default instead.
*
- * <p>This will do nothing if the providers are not installed, or if the specified type is
+ * This will do nothing if the providers are not installed, or if the specified type is
* not supported by the providers, or if the user has already selected a provider for the
* complication.
*
- * <p>Note that if the watch face has not yet been granted the RECEIVE_COMPLICATION_DATA
+ * Note that if the watch face has not yet been granted the `RECEIVE_COMPLICATION_DATA`
* permission, it will not be able to receive data from the provider unless the provider is
* from the same app package as the watch face, or the provider lists the watch face as a
* safe watch face. For system providers that may be used before your watch face has the
* permission, use [.setDefaultSystemComplicationProvider] with a safe provider
* instead.
*
- * <p>A provider not satisfying the above conditions may still be set as a default using
+ * A provider not satisfying the above conditions may still be set as a default using
* this method, but the watch face will receive placeholder data of type
* [ComplicationData.TYPE_NO_PERMISSION] until the permission has been granted.
*
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchState.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchState.kt
index 4f18d4d..e48eb3c 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchState.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchState.kt
@@ -73,9 +73,11 @@
public val isVisible: ObservableWatchData<Boolean>,
/** Whether or not the watch hardware supports low bit ambient support. */
+ @get:JvmName("hasLowBitAmbient")
public val hasLowBitAmbient: Boolean,
/** Whether or not the watch hardware supports burn in protection. */
+ @get:JvmName("hasBurnInProtection")
public val hasBurnInProtection: Boolean,
/** The physical shape of the screen. */
diff --git a/wear/wear-watchface/src/test/java/androidx/wear/watchface/TestCommon.kt b/wear/wear-watchface/src/test/java/androidx/wear/watchface/TestCommon.kt
index cf39c93..886cbeb 100644
--- a/wear/wear-watchface/src/test/java/androidx/wear/watchface/TestCommon.kt
+++ b/wear/wear-watchface/src/test/java/androidx/wear/watchface/TestCommon.kt
@@ -62,7 +62,7 @@
)
complicationsManager.addTapListener(
- object : ComplicationsManager.TapListener {
+ object : ComplicationsManager.TapCallback {
override fun onComplicationSingleTapped(complicationId: Int) {
complicationSingleTapped = complicationId
}
diff --git a/window/window-extensions/api/current.txt b/window/window-extensions/api/current.txt
index 12ad1d9..01608ca 100644
--- a/window/window-extensions/api/current.txt
+++ b/window/window-extensions/api/current.txt
@@ -21,14 +21,14 @@
public interface ExtensionInterface {
method public void onDeviceStateListenersChanged(boolean);
- method public void onWindowLayoutChangeListenerAdded(android.content.Context);
- method public void onWindowLayoutChangeListenerRemoved(android.content.Context);
+ method public void onWindowLayoutChangeListenerAdded(android.app.Activity);
+ method public void onWindowLayoutChangeListenerRemoved(android.app.Activity);
method public void setExtensionCallback(androidx.window.extensions.ExtensionInterface.ExtensionCallback);
}
public static interface ExtensionInterface.ExtensionCallback {
method public void onDeviceStateChanged(androidx.window.extensions.ExtensionDeviceState);
- method public void onWindowLayoutChanged(android.content.Context, androidx.window.extensions.ExtensionWindowLayoutInfo);
+ method public void onWindowLayoutChanged(android.app.Activity, androidx.window.extensions.ExtensionWindowLayoutInfo);
}
public final class ExtensionProvider {
diff --git a/window/window-extensions/api/public_plus_experimental_current.txt b/window/window-extensions/api/public_plus_experimental_current.txt
index 12ad1d9..01608ca 100644
--- a/window/window-extensions/api/public_plus_experimental_current.txt
+++ b/window/window-extensions/api/public_plus_experimental_current.txt
@@ -21,14 +21,14 @@
public interface ExtensionInterface {
method public void onDeviceStateListenersChanged(boolean);
- method public void onWindowLayoutChangeListenerAdded(android.content.Context);
- method public void onWindowLayoutChangeListenerRemoved(android.content.Context);
+ method public void onWindowLayoutChangeListenerAdded(android.app.Activity);
+ method public void onWindowLayoutChangeListenerRemoved(android.app.Activity);
method public void setExtensionCallback(androidx.window.extensions.ExtensionInterface.ExtensionCallback);
}
public static interface ExtensionInterface.ExtensionCallback {
method public void onDeviceStateChanged(androidx.window.extensions.ExtensionDeviceState);
- method public void onWindowLayoutChanged(android.content.Context, androidx.window.extensions.ExtensionWindowLayoutInfo);
+ method public void onWindowLayoutChanged(android.app.Activity, androidx.window.extensions.ExtensionWindowLayoutInfo);
}
public final class ExtensionProvider {
diff --git a/window/window-extensions/api/restricted_current.txt b/window/window-extensions/api/restricted_current.txt
index 12ad1d9..01608ca 100644
--- a/window/window-extensions/api/restricted_current.txt
+++ b/window/window-extensions/api/restricted_current.txt
@@ -21,14 +21,14 @@
public interface ExtensionInterface {
method public void onDeviceStateListenersChanged(boolean);
- method public void onWindowLayoutChangeListenerAdded(android.content.Context);
- method public void onWindowLayoutChangeListenerRemoved(android.content.Context);
+ method public void onWindowLayoutChangeListenerAdded(android.app.Activity);
+ method public void onWindowLayoutChangeListenerRemoved(android.app.Activity);
method public void setExtensionCallback(androidx.window.extensions.ExtensionInterface.ExtensionCallback);
}
public static interface ExtensionInterface.ExtensionCallback {
method public void onDeviceStateChanged(androidx.window.extensions.ExtensionDeviceState);
- method public void onWindowLayoutChanged(android.content.Context, androidx.window.extensions.ExtensionWindowLayoutInfo);
+ method public void onWindowLayoutChanged(android.app.Activity, androidx.window.extensions.ExtensionWindowLayoutInfo);
}
public final class ExtensionProvider {
diff --git a/window/window-extensions/src/main/java/androidx/window/extensions/ExtensionInterface.java b/window/window-extensions/src/main/java/androidx/window/extensions/ExtensionInterface.java
index 4926c63..6c809ae 100644
--- a/window/window-extensions/src/main/java/androidx/window/extensions/ExtensionInterface.java
+++ b/window/window-extensions/src/main/java/androidx/window/extensions/ExtensionInterface.java
@@ -16,7 +16,7 @@
package androidx.window.extensions;
-import android.content.Context;
+import android.app.Activity;
import androidx.annotation.NonNull;
@@ -38,19 +38,15 @@
/**
* Notifies extension that a listener for display feature layout changes was registered for the
- * given activity context.
- *
- * @param context an instance of {@link android.app.Activity}
+ * given {@link Activity} context.
*/
- void onWindowLayoutChangeListenerAdded(@NonNull Context context);
+ void onWindowLayoutChangeListenerAdded(@NonNull Activity activity);
/**
* Notifies extension that a listener for display feature layout changes was removed for the
- * given activity context.
- *
- * @param context an instance of {@link android.app.Activity}
+ * given {@link Activity} context.
*/
- void onWindowLayoutChangeListenerRemoved(@NonNull Context context);
+ void onWindowLayoutChangeListenerRemoved(@NonNull Activity activity);
/**
* Notifies the extension that a device state change listener was updated.
@@ -75,7 +71,7 @@
* Called by extension when the feature layout inside the window changes. Initial value
* should be provided as soon as possible.
*/
- void onWindowLayoutChanged(@NonNull Context context,
+ void onWindowLayoutChanged(@NonNull Activity activity,
@NonNull ExtensionWindowLayoutInfo newLayout);
}
}
diff --git a/window/window-extensions/src/main/java/androidx/window/extensions/StubExtension.java b/window/window-extensions/src/main/java/androidx/window/extensions/StubExtension.java
index 3c4532a..17751fd 100644
--- a/window/window-extensions/src/main/java/androidx/window/extensions/StubExtension.java
+++ b/window/window-extensions/src/main/java/androidx/window/extensions/StubExtension.java
@@ -16,7 +16,7 @@
package androidx.window.extensions;
-import android.content.Context;
+import android.app.Activity;
import androidx.annotation.NonNull;
@@ -29,7 +29,7 @@
*/
abstract class StubExtension implements ExtensionInterface {
private ExtensionCallback mExtensionCallback;
- private final Set<Context> mWindowLayoutChangeListenerContexts = new HashSet<>();
+ private final Set<Activity> mWindowLayoutChangeListenerContexts = new HashSet<>();
private boolean mDeviceStateChangeListenerRegistered;
@Override
@@ -38,14 +38,14 @@
}
@Override
- public void onWindowLayoutChangeListenerAdded(@NonNull Context context) {
- mWindowLayoutChangeListenerContexts.add(context);
+ public void onWindowLayoutChangeListenerAdded(@NonNull Activity activity) {
+ mWindowLayoutChangeListenerContexts.add(activity);
onListenersChanged();
}
@Override
- public void onWindowLayoutChangeListenerRemoved(@NonNull Context context) {
- mWindowLayoutChangeListenerContexts.remove(context);
+ public void onWindowLayoutChangeListenerRemoved(@NonNull Activity activity) {
+ mWindowLayoutChangeListenerContexts.remove(activity);
onListenersChanged();
}
@@ -61,15 +61,15 @@
}
}
- protected void updateWindowLayout(@NonNull Context context,
+ protected void updateWindowLayout(@NonNull Activity activity,
@NonNull ExtensionWindowLayoutInfo newLayout) {
if (mExtensionCallback != null) {
- mExtensionCallback.onWindowLayoutChanged(context, newLayout);
+ mExtensionCallback.onWindowLayoutChanged(activity, newLayout);
}
}
@NonNull
- protected Set<Context> getWindowsListeningForLayoutChanges() {
+ protected Set<Activity> getWindowsListeningForLayoutChanges() {
return mWindowLayoutChangeListenerContexts;
}
diff --git a/window/window-samples/src/main/java/androidx/window/sample/backend/ContextExtensions.kt b/window/window-samples/src/main/java/androidx/window/sample/backend/ContextExtensions.kt
deleted file mode 100644
index fffe251..0000000
--- a/window/window-samples/src/main/java/androidx/window/sample/backend/ContextExtensions.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2020 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.window.sample.backend
-
-import android.app.Activity
-import android.content.Context
-import android.content.ContextWrapper
-
-/**
- * Unwraps the hierarchy of [ContextWrapper]-s until [Activity] is reached.
- * @return Base [Activity] context or {@code null} if not available.
- */
-internal fun Context.getActivityExt(): Activity? {
- var context = this
- while (context is ContextWrapper) {
- if (context is Activity) {
- return context
- }
- context = context.baseContext
- }
- return null
-}
\ No newline at end of file
diff --git a/window/window-samples/src/main/java/androidx/window/sample/backend/MidScreenFoldBackend.kt b/window/window-samples/src/main/java/androidx/window/sample/backend/MidScreenFoldBackend.kt
index 512c76b..5ee6df2 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/backend/MidScreenFoldBackend.kt
+++ b/window/window-samples/src/main/java/androidx/window/sample/backend/MidScreenFoldBackend.kt
@@ -16,7 +16,7 @@
package androidx.window.sample.backend
-import android.content.Context
+import android.app.Activity
import android.graphics.Point
import android.graphics.Rect
import androidx.core.util.Consumer
@@ -57,11 +57,7 @@
return DeviceState.Builder().setPosture(DeviceState.POSTURE_OPENED).build()
}
- private fun getWindowLayoutInfo(context: Context): WindowLayoutInfo {
- val activity = context.getActivityExt() ?: throw IllegalArgumentException(
- "Used non-visual Context used with WindowManager. Please use an Activity or a " +
- "ContextWrapper around an Activity instead."
- )
+ private fun getWindowLayoutInfo(activity: Activity): WindowLayoutInfo {
val windowSize = activity.calculateWindowSizeExt()
val featureRect = foldRect(windowSize)
@@ -108,11 +104,11 @@
}
override fun registerLayoutChangeCallback(
- context: Context,
+ activity: Activity,
executor: Executor,
callback: Consumer<WindowLayoutInfo>
) {
- executor.execute { callback.accept(getWindowLayoutInfo(context)) }
+ executor.execute { callback.accept(getWindowLayoutInfo(activity)) }
}
override fun unregisterLayoutChangeCallback(callback: Consumer<WindowLayoutInfo>) {
diff --git a/window/window/api/current.txt b/window/window/api/current.txt
index 2741333..3abcf8b 100644
--- a/window/window/api/current.txt
+++ b/window/window/api/current.txt
@@ -32,7 +32,7 @@
public interface WindowBackend {
method public void registerDeviceStateChangeCallback(java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.window.DeviceState!>);
- method public void registerLayoutChangeCallback(android.content.Context, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.window.WindowLayoutInfo!>);
+ method public void registerLayoutChangeCallback(android.app.Activity, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.window.WindowLayoutInfo!>);
method public void unregisterDeviceStateChangeCallback(androidx.core.util.Consumer<androidx.window.DeviceState!>);
method public void unregisterLayoutChangeCallback(androidx.core.util.Consumer<androidx.window.WindowLayoutInfo!>);
}
diff --git a/window/window/api/public_plus_experimental_current.txt b/window/window/api/public_plus_experimental_current.txt
index 2741333..3abcf8b 100644
--- a/window/window/api/public_plus_experimental_current.txt
+++ b/window/window/api/public_plus_experimental_current.txt
@@ -32,7 +32,7 @@
public interface WindowBackend {
method public void registerDeviceStateChangeCallback(java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.window.DeviceState!>);
- method public void registerLayoutChangeCallback(android.content.Context, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.window.WindowLayoutInfo!>);
+ method public void registerLayoutChangeCallback(android.app.Activity, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.window.WindowLayoutInfo!>);
method public void unregisterDeviceStateChangeCallback(androidx.core.util.Consumer<androidx.window.DeviceState!>);
method public void unregisterLayoutChangeCallback(androidx.core.util.Consumer<androidx.window.WindowLayoutInfo!>);
}
diff --git a/window/window/api/restricted_current.txt b/window/window/api/restricted_current.txt
index 2741333..3abcf8b 100644
--- a/window/window/api/restricted_current.txt
+++ b/window/window/api/restricted_current.txt
@@ -32,7 +32,7 @@
public interface WindowBackend {
method public void registerDeviceStateChangeCallback(java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.window.DeviceState!>);
- method public void registerLayoutChangeCallback(android.content.Context, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.window.WindowLayoutInfo!>);
+ method public void registerLayoutChangeCallback(android.app.Activity, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.window.WindowLayoutInfo!>);
method public void unregisterDeviceStateChangeCallback(androidx.core.util.Consumer<androidx.window.DeviceState!>);
method public void unregisterLayoutChangeCallback(androidx.core.util.Consumer<androidx.window.WindowLayoutInfo!>);
}
diff --git a/window/window/src/androidTest/java/androidx/window/ExtensionAdapterTest.java b/window/window/src/androidTest/java/androidx/window/ExtensionAdapterTest.java
index f6e7f1f..c6e6348 100644
--- a/window/window/src/androidTest/java/androidx/window/ExtensionAdapterTest.java
+++ b/window/window/src/androidTest/java/androidx/window/ExtensionAdapterTest.java
@@ -62,7 +62,7 @@
@Test
@Override
public void testTranslate_validFeature() {
- Activity mockContext = mock(Activity.class);
+ Activity mockActivity = mock(Activity.class);
Rect bounds = new Rect(WINDOW_BOUNDS.left, 0, WINDOW_BOUNDS.right, 0);
ExtensionDisplayFeature foldFeature = new ExtensionDisplayFeature(bounds,
ExtensionDisplayFeature.TYPE_FOLD);
@@ -78,7 +78,7 @@
ExtensionAdapter adapter = new ExtensionAdapter();
- WindowLayoutInfo actual = adapter.translate(mockContext, windowLayoutInfo);
+ WindowLayoutInfo actual = adapter.translate(mockActivity, windowLayoutInfo);
assertEquals(expected, actual);
}
@@ -93,9 +93,9 @@
ExtensionAdapter adapter = new ExtensionAdapter();
ExtensionWindowLayoutInfo windowLayoutInfo =
new ExtensionWindowLayoutInfo(extensionDisplayFeatures);
- Activity mockContext = mock(Activity.class);
+ Activity mockActivity = mock(Activity.class);
- WindowLayoutInfo actual = adapter.translate(mockContext, windowLayoutInfo);
+ WindowLayoutInfo actual = adapter.translate(mockActivity, windowLayoutInfo);
assertTrue("Remove empty bounds feature", actual.getDisplayFeatures().isEmpty());
}
@@ -115,9 +115,10 @@
ExtensionAdapter extensionCallbackAdapter = new ExtensionAdapter();
ExtensionWindowLayoutInfo windowLayoutInfo =
new ExtensionWindowLayoutInfo(extensionDisplayFeatures);
- Activity mockContext = mock(Activity.class);
+ Activity mockActivity = mock(Activity.class);
- WindowLayoutInfo actual = extensionCallbackAdapter.translate(mockContext, windowLayoutInfo);
+ WindowLayoutInfo actual = extensionCallbackAdapter.translate(mockActivity,
+ windowLayoutInfo);
assertTrue("Remove non empty area fold feature", actual.getDisplayFeatures().isEmpty());
}
@@ -141,9 +142,10 @@
ExtensionWindowLayoutInfo windowLayoutInfo =
new ExtensionWindowLayoutInfo(extensionDisplayFeatures);
- Activity mockContext = mock(Activity.class);
+ Activity mockActivity = mock(Activity.class);
- WindowLayoutInfo actual = extensionCallbackAdapter.translate(mockContext, windowLayoutInfo);
+ WindowLayoutInfo actual = extensionCallbackAdapter.translate(mockActivity,
+ windowLayoutInfo);
assertTrue("Remove hinge feature not spanning full dimension",
actual.getDisplayFeatures().isEmpty());
@@ -166,9 +168,9 @@
ExtensionWindowLayoutInfo windowLayoutInfo =
new ExtensionWindowLayoutInfo(extensionDisplayFeatures);
- Activity mockContext = mock(Activity.class);
+ Activity mockActivity = mock(Activity.class);
- WindowLayoutInfo actual = adapter.translate(mockContext, windowLayoutInfo);
+ WindowLayoutInfo actual = adapter.translate(mockActivity, windowLayoutInfo);
assertTrue("Remove fold feature not spanning full dimension",
actual.getDisplayFeatures().isEmpty());
diff --git a/window/window/src/androidTest/java/androidx/window/ExtensionCompatTest.java b/window/window/src/androidTest/java/androidx/window/ExtensionCompatTest.java
index c1aaf670..f2d1f92 100644
--- a/window/window/src/androidTest/java/androidx/window/ExtensionCompatTest.java
+++ b/window/window/src/androidTest/java/androidx/window/ExtensionCompatTest.java
@@ -30,7 +30,6 @@
import static org.mockito.Mockito.verify;
import android.app.Activity;
-import android.content.Context;
import android.graphics.Rect;
import androidx.annotation.NonNull;
@@ -167,7 +166,7 @@
ExtensionCompat compat = new ExtensionCompat(fakeExtensionImp, new ExtensionAdapter());
ExtensionCallbackInterface mockCallback = mock(ExtensionCallbackInterface.class);
compat.setExtensionCallback(mockCallback);
- compat.onWindowLayoutChangeListenerAdded(mock(Context.class));
+ compat.onWindowLayoutChangeListenerAdded(mock(Activity.class));
fakeExtensionImp.triggerMalformedSignal();
@@ -205,7 +204,7 @@
private static final class FakeExtensionImp implements ExtensionInterface {
private ExtensionCallback mCallback;
- private final List<Context> mContexts = new ArrayList<>();
+ private final List<Activity> mActivities = new ArrayList<>();
FakeExtensionImp() {
mCallback = new ExtensionCallback() {
@@ -215,7 +214,7 @@
}
@Override
- public void onWindowLayoutChanged(@NonNull Context context,
+ public void onWindowLayoutChanged(@NonNull Activity activity,
@NonNull ExtensionWindowLayoutInfo newLayout) {
}
@@ -228,13 +227,13 @@
}
@Override
- public void onWindowLayoutChangeListenerAdded(@NonNull Context context) {
- mContexts.add(context);
+ public void onWindowLayoutChangeListenerAdded(@NonNull Activity activity) {
+ mActivities.add(activity);
}
@Override
- public void onWindowLayoutChangeListenerRemoved(@NonNull Context context) {
- mContexts.remove(context);
+ public void onWindowLayoutChangeListenerRemoved(@NonNull Activity activity) {
+ mActivities.remove(activity);
}
@Override
@@ -251,8 +250,8 @@
}
void triggerSignal(ExtensionWindowLayoutInfo info) {
- for (Context context: mContexts) {
- mCallback.onWindowLayoutChanged(context, info);
+ for (Activity activity: mActivities) {
+ mCallback.onWindowLayoutChanged(activity, info);
}
}
diff --git a/window/window/src/androidTest/java/androidx/window/ExtensionTranslatingCallbackTest.java b/window/window/src/androidTest/java/androidx/window/ExtensionTranslatingCallbackTest.java
index aeb2a82..09a9305 100644
--- a/window/window/src/androidTest/java/androidx/window/ExtensionTranslatingCallbackTest.java
+++ b/window/window/src/androidTest/java/androidx/window/ExtensionTranslatingCallbackTest.java
@@ -60,7 +60,7 @@
@Test
public void testOnWindowLayoutChange_validFeature() {
- Activity mockContext = mock(Activity.class);
+ Activity mockActivity = mock(Activity.class);
Rect bounds = new Rect(WINDOW_BOUNDS.left, 0, WINDOW_BOUNDS.right, 0);
ExtensionDisplayFeature foldFeature = new ExtensionDisplayFeature(bounds,
ExtensionDisplayFeature.TYPE_FOLD);
@@ -78,10 +78,10 @@
ExtensionTranslatingCallback extensionTranslatingCallback =
new ExtensionTranslatingCallback(mockCallback, new ExtensionAdapter());
- extensionTranslatingCallback.onWindowLayoutChanged(mockContext, windowLayoutInfo);
+ extensionTranslatingCallback.onWindowLayoutChanged(mockActivity, windowLayoutInfo);
ArgumentCaptor<WindowLayoutInfo> captor = ArgumentCaptor.forClass(WindowLayoutInfo.class);
- verify(mockCallback).onWindowLayoutChanged(eq(mockContext), captor.capture());
+ verify(mockCallback).onWindowLayoutChanged(eq(mockActivity), captor.capture());
assertEquals(expected, captor.getValue());
}
@@ -96,11 +96,11 @@
new ExtensionTranslatingCallback(mockCallback, new ExtensionAdapter());
ExtensionWindowLayoutInfo windowLayoutInfo =
new ExtensionWindowLayoutInfo(extensionDisplayFeatures);
- Activity mockContext = mock(Activity.class);
+ Activity mockActivity = mock(Activity.class);
- extensionTranslatingCallback.onWindowLayoutChanged(mockContext, windowLayoutInfo);
+ extensionTranslatingCallback.onWindowLayoutChanged(mockActivity, windowLayoutInfo);
- verify(mockCallback).onWindowLayoutChanged(eq(mockContext),
+ verify(mockCallback).onWindowLayoutChanged(eq(mockActivity),
argThat((layoutInfo) -> layoutInfo.getDisplayFeatures().isEmpty()));
}
@@ -120,11 +120,11 @@
new ExtensionTranslatingCallback(mockCallback, new ExtensionAdapter());
ExtensionWindowLayoutInfo windowLayoutInfo =
new ExtensionWindowLayoutInfo(extensionDisplayFeatures);
- Activity mockContext = mock(Activity.class);
+ Activity mockActivity = mock(Activity.class);
- extensionTranslatingCallback.onWindowLayoutChanged(mockContext, windowLayoutInfo);
+ extensionTranslatingCallback.onWindowLayoutChanged(mockActivity, windowLayoutInfo);
- verify(mockCallback).onWindowLayoutChanged(eq(mockContext),
+ verify(mockCallback).onWindowLayoutChanged(eq(mockActivity),
argThat((layoutInfo) -> layoutInfo.getDisplayFeatures().isEmpty()));
}
@@ -146,11 +146,11 @@
ExtensionWindowLayoutInfo windowLayoutInfo =
new ExtensionWindowLayoutInfo(extensionDisplayFeatures);
- Activity mockContext = mock(Activity.class);
+ Activity mockActivity = mock(Activity.class);
- extensionTranslatingCallback.onWindowLayoutChanged(mockContext, windowLayoutInfo);
+ extensionTranslatingCallback.onWindowLayoutChanged(mockActivity, windowLayoutInfo);
- verify(mockCallback).onWindowLayoutChanged(eq(mockContext),
+ verify(mockCallback).onWindowLayoutChanged(eq(mockActivity),
argThat((layoutInfo) -> layoutInfo.getDisplayFeatures().isEmpty()));
}
@@ -172,11 +172,11 @@
ExtensionWindowLayoutInfo windowLayoutInfo =
new ExtensionWindowLayoutInfo(extensionDisplayFeatures);
- Activity mockContext = mock(Activity.class);
+ Activity mockActivity = mock(Activity.class);
- extensionTranslatingCallback.onWindowLayoutChanged(mockContext, windowLayoutInfo);
+ extensionTranslatingCallback.onWindowLayoutChanged(mockActivity, windowLayoutInfo);
- verify(mockCallback).onWindowLayoutChanged(eq(mockContext),
+ verify(mockCallback).onWindowLayoutChanged(eq(mockActivity),
argThat((layoutInfo) -> layoutInfo.getDisplayFeatures().isEmpty()));
}
diff --git a/window/window/src/androidTest/java/androidx/window/ExtensionWindowBackendTest.java b/window/window/src/androidTest/java/androidx/window/ExtensionWindowBackendTest.java
index c7175a5..2361ed1 100644
--- a/window/window/src/androidTest/java/androidx/window/ExtensionWindowBackendTest.java
+++ b/window/window/src/androidTest/java/androidx/window/ExtensionWindowBackendTest.java
@@ -50,7 +50,6 @@
import java.util.ArrayList;
import java.util.List;
-import java.util.concurrent.Executor;
/** Tests for {@link ExtensionWindowBackend} class. */
@LargeTest
@@ -204,15 +203,6 @@
verify(consumer).accept(expectedWindowLayoutInfo);
}
- @Test(expected = IllegalArgumentException.class)
- public void testRegisterLayoutChangeCallback_applicationContext() {
- ExtensionWindowBackend backend = ExtensionWindowBackend.getInstance(mContext);
- backend.mWindowExtension = mock(ExtensionInterfaceCompat.class);
-
- backend.registerLayoutChangeCallback(mContext, mock(Executor.class),
- mock(Consumer.class));
- }
-
@Test
public void testLayoutChangeCallback_emitNewValue() {
ExtensionWindowBackend backend = ExtensionWindowBackend.getInstance(mContext);
diff --git a/window/window/src/androidTest/java/androidx/window/SidecarAdapterTest.java b/window/window/src/androidTest/java/androidx/window/SidecarAdapterTest.java
index 3d19204..65e2903 100644
--- a/window/window/src/androidTest/java/androidx/window/SidecarAdapterTest.java
+++ b/window/window/src/androidTest/java/androidx/window/SidecarAdapterTest.java
@@ -76,7 +76,7 @@
@Test
@Override
public void testTranslate_validFeature() {
- Activity mockContext = mock(Activity.class);
+ Activity mockActivity = mock(Activity.class);
Rect bounds = new Rect(WINDOW_BOUNDS.left, 0, WINDOW_BOUNDS.right, 0);
SidecarDisplayFeature foldFeature = sidecarDisplayFeature(bounds,
SidecarDisplayFeature.TYPE_FOLD);
@@ -91,7 +91,7 @@
SidecarAdapter sidecarAdapter = new SidecarAdapter();
- WindowLayoutInfo actual = sidecarAdapter.translate(mockContext, windowLayoutInfo);
+ WindowLayoutInfo actual = sidecarAdapter.translate(mockActivity, windowLayoutInfo);
assertEquals(expected, actual);
}
@@ -105,9 +105,9 @@
SidecarAdapter sidecarAdapter = new SidecarAdapter();
SidecarWindowLayoutInfo windowLayoutInfo = sidecarWindowLayoutInfo(sidecarDisplayFeatures);
- Activity mockContext = mock(Activity.class);
+ Activity mockActivity = mock(Activity.class);
- WindowLayoutInfo actual = sidecarAdapter.translate(mockContext, windowLayoutInfo);
+ WindowLayoutInfo actual = sidecarAdapter.translate(mockActivity, windowLayoutInfo);
assertTrue(actual.getDisplayFeatures().isEmpty());
}
@@ -128,9 +128,9 @@
ExtensionInterfaceCompat.ExtensionCallbackInterface.class);
SidecarAdapter sidecarCallbackAdapter = new SidecarAdapter();
SidecarWindowLayoutInfo windowLayoutInfo = sidecarWindowLayoutInfo(sidecarDisplayFeatures);
- Activity mockContext = mock(Activity.class);
+ Activity mockActivity = mock(Activity.class);
- WindowLayoutInfo actual = sidecarCallbackAdapter.translate(mockContext, windowLayoutInfo);
+ WindowLayoutInfo actual = sidecarCallbackAdapter.translate(mockActivity, windowLayoutInfo);
assertTrue(actual.getDisplayFeatures().isEmpty());
}
@@ -152,9 +152,9 @@
SidecarAdapter sidecarAdapter = new SidecarAdapter();
SidecarWindowLayoutInfo windowLayoutInfo = sidecarWindowLayoutInfo(sidecarDisplayFeatures);
- Activity mockContext = mock(Activity.class);
+ Activity mockActivity = mock(Activity.class);
- WindowLayoutInfo actual = sidecarAdapter.translate(mockContext, windowLayoutInfo);
+ WindowLayoutInfo actual = sidecarAdapter.translate(mockActivity, windowLayoutInfo);
assertTrue(actual.getDisplayFeatures().isEmpty());
}
@@ -176,9 +176,9 @@
SidecarWindowLayoutInfo windowLayoutInfo = sidecarWindowLayoutInfo(
extensionDisplayFeatures);
- Activity mockContext = mock(Activity.class);
+ Activity mockActivity = mock(Activity.class);
- WindowLayoutInfo actual = sidecarCallbackAdapter.translate(mockContext, windowLayoutInfo);
+ WindowLayoutInfo actual = sidecarCallbackAdapter.translate(mockActivity, windowLayoutInfo);
assertTrue(actual.getDisplayFeatures().isEmpty());
}
diff --git a/window/window/src/androidTest/java/androidx/window/WindowBackendTest.java b/window/window/src/androidTest/java/androidx/window/WindowBackendTest.java
index 1d9c2e8..6378d7a 100644
--- a/window/window/src/androidTest/java/androidx/window/WindowBackendTest.java
+++ b/window/window/src/androidTest/java/androidx/window/WindowBackendTest.java
@@ -19,7 +19,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
-import android.content.Context;
+import android.app.Activity;
import android.content.Intent;
import android.graphics.Rect;
@@ -86,7 +86,7 @@
}
@Override
- public void registerLayoutChangeCallback(@NonNull Context context,
+ public void registerLayoutChangeCallback(@NonNull Activity activity,
@NonNull Executor executor, @NonNull Consumer<WindowLayoutInfo> callback) {
executor.execute(() -> callback.accept(mWindowLayoutInfo));
}
diff --git a/window/window/src/main/java/androidx/window/ExtensionAdapter.java b/window/window/src/main/java/androidx/window/ExtensionAdapter.java
index cf032f5..c2e5549 100644
--- a/window/window/src/main/java/androidx/window/ExtensionAdapter.java
+++ b/window/window/src/main/java/androidx/window/ExtensionAdapter.java
@@ -17,7 +17,6 @@
package androidx.window;
import android.app.Activity;
-import android.content.Context;
import android.graphics.Rect;
import androidx.annotation.NonNull;
@@ -62,17 +61,16 @@
* Translates a valid {@link ExtensionDisplayFeature} into a valid {@link DisplayFeature}. If
* a feature is not valid it is removed
*
- * @param context An {@link android.app.Activity} or a {@link android.content.ContextWrapper}
- * around an {@link android.app.Activity}.
+ * @param activity An {@link android.app.Activity}.
* @param layoutInfo The source {@link ExtensionWindowLayoutInfo} to be converted
* @return {@link WindowLayoutInfo} containing the valid {@link DisplayFeature}
*/
@NonNull
- WindowLayoutInfo translate(@NonNull Context context,
+ WindowLayoutInfo translate(@NonNull Activity activity,
@NonNull ExtensionWindowLayoutInfo layoutInfo) {
List<DisplayFeature> featureList = new ArrayList<>();
for (ExtensionDisplayFeature sourceFeature : layoutInfo.getDisplayFeatures()) {
- DisplayFeature targetFeature = translate(context, sourceFeature);
+ DisplayFeature targetFeature = translate(activity, sourceFeature);
if (targetFeature != null) {
featureList.add(targetFeature);
}
@@ -81,8 +79,9 @@
}
@Nullable
- DisplayFeature translate(Context context, ExtensionDisplayFeature feature) {
- final Rect windowBounds = windowBounds(context);
+ DisplayFeature translate(Activity activity, ExtensionDisplayFeature feature) {
+ final Rect windowBounds = WindowBoundsHelper.getInstance()
+ .computeCurrentWindowBounds(activity);
if (!isValid(feature, windowBounds)) {
return null;
}
@@ -117,9 +116,4 @@
boolean matchesHeight = lhs.top == rhs.top && lhs.bottom == rhs.bottom;
return matchesWidth || matchesHeight;
}
-
- private Rect windowBounds(Context context) {
- Activity activity = WindowManager.assertActivityFromContext(context);
- return WindowBoundsHelper.getInstance().computeCurrentWindowBounds(activity);
- }
}
diff --git a/window/window/src/main/java/androidx/window/ExtensionCompat.java b/window/window/src/main/java/androidx/window/ExtensionCompat.java
index 083bca5..8a09172 100644
--- a/window/window/src/main/java/androidx/window/ExtensionCompat.java
+++ b/window/window/src/main/java/androidx/window/ExtensionCompat.java
@@ -16,6 +16,7 @@
package androidx.window;
+import android.app.Activity;
import android.content.Context;
import android.graphics.Rect;
import android.text.TextUtils;
@@ -65,13 +66,13 @@
}
@Override
- public void onWindowLayoutChangeListenerAdded(@NonNull Context context) {
- mWindowExtension.onWindowLayoutChangeListenerAdded(context);
+ public void onWindowLayoutChangeListenerAdded(@NonNull Activity activity) {
+ mWindowExtension.onWindowLayoutChangeListenerAdded(activity);
}
@Override
- public void onWindowLayoutChangeListenerRemoved(@NonNull Context context) {
- mWindowExtension.onWindowLayoutChangeListenerRemoved(context);
+ public void onWindowLayoutChangeListenerRemoved(@NonNull Activity activity) {
+ mWindowExtension.onWindowLayoutChangeListenerRemoved(activity);
}
@Override
@@ -114,9 +115,9 @@
// extension.onDeviceStateListenersChanged(boolean);
mWindowExtension.onDeviceStateListenersChanged(true /* empty */);
- // extension.onWindowLayoutChangeListenerAdded(Context);
+ // extension.onWindowLayoutChangeListenerAdded(Activity);
Method methodRegisterWindowLayoutChangeListener = mWindowExtension.getClass()
- .getMethod("onWindowLayoutChangeListenerAdded", Context.class);
+ .getMethod("onWindowLayoutChangeListenerAdded", Activity.class);
Class<?> rtRegisterWindowLayoutChangeListener =
methodRegisterWindowLayoutChangeListener.getReturnType();
if (!rtRegisterWindowLayoutChangeListener.equals(void.class)) {
@@ -125,9 +126,9 @@
+ rtRegisterWindowLayoutChangeListener);
}
- // extension.onWindowLayoutChangeListenerRemoved(Context);
+ // extension.onWindowLayoutChangeListenerRemoved(Activity);
Method methodUnregisterWindowLayoutChangeListener = mWindowExtension.getClass()
- .getMethod("onWindowLayoutChangeListenerRemoved", Context.class);
+ .getMethod("onWindowLayoutChangeListenerRemoved", Activity.class);
Class<?> rtUnregisterWindowLayoutChangeListener =
methodUnregisterWindowLayoutChangeListener.getReturnType();
if (!rtUnregisterWindowLayoutChangeListener.equals(void.class)) {
diff --git a/window/window/src/main/java/androidx/window/ExtensionInterfaceCompat.java b/window/window/src/main/java/androidx/window/ExtensionInterfaceCompat.java
index 07be11e..fe4d703 100644
--- a/window/window/src/main/java/androidx/window/ExtensionInterfaceCompat.java
+++ b/window/window/src/main/java/androidx/window/ExtensionInterfaceCompat.java
@@ -16,7 +16,7 @@
package androidx.window;
-import android.content.Context;
+import android.app.Activity;
import androidx.annotation.NonNull;
@@ -39,16 +39,16 @@
/**
* Notifies extension that a listener for display feature layout changes was registered for the
- * given activity or window context. Should notify the {@link ExtensionCallbackInterface} of
+ * given activity context. Should notify the {@link ExtensionCallbackInterface} of
* the initial {@link WindowLayoutInfo} when it is available.
*/
- void onWindowLayoutChangeListenerAdded(@NonNull Context context);
+ void onWindowLayoutChangeListenerAdded(@NonNull Activity activity);
/**
* Notifies extension that a listener for display feature layout changes was removed for the
- * given activity or window context.
+ * given activity context.
*/
- void onWindowLayoutChangeListenerRemoved(@NonNull Context context);
+ void onWindowLayoutChangeListenerRemoved(@NonNull Activity activity);
/**
* Notifies the extension that a device state change listener was updated. Should notify the
@@ -70,7 +70,7 @@
/**
* Called by extension when the feature layout inside the window changes.
*/
- void onWindowLayoutChanged(@NonNull Context context,
+ void onWindowLayoutChanged(@NonNull Activity activity,
@NonNull WindowLayoutInfo newLayout);
}
}
diff --git a/window/window/src/main/java/androidx/window/ExtensionTranslatingCallback.java b/window/window/src/main/java/androidx/window/ExtensionTranslatingCallback.java
index 7a58574..1e1570c 100644
--- a/window/window/src/main/java/androidx/window/ExtensionTranslatingCallback.java
+++ b/window/window/src/main/java/androidx/window/ExtensionTranslatingCallback.java
@@ -19,7 +19,7 @@
import static androidx.window.ExtensionInterfaceCompat.ExtensionCallbackInterface;
import static androidx.window.extensions.ExtensionInterface.ExtensionCallback;
-import android.content.Context;
+import android.app.Activity;
import androidx.annotation.NonNull;
import androidx.window.extensions.ExtensionDeviceState;
@@ -45,8 +45,8 @@
}
@Override
- public void onWindowLayoutChanged(@NonNull Context context,
+ public void onWindowLayoutChanged(@NonNull Activity activity,
@NonNull ExtensionWindowLayoutInfo newLayout) {
- mCallback.onWindowLayoutChanged(context, mAdapter.translate(context, newLayout));
+ mCallback.onWindowLayoutChanged(activity, mAdapter.translate(activity, newLayout));
}
}
diff --git a/window/window/src/main/java/androidx/window/ExtensionWindowBackend.java b/window/window/src/main/java/androidx/window/ExtensionWindowBackend.java
index 1f35772..44e0c47 100644
--- a/window/window/src/main/java/androidx/window/ExtensionWindowBackend.java
+++ b/window/window/src/main/java/androidx/window/ExtensionWindowBackend.java
@@ -17,7 +17,6 @@
package androidx.window;
import static androidx.window.ExtensionCompat.DEBUG;
-import static androidx.window.WindowManager.getActivityFromContext;
import android.annotation.SuppressLint;
import android.app.Activity;
@@ -74,7 +73,7 @@
/** Window layouts that were last reported through callbacks, used to filter out duplicates. */
@GuardedBy("sLock")
@VisibleForTesting
- final Map<Context, WindowLayoutInfo> mLastReportedWindowLayouts = new WeakHashMap<>();
+ final Map<Activity, WindowLayoutInfo> mLastReportedWindowLayouts = new WeakHashMap<>();
private static final String TAG = "WindowServer";
@@ -110,7 +109,7 @@
}
@Override
- public void registerLayoutChangeCallback(@NonNull Context context,
+ public void registerLayoutChangeCallback(@NonNull Activity activity,
@NonNull Executor executor, @NonNull Consumer<WindowLayoutInfo> callback) {
synchronized (sLock) {
if (mWindowExtension == null) {
@@ -119,28 +118,27 @@
}
return;
}
- assertActivityContext(context);
- // Check if the context was already registered, in case we need to report tracking of a
- // new context to the extension.
- boolean isContextRegistered = isContextRegistered(context);
+ // Check if the activity was already registered, in case we need to report tracking of a
+ // new activity to the extension.
+ boolean isActivityRegistered = isActivityRegistered(activity);
WindowLayoutChangeCallbackWrapper callbackWrapper =
- new WindowLayoutChangeCallbackWrapper(context, executor, callback);
+ new WindowLayoutChangeCallbackWrapper(activity, executor, callback);
mWindowLayoutChangeCallbacks.add(callbackWrapper);
- if (!isContextRegistered) {
- mWindowExtension.onWindowLayoutChangeListenerAdded(context);
+ if (!isActivityRegistered) {
+ mWindowExtension.onWindowLayoutChangeListenerAdded(activity);
}
- WindowLayoutInfo lastReportedValue = mLastReportedWindowLayouts.get(context);
+ WindowLayoutInfo lastReportedValue = mLastReportedWindowLayouts.get(activity);
if (lastReportedValue != null) {
callbackWrapper.accept(lastReportedValue);
}
}
}
- private boolean isContextRegistered(@NonNull Context context) {
+ private boolean isActivityRegistered(@NonNull Activity activity) {
for (WindowLayoutChangeCallbackWrapper callbackWrapper : mWindowLayoutChangeCallbacks) {
- if (callbackWrapper.mContext.equals(context)) {
+ if (callbackWrapper.mActivity.equals(activity)) {
return true;
}
}
@@ -169,25 +167,25 @@
// Remove the items from the list and notify extension if needed.
mWindowLayoutChangeCallbacks.removeAll(itemsToRemove);
for (WindowLayoutChangeCallbackWrapper callbackWrapper : itemsToRemove) {
- callbackRemovedForContext(callbackWrapper.mContext);
+ callbackRemovedForActivity(callbackWrapper.mActivity);
}
}
}
/**
- * Checks if there are no more registered callbacks left for the context and inform extension if
- * needed.
+ * Checks if there are no more registered callbacks left for the activity and inform
+ * extension if needed.
*/
@GuardedBy("sLock")
- private void callbackRemovedForContext(Context context) {
+ private void callbackRemovedForActivity(Activity activity) {
for (WindowLayoutChangeCallbackWrapper callbackWrapper : mWindowLayoutChangeCallbacks) {
- if (callbackWrapper.mContext.equals(context)) {
+ if (callbackWrapper.mActivity.equals(activity)) {
// Found a registered callback for token.
return;
}
}
- // No registered callbacks left for context - report to extension.
- mWindowExtension.onWindowLayoutChangeListenerRemoved(context);
+ // No registered callbacks left for the activity - report to extension.
+ mWindowExtension.onWindowLayoutChangeListenerRemoved(activity);
}
@Override
@@ -262,10 +260,10 @@
@Override
@SuppressLint("SyntheticAccessor")
- public void onWindowLayoutChanged(@NonNull Context context,
+ public void onWindowLayoutChanged(@NonNull Activity activity,
@NonNull WindowLayoutInfo newLayout) {
synchronized (sLock) {
- WindowLayoutInfo lastReportedValue = mLastReportedWindowLayouts.get(context);
+ WindowLayoutInfo lastReportedValue = mLastReportedWindowLayouts.get(activity);
if (newLayout.equals(lastReportedValue)) {
// Skipping, value already reported
if (DEBUG) {
@@ -273,11 +271,11 @@
}
return;
}
- mLastReportedWindowLayouts.put(context, newLayout);
+ mLastReportedWindowLayouts.put(activity, newLayout);
}
for (WindowLayoutChangeCallbackWrapper callbackWrapper : mWindowLayoutChangeCallbacks) {
- if (!callbackWrapper.mContext.equals(context)) {
+ if (!callbackWrapper.mActivity.equals(activity)) {
continue;
}
@@ -286,27 +284,18 @@
}
}
- private Activity assertActivityContext(Context context) {
- Activity activity = getActivityFromContext(context);
- if (activity == null) {
- throw new IllegalArgumentException("Used non-visual Context with WindowManager. "
- + "Please use an Activity or a ContextWrapper around an Activity instead.");
- }
- return activity;
- }
-
/**
* Wrapper around {@link Consumer<WindowLayoutInfo>} that also includes the {@link Executor}
- * on which the callback should run and the visual context.
+ * on which the callback should run and the {@link Activity}.
*/
private static class WindowLayoutChangeCallbackWrapper {
final Executor mExecutor;
final Consumer<WindowLayoutInfo> mCallback;
- final Context mContext;
+ final Activity mActivity;
- WindowLayoutChangeCallbackWrapper(@NonNull Context context, @NonNull Executor executor,
+ WindowLayoutChangeCallbackWrapper(@NonNull Activity activity, @NonNull Executor executor,
@NonNull Consumer<WindowLayoutInfo> callback) {
- mContext = context;
+ mActivity = activity;
mExecutor = executor;
mCallback = callback;
}
@@ -345,28 +334,44 @@
try {
if (isExtensionVersionSupported(ExtensionCompat.getExtensionVersion())) {
impl = new ExtensionCompat(context);
- } else if (isExtensionVersionSupported(SidecarCompat.getSidecarVersion())) {
- impl = new SidecarCompat(context);
+ if (!impl.validateExtensionInterface()) {
+ if (DEBUG) {
+ Log.d(TAG, "Loaded extension doesn't match the interface version");
+ }
+ impl = null;
+ }
}
} catch (Throwable t) {
if (DEBUG) {
Log.d(TAG, "Failed to load extension: " + t);
}
- return null;
+ impl = null;
+ }
+
+ if (impl == null) {
+ // Falling back to Sidecar
+ try {
+ if (isExtensionVersionSupported(SidecarCompat.getSidecarVersion())) {
+ impl = new SidecarCompat(context);
+ if (!impl.validateExtensionInterface()) {
+ if (DEBUG) {
+ Log.d(TAG, "Loaded Sidecar doesn't match the interface version");
+ }
+ impl = null;
+ }
+ }
+ } catch (Throwable t) {
+ if (DEBUG) {
+ Log.d(TAG, "Failed to load sidecar: " + t);
+ }
+ impl = null;
+ }
}
if (impl == null) {
if (DEBUG) {
- Log.d(TAG, "No supported extension found");
+ Log.d(TAG, "No supported extension or sidecar found");
}
- return null;
- }
-
- if (!impl.validateExtensionInterface()) {
- if (DEBUG) {
- Log.d(TAG, "Loaded extension doesn't match the interface version");
- }
- return null;
}
return impl;
@@ -378,8 +383,15 @@
*/
@VisibleForTesting
static boolean isExtensionVersionSupported(@Nullable Version extensionVersion) {
- return extensionVersion != null
- && Version.CURRENT.getMajor() >= extensionVersion.getMajor();
+ if (extensionVersion == null) {
+ return false;
+ }
+ if (extensionVersion.getMajor() == 1) {
+ // Disable androidx.window.extensions support in release builds of the library until the
+ // extensions API is finalized.
+ return DEBUG;
+ }
+ return Version.CURRENT.getMajor() >= extensionVersion.getMajor();
}
/**
diff --git a/window/window/src/main/java/androidx/window/SidecarCompat.java b/window/window/src/main/java/androidx/window/SidecarCompat.java
index ec58604..7de2b77 100644
--- a/window/window/src/main/java/androidx/window/SidecarCompat.java
+++ b/window/window/src/main/java/androidx/window/SidecarCompat.java
@@ -18,7 +18,6 @@
import static androidx.window.ExtensionCompat.DEBUG;
import static androidx.window.Version.VERSION_0_1;
-import static androidx.window.WindowManager.getActivityFromContext;
import android.annotation.SuppressLint;
import android.app.Activity;
@@ -50,7 +49,7 @@
// Map of active listeners registered with #onWindowLayoutChangeListenerAdded() and not yet
// removed by #onWindowLayoutChangeListenerRemoved().
- protected final SimpleArrayMap<IBinder, Context> mWindowListenerRegisteredContexts =
+ protected final SimpleArrayMap<IBinder, Activity> mWindowListenerRegisteredContexts =
new SimpleArrayMap<>();
private ExtensionCallbackInterface mExtensionCallback;
@@ -76,7 +75,7 @@
}
@Override
- public void onWindowLayoutChanged(@NonNull Context context,
+ public void onWindowLayoutChanged(@NonNull Activity activity,
@NonNull WindowLayoutInfo newLayout) {
}
@@ -99,20 +98,14 @@
@SuppressLint("SyntheticAccessor")
public void onWindowLayoutChanged(@NonNull IBinder windowToken,
@NonNull SidecarWindowLayoutInfo newLayout) {
- Context context = mWindowListenerRegisteredContexts.get(windowToken);
- if (context == null) {
- Log.w(TAG, "Unable to resolve context from window token. Missing a call"
+ Activity activity = mWindowListenerRegisteredContexts.get(windowToken);
+ if (activity == null) {
+ Log.w(TAG, "Unable to resolve activity from window token. Missing a call"
+ "to #onWindowLayoutChangeListenerAdded()?");
return;
}
- Activity activity = getActivityFromContext(context);
- if (activity == null) {
- throw new IllegalArgumentException(
- "Used non-Activity Context with WindowManager. Please use an Activity "
- + "or a ContextWrapper around an Activity instead.");
- }
- extensionCallback.onWindowLayoutChanged(context,
+ extensionCallback.onWindowLayoutChanged(activity,
mSidecarAdapter.translate(activity, newLayout));
}
});
@@ -128,8 +121,7 @@
}
@Override
- public void onWindowLayoutChangeListenerAdded(@NonNull Context context) {
- Activity activity = assertActivityContext(context);
+ public void onWindowLayoutChangeListenerAdded(@NonNull Activity activity) {
IBinder windowToken = getActivityWindowToken(activity);
if (windowToken != null) {
@@ -150,8 +142,7 @@
}
@Override
- public void onWindowLayoutChangeListenerRemoved(@NonNull Context context) {
- Activity activity = assertActivityContext(context);
+ public void onWindowLayoutChangeListenerRemoved(@NonNull Activity activity) {
IBinder windowToken = getActivityWindowToken(activity);
mSidecar.onWindowLayoutChangeListenerRemoved(windowToken);
@@ -271,15 +262,6 @@
}
}
- private Activity assertActivityContext(Context context) {
- Activity activity = getActivityFromContext(context);
- if (activity == null) {
- throw new IllegalArgumentException("Used non-visual Context with WindowManager. "
- + "Please use an Activity or a ContextWrapper around an Activity instead.");
- }
- return activity;
- }
-
@Nullable
private IBinder getActivityWindowToken(Activity activity) {
return activity.getWindow() != null ? activity.getWindow().getAttributes().token : null;
diff --git a/window/window/src/main/java/androidx/window/WindowBackend.java b/window/window/src/main/java/androidx/window/WindowBackend.java
index 7482dc3..f988c82 100644
--- a/window/window/src/main/java/androidx/window/WindowBackend.java
+++ b/window/window/src/main/java/androidx/window/WindowBackend.java
@@ -16,7 +16,7 @@
package androidx.window;
-import android.content.Context;
+import android.app.Activity;
import androidx.annotation.NonNull;
import androidx.core.util.Consumer;
@@ -30,14 +30,14 @@
public interface WindowBackend {
/**
- * Registers a callback for layout changes of the window of the current visual {@link Context}.
+ * Registers a callback for layout changes of the window for the supplied {@link Activity}.
* Must be called only after the it is attached to the window.
*/
- void registerLayoutChangeCallback(@NonNull Context context, @NonNull Executor executor,
+ void registerLayoutChangeCallback(@NonNull Activity activity, @NonNull Executor executor,
@NonNull Consumer<WindowLayoutInfo> callback);
/**
- * Unregisters a callback for window layout changes of the {@link Context} window.
+ * Unregisters a callback for window layout changes of the {@link Activity} window.
*/
void unregisterLayoutChangeCallback(@NonNull Consumer<WindowLayoutInfo> callback);
diff --git a/window/window/src/main/java/androidx/window/WindowManager.java b/window/window/src/main/java/androidx/window/WindowManager.java
index 76c3633..6d83906 100644
--- a/window/window/src/main/java/androidx/window/WindowManager.java
+++ b/window/window/src/main/java/androidx/window/WindowManager.java
@@ -35,11 +35,12 @@
*/
public final class WindowManager {
/**
- * Visual context that was registered with this instance of {@link WindowManager} at creation.
+ * Activity that was registered with this instance of {@link WindowManager} at creation.
* This is used to find the token identifier of the window when requesting layout information
- * from the {@link ExtensionInterface}.
+ * from the {@link androidx.window.sidecar.SidecarInterface} or is passed directly to the
+ * {@link ExtensionInterface}.
*/
- private Context mContext;
+ private Activity mActivity;
/**
* The backend that supplies the information through this class.
*/
@@ -66,12 +67,13 @@
* Pass a custom {@link WindowBackend} implementation for testing.
*/
public WindowManager(@NonNull Context context, @NonNull WindowBackend windowBackend) {
- if (getActivityFromContext(context) == null) {
+ Activity activity = getActivityFromContext(context);
+ if (activity == null) {
throw new IllegalArgumentException("Used non-visual Context to obtain an instance of "
+ "WindowManager. Please use an Activity or a ContextWrapper around one "
+ "instead.");
}
- mContext = context;
+ mActivity = activity;
mWindowBackend = windowBackend;
}
@@ -82,7 +84,7 @@
*/
public void registerLayoutChangeCallback(@NonNull Executor executor,
@NonNull Consumer<WindowLayoutInfo> callback) {
- mWindowBackend.registerLayoutChangeCallback(mContext, executor, callback);
+ mWindowBackend.registerLayoutChangeCallback(mActivity, executor, callback);
}
/**
@@ -124,8 +126,7 @@
*/
@NonNull
public WindowMetrics getCurrentWindowMetrics() {
- Activity activity = getActivityFromContext(mContext);
- Rect currentBounds = WindowBoundsHelper.getInstance().computeCurrentWindowBounds(activity);
+ Rect currentBounds = WindowBoundsHelper.getInstance().computeCurrentWindowBounds(mActivity);
return new WindowMetrics(currentBounds);
}
@@ -153,8 +154,7 @@
*/
@NonNull
public WindowMetrics getMaximumWindowMetrics() {
- Activity activity = getActivityFromContext(mContext);
- Rect maxBounds = WindowBoundsHelper.getInstance().computeMaximumWindowBounds(activity);
+ Rect maxBounds = WindowBoundsHelper.getInstance().computeMaximumWindowBounds(mActivity);
return new WindowMetrics(maxBounds);
}
@@ -163,7 +163,7 @@
* @return Base {@link Activity} context or {@code null} if not available.
*/
@Nullable
- static Activity getActivityFromContext(Context context) {
+ private static Activity getActivityFromContext(Context context) {
while (context instanceof ContextWrapper) {
if (context instanceof Activity) {
return (Activity) context;
@@ -172,21 +172,4 @@
}
return null;
}
-
- /**
- * Unwraps the hierarchy of {@link ContextWrapper}-s until {@link Activity} is reached.
- * @return Base {@link Activity} context or throws {@link IllegalArgumentException} if not
- * available.
- */
- @NonNull
- static Activity assertActivityFromContext(Context context) throws IllegalArgumentException {
- while (context instanceof ContextWrapper) {
- if (context instanceof Activity) {
- return (Activity) context;
- }
- context = ((ContextWrapper) context).getBaseContext();
- }
- throw new IllegalArgumentException("Used non-visual Context with WindowManager. "
- + "Please use an Activity or a ContextWrapper around an Activity instead.");
- }
}