Merge "[Elevation] Remove tonal elevation animations as this interferes with ripple opacities" into androidx-main
diff --git a/annotation/annotation-experimental-lint/integration-tests/lint-baseline.xml b/annotation/annotation-experimental-lint/integration-tests/lint-baseline.xml
index ac48471..7155089 100644
--- a/annotation/annotation-experimental-lint/integration-tests/lint-baseline.xml
+++ b/annotation/annotation-experimental-lint/integration-tests/lint-baseline.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-alpha05" type="baseline" client="gradle" dependencies="false" name="AGP (8.0.0-alpha05)" variant="all" version="8.0.0-alpha05">
+<issues format="6" by="lint 8.2.0-beta01" type="baseline" client="gradle" dependencies="false" name="AGP (8.2.0-beta01)" variant="all" version="8.2.0-beta01">
<issue
id="ExperimentalAnnotationRetention"
@@ -327,6 +327,15 @@
<issue
id="UnsafeOptInUsageError"
+ message="This declaration is opt-in and its usage should be marked with `@sample.optin.ExperimentalJavaAnnotation` or `@OptIn(markerClass = sample.optin.ExperimentalJavaAnnotation.class)`"
+ errorLine1=" player.accessor"
+ errorLine2=" ~~~~~~~~">
+ <location
+ file="src/main/java/sample/optin/RegressionTestKotlin298322402.kt"/>
+ </issue>
+
+ <issue
+ id="UnsafeOptInUsageError"
message="This declaration is opt-in and its usage should be marked with `@sample.kotlin.ExperimentalJavaAnnotation` or `@OptIn(markerClass = sample.kotlin.ExperimentalJavaAnnotation.class)`"
errorLine1=" AnnotatedJavaClass experimentalObject = new AnnotatedJavaClass();"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
diff --git a/annotation/annotation-experimental-lint/integration-tests/src/main/java/sample/optin/AnnotatedJavaMembers.java b/annotation/annotation-experimental-lint/integration-tests/src/main/java/sample/optin/AnnotatedJavaMembers.java
index 82955e3..88faa0f 100644
--- a/annotation/annotation-experimental-lint/integration-tests/src/main/java/sample/optin/AnnotatedJavaMembers.java
+++ b/annotation/annotation-experimental-lint/integration-tests/src/main/java/sample/optin/AnnotatedJavaMembers.java
@@ -38,6 +38,11 @@
return -1;
}
+ @ExperimentalJavaAnnotation
+ public int getAccessor() {
+ return -1;
+ }
+
public int getFieldWithSetMarker() {
return mFieldWithSetMarker;
}
diff --git a/camera/camera-effects/src/main/java/androidx/camera/effects/Portrait.java b/annotation/annotation-experimental-lint/integration-tests/src/main/java/sample/optin/RegressionTestKotlin298322402.kt
similarity index 66%
rename from camera/camera-effects/src/main/java/androidx/camera/effects/Portrait.java
rename to annotation/annotation-experimental-lint/integration-tests/src/main/java/sample/optin/RegressionTestKotlin298322402.kt
index 7a355f8..5a8eb40 100644
--- a/camera/camera-effects/src/main/java/androidx/camera/effects/Portrait.java
+++ b/annotation/annotation-experimental-lint/integration-tests/src/main/java/sample/optin/RegressionTestKotlin298322402.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,16 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+@file:Suppress("unused")
-package androidx.camera.effects;
+package sample.optin
-import androidx.annotation.RestrictTo;
-
-/**
- * Provides a portrait post-processing effect.
- *
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class Portrait {
- // TODO: implement this
+internal class RegressionTestKotlin298322402 {
+ fun testMethod(player: AnnotatedJavaMembers) {
+ player.accessor
+ }
}
diff --git a/annotation/annotation-experimental-lint/src/main/java/androidx/annotation/experimental/lint/ExperimentalDetector.kt b/annotation/annotation-experimental-lint/src/main/java/androidx/annotation/experimental/lint/ExperimentalDetector.kt
index ef20bed..54cb668 100644
--- a/annotation/annotation-experimental-lint/src/main/java/androidx/annotation/experimental/lint/ExperimentalDetector.kt
+++ b/annotation/annotation-experimental-lint/src/main/java/androidx/annotation/experimental/lint/ExperimentalDetector.kt
@@ -35,12 +35,14 @@
import com.intellij.psi.PsiAnnotation
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiClassType
+import com.intellij.psi.PsiComment
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiField
import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiModifier
import com.intellij.psi.PsiModifierListOwner
import com.intellij.psi.PsiPackage
+import com.intellij.psi.PsiWhiteSpace
import com.intellij.psi.impl.source.PsiClassReferenceType
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.util.PsiTreeUtil
@@ -643,24 +645,38 @@
else -> throw IllegalArgumentException("Unsupported element type")
}
- // If the element has a modifier list, e.g. not an anonymous class or lambda, then place the
- // insertion point at the beginning of the modifiers list. This ensures that we don't insert
- // the annotation in an invalid position, such as after the "public" or "fun" keywords. We
- // also don't want to place it on the element range itself, since that would place it before
- // the comments.
- val elementLocation = context.getLocation(element.modifierList ?: element as UElement)
+ // If the element can include modifiers, e.g. not an anonymous class or lambda, find the
+ // where the list should start. This ensures that we don't insert the annotation in an
+ // invalid position, such as after the `public` or `fun` keywords. We also don't want to
+ // place it on the element range itself, since that would place it before the comments.
+ val elementSourcePsi = element.sourcePsi
+ val elementForInsert = if (elementSourcePsi is PsiModifierListOwner) {
+ elementSourcePsi.asIterable().firstOrNull { child ->
+ child !is PsiWhiteSpace && child !is PsiComment
+ } ?: throw IllegalArgumentException("Failed to locate element declaration")
+ } else {
+ element
+ }
return fix()
- .replace()
.name("Add '$annotation' annotation to $elementLabel")
- .range(elementLocation)
- .beginning()
- .shortenNames()
- .with("$annotation ")
+ .annotate(annotation, true)
+ .range(context.getLocation(elementForInsert))
.build()
}
/**
+ * Returns an iterable of child elements.
+ */
+ private fun PsiElement.asIterable(): Iterable<PsiElement> = object : Iterable<PsiElement> {
+ override fun iterator(): Iterator<PsiElement> = object : Iterator<PsiElement> {
+ private var current = firstChild
+ override fun hasNext(): Boolean = current != null
+ override fun next(): PsiElement = current.apply { current = nextSibling }
+ }
+ }
+
+ /**
* Reports an issue and trims indentation on the [message].
*/
private fun report(
diff --git a/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/ExperimentalDetectorTest.kt b/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/ExperimentalDetectorTest.kt
index 25567ce..21e170e 100644
--- a/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/ExperimentalDetectorTest.kt
+++ b/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/ExperimentalDetectorTest.kt
@@ -72,52 +72,40 @@
val expectedFix = """
Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 25: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalDateTime.class)' annotation to 'getDateUnsafe':
@@ -24 +24
-- int getDateUnsafe() {
-+ @androidx.annotation.OptIn(markerClass = ExperimentalDateTime.class) int getDateUnsafe() {
++ @androidx.annotation.OptIn(markerClass = ExperimentalDateTime.class)
Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 25: Add '@sample.experimental.ExperimentalDateTime' annotation to 'getDateUnsafe':
@@ -24 +24
-- int getDateUnsafe() {
-+ @ExperimentalDateTime int getDateUnsafe() {
++ @ExperimentalDateTime
Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 25: Add '@sample.experimental.ExperimentalDateTime' annotation to containing class 'UseJavaExperimentalFromJava':
@@ -19 +19
-- @SuppressWarnings({"unused", "WeakerAccess", "deprecation"})
-+ @ExperimentalDateTime @SuppressWarnings({"unused", "WeakerAccess", "deprecation"})
++ @ExperimentalDateTime
Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 26: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalDateTime.class)' annotation to 'getDateUnsafe':
@@ -24 +24
-- int getDateUnsafe() {
-+ @androidx.annotation.OptIn(markerClass = ExperimentalDateTime.class) int getDateUnsafe() {
++ @androidx.annotation.OptIn(markerClass = ExperimentalDateTime.class)
Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 26: Add '@sample.experimental.ExperimentalDateTime' annotation to 'getDateUnsafe':
@@ -24 +24
-- int getDateUnsafe() {
-+ @ExperimentalDateTime int getDateUnsafe() {
++ @ExperimentalDateTime
Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 26: Add '@sample.experimental.ExperimentalDateTime' annotation to containing class 'UseJavaExperimentalFromJava':
@@ -19 +19
-- @SuppressWarnings({"unused", "WeakerAccess", "deprecation"})
-+ @ExperimentalDateTime @SuppressWarnings({"unused", "WeakerAccess", "deprecation"})
++ @ExperimentalDateTime
Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 53: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalLocation.class)' annotation to 'getDateExperimentalLocationUnsafe':
@@ -50 +50
-- @ExperimentalDateTime
-+ @androidx.annotation.OptIn(markerClass = ExperimentalLocation.class) @ExperimentalDateTime
++ @androidx.annotation.OptIn(markerClass = ExperimentalLocation.class)
Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 53: Add '@sample.experimental.ExperimentalLocation' annotation to 'getDateExperimentalLocationUnsafe':
@@ -50 +50
-- @ExperimentalDateTime
-+ @ExperimentalLocation @ExperimentalDateTime
++ @ExperimentalLocation
Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 53: Add '@sample.experimental.ExperimentalLocation' annotation to containing class 'UseJavaExperimentalFromJava':
@@ -19 +19
-- @SuppressWarnings({"unused", "WeakerAccess", "deprecation"})
-+ @ExperimentalLocation @SuppressWarnings({"unused", "WeakerAccess", "deprecation"})
++ @ExperimentalLocation
Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 54: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalLocation.class)' annotation to 'getDateExperimentalLocationUnsafe':
@@ -50 +50
-- @ExperimentalDateTime
-+ @androidx.annotation.OptIn(markerClass = ExperimentalLocation.class) @ExperimentalDateTime
++ @androidx.annotation.OptIn(markerClass = ExperimentalLocation.class)
Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 54: Add '@sample.experimental.ExperimentalLocation' annotation to 'getDateExperimentalLocationUnsafe':
@@ -50 +50
-- @ExperimentalDateTime
-+ @ExperimentalLocation @ExperimentalDateTime
++ @ExperimentalLocation
Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 54: Add '@sample.experimental.ExperimentalLocation' annotation to containing class 'UseJavaExperimentalFromJava':
@@ -19 +19
-- @SuppressWarnings({"unused", "WeakerAccess", "deprecation"})
-+ @ExperimentalLocation @SuppressWarnings({"unused", "WeakerAccess", "deprecation"})
++ @ExperimentalLocation
""".trimIndent()
/* ktlint-enable max-line-length */
@@ -189,53 +177,41 @@
val expectedFix = """
Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 29: Add '@androidx.annotation.OptIn(sample.experimental.ExperimentalDateTime::class)' annotation to 'getDateUnsafe':
-@@ -1 +1
-- /*
-+ @androidx.annotation.OptIn(ExperimentalDateTime::class) /*
+@@ -25 +25
++ @androidx.annotation.OptIn(ExperimentalDateTime::class)
Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 29: Add '@sample.experimental.ExperimentalDateTime' annotation to 'getDateUnsafe':
-@@ -1 +1
-- /*
-+ @ExperimentalDateTime /*
+@@ -25 +25
++ @ExperimentalDateTime
Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 29: Add '@sample.experimental.ExperimentalDateTime' annotation to containing class 'UseJavaExperimentalFromKt':
-@@ -23 +23
-- @Suppress("unused", "MemberVisibilityCanBePrivate")
-+ @ExperimentalDateTime @Suppress("unused", "MemberVisibilityCanBePrivate")
+@@ -1 +1
++ @ExperimentalDateTime
Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 30: Add '@androidx.annotation.OptIn(sample.experimental.ExperimentalDateTime::class)' annotation to 'getDateUnsafe':
-@@ -1 +1
-- /*
-+ @androidx.annotation.OptIn(ExperimentalDateTime::class) /*
+@@ -25 +25
++ @androidx.annotation.OptIn(ExperimentalDateTime::class)
Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 30: Add '@sample.experimental.ExperimentalDateTime' annotation to 'getDateUnsafe':
-@@ -1 +1
-- /*
-+ @ExperimentalDateTime /*
+@@ -25 +25
++ @ExperimentalDateTime
Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 30: Add '@sample.experimental.ExperimentalDateTime' annotation to containing class 'UseJavaExperimentalFromKt':
-@@ -23 +23
-- @Suppress("unused", "MemberVisibilityCanBePrivate")
-+ @ExperimentalDateTime @Suppress("unused", "MemberVisibilityCanBePrivate")
+@@ -1 +1
++ @ExperimentalDateTime
Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 57: Add '@androidx.annotation.OptIn(sample.experimental.ExperimentalLocation::class)' annotation to 'getDateExperimentalLocationUnsafe':
-@@ -54 +54
-- @ExperimentalDateTime
-+ @androidx.annotation.OptIn(ExperimentalLocation::class) @ExperimentalDateTime
+@@ -51 +51
++ @androidx.annotation.OptIn(ExperimentalLocation::class)
Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 57: Add '@sample.experimental.ExperimentalLocation' annotation to 'getDateExperimentalLocationUnsafe':
-@@ -54 +54
-- @ExperimentalDateTime
-+ @ExperimentalLocation @ExperimentalDateTime
+@@ -51 +51
++ @ExperimentalLocation
Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 57: Add '@sample.experimental.ExperimentalLocation' annotation to containing class 'UseJavaExperimentalFromKt':
-@@ -23 +23
-- @Suppress("unused", "MemberVisibilityCanBePrivate")
-+ @ExperimentalLocation @Suppress("unused", "MemberVisibilityCanBePrivate")
+@@ -1 +1
++ @ExperimentalLocation
Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 58: Add '@androidx.annotation.OptIn(sample.experimental.ExperimentalLocation::class)' annotation to 'getDateExperimentalLocationUnsafe':
-@@ -54 +54
-- @ExperimentalDateTime
-+ @androidx.annotation.OptIn(ExperimentalLocation::class) @ExperimentalDateTime
+@@ -51 +51
++ @androidx.annotation.OptIn(ExperimentalLocation::class)
Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 58: Add '@sample.experimental.ExperimentalLocation' annotation to 'getDateExperimentalLocationUnsafe':
-@@ -54 +54
-- @ExperimentalDateTime
-+ @ExperimentalLocation @ExperimentalDateTime
+@@ -51 +51
++ @ExperimentalLocation
Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 58: Add '@sample.experimental.ExperimentalLocation' annotation to containing class 'UseJavaExperimentalFromKt':
-@@ -23 +23
-- @Suppress("unused", "MemberVisibilityCanBePrivate")
-+ @ExperimentalLocation @Suppress("unused", "MemberVisibilityCanBePrivate")
+@@ -1 +1
++ @ExperimentalLocation
""".trimIndent()
/* ktlint-enable max-line-length */
@@ -286,100 +262,76 @@
val expectedFix = """
Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 25: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalDateTimeKt.class)' annotation to 'getDateUnsafe':
@@ -24 +24
-- int getDateUnsafe() {
-+ @androidx.annotation.OptIn(markerClass = ExperimentalDateTimeKt.class) int getDateUnsafe() {
++ @androidx.annotation.OptIn(markerClass = ExperimentalDateTimeKt.class)
Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 25: Add '@sample.experimental.ExperimentalDateTimeKt' annotation to 'getDateUnsafe':
@@ -24 +24
-- int getDateUnsafe() {
-+ @ExperimentalDateTimeKt int getDateUnsafe() {
++ @ExperimentalDateTimeKt
Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 25: Add '@sample.experimental.ExperimentalDateTimeKt' annotation to containing class 'UseKtExperimentalFromJava':
@@ -19 +19
-- @SuppressWarnings({"unused", "WeakerAccess"})
-+ @ExperimentalDateTimeKt @SuppressWarnings({"unused", "WeakerAccess"})
++ @ExperimentalDateTimeKt
Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 26: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalDateTimeKt.class)' annotation to 'getDateUnsafe':
@@ -24 +24
-- int getDateUnsafe() {
-+ @androidx.annotation.OptIn(markerClass = ExperimentalDateTimeKt.class) int getDateUnsafe() {
++ @androidx.annotation.OptIn(markerClass = ExperimentalDateTimeKt.class)
Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 26: Add '@sample.experimental.ExperimentalDateTimeKt' annotation to 'getDateUnsafe':
@@ -24 +24
-- int getDateUnsafe() {
-+ @ExperimentalDateTimeKt int getDateUnsafe() {
++ @ExperimentalDateTimeKt
Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 26: Add '@sample.experimental.ExperimentalDateTimeKt' annotation to containing class 'UseKtExperimentalFromJava':
@@ -19 +19
-- @SuppressWarnings({"unused", "WeakerAccess"})
-+ @ExperimentalDateTimeKt @SuppressWarnings({"unused", "WeakerAccess"})
++ @ExperimentalDateTimeKt
Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 54: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalLocationKt.class)' annotation to 'getDateExperimentalLocationUnsafe':
@@ -51 +51
-- @ExperimentalDateTimeKt
-+ @androidx.annotation.OptIn(markerClass = ExperimentalLocationKt.class) @ExperimentalDateTimeKt
++ @androidx.annotation.OptIn(markerClass = ExperimentalLocationKt.class)
Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 54: Add '@sample.experimental.ExperimentalLocationKt' annotation to 'getDateExperimentalLocationUnsafe':
@@ -51 +51
-- @ExperimentalDateTimeKt
-+ @ExperimentalLocationKt @ExperimentalDateTimeKt
++ @ExperimentalLocationKt
Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 54: Add '@sample.experimental.ExperimentalLocationKt' annotation to containing class 'UseKtExperimentalFromJava':
@@ -19 +19
-- @SuppressWarnings({"unused", "WeakerAccess"})
-+ @ExperimentalLocationKt @SuppressWarnings({"unused", "WeakerAccess"})
++ @ExperimentalLocationKt
Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 55: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalLocationKt.class)' annotation to 'getDateExperimentalLocationUnsafe':
@@ -51 +51
-- @ExperimentalDateTimeKt
-+ @androidx.annotation.OptIn(markerClass = ExperimentalLocationKt.class) @ExperimentalDateTimeKt
++ @androidx.annotation.OptIn(markerClass = ExperimentalLocationKt.class)
Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 55: Add '@sample.experimental.ExperimentalLocationKt' annotation to 'getDateExperimentalLocationUnsafe':
@@ -51 +51
-- @ExperimentalDateTimeKt
-+ @ExperimentalLocationKt @ExperimentalDateTimeKt
++ @ExperimentalLocationKt
Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 55: Add '@sample.experimental.ExperimentalLocationKt' annotation to containing class 'UseKtExperimentalFromJava':
@@ -19 +19
-- @SuppressWarnings({"unused", "WeakerAccess"})
-+ @ExperimentalLocationKt @SuppressWarnings({"unused", "WeakerAccess"})
++ @ExperimentalLocationKt
Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 88: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalDateTimeKt.class)' annotation to 'regressionTestStaticUsage':
@@ -87 +87
-- void regressionTestStaticUsage() {
-+ @androidx.annotation.OptIn(markerClass = ExperimentalDateTimeKt.class) void regressionTestStaticUsage() {
++ @androidx.annotation.OptIn(markerClass = ExperimentalDateTimeKt.class)
Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 88: Add '@sample.experimental.ExperimentalDateTimeKt' annotation to 'regressionTestStaticUsage':
@@ -87 +87
-- void regressionTestStaticUsage() {
-+ @ExperimentalDateTimeKt void regressionTestStaticUsage() {
++ @ExperimentalDateTimeKt
Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 88: Add '@sample.experimental.ExperimentalDateTimeKt' annotation to containing class 'UseKtExperimentalFromJava':
@@ -19 +19
-- @SuppressWarnings({"unused", "WeakerAccess"})
-+ @ExperimentalDateTimeKt @SuppressWarnings({"unused", "WeakerAccess"})
++ @ExperimentalDateTimeKt
Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 89: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalDateTimeKt.class)' annotation to 'regressionTestStaticUsage':
@@ -87 +87
-- void regressionTestStaticUsage() {
-+ @androidx.annotation.OptIn(markerClass = ExperimentalDateTimeKt.class) void regressionTestStaticUsage() {
++ @androidx.annotation.OptIn(markerClass = ExperimentalDateTimeKt.class)
Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 89: Add '@sample.experimental.ExperimentalDateTimeKt' annotation to 'regressionTestStaticUsage':
@@ -87 +87
-- void regressionTestStaticUsage() {
-+ @ExperimentalDateTimeKt void regressionTestStaticUsage() {
++ @ExperimentalDateTimeKt
Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 89: Add '@sample.experimental.ExperimentalDateTimeKt' annotation to containing class 'UseKtExperimentalFromJava':
@@ -19 +19
-- @SuppressWarnings({"unused", "WeakerAccess"})
-+ @ExperimentalDateTimeKt @SuppressWarnings({"unused", "WeakerAccess"})
++ @ExperimentalDateTimeKt
Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 96: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalDateTimeKt.class)' annotation to 'regressionTestInlineUsage':
@@ -95 +95
-- void regressionTestInlineUsage() {
-+ @androidx.annotation.OptIn(markerClass = ExperimentalDateTimeKt.class) void regressionTestInlineUsage() {
++ @androidx.annotation.OptIn(markerClass = ExperimentalDateTimeKt.class)
Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 96: Add '@sample.experimental.ExperimentalDateTimeKt' annotation to 'regressionTestInlineUsage':
@@ -95 +95
-- void regressionTestInlineUsage() {
-+ @ExperimentalDateTimeKt void regressionTestInlineUsage() {
++ @ExperimentalDateTimeKt
Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 96: Add '@sample.experimental.ExperimentalDateTimeKt' annotation to containing class 'UseKtExperimentalFromJava':
@@ -19 +19
-- @SuppressWarnings({"unused", "WeakerAccess"})
-+ @ExperimentalDateTimeKt @SuppressWarnings({"unused", "WeakerAccess"})
++ @ExperimentalDateTimeKt
Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 97: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalDateTime.class)' annotation to 'regressionTestInlineUsage':
@@ -95 +95
-- void regressionTestInlineUsage() {
-+ @androidx.annotation.OptIn(markerClass = ExperimentalDateTime.class) void regressionTestInlineUsage() {
++ @androidx.annotation.OptIn(markerClass = ExperimentalDateTime.class)
Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 97: Add '@sample.experimental.ExperimentalDateTime' annotation to 'regressionTestInlineUsage':
@@ -95 +95
-- void regressionTestInlineUsage() {
-+ @ExperimentalDateTime void regressionTestInlineUsage() {
++ @ExperimentalDateTime
Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 97: Add '@sample.experimental.ExperimentalDateTime' annotation to containing class 'UseKtExperimentalFromJava':
@@ -19 +19
-- @SuppressWarnings({"unused", "WeakerAccess"})
-+ @ExperimentalDateTime @SuppressWarnings({"unused", "WeakerAccess"})
++ @ExperimentalDateTime
""".trimIndent()
/* ktlint-enable max-line-length */
diff --git a/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/RequiresOptInDetectorTest.kt b/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/RequiresOptInDetectorTest.kt
index 99ca8e3..f26e382 100644
--- a/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/RequiresOptInDetectorTest.kt
+++ b/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/RequiresOptInDetectorTest.kt
@@ -23,7 +23,6 @@
import com.android.tools.lint.checks.infrastructure.TestFiles.kotlin
import com.android.tools.lint.checks.infrastructure.TestLintResult
import com.android.tools.lint.checks.infrastructure.TestLintTask.lint
-import com.android.tools.lint.checks.infrastructure.TestMode
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@@ -39,7 +38,6 @@
*testFiles
)
.issues(*ExperimentalDetector.ISSUES.toTypedArray())
- .testModes(TestMode.PARTIAL)
.run()
}
@@ -444,6 +442,38 @@
check(*input).expect(expected)
}
+ @Test
+ fun regressionTestKotlin298322402() {
+ val input = arrayOf(
+ javaSample("sample.optin.ExperimentalJavaAnnotation"),
+ javaSample("sample.optin.AnnotatedJavaMembers"),
+ ktSample("sample.optin.RegressionTestKotlin298322402")
+ )
+
+ /* ktlint-disable max-line-length */
+ val expected = """
+src/sample/optin/RegressionTestKotlin298322402.kt:22: Error: This declaration is opt-in and its usage should be marked with @sample.optin.ExperimentalJavaAnnotation or @OptIn(markerClass = sample.optin.ExperimentalJavaAnnotation.class) [UnsafeOptInUsageError]
+ player.accessor
+ ~~~~~~~~
+1 errors, 0 warnings
+ """.trimIndent()
+
+ val expectedFix = """
+Fix for src/sample/optin/RegressionTestKotlin298322402.kt line 22: Add '@androidx.annotation.OptIn(sample.optin.ExperimentalJavaAnnotation::class)' annotation to 'testMethod':
+@@ -21 +21
++ @androidx.annotation.OptIn(ExperimentalJavaAnnotation::class)
+Fix for src/sample/optin/RegressionTestKotlin298322402.kt line 22: Add '@sample.optin.ExperimentalJavaAnnotation' annotation to 'testMethod':
+@@ -21 +21
++ @ExperimentalJavaAnnotation
+Fix for src/sample/optin/RegressionTestKotlin298322402.kt line 22: Add '@sample.optin.ExperimentalJavaAnnotation' annotation to containing class 'RegressionTestKotlin298322402':
+@@ -1 +1
++ @ExperimentalJavaAnnotation
+ """.trimIndent()
+ /* ktlint-enable max-line-length */
+
+ check(*input).expectFixDiffs(expectedFix).expect(expected)
+ }
+
/* ktlint-disable max-line-length */
companion object {
/**
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/GenericDocumentInternalTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/GenericDocumentInternalTest.java
index b514854..8161c1c 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/GenericDocumentInternalTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/GenericDocumentInternalTest.java
@@ -18,8 +18,6 @@
import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.assertThrows;
-
import android.os.Bundle;
import android.os.Parcel;
@@ -107,48 +105,4 @@
assertThat(outDoc.getPropertyDocument("propDocument").getPropertyBytesArray("propBytes"))
.isEqualTo(new byte[][]{{3, 4}});
}
-
- @Test
- public void testPropertyParcel_onePropertySet_success() {
- String[] stringValues = {"a", "b"};
- long[] longValues = {1L, 2L};
- double[] doubleValues = {1.0, 2.0};
- boolean[] booleanValues = {true, false};
- byte[][] bytesValues = {new byte[1]};
- Bundle[] bundleValues = {new Bundle()};
-
- assertThat(new PropertyParcel.Builder("name").setStringValues(
- stringValues).build().getStringValues()).isEqualTo(
- Arrays.copyOf(stringValues, stringValues.length));
- assertThat(new PropertyParcel.Builder("name").setLongValues(
- longValues).build().getLongValues()).isEqualTo(
- Arrays.copyOf(longValues, longValues.length));
- assertThat(new PropertyParcel.Builder("name").setDoubleValues(
- doubleValues).build().getDoubleValues()).isEqualTo(
- Arrays.copyOf(doubleValues, doubleValues.length));
- assertThat(new PropertyParcel.Builder("name").setBooleanValues(
- booleanValues).build().getBooleanValues()).isEqualTo(
- Arrays.copyOf(booleanValues, booleanValues.length));
- assertThat(new PropertyParcel.Builder("name").setBytesValues(
- bytesValues).build().getBytesValues()).isEqualTo(
- Arrays.copyOf(bytesValues, bytesValues.length));
- assertThat(new PropertyParcel.Builder("name").setDocumentValues(
- bundleValues).build().getDocumentValues()).isEqualTo(
- Arrays.copyOf(bundleValues, bundleValues.length));
- }
-
- @Test
- public void testPropertyParcel_moreThanOnePropertySet_exceptionThrown() {
- String[] stringValues = {"a", "b"};
- long[] longValues = {1L, 2L};
- PropertyParcel.Builder propertyParcelBuilder =
- new PropertyParcel.Builder("name")
- .setStringValues(stringValues)
- .setLongValues(longValues);
-
- IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
- () -> propertyParcelBuilder.build());
-
- assertThat(exception.getMessage()).contains("One and only one type array");
- }
}
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/safeparcel/GenericDocumentParcelTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/safeparcel/GenericDocumentParcelTest.java
new file mode 100644
index 0000000..47df245
--- /dev/null
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/safeparcel/GenericDocumentParcelTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appsearch.app.safeparcel;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Map;
+
+/** Tests for {@link androidx.appsearch.app.GenericDocument} related SafeParcels. */
+public class GenericDocumentParcelTest {
+ @Test
+ public void testPropertyParcel_onePropertySet_success() {
+ String[] stringValues = {"a", "b"};
+ long[] longValues = {1L, 2L};
+ double[] doubleValues = {1.0, 2.0};
+ boolean[] booleanValues = {true, false};
+ byte[][] bytesValues = {new byte[1]};
+ GenericDocumentParcel[] docValues = {(new GenericDocumentParcel.Builder(
+ "namespace", "id", "schemaType")).build()};
+
+ assertThat(new PropertyParcel.Builder("name").setStringValues(
+ stringValues).build().getStringValues()).isEqualTo(
+ Arrays.copyOf(stringValues, stringValues.length));
+ assertThat(new PropertyParcel.Builder("name").setLongValues(
+ longValues).build().getLongValues()).isEqualTo(
+ Arrays.copyOf(longValues, longValues.length));
+ assertThat(new PropertyParcel.Builder("name").setDoubleValues(
+ doubleValues).build().getDoubleValues()).isEqualTo(
+ Arrays.copyOf(doubleValues, doubleValues.length));
+ assertThat(new PropertyParcel.Builder("name").setBooleanValues(
+ booleanValues).build().getBooleanValues()).isEqualTo(
+ Arrays.copyOf(booleanValues, booleanValues.length));
+ assertThat(new PropertyParcel.Builder("name").setBytesValues(
+ bytesValues).build().getBytesValues()).isEqualTo(
+ Arrays.copyOf(bytesValues, bytesValues.length));
+ assertThat(new PropertyParcel.Builder("name").setDocumentValues(
+ docValues).build().getDocumentValues()).isEqualTo(
+ Arrays.copyOf(docValues, docValues.length));
+ }
+
+ @Test
+ public void testPropertyParcel_moreThanOnePropertySet_exceptionThrown() {
+ String[] stringValues = {"a", "b"};
+ long[] longValues = {1L, 2L};
+ PropertyParcel.Builder propertyParcelBuilder =
+ new PropertyParcel.Builder("name")
+ .setStringValues(stringValues)
+ .setLongValues(longValues);
+
+ IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
+ () -> propertyParcelBuilder.build());
+
+ assertThat(exception.getMessage()).contains("One and only one type array");
+ }
+
+ @Test
+ public void testGenericDocumentParcel_propertiesGeneratedCorrectly() {
+ GenericDocumentParcel.Builder builder =
+ new GenericDocumentParcel.Builder(
+ /*namespace=*/ "namespace",
+ /*id=*/ "id",
+ /*schemaType=*/ "schemaType");
+ long[] longArray = new long[]{1L, 2L, 3L};
+ String[] stringArray = new String[]{"hello", "world", "!"};
+ builder.putInPropertyMap(/*name=*/ "longArray", /*values=*/ longArray);
+ builder.putInPropertyMap(/*name=*/ "stringArray", /*values=*/ stringArray);
+ GenericDocumentParcel genericDocumentParcel = builder.build();
+
+ PropertyParcel[] properties = genericDocumentParcel.getProperties();
+ Map<String, PropertyParcel> propertyMap = genericDocumentParcel.getPropertyMap();
+ PropertyParcel longArrayProperty = new PropertyParcel.Builder(
+ /*name=*/ "longArray").setLongValues(longArray).build();
+ PropertyParcel stringArrayProperty = new PropertyParcel.Builder(
+ /*name=*/ "stringArray").setStringValues(stringArray).build();
+
+ assertThat(properties).asList().containsExactly(longArrayProperty, stringArrayProperty);
+ assertThat(propertyMap).containsExactly("longArray", longArrayProperty,
+ "stringArray", stringArrayProperty);
+ }
+
+ @Test
+ public void testGenericDocumentParcel_buildFromAnotherDocumentParcelCorrectly() {
+ GenericDocumentParcel.Builder builder =
+ new GenericDocumentParcel.Builder(
+ /*namespace=*/ "namespace",
+ /*id=*/ "id",
+ /*schemaType=*/ "schemaType");
+ long[] longArray = new long[]{1L, 2L, 3L};
+ String[] stringArray = new String[]{"hello", "world", "!"};
+ builder.putInPropertyMap(/*name=*/ "longArray", /*values=*/ longArray);
+ builder.putInPropertyMap(/*name=*/ "stringArray", /*values=*/ stringArray);
+ GenericDocumentParcel genericDocumentParcel = builder.build();
+
+ GenericDocumentParcel genericDocumentParcelCopy =
+ new GenericDocumentParcel.Builder(genericDocumentParcel).build();
+
+ assertThat(genericDocumentParcelCopy.getNamespace()).isEqualTo(
+ genericDocumentParcel.getNamespace());
+ assertThat(genericDocumentParcelCopy.getId()).isEqualTo(genericDocumentParcel.getId());
+ assertThat(genericDocumentParcelCopy.getSchemaType()).isEqualTo(
+ genericDocumentParcel.getSchemaType());
+ assertThat(genericDocumentParcelCopy.getCreationTimestampMillis()).isEqualTo(
+ genericDocumentParcel.getCreationTimestampMillis());
+ assertThat(genericDocumentParcelCopy.getTtlMillis()).isEqualTo(
+ genericDocumentParcel.getTtlMillis());
+ assertThat(genericDocumentParcelCopy.getScore()).isEqualTo(
+ genericDocumentParcel.getScore());
+ // Check it is a copy.
+ assertThat(genericDocumentParcelCopy).isNotSameInstanceAs(genericDocumentParcel);
+ assertThat(genericDocumentParcelCopy.getProperties()).isEqualTo(
+ genericDocumentParcel.getProperties());
+ }
+}
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/safeparcel/GenericDocumentParcel.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/safeparcel/GenericDocumentParcel.java
new file mode 100644
index 0000000..3d92c02
--- /dev/null
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/safeparcel/GenericDocumentParcel.java
@@ -0,0 +1,494 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appsearch.app.safeparcel;
+
+import android.os.Parcel;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.appsearch.annotation.CanIgnoreReturnValue;
+import androidx.appsearch.app.AppSearchSchema;
+import androidx.appsearch.app.AppSearchSession;
+import androidx.appsearch.app.GenericDocument;
+import androidx.appsearch.safeparcel.AbstractSafeParcelable;
+import androidx.appsearch.safeparcel.SafeParcelable;
+import androidx.appsearch.safeparcel.stub.StubCreators.GenericDocumentParcelCreator;
+import androidx.collection.ArrayMap;
+
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Holds data for a {@link GenericDocument}.
+ *
+ * @exportToFramework:hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+@SafeParcelable.Class(creator = "GenericDocumentParcelCreator")
+public final class GenericDocumentParcel extends AbstractSafeParcelable {
+ @NonNull
+ public static final GenericDocumentParcelCreator CREATOR =
+ new GenericDocumentParcelCreator();
+
+ /** The default score of document. */
+ private static final int DEFAULT_SCORE = 0;
+
+ /** The default time-to-live in millisecond of a document, which is infinity. */
+ private static final long DEFAULT_TTL_MILLIS = 0L;
+
+ /** Default but invalid value for {@code mCreationTimestampMillis}. */
+ private static final long INVALID_CREATION_TIMESTAMP_MILLIS = -1L;
+
+ @Field(id = 1, getter = "getNamespace")
+ @NonNull
+ private final String mNamespace;
+
+ @Field(id = 2, getter = "getId")
+ @NonNull
+ private final String mId;
+
+ @Field(id = 3, getter = "getSchemaType")
+ @NonNull
+ private final String mSchemaType;
+
+ @Field(id = 4, getter = "getCreationTimestampMillis")
+ private final long mCreationTimestampMillis;
+
+ @Field(id = 5, getter = "getTtlMillis")
+ private final long mTtlMillis;
+
+ @Field(id = 6, getter = "getScore")
+ private final int mScore;
+
+ /**
+ * Contains all properties in {@link GenericDocument} in a list.
+ *
+ * <p>Unfortunately SafeParcelable doesn't support map type so we have to use a list here.
+ */
+ @Field(id = 7, getter = "getProperties")
+ @NonNull
+ private final PropertyParcel[] mProperties;
+
+ /**
+ * Contains all properties in {@link GenericDocument} to support getting properties via name
+ *
+ * <p>This map is created for quick looking up property by name.
+ */
+ @NonNull
+ private final Map<String, PropertyParcel> mPropertyMap;
+
+ @Nullable
+ private Integer mHashCode;
+
+ /**
+ * The constructor taking the property list, and create map internally from this list.
+ *
+ * <p> This will be used in createFromParcel, so creating the property map can not be avoided
+ * in this constructor.
+ */
+ @Constructor
+ GenericDocumentParcel(
+ @Param(id = 1) @NonNull String namespace,
+ @Param(id = 2) @NonNull String id,
+ @Param(id = 3) @NonNull String schemaType,
+ @Param(id = 4) long creationTimestampMillis,
+ @Param(id = 5) long ttlMillis,
+ @Param(id = 6) int score,
+ @Param(id = 7) @NonNull PropertyParcel[] properties) {
+ this(namespace, id, schemaType, creationTimestampMillis, ttlMillis, score,
+ properties, createPropertyMapFromPropertyArray(properties));
+ }
+
+ /**
+ * A constructor taking both property list and property map.
+ *
+ * <p>Caller needs to make sure property list and property map
+ * matches(map is generated from list, or list generated from map).
+ */
+ GenericDocumentParcel(
+ @NonNull String namespace,
+ @NonNull String id,
+ @NonNull String schemaType,
+ long creationTimestampMillis,
+ long ttlMillis,
+ int score,
+ @NonNull PropertyParcel[] properties,
+ @NonNull Map<String, PropertyParcel> propertyMap) {
+ mNamespace = Objects.requireNonNull(namespace);
+ mId = Objects.requireNonNull(id);
+ mSchemaType = Objects.requireNonNull(schemaType);
+ mCreationTimestampMillis = creationTimestampMillis;
+ mTtlMillis = ttlMillis;
+ mScore = score;
+ mProperties = Objects.requireNonNull(properties);
+ mPropertyMap = Objects.requireNonNull(propertyMap);
+ }
+
+ private static Map<String, PropertyParcel> createPropertyMapFromPropertyArray(
+ @NonNull PropertyParcel[] properties) {
+ Objects.requireNonNull(properties);
+ Map<String, PropertyParcel> propertyMap = new ArrayMap<>(properties.length);
+ for (int i = 0; i < properties.length; ++i) {
+ PropertyParcel property = properties[i];
+ propertyMap.put(property.getPropertyName(), property);
+ }
+ return propertyMap;
+ }
+
+ /** Returns the unique identifier of the {@link GenericDocument}. */
+ @NonNull
+ public String getId() {
+ return mId;
+ }
+
+ /** Returns the namespace of the {@link GenericDocument}. */
+ @NonNull
+ public String getNamespace() {
+ return mNamespace;
+ }
+
+ /** Returns the {@link AppSearchSchema} type of the {@link GenericDocument}. */
+ @NonNull
+ public String getSchemaType() {
+ return mSchemaType;
+ }
+
+ /** Returns the creation timestamp of the {@link GenericDocument}, in milliseconds. */
+ /*@exportToFramework:CurrentTimeMillisLong*/
+ public long getCreationTimestampMillis() {
+ return mCreationTimestampMillis;
+ }
+
+ /** Returns the TTL (time-to-live) of the {@link GenericDocument}, in milliseconds. */
+ public long getTtlMillis() {
+ return mTtlMillis;
+ }
+
+ /** Returns the score of the {@link GenericDocument}. */
+ public int getScore() {
+ return mScore;
+ }
+
+ /** Returns the names of all properties defined in this document. */
+ @NonNull
+ public Set<String> getPropertyNames() {
+ return mPropertyMap.keySet();
+ }
+
+ /** Returns all the properties the document has. */
+ @NonNull
+ public PropertyParcel[] getProperties() {
+ return mProperties;
+ }
+
+ /** Returns the property map the document has. */
+ @NonNull
+ public Map<String, PropertyParcel> getPropertyMap() {
+ return mPropertyMap;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (!(other instanceof GenericDocumentParcel)) {
+ return false;
+ }
+ GenericDocumentParcel otherDocument = (GenericDocumentParcel) other;
+ return mNamespace.equals(otherDocument.mNamespace)
+ && mId.equals(otherDocument.mId)
+ && mSchemaType.equals(otherDocument.mSchemaType)
+ && mTtlMillis == otherDocument.mTtlMillis
+ && mCreationTimestampMillis == otherDocument.mCreationTimestampMillis
+ && mScore == otherDocument.mScore
+ && Arrays.equals(mProperties, otherDocument.mProperties)
+ && Objects.equals(mPropertyMap, otherDocument.mPropertyMap);
+ }
+
+ @Override
+ public int hashCode() {
+ if (mHashCode == null) {
+ mHashCode = Objects.hash(
+ mNamespace,
+ mId,
+ mSchemaType,
+ mTtlMillis,
+ mScore,
+ mCreationTimestampMillis,
+ Arrays.hashCode(mProperties),
+ mPropertyMap.hashCode());
+ }
+ return mHashCode;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ GenericDocumentParcelCreator.writeToParcel(this, dest, flags);
+ }
+
+ /** The builder class for {@link GenericDocumentParcel}. */
+ public static final class Builder {
+ private String mNamespace;
+ private String mId;
+ private String mSchemaType;
+ private long mCreationTimestampMillis;
+ private long mTtlMillis;
+ private int mScore;
+ private Map<String, PropertyParcel> mPropertyMap;
+ private boolean mBuilt = false;
+
+ /**
+ * Creates a new {@link GenericDocument.Builder}.
+ *
+ * <p>Document IDs are unique within a namespace.
+ *
+ * <p>The number of namespaces per app should be kept small for efficiency reasons.
+ */
+ public Builder(@NonNull String namespace, @NonNull String id, @NonNull String schemaType) {
+ mNamespace = Objects.requireNonNull(namespace);
+ mId = Objects.requireNonNull(id);
+ mSchemaType = Objects.requireNonNull(schemaType);
+ mCreationTimestampMillis = INVALID_CREATION_TIMESTAMP_MILLIS;
+ mTtlMillis = DEFAULT_TTL_MILLIS;
+ mScore = DEFAULT_SCORE;
+ mPropertyMap = new ArrayMap<>();
+ }
+
+ /**
+ * Creates a new {@link GenericDocumentParcel.Builder} from the given
+ * {@link GenericDocumentParcel}.
+ */
+ Builder(@NonNull GenericDocumentParcel documentSafeParcel) {
+ Objects.requireNonNull(documentSafeParcel);
+
+ mNamespace = documentSafeParcel.mNamespace;
+ mId = documentSafeParcel.mId;
+ mSchemaType = documentSafeParcel.mSchemaType;
+ mCreationTimestampMillis = documentSafeParcel.mCreationTimestampMillis;
+ mTtlMillis = documentSafeParcel.mTtlMillis;
+ mScore = documentSafeParcel.mScore;
+
+ // Create a shallow copy of the map so we won't change the original one.
+ Map<String, PropertyParcel> propertyMap = documentSafeParcel.mPropertyMap;
+ mPropertyMap = new ArrayMap<>(propertyMap.size());
+ for (PropertyParcel value : propertyMap.values()) {
+ mPropertyMap.put(value.getPropertyName(), value);
+ }
+ }
+
+ /**
+ * Sets the app-defined namespace this document resides in, changing the value provided in
+ * the constructor. No special values are reserved or understood by the infrastructure.
+ *
+ * <p>Document IDs are unique within a namespace.
+ *
+ * <p>The number of namespaces per app should be kept small for efficiency reasons.
+ */
+ @CanIgnoreReturnValue
+ @NonNull
+ public Builder setNamespace(@NonNull String namespace) {
+ Objects.requireNonNull(namespace);
+ resetIfBuilt();
+ mNamespace = namespace;
+ return this;
+ }
+
+ /**
+ * Sets the ID of this document, changing the value provided in the constructor. No special
+ * values are reserved or understood by the infrastructure.
+ *
+ * <p>Document IDs are unique within a namespace.
+ */
+ @CanIgnoreReturnValue
+ @NonNull
+ public Builder setId(@NonNull String id) {
+ Objects.requireNonNull(id);
+ resetIfBuilt();
+ mId = id;
+ return this;
+ }
+
+ /**
+ * Sets the schema type of this document, changing the value provided in the constructor.
+ *
+ * <p>To successfully index a document, the schema type must match the name of an {@link
+ * AppSearchSchema} object previously provided to {@link AppSearchSession#setSchema}.
+ */
+ @CanIgnoreReturnValue
+ @NonNull
+ public Builder setSchemaType(@NonNull String schemaType) {
+ Objects.requireNonNull(schemaType);
+ resetIfBuilt();
+ mSchemaType = schemaType;
+ return this;
+ }
+
+ /** Sets the score of the parent {@link GenericDocument}. */
+ @CanIgnoreReturnValue
+ @NonNull
+ public Builder setScore(int score) {
+ resetIfBuilt();
+ mScore = score;
+ return this;
+ }
+
+ /**
+ * Sets the creation timestamp of the {@link GenericDocument}, in milliseconds.
+ *
+ * <p>This should be set using a value obtained from the {@link System#currentTimeMillis}
+ * time base.
+ *
+ * <p>If this method is not called, this will be set to the time the object is built.
+ *
+ * @param creationTimestampMillis a creation timestamp in milliseconds.
+ */
+ @CanIgnoreReturnValue
+ @NonNull
+ public Builder setCreationTimestampMillis(
+ /*@exportToFramework:CurrentTimeMillisLong*/ long creationTimestampMillis) {
+ resetIfBuilt();
+ mCreationTimestampMillis = creationTimestampMillis;
+ return this;
+ }
+
+ /**
+ * Sets the TTL (time-to-live) of the {@link GenericDocument}, in milliseconds.
+ *
+ * <p>The TTL is measured against {@link #getCreationTimestampMillis}. At the timestamp of
+ * {@code creationTimestampMillis + ttlMillis}, measured in the {@link
+ * System#currentTimeMillis} time base, the document will be auto-deleted.
+ *
+ * <p>The default value is 0, which means the document is permanent and won't be
+ * auto-deleted until the app is uninstalled or {@link AppSearchSession#remove} is called.
+ *
+ * @param ttlMillis a non-negative duration in milliseconds.
+ * @throws IllegalArgumentException if ttlMillis is negative.
+ */
+ @CanIgnoreReturnValue
+ @NonNull
+ public Builder setTtlMillis(long ttlMillis) {
+ if (ttlMillis < 0) {
+ throw new IllegalArgumentException("Document ttlMillis cannot be negative.");
+ }
+ resetIfBuilt();
+ mTtlMillis = ttlMillis;
+ return this;
+ }
+
+ /**
+ * Clears the value for the property with the given name.
+ *
+ * <p>Note that this method does not support property paths.
+ *
+ * @param name The name of the property to clear.
+ */
+ @CanIgnoreReturnValue
+ @NonNull
+ public Builder clearProperty(@NonNull String name) {
+ Objects.requireNonNull(name);
+ resetIfBuilt();
+ mPropertyMap.remove(name);
+ return this;
+ }
+
+ /** puts an array of {@link String} in property map. */
+ @NonNull
+ public Builder putInPropertyMap(@NonNull String name, @NonNull String[] values)
+ throws IllegalArgumentException {
+ mPropertyMap.put(name,
+ new PropertyParcel.Builder(name).setStringValues(values).build());
+ return this;
+ }
+
+ /** puts an array of boolean in property map. */
+ @NonNull
+ public Builder putInPropertyMap(@NonNull String name, @NonNull boolean[] values) {
+ mPropertyMap.put(name,
+ new PropertyParcel.Builder(name).setBooleanValues(values).build());
+ return this;
+ }
+
+ /** puts an array of double in property map. */
+ @NonNull
+ public Builder putInPropertyMap(@NonNull String name, @NonNull double[] values) {
+ mPropertyMap.put(name,
+ new PropertyParcel.Builder(name).setDoubleValues(values).build());
+ return this;
+ }
+
+ /** puts an array of long in property map. */
+ @NonNull
+ public Builder putInPropertyMap(@NonNull String name, @NonNull long[] values) {
+ mPropertyMap.put(name,
+ new PropertyParcel.Builder(name).setLongValues(values).build());
+ return this;
+ }
+
+ /**
+ * Converts and saves a byte[][] into {@link #mProperties}.
+ */
+ @NonNull
+ public Builder putInPropertyMap(@NonNull String name, @NonNull byte[][] values) {
+ mPropertyMap.put(name,
+ new PropertyParcel.Builder(name).setBytesValues(values).build());
+ return this;
+ }
+
+ /** puts an array of {@link GenericDocumentParcel} in property map. */
+ @NonNull
+ public Builder putInPropertyMap(@NonNull String name,
+ @NonNull GenericDocumentParcel[] values) {
+ mPropertyMap.put(name,
+ new PropertyParcel.Builder(name).setDocumentValues(values).build());
+ return this;
+ }
+
+ /** Builds the {@link GenericDocument} object. */
+ @NonNull
+ public GenericDocumentParcel build() {
+ mBuilt = true;
+ // Set current timestamp for creation timestamp by default.
+ if (mCreationTimestampMillis == INVALID_CREATION_TIMESTAMP_MILLIS) {
+ mCreationTimestampMillis = System.currentTimeMillis();
+ }
+ return new GenericDocumentParcel(
+ mNamespace,
+ mId,
+ mSchemaType,
+ mCreationTimestampMillis,
+ mTtlMillis,
+ mScore,
+ mPropertyMap.values().toArray(new PropertyParcel[0]));
+ }
+
+ void resetIfBuilt() {
+ if (mBuilt) {
+ Map<String, PropertyParcel> propertyMap = mPropertyMap;
+ mPropertyMap = new ArrayMap<>(propertyMap.size());
+ for (PropertyParcel value : propertyMap.values()) {
+ // PropertyParcel is not deep copied since it is not mutable.
+ mPropertyMap.put(value.getPropertyName(), value);
+ }
+ mBuilt = false;
+ }
+ }
+ }
+}
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/PropertyParcel.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/safeparcel/PropertyParcel.java
similarity index 88%
rename from appsearch/appsearch/src/main/java/androidx/appsearch/app/PropertyParcel.java
rename to appsearch/appsearch/src/main/java/androidx/appsearch/app/safeparcel/PropertyParcel.java
index 7e6fa88..f9034a9 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/PropertyParcel.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/safeparcel/PropertyParcel.java
@@ -14,10 +14,9 @@
* limitations under the License.
*/
-package androidx.appsearch.app;
+package androidx.appsearch.app.safeparcel;
-import android.os.Bundle;
import android.os.Parcel;
import androidx.annotation.NonNull;
@@ -26,7 +25,6 @@
import androidx.appsearch.safeparcel.AbstractSafeParcelable;
import androidx.appsearch.safeparcel.SafeParcelable;
import androidx.appsearch.safeparcel.stub.StubCreators.PropertyParcelCreator;
-import androidx.appsearch.util.BundleUtil;
import java.util.Arrays;
import java.util.Objects;
@@ -67,10 +65,9 @@
@Field(id = 6, getter = "getBytesValues")
private final byte[][] mBytesValues;
- // TODO(b/24205844) Change it to GenericDocumentParcel once it is added.
@Nullable
@Field(id = 7, getter = "getDocumentValues")
- private final Bundle[] mDocumentValues;
+ private final GenericDocumentParcel[] mDocumentValues;
@Nullable private Integer mHashCode;
@@ -82,7 +79,7 @@
@Param(id = 4) @Nullable double[] doubleValues,
@Param(id = 5) @Nullable boolean[] booleanValues,
@Param(id = 6) @Nullable byte[][] bytesValues,
- @Param(id = 7) @Nullable Bundle[] documentValues) {
+ @Param(id = 7) @Nullable GenericDocumentParcel[] documentValues) {
mPropertyName = Objects.requireNonNull(propertyName);
mStringValues = stringValues;
mLongValues = longValues;
@@ -129,9 +126,9 @@
return mBytesValues;
}
- /** Returns {@link Bundle} in an array. */
+ /** Returns {@link GenericDocumentParcel}s in an array. */
@Nullable
- public Bundle[] getDocumentValues() {
+ public GenericDocumentParcel[] getDocumentValues() {
return mDocumentValues;
}
@@ -209,13 +206,7 @@
} else if (mBytesValues != null) {
hashCode = Arrays.deepHashCode(mBytesValues);
} else if (mDocumentValues != null) {
- // TODO(b/24205844) change those to Arrays.hashCode() as well once we replace
- // this Bundle[] with GenericDocumentParcel[].
- int[] innerHashCodes = new int[mDocumentValues.length];
- for (int i = 0; i < mDocumentValues.length; ++i) {
- innerHashCodes[i] = BundleUtil.deepHashCode(mDocumentValues[i]);
- }
- hashCode = Arrays.hashCode(innerHashCodes);
+ hashCode = Arrays.hashCode(mDocumentValues);
}
mHashCode = Objects.hash(mPropertyName, hashCode);
}
@@ -239,9 +230,7 @@
&& Arrays.equals(mDoubleValues, otherPropertyParcel.mDoubleValues)
&& Arrays.equals(mBooleanValues, otherPropertyParcel.mBooleanValues)
&& Arrays.deepEquals(mBytesValues, otherPropertyParcel.mBytesValues)
- // TODO(b/24205844) Change it to Arrays.equals once GenericDocumentParcel is added.
- && BundleUtil.bundleValueEquals(
- mDocumentValues, otherPropertyParcel.mDocumentValues);
+ && Arrays.equals(mDocumentValues, otherPropertyParcel.mDocumentValues);
}
/** Builder for {@link PropertyParcel}. */
@@ -252,7 +241,7 @@
private double[] mDoubleValues;
private boolean[] mBooleanValues;
private byte[][] mBytesValues;
- private Bundle[] mDocumentValues;
+ private GenericDocumentParcel[] mDocumentValues;
public Builder(@NonNull String propertyName) {
mPropertyName = Objects.requireNonNull(propertyName);
@@ -295,7 +284,7 @@
/** Sets document values. */
@NonNull
- public Builder setDocumentValues(@NonNull Bundle[] documentValues) {
+ public Builder setDocumentValues(@NonNull GenericDocumentParcel[] documentValues) {
mDocumentValues = Objects.requireNonNull(documentValues);
return this;
}
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/safeparcel/stub/StubCreators.java b/appsearch/appsearch/src/main/java/androidx/appsearch/safeparcel/stub/StubCreators.java
index 5b36ecd..c4f9241 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/safeparcel/stub/StubCreators.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/safeparcel/stub/StubCreators.java
@@ -16,7 +16,9 @@
package androidx.appsearch.safeparcel.stub;
import androidx.annotation.RestrictTo;
+import androidx.appsearch.app.safeparcel.GenericDocumentParcel;
import androidx.appsearch.app.safeparcel.PropertyConfigParcel;
+import androidx.appsearch.app.safeparcel.PropertyParcel;
/**
* Stub creators for any classes extending
@@ -33,7 +35,7 @@
public static class StorageInfoCreator extends AbstractCreator {
}
- /** Stub creator for {@link androidx.appsearch.app.PropertyParcel}. */
+ /** Stub creator for {@link PropertyParcel}. */
public static class PropertyParcelCreator extends AbstractCreator {
}
@@ -68,4 +70,8 @@
*/
public static class DocumentIndexingConfigParcelCreator extends AbstractCreator {
}
+
+ /** Stub creator for {@link GenericDocumentParcel}. */
+ public static class GenericDocumentParcelCreator extends AbstractCreator {
+ }
}
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/InstrumentationResults.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/InstrumentationResults.kt
index d778a9f..bae0b4c 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/InstrumentationResults.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/InstrumentationResults.kt
@@ -298,6 +298,9 @@
absoluteFilePath: String,
reportOnRunEndOnly: Boolean = false
) {
+ require(!key.contains('=')) {
+ "Key must not contain '=', which breaks instrumentation result string parsing"
+ }
if (reportOnRunEndOnly) {
InstrumentationResultScope(runEndResultBundle).fileRecord(key, absoluteFilePath)
} else {
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/Outputs.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/Outputs.kt
index 53c59e4..44262c6 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/Outputs.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/Outputs.kt
@@ -117,7 +117,6 @@
*/
fun writeFile(
fileName: String,
- reportKey: String,
reportOnRunEndOnly: Boolean = false,
block: (file: File) -> Unit,
): String {
@@ -143,7 +142,7 @@
}
InstrumentationResults.reportAdditionalFileToCopy(
- key = reportKey,
+ key = sanitizedName,
absoluteFilePath = destination.absolutePath,
reportOnRunEndOnly = reportOnRunEndOnly
)
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/Profiler.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/Profiler.kt
index 19d47fc..1a7b9308 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/Profiler.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/Profiler.kt
@@ -293,8 +293,7 @@
override fun stop() {
session!!.stopRecording()
Outputs.writeFile(
- fileName = outputRelativePath!!,
- reportKey = "simpleperf_trace"
+ fileName = outputRelativePath!!
) {
session!!.convertSimpleperfOutputToProto("simpleperf.data", it.absolutePath)
}
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/ResultWriter.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/ResultWriter.kt
index 873ead3..03c8c81 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/ResultWriter.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/ResultWriter.kt
@@ -43,7 +43,6 @@
Outputs.writeFile(
fileName = "$packageName-benchmarkData.json",
- reportKey = "results_json",
reportOnRunEndOnly = true
) {
Log.d(
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoCaptureWrapper.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoCaptureWrapper.kt
index 5eaa17c..22529eb 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoCaptureWrapper.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoCaptureWrapper.kt
@@ -76,8 +76,7 @@
@RequiresApi(23)
private fun stop(traceLabel: String): String {
return Outputs.writeFile(
- fileName = "${traceLabel}_${dateToFileName()}.perfetto-trace",
- reportKey = "perfetto_trace_$traceLabel"
+ fileName = "${traceLabel}_${dateToFileName()}.perfetto-trace"
) {
capture!!.stop(it.absolutePath)
if (Outputs.forceFilesForShellAccessible) {
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoHelper.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoHelper.kt
index b4fdbc1..6c67eab 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoHelper.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoHelper.kt
@@ -421,8 +421,6 @@
// supported by a device. That is why we need to search from most specific to
// least specific. For e.g. emulators claim to support aarch64, when in reality
// they can only support x86 or x86_64.
- // Note: Cuttlefish is x86 but claims support for x86_64
- Build.MODEL.contains("Cuttlefish") -> "x86" // TODO(204892353): handle properly
Build.SUPPORTED_64_BIT_ABIS.any { it.startsWith("x86_64") } -> "x86_64"
Build.SUPPORTED_32_BIT_ABIS.any { it.startsWith("x86") } -> "x86"
Build.SUPPORTED_64_BIT_ABIS.any { it.startsWith("arm64") } -> "aarch64"
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/BaselineProfiles.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/BaselineProfiles.kt
index bb55014..1514db6 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/BaselineProfiles.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/BaselineProfiles.kt
@@ -190,23 +190,21 @@
// Write a file with a timestamp to be able to disambiguate between runs with the same
// unique name.
- val (fileName, reportKey, tsFileName) =
+ val (fileName, tsFileName) =
if (includeInStartupProfile && Arguments.enableStartupProfiles) {
arrayOf(
"$uniqueFilePrefix-startup-prof.txt",
- "startup-profile",
"$uniqueFilePrefix-startup-prof-${Outputs.dateToFileName()}.txt"
)
} else {
arrayOf(
"$uniqueFilePrefix-baseline-prof.txt",
- "baseline-profile",
"$uniqueFilePrefix-baseline-prof-${Outputs.dateToFileName()}.txt"
)
}
- val absolutePath = Outputs.writeFile(fileName, reportKey) { it.writeText(profile) }
- val tsAbsolutePath = Outputs.writeFile(tsFileName, "baseline-profile-ts") {
+ val absolutePath = Outputs.writeFile(fileName) { it.writeText(profile) }
+ val tsAbsolutePath = Outputs.writeFile(tsFileName) {
Log.d(TAG, "Pull Baseline Profile with: `adb pull \"${it.absolutePath}\" .`")
it.writeText(profile)
}
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/MacrobenchmarkScope.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/MacrobenchmarkScope.kt
index 9b141c6..5990a47 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/MacrobenchmarkScope.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/MacrobenchmarkScope.kt
@@ -332,7 +332,7 @@
// Staging location before we write it again using Outputs.writeFile(...)
Shell.executeScriptSilent("cp '$tracePath' '$stagingFile'")
// Report(
- val outputPath = Outputs.writeFile(fileName, fileName) {
+ val outputPath = Outputs.writeFile(fileName) {
Log.d(TAG, "Writing method traces to ${it.absolutePath}")
stagingFile.copyTo(it, overwrite = true)
// Cleanup
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/MethodTracing.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/MethodTracing.kt
index f77efc40e..790413a 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/MethodTracing.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/MethodTracing.kt
@@ -60,7 +60,7 @@
val stagingPath = "${Outputs.dirUsableByAppAndShell}/_$fileName"
Shell.executeScriptSilent("cp '$sourcePath' '$stagingPath'")
// Report
- Outputs.writeFile(fileName, fileName) {
+ Outputs.writeFile(fileName) {
val staging = File(stagingPath)
// No need to clean up, here because the clean up happens automatically on subsequent
// test runs.
diff --git a/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricAdvertiseTest.kt b/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricAdvertiseTest.kt
index 8c6a2e1..f27a1e5 100644
--- a/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricAdvertiseTest.kt
+++ b/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricAdvertiseTest.kt
@@ -34,7 +34,7 @@
@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
class RobolectricAdvertiseTest {
private val context: Context = RuntimeEnvironment.getApplication()
- private var bluetoothLe = BluetoothLe(context)
+ private var bluetoothLe = BluetoothLe.getInstance(context)
@Test
fun advertiseSuccess() = runTest {
diff --git a/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricGattClientTest.kt b/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricGattClientTest.kt
index ac2372c..ab35eda 100644
--- a/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricGattClientTest.kt
+++ b/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricGattClientTest.kt
@@ -39,6 +39,7 @@
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runTest
+import org.junit.After
import org.junit.Assert
import org.junit.Assert.assertTrue
import org.junit.Before
@@ -98,11 +99,16 @@
@Before
fun setUp() {
- bluetoothLe = BluetoothLe(context)
+ bluetoothLe = BluetoothLe.getInstance(context)
clientAdapter = StubClientFrameworkAdapter(bluetoothLe.client.fwkAdapter)
bluetoothLe.client.fwkAdapter = clientAdapter
}
+ @After
+ fun tearDown() {
+ bluetoothLe.client.fwkAdapter = clientAdapter.baseAdapter
+ }
+
@Test
fun connectGatt() = runTest {
val device = createDevice("00:11:22:33:44:55")
@@ -366,7 +372,7 @@
}
class StubClientFrameworkAdapter(
- private val baseAdapter: GattClient.FrameworkAdapter
+ internal val baseAdapter: GattClient.FrameworkAdapter
) : GattClient.FrameworkAdapter {
var gattServices: List<FwkService> = listOf()
var callback: BluetoothGattCallback? = null
diff --git a/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricGattServerTest.kt b/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricGattServerTest.kt
index 741377c..07e8583 100644
--- a/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricGattServerTest.kt
+++ b/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricGattServerTest.kt
@@ -41,6 +41,7 @@
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
+import org.junit.After
import org.junit.Assert
import org.junit.Before
import org.junit.Test
@@ -90,11 +91,16 @@
@Before
fun setUp() {
- bluetoothLe = BluetoothLe(context)
+ bluetoothLe = BluetoothLe.getInstance(context)
serverAdapter = StubServerFrameworkAdapter(bluetoothLe.server.fwkAdapter)
bluetoothLe.server.fwkAdapter = serverAdapter
}
+ @After
+ fun tearDown() {
+ bluetoothLe.server.fwkAdapter = serverAdapter.baseAdapter
+ }
+
@Test
fun openGattServer() = runTest {
val device = createDevice("00:11:22:33:44:55")
@@ -498,7 +504,7 @@
}
class StubServerFrameworkAdapter(
- private val baseAdapter: GattServer.FrameworkAdapter
+ val baseAdapter: GattServer.FrameworkAdapter
) : GattServer.FrameworkAdapter {
val shadowGattServer: ShadowBluetoothGattServer
get() = shadowOf(gattServer)
diff --git a/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricScanTest.kt b/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricScanTest.kt
index 189873a..a983408 100644
--- a/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricScanTest.kt
+++ b/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricScanTest.kt
@@ -39,7 +39,7 @@
@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
class RobolectricScanTest {
private val context: Context = RuntimeEnvironment.getApplication()
- private var bluetoothLe = BluetoothLe(context)
+ private var bluetoothLe = BluetoothLe.getInstance(context)
private companion object {
private const val TIMEOUT_MS: Long = 2_000
}
diff --git a/bluetooth/bluetooth/api/current.txt b/bluetooth/bluetooth/api/current.txt
index 004a624..2618c72 100644
--- a/bluetooth/bluetooth/api/current.txt
+++ b/bluetooth/bluetooth/api/current.txt
@@ -61,11 +61,16 @@
}
public final class BluetoothLe {
- ctor public BluetoothLe(android.content.Context context);
method @RequiresPermission("android.permission.BLUETOOTH_ADVERTISE") public suspend Object? advertise(androidx.bluetooth.AdvertiseParams advertiseParams, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?>? block, optional kotlin.coroutines.Continuation<? super kotlin.Unit>);
method @RequiresPermission("android.permission.BLUETOOTH_CONNECT") public suspend <R> Object? connectGatt(androidx.bluetooth.BluetoothDevice device, kotlin.jvm.functions.Function2<? super androidx.bluetooth.BluetoothLe.GattClientScope,? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super R>);
+ method public static androidx.bluetooth.BluetoothLe getInstance(android.content.Context context);
method public suspend <R> Object? openGattServer(java.util.List<androidx.bluetooth.GattService> services, kotlin.jvm.functions.Function2<? super androidx.bluetooth.BluetoothLe.GattServerConnectScope,? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super R>);
method @RequiresPermission("android.permission.BLUETOOTH_SCAN") public kotlinx.coroutines.flow.Flow<androidx.bluetooth.ScanResult> scan(optional java.util.List<androidx.bluetooth.ScanFilter> filters);
+ field public static final androidx.bluetooth.BluetoothLe.Companion Companion;
+ }
+
+ public static final class BluetoothLe.Companion {
+ method public androidx.bluetooth.BluetoothLe getInstance(android.content.Context context);
}
public static interface BluetoothLe.GattClientScope {
diff --git a/bluetooth/bluetooth/api/restricted_current.txt b/bluetooth/bluetooth/api/restricted_current.txt
index 004a624..2618c72 100644
--- a/bluetooth/bluetooth/api/restricted_current.txt
+++ b/bluetooth/bluetooth/api/restricted_current.txt
@@ -61,11 +61,16 @@
}
public final class BluetoothLe {
- ctor public BluetoothLe(android.content.Context context);
method @RequiresPermission("android.permission.BLUETOOTH_ADVERTISE") public suspend Object? advertise(androidx.bluetooth.AdvertiseParams advertiseParams, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?>? block, optional kotlin.coroutines.Continuation<? super kotlin.Unit>);
method @RequiresPermission("android.permission.BLUETOOTH_CONNECT") public suspend <R> Object? connectGatt(androidx.bluetooth.BluetoothDevice device, kotlin.jvm.functions.Function2<? super androidx.bluetooth.BluetoothLe.GattClientScope,? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super R>);
+ method public static androidx.bluetooth.BluetoothLe getInstance(android.content.Context context);
method public suspend <R> Object? openGattServer(java.util.List<androidx.bluetooth.GattService> services, kotlin.jvm.functions.Function2<? super androidx.bluetooth.BluetoothLe.GattServerConnectScope,? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super R>);
method @RequiresPermission("android.permission.BLUETOOTH_SCAN") public kotlinx.coroutines.flow.Flow<androidx.bluetooth.ScanResult> scan(optional java.util.List<androidx.bluetooth.ScanFilter> filters);
+ field public static final androidx.bluetooth.BluetoothLe.Companion Companion;
+ }
+
+ public static final class BluetoothLe.Companion {
+ method public androidx.bluetooth.BluetoothLe getInstance(android.content.Context context);
}
public static interface BluetoothLe.GattClientScope {
diff --git a/bluetooth/bluetooth/src/androidTest/java/androidx/bluetooth/BluetoothLeTest.kt b/bluetooth/bluetooth/src/androidTest/java/androidx/bluetooth/BluetoothLeTest.kt
index 822fd61..8e1f074 100644
--- a/bluetooth/bluetooth/src/androidTest/java/androidx/bluetooth/BluetoothLeTest.kt
+++ b/bluetooth/bluetooth/src/androidTest/java/androidx/bluetooth/BluetoothLeTest.kt
@@ -59,7 +59,7 @@
context = ApplicationProvider.getApplicationContext()
bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
bluetoothAdapter = bluetoothManager.adapter
- bluetoothLe = BluetoothLe(context)
+ bluetoothLe = BluetoothLe.getInstance(context)
Assume.assumeNotNull(bluetoothAdapter)
Assume.assumeTrue(bluetoothAdapter.isEnabled)
diff --git a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothLe.kt b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothLe.kt
index 24d6dd1..5b34edc 100644
--- a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothLe.kt
+++ b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothLe.kt
@@ -49,10 +49,20 @@
* Entry point for BLE related operations. This class provides a way to perform Bluetooth LE
* operations such as scanning, advertising, and connection with a respective [BluetoothDevice].
*/
-class BluetoothLe constructor(private val context: Context) {
+class BluetoothLe private constructor(private val context: Context) {
- private companion object {
+ companion object {
private const val TAG = "BluetoothLe"
+ @Volatile
+ @JvmStatic
+ private var instance: BluetoothLe? = null
+
+ @Suppress("VisiblySynchronized")
+ @JvmStatic
+ fun getInstance(context: Context) =
+ instance ?: synchronized(this) {
+ instance ?: BluetoothLe(context.applicationContext).also { instance = it }
+ }
}
@RequiresApi(34)
diff --git a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/advertiser/AdvertiserFragment.kt b/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/advertiser/AdvertiserFragment.kt
index 3e1623b..a8b1eb1 100644
--- a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/advertiser/AdvertiserFragment.kt
+++ b/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/advertiser/AdvertiserFragment.kt
@@ -136,7 +136,7 @@
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
- bluetoothLe = BluetoothLe(requireContext())
+ bluetoothLe = BluetoothLe.getInstance(requireContext())
_binding = FragmentAdvertiserBinding.inflate(inflater, container, false)
diff --git a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/scanner/ScannerFragment.kt b/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/scanner/ScannerFragment.kt
index acdb648..b65bbde 100644
--- a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/scanner/ScannerFragment.kt
+++ b/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/scanner/ScannerFragment.kt
@@ -140,7 +140,7 @@
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- bluetoothLe = BluetoothLe(requireContext())
+ bluetoothLe = BluetoothLe.getInstance(requireContext())
binding.tabLayout.addOnTabSelectedListener(onTabSelectedListener)
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
index 73b8580..c49ae6d 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
@@ -792,14 +792,20 @@
File(project.buildDir, "../nativeBuildStaging")
}
+ @Suppress("UnstableApiUsage") // finalizeDsl, minCompileSdkExtension
private fun LibraryExtension.configureAndroidLibraryOptions(
project: Project,
androidXExtension: AndroidXExtension
) {
- // Note, this should really match COMPILE_SDK_VERSION, however
- // this API takes an integer and we are unable to set it to a
- // pre-release SDK.
- defaultConfig.aarMetadata.minCompileSdk = project.defaultAndroidConfig.targetSdk
+ // Propagate the compileSdk value into minCompileSdk. Don't propagate compileSdkExtension,
+ // since only one library actually depends on the extension APIs and they can explicitly
+ // declare that in their build.gradle. Note that when we're using a preview SDK, the value
+ // for compileSdk will be null and the resulting AAR metadata won't have a minCompileSdk --
+ // this is okay because AGP automatically embeds forceCompileSdkPreview in the AAR metadata
+ // and uses it instead of minCompileSdk.
+ project.extensions.findByType<LibraryAndroidComponentsExtension>()!!.finalizeDsl {
+ it.defaultConfig.aarMetadata.minCompileSdk = it.compileSdk
+ }
// The full Guava artifact is very large, so they split off a special artifact containing a
// standalone version of the commonly-used ListenableFuture interface. However, they also
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt b/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt
index 5a881a2..a307c9d 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt
@@ -345,6 +345,9 @@
disable.add("LintError")
}
+ // Disable a check that's only relevant for apps that ship to Play Store. (b/299278101)
+ disable.add("ExpiredTargetSdkVersion")
+
// Reenable after b/238892319 is resolved
disable.add("NotificationPermission")
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/MavenUploadHelper.kt b/buildSrc/private/src/main/kotlin/androidx/build/MavenUploadHelper.kt
index cd60cc3..9380bb9 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/MavenUploadHelper.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/MavenUploadHelper.kt
@@ -443,6 +443,9 @@
scm.url.set("https://cs.android.com/androidx/platform/frameworks/support")
scm.connection.set(ANDROID_GIT_URL)
}
+ pom.organization { org ->
+ org.name.set("The Android Open Source Project")
+ }
pom.developers { devs ->
devs.developer { dev -> dev.name.set("The Android Open Source Project") }
}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/dackka/DackkaTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/dackka/DackkaTask.kt
index c7006f8..144ed14 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/dackka/DackkaTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/dackka/DackkaTask.kt
@@ -34,6 +34,7 @@
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
@@ -49,6 +50,8 @@
constructor(private val workerExecutor: WorkerExecutor, private val objects: ObjectFactory) :
DefaultTask() {
+ @Internal lateinit var argsJsonFile: File
+
@get:[InputFiles PathSensitive(PathSensitivity.RELATIVE)]
abstract val projectStructureMetadataFile: RegularFileProperty
@@ -220,10 +223,8 @@
)
val json = gson.toJson(jsonMap)
- val outputFile = File.createTempFile("dackkaArgs", ".json")
- outputFile.deleteOnExit()
- outputFile.writeText(json)
- return outputFile
+ argsJsonFile.writeText(json)
+ return argsJsonFile
}
/**
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt
index e39904c2..e6ccb06 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt
@@ -487,6 +487,10 @@
val dackkaTask =
project.tasks.register("docs", DackkaTask::class.java) { task ->
var taskStartTime: LocalDateTime? = null
+ task.argsJsonFile = File(
+ project.rootProject.getDistributionDirectory(),
+ "dackkaArgs-${project.name}.json"
+ )
task.apply {
dependsOn(unzipJvmSourcesTask)
dependsOn(unzipSamplesTask)
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraSurfaceAdapter.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraSurfaceAdapter.kt
index e8b59257..72d3930 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraSurfaceAdapter.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraSurfaceAdapter.kt
@@ -122,6 +122,7 @@
* @param newUseCaseConfigsSupportedSizeMap map of configurations of the use cases to the
* supported sizes list that will be given a
* suggested stream specification
+ * @param isPreviewStabilizationOn whether the preview stabilization is enabled.
* @return map of suggested stream specifications for given use cases
* @throws IllegalArgumentException if {@code newUseCaseConfigs} is an empty list, if
* there isn't a supported combination of surfaces
@@ -132,7 +133,8 @@
cameraMode: Int,
cameraId: String,
existingSurfaces: List<AttachedSurfaceInfo>,
- newUseCaseConfigsSupportedSizeMap: Map<UseCaseConfig<*>, List<Size>>
+ newUseCaseConfigsSupportedSizeMap: Map<UseCaseConfig<*>, List<Size>>,
+ isPreviewStabilizationOn: Boolean
): Pair<Map<UseCaseConfig<*>, StreamSpec>, Map<AttachedSurfaceInfo, StreamSpec>> {
if (!checkIfSupportedCombinationExist(cameraId)) {
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraMetadata.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraMetadata.kt
index ec0be0e..7274b3b 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraMetadata.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraMetadata.kt
@@ -36,8 +36,7 @@
* This allows all fields to be accessed and return reasonable values on all OS versions.
*/
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-internal class Camera2CameraMetadata
-constructor(
+internal class Camera2CameraMetadata(
override val camera: CameraId,
override val isRedacted: Boolean,
private val characteristics: CameraCharacteristics,
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2MetadataCache.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2MetadataCache.kt
index e7187fe..ba3953d7 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2MetadataCache.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2MetadataCache.kt
@@ -17,7 +17,9 @@
package androidx.camera.camera2.pipe.compat
import android.content.Context
+import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CameraManager
+import android.os.Build
import android.util.ArrayMap
import androidx.annotation.GuardedBy
import androidx.annotation.RequiresApi
@@ -105,7 +107,13 @@
// Merge the camera specific and global cache blocklists together.
// this will prevent these values from being cached after first access.
- val cameraBlocklist = cameraMetadataConfig.cameraCacheBlocklist[cameraId]
+ val cameraBlocklist =
+ if (shouldBlockSensorOrientationCache(characteristics)) {
+ (cameraMetadataConfig.cameraCacheBlocklist[cameraId] ?: emptySet()) +
+ CameraCharacteristics.SENSOR_ORIENTATION
+ } else {
+ cameraMetadataConfig.cameraCacheBlocklist[cameraId]
+ }
val cacheBlocklist =
if (cameraBlocklist == null) {
cameraMetadataConfig.cacheBlocklist
@@ -146,4 +154,9 @@
}
private fun isMetadataRedacted(): Boolean = !permissions.hasCameraPermission
+
+ private fun shouldBlockSensorOrientationCache(characteristics: CameraCharacteristics): Boolean {
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.S_V2 &&
+ characteristics[CameraCharacteristics.INFO_DEVICE_STATE_SENSOR_ORIENTATION_MAP] != null
+ }
}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraImpl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraImpl.java
index 78ff31d..5422996 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraImpl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraImpl.java
@@ -1122,7 +1122,7 @@
try {
mSupportedSurfaceCombination.getSuggestedStreamSpecifications(cameraMode,
- attachedSurfaces, useCaseConfigToSizeMap);
+ attachedSurfaces, useCaseConfigToSizeMap, false);
} catch (IllegalArgumentException e) {
debugLog("Surface combination with metering repeating not supported!", e);
return false;
@@ -1480,7 +1480,11 @@
}
Logger.e(TAG, "Unable to configure camera " + Camera2CameraImpl.this, t);
- resetCaptureSession(/*abortInFlightCaptures=*/false);
+
+ // Reset capture session if the latest capture session fails to open.
+ if (mCaptureSession == captureSession) {
+ resetCaptureSession(/*abortInFlightCaptures=*/false);
+ }
}
}, mExecutor);
}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraInfoImpl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraInfoImpl.java
index 3729cf8..e6da980 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraInfoImpl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraInfoImpl.java
@@ -16,10 +16,12 @@
package androidx.camera.camera2.internal;
+import static android.hardware.camera2.CameraCharacteristics.CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES;
+import static android.hardware.camera2.CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_ON;
+import static android.hardware.camera2.CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION;
import static android.hardware.camera2.CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING;
import static android.hardware.camera2.CameraMetadata.SENSOR_INFO_TIMESTAMP_SOURCE_REALTIME;
import static android.hardware.camera2.CameraMetadata.SENSOR_INFO_TIMESTAMP_SOURCE_UNKNOWN;
-
import static androidx.camera.camera2.internal.ZslUtil.isCapabilitySupported;
import android.hardware.camera2.CameraCharacteristics;
@@ -505,6 +507,36 @@
}
}
+ @Override
+ public boolean isVideoStabilizationSupported() {
+ int[] availableVideoStabilizationModes =
+ mCameraCharacteristicsCompat.get(
+ CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES);
+ if (availableVideoStabilizationModes != null) {
+ for (int mode : availableVideoStabilizationModes) {
+ if (mode == CONTROL_VIDEO_STABILIZATION_MODE_ON) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isPreviewStabilizationSupported() {
+ int[] availableVideoStabilizationModes =
+ mCameraCharacteristicsCompat.get(
+ CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES);
+ if (availableVideoStabilizationModes != null) {
+ for (int mode : availableVideoStabilizationModes) {
+ if (mode == CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
/**
* Gets the implementation of {@link Camera2CameraInfo}.
*/
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2DeviceSurfaceManager.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2DeviceSurfaceManager.java
index 7c1d95e..1322af7 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2DeviceSurfaceManager.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2DeviceSurfaceManager.java
@@ -171,7 +171,8 @@
@CameraMode.Mode int cameraMode,
@NonNull String cameraId,
@NonNull List<AttachedSurfaceInfo> existingSurfaces,
- @NonNull Map<UseCaseConfig<?>, List<Size>> newUseCaseConfigsSupportedSizeMap) {
+ @NonNull Map<UseCaseConfig<?>, List<Size>> newUseCaseConfigsSupportedSizeMap,
+ boolean isPreviewStabilizationOn) {
Preconditions.checkArgument(!newUseCaseConfigsSupportedSizeMap.isEmpty(),
"No new use cases to be bound.");
@@ -186,6 +187,7 @@
return supportedSurfaceCombination.getSuggestedStreamSpecifications(
cameraMode,
existingSurfaces,
- newUseCaseConfigsSupportedSizeMap);
+ newUseCaseConfigsSupportedSizeMap,
+ isPreviewStabilizationOn);
}
}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/GuaranteedConfigurationsUtil.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/GuaranteedConfigurationsUtil.java
index d18bd1b..5a4413f 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/GuaranteedConfigurationsUtil.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/GuaranteedConfigurationsUtil.java
@@ -833,6 +833,93 @@
}
/**
+ * Returns the supported stream combinations for preview stabilization.
+ */
+ @RequiresApi(api = Build.VERSION_CODES.TIRAMISU)
+ @NonNull
+ public static List<SurfaceCombination> getPreviewStabilizationSupportedCombinationList() {
+ List<SurfaceCombination> combinationList = new ArrayList<>();
+
+ // (PRIV, s1440p)
+ SurfaceCombination surfaceCombination1 = new SurfaceCombination();
+ surfaceCombination1.addSurfaceConfig(
+ SurfaceConfig.create(ConfigType.PRIV, ConfigSize.s1440p));
+ combinationList.add(surfaceCombination1);
+
+ // (YUV, s1440p)
+ SurfaceCombination surfaceCombination2 = new SurfaceCombination();
+ surfaceCombination2.addSurfaceConfig(
+ SurfaceConfig.create(ConfigType.YUV, ConfigSize.s1440p));
+ combinationList.add(surfaceCombination2);
+
+ // (PRIV, s1440p) + (JPEG, MAXIMUM)
+ SurfaceCombination surfaceCombination3 = new SurfaceCombination();
+ surfaceCombination3.addSurfaceConfig(
+ SurfaceConfig.create(ConfigType.PRIV, ConfigSize.s1440p));
+ surfaceCombination3.addSurfaceConfig(
+ SurfaceConfig.create(ConfigType.JPEG, ConfigSize.MAXIMUM));
+ combinationList.add(surfaceCombination3);
+
+ // (YUV, s1440p) + (JPEG, MAXIMUM)
+ SurfaceCombination surfaceCombination4 = new SurfaceCombination();
+ surfaceCombination4.addSurfaceConfig(
+ SurfaceConfig.create(ConfigType.YUV, ConfigSize.s1440p));
+ surfaceCombination4.addSurfaceConfig(
+ SurfaceConfig.create(ConfigType.JPEG, ConfigSize.MAXIMUM));
+ combinationList.add(surfaceCombination4);
+
+ // (PRIV, s1440p) + (YUV, MAXIMUM)
+ SurfaceCombination surfaceCombination5 = new SurfaceCombination();
+ surfaceCombination5.addSurfaceConfig(
+ SurfaceConfig.create(ConfigType.PRIV, ConfigSize.s1440p));
+ surfaceCombination5.addSurfaceConfig(
+ SurfaceConfig.create(ConfigType.YUV, ConfigSize.MAXIMUM));
+ combinationList.add(surfaceCombination5);
+
+ // (YUV, s1440p) + (YUV, MAXIMUM)
+ SurfaceCombination surfaceCombination6 = new SurfaceCombination();
+ surfaceCombination6.addSurfaceConfig(
+ SurfaceConfig.create(ConfigType.YUV, ConfigSize.s1440p));
+ surfaceCombination6.addSurfaceConfig(
+ SurfaceConfig.create(ConfigType.YUV, ConfigSize.MAXIMUM));
+ combinationList.add(surfaceCombination6);
+
+ // (PRIV, PREVIEW) + (PRIV, s1440)
+ SurfaceCombination surfaceCombination7 = new SurfaceCombination();
+ surfaceCombination7.addSurfaceConfig(
+ SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
+ surfaceCombination7.addSurfaceConfig(
+ SurfaceConfig.create(ConfigType.PRIV, ConfigSize.s1440p));
+ combinationList.add(surfaceCombination7);
+
+ // (YUV, PREVIEW) + (PRIV, s1440)
+ SurfaceCombination surfaceCombination8 = new SurfaceCombination();
+ surfaceCombination8.addSurfaceConfig(
+ SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
+ surfaceCombination8.addSurfaceConfig(
+ SurfaceConfig.create(ConfigType.PRIV, ConfigSize.s1440p));
+ combinationList.add(surfaceCombination8);
+
+ // (PRIV, PREVIEW) + (YUV, s1440)
+ SurfaceCombination surfaceCombination9 = new SurfaceCombination();
+ surfaceCombination9.addSurfaceConfig(
+ SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
+ surfaceCombination9.addSurfaceConfig(
+ SurfaceConfig.create(ConfigType.YUV, ConfigSize.s1440p));
+ combinationList.add(surfaceCombination9);
+
+ // (YUV, PREVIEW) + (YUV, s1440)
+ SurfaceCombination surfaceCombination10 = new SurfaceCombination();
+ surfaceCombination10.addSurfaceConfig(
+ SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
+ surfaceCombination10.addSurfaceConfig(
+ SurfaceConfig.create(ConfigType.YUV, ConfigSize.s1440p));
+ combinationList.add(surfaceCombination10);
+
+ return combinationList;
+ }
+
+ /**
* Returns the supported stream combinations based on the hardware level and capabilities of
* the device.
*/
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ProcessingCaptureSession.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ProcessingCaptureSession.java
index 9b14123..e89c415 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ProcessingCaptureSession.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ProcessingCaptureSession.java
@@ -452,7 +452,12 @@
switch (mProcessorState) {
case ON_CAPTURE_SESSION_ENDED:
case SESSION_INITIALIZED:
- future.addListener(() -> mSessionProcessor.deInitSession(), mExecutor);
+ future.addListener(() -> {
+ Logger.d(TAG, "== deInitSession (id=" + mInstanceId + ")");
+ mSessionProcessor.deInitSession();
+ // Use direct executor to ensure deInitSession is invoked as soon as session is
+ // closed.
+ }, CameraXExecutors.directExecutor());
break;
default:
break;
@@ -478,6 +483,7 @@
}
mRequestProcessor = new Camera2RequestProcessor(captureSession,
getSessionProcessorSurfaceList(mProcessorSessionConfig.getSurfaces()));
+ Logger.d(TAG, "== onCaptureSessinStarted (id = " + mInstanceId + ")");
mSessionProcessor.onCaptureSessionStart(mRequestProcessor);
mProcessorState = ProcessorState.ON_CAPTURE_SESSION_STARTED;
@@ -534,6 +540,7 @@
Logger.d(TAG, "close (id=" + mInstanceId + ") state=" + mProcessorState);
if (mProcessorState == ProcessorState.ON_CAPTURE_SESSION_STARTED) {
+ Logger.d(TAG, "== onCaptureSessionEnd (id = " + mInstanceId + ")");
mSessionProcessor.onCaptureSessionEnd();
if (mRequestProcessor != null) {
mRequestProcessor.close();
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedSurfaceCombination.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedSurfaceCombination.java
index cd453ff..a6d9b50 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedSurfaceCombination.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedSurfaceCombination.java
@@ -18,7 +18,6 @@
import static android.content.pm.PackageManager.FEATURE_CAMERA_CONCURRENT;
import static android.hardware.camera2.CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES;
-
import static androidx.camera.core.internal.utils.SizeUtil.RESOLUTION_1080P;
import static androidx.camera.core.internal.utils.SizeUtil.RESOLUTION_480P;
import static androidx.camera.core.internal.utils.SizeUtil.RESOLUTION_VGA;
@@ -95,6 +94,8 @@
private final List<SurfaceCombination> mSurfaceCombinations = new ArrayList<>();
private final List<SurfaceCombination> mUltraHighSurfaceCombinations = new ArrayList<>();
private final List<SurfaceCombination> mConcurrentSurfaceCombinations = new ArrayList<>();
+ private final List<SurfaceCombination> mPreviewStabilizationSurfaceCombinations =
+ new ArrayList<>();
private final Map<FeatureSettings, List<SurfaceCombination>>
mFeatureSettingsToSupportedCombinationsMap = new HashMap<>();
private final List<SurfaceCombination> mSurfaceCombinations10Bit = new ArrayList<>();
@@ -110,6 +111,7 @@
private boolean mIsConcurrentCameraModeSupported = false;
private boolean mIsStreamUseCaseSupported = false;
private boolean mIsUltraHighResolutionSensorSupported = false;
+ private boolean mIsPreviewStabilizationSupported = false;
@VisibleForTesting
SurfaceSizeDefinition mSurfaceSizeDefinition;
List<Integer> mSurfaceSizeDefinitionFormats = new ArrayList<>();
@@ -184,6 +186,12 @@
generateStreamUseCaseSupportedCombinationList();
}
+ mIsPreviewStabilizationSupported =
+ VideoStabilizationUtil.isPreviewStabilizationSupported(mCharacteristics);
+ if (mIsPreviewStabilizationSupported) {
+ generatePreviewStabilizationSupportedCombinationList();
+ }
+
generateSurfaceSizeDefinition();
checkCustomization();
}
@@ -267,7 +275,8 @@
supportedSurfaceCombinations.addAll(mSurfaceCombinations);
break;
default:
- supportedSurfaceCombinations.addAll(mSurfaceCombinations);
+ supportedSurfaceCombinations.addAll(featureSettings.isPreviewStabilizationOn()
+ ? mPreviewStabilizationSurfaceCombinations : mSurfaceCombinations);
break;
}
} else if (featureSettings.getRequiredMaxBitDepth() == DynamicRange.BIT_DEPTH_10_BIT) {
@@ -521,6 +530,7 @@
* @param attachedSurfaces the existing surfaces.
* @param newUseCaseConfigsSupportedSizeMap newly added UseCaseConfig to supported output
* sizes map.
+ * @param isPreviewStabilizationOn whether the preview stabilization is enabled.
* @return the suggested stream specifications, which is a pair of mappings. The first
* mapping is from UseCaseConfig to the suggested stream specification representing new
* UseCases. The second mapping is from attachedSurfaceInfo to the suggested stream
@@ -536,7 +546,8 @@
getSuggestedStreamSpecifications(
@CameraMode.Mode int cameraMode,
@NonNull List<AttachedSurfaceInfo> attachedSurfaces,
- @NonNull Map<UseCaseConfig<?>, List<Size>> newUseCaseConfigsSupportedSizeMap) {
+ @NonNull Map<UseCaseConfig<?>, List<Size>> newUseCaseConfigsSupportedSizeMap,
+ boolean isPreviewStabilizationOn) {
// Refresh Preview Size based on current display configurations.
refreshPreviewSize();
List<SurfaceConfig> surfaceConfigs = new ArrayList<>();
@@ -553,7 +564,8 @@
mDynamicRangeResolver.resolveAndValidateDynamicRanges(attachedSurfaces,
newUseCaseConfigs, useCasesPriorityOrder);
int requiredMaxBitDepth = getRequiredMaxBitDepth(resolvedDynamicRanges);
- FeatureSettings featureSettings = FeatureSettings.of(cameraMode, requiredMaxBitDepth);
+ FeatureSettings featureSettings = FeatureSettings.of(cameraMode, requiredMaxBitDepth,
+ isPreviewStabilizationOn);
if (cameraMode != CameraMode.DEFAULT
&& requiredMaxBitDepth == DynamicRange.BIT_DEPTH_10_BIT) {
throw new IllegalArgumentException(String.format("No supported surface combination is "
@@ -1104,6 +1116,13 @@
}
}
+ private void generatePreviewStabilizationSupportedCombinationList() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ mPreviewStabilizationSurfaceCombinations.addAll(
+ GuaranteedConfigurationsUtil.getPreviewStabilizationSupportedCombinationList());
+ }
+ }
+
private void checkCustomization() {
// TODO(b/119466260): Integrate found feasible stream combinations into supported list
}
@@ -1334,9 +1353,10 @@
abstract static class FeatureSettings {
@NonNull
static FeatureSettings of(@CameraMode.Mode int cameraMode,
- @RequiredMaxBitDepth int requiredMaxBitDepth) {
+ @RequiredMaxBitDepth int requiredMaxBitDepth,
+ boolean isPreviewStabilizationOn) {
return new AutoValue_SupportedSurfaceCombination_FeatureSettings(
- cameraMode, requiredMaxBitDepth);
+ cameraMode, requiredMaxBitDepth, isPreviewStabilizationOn);
}
/**
@@ -1364,5 +1384,10 @@
* {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT}.
*/
abstract @RequiredMaxBitDepth int getRequiredMaxBitDepth();
+
+ /**
+ * Whether the preview stabilization is enabled.
+ */
+ abstract boolean isPreviewStabilizationOn();
}
}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/VideoStabilizationUtil.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/VideoStabilizationUtil.java
new file mode 100644
index 0000000..b9f5eeb
--- /dev/null
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/VideoStabilizationUtil.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.internal;
+
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CaptureRequest;
+import android.os.Build;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat;
+
+/**
+ * A class that contains utility methods for video stabilization.
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+public final class VideoStabilizationUtil {
+
+ private VideoStabilizationUtil() {
+ }
+
+ /**
+ * Return true if the given camera characteristics support preview stabilization.
+ */
+ public static boolean isPreviewStabilizationSupported(
+ @NonNull CameraCharacteristicsCompat characteristicsCompat) {
+ if (Build.VERSION.SDK_INT < 33) {
+ return false;
+ }
+ int[] availableVideoStabilizationModes = characteristicsCompat.get(
+ CameraCharacteristics.CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES);
+ if (availableVideoStabilizationModes == null
+ || availableVideoStabilizationModes.length == 0) {
+ return false;
+ }
+ for (int mode : availableVideoStabilizationModes) {
+ if (mode == CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2CameraInfoImplTest.java b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2CameraInfoImplTest.java
index a0447b4..5908b96 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2CameraInfoImplTest.java
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2CameraInfoImplTest.java
@@ -17,12 +17,12 @@
package androidx.camera.camera2.internal;
import static android.hardware.camera2.CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES;
-
+import static android.hardware.camera2.CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_OFF;
+import static android.hardware.camera2.CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_ON;
+import static android.hardware.camera2.CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION;
import static androidx.camera.core.DynamicRange.HLG_10_BIT;
import static androidx.camera.core.DynamicRange.SDR;
-
import static com.google.common.truth.Truth.assertThat;
-
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -678,6 +678,40 @@
assertThat(resultFpsRanges1).isEmpty();
}
+ /**
+ * Test for preview stabilization.
+ */
+ @Test
+ public void cameraInfo_isPreviewStabilizationSupported()
+ throws CameraAccessExceptionCompat {
+ init(/* hasAvailableCapabilities = */ false);
+
+ // Camera0
+ CameraInfo cameraInfo0 = new Camera2CameraInfoImpl(CAMERA0_ID,
+ mCameraManagerCompat);
+
+
+ if (Build.VERSION.SDK_INT >= 33) {
+ assertThat(cameraInfo0.isPreviewStabilizationSupported()).isTrue();
+ } else {
+ assertThat(cameraInfo0.isPreviewStabilizationSupported()).isFalse();
+ }
+ assertThat(cameraInfo0.isVideoStabilizationSupported()).isTrue();
+
+ // Camera1
+ CameraInfo cameraInfo1 = new Camera2CameraInfoImpl(CAMERA1_ID,
+ mCameraManagerCompat);
+
+ assertThat(cameraInfo1.isPreviewStabilizationSupported()).isFalse();
+ assertThat(cameraInfo0.isVideoStabilizationSupported()).isTrue();
+
+ // Camera2
+ CameraInfo cameraInfo2 = new Camera2CameraInfoImpl(CAMERA2_ID,
+ mCameraManagerCompat);
+ assertThat(cameraInfo2.isPreviewStabilizationSupported()).isFalse();
+ assertThat(cameraInfo2.isVideoStabilizationSupported()).isFalse();
+ }
+
@Test
public void cameraInfo_checkDefaultCameraIntrinsicZoomRatio()
throws CameraAccessExceptionCompat {
@@ -804,6 +838,24 @@
CAMERA0_DYNAMIC_RANGE_PROFILES);
}
+ // Add video stabilization modes
+ if (Build.VERSION.SDK_INT >= 33) {
+ shadowCharacteristics0.set(
+ CameraCharacteristics.CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES,
+ new int[] {
+ CONTROL_VIDEO_STABILIZATION_MODE_OFF,
+ CONTROL_VIDEO_STABILIZATION_MODE_ON,
+ CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION
+ });
+ } else {
+ shadowCharacteristics0.set(
+ CameraCharacteristics.CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES,
+ new int[] {
+ CONTROL_VIDEO_STABILIZATION_MODE_OFF,
+ CONTROL_VIDEO_STABILIZATION_MODE_ON
+ });
+ }
+
// Mock the request capability
if (hasAvailableCapabilities) {
shadowCharacteristics0.set(REQUEST_AVAILABLE_CAPABILITIES,
@@ -837,6 +889,14 @@
shadowCharacteristics1.set(
CameraCharacteristics.FLASH_INFO_AVAILABLE, CAMERA1_FLASH_INFO_BOOLEAN);
+ // Add video stabilization modes
+ shadowCharacteristics1.set(
+ CameraCharacteristics.CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES,
+ new int[] {
+ CONTROL_VIDEO_STABILIZATION_MODE_OFF,
+ CONTROL_VIDEO_STABILIZATION_MODE_ON
+ });
+
// Mock the supported resolutions
{
int formatPrivate = ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE;
@@ -887,6 +947,13 @@
CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES,
CAMERA2_AE_FPS_RANGES);
+ // Add video stabilization modes
+ shadowCharacteristics2.set(
+ CameraCharacteristics.CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES,
+ new int[] {
+ CONTROL_VIDEO_STABILIZATION_MODE_OFF
+ });
+
// Add the camera to the camera service
((ShadowCameraManager)
Shadow.extract(
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/StreamUseCaseTest.java b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/StreamUseCaseTest.java
index 1c83442..496663c 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/StreamUseCaseTest.java
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/StreamUseCaseTest.java
@@ -17,12 +17,10 @@
package androidx.camera.camera2.internal;
import static android.os.Build.VERSION.SDK_INT;
-
import static androidx.camera.camera2.internal.StreamUseCaseUtil.STREAM_USE_CASE_STREAM_SPEC_OPTION;
import static androidx.camera.core.DynamicRange.BIT_DEPTH_10_BIT;
import static androidx.camera.core.ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY;
import static androidx.camera.core.ImageCapture.CAPTURE_MODE_ZERO_SHUTTER_LAG;
-
import static junit.framework.TestCase.assertFalse;
import static junit.framework.TestCase.assertTrue;
@@ -198,14 +196,16 @@
public void shouldUseStreamUseCase_cameraModeNotSupported() {
assertFalse(StreamUseCaseUtil.shouldUseStreamUseCase(
SupportedSurfaceCombination.FeatureSettings.of(CameraMode.CONCURRENT_CAMERA,
- DynamicRange.BIT_DEPTH_8_BIT)));
+ DynamicRange.BIT_DEPTH_8_BIT,
+ false)));
}
@Test
public void shouldUseStreamUseCase_bitDepthNotSupported() {
assertFalse(StreamUseCaseUtil.shouldUseStreamUseCase(
SupportedSurfaceCombination.FeatureSettings.of(CameraMode.DEFAULT,
- BIT_DEPTH_10_BIT)));
+ BIT_DEPTH_10_BIT,
+ false)));
}
@Test
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedSurfaceCombinationTest.kt b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedSurfaceCombinationTest.kt
index c5d0844..2db0d98 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedSurfaceCombinationTest.kt
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedSurfaceCombinationTest.kt
@@ -212,7 +212,7 @@
GuaranteedConfigurationsUtil.getLegacySupportedCombinationList().forEach {
assertThat(
supportedSurfaceCombination.checkSupported(
- FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT),
+ FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT, false),
it.surfaceConfigList
)
).isTrue()
@@ -228,7 +228,7 @@
GuaranteedConfigurationsUtil.getLimitedSupportedCombinationList().forEach {
assertThat(
supportedSurfaceCombination.checkSupported(
- FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT),
+ FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT, false),
it.surfaceConfigList
)
).isFalse()
@@ -244,7 +244,7 @@
GuaranteedConfigurationsUtil.getFullSupportedCombinationList().forEach {
assertThat(
supportedSurfaceCombination.checkSupported(
- FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT),
+ FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT, false),
it.surfaceConfigList
)
).isFalse()
@@ -260,7 +260,7 @@
GuaranteedConfigurationsUtil.getLevel3SupportedCombinationList().forEach {
assertThat(
supportedSurfaceCombination.checkSupported(
- FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT),
+ FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT, false),
it.surfaceConfigList
)
).isFalse()
@@ -278,7 +278,7 @@
GuaranteedConfigurationsUtil.getLimitedSupportedCombinationList().forEach {
assertThat(
supportedSurfaceCombination.checkSupported(
- FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT),
+ FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT, false),
it.surfaceConfigList
)
).isTrue()
@@ -296,7 +296,7 @@
GuaranteedConfigurationsUtil.getFullSupportedCombinationList().forEach {
assertThat(
supportedSurfaceCombination.checkSupported(
- FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT),
+ FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT, false),
it.surfaceConfigList
)
).isFalse()
@@ -314,7 +314,7 @@
GuaranteedConfigurationsUtil.getLevel3SupportedCombinationList().forEach {
assertThat(
supportedSurfaceCombination.checkSupported(
- FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT),
+ FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT, false),
it.surfaceConfigList
)
).isFalse()
@@ -332,7 +332,7 @@
GuaranteedConfigurationsUtil.getFullSupportedCombinationList().forEach {
assertThat(
supportedSurfaceCombination.checkSupported(
- FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT),
+ FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT, false),
it.surfaceConfigList
)
).isTrue()
@@ -350,7 +350,7 @@
GuaranteedConfigurationsUtil.getLevel3SupportedCombinationList().forEach {
assertThat(
supportedSurfaceCombination.checkSupported(
- FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT),
+ FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT, false),
it.surfaceConfigList
)
).isFalse()
@@ -369,7 +369,7 @@
GuaranteedConfigurationsUtil.getLimitedSupportedCombinationList().forEach {
assertThat(
supportedSurfaceCombination.checkSupported(
- FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT),
+ FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT, false),
it.surfaceConfigList
)
).isTrue()
@@ -388,7 +388,7 @@
GuaranteedConfigurationsUtil.getLegacySupportedCombinationList().forEach {
assertThat(
supportedSurfaceCombination.checkSupported(
- FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT),
+ FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT, false),
it.surfaceConfigList
)
).isTrue()
@@ -407,7 +407,7 @@
GuaranteedConfigurationsUtil.getFullSupportedCombinationList().forEach {
assertThat(
supportedSurfaceCombination.checkSupported(
- FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT),
+ FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT, false),
it.surfaceConfigList
)
).isTrue()
@@ -426,7 +426,7 @@
GuaranteedConfigurationsUtil.getRAWSupportedCombinationList().forEach {
assertThat(
supportedSurfaceCombination.checkSupported(
- FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT),
+ FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT, false),
it.surfaceConfigList
)
).isTrue()
@@ -444,7 +444,7 @@
GuaranteedConfigurationsUtil.getLevel3SupportedCombinationList().forEach {
assertThat(
supportedSurfaceCombination.checkSupported(
- FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT),
+ FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT, false),
it.surfaceConfigList
)
).isTrue()
@@ -465,7 +465,7 @@
GuaranteedConfigurationsUtil.getConcurrentSupportedCombinationList().forEach {
assertThat(
supportedSurfaceCombination.checkSupported(
- FeatureSettings.of(CameraMode.CONCURRENT_CAMERA, BIT_DEPTH_8_BIT),
+ FeatureSettings.of(CameraMode.CONCURRENT_CAMERA, BIT_DEPTH_8_BIT, false),
it.surfaceConfigList
)
).isTrue()
@@ -490,7 +490,7 @@
assertThat(
supportedSurfaceCombination.checkSupported(
FeatureSettings.of(
- CameraMode.ULTRA_HIGH_RESOLUTION_CAMERA, BIT_DEPTH_8_BIT
+ CameraMode.ULTRA_HIGH_RESOLUTION_CAMERA, BIT_DEPTH_8_BIT, false
),
it.surfaceConfigList
)
@@ -498,6 +498,25 @@
}
}
+ @Test
+ @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+ fun checkPreviewStabilizationSurfaceCombinationSupportedWhenEnabled() {
+ setupCameraAndInitCameraX(
+ hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3
+ )
+ val supportedSurfaceCombination = SupportedSurfaceCombination(
+ context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
+ )
+ GuaranteedConfigurationsUtil.getPreviewStabilizationSupportedCombinationList().forEach {
+ assertThat(
+ supportedSurfaceCombination.checkSupported(
+ FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT, true),
+ it.surfaceConfigList
+ )
+ ).isTrue()
+ }
+ }
+
// //////////////////////////////////////////////////////////////////////////////////////////
//
// Surface config transformation tests
@@ -1451,7 +1470,8 @@
val resultPair = supportedSurfaceCombination.getSuggestedStreamSpecifications(
cameraMode,
attachedSurfaceInfoList,
- useCaseConfigToOutputSizesMap
+ useCaseConfigToOutputSizesMap,
+ false
)
val suggestedStreamSpecsForNewUseCases = resultPair.first
val suggestedStreamSpecsForOldSurfaces = resultPair.second
@@ -1564,7 +1584,7 @@
GuaranteedConfigurationsUtil.get10BitSupportedCombinationList().forEach {
assertThat(
supportedSurfaceCombination.checkSupported(
- FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_10_BIT),
+ FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_10_BIT, false),
it.surfaceConfigList
)
).isTrue()
@@ -3327,6 +3347,19 @@
set(CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES, uc)
}
+ val vs: IntArray
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ vs = intArrayOf(
+ CameraCharacteristics.CONTROL_VIDEO_STABILIZATION_MODE_OFF,
+ CameraCharacteristics.CONTROL_VIDEO_STABILIZATION_MODE_ON,
+ CameraCharacteristics.CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION)
+ } else {
+ vs = intArrayOf(
+ CameraCharacteristics.CONTROL_VIDEO_STABILIZATION_MODE_OFF,
+ CameraCharacteristics.CONTROL_VIDEO_STABILIZATION_MODE_ON)
+ }
+ set(CameraCharacteristics.CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES, vs)
+
capabilities?.let {
set(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES, it)
}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/CameraInfo.java b/camera/camera-core/src/main/java/androidx/camera/core/CameraInfo.java
index 105bde1..8c0750e 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/CameraInfo.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/CameraInfo.java
@@ -298,6 +298,26 @@
}
/**
+ * Returns if video stabilization is supported on the device.
+ *
+ * @return true if supported, otherwise false.
+ */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ default boolean isVideoStabilizationSupported() {
+ return false;
+ }
+
+ /**
+ * Returns if preview stabilization is supported on the device.
+ *
+ * @return true if supported, otherwise false.
+ */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ default boolean isPreviewStabilizationSupported() {
+ return false;
+ }
+
+ /**
* Returns if {@link ImageFormat#PRIVATE} reprocessing is supported on the device.
*
* @return true if supported, otherwise false.
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraDeviceSurfaceManager.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraDeviceSurfaceManager.java
index 9abb314..540f454 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraDeviceSurfaceManager.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraDeviceSurfaceManager.java
@@ -83,6 +83,7 @@
* @param newUseCaseConfigsSupportedSizeMap map of configurations of the use cases to the
* supported output sizes list that will be given a
* suggested stream specification
+ * @param isPreviewStabilizationOn whether the preview stabilization is enabled.
* @return map of suggested stream specifications for given use cases
* @throws IllegalStateException if not initialized
* @throws IllegalArgumentException if {@code newUseCaseConfigs} is an empty list, if
@@ -96,5 +97,6 @@
@CameraMode.Mode int cameraMode,
@NonNull String cameraId,
@NonNull List<AttachedSurfaceInfo> existingSurfaces,
- @NonNull Map<UseCaseConfig<?>, List<Size>> newUseCaseConfigsSupportedSizeMap);
+ @NonNull Map<UseCaseConfig<?>, List<Size>> newUseCaseConfigsSupportedSizeMap,
+ boolean isPreviewStabilizationOn);
}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/Config.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/Config.java
index ab1314e..be3b3b8 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/Config.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/Config.java
@@ -46,8 +46,8 @@
* Returns whether this configuration contains the supplied option.
*
* @param id The {@link Option} to search for in this configuration.
- * @return <code>true</code> if this configuration contains the supplied option; <code>false
- * </code> otherwise.
+ * @return {@code true} if this configuration contains the supplied option; {@code false}
+ * otherwise.
*/
boolean containsOption(@NonNull Option<?> id);
@@ -77,7 +77,7 @@
* @param valueIfMissing The value to return if the specified {@link Option} does not exist in
* this configuration.
* @param <ValueT> The type for the value associated with the supplied {@link Option}.
- * @return The value stored in this configuration, or <code>valueIfMissing</code> if it does
+ * @return The value stored in this configuration, or {@code valueIfMissing} if it does
* not exist.
*/
@Nullable
@@ -116,12 +116,10 @@
* option such as \"<code>
* camerax.core.example</code>\".
* @param matcher A callback used to receive results of the search. Results will be
- * sent to
- * {@link OptionMatcher#onOptionMatched(Option)} in the order in which
- * they are found inside
- * this configuration. Subsequent results will continue to be sent as
- * long as {@link
- * OptionMatcher#onOptionMatched(Option)} returns <code>true</code>.
+ * sent to {@link OptionMatcher#onOptionMatched(Option)} in the order
+ * in which they are found inside this configuration. Subsequent
+ * results will continue to be sent as long as {@link
+ * OptionMatcher#onOptionMatched(Option)} returns {@code true}.
*/
void findOptions(@NonNull String idSearchString, @NonNull OptionMatcher matcher);
@@ -199,8 +197,7 @@
* @param valueClass The class of the value stored by this option.
* @param <T> The type of the value stored by this option.
* @param token An optional, type-erased object for storing more context for this
- * specific
- * option. Generally this object should have static scope and be
+ * specific option. Generally this object should have static scope and be
* immutable.
* @return An {@link Option} object which can be used to store/retrieve values from a {@link
* Config}.
@@ -250,11 +247,14 @@
*/
enum OptionPriority {
/**
- * Should only be used externally by apps. It takes precedence over any other option
- * values at the risk of causing unexpected behavior.
+ * It takes precedence over any other option values at the risk of causing unexpected
+ * behavior.
*
- * <p>This should not used internally in CameraX. It conflicts when merging different
- * values set to ALWAY_OVERRIDE.
+ * <p>If the same option is already set, the option with this priority will overwrite the
+ * value.
+ *
+ * <p>This priority should only be used to explicitly specify an option, such as used by
+ * {@code Camera2Interop} or {@code Camera2CameraControl}, and should be used with caution.
*/
ALWAYS_OVERRIDE,
@@ -262,15 +262,17 @@
* It's a required option value in order to achieve expected CameraX behavior. It takes
* precedence over {@link #OPTIONAL} option values.
*
- * <p>If apps set ALWAYS_OVERRIDE options, it'll override REQUIRED option values and can
- * potentially cause unexpected behaviors. It conflicts when merging different values set
- * to REQUIRED.
+ * <p>If two values are set to the same option, the value with {@link #ALWAYS_OVERRIDE}
+ * priority will overwrite this priority and can potentially cause unexpected behaviors.
+ *
+ * <p>If two values are set to the same option with this priority, it might indicate a
+ * programming error internally and an exception will be thrown when merging the configs.
*/
REQUIRED,
/**
* The lowest priority, it can be overridden by any other option value. When two option
- * values are set as OPTIONAL, the newer value takes precedence over the old one.
+ * values are set with this priority, the newer value takes precedence over the old one.
*/
OPTIONAL
}
@@ -278,35 +280,25 @@
/**
* Returns if values with these {@link OptionPriority} conflict or not.
*
- * Currently it is not allowed to have different values with same ALWAYS_OVERRIDE
- * priority or to have different values with same REQUIRED priority.
+ * <p>Currently it is not allowed the same option to have different values with priority
+ * {@link OptionPriority#REQUIRED}.
*/
static boolean hasConflict(@NonNull OptionPriority priority1,
@NonNull OptionPriority priority2) {
- if (priority1 == OptionPriority.ALWAYS_OVERRIDE
- && priority2 == OptionPriority.ALWAYS_OVERRIDE) {
- return true;
- }
-
- if (priority1 == OptionPriority.REQUIRED
- && priority2 == OptionPriority.REQUIRED) {
- return true;
- }
-
- return false;
+ return priority1 == OptionPriority.REQUIRED
+ && priority2 == OptionPriority.REQUIRED;
}
/**
- * Merges two configs
+ * Merges two configs.
*
* @param extendedConfig the extended config. The options in the extendedConfig will be applied
* on top of the baseConfig based on the option priorities.
- * @param baseConfig the base config
- * @return a {@link MutableOptionsBundle} of the merged config
+ * @param baseConfig the base config.
+ * @return a {@link MutableOptionsBundle} of the merged config.
*/
@NonNull
- static Config mergeConfigs(@Nullable Config extendedConfig,
- @Nullable Config baseConfig) {
+ static Config mergeConfigs(@Nullable Config extendedConfig, @Nullable Config baseConfig) {
if (extendedConfig == null && baseConfig == null) {
return OptionsBundle.emptyBundle();
}
@@ -333,12 +325,12 @@
/**
* Merges a specific option value from two configs.
*
- * @param mergedConfig the final output config
+ * @param mergedConfig the final output config.
* @param baseConfig the base config contains the option value which might be overridden by
* the corresponding option value in the extend config.
* @param extendedConfig the extended config contains the option value which might override
* the corresponding option value in the base config.
- * @param opt the option to merge
+ * @param opt the option to merge.
*/
static void mergeOptionValue(@NonNull MutableOptionsBundle mergedConfig,
@NonNull Config baseConfig,
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
index eff1afc..e998934 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
@@ -25,7 +25,6 @@
import static androidx.camera.core.streamsharing.StreamSharing.isStreamSharing;
import static androidx.core.util.Preconditions.checkArgument;
import static androidx.core.util.Preconditions.checkState;
-
import static java.util.Collections.emptyList;
import static java.util.Objects.requireNonNull;
@@ -677,6 +676,7 @@
SupportedOutputSizesSorter supportedOutputSizesSorter = new SupportedOutputSizesSorter(
cameraInfoInternal,
sensorRect != null ? rectToSize(sensorRect) : null);
+ boolean isPreviewStabilizationOn = false;
for (UseCase useCase : newUseCases) {
ConfigPair configPair = configPairMap.get(useCase);
// Combine with default configuration.
@@ -687,6 +687,9 @@
configToSupportedSizesMap.put(combinedUseCaseConfig,
supportedOutputSizesSorter.getSortedSupportedOutputSizes(
combinedUseCaseConfig));
+
+ // TODO(kailianc): extract the use case's video stabilization settings and set
+ // to isPreviewStabilizationOn
}
// Get suggested stream specifications and update the use case session configuration
@@ -695,7 +698,8 @@
mCameraDeviceSurfaceManager.getSuggestedStreamSpecs(
cameraMode,
cameraId, existingSurfaces,
- configToSupportedSizesMap);
+ configToSupportedSizesMap,
+ isPreviewStabilizationOn);
for (Map.Entry<UseCaseConfig<?>, UseCase> entry : configToUseCaseMap.entrySet()) {
suggestedStreamSpecs.put(entry.getValue(),
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/LargeJpegImageQuirk.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/LargeJpegImageQuirk.java
index c6ad529..471095e 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/LargeJpegImageQuirk.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/LargeJpegImageQuirk.java
@@ -28,16 +28,16 @@
/**
* <p>QuirkSummary
- * Bug Id: 288828159
+ * Bug Id: 288828159, 299069235
* Description: Quirk required to check whether the captured JPEG image contains redundant
* 0's padding data. For example, Samsung A5 (2017) series devices have the
* problem and result in the output JPEG image to be extremely large (about 32 MB).
- * Device(s): Samsung Galaxy A5 (2017), A52, A70, A72 and S7 series devices
+ * Device(s): Samsung Galaxy A5 (2017), A52, A70, A72, S7 series devices and Vivo S16 device
*/
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
public final class LargeJpegImageQuirk implements Quirk {
- private static final Set<String> DEVICE_MODELS = new HashSet<>(Arrays.asList(
+ private static final Set<String> SAMSUNG_DEVICE_MODELS = new HashSet<>(Arrays.asList(
// Samsung Galaxy A5 series devices
"SM-A520F",
"SM-A520L",
@@ -70,7 +70,22 @@
"SM-S906B"
));
+ private static final Set<String> VIVO_DEVICE_MODELS = new HashSet<>(Arrays.asList(
+ // Vivo S16
+ "V2244A"
+ ));
+
static boolean load() {
- return DEVICE_MODELS.contains(Build.MODEL.toUpperCase(Locale.US));
+ return isSamsungProblematicDevice() || isVivoProblematicDevice();
+ }
+
+ private static boolean isSamsungProblematicDevice() {
+ return "Samsung".equalsIgnoreCase(Build.BRAND) && SAMSUNG_DEVICE_MODELS.contains(
+ Build.MODEL.toUpperCase(Locale.US));
+ }
+
+ private static boolean isVivoProblematicDevice() {
+ return "Vivo".equalsIgnoreCase(Build.BRAND) && VIVO_DEVICE_MODELS.contains(
+ Build.MODEL.toUpperCase(Locale.US));
}
}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/workaround/InvalidJpegDataParser.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/workaround/InvalidJpegDataParser.java
index a9b122a..4a0d527 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/workaround/InvalidJpegDataParser.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/workaround/InvalidJpegDataParser.java
@@ -18,6 +18,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
+import androidx.annotation.VisibleForTesting;
import androidx.camera.core.internal.compat.quirk.DeviceQuirks;
import androidx.camera.core.internal.compat.quirk.LargeJpegImageQuirk;
@@ -41,12 +42,23 @@
return bytes.length;
}
+ int jfifEoiMarkEndPosition = getJfifEoiMarkEndPosition(bytes);
+
+ return jfifEoiMarkEndPosition != -1 ? jfifEoiMarkEndPosition : bytes.length;
+ }
+
+ /**
+ * Returns the end position of JFIF EOI mark. Returns -1 while JFIF EOI mark can't be found
+ * in the provided byte array.
+ */
+ @VisibleForTesting
+ public static int getJfifEoiMarkEndPosition(@NonNull byte[] bytes) {
// Parses the JFIF segments from the start of the JPEG image data
int markPosition = 0x2;
while (true) {
// Breaks the while-loop and return null if the mark byte can't be correctly found.
if (markPosition + 4 > bytes.length || bytes[markPosition] != ((byte) 0xff)) {
- return bytes.length;
+ return -1;
}
int segmentLength =
@@ -65,7 +77,7 @@
while (true) {
// Breaks the while-loop and return null if EOI mark can't be found
if (eoiPosition + 2 > bytes.length) {
- return bytes.length;
+ return -1;
}
if (bytes[eoiPosition] == ((byte) 0xff) && bytes[eoiPosition + 1] == ((byte) 0xd9)) {
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/impl/ConfigTest.java b/camera/camera-core/src/test/java/androidx/camera/core/impl/ConfigTest.java
index 96b086a..e78d689f 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/impl/ConfigTest.java
+++ b/camera/camera-core/src/test/java/androidx/camera/core/impl/ConfigTest.java
@@ -89,8 +89,8 @@
}
@Test
- public void hasConflict_whenTwoValueAreALWAYSOVERRIDE() {
- assertThat(Config.hasConflict(ALWAYS_OVERRIDE, ALWAYS_OVERRIDE)).isTrue();
+ public void noConflict_whenTwoValueAreALWAYSOVERRIDE() {
+ assertThat(Config.hasConflict(ALWAYS_OVERRIDE, ALWAYS_OVERRIDE)).isFalse();
}
@Test
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/impl/MutableOptionsBundleTest.java b/camera/camera-core/src/test/java/androidx/camera/core/impl/MutableOptionsBundleTest.java
index 1db4883..be0e91c 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/impl/MutableOptionsBundleTest.java
+++ b/camera/camera-core/src/test/java/androidx/camera/core/impl/MutableOptionsBundleTest.java
@@ -108,13 +108,18 @@
assertThat(config2.retrieveOptionWithPriority(OPTION_2, OPTIONAL)).isEqualTo(VALUE_1);
}
- @Test(expected = IllegalArgumentException.class)
+ @Test
public void insertOption_ALWAYSOVERRIDE_ALWAYSOVERRIDE() {
MutableOptionsBundle mutOpts = MutableOptionsBundle.create();
mutOpts.insertOption(OPTION_1, ALWAYS_OVERRIDE, VALUE_1);
- // should throw an Error
mutOpts.insertOption(OPTION_1, ALWAYS_OVERRIDE, VALUE_2);
+
+ assertThat(mutOpts.retrieveOption(OPTION_1)).isEqualTo(VALUE_2);
+ Config.OptionPriority highestPriority = Collections.min(mutOpts.getPriorities(OPTION_1));
+ assertThat(highestPriority).isEqualTo(ALWAYS_OVERRIDE);
+ assertThat(mutOpts.retrieveOptionWithPriority(OPTION_1, highestPriority))
+ .isEqualTo(VALUE_2);
}
@Test
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/internal/compat/workaround/InvalidJpegDataParserTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/internal/compat/workaround/InvalidJpegDataParserTest.kt
index cdd2576..127b136 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/internal/compat/workaround/InvalidJpegDataParserTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/internal/compat/workaround/InvalidJpegDataParserTest.kt
@@ -73,31 +73,34 @@
@DoNotInstrument
@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
class InvalidJpegDataParserTest(
+ private val brand: String,
private val model: String,
private val data: ByteArray,
private val validDataLength: Int,
- ) {
+) {
companion object {
@JvmStatic
- @ParameterizedRobolectricTestRunner.Parameters(name = "model={0}, data={1}, length={2}")
+ @ParameterizedRobolectricTestRunner.Parameters(
+ name = "brand={0}, model={1}, data={2}, length={3}")
fun data() = mutableListOf<Array<Any?>>().apply {
- add(arrayOf("SM-A520F", problematicJpegByteArray, 18))
- add(arrayOf("SM-A520F", problematicJpegByteArray2, 18))
- add(arrayOf("SM-A520F", correctJpegByteArray1, 18))
- add(arrayOf("SM-A520F", correctJpegByteArray2, 18))
- add(arrayOf("SM-A520F", invalidVeryShortData, 2))
- add(arrayOf("SM-A520F", invalidNoSosData, 28))
- add(arrayOf("SM-A520F", invalidNoEoiData, 28))
- add(arrayOf("fake-model", problematicJpegByteArray, 42))
- add(arrayOf("fake-model", problematicJpegByteArray2, 64))
- add(arrayOf("fake-model", correctJpegByteArray1, 28))
- add(arrayOf("fake-model", correctJpegByteArray2, 18))
+ add(arrayOf("SAMSUNG", "SM-A520F", problematicJpegByteArray, 18))
+ add(arrayOf("SAMSUNG", "SM-A520F", problematicJpegByteArray2, 18))
+ add(arrayOf("SAMSUNG", "SM-A520F", correctJpegByteArray1, 18))
+ add(arrayOf("SAMSUNG", "SM-A520F", correctJpegByteArray2, 18))
+ add(arrayOf("SAMSUNG", "SM-A520F", invalidVeryShortData, 2))
+ add(arrayOf("SAMSUNG", "SM-A520F", invalidNoSosData, 28))
+ add(arrayOf("SAMSUNG", "SM-A520F", invalidNoEoiData, 28))
+ add(arrayOf("fake-brand", "fake-model", problematicJpegByteArray, 42))
+ add(arrayOf("fake-brand", "fake-model", problematicJpegByteArray2, 64))
+ add(arrayOf("fake-brand", "fake-model", correctJpegByteArray1, 28))
+ add(arrayOf("fake-brand", "fake-model", correctJpegByteArray2, 18))
}
}
@Test
fun canGetValidJpegDataLength() {
+ ReflectionHelpers.setStaticField(Build::class.java, "BRAND", brand)
ReflectionHelpers.setStaticField(Build::class.java, "MODEL", model)
assertThat(InvalidJpegDataParser().getValidDataLength(data)).isEqualTo(validDataLength)
}
diff --git a/camera/camera-effects/src/androidTest/java/androidx/camera/effects/internal/SurfaceProcessorImplDeviceTest.kt b/camera/camera-effects/src/androidTest/java/androidx/camera/effects/internal/SurfaceProcessorImplDeviceTest.kt
index 5360c0e..e1c03b6 100644
--- a/camera/camera-effects/src/androidTest/java/androidx/camera/effects/internal/SurfaceProcessorImplDeviceTest.kt
+++ b/camera/camera-effects/src/androidTest/java/androidx/camera/effects/internal/SurfaceProcessorImplDeviceTest.kt
@@ -16,6 +16,9 @@
package androidx.camera.effects.internal
+import android.graphics.Color
+import android.graphics.Matrix
+import android.graphics.Rect
import android.graphics.SurfaceTexture
import android.util.Size
import android.view.Surface
@@ -23,7 +26,11 @@
import androidx.camera.core.CameraEffect.VIDEO_CAPTURE
import androidx.camera.core.SurfaceOutput
import androidx.camera.core.SurfaceRequest
+import androidx.camera.core.SurfaceRequest.TransformationInfo
+import androidx.camera.core.impl.utils.TransformUtils.sizeToRect
+import androidx.camera.effects.Frame
import androidx.camera.testing.fakes.FakeCamera
+import androidx.camera.testing.impl.TestImageUtil.getAverageDiff
import androidx.core.util.Consumer
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SdkSuppress
@@ -49,12 +56,19 @@
class SurfaceProcessorImplDeviceTest {
companion object {
- private val SIZE = Size(640, 480)
+ private const val ROTATION_DEGREES = 90
+ private val TRANSFORM = Matrix().apply {
+ postRotate(90F)
+ }
+ private const val INPUT_COLOR = Color.GREEN
+ private const val OVERLAY_COLOR = Color.RED
// The timeout is set to 200ms to qualify for @SmallTest.
private const val TIMEOUT_MILLIS = 200L
}
+ private val size = Size(640, 480)
+ private val cropRect = sizeToRect(size)
private lateinit var surfaceRequest: SurfaceRequest
private lateinit var outputTexture: SurfaceTexture
private lateinit var outputSurface: Surface
@@ -63,18 +77,28 @@
private lateinit var surfaceOutput: SurfaceOutput
private lateinit var surfaceOutput2: SurfaceOutput
private lateinit var processor: SurfaceProcessorImpl
+ private lateinit var transformationInfo: TransformationInfo
@Before
fun setUp() {
- surfaceRequest = SurfaceRequest(SIZE, FakeCamera()) {}
+ transformationInfo = TransformationInfo.of(
+ cropRect,
+ ROTATION_DEGREES,
+ Surface.ROTATION_90,
+ true,
+ TRANSFORM,
+ true
+ )
+ surfaceRequest = SurfaceRequest(size, FakeCamera()) {}
+ surfaceRequest.updateTransformationInfo(transformationInfo)
outputTexture = SurfaceTexture(0)
outputTexture.detachFromGLContext()
outputSurface = Surface(outputTexture)
outputTexture2 = SurfaceTexture(1)
outputTexture2.detachFromGLContext()
outputSurface2 = Surface(outputTexture2)
- surfaceOutput = SurfaceOutputImpl(outputSurface)
- surfaceOutput2 = SurfaceOutputImpl(outputSurface2)
+ surfaceOutput = SurfaceOutputImpl(outputSurface, size)
+ surfaceOutput2 = SurfaceOutputImpl(outputSurface2, size)
}
@After
@@ -89,6 +113,65 @@
}
@Test
+ fun onDrawListenerReturnsFalse_notDrawnToOutput() = runBlocking {
+ // Act: return false in the on draw listener.
+ val latch = fillFramesAndWaitForOutput(0, 1) {
+ it.setOnDrawListener { false }
+ }
+ // Assert: output is not drawn.
+ assertThat(latch.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isFalse()
+ }
+
+ @Test
+ fun onDrawListener_receivesTransformationInfo() = runBlocking {
+ // Arrange.
+ var frameReceived: Frame? = null
+ // Act: fill frames and wait draw frame listener.
+ val latch = fillFramesAndWaitForOutput(0, 1) { processor ->
+ processor.setOnDrawListener { frame ->
+ frameReceived = frame
+ true
+ }
+ }
+ // Assert: draw frame listener receives correct transformation info.
+ assertThat(latch.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue()
+ assertThat(frameReceived!!.size).isEqualTo(size)
+ assertThat(frameReceived!!.cropRect).isEqualTo(transformationInfo.cropRect)
+ assertThat(frameReceived!!.getMirroring()).isEqualTo(transformationInfo.mirroring)
+ assertThat(frameReceived!!.sensorToBufferTransform)
+ .isEqualTo(transformationInfo.sensorToBufferTransform)
+ assertThat(frameReceived!!.rotationDegrees).isEqualTo(ROTATION_DEGREES)
+ }
+
+ @Test
+ fun canvasInvalidated_overlayDrawnToOutput(): Unit = runBlocking {
+ val latch = fillFramesAndWaitForOutput(0, 1) { processor ->
+ processor.setOnDrawListener { frame ->
+ // Act: invalidate overlay canvas and draw color.
+ frame.invalidateOverlayCanvas().drawColor(OVERLAY_COLOR)
+ true
+ }
+ }
+ // Assert: output receives frame with overlay color.
+ assertThat(latch.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue()
+ assertOutputColor(OVERLAY_COLOR)
+ }
+
+ @Test
+ fun canvasNotInvalidated_overlayNotDrawnToOutput() = runBlocking {
+ val latch = fillFramesAndWaitForOutput(0, 1) { processor ->
+ processor.setOnDrawListener { frame ->
+ // Act: draw color on overlay canvas without invalidating.
+ frame.overlayCanvas.drawColor(OVERLAY_COLOR)
+ true
+ }
+ }
+ // Assert: output receives frame with input color
+ assertThat(latch.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue()
+ assertOutputColor(INPUT_COLOR)
+ }
+
+ @Test
fun zeroQueueDepth_inputDrawnToOutput() = runBlocking {
// Assert: output receives frame when queue depth == 0.
val latch = fillFramesAndWaitForOutput(0, 1)
@@ -122,10 +205,7 @@
// Act: replace output surface so the cached frame is no longer valid. The cached frame
// should be marked empty and not blocking the pipeline.
- val countDownLatch = CountDownLatch(1)
- outputTexture2.setOnFrameAvailableListener {
- countDownLatch.countDown()
- }
+ val countDownLatch = getTextureUpdateLatch(outputTexture2)
withContext(processor.glExecutor.asCoroutineDispatcher()) {
processor.onOutputSurface(surfaceOutput2)
}
@@ -138,6 +218,25 @@
}
/**
+ * Renders the input surface to a bitmap and asserts that the color of the bitmap.
+ */
+ private suspend fun assertOutputColor(color: Int) {
+ val matrix = FloatArray(16)
+ android.opengl.Matrix.setIdentityM(matrix, 0)
+ withContext(processor.glExecutor.asCoroutineDispatcher()) {
+ val bitmap = processor.glRendererForTesting
+ .renderInputToBitmap(size.width, size.height, matrix)
+ assertThat(
+ getAverageDiff(
+ bitmap,
+ Rect(0, 0, size.width, size.height),
+ color
+ )
+ ).isEqualTo(0)
+ }
+ }
+
+ /**
* Creates a processor and draws frames to the input surface.
*
* @param queueDepth The queue depth of the processor.
@@ -146,18 +245,17 @@
*/
private suspend fun fillFramesAndWaitForOutput(
queueDepth: Int,
- frameCount: Int
+ frameCount: Int,
+ configureProcessor: (SurfaceProcessorImpl) -> Unit = {},
): CountDownLatch {
// Arrange: Create a processor.
processor = SurfaceProcessorImpl(queueDepth)
+ configureProcessor(processor)
withContext(processor.glExecutor.asCoroutineDispatcher()) {
processor.onInputSurface(surfaceRequest)
processor.onOutputSurface(surfaceOutput)
}
- val countDownLatch = CountDownLatch(1)
- outputTexture.setOnFrameAvailableListener {
- countDownLatch.countDown()
- }
+ val countDownLatch = getTextureUpdateLatch(outputTexture)
// Act: Draw frames to the input surface.
val inputSurface = surfaceRequest.deferrableSurface.surface.get()
@@ -174,6 +272,7 @@
*/
private suspend fun drawSurface(surface: Surface) {
val canvas = surface.lockCanvas(null)
+ canvas.drawColor(INPUT_COLOR)
surface.unlockCanvasAndPost(canvas)
// Drain the GL thread to ensure the processor caches or draws the frame. Otherwise, the
// input SurfaceTexture's onSurfaceAvailable callback may only get called once for
@@ -182,7 +281,16 @@
}
}
- private class SurfaceOutputImpl(private val surface: Surface) : SurfaceOutput {
+ private fun getTextureUpdateLatch(surfaceTexture: SurfaceTexture): CountDownLatch {
+ val countDownLatch = CountDownLatch(1)
+ surfaceTexture.setOnFrameAvailableListener {
+ countDownLatch.countDown()
+ }
+ return countDownLatch
+ }
+
+ private class SurfaceOutputImpl(private val surface: Surface, val surfaceSize: Size) :
+ SurfaceOutput {
override fun close() {
}
@@ -199,7 +307,7 @@
}
override fun getSize(): Size {
- return SIZE
+ return surfaceSize
}
override fun updateTransformMatrix(updated: FloatArray, original: FloatArray) {
diff --git a/camera/camera-effects/src/main/java/androidx/camera/effects/Frame.java b/camera/camera-effects/src/main/java/androidx/camera/effects/Frame.java
new file mode 100644
index 0000000..ee6b8a9
--- /dev/null
+++ b/camera/camera-effects/src/main/java/androidx/camera/effects/Frame.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.effects;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.SurfaceTexture;
+import android.hardware.camera2.CameraCharacteristics;
+import android.util.Size;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.RestrictTo;
+import androidx.camera.core.ImageInfo;
+import androidx.camera.core.SurfaceRequest;
+
+import com.google.auto.value.AutoValue;
+
+/**
+ * Represents a frame that will be rendered next.
+ *
+ * <p>This class can be used to overlay graphics or data on camera output. It contains
+ * information for drawing an overlay, including sensor-to-buffer transform, size, crop rect,
+ * rotation, mirroring, and timestamp. It also provides a {@link Canvas} for the drawing.
+ *
+ * TODO(b/297509601): Make it public API in 1.4.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@RequiresApi(21)
+@AutoValue
+public abstract class Frame {
+
+ private boolean mIsOverlayDirty = false;
+
+ /**
+ * Internal API to create a frame.
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ @NonNull
+ public static Frame of(
+ @NonNull Canvas overlayCanvas,
+ long timestampNs,
+ @NonNull Size size,
+ @NonNull SurfaceRequest.TransformationInfo transformationInfo) {
+ return new AutoValue_Frame(transformationInfo.getSensorToBufferTransform(), size,
+ transformationInfo.getCropRect(), transformationInfo.getRotationDegrees(),
+ transformationInfo.getMirroring(), timestampNs, overlayCanvas);
+ }
+
+ /**
+ * Returns the sensor to image buffer transform matrix.
+ *
+ * <p>The value is a mapping from sensor coordinates to buffer coordinates, which is,
+ * from the rect of {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE} to the
+ * rect defined by {@code (0, 0, #getSize()#getWidth(), #getSize()#getHeight())}.
+ *
+ * <p>The value can be set on the {@link Canvas} using {@link Canvas#setMatrix} API. This
+ * transforms the {@link Canvas} to the sensor coordinate system.
+ *
+ * @see SurfaceRequest.TransformationInfo#getSensorToBufferTransform()
+ */
+ @NonNull
+ public abstract Matrix getSensorToBufferTransform();
+
+ /**
+ * Returns the resolution of the frame.
+ *
+ * @see SurfaceRequest#getResolution()
+ */
+ @NonNull
+ public abstract Size getSize();
+
+ /**
+ * Returns the crop rect rectangle.
+ *
+ * <p>The value represents how the frame will be cropped by the CameraX pipeline. The crop
+ * rectangle specifies the region of valid pixels in the frame, using coordinates from (0, 0)
+ * to the (width, height) of {@link #getSize()}. Only the overlay drawn within the bound of
+ * the crop rect will be visible to the end users.
+ *
+ * <p>The crop rect is applied before the rotating and mirroring. The order of the operations
+ * is as follows: 1) cropping, 2) rotating and 3) mirroring.
+ *
+ * @see SurfaceRequest.TransformationInfo#getCropRect()
+ */
+ @NonNull
+ public abstract Rect getCropRect();
+
+ /**
+ * Returns the rotation degrees of the frame.
+ *
+ * <p>This is a clockwise rotation in degrees that needs to be applied to the frame. The
+ * rotation will be determined by {@link CameraCharacteristics} and UseCase configuration.
+ * The app must draw the overlay according to the rotation degrees to ensure it is
+ * displayed correctly to the end users.
+ *
+ * <p>The rotation is applied after the cropping but before the mirroring. The order of the
+ * operations is as follows: 1) cropping, 2) rotating and 3) mirroring.
+ *
+ * @see SurfaceRequest.TransformationInfo#getRotationDegrees()
+ */
+ public abstract int getRotationDegrees();
+
+ /**
+ * Returns whether the buffer will be mirrored.
+ *
+ * <p>This flag indicates whether the buffer will be mirrored by the pipeline vertically. For
+ * example, for front camera preview, the buffer is usually mirrored before displayed to end
+ * users.
+ *
+ * <p>The mirroring is applied after the cropping and the rotating. The order of the
+ * operations is as follows: 1) cropping, 2) rotating and 3) mirroring.
+ *
+ * @see SurfaceRequest.TransformationInfo#getMirroring()
+ */
+ public abstract boolean getMirroring();
+
+ /**
+ * Returns the timestamp of the frame in nanoseconds.
+ *
+ * @see SurfaceTexture#getTimestamp()
+ * @see ImageInfo#getTimestamp()
+ */
+ public abstract long getTimestampNs();
+
+ /**
+ * Invalidates and returns the overlay canvas.
+ *
+ * <p>Call this method to get the {@link Canvas} for drawing an overlay on top of the frame.
+ * The {@link Canvas} is backed by a {@link Bitmap} with the sizes equals {@link #getSize()} and
+ * the format equals {@link Bitmap.Config#ARGB_8888}. To draw object in camera sensor
+ * coordinates, apply {@link #getSensorToBufferTransform()} via
+ * {@link Canvas#setMatrix(Matrix)} before drawing.
+ *
+ * <p>Only call this method if the caller needs to draw overlay on the frame. Calling this
+ * method will upload the {@link Bitmap} to GPU for blending.
+ */
+ @NonNull
+ public Canvas invalidateOverlayCanvas() {
+ mIsOverlayDirty = true;
+ return getOverlayCanvas();
+ }
+
+ /**
+ * Internal API to check whether the overlay canvas is dirty.
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public boolean isOverlayDirty() {
+ return mIsOverlayDirty;
+ }
+
+
+ /**
+ * Internal API to get the overlay canvas without invalidating it.
+ */
+ @NonNull
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public abstract Canvas getOverlayCanvas();
+}
diff --git a/camera/camera-effects/src/main/java/androidx/camera/effects/internal/SurfaceProcessorImpl.java b/camera/camera-effects/src/main/java/androidx/camera/effects/internal/SurfaceProcessorImpl.java
index aaaaec1..2c8ec15 100644
--- a/camera/camera-effects/src/main/java/androidx/camera/effects/internal/SurfaceProcessorImpl.java
+++ b/camera/camera-effects/src/main/java/androidx/camera/effects/internal/SurfaceProcessorImpl.java
@@ -33,10 +33,13 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
+import androidx.annotation.VisibleForTesting;
+import androidx.arch.core.util.Function;
import androidx.camera.core.SurfaceOutput;
import androidx.camera.core.SurfaceProcessor;
import androidx.camera.core.SurfaceRequest;
import androidx.camera.core.impl.utils.executor.CameraXExecutors;
+import androidx.camera.effects.Frame;
import androidx.camera.effects.opengl.GlRenderer;
import androidx.core.util.Pair;
@@ -77,6 +80,10 @@
private Canvas mOverlayCanvas;
@Nullable
private Pair<SurfaceOutput, Surface> mOutputSurfacePair = null;
+ @Nullable
+ private SurfaceRequest.TransformationInfo mTransformationInfo = null;
+ @Nullable
+ private Function<Frame, Boolean> mOnDrawListener;
private boolean mIsReleased = false;
@@ -110,6 +117,11 @@
});
surfaceTexture.setOnFrameAvailableListener(this, mGlHandler);
+ // Listen for transformation updates.
+ mTransformationInfo = null;
+ surfaceRequest.setTransformationInfoListener(mGlExecutor, transformationInfo ->
+ mTransformationInfo = transformationInfo);
+
// Configure buffers based on the input size.
createBufferAndOverlay(surfaceRequest.getResolution());
}
@@ -209,6 +221,20 @@
return mGlExecutor;
}
+ /**
+ * Sets the listener that listens to frame updates and draws overlay.
+ *
+ * <p>CameraX invokes this {@link Function} on the GL thread each time a frame is drawn. The
+ * caller can use implement the {@link Function} to draw overlay on the frame.
+ *
+ * <p>The {@link Function} accepts a {@link Frame} object which provides information on how to
+ * draw the overlay. The return value of the {@link Function} indicates whether the frame
+ * should be drawn. If false, the frame will be dropped.
+ */
+ public void setOnDrawListener(@Nullable Function<Frame, Boolean> onDrawListener) {
+ runOnGlThread(() -> mOnDrawListener = onDrawListener);
+ }
+
// *** Private methods ***
private void runOnGlThread(@NonNull Runnable runnable) {
@@ -264,7 +290,22 @@
*/
@SuppressWarnings("unused")
private boolean drawOverlay(long timestampNs) {
- // TODO(b/297509601) implement this method. See: aosp/2676397.
+ checkGlThread();
+ if (mTransformationInfo == null || mOnDrawListener == null) {
+ return true;
+ }
+ Frame frame = Frame.of(
+ requireNonNull(mOverlayCanvas),
+ timestampNs,
+ requireNonNull(mInputSize),
+ mTransformationInfo);
+ if (!mOnDrawListener.apply(frame)) {
+ // The caller wants to drop the frame.
+ return false;
+ }
+ if (frame.isOverlayDirty()) {
+ mGlRenderer.uploadOverlay(requireNonNull(mOverlayBitmap));
+ }
return true;
}
@@ -275,4 +316,9 @@
private boolean isGlThread() {
return Thread.currentThread() == mGlThread;
}
+
+ @VisibleForTesting
+ GlRenderer getGlRendererForTesting() {
+ return mGlRenderer;
+ }
}
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/compat/workaround/OnEnableDisableSessionDurationCheckTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/compat/workaround/OnEnableDisableSessionDurationCheckTest.kt
index abd3bda..9bc767e 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/compat/workaround/OnEnableDisableSessionDurationCheckTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/compat/workaround/OnEnableDisableSessionDurationCheckTest.kt
@@ -17,6 +17,7 @@
package androidx.camera.extensions.internal.compat.workaround
import androidx.camera.extensions.internal.compat.workaround.OnEnableDisableSessionDurationCheck.MIN_DURATION_FOR_ENABLE_DISABLE_SESSION
+import androidx.camera.testing.impl.AndroidUtil
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SdkSuppress
import androidx.test.filters.SmallTest
@@ -24,6 +25,8 @@
import kotlin.system.measureTimeMillis
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
+import org.junit.Assume.assumeFalse
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -35,6 +38,11 @@
const val TOLERANCE = 60L
}
+ @Before
+ fun setUp() {
+ assumeFalse(AndroidUtil.isEmulatorAndAPI21())
+ }
+
@Test
fun enabled_ensureMinimalDuration() = runBlocking {
// Arrange
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/E2ETestUtil.kt b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/FileUtil.kt
similarity index 60%
rename from camera/camera-testing/src/main/java/androidx/camera/testing/impl/E2ETestUtil.kt
rename to camera/camera-testing/src/main/java/androidx/camera/testing/impl/FileUtil.kt
index e454f702..b482047 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/E2ETestUtil.kt
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/FileUtil.kt
@@ -18,10 +18,12 @@
import android.content.ContentResolver
import android.content.ContentValues
+import android.net.Uri
import android.os.Environment.DIRECTORY_DOCUMENTS
import android.os.Environment.DIRECTORY_MOVIES
import android.os.Environment.getExternalStoragePublicDirectory
import android.provider.MediaStore
+import android.util.Log
import androidx.annotation.RequiresApi
import androidx.camera.core.Logger
import androidx.camera.video.FileOutputOptions
@@ -32,12 +34,12 @@
import java.io.FileOutputStream
import java.io.OutputStreamWriter
-private const val TAG = "E2ETestUtil"
+private const val TAG = "FileUtil"
private const val EXTENSION_MP4 = "mp4"
private const val EXTENSION_TEXT = "txt"
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-object E2ETestUtil {
+object FileUtil {
/**
* Write the given text to the external storage.
@@ -101,7 +103,7 @@
): FileOutputOptions {
val fileNameWithExtension = "$fileName.$extension"
val folder = getExternalStoragePublicDirectory(DIRECTORY_MOVIES)
- if (!folder.exists() && !folder.mkdirs()) {
+ if (!createFolder(folder)) {
Logger.e(TAG, "Failed to create directory: $folder")
}
return FileOutputOptions.Builder(File(folder, fileNameWithExtension)).build()
@@ -119,14 +121,10 @@
fun generateVideoMediaStoreOptions(
contentResolver: ContentResolver,
fileName: String
- ): MediaStoreOutputOptions {
- val contentValues = generateVideoContentValues(fileName)
-
- return MediaStoreOutputOptions.Builder(
- contentResolver,
- MediaStore.Video.Media.EXTERNAL_CONTENT_URI
- ).setContentValues(contentValues).build()
- }
+ ): MediaStoreOutputOptions = MediaStoreOutputOptions.Builder(
+ contentResolver,
+ MediaStore.Video.Media.EXTERNAL_CONTENT_URI
+ ).setContentValues(generateVideoContentValues(fileName)).build()
/**
* Check if the given file name string is valid.
@@ -145,14 +143,79 @@
return !fileName.isNullOrBlank()
}
- private fun generateVideoContentValues(fileName: String): ContentValues {
- val res = ContentValues()
- res.put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4")
- res.put(MediaStore.Video.Media.TITLE, fileName)
- res.put(MediaStore.Video.Media.DISPLAY_NAME, fileName)
- res.put(MediaStore.Video.Media.DATE_ADDED, System.currentTimeMillis() / 1000)
- res.put(MediaStore.Video.Media.DATE_TAKEN, System.currentTimeMillis())
+ /**
+ * Creates parent folder for the input file path.
+ *
+ * @param filePath the input file path to create its parent folder.
+ * @return `true` if the parent folder already exists or is created successfully.
+ * `false` if the existing parent folder path is not a folder or failed to create.
+ */
+ @JvmStatic
+ fun createParentFolder(filePath: String): Boolean {
+ return createParentFolder(File(filePath))
+ }
- return res
+ /**
+ * Creates parent folder for the input file.
+ *
+ * @param file the input file to create its parent folder
+ * @return `true` if the parent folder already exists or is created successfully.
+ * `false` if the existing parent folder path is not a folder or failed to create.
+ */
+ @JvmStatic
+ fun createParentFolder(file: File): Boolean = file.parentFile?.let {
+ createFolder(it)
+ } ?: false
+
+ /**
+ * Creates folder for the input file.
+ *
+ * @param file the input file to create folder
+ * @return `true` if the folder already exists or is created successfully.
+ * `false` if the existing folder path is not a folder or failed to create.
+ */
+ @JvmStatic
+ fun createFolder(file: File): Boolean = if (file.exists()) {
+ file.isDirectory
+ } else {
+ file.mkdirs()
+ }
+
+ /**
+ * Gets the absolute path from a Uri.
+ *
+ * @param resolver the content resolver.
+ * @param contentUri the content uri.
+ * @return the file path of the content uri or null if not found.
+ */
+ @JvmStatic
+ fun getAbsolutePathFromUri(resolver: ContentResolver, contentUri: Uri): String? {
+ // MediaStore.Video.Media.DATA was deprecated in API level 29.
+ val column = MediaStore.Video.Media.DATA
+ try {
+ resolver.query(contentUri, arrayOf(column), null, null, null)!!.use { cursor ->
+ val columnIndex = cursor.getColumnIndexOrThrow(column)
+ cursor.moveToFirst()
+ return cursor.getString(columnIndex)
+ }
+ } catch (e: RuntimeException) {
+ Log.e(
+ TAG,
+ String.format(
+ "Failed in getting absolute path for Uri %s with Exception %s",
+ contentUri, e
+ ), e
+ )
+ return null
+ }
+ }
+
+ private fun generateVideoContentValues(fileName: String) = ContentValues().apply {
+ put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4")
+ put(MediaStore.Video.Media.TITLE, fileName)
+ put(MediaStore.Video.Media.DISPLAY_NAME, fileName)
+ val currentTimeMs = System.currentTimeMillis()
+ put(MediaStore.Video.Media.DATE_ADDED, currentTimeMs / 1000)
+ put(MediaStore.Video.Media.DATE_TAKEN, currentTimeMs)
}
}
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeCameraDeviceSurfaceManager.java b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeCameraDeviceSurfaceManager.java
index c0a5fe5..4813cdc 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeCameraDeviceSurfaceManager.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeCameraDeviceSurfaceManager.java
@@ -18,9 +18,7 @@
import static android.graphics.ImageFormat.JPEG;
import static android.graphics.ImageFormat.YUV_420_888;
-
import static androidx.camera.core.impl.ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE;
-
import static com.google.common.primitives.Ints.asList;
import android.util.Pair;
@@ -90,7 +88,8 @@
@CameraMode.Mode int cameraMode,
@NonNull String cameraId,
@NonNull List<AttachedSurfaceInfo> existingSurfaces,
- @NonNull Map<UseCaseConfig<?>, List<Size>> newUseCaseConfigsSupportedSizeMap) {
+ @NonNull Map<UseCaseConfig<?>, List<Size>> newUseCaseConfigsSupportedSizeMap,
+ boolean isPreviewStabilizationOn) {
List<UseCaseConfig<?>> newUseCaseConfigs =
new ArrayList<>(newUseCaseConfigsSupportedSizeMap.keySet());
checkSurfaceCombo(existingSurfaces, newUseCaseConfigs);
diff --git a/camera/camera-testing/src/test/java/androidx/camera/testing/fakes/FakeCameraDeviceSurfaceManagerTest.java b/camera/camera-testing/src/test/java/androidx/camera/testing/fakes/FakeCameraDeviceSurfaceManagerTest.java
index 453a9af..1ac4332 100644
--- a/camera/camera-testing/src/test/java/androidx/camera/testing/fakes/FakeCameraDeviceSurfaceManagerTest.java
+++ b/camera/camera-testing/src/test/java/androidx/camera/testing/fakes/FakeCameraDeviceSurfaceManagerTest.java
@@ -17,12 +17,9 @@
package androidx.camera.testing.fakes;
import static android.graphics.ImageFormat.YUV_420_888;
-
import static androidx.camera.core.impl.SurfaceConfig.ConfigSize.PREVIEW;
import static androidx.camera.core.impl.SurfaceConfig.ConfigType.YUV;
-
import static com.google.common.truth.Truth.assertThat;
-
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
@@ -96,7 +93,8 @@
CameraMode.DEFAULT,
FAKE_CAMERA_ID0,
emptyList(),
- createConfigOutputSizesMap(preview, analysis));
+ createConfigOutputSizesMap(preview, analysis),
+ false);
}
@Test(expected = IllegalArgumentException.class)
@@ -114,7 +112,8 @@
mFakeCameraDeviceSurfaceManager.getSuggestedStreamSpecs(
CameraMode.DEFAULT,
FAKE_CAMERA_ID0,
- singletonList(analysis), createConfigOutputSizesMap(preview, video));
+ singletonList(analysis), createConfigOutputSizesMap(preview, video),
+ false);
}
@Test(expected = IllegalArgumentException.class)
@@ -125,7 +124,8 @@
mFakeCameraDeviceSurfaceManager.getSuggestedStreamSpecs(
CameraMode.DEFAULT,
FAKE_CAMERA_ID0,
- Collections.emptyList(), createConfigOutputSizesMap(preview, video, analysis));
+ Collections.emptyList(), createConfigOutputSizesMap(preview, video, analysis),
+ false);
}
@Test
@@ -134,12 +134,14 @@
mFakeCameraDeviceSurfaceManager.getSuggestedStreamSpecs(
CameraMode.DEFAULT,
FAKE_CAMERA_ID0,
- emptyList(), createConfigOutputSizesMap(mFakeUseCaseConfig)).first;
+ emptyList(), createConfigOutputSizesMap(mFakeUseCaseConfig),
+ false).first;
Map<UseCaseConfig<?>, StreamSpec> suggestedStreamSpecCamera1 =
mFakeCameraDeviceSurfaceManager.getSuggestedStreamSpecs(
CameraMode.DEFAULT,
FAKE_CAMERA_ID1,
- emptyList(), createConfigOutputSizesMap(mFakeUseCaseConfig)).first;
+ emptyList(), createConfigOutputSizesMap(mFakeUseCaseConfig),
+ false).first;
assertThat(suggestedStreamSpecsCamera0.get(mFakeUseCaseConfig)).isEqualTo(
StreamSpec.builder(new Size(FAKE_WIDTH0, FAKE_HEIGHT0)).build());
diff --git a/camera/camera-video/api/current.txt b/camera/camera-video/api/current.txt
index b9cc77a..7f1314e 100644
--- a/camera/camera-video/api/current.txt
+++ b/camera/camera-video/api/current.txt
@@ -2,10 +2,12 @@
package androidx.camera.video {
@RequiresApi(21) @com.google.auto.value.AutoValue public abstract class AudioStats {
+ method public double getAudioAmplitude();
method public abstract int getAudioState();
method public abstract Throwable? getErrorCause();
method public boolean hasAudio();
method public boolean hasError();
+ field public static final double AUDIO_AMPLITUDE_NONE = 0.0;
field public static final int AUDIO_STATE_ACTIVE = 0; // 0x0
field public static final int AUDIO_STATE_DISABLED = 1; // 0x1
field public static final int AUDIO_STATE_ENCODER_ERROR = 3; // 0x3
@@ -14,6 +16,9 @@
field public static final int AUDIO_STATE_SOURCE_SILENCED = 2; // 0x2
}
+ @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalAudioApi {
+ }
+
@SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalPersistentRecording {
}
diff --git a/camera/camera-video/api/restricted_current.txt b/camera/camera-video/api/restricted_current.txt
index b9cc77a..7f1314e 100644
--- a/camera/camera-video/api/restricted_current.txt
+++ b/camera/camera-video/api/restricted_current.txt
@@ -2,10 +2,12 @@
package androidx.camera.video {
@RequiresApi(21) @com.google.auto.value.AutoValue public abstract class AudioStats {
+ method public double getAudioAmplitude();
method public abstract int getAudioState();
method public abstract Throwable? getErrorCause();
method public boolean hasAudio();
method public boolean hasError();
+ field public static final double AUDIO_AMPLITUDE_NONE = 0.0;
field public static final int AUDIO_STATE_ACTIVE = 0; // 0x0
field public static final int AUDIO_STATE_DISABLED = 1; // 0x1
field public static final int AUDIO_STATE_ENCODER_ERROR = 3; // 0x3
@@ -14,6 +16,9 @@
field public static final int AUDIO_STATE_SOURCE_SILENCED = 2; // 0x2
}
+ @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalAudioApi {
+ }
+
@SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalPersistentRecording {
}
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/AudioStats.java b/camera/camera-video/src/main/java/androidx/camera/video/AudioStats.java
index 7f171f5..dde69be 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/AudioStats.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/AudioStats.java
@@ -104,7 +104,6 @@
* Should audio recording be disabled, any attempts to retrieve the amplitude will
* return this value.
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY)
public static final double AUDIO_AMPLITUDE_NONE = 0;
@IntDef({AUDIO_STATE_ACTIVE, AUDIO_STATE_DISABLED, AUDIO_STATE_SOURCE_SILENCED,
@@ -168,8 +167,8 @@
public abstract Throwable getErrorCause();
/**
- * Returns the maximum absolute amplitude of the audio most recently sampled. Returns
- * {@link #AUDIO_AMPLITUDE_NONE} if audio is disabled.
+ * Returns the maximum absolute amplitude of the audio most recently sampled in the past 2
+ * nanoseconds
*
* <p>The amplitude is the maximum absolute value over all channels which the audio was
* most recently sampled from.
@@ -177,10 +176,11 @@
* <p>Amplitude is a relative measure of the maximum sound pressure/voltage range of the device
* microphone.
*
+ * <p>Returns {@link #AUDIO_AMPLITUDE_NONE} if audio is disabled.
* <p>The amplitude value returned will be a double between {@code 0} and {@code 1}.
+ *
*/
@OptIn(markerClass = ExperimentalAudioApi.class)
- @RestrictTo(RestrictTo.Scope.LIBRARY)
public double getAudioAmplitude() {
if (getAudioState() == AUDIO_STATE_DISABLED) {
return AUDIO_AMPLITUDE_NONE;
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/ExperimentalAudioApi.java b/camera/camera-video/src/main/java/androidx/camera/video/ExperimentalAudioApi.java
index bf07a6e..99de642 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/ExperimentalAudioApi.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/ExperimentalAudioApi.java
@@ -19,16 +19,14 @@
import static java.lang.annotation.RetentionPolicy.CLASS;
import androidx.annotation.RequiresOptIn;
-import androidx.annotation.RestrictTo;
import java.lang.annotation.Retention;
/**
- * Denotes that the methods on retrieving audio amplitude data are experimental and may
+ * Denotes that the annotated element relates to an experimental audio feature and may
* change in a future release.
*/
@Retention(CLASS)
@RequiresOptIn
-@RestrictTo(RestrictTo.Scope.LIBRARY)
public @interface ExperimentalAudioApi {
}
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java b/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
index d39f6dc..8e8bbd6 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
@@ -905,7 +905,7 @@
sessionConfigBuilder.clearSurfaces();
DynamicRange dynamicRange = streamSpec.getDynamicRange();
- if (!isStreamError) {
+ if (!isStreamError && mDeferrableSurface != null) {
if (isStreamActive) {
sessionConfigBuilder.addSurface(mDeferrableSurface, dynamicRange);
} else {
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/PreviewDelayWhenVideoCaptureIsBoundQuirk.java b/camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/PreviewDelayWhenVideoCaptureIsBoundQuirk.java
index efffa0c..558d22d 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/PreviewDelayWhenVideoCaptureIsBoundQuirk.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/PreviewDelayWhenVideoCaptureIsBoundQuirk.java
@@ -21,22 +21,20 @@
import androidx.annotation.RequiresApi;
import androidx.camera.core.impl.Quirk;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Locale;
-import java.util.Set;
-
/**
* <p>QuirkSummary
* Bug Id: b/223643510
* Description: Quirk indicates Preview is delayed on some Huawei devices when the Preview uses
- * certain resolutions and VideoCapture is bound.
+ * certain resolutions and VideoCapture is bound. The quirk applies on all
+ * Huawei devices since there is a certain number of unknown devices with this
+ * issue.
* Device(s): Some Huawei devices.
*/
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
public class PreviewDelayWhenVideoCaptureIsBoundQuirk implements Quirk {
- private static final Set<String> HUAWEI_DEVICE_LIST = new HashSet<>(Arrays.asList(
+ /*
+ Known devices:
"HWELE", // P30
"HW-02L", // P30 Pro
"HWVOG", // P30 Pro
@@ -44,15 +42,16 @@
"HWLYA", // Mate 20 Pro
"HWCOL", // Honor 10
"HWPAR" // Nova 3
- ));
- private static final Set<String> HUAWEI_MODEL_LIST = new HashSet<>(Arrays.asList(
+ Known models:
"ELS-AN00", "ELS-TN00", "ELS-NX9", "ELS-N04" // P40 Pro
- ));
+
+ Known others:
+ mate40
+ honor v2
+ */
static boolean load() {
- return "Huawei".equalsIgnoreCase(Build.MANUFACTURER)
- && (HUAWEI_DEVICE_LIST.contains(Build.DEVICE.toUpperCase(Locale.US))
- || HUAWEI_MODEL_LIST.contains(Build.MODEL.toUpperCase(Locale.US)));
+ return "Huawei".equalsIgnoreCase(Build.MANUFACTURER);
}
}
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/CameraController.java b/camera/camera-view/src/main/java/androidx/camera/view/CameraController.java
index d92bad2..b76e3c6 100644
--- a/camera/camera-view/src/main/java/androidx/camera/view/CameraController.java
+++ b/camera/camera-view/src/main/java/androidx/camera/view/CameraController.java
@@ -655,13 +655,17 @@
/**
* Captures a new still image and saves to a file along with application specified metadata.
*
- * <p> The callback will be called only once for every invocation of this method.
+ * <p>The callback will be called only once for every invocation of this method.
*
- * <p> By default, the saved image is mirrored to match the output of the preview if front
+ * <p>By default, the saved image is mirrored to match the output of the preview if front
* camera is used. To override this behavior, the app needs to explicitly set the flag to
* {@code false} using {@link ImageCapture.Metadata#setReversedHorizontal} and
* {@link ImageCapture.OutputFileOptions.Builder#setMetadata}.
*
+ * <p>The saved image is cropped to match the aspect ratio of the {@link PreviewView}. To
+ * take a picture with the maximum available resolution, make sure that the
+ * {@link PreviewView}'s aspect ratio is 4:3.
+ *
* @param outputFileOptions Options to store the newly captured image.
* @param executor The executor in which the callback methods will be run.
* @param imageSavedCallback Callback to be called for the newly captured image.
@@ -1773,8 +1777,8 @@
* <p>Valid zoom values range from {@link ZoomState#getMinZoomRatio()} to
* {@link ZoomState#getMaxZoomRatio()}.
*
- * <p> No-ops if the camera is not ready. The {@link ListenableFuture} completes successfully
- * in this case.
+ * <p>If the value is set before the camera is ready, {@link CameraController} waits for the
+ * camera to be ready and then sets the zoom ratio.
*
* @param zoomRatio The requested zoom ratio.
* @return a {@link ListenableFuture} which is finished when camera is set to the given ratio.
@@ -1797,13 +1801,13 @@
/**
* Sets current zoom by a linear zoom value ranging from 0f to 1.0f.
*
- * <p> LinearZoom 0f represents the minimum zoom while linearZoom 1.0f represents the maximum
+ * <p>LinearZoom 0f represents the minimum zoom while linearZoom 1.0f represents the maximum
* zoom. The advantage of linearZoom is that it ensures the field of view (FOV) varies
* linearly with the linearZoom value, for use with slider UI elements (while
* {@link #setZoomRatio(float)} works well for pinch-zoom gestures).
*
- * <p> No-ops if the camera is not ready. The {@link ListenableFuture} completes successfully
- * in this case.
+ * <p>If the value is set before the camera is ready, {@link CameraController} waits for the
+ * camera to be ready and then sets the linear zoom.
*
* @return a {@link ListenableFuture} which is finished when camera is set to the given ratio.
* It fails with {@link CameraControl.OperationCanceledException} if there is newer value
@@ -1840,8 +1844,8 @@
/**
* Enable the torch or disable the torch.
*
- * <p> No-ops if the camera is not ready. The {@link ListenableFuture} completes successfully
- * in this case.
+ * <p>If the value is set before the camera is ready, {@link CameraController} waits for the
+ * camera to be ready and then enables the torch.
*
* @param torchEnabled true to turn on the torch, false to turn it off.
* @return A {@link ListenableFuture} which is successful when the torch was changed to the
diff --git a/camera/integration-tests/coretestapp/lint-baseline.xml b/camera/integration-tests/coretestapp/lint-baseline.xml
index 4cbca68..78ea985 100644
--- a/camera/integration-tests/coretestapp/lint-baseline.xml
+++ b/camera/integration-tests/coretestapp/lint-baseline.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.2.0-alpha15" type="baseline" client="gradle" dependencies="false" name="AGP (8.2.0-alpha15)" variant="all" version="8.2.0-alpha15">
+<issues format="6" by="lint 8.2.0-beta01" type="baseline" client="gradle" dependencies="false" name="AGP (8.2.0-beta01)" variant="all" version="8.2.0-beta01">
<issue
id="BanThreadSleep"
@@ -12,108 +12,90 @@
<issue
id="RestrictedApiAndroidX"
- message="CameraInfoInternal can only be accessed from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
- errorLine1=" if (cameraInfo instanceof CameraInfoInternal) {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~">
+ message="FileUtil.canDeviceWriteToMediaStore can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" if (!canDeviceWriteToMediaStore()) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/camera/integration/core/CameraXActivity.java"/>
</issue>
<issue
id="RestrictedApiAndroidX"
- message="DeviceQuirks.getAll can only be called from within the same library (androidx.camera:camera-video)"
- errorLine1=" Quirks deviceQuirks = DeviceQuirks.getAll();"
- errorLine2=" ~~~~~~">
+ message="FileUtil.generateVideoFileOutputOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" this, generateVideoFileOutputOptions(fileName, extension));"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/camera/integration/core/CameraXActivity.java"/>
</issue>
<issue
id="RestrictedApiAndroidX"
- message="CameraInfoInternal can only be accessed from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
- errorLine1=" Quirks cameraQuirks = ((CameraInfoInternal) cameraInfo).getCameraQuirks();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~">
+ message="FileUtil.generateVideoFileOutputOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" this, generateVideoFileOutputOptions(fileName, extension));"
+ errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/camera/integration/core/CameraXActivity.java"/>
</issue>
<issue
id="RestrictedApiAndroidX"
- message="CameraInfoInternal.getCameraQuirks can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
- errorLine1=" Quirks cameraQuirks = ((CameraInfoInternal) cameraInfo).getCameraQuirks();"
- errorLine2=" ~~~~~~~~~~~~~~~">
+ message="FileUtil.generateVideoFileOutputOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" this, generateVideoFileOutputOptions(fileName, extension));"
+ errorLine2=" ~~~~~~~~~">
<location
file="src/main/java/androidx/camera/integration/core/CameraXActivity.java"/>
</issue>
<issue
id="RestrictedApiAndroidX"
- message="Quirks.contains can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
- errorLine1=" if (deviceQuirks.contains(CrashWhenTakingPhotoWithAutoFlashAEModeQuirk.class)"
- errorLine2=" ~~~~~~~~">
+ message="FileUtil.generateVideoMediaStoreOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" generateVideoMediaStoreOptions(getContentResolver(), fileName));"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/camera/integration/core/CameraXActivity.java"/>
</issue>
<issue
id="RestrictedApiAndroidX"
- message="Quirks.contains can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
- errorLine1=" if (deviceQuirks.contains(CrashWhenTakingPhotoWithAutoFlashAEModeQuirk.class)"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ message="FileUtil.generateVideoMediaStoreOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" generateVideoMediaStoreOptions(getContentResolver(), fileName));"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/camera/integration/core/CameraXActivity.java"/>
</issue>
<issue
id="RestrictedApiAndroidX"
- message="Quirks.contains can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
- errorLine1=" || cameraQuirks.contains(ImageCaptureFailWithAutoFlashQuirk.class)"
- errorLine2=" ~~~~~~~~">
+ message="FileUtil.generateVideoMediaStoreOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" generateVideoMediaStoreOptions(getContentResolver(), fileName));"
+ errorLine2=" ~~~~~~~~">
<location
file="src/main/java/androidx/camera/integration/core/CameraXActivity.java"/>
</issue>
<issue
id="RestrictedApiAndroidX"
- message="Quirks.contains can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
- errorLine1=" || cameraQuirks.contains(ImageCaptureFailWithAutoFlashQuirk.class)"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ message="FileUtil.getAbsolutePathFromUri can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" videoFilePath = getAbsolutePathFromUri("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/camera/integration/core/CameraXActivity.java"/>
</issue>
<issue
id="RestrictedApiAndroidX"
- message="Quirks.contains can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
- errorLine1=" || cameraQuirks.contains(ImageCaptureFlashNotFireQuirk.class)) {"
- errorLine2=" ~~~~~~~~">
+ message="FileUtil.getAbsolutePathFromUri can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" getApplicationContext().getContentResolver(),"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/camera/integration/core/CameraXActivity.java"/>
</issue>
<issue
id="RestrictedApiAndroidX"
- message="Quirks.contains can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
- errorLine1=" || cameraQuirks.contains(ImageCaptureFlashNotFireQuirk.class)) {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/camera/integration/core/CameraXActivity.java"/>
- </issue>
-
- <issue
- id="RestrictedApiAndroidX"
- message="DeviceQuirks.get can only be called from within the same library (androidx.camera:camera-video)"
- errorLine1=" if (DeviceQuirks.get(MediaStoreVideoCannotWrite.class) != null) {"
- errorLine2=" ~~~">
- <location
- file="src/main/java/androidx/camera/integration/core/CameraXActivity.java"/>
- </issue>
-
- <issue
- id="RestrictedApiAndroidX"
- message="DeviceQuirks.get can only be called from within the same library (androidx.camera:camera-video)"
- errorLine1=" if (DeviceQuirks.get(MediaStoreVideoCannotWrite.class) != null) {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ message="FileUtil.getAbsolutePathFromUri can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" uri"
+ errorLine2=" ~~~">
<location
file="src/main/java/androidx/camera/integration/core/CameraXActivity.java"/>
</issue>
@@ -183,6 +165,69 @@
<issue
id="RestrictedApiAndroidX"
+ message="FileUtil.createParentFolder can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" if (createParentFolder(pictureFolder)) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/camera/integration/core/CameraXActivity.java"/>
+ </issue>
+
+ <issue
+ id="RestrictedApiAndroidX"
+ message="FileUtil.createParentFolder can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" if (createParentFolder(pictureFolder)) {"
+ errorLine2=" ~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/camera/integration/core/CameraXActivity.java"/>
+ </issue>
+
+ <issue
+ id="RestrictedApiAndroidX"
+ message="FileUtil.getAbsolutePathFromUri can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" getAbsolutePathFromUri(getApplicationContext().getContentResolver(),"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/camera/integration/core/CameraXActivity.java"/>
+ </issue>
+
+ <issue
+ id="RestrictedApiAndroidX"
+ message="FileUtil.getAbsolutePathFromUri can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" getAbsolutePathFromUri(getApplicationContext().getContentResolver(),"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/camera/integration/core/CameraXActivity.java"/>
+ </issue>
+
+ <issue
+ id="RestrictedApiAndroidX"
+ message="FileUtil.getAbsolutePathFromUri can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" MediaStore.Video.Media.EXTERNAL_CONTENT_URI);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/camera/integration/core/CameraXActivity.java"/>
+ </issue>
+
+ <issue
+ id="RestrictedApiAndroidX"
+ message="FileUtil.createParentFolder can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" if (videoFilePath == null || !createParentFolder(videoFilePath)) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/camera/integration/core/CameraXActivity.java"/>
+ </issue>
+
+ <issue
+ id="RestrictedApiAndroidX"
+ message="FileUtil.createParentFolder can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" if (videoFilePath == null || !createParentFolder(videoFilePath)) {"
+ errorLine2=" ~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/camera/integration/core/CameraXActivity.java"/>
+ </issue>
+
+ <issue
+ id="RestrictedApiAndroidX"
message="CameraXExecutors.mainThreadExecutor can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
errorLine1=" CameraXExecutors.mainThreadExecutor());"
errorLine2=" ~~~~~~~~~~~~~~~~~~">
@@ -192,6 +237,24 @@
<issue
id="RestrictedApiAndroidX"
+ message="FileUtil.createParentFolder can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" if (createParentFolder(pictureFolder)) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/camera/integration/core/CameraXService.java"/>
+ </issue>
+
+ <issue
+ id="RestrictedApiAndroidX"
+ message="FileUtil.createParentFolder can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" if (createParentFolder(pictureFolder)) {"
+ errorLine2=" ~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/camera/integration/core/CameraXService.java"/>
+ </issue>
+
+ <issue
+ id="RestrictedApiAndroidX"
message="TransformationInfo.hasCameraTransform can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
errorLine1=" mHasCameraTransform = transformationInfo.hasCameraTransform();"
errorLine2=" ~~~~~~~~~~~~~~~~~~">
@@ -264,34 +327,34 @@
<issue
id="RestrictedApiAndroidX"
- message="E2ETestUtil.canDeviceWriteToMediaStore can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
- errorLine1=" if (E2ETestUtil.canDeviceWriteToMediaStore()) {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ message="FileUtil.canDeviceWriteToMediaStore can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" if (FileUtil.canDeviceWriteToMediaStore()) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/camera/integration/core/VideoCameraSwitchingActivity.java"/>
</issue>
<issue
id="RestrictedApiAndroidX"
- message="E2ETestUtil.generateVideoMediaStoreOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
- errorLine1=" E2ETestUtil.generateVideoMediaStoreOptions(this.getContentResolver(),"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ message="FileUtil.generateVideoMediaStoreOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" FileUtil.generateVideoMediaStoreOptions(this.getContentResolver(),"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/camera/integration/core/VideoCameraSwitchingActivity.java"/>
</issue>
<issue
id="RestrictedApiAndroidX"
- message="E2ETestUtil.generateVideoMediaStoreOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
- errorLine1=" E2ETestUtil.generateVideoMediaStoreOptions(this.getContentResolver(),"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ message="FileUtil.generateVideoMediaStoreOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" FileUtil.generateVideoMediaStoreOptions(this.getContentResolver(),"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/camera/integration/core/VideoCameraSwitchingActivity.java"/>
</issue>
<issue
id="RestrictedApiAndroidX"
- message="E2ETestUtil.generateVideoMediaStoreOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ message="FileUtil.generateVideoMediaStoreOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
errorLine1=" videoFileName));"
errorLine2=" ~~~~~~~~~~~~~">
<location
@@ -300,52 +363,52 @@
<issue
id="RestrictedApiAndroidX"
- message="E2ETestUtil.generateVideoFileOutputOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
- errorLine1=" E2ETestUtil.generateVideoFileOutputOptions(videoFileName, "mp4"));"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ message="FileUtil.generateVideoFileOutputOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" FileUtil.generateVideoFileOutputOptions(videoFileName, "mp4"));"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/camera/integration/core/VideoCameraSwitchingActivity.java"/>
</issue>
<issue
id="RestrictedApiAndroidX"
- message="E2ETestUtil.generateVideoFileOutputOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
- errorLine1=" E2ETestUtil.generateVideoFileOutputOptions(videoFileName, "mp4"));"
- errorLine2=" ~~~~~~~~~~~~~">
+ message="FileUtil.generateVideoFileOutputOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" FileUtil.generateVideoFileOutputOptions(videoFileName, "mp4"));"
+ errorLine2=" ~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/camera/integration/core/VideoCameraSwitchingActivity.java"/>
</issue>
<issue
id="RestrictedApiAndroidX"
- message="E2ETestUtil.generateVideoFileOutputOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
- errorLine1=" E2ETestUtil.generateVideoFileOutputOptions(videoFileName, "mp4"));"
- errorLine2=" ~~~~~">
+ message="FileUtil.generateVideoFileOutputOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" FileUtil.generateVideoFileOutputOptions(videoFileName, "mp4"));"
+ errorLine2=" ~~~~~">
<location
file="src/main/java/androidx/camera/integration/core/VideoCameraSwitchingActivity.java"/>
</issue>
<issue
id="RestrictedApiAndroidX"
- message="E2ETestUtil.writeTextToExternalFile can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
- errorLine1=" E2ETestUtil.writeTextToExternalFile(information,"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ message="FileUtil.writeTextToExternalFile can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" FileUtil.writeTextToExternalFile(information,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/camera/integration/core/VideoCameraSwitchingActivity.java"/>
</issue>
<issue
id="RestrictedApiAndroidX"
- message="E2ETestUtil.writeTextToExternalFile can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
- errorLine1=" E2ETestUtil.writeTextToExternalFile(information,"
- errorLine2=" ~~~~~~~~~~~">
+ message="FileUtil.writeTextToExternalFile can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" FileUtil.writeTextToExternalFile(information,"
+ errorLine2=" ~~~~~~~~~~~">
<location
file="src/main/java/androidx/camera/integration/core/VideoCameraSwitchingActivity.java"/>
</issue>
<issue
id="RestrictedApiAndroidX"
- message="E2ETestUtil.writeTextToExternalFile can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ message="FileUtil.writeTextToExternalFile can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
errorLine1=" generateFileName(INFO_FILE_PREFIX, false), "txt");"
errorLine2=" ~~~~~~~~~~~~~~~~">
<location
@@ -354,7 +417,7 @@
<issue
id="RestrictedApiAndroidX"
- message="E2ETestUtil.writeTextToExternalFile can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ message="FileUtil.writeTextToExternalFile can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
errorLine1=" generateFileName(INFO_FILE_PREFIX, false), "txt");"
errorLine2=" ~~~~~">
<location
@@ -363,36 +426,36 @@
<issue
id="RestrictedApiAndroidX"
- message="E2ETestUtil.isFileNameValid can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
- errorLine1=" if (!isUnique && !E2ETestUtil.isFileNameValid(prefix)) {"
- errorLine2=" ~~~~~~~~~~~~~~~">
+ message="FileUtil.isFileNameValid can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" if (!isUnique && !FileUtil.isFileNameValid(prefix)) {"
+ errorLine2=" ~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/camera/integration/core/VideoCameraSwitchingActivity.java"/>
</issue>
<issue
id="RestrictedApiAndroidX"
- message="E2ETestUtil.isFileNameValid can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
- errorLine1=" if (!isUnique && !E2ETestUtil.isFileNameValid(prefix)) {"
- errorLine2=" ~~~~~~">
+ message="FileUtil.isFileNameValid can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" if (!isUnique && !FileUtil.isFileNameValid(prefix)) {"
+ errorLine2=" ~~~~~~">
<location
file="src/main/java/androidx/camera/integration/core/VideoCameraSwitchingActivity.java"/>
</issue>
<issue
id="RestrictedApiAndroidX"
- message="E2ETestUtil.isFileNameValid can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
- errorLine1=" if (E2ETestUtil.isFileNameValid(prefix)) {"
- errorLine2=" ~~~~~~~~~~~~~~~">
+ message="FileUtil.isFileNameValid can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" if (FileUtil.isFileNameValid(prefix)) {"
+ errorLine2=" ~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/camera/integration/core/VideoCameraSwitchingActivity.java"/>
</issue>
<issue
id="RestrictedApiAndroidX"
- message="E2ETestUtil.isFileNameValid can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
- errorLine1=" if (E2ETestUtil.isFileNameValid(prefix)) {"
- errorLine2=" ~~~~~~">
+ message="FileUtil.isFileNameValid can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" if (FileUtil.isFileNameValid(prefix)) {"
+ errorLine2=" ~~~~~~">
<location
file="src/main/java/androidx/camera/integration/core/VideoCameraSwitchingActivity.java"/>
</issue>
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CameraXServiceTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CameraXServiceTest.kt
index 0810339..df79650 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CameraXServiceTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CameraXServiceTest.kt
@@ -16,6 +16,7 @@
package androidx.camera.integration.core
+import android.Manifest
import android.app.ActivityManager
import android.app.Service
import android.content.ComponentName
@@ -32,6 +33,7 @@
import androidx.camera.core.ImageCapture
import androidx.camera.core.UseCase
import androidx.camera.integration.core.CameraXService.ACTION_BIND_USE_CASES
+import androidx.camera.integration.core.CameraXService.ACTION_TAKE_PICTURE
import androidx.camera.integration.core.CameraXService.EXTRA_IMAGE_ANALYSIS_ENABLED
import androidx.camera.integration.core.CameraXService.EXTRA_IMAGE_CAPTURE_ENABLED
import androidx.camera.integration.core.CameraXService.EXTRA_VIDEO_CAPTURE_ENABLED
@@ -45,6 +47,7 @@
import androidx.lifecycle.Lifecycle
import androidx.test.core.app.ApplicationProvider
import androidx.test.filters.LargeTest
+import androidx.test.rule.GrantPermissionRule
import androidx.testutils.LifecycleOwnerUtils
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.TimeUnit
@@ -72,6 +75,12 @@
)
@get:Rule
+ val permissionRule: GrantPermissionRule =
+ GrantPermissionRule.grant(
+ Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ )
+
+ @get:Rule
val cameraPipeConfigTestRule = CameraPipeConfigTestRule(
active = implName == CameraPipeConfig::class.simpleName,
)
@@ -105,6 +114,7 @@
@After
fun tearDown() {
if (this::service.isInitialized) {
+ service.deleteSavedMediaFiles()
context.unbindService(serviceConnection)
context.stopService(createServiceIntent())
@@ -165,6 +175,21 @@
assertThat(latch.await(15, TimeUnit.SECONDS)).isTrue()
}
+ @Test
+ fun canTakePicture() = runBlocking {
+ // Arrange.
+ context.startService(createServiceIntent(ACTION_BIND_USE_CASES).apply {
+ putExtra(EXTRA_IMAGE_CAPTURE_ENABLED, true)
+ })
+
+ // Act.
+ val latch = service.acquireTakePictureCountDownLatch()
+ context.startService(createServiceIntent(ACTION_TAKE_PICTURE))
+
+ // Assert.
+ assertThat(latch.await(15, TimeUnit.SECONDS)).isTrue()
+ }
+
private fun createServiceIntent(action: String? = null) =
Intent(context, CameraXService::class.java).apply {
action?.let { setAction(it) }
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageCaptureTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageCaptureTest.kt
index abb5eaed..e10ff38 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageCaptureTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageCaptureTest.kt
@@ -64,6 +64,7 @@
import androidx.camera.core.impl.utils.CameraOrientationUtil
import androidx.camera.core.impl.utils.Exif
import androidx.camera.core.internal.compat.workaround.ExifRotationAvailability
+import androidx.camera.core.internal.compat.workaround.InvalidJpegDataParser
import androidx.camera.core.resolutionselector.AspectRatioStrategy
import androidx.camera.core.resolutionselector.ResolutionFilter
import androidx.camera.core.resolutionselector.ResolutionSelector
@@ -1802,6 +1803,89 @@
assertThat(imageProperties.size).isEqualTo(maxHighResolutionOutputSize)
}
+ /**
+ * See b/288828159 for the detailed info of the issue
+ */
+ @Test
+ fun jpegImageZeroPaddingDataDetectionTest(): Unit = runBlocking {
+ val imageCapture = ImageCapture.Builder().build()
+
+ withContext(Dispatchers.Main) {
+ cameraProvider.bindToLifecycle(fakeLifecycleOwner, BACK_SELECTOR, imageCapture)
+ }
+
+ val latch = CountdownDeferred(1)
+ var errors: Exception? = null
+
+ val callback = object : ImageCapture.OnImageCapturedCallback() {
+ override fun onCaptureSuccess(image: ImageProxy) {
+ val planes = image.planes
+ val buffer = planes[0].buffer
+ val data = ByteArray(buffer.capacity())
+ buffer.rewind()
+ buffer[data]
+
+ image.close()
+
+ val invalidJpegDataParser = InvalidJpegDataParser()
+
+ // Only checks the unnecessary zero padding data when the device is not included in
+ // the LargeJpegImageQuirk device list. InvalidJpegDataParser#getValidDataLength()
+ // should have returned the valid data length to avoid the extremely large JPEG
+ // file issue.
+ if (invalidJpegDataParser.getValidDataLength(data) == data.size &&
+ containsZeroPaddingDataAfterEoi(data)
+ ) {
+ errors = Exception("UNNECESSARY_JPEG_ZERO_PADDING_DATA_DETECTED!")
+ }
+
+ latch.countDown()
+ }
+
+ override fun onError(exception: ImageCaptureException) {
+ errors = exception
+ latch.countDown()
+ }
+ }
+
+ imageCapture.takePicture(mainExecutor, callback)
+
+ // Wait for the signal that the image has been captured.
+ assertThat(withTimeoutOrNull(CAPTURE_TIMEOUT) {
+ latch.await()
+ }).isNotNull()
+ assertThat(errors).isNull()
+ }
+
+ /**
+ * This util function is only used to detect the unnecessary zero padding data after EOI. It
+ * will directly return false when it fails to parse the JPEG byte array data.
+ */
+ private fun containsZeroPaddingDataAfterEoi(bytes: ByteArray): Boolean {
+ val jfifEoiMarkEndPosition = InvalidJpegDataParser.getJfifEoiMarkEndPosition(bytes)
+
+ // Directly returns false when EOI mark can't be found.
+ if (jfifEoiMarkEndPosition == -1) {
+ return false
+ }
+
+ // Will check 1mb data to know whether unnecessary zero padding data exists or not.
+ // Directly returns false when the data length is long enough
+ val dataLengthToDetect = 1_000_000
+ if (jfifEoiMarkEndPosition + dataLengthToDetect > bytes.size) {
+ return false
+ }
+
+ // Checks that there are at least continuous 1mb of unnecessary zero padding data after EOI
+ for (position in jfifEoiMarkEndPosition..jfifEoiMarkEndPosition + dataLengthToDetect) {
+ if (bytes[position] != 0x00.toByte()) {
+ return false
+ }
+ }
+
+ return true
+ }
+
private fun createNonRotatedConfiguration(): ImageCaptureConfig {
// Create a configuration with target rotation that matches the sensor rotation.
// This assumes a back-facing camera (facing away from screen)
diff --git a/camera/integration-tests/coretestapp/src/main/AndroidManifest.xml b/camera/integration-tests/coretestapp/src/main/AndroidManifest.xml
index 4954bfb..3fd027e 100644
--- a/camera/integration-tests/coretestapp/src/main/AndroidManifest.xml
+++ b/camera/integration-tests/coretestapp/src/main/AndroidManifest.xml
@@ -83,6 +83,7 @@
android:label="CameraX Service">
<intent-filter>
<action android:name="androidx.camera.integration.core.intent.action.BIND_USE_CASES" />
+ <action android:name="androidx.camera.integration.core.intent.action.TAKE_PICTURE" />
</intent-filter>
</service>
</application>
diff --git a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
index f89839e..b304a9a 100644
--- a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
+++ b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
@@ -25,21 +25,25 @@
import static androidx.camera.core.ImageCapture.FLASH_MODE_OFF;
import static androidx.camera.core.ImageCapture.FLASH_MODE_ON;
import static androidx.camera.core.MirrorMode.MIRROR_MODE_ON_FRONT_ONLY;
+import static androidx.camera.testing.impl.FileUtil.canDeviceWriteToMediaStore;
+import static androidx.camera.testing.impl.FileUtil.createParentFolder;
+import static androidx.camera.testing.impl.FileUtil.generateVideoFileOutputOptions;
+import static androidx.camera.testing.impl.FileUtil.generateVideoMediaStoreOptions;
+import static androidx.camera.testing.impl.FileUtil.getAbsolutePathFromUri;
import static androidx.camera.video.VideoRecordEvent.Finalize.ERROR_DURATION_LIMIT_REACHED;
import static androidx.camera.video.VideoRecordEvent.Finalize.ERROR_FILE_SIZE_LIMIT_REACHED;
import static androidx.camera.video.VideoRecordEvent.Finalize.ERROR_INSUFFICIENT_STORAGE;
import static androidx.camera.video.VideoRecordEvent.Finalize.ERROR_NONE;
import static androidx.camera.video.VideoRecordEvent.Finalize.ERROR_SOURCE_INACTIVE;
+import static java.util.Objects.requireNonNull;
+
import android.Manifest;
import android.annotation.SuppressLint;
-import android.content.ContentResolver;
import android.content.ContentValues;
-import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
-import android.database.Cursor;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.display.DisplayManager;
import android.media.MediaScannerConnection;
@@ -126,8 +130,6 @@
import androidx.camera.video.VideoCapabilities;
import androidx.camera.video.VideoCapture;
import androidx.camera.video.VideoRecordEvent;
-import androidx.camera.video.internal.compat.quirk.DeviceQuirks;
-import androidx.camera.video.internal.compat.quirk.MediaStoreVideoCannotWrite;
import androidx.core.content.ContextCompat;
import androidx.core.math.MathUtils;
import androidx.core.util.Consumer;
@@ -357,6 +359,9 @@
@Override
public void onSuccess(@Nullable Integer result) {
+ if (result == null) {
+ return;
+ }
CameraInfo cameraInfo = getCameraInfo();
if (cameraInfo != null) {
ExposureState exposureState = cameraInfo.getExposureState();
@@ -478,7 +483,7 @@
@VisibleForTesting
public void resetViewIdlingResource() {
mPreviewFrameCount.set(0);
- // Make the view idling resource non-idle, until required framecount achieved.
+ // Make the view idling resource non-idle, until required frame count achieved.
if (mViewIdlingResource.isIdleNow()) {
mViewIdlingResource.increment();
}
@@ -596,14 +601,17 @@
case IDLE:
createDefaultVideoFolderIfNotExist();
final PendingRecording pendingRecording;
- if (DeviceQuirks.get(MediaStoreVideoCannotWrite.class) != null) {
- // Use FileOutputOption for devices in MediaStoreVideoCannotWrite Quirk.
- pendingRecording = getVideoCapture().getOutput().prepareRecording(
- this, getNewVideoFileOutputOptions());
- } else {
+ String fileName = "video_" + System.currentTimeMillis();
+ String extension = "mp4";
+ if (canDeviceWriteToMediaStore()) {
// Use MediaStoreOutputOptions for public share media storage.
pendingRecording = getVideoCapture().getOutput().prepareRecording(
- this, getNewVideoOutputMediaStoreOptions());
+ this,
+ generateVideoMediaStoreOptions(getContentResolver(), fileName));
+ } else {
+ // Use FileOutputOption for devices in MediaStoreVideoCannotWrite Quirk.
+ pendingRecording = getVideoCapture().getOutput().prepareRecording(
+ this, generateVideoFileOutputOptions(fileName, extension));
}
resetVideoSavedIdlingResource();
@@ -806,32 +814,6 @@
}
}
- @NonNull
- private MediaStoreOutputOptions getNewVideoOutputMediaStoreOptions() {
- String videoFileName = "video_" + System.currentTimeMillis();
- ContentValues contentValues = new ContentValues();
- contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4");
- contentValues.put(MediaStore.Video.Media.TITLE, videoFileName);
- contentValues.put(MediaStore.Video.Media.DISPLAY_NAME, videoFileName);
- contentValues.put(MediaStore.Video.Media.DATE_ADDED, System.currentTimeMillis() / 1000);
- contentValues.put(MediaStore.Video.Media.DATE_TAKEN, System.currentTimeMillis());
- return new MediaStoreOutputOptions.Builder(getContentResolver(),
- MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
- .setContentValues(contentValues)
- .build();
- }
-
- @NonNull
- private FileOutputOptions getNewVideoFileOutputOptions() {
- String videoFileName = "video_" + System.currentTimeMillis() + ".mp4";
- File videoFolder = Environment.getExternalStoragePublicDirectory(
- Environment.DIRECTORY_MOVIES);
- if (!videoFolder.exists() && !videoFolder.mkdirs()) {
- Log.e(TAG, "Failed to create directory: " + videoFolder);
- }
- return new FileOutputOptions.Builder(new File(videoFolder, videoFileName)).build();
- }
-
private void updateRecordingStats(@NonNull RecordingStats stats) {
double durationMs = TimeUnit.NANOSECONDS.toMillis(stats.getRecordedDurationNanos());
// Show megabytes in International System of Units (SI)
@@ -890,7 +872,7 @@
Toast.LENGTH_SHORT).show());
if (mSessionImagesUriSet != null) {
mSessionImagesUriSet.add(
- Objects.requireNonNull(
+ requireNonNull(
outputFileResults.getSavedUri()));
}
}
@@ -956,7 +938,7 @@
Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
}
} catch (IllegalArgumentException e) {
- Toast.makeText(this, "Failed to swich Camera. Error:" + e.getMessage(),
+ Toast.makeText(this, "Failed to switch Camera. Error:" + e.getMessage(),
Toast.LENGTH_SHORT).show();
}
});
@@ -1008,8 +990,8 @@
private void setUpTorchButton() {
mTorchButton.setOnClickListener(v -> {
- Objects.requireNonNull(getCameraInfo());
- Objects.requireNonNull(getCameraControl());
+ requireNonNull(getCameraInfo());
+ requireNonNull(getCameraControl());
Integer torchState = getCameraInfo().getTorchState().getValue();
boolean toggledState = !Objects.equals(torchState, TorchState.ON);
Log.d(TAG, "Set camera torch: " + toggledState);
@@ -1029,8 +1011,8 @@
private void setUpEVButton() {
mPlusEV.setOnClickListener(v -> {
- Objects.requireNonNull(getCameraInfo());
- Objects.requireNonNull(getCameraControl());
+ requireNonNull(getCameraInfo());
+ requireNonNull(getCameraControl());
ExposureState exposureState = getCameraInfo().getExposureState();
Range<Integer> range = exposureState.getExposureCompensationRange();
@@ -1048,8 +1030,8 @@
});
mDecEV.setOnClickListener(v -> {
- Objects.requireNonNull(getCameraInfo());
- Objects.requireNonNull(getCameraControl());
+ requireNonNull(getCameraInfo());
+ requireNonNull(getCameraControl());
ExposureState exposureState = getCameraInfo().getExposureState();
Range<Integer> range = exposureState.getExposureCompensationRange();
@@ -1120,12 +1102,12 @@
ViewGroup.LayoutParams lp = viewFinderStub.getLayoutParams();
if (orientation == Configuration.ORIENTATION_PORTRAIT) {
lp.width = displayMetrics.widthPixels;
- lp.height = (int) (displayMetrics.widthPixels / ratio.getDenominator()
- * ratio.getNumerator());
+ lp.height = displayMetrics.widthPixels / ratio.getDenominator()
+ * ratio.getNumerator();
} else {
lp.height = displayMetrics.heightPixels;
- lp.width = (int) (displayMetrics.heightPixels / ratio.getDenominator()
- * ratio.getNumerator());
+ lp.width = displayMetrics.heightPixels / ratio.getDenominator()
+ * ratio.getNumerator();
}
viewFinderStub.setLayoutParams(lp);
}
@@ -1329,7 +1311,7 @@
};
DisplayManager dpyMgr =
- Objects.requireNonNull((DisplayManager) getSystemService(Context.DISPLAY_SERVICE));
+ requireNonNull(ContextCompat.getSystemService(this, DisplayManager.class));
dpyMgr.registerDisplayListener(mDisplayListener, new Handler(Looper.getMainLooper()));
StrictMode.VmPolicy vmPolicy =
@@ -1446,7 +1428,7 @@
public void onDestroy() {
super.onDestroy();
DisplayManager dpyMgr =
- Objects.requireNonNull((DisplayManager) getSystemService(Context.DISPLAY_SERVICE));
+ requireNonNull(ContextCompat.getSystemService(this, DisplayManager.class));
dpyMgr.unregisterDisplayListener(mDisplayListener);
mPreviewRenderer.shutdown();
mImageCaptureExecutorService.shutdown();
@@ -1577,12 +1559,9 @@
// Remove ImageAnalysis to check whether the new use cases combination can be supported.
if (mAnalysisToggle.isChecked()) {
mAnalysisToggle.setChecked(false);
- if (isCheckedUseCasesCombinationSupported()) {
- return;
- }
+ // No need to do further use case combination check since Preview + ImageCapture
+ // should be always supported.
}
-
- // Preview + ImageCapture should be always supported.
}
/**
@@ -1687,7 +1666,7 @@
new ActivityResultContracts.RequestMultiplePermissions(),
result -> {
for (String permission : REQUIRED_PERMISSIONS) {
- if (!Objects.requireNonNull(result.get(permission))) {
+ if (!requireNonNull(result.get(permission))) {
Toast.makeText(getApplicationContext(),
"Camera permission denied.",
Toast.LENGTH_SHORT)
@@ -1720,10 +1699,8 @@
void createDefaultPictureFolderIfNotExist() {
File pictureFolder = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES);
- if (!pictureFolder.exists()) {
- if (!pictureFolder.mkdir()) {
- Log.e(TAG, "Failed to create directory: " + pictureFolder);
- }
+ if (createParentFolder(pictureFolder)) {
+ Log.e(TAG, "Failed to create directory: " + pictureFolder);
}
}
@@ -1732,17 +1709,8 @@
String videoFilePath =
getAbsolutePathFromUri(getApplicationContext().getContentResolver(),
MediaStore.Video.Media.EXTERNAL_CONTENT_URI);
-
- // If cannot get the video path, just skip checking and create folder.
- if (videoFilePath == null) {
- return;
- }
- File videoFile = new File(videoFilePath);
-
- if (videoFile.getParentFile() != null && !videoFile.getParentFile().exists()) {
- if (!videoFile.getParentFile().mkdir()) {
- Log.e(TAG, "Failed to create directory: " + videoFile);
- }
+ if (videoFilePath == null || !createParentFolder(videoFilePath)) {
+ Log.e(TAG, "Failed to create parent directory for: " + videoFilePath);
}
}
@@ -1778,13 +1746,14 @@
ScaleGestureDetector.SimpleOnScaleGestureListener mScaleGestureListener =
new ScaleGestureDetector.SimpleOnScaleGestureListener() {
@Override
- public boolean onScale(ScaleGestureDetector detector) {
+ public boolean onScale(@NonNull ScaleGestureDetector detector) {
if (mCamera == null) {
return true;
}
CameraInfo cameraInfo = mCamera.getCameraInfo();
- float newZoom = cameraInfo.getZoomState().getValue().getZoomRatio()
+ float newZoom =
+ requireNonNull(cameraInfo.getZoomState().getValue()).getZoomRatio()
* detector.getScaleFactor();
setZoomRatio(newZoom);
return true;
@@ -1794,7 +1763,7 @@
GestureDetector.OnGestureListener onTapGestureListener =
new GestureDetector.SimpleOnGestureListener() {
@Override
- public boolean onSingleTapUp(MotionEvent e) {
+ public boolean onSingleTapUp(@NonNull MotionEvent e) {
if (mCamera == null) {
return false;
}
@@ -1834,7 +1803,8 @@
mZoomSeekBar.setMax(MAX_SEEKBAR_VALUE);
mZoomSeekBar.setProgress(
- (int) (cameraInfo.getZoomState().getValue().getLinearZoom() * MAX_SEEKBAR_VALUE));
+ (int) (requireNonNull(cameraInfo.getZoomState().getValue()).getLinearZoom()
+ * MAX_SEEKBAR_VALUE));
mZoomSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
@@ -1884,7 +1854,7 @@
private boolean is2XZoomSupported() {
CameraInfo cameraInfo = getCameraInfo();
return cameraInfo != null
- && cameraInfo.getZoomState().getValue().getMaxZoomRatio() >= 2.0f;
+ && requireNonNull(cameraInfo.getZoomState().getValue()).getMaxZoomRatio() >= 2.0f;
}
private void setUpZoomButton() {
@@ -1901,7 +1871,7 @@
CameraInfo cameraInfo = mCamera.getCameraInfo();
CameraControl cameraControl = mCamera.getCameraControl();
float clampedNewZoom = MathUtils.clamp(newZoom,
- cameraInfo.getZoomState().getValue().getMinZoomRatio(),
+ requireNonNull(cameraInfo.getZoomState().getValue()).getMinZoomRatio(),
cameraInfo.getZoomState().getValue().getMaxZoomRatio());
Log.d(TAG, "setZoomRatio ratio: " + clampedNewZoom);
@@ -1932,34 +1902,6 @@
});
}
- /** Gets the absolute path from a Uri. */
- @Nullable
- public String getAbsolutePathFromUri(@NonNull ContentResolver resolver,
- @NonNull Uri contentUri) {
- Cursor cursor = null;
- try {
- // We should include in any Media collections.
- String[] proj;
- int columnIndex;
- // MediaStore.Video.Media.DATA was deprecated in API level 29.
- proj = new String[]{MediaStore.Video.Media.DATA};
- cursor = resolver.query(contentUri, proj, null, null, null);
- columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA);
-
- cursor.moveToFirst();
- return cursor.getString(columnIndex);
- } catch (RuntimeException e) {
- Log.e(TAG, String.format(
- "Failed in getting absolute path for Uri %s with Exception %s",
- contentUri, e));
- return "";
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- }
-
private class SessionMediaUriSet {
private final Set<Uri> mSessionMediaUris;
diff --git a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXService.java b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXService.java
index 45f7201..4e5333c 100644
--- a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXService.java
+++ b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXService.java
@@ -17,15 +17,21 @@
package androidx.camera.integration.core;
import static androidx.camera.core.CameraSelector.DEFAULT_BACK_CAMERA;
+import static androidx.camera.testing.impl.FileUtil.createParentFolder;
import static com.google.common.base.Preconditions.checkNotNull;
import android.app.NotificationChannel;
import android.app.NotificationManager;
+import android.content.ContentValues;
import android.content.Intent;
+import android.net.Uri;
import android.os.Binder;
import android.os.Build;
+import android.os.Environment;
import android.os.IBinder;
+import android.os.SystemClock;
+import android.provider.MediaStore;
import android.util.Log;
import androidx.annotation.DoNotInline;
@@ -35,6 +41,7 @@
import androidx.annotation.VisibleForTesting;
import androidx.camera.core.ImageAnalysis;
import androidx.camera.core.ImageCapture;
+import androidx.camera.core.ImageCaptureException;
import androidx.camera.core.UseCase;
import androidx.camera.core.UseCaseGroup;
import androidx.camera.lifecycle.ProcessCameraProvider;
@@ -47,9 +54,18 @@
import com.google.common.util.concurrent.ListenableFuture;
+import java.io.File;
+import java.text.Format;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
@@ -64,6 +80,8 @@
// Actions
public static final String ACTION_BIND_USE_CASES =
"androidx.camera.integration.core.intent.action.BIND_USE_CASES";
+ public static final String ACTION_TAKE_PICTURE =
+ "androidx.camera.integration.core.intent.action.TAKE_PICTURE";
// Extras
public static final String EXTRA_VIDEO_CAPTURE_ENABLED = "EXTRA_VIDEO_CAPTURE_ENABLED";
@@ -73,12 +91,22 @@
private final IBinder mBinder = new CameraXServiceBinder();
////////////////////////////////////////////////////////////////////////////////////////////////
+ // Members only accessed on main thread //
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ private final Map<Class<?>, UseCase> mBoundUseCases = new HashMap<>();
+ //--------------------------------------------------------------------------------------------//
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////
// Members for testing //
////////////////////////////////////////////////////////////////////////////////////////////////
+ private final Set<Uri> mSavedImageUri = new HashSet<>();
+
@Nullable
private Consumer<Collection<UseCase>> mOnUseCaseBoundCallback;
@Nullable
private CountDownLatch mAnalysisFrameLatch;
+ @Nullable
+ private CountDownLatch mTakePictureLatch;
//--------------------------------------------------------------------------------------------//
@Override
@@ -101,6 +129,8 @@
Log.d(TAG, "onStartCommand: action = " + action + ", extras = " + intent.getExtras());
if (ACTION_BIND_USE_CASES.equals(action)) {
bindToLifecycle(intent);
+ } else if (ACTION_TAKE_PICTURE.equals(action)) {
+ takePicture();
}
}
return super.onStartCommand(intent, flags, startId);
@@ -125,6 +155,7 @@
throw new IllegalStateException(e);
}
cameraProvider.unbindAll();
+ mBoundUseCases.clear();
UseCaseGroup useCaseGroup = resolveUseCaseGroup(intent);
List<UseCase> boundUseCases = Collections.emptyList();
if (useCaseGroup != null) {
@@ -136,6 +167,9 @@
}
}
Log.d(TAG, "Bound UseCases: " + boundUseCases);
+ for (UseCase boundUseCase : boundUseCases) {
+ mBoundUseCases.put(boundUseCase.getClass(), boundUseCase);
+ }
if (mOnUseCaseBoundCallback != null) {
mOnUseCaseBoundCallback.accept(boundUseCases);
}
@@ -183,6 +217,61 @@
return checkNotNull(ContextCompat.getSystemService(this, NotificationManager.class));
}
+ @Nullable
+ private ImageCapture getImageCapture() {
+ return (ImageCapture) mBoundUseCases.get(ImageCapture.class);
+ }
+
+ private void takePicture() {
+ ImageCapture imageCapture = getImageCapture();
+ if (imageCapture == null) {
+ Log.w(TAG, "ImageCapture is not bound.");
+ return;
+ }
+ createDefaultPictureFolderIfNotExist();
+ Format formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS", Locale.US);
+ String fileName = "ServiceTestApp-" + formatter.format(Calendar.getInstance().getTime())
+ + ".jpg";
+ ContentValues contentValues = new ContentValues();
+ contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);
+ contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg");
+ ImageCapture.OutputFileOptions outputFileOptions =
+ new ImageCapture.OutputFileOptions.Builder(
+ getContentResolver(),
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+ contentValues).build();
+ long startTimeMs = SystemClock.elapsedRealtime();
+ imageCapture.takePicture(outputFileOptions,
+ ContextCompat.getMainExecutor(this),
+ new ImageCapture.OnImageSavedCallback() {
+ @Override
+ public void onImageSaved(
+ @NonNull ImageCapture.OutputFileResults outputFileResults) {
+ long durationMs = SystemClock.elapsedRealtime() - startTimeMs;
+ Log.d(TAG, "Saved image " + outputFileResults.getSavedUri()
+ + " (" + durationMs + " ms)");
+ mSavedImageUri.add(outputFileResults.getSavedUri());
+ if (mTakePictureLatch != null) {
+ mTakePictureLatch.countDown();
+ }
+ }
+
+ @Override
+ public void onError(@NonNull ImageCaptureException exception) {
+ Log.e(TAG, "Failed to save image by " + exception.getImageCaptureError(),
+ exception);
+ }
+ });
+ }
+
+ private void createDefaultPictureFolderIfNotExist() {
+ File pictureFolder = Environment.getExternalStoragePublicDirectory(
+ Environment.DIRECTORY_PICTURES);
+ if (!createParentFolder(pictureFolder)) {
+ Log.e(TAG, "Failed to create directory: " + pictureFolder);
+ }
+ }
+
private final ImageAnalysis.Analyzer mAnalyzer = image -> {
if (mAnalysisFrameLatch != null) {
mAnalysisFrameLatch.countDown();
@@ -217,11 +306,34 @@
}
@VisibleForTesting
+ @NonNull
CountDownLatch acquireAnalysisFrameCountDownLatch() {
mAnalysisFrameLatch = new CountDownLatch(3);
return mAnalysisFrameLatch;
}
+ @VisibleForTesting
+ @NonNull
+ CountDownLatch acquireTakePictureCountDownLatch() {
+ mTakePictureLatch = new CountDownLatch(1);
+ return mTakePictureLatch;
+ }
+
+ @VisibleForTesting
+ void deleteSavedMediaFiles() {
+ deleteUriSet(mSavedImageUri);
+ }
+
+ private void deleteUriSet(@NonNull Set<Uri> uriSet) {
+ for (Uri uri : uriSet) {
+ try {
+ getContentResolver().delete(uri, null, null);
+ } catch (RuntimeException e) {
+ Log.w(TAG, "Unable to delete uri: " + uri, e);
+ }
+ }
+ }
+
class CameraXServiceBinder extends Binder {
@NonNull
CameraXService getService() {
diff --git a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXViewModel.java b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXViewModel.java
index 61a1f09..620f7e0 100644
--- a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXViewModel.java
+++ b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXViewModel.java
@@ -107,7 +107,7 @@
@OptIn(markerClass = ExperimentalCameraProviderConfiguration.class)
@MainThread
public static boolean isCameraProviderUnInitializedOrSameAsParameter(
- @NonNull String cameraImplementation) {
+ @Nullable String cameraImplementation) {
if (sConfiguredCameraXCameraImplementation == null) {
return true;
@@ -116,11 +116,7 @@
sConfiguredCameraXCameraImplementation);
cameraImplementation = getCameraProviderName(cameraImplementation);
- if (currentCameraProvider.equals(cameraImplementation)) {
- return true;
- }
-
- return false;
+ return currentCameraProvider.equals(cameraImplementation);
}
/**
@@ -129,7 +125,7 @@
*/
@OptIn(markerClass = ExperimentalCameraProviderConfiguration.class)
@MainThread
- private static String getCameraProviderName(String mCameraProvider) {
+ private static String getCameraProviderName(@Nullable String mCameraProvider) {
if (mCameraProvider == null) {
mCameraProvider = CAMERA2_IMPLEMENTATION_OPTION;
}
diff --git a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/VideoCameraSwitchingActivity.java b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/VideoCameraSwitchingActivity.java
index c775b3d..976b5f3 100644
--- a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/VideoCameraSwitchingActivity.java
+++ b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/VideoCameraSwitchingActivity.java
@@ -33,7 +33,7 @@
import androidx.camera.core.Logger;
import androidx.camera.core.Preview;
import androidx.camera.lifecycle.ProcessCameraProvider;
-import androidx.camera.testing.impl.E2ETestUtil;
+import androidx.camera.testing.impl.FileUtil;
import androidx.camera.video.ExperimentalPersistentRecording;
import androidx.camera.video.PendingRecording;
import androidx.camera.video.Recorder;
@@ -232,14 +232,14 @@
final String videoFileName = generateFileName(VIDEO_FILE_PREFIX, true);
final PendingRecording pendingRecording;
- if (E2ETestUtil.canDeviceWriteToMediaStore()) {
+ if (FileUtil.canDeviceWriteToMediaStore()) {
// Use MediaStoreOutputOptions for public share media storage.
pendingRecording = mVideoCapture.getOutput().prepareRecording(this,
- E2ETestUtil.generateVideoMediaStoreOptions(this.getContentResolver(),
+ FileUtil.generateVideoMediaStoreOptions(this.getContentResolver(),
videoFileName));
} else {
pendingRecording = mVideoCapture.getOutput().prepareRecording(this,
- E2ETestUtil.generateVideoFileOutputOptions(videoFileName, "mp4"));
+ FileUtil.generateVideoFileOutputOptions(videoFileName, "mp4"));
}
mRecording = pendingRecording
.asPersistentRecording() // Perform the recording as a persistent recording.
@@ -274,17 +274,17 @@
private void exportTestInformation() {
String information = KEY_DEVICE_ORIENTATION + ": " + mDeviceOrientation;
- E2ETestUtil.writeTextToExternalFile(information,
+ FileUtil.writeTextToExternalFile(information,
generateFileName(INFO_FILE_PREFIX, false), "txt");
}
@NonNull
private String generateFileName(@Nullable String prefix, boolean isUnique) {
- if (!isUnique && !E2ETestUtil.isFileNameValid(prefix)) {
+ if (!isUnique && !FileUtil.isFileNameValid(prefix)) {
throw new IllegalArgumentException("Invalid arguments for generating file name.");
}
StringBuilder fileName = new StringBuilder();
- if (E2ETestUtil.isFileNameValid(prefix)) {
+ if (FileUtil.isFileNameValid(prefix)) {
fileName.append(prefix);
if (isUnique) {
fileName.append("_");
diff --git a/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/viewpager/ViewPagerActivityTest.kt b/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/viewpager/ViewPagerActivityTest.kt
index e3dc0e9..49a518d 100644
--- a/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/viewpager/ViewPagerActivityTest.kt
+++ b/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/viewpager/ViewPagerActivityTest.kt
@@ -39,7 +39,6 @@
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
-import androidx.test.filters.FlakyTest
import androidx.test.filters.LargeTest
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
@@ -146,7 +145,6 @@
}
// The test makes sure the TextureView surface texture keeps the same after switch.
- @FlakyTest(bugId = 230873000)
@Test
fun testPreviewViewUpdateAfterSwitch() {
launchActivity(lensFacing, cameraXConfig).use { scenario ->
diff --git a/camera/integration-tests/viewtestapp/lint-baseline.xml b/camera/integration-tests/viewtestapp/lint-baseline.xml
index acea7110..cfacbf5 100644
--- a/camera/integration-tests/viewtestapp/lint-baseline.xml
+++ b/camera/integration-tests/viewtestapp/lint-baseline.xml
@@ -264,7 +264,7 @@
<issue
id="RestrictedApiAndroidX"
- message="E2ETestUtil.canDeviceWriteToMediaStore can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ message="FileUtil.canDeviceWriteToMediaStore can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
errorLine1=" return if (canDeviceWriteToMediaStore()) {"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
@@ -273,7 +273,7 @@
<issue
id="RestrictedApiAndroidX"
- message="E2ETestUtil.generateVideoMediaStoreOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ message="FileUtil.generateVideoMediaStoreOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
errorLine1=" generateVideoMediaStoreOptions(context.contentResolver, fileName)"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
@@ -282,7 +282,7 @@
<issue
id="RestrictedApiAndroidX"
- message="E2ETestUtil.generateVideoMediaStoreOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ message="FileUtil.generateVideoMediaStoreOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
errorLine1=" generateVideoMediaStoreOptions(context.contentResolver, fileName)"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
<location
@@ -291,7 +291,7 @@
<issue
id="RestrictedApiAndroidX"
- message="E2ETestUtil.generateVideoMediaStoreOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ message="FileUtil.generateVideoMediaStoreOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
errorLine1=" generateVideoMediaStoreOptions(context.contentResolver, fileName)"
errorLine2=" ~~~~~~~~">
<location
@@ -300,7 +300,7 @@
<issue
id="RestrictedApiAndroidX"
- message="E2ETestUtil.generateVideoFileOutputOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ message="FileUtil.generateVideoFileOutputOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
errorLine1=" recorder.prepareRecording(context, generateVideoFileOutputOptions(fileName))"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
@@ -309,7 +309,7 @@
<issue
id="RestrictedApiAndroidX"
- message="E2ETestUtil.generateVideoFileOutputOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ message="FileUtil.generateVideoFileOutputOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
errorLine1=" recorder.prepareRecording(context, generateVideoFileOutputOptions(fileName))"
errorLine2=" ~~~~~~~~">
<location
@@ -318,7 +318,7 @@
<issue
id="RestrictedApiAndroidX"
- message="E2ETestUtil.writeTextToExternalFile can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ message="FileUtil.writeTextToExternalFile can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
errorLine1=" writeTextToExternalFile(information, fileName)"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
<location
@@ -327,7 +327,7 @@
<issue
id="RestrictedApiAndroidX"
- message="E2ETestUtil.writeTextToExternalFile can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ message="FileUtil.writeTextToExternalFile can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
errorLine1=" writeTextToExternalFile(information, fileName)"
errorLine2=" ~~~~~~~~~~~">
<location
@@ -336,7 +336,7 @@
<issue
id="RestrictedApiAndroidX"
- message="E2ETestUtil.writeTextToExternalFile can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ message="FileUtil.writeTextToExternalFile can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
errorLine1=" writeTextToExternalFile(information, fileName)"
errorLine2=" ~~~~~~~~">
<location
diff --git a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/StreamSharingActivity.kt b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/StreamSharingActivity.kt
index a593ec0..6577ce6 100644
--- a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/StreamSharingActivity.kt
+++ b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/StreamSharingActivity.kt
@@ -33,10 +33,10 @@
import androidx.camera.core.UseCase
import androidx.camera.core.impl.utils.executor.CameraXExecutors
import androidx.camera.lifecycle.ProcessCameraProvider
-import androidx.camera.testing.impl.E2ETestUtil.canDeviceWriteToMediaStore
-import androidx.camera.testing.impl.E2ETestUtil.generateVideoFileOutputOptions
-import androidx.camera.testing.impl.E2ETestUtil.generateVideoMediaStoreOptions
-import androidx.camera.testing.impl.E2ETestUtil.writeTextToExternalFile
+import androidx.camera.testing.impl.FileUtil.canDeviceWriteToMediaStore
+import androidx.camera.testing.impl.FileUtil.generateVideoFileOutputOptions
+import androidx.camera.testing.impl.FileUtil.generateVideoMediaStoreOptions
+import androidx.camera.testing.impl.FileUtil.writeTextToExternalFile
import androidx.camera.video.PendingRecording
import androidx.camera.video.Recorder
import androidx.camera.video.Recording
diff --git a/collection/collection/api/current.txt b/collection/collection/api/current.txt
index 5bcab89..313ca38 100644
--- a/collection/collection/api/current.txt
+++ b/collection/collection/api/current.txt
@@ -563,6 +563,58 @@
method @IntRange(from=0L) public int trim();
}
+ public final class MutableObjectList<E> extends androidx.collection.ObjectList<E> {
+ ctor public MutableObjectList(optional int initialCapacity);
+ method public boolean add(E element);
+ method public void add(@IntRange(from=0L) int index, E element);
+ method public boolean addAll(androidx.collection.ObjectList<E> elements);
+ method public boolean addAll(androidx.collection.ScatterSet<E> elements);
+ method public boolean addAll(E![] elements);
+ method public boolean addAll(@IntRange(from=0L) int index, androidx.collection.ObjectList<E> elements);
+ method public boolean addAll(@IntRange(from=0L) int index, E![] elements);
+ method public boolean addAll(@IntRange(from=0L) int index, java.util.Collection<? extends E> elements);
+ method public boolean addAll(Iterable<? extends E> elements);
+ method public boolean addAll(java.util.List<? extends E> elements);
+ method public boolean addAll(kotlin.sequences.Sequence<? extends E> elements);
+ method public java.util.List<E> asList();
+ method public java.util.List<E> asMutableList();
+ method public void clear();
+ method public void ensureCapacity(int capacity);
+ method public inline int getCapacity();
+ method public operator void minusAssign(androidx.collection.ObjectList<E> elements);
+ method public operator void minusAssign(androidx.collection.ScatterSet<E> elements);
+ method public inline operator void minusAssign(E element);
+ method public operator void minusAssign(E![] elements);
+ method public operator void minusAssign(Iterable<? extends E> elements);
+ method public operator void minusAssign(java.util.List<? extends E> elements);
+ method public operator void minusAssign(kotlin.sequences.Sequence<? extends E> elements);
+ method public operator void plusAssign(androidx.collection.ObjectList<E> elements);
+ method public operator void plusAssign(androidx.collection.ScatterSet<E> elements);
+ method public inline operator void plusAssign(E element);
+ method public operator void plusAssign(E![] elements);
+ method public operator void plusAssign(Iterable<? extends E> elements);
+ method public operator void plusAssign(java.util.List<? extends E> elements);
+ method public operator void plusAssign(kotlin.sequences.Sequence<? extends E> elements);
+ method public boolean remove(E element);
+ method public boolean removeAll(androidx.collection.ObjectList<E> elements);
+ method public boolean removeAll(androidx.collection.ScatterSet<E> elements);
+ method public boolean removeAll(E![] elements);
+ method public boolean removeAll(Iterable<? extends E> elements);
+ method public boolean removeAll(java.util.List<? extends E> elements);
+ method public boolean removeAll(kotlin.sequences.Sequence<? extends E> elements);
+ method public E removeAt(@IntRange(from=0L) int index);
+ method public inline void removeIf(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+ method public void removeRange(@IntRange(from=0L) int start, @IntRange(from=0L) int end);
+ method public boolean retainAll(androidx.collection.ObjectList<E> elements);
+ method public boolean retainAll(E![] elements);
+ method public boolean retainAll(Iterable<? extends E> elements);
+ method public boolean retainAll(java.util.Collection<? extends E> elements);
+ method public boolean retainAll(kotlin.sequences.Sequence<? extends E> elements);
+ method public operator E set(@IntRange(from=0L) int index, E element);
+ method public void trim(optional int minCapacity);
+ property public final inline int capacity;
+ }
+
public final class MutableScatterMap<K, V> extends androidx.collection.ScatterMap<K,V> {
ctor public MutableScatterMap(optional int initialCapacity);
method public java.util.Map<K,V> asMutableMap();
@@ -619,6 +671,66 @@
method @IntRange(from=0L) public int trim();
}
+ public abstract sealed class ObjectList<E> {
+ method public final boolean any();
+ method public final inline boolean any(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+ method public abstract java.util.List<E> asList();
+ method public final operator boolean contains(E element);
+ method public final boolean containsAll(androidx.collection.ObjectList<E> elements);
+ method public final boolean containsAll(E![] elements);
+ method public final boolean containsAll(Iterable<? extends E> elements);
+ method public final boolean containsAll(java.util.List<? extends E> elements);
+ method public final int count();
+ method public final inline int count(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+ method public final E elementAt(@IntRange(from=0L) int index);
+ method public final inline E elementAtOrElse(@IntRange(from=0L) int index, kotlin.jvm.functions.Function1<? super java.lang.Integer,? extends E> defaultValue);
+ method public final E first();
+ method public final inline E first(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+ method public final inline E? firstOrNull();
+ method public final inline E? firstOrNull(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+ method public final inline <R> R fold(R initial, kotlin.jvm.functions.Function2<? super R,? super E,? extends R> operation);
+ method public final inline <R> R foldIndexed(R initial, kotlin.jvm.functions.Function3<? super java.lang.Integer,? super R,? super E,? extends R> operation);
+ method public final inline <R> R foldRight(R initial, kotlin.jvm.functions.Function2<? super E,? super R,? extends R> operation);
+ method public final inline <R> R foldRightIndexed(R initial, kotlin.jvm.functions.Function3<? super java.lang.Integer,? super E,? super R,? extends R> operation);
+ method public final inline void forEach(kotlin.jvm.functions.Function1<? super E,kotlin.Unit> block);
+ method public final inline void forEachIndexed(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super E,kotlin.Unit> block);
+ method public final inline void forEachReversed(kotlin.jvm.functions.Function1<? super E,kotlin.Unit> block);
+ method public final inline void forEachReversedIndexed(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super E,kotlin.Unit> block);
+ method public final operator E get(@IntRange(from=0L) int index);
+ method public final inline kotlin.ranges.IntRange getIndices();
+ method @IntRange(from=-1L) public final inline int getLastIndex();
+ method @IntRange(from=0L) public final int getSize();
+ method public final int indexOf(E element);
+ method public final inline int indexOfFirst(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+ method public final inline int indexOfLast(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+ method public final boolean isEmpty();
+ method public final boolean isNotEmpty();
+ method public final E last();
+ method public final inline E last(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+ method public final int lastIndexOf(E element);
+ method public final inline E? lastOrNull();
+ method public final inline E? lastOrNull(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+ method public final boolean none();
+ method public final inline boolean reversedAny(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+ property public final inline kotlin.ranges.IntRange indices;
+ property @IntRange(from=-1L) public final inline int lastIndex;
+ property @IntRange(from=0L) public final int size;
+ }
+
+ public final class ObjectListKt {
+ method public static <E> androidx.collection.ObjectList<E> emptyObjectList();
+ method public static inline <E> androidx.collection.MutableObjectList<E> mutableObjectListOf();
+ method public static <E> androidx.collection.MutableObjectList<E> mutableObjectListOf(E element1);
+ method public static <E> androidx.collection.MutableObjectList<E> mutableObjectListOf(E element1, E element2);
+ method public static <E> androidx.collection.MutableObjectList<E> mutableObjectListOf(E element1, E element2, E element3);
+ method public static inline <E> androidx.collection.MutableObjectList<E> mutableObjectListOf(E?... elements);
+ method public static <E> androidx.collection.ObjectList<E> objectListOf();
+ method public static <E> androidx.collection.ObjectList<E> objectListOf(E element1);
+ method public static <E> androidx.collection.ObjectList<E> objectListOf(E element1, E element2);
+ method public static <E> androidx.collection.ObjectList<E> objectListOf(E element1, E element2, E element3);
+ method public static <E> androidx.collection.ObjectList<E> objectListOf(E?... elements);
+ }
+
@kotlin.jvm.JvmInline public final value class PairFloatFloat {
ctor public PairFloatFloat(float first, float second);
method public inline operator float component1();
diff --git a/collection/collection/api/restricted_current.txt b/collection/collection/api/restricted_current.txt
index a46df5c..9847883 100644
--- a/collection/collection/api/restricted_current.txt
+++ b/collection/collection/api/restricted_current.txt
@@ -578,6 +578,58 @@
method @IntRange(from=0L) public int trim();
}
+ public final class MutableObjectList<E> extends androidx.collection.ObjectList<E> {
+ ctor public MutableObjectList(optional int initialCapacity);
+ method public boolean add(E element);
+ method public void add(@IntRange(from=0L) int index, E element);
+ method public boolean addAll(androidx.collection.ObjectList<E> elements);
+ method public boolean addAll(androidx.collection.ScatterSet<E> elements);
+ method public boolean addAll(E![] elements);
+ method public boolean addAll(@IntRange(from=0L) int index, androidx.collection.ObjectList<E> elements);
+ method public boolean addAll(@IntRange(from=0L) int index, E![] elements);
+ method public boolean addAll(@IntRange(from=0L) int index, java.util.Collection<? extends E> elements);
+ method public boolean addAll(Iterable<? extends E> elements);
+ method public boolean addAll(java.util.List<? extends E> elements);
+ method public boolean addAll(kotlin.sequences.Sequence<? extends E> elements);
+ method public java.util.List<E> asList();
+ method public java.util.List<E> asMutableList();
+ method public void clear();
+ method public void ensureCapacity(int capacity);
+ method public inline int getCapacity();
+ method public operator void minusAssign(androidx.collection.ObjectList<E> elements);
+ method public operator void minusAssign(androidx.collection.ScatterSet<E> elements);
+ method public inline operator void minusAssign(E element);
+ method public operator void minusAssign(E![] elements);
+ method public operator void minusAssign(Iterable<? extends E> elements);
+ method public operator void minusAssign(java.util.List<? extends E> elements);
+ method public operator void minusAssign(kotlin.sequences.Sequence<? extends E> elements);
+ method public operator void plusAssign(androidx.collection.ObjectList<E> elements);
+ method public operator void plusAssign(androidx.collection.ScatterSet<E> elements);
+ method public inline operator void plusAssign(E element);
+ method public operator void plusAssign(E![] elements);
+ method public operator void plusAssign(Iterable<? extends E> elements);
+ method public operator void plusAssign(java.util.List<? extends E> elements);
+ method public operator void plusAssign(kotlin.sequences.Sequence<? extends E> elements);
+ method public boolean remove(E element);
+ method public boolean removeAll(androidx.collection.ObjectList<E> elements);
+ method public boolean removeAll(androidx.collection.ScatterSet<E> elements);
+ method public boolean removeAll(E![] elements);
+ method public boolean removeAll(Iterable<? extends E> elements);
+ method public boolean removeAll(java.util.List<? extends E> elements);
+ method public boolean removeAll(kotlin.sequences.Sequence<? extends E> elements);
+ method public E removeAt(@IntRange(from=0L) int index);
+ method public inline void removeIf(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+ method public void removeRange(@IntRange(from=0L) int start, @IntRange(from=0L) int end);
+ method public boolean retainAll(androidx.collection.ObjectList<E> elements);
+ method public boolean retainAll(E![] elements);
+ method public boolean retainAll(Iterable<? extends E> elements);
+ method public boolean retainAll(java.util.Collection<? extends E> elements);
+ method public boolean retainAll(kotlin.sequences.Sequence<? extends E> elements);
+ method public operator E set(@IntRange(from=0L) int index, E element);
+ method public void trim(optional int minCapacity);
+ property public final inline int capacity;
+ }
+
public final class MutableScatterMap<K, V> extends androidx.collection.ScatterMap<K,V> {
ctor public MutableScatterMap(optional int initialCapacity);
method public java.util.Map<K,V> asMutableMap();
@@ -635,6 +687,68 @@
method @IntRange(from=0L) public int trim();
}
+ public abstract sealed class ObjectList<E> {
+ method public final boolean any();
+ method public final inline boolean any(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+ method public abstract java.util.List<E> asList();
+ method public final operator boolean contains(E element);
+ method public final boolean containsAll(androidx.collection.ObjectList<E> elements);
+ method public final boolean containsAll(E![] elements);
+ method public final boolean containsAll(Iterable<? extends E> elements);
+ method public final boolean containsAll(java.util.List<? extends E> elements);
+ method public final int count();
+ method public final inline int count(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+ method public final E elementAt(@IntRange(from=0L) int index);
+ method public final inline E elementAtOrElse(@IntRange(from=0L) int index, kotlin.jvm.functions.Function1<? super java.lang.Integer,? extends E> defaultValue);
+ method public final E first();
+ method public final inline E first(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+ method public final inline E? firstOrNull();
+ method public final inline E? firstOrNull(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+ method public final inline <R> R fold(R initial, kotlin.jvm.functions.Function2<? super R,? super E,? extends R> operation);
+ method public final inline <R> R foldIndexed(R initial, kotlin.jvm.functions.Function3<? super java.lang.Integer,? super R,? super E,? extends R> operation);
+ method public final inline <R> R foldRight(R initial, kotlin.jvm.functions.Function2<? super E,? super R,? extends R> operation);
+ method public final inline <R> R foldRightIndexed(R initial, kotlin.jvm.functions.Function3<? super java.lang.Integer,? super E,? super R,? extends R> operation);
+ method public final inline void forEach(kotlin.jvm.functions.Function1<? super E,kotlin.Unit> block);
+ method public final inline void forEachIndexed(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super E,kotlin.Unit> block);
+ method public final inline void forEachReversed(kotlin.jvm.functions.Function1<? super E,kotlin.Unit> block);
+ method public final inline void forEachReversedIndexed(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super E,kotlin.Unit> block);
+ method public final operator E get(@IntRange(from=0L) int index);
+ method public final inline kotlin.ranges.IntRange getIndices();
+ method @IntRange(from=-1L) public final inline int getLastIndex();
+ method @IntRange(from=0L) public final int getSize();
+ method public final int indexOf(E element);
+ method public final inline int indexOfFirst(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+ method public final inline int indexOfLast(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+ method public final boolean isEmpty();
+ method public final boolean isNotEmpty();
+ method public final E last();
+ method public final inline E last(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+ method public final int lastIndexOf(E element);
+ method public final inline E? lastOrNull();
+ method public final inline E? lastOrNull(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+ method public final boolean none();
+ method public final inline boolean reversedAny(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+ property public final inline kotlin.ranges.IntRange indices;
+ property @IntRange(from=-1L) public final inline int lastIndex;
+ property @IntRange(from=0L) public final int size;
+ field @kotlin.PublishedApi internal int _size;
+ field @kotlin.PublishedApi internal Object![] content;
+ }
+
+ public final class ObjectListKt {
+ method public static <E> androidx.collection.ObjectList<E> emptyObjectList();
+ method public static inline <E> androidx.collection.MutableObjectList<E> mutableObjectListOf();
+ method public static <E> androidx.collection.MutableObjectList<E> mutableObjectListOf(E element1);
+ method public static <E> androidx.collection.MutableObjectList<E> mutableObjectListOf(E element1, E element2);
+ method public static <E> androidx.collection.MutableObjectList<E> mutableObjectListOf(E element1, E element2, E element3);
+ method public static inline <E> androidx.collection.MutableObjectList<E> mutableObjectListOf(E?... elements);
+ method public static <E> androidx.collection.ObjectList<E> objectListOf();
+ method public static <E> androidx.collection.ObjectList<E> objectListOf(E element1);
+ method public static <E> androidx.collection.ObjectList<E> objectListOf(E element1, E element2);
+ method public static <E> androidx.collection.ObjectList<E> objectListOf(E element1, E element2, E element3);
+ method public static <E> androidx.collection.ObjectList<E> objectListOf(E?... elements);
+ }
+
@kotlin.jvm.JvmInline public final value class PairFloatFloat {
ctor public PairFloatFloat(float first, float second);
method public inline operator float component1();
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectList.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectList.kt
new file mode 100644
index 0000000..105ebaf
--- /dev/null
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectList.kt
@@ -0,0 +1,1560 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@file:Suppress("NOTHING_TO_INLINE", "RedundantVisibilityModifier", "UNCHECKED_CAST")
+@file:OptIn(ExperimentalContracts::class)
+
+package androidx.collection
+
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.contract
+import kotlin.jvm.JvmField
+
+/**
+ * [ObjectList] is a [List]-like collection for reference types. It is optimized for fast
+ * access, avoiding virtual and interface method access. Methods avoid allocation whenever
+ * possible. For example [forEach] does not need allocate an [Iterator].
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the list (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. It is also not safe to mutate during reentrancy --
+ * in the middle of a [forEach], for example. However, concurrent reads are safe.
+ *
+ * **Note** [List] access is available through [asList] when developers need access to the
+ * common API.
+ *
+ * @see MutableObjectList
+ * @see FloatList
+ * @see IntList
+ * @eee LongList
+ */
+public sealed class ObjectList<E>(initialCapacity: Int) {
+ @JvmField
+ @PublishedApi
+ internal var content: Array<Any?> = if (initialCapacity == 0) {
+ EmptyArray
+ } else {
+ arrayOfNulls(initialCapacity)
+ }
+
+ @Suppress("PropertyName")
+ @JvmField
+ @PublishedApi
+ internal var _size: Int = 0
+
+ /**
+ * The number of elements in the [ObjectList].
+ */
+ @get:androidx.annotation.IntRange(from = 0)
+ public val size: Int
+ get() = _size
+
+ /**
+ * Returns the last valid index in the [ObjectList]. This can be `-1` when the list is empty.
+ */
+ @get:androidx.annotation.IntRange(from = -1)
+ public inline val lastIndex: Int get() = _size - 1
+
+ /**
+ * Returns an [IntRange] of the valid indices for this [ObjectList].
+ */
+ public inline val indices: IntRange get() = 0 until _size
+
+ /**
+ * Returns `true` if the collection has no elements in it.
+ */
+ public fun none(): Boolean {
+ return isEmpty()
+ }
+
+ /**
+ * Returns `true` if there's at least one element in the collection.
+ */
+ public fun any(): Boolean {
+ return isNotEmpty()
+ }
+
+ /**
+ * Returns `true` if any of the elements give a `true` return value for [predicate].
+ */
+ public inline fun any(predicate: (element: E) -> Boolean): Boolean {
+ contract { callsInPlace(predicate) }
+ forEach {
+ if (predicate(it)) {
+ return true
+ }
+ }
+ return false
+ }
+
+ /**
+ * Returns `true` if any of the elements give a `true` return value for [predicate] while
+ * iterating in the reverse order.
+ */
+ public inline fun reversedAny(predicate: (element: E) -> Boolean): Boolean {
+ contract { callsInPlace(predicate) }
+ forEachReversed {
+ if (predicate(it)) {
+ return true
+ }
+ }
+ return false
+ }
+
+ /**
+ * Returns `true` if the [ObjectList] contains [element] or `false` otherwise.
+ */
+ public operator fun contains(element: E): Boolean {
+ forEach {
+ if (it == element) {
+ return true
+ }
+ }
+ return false
+ }
+
+ /**
+ * Returns `true` if the [ObjectList] contains all elements in [elements] or `false` if
+ * one or more are missing.
+ */
+ public fun containsAll(@Suppress("ArrayReturn") elements: Array<E>): Boolean {
+ for (i in elements.indices) {
+ if (!contains(elements[i])) return false
+ }
+ return true
+ }
+
+ /**
+ * Returns `true` if the [ObjectList] contains all elements in [elements] or `false` if
+ * one or more are missing.
+ */
+ public fun containsAll(elements: List<E>): Boolean {
+ for (i in elements.indices) {
+ if (!contains(elements[i])) return false
+ }
+ return true
+ }
+
+ /**
+ * Returns `true` if the [ObjectList] contains all elements in [elements] or `false` if
+ * one or more are missing.
+ */
+ public fun containsAll(elements: Iterable<E>): Boolean {
+ elements.forEach { element ->
+ if (!contains(element)) return false
+ }
+ return true
+ }
+
+ /**
+ * Returns `true` if the [ObjectList] contains all elements in [elements] or `false` if
+ * one or more are missing.
+ */
+ public fun containsAll(elements: ObjectList<E>): Boolean {
+ elements.forEach { element ->
+ if (!contains(element)) return false
+ }
+ return true
+ }
+
+ /**
+ * Returns the number of elements in this list.
+ */
+ public fun count(): Int = _size
+
+ /**
+ * Counts the number of elements matching [predicate].
+ * @return The number of elements in this list for which [predicate] returns true.
+ */
+ public inline fun count(predicate: (element: E) -> Boolean): Int {
+ contract { callsInPlace(predicate) }
+ var count = 0
+ forEach { if (predicate(it)) count++ }
+ return count
+ }
+
+ /**
+ * Returns the first element in the [ObjectList] or throws a [NoSuchElementException] if
+ * it [isEmpty].
+ */
+ public fun first(): E {
+ if (isEmpty()) {
+ throw NoSuchElementException("ObjectList is empty.")
+ }
+ return content[0] as E
+ }
+
+ /**
+ * Returns the first element in the [ObjectList] for which [predicate] returns `true` or
+ * throws [NoSuchElementException] if nothing matches.
+ * @see indexOfFirst
+ * @see firstOrNull
+ */
+ public inline fun first(predicate: (element: E) -> Boolean): E {
+ contract { callsInPlace(predicate) }
+ forEach { element ->
+ if (predicate(element)) return element
+ }
+ throw NoSuchElementException("ObjectList contains no element matching the predicate.")
+ }
+
+ /**
+ * Returns the first element in the [ObjectList] or `null` if it [isEmpty].
+ */
+ public inline fun firstOrNull(): E? = if (isEmpty()) null else get(0)
+
+ /**
+ * Returns the first element in the [ObjectList] for which [predicate] returns `true` or
+ * `null` if nothing matches.
+ * @see indexOfFirst
+ */
+ public inline fun firstOrNull(predicate: (element: E) -> Boolean): E? {
+ contract { callsInPlace(predicate) }
+ forEach { element ->
+ if (predicate(element)) return element
+ }
+ return null
+ }
+
+ /**
+ * Accumulates values, starting with [initial], and applying [operation] to each element
+ * in the [ObjectList] in order.
+ * @param initial The value of `acc` for the first call to [operation] or return value if
+ * there are no elements in this list.
+ * @param operation function that takes current accumulator value and an element, and
+ * calculates the next accumulator value.
+ */
+ public inline fun <R> fold(initial: R, operation: (acc: R, element: E) -> R): R {
+ contract { callsInPlace(operation) }
+ var acc = initial
+ forEach { element ->
+ acc = operation(acc, element)
+ }
+ return acc
+ }
+
+ /**
+ * Accumulates values, starting with [initial], and applying [operation] to each element
+ * in the [ObjectList] in order.
+ */
+ public inline fun <R> foldIndexed(
+ initial: R,
+ operation: (index: Int, acc: R, element: E) -> R
+ ): R {
+ contract { callsInPlace(operation) }
+ var acc = initial
+ forEachIndexed { i, element ->
+ acc = operation(i, acc, element)
+ }
+ return acc
+ }
+
+ /**
+ * Accumulates values, starting with [initial], and applying [operation] to each element
+ * in the [ObjectList] in reverse order.
+ * @param initial The value of `acc` for the first call to [operation] or return value if
+ * there are no elements in this list.
+ * @param operation function that takes an element and the current accumulator value, and
+ * calculates the next accumulator value.
+ */
+ public inline fun <R> foldRight(initial: R, operation: (element: E, acc: R) -> R): R {
+ contract { callsInPlace(operation) }
+ var acc = initial
+ forEachReversed { element ->
+ acc = operation(element, acc)
+ }
+ return acc
+ }
+
+ /**
+ * Accumulates values, starting with [initial], and applying [operation] to each element
+ * in the [ObjectList] in reverse order.
+ */
+ public inline fun <R> foldRightIndexed(
+ initial: R,
+ operation: (index: Int, element: E, acc: R) -> R
+ ): R {
+ contract { callsInPlace(operation) }
+ var acc = initial
+ forEachReversedIndexed { i, element ->
+ acc = operation(i, element, acc)
+ }
+ return acc
+ }
+
+ /**
+ * Calls [block] for each element in the [ObjectList], in order.
+ * @param block will be executed for every element in the list, accepting an element from
+ * the list
+ */
+ public inline fun forEach(block: (element: E) -> Unit) {
+ contract { callsInPlace(block) }
+ val content = content
+ for (i in 0 until _size) {
+ block(content[i] as E)
+ }
+ }
+
+ /**
+ * Calls [block] for each element in the [ObjectList] along with its index, in order.
+ * @param block will be executed for every element in the list, accepting the index and
+ * the element at that index.
+ */
+ public inline fun forEachIndexed(block: (index: Int, element: E) -> Unit) {
+ contract { callsInPlace(block) }
+ val content = content
+ for (i in 0 until _size) {
+ block(i, content[i] as E)
+ }
+ }
+
+ /**
+ * Calls [block] for each element in the [ObjectList] in reverse order.
+ * @param block will be executed for every element in the list, accepting an element from
+ * the list
+ */
+ public inline fun forEachReversed(block: (element: E) -> Unit) {
+ contract { callsInPlace(block) }
+ val content = content
+ for (i in _size - 1 downTo 0) {
+ block(content[i] as E)
+ }
+ }
+
+ /**
+ * Calls [block] for each element in the [ObjectList] along with its index, in reverse
+ * order.
+ * @param block will be executed for every element in the list, accepting the index and
+ * the element at that index.
+ */
+ public inline fun forEachReversedIndexed(block: (index: Int, element: E) -> Unit) {
+ contract { callsInPlace(block) }
+ val content = content
+ for (i in _size - 1 downTo 0) {
+ block(i, content[i] as E)
+ }
+ }
+
+ /**
+ * Returns the element at the given [index] or throws [IndexOutOfBoundsException] if
+ * the [index] is out of bounds of this collection.
+ */
+ public operator fun get(@androidx.annotation.IntRange(from = 0) index: Int): E {
+ if (index !in 0 until _size) {
+ throw IndexOutOfBoundsException("Index $index must be in 0..$lastIndex")
+ }
+ return content[index] as E
+ }
+
+ /**
+ * Returns the element at the given [index] or throws [IndexOutOfBoundsException] if
+ * the [index] is out of bounds of this collection.
+ */
+ public fun elementAt(@androidx.annotation.IntRange(from = 0) index: Int): E {
+ if (index !in 0 until _size) {
+ throw IndexOutOfBoundsException("Index $index must be in 0..$lastIndex")
+ }
+ return content[index] as E
+ }
+
+ /**
+ * Returns the element at the given [index] or [defaultValue] if [index] is out of bounds
+ * of the collection.
+ * @param index The index of the element whose value should be returned
+ * @param defaultValue A lambda to call with [index] as a parameter to return a value at
+ * an index not in the list.
+ */
+ public inline fun elementAtOrElse(
+ @androidx.annotation.IntRange(from = 0) index: Int,
+ defaultValue: (index: Int) -> E
+ ): E {
+ if (index !in 0 until _size) {
+ return defaultValue(index)
+ }
+ return content[index] as E
+ }
+
+ /**
+ * Returns the index of [element] in the [ObjectList] or `-1` if [element] is not there.
+ */
+ public fun indexOf(element: E): Int {
+ forEachIndexed { i, item ->
+ if (element == item) {
+ return i
+ }
+ }
+ return -1
+ }
+
+ /**
+ * Returns the index if the first element in the [ObjectList] for which [predicate]
+ * returns `true` or -1 if there was no element for which predicate returned `true`.
+ */
+ public inline fun indexOfFirst(predicate: (element: E) -> Boolean): Int {
+ contract { callsInPlace(predicate) }
+ forEachIndexed { i, element ->
+ if (predicate(element)) {
+ return i
+ }
+ }
+ return -1
+ }
+
+ /**
+ * Returns the index if the last element in the [ObjectList] for which [predicate]
+ * returns `true` or -1 if there was no element for which predicate returned `true`.
+ */
+ public inline fun indexOfLast(predicate: (element: E) -> Boolean): Int {
+ contract { callsInPlace(predicate) }
+ forEachReversedIndexed { i, element ->
+ if (predicate(element)) {
+ return i
+ }
+ }
+ return -1
+ }
+
+ /**
+ * Returns `true` if the [ObjectList] has no elements in it or `false` otherwise.
+ */
+ public fun isEmpty(): Boolean = _size == 0
+
+ /**
+ * Returns `true` if there are elements in the [ObjectList] or `false` if it is empty.
+ */
+ public fun isNotEmpty(): Boolean = _size != 0
+
+ /**
+ * Returns the last element in the [ObjectList] or throws a [NoSuchElementException] if
+ * it [isEmpty].
+ */
+ public fun last(): E {
+ if (isEmpty()) {
+ throw NoSuchElementException("ObjectList is empty.")
+ }
+ return content[lastIndex] as E
+ }
+
+ /**
+ * Returns the last element in the [ObjectList] for which [predicate] returns `true` or
+ * throws [NoSuchElementException] if nothing matches.
+ * @see indexOfLast
+ * @see lastOrNull
+ */
+ public inline fun last(predicate: (element: E) -> Boolean): E {
+ contract { callsInPlace(predicate) }
+ forEachReversed { element ->
+ if (predicate(element)) {
+ return element
+ }
+ }
+ throw NoSuchElementException("ObjectList contains no element matching the predicate.")
+ }
+
+ /**
+ * Returns the last element in the [ObjectList] or `null` if it [isEmpty].
+ */
+ public inline fun lastOrNull(): E? = if (isEmpty()) null else content[lastIndex] as E
+
+ /**
+ * Returns the last element in the [ObjectList] for which [predicate] returns `true` or
+ * `null` if nothing matches.
+ * @see indexOfLast
+ */
+ public inline fun lastOrNull(predicate: (element: E) -> Boolean): E? {
+ contract { callsInPlace(predicate) }
+ forEachReversed { element ->
+ if (predicate(element)) {
+ return element
+ }
+ }
+ return null
+ }
+
+ /**
+ * Returns the index of the last element in the [ObjectList] that is the same as
+ * [element] or `-1` if no elements match.
+ */
+ public fun lastIndexOf(element: E): Int {
+ forEachReversedIndexed { i, item ->
+ if (element == item) {
+ return i
+ }
+ }
+ return -1
+ }
+
+ /**
+ * Returns a [List] view into the [ObjectList]. All access to the collection will be
+ * less efficient and abides by the allocation requirements of the [List]. For example,
+ * [List.forEach] will allocate an iterator. All access will go through the more expensive
+ * interface calls. Critical performance areas should use the [ObjectList] API rather than
+ * [List] API, when possible.
+ */
+ public abstract fun asList(): List<E>
+
+ /**
+ * Returns a hash code based on the contents of the [ObjectList].
+ */
+ override fun hashCode(): Int {
+ var hashCode = 0
+ forEach { element ->
+ hashCode += 31 * element.hashCode()
+ }
+ return hashCode
+ }
+
+ /**
+ * Returns `true` if [other] is a [ObjectList] and the contents of this and [other] are the
+ * same.
+ */
+ override fun equals(other: Any?): Boolean {
+ if (other !is ObjectList<*> || other._size != _size) {
+ return false
+ }
+ val content = content
+ val otherContent = other.content
+ for (i in indices) {
+ if (content[i] != otherContent[i]) {
+ return false
+ }
+ }
+ return true
+ }
+
+ /**
+ * Returns a String representation of the list, surrounded by "[]" and each element
+ * separated by ", ".
+ */
+ override fun toString(): String {
+ if (isEmpty()) {
+ return "[]"
+ }
+ val last = lastIndex
+ return buildString {
+ append('[')
+ val content = content
+ for (i in 0 until last) {
+ append(content[i])
+ append(',')
+ append(' ')
+ }
+ append(content[last])
+ append(']')
+ }
+ }
+}
+
+/**
+ * [MutableObjectList] is a [MutableList]-like collection for reference types. It is optimized
+ * for fast access, avoiding virtual and interface method access. Methods avoid allocation
+ * whenever possible. For example [forEach] does not need allocate an [Iterator].
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the list (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. It is also not safe to mutate during reentrancy --
+ * in the middle of a [forEach], for example. However, concurrent reads are safe.
+ *
+ * **Note** [List] access is available through [asList] when developers need access to the
+ * common API.
+
+ * **Note** [MutableList] access is available through [asMutableList] when developers need
+ * access to the common API.
+ *
+ * @see ObjectList
+ * @see MutableFloatList
+ * @see MutableIntList
+ * @eee MutableLongList
+ */
+public class MutableObjectList<E>(
+ initialCapacity: Int = 16
+) : ObjectList<E>(initialCapacity) {
+ private var list: ObjectListMutableList<E>? = null
+
+ /**
+ * Returns the total number of elements that can be held before the [MutableObjectList] must
+ * grow.
+ *
+ * @see ensureCapacity
+ */
+ public inline val capacity: Int
+ get() = content.size
+
+ /**
+ * Adds [element] to the [MutableObjectList] and returns `true`.
+ */
+ public fun add(element: E): Boolean {
+ ensureCapacity(_size + 1)
+ content[_size] = element
+ _size++
+ return true
+ }
+
+ /**
+ * Adds [element] to the [MutableObjectList] at the given [index], shifting over any
+ * elements at [index] and after, if any.
+ * @throws IndexOutOfBoundsException if [index] isn't between 0 and [size], inclusive
+ */
+ public fun add(@androidx.annotation.IntRange(from = 0) index: Int, element: E) {
+ if (index !in 0.._size) {
+ throw IndexOutOfBoundsException("Index $index must be in 0..$_size")
+ }
+ ensureCapacity(_size + 1)
+ val content = content
+ if (index != _size) {
+ content.copyInto(
+ destination = content,
+ destinationOffset = index + 1,
+ startIndex = index,
+ endIndex = _size
+ )
+ }
+ content[index] = element
+ _size++
+ }
+
+ /**
+ * Adds all [elements] to the [MutableObjectList] at the given [index], shifting over any
+ * elements at [index] and after, if any.
+ * @return `true` if the [MutableObjectList] was changed or `false` if [elements] was empty
+ * @throws IndexOutOfBoundsException if [index] isn't between 0 and [size], inclusive.
+ */
+ public fun addAll(
+ @androidx.annotation.IntRange(from = 0) index: Int,
+ @Suppress("ArrayReturn") elements: Array<E>
+ ): Boolean {
+ if (index !in 0.._size) {
+ throw IndexOutOfBoundsException("Index $index must be in 0..$_size")
+ }
+ if (elements.isEmpty()) return false
+ ensureCapacity(_size + elements.size)
+ val content = content
+ if (index != _size) {
+ content.copyInto(
+ destination = content,
+ destinationOffset = index + elements.size,
+ startIndex = index,
+ endIndex = _size
+ )
+ }
+ elements.copyInto(content, index)
+ _size += elements.size
+ return true
+ }
+
+ /**
+ * Adds all [elements] to the [MutableObjectList] at the given [index], shifting over any
+ * elements at [index] and after, if any.
+ * @return `true` if the [MutableObjectList] was changed or `false` if [elements] was empty
+ * @throws IndexOutOfBoundsException if [index] isn't between 0 and [size], inclusive.
+ */
+ public fun addAll(
+ @androidx.annotation.IntRange(from = 0) index: Int,
+ elements: Collection<E>
+ ): Boolean {
+ if (index !in 0.._size) {
+ throw IndexOutOfBoundsException("Index $index must be in 0..$_size")
+ }
+ if (elements.isEmpty()) return false
+ ensureCapacity(_size + elements.size)
+ val content = content
+ if (index != _size) {
+ content.copyInto(
+ destination = content,
+ destinationOffset = index + elements.size,
+ startIndex = index,
+ endIndex = _size
+ )
+ }
+ elements.forEachIndexed { i, element ->
+ content[index + i] = element
+ }
+ _size += elements.size
+ return true
+ }
+
+ /**
+ * Adds all [elements] to the [MutableObjectList] at the given [index], shifting over any
+ * elements at [index] and after, if any.
+ * @return `true` if the [MutableObjectList] was changed or `false` if [elements] was empty
+ * @throws IndexOutOfBoundsException if [index] isn't between 0 and [size], inclusive
+ */
+ public fun addAll(
+ @androidx.annotation.IntRange(from = 0) index: Int,
+ elements: ObjectList<E>
+ ): Boolean {
+ if (index !in 0.._size) {
+ throw IndexOutOfBoundsException("Index $index must be in 0..$_size")
+ }
+ if (elements.isEmpty()) return false
+ ensureCapacity(_size + elements._size)
+ val content = content
+ if (index != _size) {
+ content.copyInto(
+ destination = content,
+ destinationOffset = index + elements._size,
+ startIndex = index,
+ endIndex = _size
+ )
+ }
+ elements.content.copyInto(
+ destination = content,
+ destinationOffset = index,
+ startIndex = 0,
+ endIndex = elements._size
+ )
+ _size += elements._size
+ return true
+ }
+
+ /**
+ * Adds all [elements] to the end of the [MutableObjectList] and returns `true` if the
+ * [MutableObjectList] was changed or `false` if [elements] was empty.
+ */
+ public fun addAll(elements: ObjectList<E>): Boolean {
+ val oldSize = _size
+ plusAssign(elements)
+ return oldSize != _size
+ }
+
+ /**
+ * Adds all [elements] to the end of the [MutableObjectList] and returns `true` if the
+ * [MutableObjectList] was changed or `false` if [elements] was empty.
+ */
+ public fun addAll(elements: ScatterSet<E>): Boolean {
+ val oldSize = _size
+ plusAssign(elements)
+ return oldSize != _size
+ }
+
+ /**
+ * Adds all [elements] to the end of the [MutableObjectList] and returns `true` if the
+ * [MutableObjectList] was changed or `false` if [elements] was empty.
+ */
+ public fun addAll(@Suppress("ArrayReturn") elements: Array<E>): Boolean {
+ val oldSize = _size
+ plusAssign(elements)
+ return oldSize != _size
+ }
+
+ /**
+ * Adds all [elements] to the end of the [MutableObjectList] and returns `true` if the
+ * [MutableObjectList] was changed or `false` if [elements] was empty.
+ */
+ public fun addAll(elements: List<E>): Boolean {
+ val oldSize = _size
+ plusAssign(elements)
+ return oldSize != _size
+ }
+
+ /**
+ * Adds all [elements] to the end of the [MutableObjectList] and returns `true` if the
+ * [MutableObjectList] was changed or `false` if [elements] was empty.
+ */
+ public fun addAll(elements: Iterable<E>): Boolean {
+ val oldSize = _size
+ plusAssign(elements)
+ return oldSize != _size
+ }
+
+ /**
+ * Adds all [elements] to the end of the [MutableObjectList] and returns `true` if the
+ * [MutableObjectList] was changed or `false` if [elements] was empty.
+ */
+ public fun addAll(elements: Sequence<E>): Boolean {
+ val oldSize = _size
+ plusAssign(elements)
+ return oldSize != _size
+ }
+
+ /**
+ * Adds all [elements] to the end of the [MutableObjectList].
+ */
+ public operator fun plusAssign(elements: ObjectList<E>) {
+ if (elements.isEmpty()) return
+ ensureCapacity(_size + elements._size)
+ val content = content
+ elements.content.copyInto(
+ destination = content,
+ destinationOffset = _size,
+ startIndex = 0,
+ endIndex = elements._size
+ )
+ _size += elements._size
+ }
+
+ /**
+ * Adds all [elements] to the end of the [MutableObjectList].
+ */
+ public operator fun plusAssign(elements: ScatterSet<E>) {
+ if (elements.isEmpty()) return
+ ensureCapacity(_size + elements.size)
+ elements.forEach { element ->
+ plusAssign(element)
+ }
+ }
+
+ /**
+ * Adds all [elements] to the end of the [MutableObjectList].
+ */
+ public operator fun plusAssign(@Suppress("ArrayReturn") elements: Array<E>) {
+ if (elements.isEmpty()) return
+ ensureCapacity(_size + elements.size)
+ val content = content
+ elements.copyInto(content, _size)
+ _size += elements.size
+ }
+
+ /**
+ * Adds all [elements] to the end of the [MutableObjectList].
+ */
+ public operator fun plusAssign(elements: List<E>) {
+ if (elements.isEmpty()) return
+ val size = _size
+ ensureCapacity(size + elements.size)
+ val content = content
+ for (i in elements.indices) {
+ content[i + size] = elements[i]
+ }
+ _size += elements.size
+ }
+
+ /**
+ * Adds all [elements] to the end of the [MutableObjectList].
+ */
+ public operator fun plusAssign(elements: Iterable<E>) {
+ elements.forEach { element ->
+ plusAssign(element)
+ }
+ }
+
+ /**
+ * Adds all [elements] to the end of the [MutableObjectList].
+ */
+ public operator fun plusAssign(elements: Sequence<E>) {
+ elements.forEach { element ->
+ plusAssign(element)
+ }
+ }
+
+ /**
+ * Removes all elements in the [MutableObjectList]. The storage isn't released.
+ * @see trim
+ */
+ public fun clear() {
+ content.fill(null, fromIndex = 0, toIndex = _size)
+ _size = 0
+ }
+
+ /**
+ * Reduces the internal storage. If [capacity] is greater than [minCapacity] and [size], the
+ * internal storage is reduced to the maximum of [size] and [minCapacity].
+ * @see ensureCapacity
+ */
+ public fun trim(minCapacity: Int = _size) {
+ val minSize = maxOf(minCapacity, _size)
+ if (capacity > minSize) {
+ content = content.copyOf(minSize)
+ }
+ }
+
+ /**
+ * Ensures that there is enough space to store [capacity] elements in the [MutableObjectList].
+ * @see trim
+ */
+ public fun ensureCapacity(capacity: Int) {
+ val oldContent = content
+ if (oldContent.size < capacity) {
+ val newSize = maxOf(capacity, oldContent.size * 3 / 2)
+ content = oldContent.copyOf(newSize)
+ }
+ }
+
+ /**
+ * [add] [element] to the [MutableObjectList].
+ */
+ public inline operator fun plusAssign(element: E) {
+ add(element)
+ }
+
+ /**
+ * [remove] [element] from the [MutableObjectList]
+ */
+ public inline operator fun minusAssign(element: E) {
+ remove(element)
+ }
+
+ /**
+ * Removes [element] from the [MutableObjectList]. If [element] was in the [MutableObjectList]
+ * and was removed, `true` will be returned, or `false` will be returned if the element
+ * was not found.
+ */
+ public fun remove(element: E): Boolean {
+ val index = indexOf(element)
+ if (index >= 0) {
+ removeAt(index)
+ return true
+ }
+ return false
+ }
+
+ /**
+ * Removes all elements in this list for which [predicate] returns `true`.
+ */
+ public inline fun removeIf(predicate: (element: E) -> Boolean) {
+ var gap = 0
+ val size = _size
+ val content = content
+ for (i in indices) {
+ content[i - gap] = content[i]
+ if (predicate(content[i] as E)) {
+ gap++
+ }
+ }
+ content.fill(null, fromIndex = size - gap, toIndex = size)
+ _size -= gap
+ }
+
+ /**
+ * Removes all [elements] from the [MutableObjectList] and returns `true` if anything was removed.
+ */
+ public fun removeAll(@Suppress("ArrayReturn") elements: Array<E>): Boolean {
+ val initialSize = _size
+ for (i in elements.indices) {
+ remove(elements[i])
+ }
+ return initialSize != _size
+ }
+
+ /**
+ * Removes all [elements] from the [MutableObjectList] and returns `true` if anything was removed.
+ */
+ public fun removeAll(elements: ObjectList<E>): Boolean {
+ val initialSize = _size
+ minusAssign(elements)
+ return initialSize != _size
+ }
+
+ /**
+ * Removes all [elements] from the [MutableObjectList] and returns `true` if anything was removed.
+ */
+ public fun removeAll(elements: ScatterSet<E>): Boolean {
+ val initialSize = _size
+ minusAssign(elements)
+ return initialSize != _size
+ }
+
+ /**
+ * Removes all [elements] from the [MutableObjectList] and returns `true` if anything was removed.
+ */
+ public fun removeAll(elements: List<E>): Boolean {
+ val initialSize = _size
+ minusAssign(elements)
+ return initialSize != _size
+ }
+
+ /**
+ * Removes all [elements] from the [MutableObjectList] and returns `true` if anything was removed.
+ */
+ public fun removeAll(elements: Iterable<E>): Boolean {
+ val initialSize = _size
+ minusAssign(elements)
+ return initialSize != _size
+ }
+
+ /**
+ * Removes all [elements] from the [MutableObjectList] and returns `true` if anything was removed.
+ */
+ public fun removeAll(elements: Sequence<E>): Boolean {
+ val initialSize = _size
+ minusAssign(elements)
+ return initialSize != _size
+ }
+
+ /**
+ * Removes all [elements] from the [MutableObjectList].
+ */
+ public operator fun minusAssign(@Suppress("ArrayReturn") elements: Array<E>) {
+ elements.forEach { element ->
+ minusAssign(element)
+ }
+ }
+
+ /**
+ * Removes all [elements] from the [MutableObjectList].
+ */
+ public operator fun minusAssign(elements: ObjectList<E>) {
+ elements.forEach { element ->
+ minusAssign(element)
+ }
+ }
+
+ /**
+ * Removes all [elements] from the [MutableObjectList].
+ */
+ public operator fun minusAssign(elements: ScatterSet<E>) {
+ elements.forEach { element ->
+ minusAssign(element)
+ }
+ }
+
+ /**
+ * Removes all [elements] from the [MutableObjectList].
+ */
+ public operator fun minusAssign(elements: List<E>) {
+ for (i in elements.indices) {
+ minusAssign(elements[i])
+ }
+ }
+
+ /**
+ * Removes all [elements] from the [MutableObjectList].
+ */
+ public operator fun minusAssign(elements: Iterable<E>) {
+ elements.forEach { element ->
+ minusAssign(element)
+ }
+ }
+
+ /**
+ * Removes all [elements] from the [MutableObjectList].
+ */
+ public operator fun minusAssign(elements: Sequence<E>) {
+ elements.forEach { element ->
+ minusAssign(element)
+ }
+ }
+
+ /**
+ * Removes the element at the given [index] and returns it.
+ * @throws IndexOutOfBoundsException if [index] isn't between 0 and [lastIndex], inclusive
+ */
+ public fun removeAt(@androidx.annotation.IntRange(from = 0) index: Int): E {
+ if (index !in 0 until _size) {
+ throw IndexOutOfBoundsException("Index $index must be in 0..$lastIndex")
+ }
+ val content = content
+ val element = content[index]
+ if (index != lastIndex) {
+ content.copyInto(
+ destination = content,
+ destinationOffset = index,
+ startIndex = index + 1,
+ endIndex = _size
+ )
+ }
+ _size--
+ content[_size] = null
+ return element as E
+ }
+
+ /**
+ * Removes elements from index [start] (inclusive) to [end] (exclusive).
+ * @throws IndexOutOfBoundsException if [start] or [end] isn't between 0 and [size], inclusive
+ * @throws IllegalArgumentException if [start] is greater than [end]
+ */
+ public fun removeRange(
+ @androidx.annotation.IntRange(from = 0) start: Int,
+ @androidx.annotation.IntRange(from = 0) end: Int
+ ) {
+ if (start !in 0.._size || end !in 0.._size) {
+ throw IndexOutOfBoundsException("Start ($start) and end ($end) must be in 0..$_size")
+ }
+ if (end < start) {
+ throw IllegalArgumentException("Start ($start) is more than end ($end)")
+ }
+ if (end != start) {
+ if (end < _size) {
+ content.copyInto(
+ destination = content,
+ destinationOffset = start,
+ startIndex = end,
+ endIndex = _size
+ )
+ }
+ val newSize = _size - (end - start)
+ content.fill(null, fromIndex = newSize, toIndex = _size)
+ _size = newSize
+ }
+ }
+
+ /**
+ * Keeps only [elements] in the [MutableObjectList] and removes all other values.
+ * @return `true` if the [MutableObjectList] has changed.
+ */
+ public fun retainAll(@Suppress("ArrayReturn") elements: Array<E>): Boolean {
+ val initialSize = _size
+ val content = content
+ for (i in lastIndex downTo 0) {
+ val element = content[i]
+ if (elements.indexOfFirst { it == element } < 0) {
+ removeAt(i)
+ }
+ }
+ return initialSize != _size
+ }
+
+ /**
+ * Keeps only [elements] in the [MutableObjectList] and removes all other values.
+ * @return `true` if the [MutableObjectList] has changed.
+ */
+ public fun retainAll(elements: ObjectList<E>): Boolean {
+ val initialSize = _size
+ val content = content
+ for (i in lastIndex downTo 0) {
+ val element = content[i] as E
+ if (element !in elements) {
+ removeAt(i)
+ }
+ }
+ return initialSize != _size
+ }
+
+ /**
+ * Keeps only [elements] in the [MutableObjectList] and removes all other values.
+ * @return `true` if the [MutableObjectList] has changed.
+ */
+ public fun retainAll(elements: Collection<E>): Boolean {
+ val initialSize = _size
+ val content = content
+ for (i in lastIndex downTo 0) {
+ val element = content[i] as E
+ if (element !in elements) {
+ removeAt(i)
+ }
+ }
+ return initialSize != _size
+ }
+
+ /**
+ * Keeps only [elements] in the [MutableObjectList] and removes all other values.
+ * @return `true` if the [MutableObjectList] has changed.
+ */
+ public fun retainAll(elements: Iterable<E>): Boolean {
+ val initialSize = _size
+ val content = content
+ for (i in lastIndex downTo 0) {
+ val element = content[i] as E
+ if (element !in elements) {
+ removeAt(i)
+ }
+ }
+ return initialSize != _size
+ }
+
+ /**
+ * Keeps only [elements] in the [MutableObjectList] and removes all other values.
+ * @return `true` if the [MutableObjectList] has changed.
+ */
+ public fun retainAll(elements: Sequence<E>): Boolean {
+ val initialSize = _size
+ val content = content
+ for (i in lastIndex downTo 0) {
+ val element = content[i] as E
+ if (element !in elements) {
+ removeAt(i)
+ }
+ }
+ return initialSize != _size
+ }
+
+ /**
+ * Sets the value at [index] to [element].
+ * @return the previous value set at [index]
+ * @throws IndexOutOfBoundsException if [index] isn't between 0 and [lastIndex], inclusive
+ */
+ public operator fun set(
+ @androidx.annotation.IntRange(from = 0) index: Int,
+ element: E
+ ): E {
+ if (index !in 0 until _size) {
+ throw IndexOutOfBoundsException("set index $index must be between 0 .. $lastIndex")
+ }
+ val content = content
+ val old = content[index]
+ content[index] = element
+ return old as E
+ }
+
+ override fun asList(): List<E> = asMutableList()
+
+ /**
+ * Returns a [MutableList] view into the [MutableObjectList]. All access to the collection
+ * will be less efficient and abides by the allocation requirements of the
+ * [MutableList]. For example, [MutableList.forEach] will allocate an iterator.
+ * All access will go through the more expensive interface calls. Critical performance
+ * areas should use the [MutableObjectList] API rather than [MutableList] API, when possible.
+ */
+ public fun asMutableList(): MutableList<E> = list ?: ObjectListMutableList(this).also {
+ list = it
+ }
+
+ private class MutableObjectListIterator<T>(
+ private val list: MutableList<T>,
+ private var index: Int
+ ) : MutableListIterator<T> {
+ override fun hasNext(): Boolean {
+ return index < list.size
+ }
+
+ override fun next(): T {
+ return list[index++]
+ }
+
+ override fun remove() {
+ index--
+ list.removeAt(index)
+ }
+
+ override fun hasPrevious(): Boolean {
+ return index > 0
+ }
+
+ override fun nextIndex(): Int {
+ return index
+ }
+
+ override fun previous(): T {
+ index--
+ return list[index]
+ }
+
+ override fun previousIndex(): Int {
+ return index - 1
+ }
+
+ override fun add(element: T) {
+ list.add(index, element)
+ index++
+ }
+
+ override fun set(element: T) {
+ list[index] = element
+ }
+ }
+
+ /**
+ * [MutableList] implementation for a [MutableObjectList], used in [asMutableList].
+ */
+ private class ObjectListMutableList<T>(
+ private val objectList: MutableObjectList<T>
+ ) : MutableList<T> {
+ override val size: Int
+ get() = objectList.size
+
+ override fun contains(element: T): Boolean = objectList.contains(element)
+
+ override fun containsAll(elements: Collection<T>): Boolean =
+ objectList.containsAll(elements)
+
+ override fun get(index: Int): T {
+ checkIndex(index)
+ return objectList[index]
+ }
+
+ override fun indexOf(element: T): Int = objectList.indexOf(element)
+
+ override fun isEmpty(): Boolean = objectList.isEmpty()
+
+ override fun iterator(): MutableIterator<T> = MutableObjectListIterator(this, 0)
+
+ override fun lastIndexOf(element: T): Int = objectList.lastIndexOf(element)
+
+ override fun add(element: T): Boolean = objectList.add(element)
+
+ override fun add(index: Int, element: T) = objectList.add(index, element)
+
+ override fun addAll(index: Int, elements: Collection<T>): Boolean =
+ objectList.addAll(index, elements)
+
+ override fun addAll(elements: Collection<T>): Boolean = objectList.addAll(elements)
+
+ override fun clear() = objectList.clear()
+
+ override fun listIterator(): MutableListIterator<T> = MutableObjectListIterator(this, 0)
+
+ override fun listIterator(index: Int): MutableListIterator<T> =
+ MutableObjectListIterator(this, index)
+
+ override fun remove(element: T): Boolean = objectList.remove(element)
+
+ override fun removeAll(elements: Collection<T>): Boolean = objectList.removeAll(elements)
+
+ override fun removeAt(index: Int): T {
+ checkIndex(index)
+ return objectList.removeAt(index)
+ }
+
+ override fun retainAll(elements: Collection<T>): Boolean = objectList.retainAll(elements)
+
+ override fun set(index: Int, element: T): T {
+ checkIndex(index)
+ return objectList.set(index, element)
+ }
+
+ override fun subList(fromIndex: Int, toIndex: Int): MutableList<T> {
+ checkSubIndex(fromIndex, toIndex)
+ return SubList(this, fromIndex, toIndex)
+ }
+ }
+
+ /**
+ * A view into an underlying [MutableList] that directly accesses the underlying [MutableList].
+ * This is important for the implementation of [List.subList]. A change to the [SubList]
+ * also changes the referenced [MutableList].
+ */
+ private class SubList<T>(
+ private val list: MutableList<T>,
+ private val start: Int,
+ private var end: Int
+ ) : MutableList<T> {
+ override val size: Int
+ get() = end - start
+
+ override fun contains(element: T): Boolean {
+ for (i in start until end) {
+ if (list[i] == element) {
+ return true
+ }
+ }
+ return false
+ }
+
+ override fun containsAll(elements: Collection<T>): Boolean {
+ elements.forEach {
+ if (!contains(it)) {
+ return false
+ }
+ }
+ return true
+ }
+
+ override fun get(index: Int): T {
+ checkIndex(index)
+ return list[index + start]
+ }
+
+ override fun indexOf(element: T): Int {
+ for (i in start until end) {
+ if (list[i] == element) {
+ return i - start
+ }
+ }
+ return -1
+ }
+
+ override fun isEmpty(): Boolean = end == start
+
+ override fun iterator(): MutableIterator<T> = MutableObjectListIterator(this, 0)
+
+ override fun lastIndexOf(element: T): Int {
+ for (i in end - 1 downTo start) {
+ if (list[i] == element) {
+ return i - start
+ }
+ }
+ return -1
+ }
+
+ override fun add(element: T): Boolean {
+ list.add(end++, element)
+ return true
+ }
+
+ override fun add(index: Int, element: T) {
+ list.add(index + start, element)
+ end++
+ }
+
+ override fun addAll(index: Int, elements: Collection<T>): Boolean {
+ list.addAll(index + start, elements)
+ end += elements.size
+ return elements.size > 0
+ }
+
+ override fun addAll(elements: Collection<T>): Boolean {
+ list.addAll(end, elements)
+ end += elements.size
+ return elements.size > 0
+ }
+
+ override fun clear() {
+ for (i in end - 1 downTo start) {
+ list.removeAt(i)
+ }
+ end = start
+ }
+
+ override fun listIterator(): MutableListIterator<T> = MutableObjectListIterator(this, 0)
+
+ override fun listIterator(index: Int): MutableListIterator<T> =
+ MutableObjectListIterator(this, index)
+
+ override fun remove(element: T): Boolean {
+ for (i in start until end) {
+ if (list[i] == element) {
+ list.removeAt(i)
+ end--
+ return true
+ }
+ }
+ return false
+ }
+
+ override fun removeAll(elements: Collection<T>): Boolean {
+ val originalEnd = end
+ elements.forEach {
+ remove(it)
+ }
+ return originalEnd != end
+ }
+
+ override fun removeAt(index: Int): T {
+ checkIndex(index)
+ val element = list.removeAt(index + start)
+ end--
+ return element
+ }
+
+ override fun retainAll(elements: Collection<T>): Boolean {
+ val originalEnd = end
+ for (i in end - 1 downTo start) {
+ val element = list[i]
+ if (element !in elements) {
+ list.removeAt(i)
+ end--
+ }
+ }
+ return originalEnd != end
+ }
+
+ override fun set(index: Int, element: T): T {
+ checkIndex(index)
+ return list.set(index + start, element)
+ }
+
+ override fun subList(fromIndex: Int, toIndex: Int): MutableList<T> {
+ checkSubIndex(fromIndex, toIndex)
+ return SubList(this, fromIndex, toIndex)
+ }
+ }
+}
+
+private fun List<*>.checkIndex(index: Int) {
+ val size = size
+ if (index < 0 || index >= size) {
+ throw IndexOutOfBoundsException("Index $index is out of bounds. " +
+ "The list has $size elements.")
+ }
+}
+
+private fun List<*>.checkSubIndex(fromIndex: Int, toIndex: Int) {
+ val size = size
+ if (fromIndex > toIndex) {
+ throw IllegalArgumentException("Indices are out of order. fromIndex ($fromIndex) is " +
+ "greater than toIndex ($toIndex).")
+ }
+ if (fromIndex < 0) {
+ throw IndexOutOfBoundsException("fromIndex ($fromIndex) is less than 0.")
+ }
+ if (toIndex > size) {
+ throw IndexOutOfBoundsException(
+ "toIndex ($toIndex) is more than than the list size ($size)"
+ )
+ }
+}
+
+// Empty array used when nothing is allocated
+private val EmptyArray = arrayOfNulls<Any>(0)
+
+private val EmptyObjectList: ObjectList<Any?> = MutableObjectList(0)
+
+/**
+ * @return a read-only [ObjectList] with nothing in it.
+ */
+public fun <E> emptyObjectList(): ObjectList<E> = EmptyObjectList as ObjectList<E>
+
+/**
+ * @return a read-only [ObjectList] with nothing in it.
+ */
+public fun <E> objectListOf(): ObjectList<E> = EmptyObjectList as ObjectList<E>
+
+/**
+ * @return a new read-only [ObjectList] with [element1] as the only element in the list.
+ */
+public fun <E> objectListOf(element1: E): ObjectList<E> = mutableObjectListOf(element1)
+
+/**
+ * @return a new read-only [ObjectList] with 2 elements, [element1] and [element2], in order.
+ */
+public fun <E> objectListOf(element1: E, element2: E): ObjectList<E> =
+ mutableObjectListOf(element1, element2)
+
+/**
+ * @return a new read-only [ObjectList] with 3 elements, [element1], [element2], and [element3],
+ * in order.
+ */
+public fun <E> objectListOf(element1: E, element2: E, element3: E): ObjectList<E> =
+ mutableObjectListOf(element1, element2, element3)
+
+/**
+ * @return a new read-only [ObjectList] with [elements] in order.
+ */
+public fun <E> objectListOf(vararg elements: E): ObjectList<E> =
+ MutableObjectList<E>(elements.size).apply { plusAssign(elements as Array<E>) }
+
+/**
+ * @return a new empty [MutableObjectList] with the default capacity.
+ */
+public inline fun <E> mutableObjectListOf(): MutableObjectList<E> = MutableObjectList()
+
+/**
+ * @return a new [MutableObjectList] with [element1] as the only element in the list.
+ */
+public fun <E> mutableObjectListOf(element1: E): MutableObjectList<E> {
+ val list = MutableObjectList<E>(1)
+ list += element1
+ return list
+}
+
+/**
+ * @return a new [MutableObjectList] with 2 elements, [element1] and [element2], in order.
+ */
+public fun <E> mutableObjectListOf(element1: E, element2: E): MutableObjectList<E> {
+ val list = MutableObjectList<E>(2)
+ list += element1
+ list += element2
+ return list
+}
+
+/**
+ * @return a new [MutableObjectList] with 3 elements, [element1], [element2], and [element3],
+ * in order.
+ */
+public fun <E> mutableObjectListOf(element1: E, element2: E, element3: E): MutableObjectList<E> {
+ val list = MutableObjectList<E>(3)
+ list += element1
+ list += element2
+ list += element3
+ return list
+}
+
+/**
+ * @return a new [MutableObjectList] with the given elements, in order.
+ */
+public inline fun <E> mutableObjectListOf(vararg elements: E): MutableObjectList<E> =
+ MutableObjectList<E>(elements.size).apply { plusAssign(elements as Array<E>) }
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectListTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectListTest.kt
new file mode 100644
index 0000000..d524dcf
--- /dev/null
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectListTest.kt
@@ -0,0 +1,1312 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.collection
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertNotEquals
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+
+class ObjectListTest {
+ private val list: MutableObjectList<Int> = mutableObjectListOf(1, 2, 3, 4, 5)
+
+ @Test
+ fun emptyConstruction() {
+ val l = mutableObjectListOf<Int>()
+ assertEquals(0, l.size)
+ assertEquals(16, l.capacity)
+ }
+
+ @Test
+ fun sizeConstruction() {
+ val l = MutableObjectList<Int>(4)
+ assertEquals(4, l.capacity)
+ }
+
+ @Test
+ fun contentConstruction() {
+ val l = mutableObjectListOf(1, 2, 3)
+ assertEquals(3, l.size)
+ assertEquals(1, l[0])
+ assertEquals(2, l[1])
+ assertEquals(3, l[2])
+ assertEquals(3, l.capacity)
+ repeat(2) {
+ val l2 = mutableObjectListOf(1, 2, 3, 4, 5)
+ assertEquals(list, l2)
+ l2.removeAt(0)
+ }
+ }
+
+ @Test
+ fun hashCodeTest() {
+ val l2 = mutableObjectListOf(1, 2, 3, 4, 5)
+ assertEquals(list.hashCode(), l2.hashCode())
+ l2.removeAt(4)
+ assertNotEquals(list.hashCode(), l2.hashCode())
+ l2.add(5)
+ assertEquals(list.hashCode(), l2.hashCode())
+ l2.clear()
+ assertNotEquals(list.hashCode(), l2.hashCode())
+ }
+
+ @Test
+ fun equalsTest() {
+ val l2 = mutableObjectListOf(1, 2, 3, 4, 5)
+ assertEquals(list, l2)
+ assertNotEquals(list, mutableObjectListOf())
+ l2.removeAt(4)
+ assertNotEquals(list, l2)
+ l2.add(5)
+ assertEquals(list, l2)
+ l2.clear()
+ assertNotEquals(list, l2)
+ }
+
+ @Test
+ fun string() {
+ assertEquals("[1, 2, 3, 4, 5]", list.toString())
+ assertEquals("[]", mutableObjectListOf<Int>().toString())
+ }
+
+ @Test
+ fun size() {
+ assertEquals(5, list.size)
+ assertEquals(5, list.count())
+ val l2 = mutableObjectListOf<Int>()
+ assertEquals(0, l2.size)
+ assertEquals(0, l2.count())
+ l2 += 1
+ assertEquals(1, l2.size)
+ assertEquals(1, l2.count())
+ }
+
+ @Test
+ fun get() {
+ assertEquals(1, list[0])
+ assertEquals(5, list[4])
+ assertEquals(1, list.elementAt(0))
+ assertEquals(5, list.elementAt(4))
+ }
+
+ @Test
+ fun getOutOfBounds() {
+ assertFailsWith(IndexOutOfBoundsException::class) {
+ list[5]
+ }
+ }
+
+ @Test
+ fun getOutOfBoundsNegative() {
+ assertFailsWith(IndexOutOfBoundsException::class) {
+ list[-1]
+ }
+ }
+
+ @Test
+ fun elementAtOfBounds() {
+ assertFailsWith(IndexOutOfBoundsException::class) {
+ list.elementAt(5)
+ }
+ }
+
+ @Test
+ fun elementAtOfBoundsNegative() {
+ assertFailsWith(IndexOutOfBoundsException::class) {
+ list.elementAt(-1)
+ }
+ }
+
+ @Test
+ fun elementAtOrElse() {
+ assertEquals(1, list.elementAtOrElse(0) {
+ assertEquals(0, it)
+ 0
+ })
+ assertEquals(0, list.elementAtOrElse(-1) {
+ assertEquals(-1, it)
+ 0
+ })
+ assertEquals(0, list.elementAtOrElse(5) {
+ assertEquals(5, it)
+ 0
+ })
+ }
+
+ @Test
+ fun count() {
+ assertEquals(1, list.count { it < 2 })
+ assertEquals(0, list.count { it < 0 })
+ assertEquals(5, list.count { it < 10 })
+ }
+
+ @Test
+ fun isEmpty() {
+ assertFalse(list.isEmpty())
+ assertFalse(list.none())
+ assertTrue(mutableObjectListOf<Int>().isEmpty())
+ assertTrue(mutableObjectListOf<Int>().none())
+ }
+
+ @Test
+ fun isNotEmpty() {
+ assertTrue(list.isNotEmpty())
+ assertTrue(list.any())
+ assertFalse(mutableObjectListOf<Int>().isNotEmpty())
+ }
+
+ @Test
+ fun indices() {
+ assertEquals(IntRange(0, 4), list.indices)
+ assertEquals(IntRange(0, -1), mutableObjectListOf<Int>().indices)
+ }
+
+ @Test
+ fun any() {
+ assertTrue(list.any { it == 5 })
+ assertTrue(list.any { it == 1 })
+ assertFalse(list.any { it == 0 })
+ }
+
+ @Test
+ fun reversedAny() {
+ val reversedList = mutableObjectListOf<Int>()
+ assertFalse(
+ list.reversedAny {
+ reversedList.add(it)
+ false
+ }
+ )
+ val reversedContent = mutableObjectListOf(5, 4, 3, 2, 1)
+ assertEquals(reversedContent, reversedList)
+
+ val reversedSublist = mutableObjectListOf<Int>()
+ assertTrue(
+ list.reversedAny {
+ reversedSublist.add(it)
+ reversedSublist.size == 2
+ }
+ )
+ assertEquals(reversedSublist, mutableObjectListOf(5, 4))
+ }
+
+ @Test
+ fun forEach() {
+ val copy = mutableObjectListOf<Int>()
+ list.forEach { copy += it }
+ assertEquals(list, copy)
+ }
+
+ @Test
+ fun forEachReversed() {
+ val copy = mutableObjectListOf<Int>()
+ list.forEachReversed { copy += it }
+ assertEquals(copy, mutableObjectListOf(5, 4, 3, 2, 1))
+ }
+
+ @Test
+ fun forEachIndexed() {
+ val copy = mutableObjectListOf<Int>()
+ val indices = mutableObjectListOf<Int>()
+ list.forEachIndexed { index, open ->
+ copy += open
+ indices += index
+ }
+ assertEquals(list, copy)
+ assertEquals(indices, mutableObjectListOf(0, 1, 2, 3, 4))
+ }
+
+ @Test
+ fun forEachReversedIndexed() {
+ val copy = mutableObjectListOf<Int>()
+ val indices = mutableObjectListOf<Int>()
+ list.forEachReversedIndexed { index, open ->
+ copy += open
+ indices += index
+ }
+ assertEquals(copy, mutableObjectListOf(5, 4, 3, 2, 1))
+ assertEquals(indices, mutableObjectListOf(4, 3, 2, 1, 0))
+ }
+
+ @Test
+ fun indexOfFirst() {
+ assertEquals(0, list.indexOfFirst { it < 2 })
+ assertEquals(4, list.indexOfFirst { it > 4 })
+ assertEquals(-1, list.indexOfFirst { it < 0 })
+ assertEquals(0, mutableObjectListOf(8, 8).indexOfFirst { it > 7 })
+ }
+
+ @Test
+ fun firstOrNullNoParam() {
+ assertEquals(1, list.firstOrNull())
+ assertNull(emptyObjectList<Int>().firstOrNull())
+ }
+
+ @Test
+ fun firstOrNull() {
+ assertEquals(1, list.firstOrNull { it < 5 })
+ assertEquals(3, list.firstOrNull { it > 2 })
+ assertEquals(5, list.firstOrNull { it > 4 })
+ assertNull(list.firstOrNull { it > 5 })
+ }
+
+ @Test
+ fun lastOrNullNoParam() {
+ assertEquals(5, list.lastOrNull())
+ assertNull(emptyObjectList<Int>().lastOrNull())
+ }
+
+ @Test
+ fun lastOrNull() {
+ assertEquals(4, list.lastOrNull { it < 5 })
+ assertEquals(5, list.lastOrNull { it > 2 })
+ assertEquals(1, list.lastOrNull { it < 2 })
+ assertNull(list.firstOrNull { it > 5 })
+ }
+
+ @Test
+ fun indexOfLast() {
+ assertEquals(0, list.indexOfLast { it < 2 })
+ assertEquals(4, list.indexOfLast { it > 4 })
+ assertEquals(-1, list.indexOfLast { it < 0 })
+ assertEquals(1, objectListOf(8, 8).indexOfLast { it > 7 })
+ }
+
+ @Test
+ fun contains() {
+ assertTrue(list.contains(5))
+ assertTrue(list.contains(1))
+ assertFalse(list.contains(0))
+ }
+
+ @Test
+ fun containsAllList() {
+ assertTrue(list.containsAll(mutableObjectListOf(2, 3, 1)))
+ assertFalse(list.containsAll(mutableObjectListOf(2, 3, 6)))
+ }
+
+ @Test
+ fun lastIndexOf() {
+ assertEquals(4, list.lastIndexOf(5))
+ assertEquals(1, list.lastIndexOf(2))
+ val copy = mutableObjectListOf<Int>()
+ copy.addAll(list)
+ copy.addAll(list)
+ assertEquals(5, copy.lastIndexOf(1))
+ }
+
+ @Test
+ fun first() {
+ assertEquals(1, list.first())
+ }
+
+ @Test
+ fun firstException() {
+ assertFailsWith(NoSuchElementException::class) {
+ mutableObjectListOf<Int>().first()
+ }
+ }
+
+ @Test
+ fun firstWithPredicate() {
+ assertEquals(5, list.first { it > 4 })
+ assertEquals(1, mutableObjectListOf(1, 5).first { it > 0 })
+ }
+
+ @Test
+ fun firstWithPredicateException() {
+ assertFailsWith(NoSuchElementException::class) {
+ mutableObjectListOf<Int>().first { it > 8 }
+ }
+ }
+
+ @Test
+ fun last() {
+ assertEquals(5, list.last())
+ }
+
+ @Test
+ fun lastException() {
+ assertFailsWith(NoSuchElementException::class) {
+ mutableObjectListOf<Int>().last()
+ }
+ }
+
+ @Test
+ fun lastWithPredicate() {
+ assertEquals(1, list.last { it < 2 })
+ assertEquals(5, objectListOf(1, 5).last { it > 0 })
+ }
+
+ @Test
+ fun lastWithPredicateException() {
+ assertFailsWith<NoSuchElementException> {
+ objectListOf(2).last { it > 2 }
+ }
+ }
+
+ @Test
+ fun fold() {
+ assertEquals("12345", list.fold("") { acc, i -> acc + i.toString() })
+ }
+
+ @Test
+ fun foldIndexed() {
+ assertEquals(
+ "01-12-23-34-45-",
+ list.foldIndexed("") { index, acc, i ->
+ "$acc$index$i-"
+ }
+ )
+ }
+
+ @Test
+ fun foldRight() {
+ assertEquals("54321", list.foldRight("") { i, acc -> acc + i.toString() })
+ }
+
+ @Test
+ fun foldRightIndexed() {
+ assertEquals(
+ "45-34-23-12-01-",
+ list.foldRightIndexed("") { index, i, acc ->
+ "$acc$index$i-"
+ }
+ )
+ }
+
+ @Test
+ fun add() {
+ val l = mutableObjectListOf(1, 2, 3)
+ l += 4
+ l.add(5)
+ assertEquals(list, l)
+ }
+
+ @Test
+ fun addAtIndex() {
+ val l = mutableObjectListOf(2, 4)
+ l.add(2, 5)
+ l.add(0, 1)
+ l.add(2, 3)
+ assertEquals(list, l)
+ assertFailsWith(IndexOutOfBoundsException::class) {
+ l.add(-1, 2)
+ }
+ assertFailsWith(IndexOutOfBoundsException::class) {
+ l.add(6, 2)
+ }
+ }
+
+ @Test
+ fun addAllListAtIndex() {
+ val l = mutableObjectListOf(4)
+ val l2 = mutableObjectListOf(1, 2)
+ val l3 = mutableObjectListOf(5)
+ val l4 = mutableObjectListOf(3)
+ assertTrue(l4.addAll(1, l3))
+ assertTrue(l4.addAll(0, l2))
+ assertTrue(l4.addAll(3, l))
+ assertFalse(l4.addAll(0, mutableObjectListOf()))
+ assertEquals(list, l4)
+ assertFailsWith(IndexOutOfBoundsException::class) {
+ l4.addAll(6, mutableObjectListOf())
+ }
+ assertFailsWith(IndexOutOfBoundsException::class) {
+ l4.addAll(-1, mutableObjectListOf())
+ }
+ }
+
+ @Test
+ fun addAllObjectList() {
+ val l = MutableObjectList<Int>()
+ l.add(3)
+ l.add(4)
+ l.add(5)
+ val l2 = mutableObjectListOf(1, 2)
+ assertTrue(l2.addAll(l))
+ assertEquals(list, l2)
+ assertFalse(l2.addAll(mutableObjectListOf()))
+ }
+
+ @Test
+ fun addAllList() {
+ val l = listOf(3, 4, 5)
+ val l2 = mutableObjectListOf(1, 2)
+ assertTrue(l2.addAll(l))
+ assertEquals(list, l2)
+ assertFalse(l2.addAll(mutableObjectListOf()))
+ }
+
+ @Test
+ fun addAllIterable() {
+ val l = listOf(3, 4, 5) as Iterable<Int>
+ val l2 = mutableObjectListOf(1, 2)
+ assertTrue(l2.addAll(l))
+ assertEquals(list, l2)
+ assertFalse(l2.addAll(mutableObjectListOf()))
+ }
+
+ @Test
+ fun addAllSequence() {
+ val l = listOf(3, 4, 5).asSequence()
+ val l2 = mutableObjectListOf(1, 2)
+ assertTrue(l2.addAll(l))
+ assertEquals(list, l2)
+ assertFalse(l2.addAll(mutableObjectListOf()))
+ }
+
+ @Test
+ fun plusAssignObjectList() {
+ val l = objectListOf(3, 4, 5)
+ val l2 = mutableObjectListOf(1, 2)
+ l2 += l
+ assertEquals(list, l2)
+ }
+
+ @Test
+ fun plusAssignIterable() {
+ val l = listOf(3, 4, 5) as Iterable<Int>
+ val l2 = mutableObjectListOf(1, 2)
+ l2 += l
+ assertEquals(list, l2)
+ }
+
+ @Test
+ fun plusAssignSequence() {
+ val l = arrayOf(3, 4, 5).asSequence()
+ val l2 = mutableObjectListOf(1, 2)
+ l2 += l
+ assertEquals(list, l2)
+ }
+
+ @Test
+ fun addAllArrayAtIndex() {
+ val a1 = arrayOf(4)
+ val a2 = arrayOf(1, 2)
+ val a3 = arrayOf(5)
+ val l = mutableObjectListOf(3)
+ assertTrue(l.addAll(1, a3))
+ assertTrue(l.addAll(0, a2))
+ assertTrue(l.addAll(3, a1))
+ assertFalse(l.addAll(0, arrayOf()))
+ assertEquals(list, l)
+ assertFailsWith(IndexOutOfBoundsException::class) {
+ l.addAll(6, arrayOf())
+ }
+ assertFailsWith(IndexOutOfBoundsException::class) {
+ l.addAll(-1, arrayOf())
+ }
+ }
+
+ @Test
+ fun addAllArray() {
+ val a = arrayOf(3, 4, 5)
+ val v = mutableObjectListOf(1, 2)
+ v.addAll(a)
+ assertEquals(5, v.size)
+ assertEquals(3, v[2])
+ assertEquals(4, v[3])
+ assertEquals(5, v[4])
+ }
+
+ @Test
+ fun plusAssignArray() {
+ val a = arrayOf(3, 4, 5)
+ val v = mutableObjectListOf(1, 2)
+ v += a
+ assertEquals(list, v)
+ }
+
+ @Test
+ fun clear() {
+ val l = mutableObjectListOf<Int>()
+ l.addAll(list)
+ assertTrue(l.isNotEmpty())
+ l.clear()
+ assertTrue(l.isEmpty())
+ repeat(5) { index ->
+ assertNull(l.content[index])
+ }
+ }
+
+ @Test
+ fun trim() {
+ val l = mutableObjectListOf(1)
+ l.trim()
+ assertEquals(1, l.capacity)
+ l += arrayOf(1, 2, 3, 4, 5)
+ l.trim()
+ assertEquals(6, l.capacity)
+ assertEquals(6, l.size)
+ l.clear()
+ l.trim()
+ assertEquals(0, l.capacity)
+ l.trim(100)
+ assertEquals(0, l.capacity)
+ l += arrayOf(1, 2, 3, 4, 5)
+ l -= 5
+ l.trim(5)
+ assertEquals(5, l.capacity)
+ l.trim(4)
+ assertEquals(4, l.capacity)
+ l.trim(3)
+ assertEquals(4, l.capacity)
+ }
+
+ @Test
+ fun remove() {
+ val l = mutableObjectListOf(1, 2, 3, 4, 5)
+ l.remove(3)
+ assertEquals(mutableObjectListOf(1, 2, 4, 5), l)
+ }
+
+ @Test
+ fun removeIf() {
+ val l = mutableObjectListOf(1, 2, 3, 4, 5, 6)
+ l.removeIf { it == 100 }
+ assertEquals(objectListOf(1, 2, 3, 4, 5, 6), l)
+ l.removeIf { it % 2 == 0 }
+ assertEquals(objectListOf(1, 3, 5), l)
+ repeat(3) {
+ assertNull(l.content[3 + it])
+ }
+ l.removeIf { it != 3 }
+ assertEquals(objectListOf(3), l)
+ }
+
+ @Test
+ fun removeAt() {
+ val l = mutableObjectListOf(1, 2, 3, 4, 5)
+ l.removeAt(2)
+ assertNull(l.content[4])
+ assertEquals(mutableObjectListOf(1, 2, 4, 5), l)
+ assertFailsWith(IndexOutOfBoundsException::class) {
+ l.removeAt(6)
+ }
+ assertFailsWith(IndexOutOfBoundsException::class) {
+ l.removeAt(-1)
+ }
+ }
+
+ @Test
+ fun set() {
+ val l = mutableObjectListOf(0, 0, 0, 0, 0)
+ l[0] = 1
+ l[4] = 5
+ l[2] = 3
+ l[1] = 2
+ l[3] = 4
+ assertEquals(list, l)
+ assertFailsWith<IndexOutOfBoundsException> {
+ l[-1] = 1
+ }
+ assertFailsWith<IndexOutOfBoundsException> {
+ l[6] = 1
+ }
+ assertEquals(4, l.set(3, 1));
+ }
+
+ @Test
+ fun ensureCapacity() {
+ val l = mutableObjectListOf(1)
+ assertEquals(1, l.capacity)
+ l.ensureCapacity(5)
+ assertEquals(5, l.capacity)
+ }
+
+ @Test
+ fun removeAllObjectList() {
+ assertFalse(list.removeAll(mutableObjectListOf(0, 10, 15)))
+ val l = mutableObjectListOf(0, 1, 15, 10, 2, 3, 4, 5, 20, 5)
+ assertTrue(l.removeAll(mutableObjectListOf(20, 0, 15, 10, 5)))
+ assertEquals(list, l)
+ }
+
+ @Test
+ fun removeAllScatterSet() {
+ assertFalse(list.removeAll(scatterSetOf(0, 10, 15)))
+ val l = mutableObjectListOf(0, 1, 15, 10, 2, 3, 4, 5, 20, 5)
+ assertTrue(l.removeAll(scatterSetOf(20, 0, 15, 10, 5)))
+ assertEquals(list, l)
+ }
+
+ @Test
+ fun removeAllArray() {
+ assertFalse(list.removeAll(arrayOf(0, 10, 15)))
+ val l = mutableObjectListOf(0, 1, 15, 10, 2, 3, 4, 5, 20, 5)
+ assertTrue(l.removeAll(arrayOf(20, 0, 15, 10, 5)))
+ assertEquals(list, l)
+ }
+
+ @Test
+ fun removeAllList() {
+ assertFalse(list.removeAll(listOf(0, 10, 15)))
+ val l = mutableObjectListOf(0, 1, 15, 10, 2, 3, 4, 5, 20, 5)
+ assertTrue(l.removeAll(listOf(20, 0, 15, 10, 5)))
+ assertEquals(list, l)
+ }
+
+ @Test
+ fun removeAllIterable() {
+ assertFalse(list.removeAll(listOf(0, 10, 15) as Iterable<Int>))
+ val l = mutableObjectListOf(0, 1, 15, 10, 2, 3, 4, 5, 20, 5)
+ assertTrue(l.removeAll(listOf(20, 0, 15, 10, 5) as Iterable<Int>))
+ assertEquals(list, l)
+ }
+
+ @Test
+ fun removeAllSequence() {
+ assertFalse(list.removeAll(listOf(0, 10, 15).asSequence()))
+ val l = mutableObjectListOf(0, 1, 15, 10, 2, 3, 4, 5, 20, 5)
+ assertTrue(l.removeAll(listOf(20, 0, 15, 10, 5).asSequence()))
+ assertEquals(list, l)
+ }
+
+ @Test
+ fun minusAssignObjectList() {
+ val l = mutableObjectListOf<Int>().also { it += list }
+ l -= mutableObjectListOf(0, 10, 15)
+ assertEquals(list, l)
+ val l2 = mutableObjectListOf(0, 1, 15, 10, 2, 3, 4, 5, 20, 5)
+ l2 -= mutableObjectListOf(20, 0, 15, 10, 5)
+ assertEquals(list, l2)
+ }
+
+ @Test
+ fun minusAssignScatterSet() {
+ val l = mutableObjectListOf<Int>().also { it += list }
+ l -= scatterSetOf(0, 10, 15)
+ assertEquals(list, l)
+ val l2 = mutableObjectListOf(0, 1, 15, 10, 2, 3, 4, 5, 20, 5)
+ l2 -= scatterSetOf(20, 0, 15, 10, 5)
+ assertEquals(list, l2)
+ }
+
+ @Test
+ fun minusAssignArray() {
+ val l = mutableObjectListOf<Int>().also { it += list }
+ l -= arrayOf(0, 10, 15)
+ assertEquals(list, l)
+ val l2 = mutableObjectListOf(0, 1, 15, 10, 2, 3, 4, 5, 20, 5)
+ l2 -= arrayOf(20, 0, 15, 10, 5)
+ assertEquals(list, l2)
+ }
+
+ @Test
+ fun minusAssignList() {
+ val l = mutableObjectListOf<Int>().also { it += list }
+ l -= listOf(0, 10, 15)
+ assertEquals(list, l)
+ val l2 = mutableObjectListOf(0, 1, 15, 10, 2, 3, 4, 5, 20, 5)
+ l2 -= listOf(20, 0, 15, 10, 5)
+ assertEquals(list, l2)
+ }
+
+ @Test
+ fun minusAssignIterable() {
+ val l = mutableObjectListOf<Int>().also { it += list }
+ l -= listOf(0, 10, 15) as Iterable<Int>
+ assertEquals(list, l)
+ val l2 = mutableObjectListOf(0, 1, 15, 10, 2, 3, 4, 5, 20, 5)
+ l2 -= listOf(20, 0, 15, 10, 5) as Iterable<Int>
+ assertEquals(list, l2)
+ }
+
+ @Test
+ fun minusAssignSequence() {
+ val l = mutableObjectListOf<Int>().also { it += list }
+ l -= listOf(0, 10, 15).asSequence()
+ assertEquals(list, l)
+ val l2 = mutableObjectListOf(0, 1, 15, 10, 2, 3, 4, 5, 20, 5)
+ l2 -= listOf(20, 0, 15, 10, 5).asSequence()
+ assertEquals(list, l2)
+ }
+
+ @Test
+ fun retainAll() {
+ assertFalse(list.retainAll(mutableObjectListOf(1, 2, 3, 4, 5, 6)))
+ val l = mutableObjectListOf(0, 1, 15, 10, 2, 3, 4, 5, 20)
+ assertTrue(l.retainAll(mutableObjectListOf(1, 2, 3, 4, 5, 6)))
+ assertEquals(list, l)
+ }
+
+ @Test
+ fun retainAllArray() {
+ assertFalse(list.retainAll(arrayOf(1, 2, 3, 4, 5, 6)))
+ val l = mutableObjectListOf(0, 1, 15, 10, 2, 3, 4, 5, 20)
+ assertTrue(l.retainAll(arrayOf(1, 2, 3, 4, 5, 6)))
+ assertEquals(list, l)
+ }
+
+ @Test
+ fun retainAllCollection() {
+ assertFalse(list.retainAll(listOf(1, 2, 3, 4, 5, 6)))
+ val l = mutableObjectListOf(0, 1, 15, 10, 2, 3, 4, 5, 20)
+ assertTrue(l.retainAll(listOf(1, 2, 3, 4, 5, 6)))
+ assertEquals(list, l)
+ }
+
+ @Test
+ fun retainAllIterable() {
+ assertFalse(list.retainAll(listOf(1, 2, 3, 4, 5, 6) as Iterable<Int>))
+ val l = mutableObjectListOf(0, 1, 15, 10, 2, 3, 4, 5, 20)
+ assertTrue(l.retainAll(listOf(1, 2, 3, 4, 5, 6) as Iterable<Int>))
+ assertEquals(list, l)
+ }
+
+ @Test
+ fun retainAllSequence() {
+ assertFalse(list.retainAll(arrayOf(1, 2, 3, 4, 5, 6).asSequence()))
+ val l = mutableObjectListOf(0, 1, 15, 10, 2, 3, 4, 5, 20)
+ assertTrue(l.retainAll(arrayOf(1, 2, 3, 4, 5, 6).asSequence()))
+ assertEquals(list, l)
+ }
+
+ @Test
+ fun removeRange() {
+ val l = mutableObjectListOf(1, 9, 7, 6, 2, 3, 4, 5)
+ l.removeRange(1, 4)
+ assertNull(l.content[5])
+ assertNull(l.content[6])
+ assertNull(l.content[7])
+ assertEquals(list, l)
+ assertFailsWith<IndexOutOfBoundsException> {
+ l.removeRange(6, 6)
+ }
+ assertFailsWith<IndexOutOfBoundsException> {
+ l.removeRange(100, 200)
+ }
+ assertFailsWith<IndexOutOfBoundsException> {
+ l.removeRange(-1, 0)
+ }
+ assertFailsWith<IllegalArgumentException> {
+ l.removeRange(3, 2)
+ }
+ }
+
+ @Test
+ fun testEmptyObjectList() {
+ val l = emptyObjectList<Int>()
+ assertEquals(0, l.size)
+ }
+
+ @Test
+ fun objectListOfEmpty() {
+ val l = objectListOf<Int>()
+ assertEquals(0, l.size)
+ }
+
+ @Test
+ fun objectListOfOneValue() {
+ val l = objectListOf(2)
+ assertEquals(1, l.size)
+ assertEquals(2, l[0])
+ }
+
+ @Test
+ fun objectListOfTwoValues() {
+ val l = objectListOf(2, 1)
+ assertEquals(2, l.size)
+ assertEquals(2, l[0])
+ assertEquals(1, l[1])
+ }
+
+ @Test
+ fun objectListOfThreeValues() {
+ val l = objectListOf(2, 10, -1)
+ assertEquals(3, l.size)
+ assertEquals(2, l[0])
+ assertEquals(10, l[1])
+ assertEquals(-1, l[2])
+ }
+
+ @Test
+ fun objectListOfFourValues() {
+ val l = objectListOf(2, 10, -1, 10)
+ assertEquals(4, l.size)
+ assertEquals(2, l[0])
+ assertEquals(10, l[1])
+ assertEquals(-1, l[2])
+ assertEquals(10, l[3])
+ }
+
+ @Test
+ fun mutableObjectListOfOneValue() {
+ val l = mutableObjectListOf(2)
+ assertEquals(1, l.size)
+ assertEquals(1, l.capacity)
+ assertEquals(2, l[0])
+ }
+
+ @Test
+ fun mutableObjectListOfTwoValues() {
+ val l = mutableObjectListOf(2, 1)
+ assertEquals(2, l.size)
+ assertEquals(2, l.capacity)
+ assertEquals(2, l[0])
+ assertEquals(1, l[1])
+ }
+
+ @Test
+ fun mutableObjectListOfThreeValues() {
+ val l = mutableObjectListOf(2, 10, -1)
+ assertEquals(3, l.size)
+ assertEquals(3, l.capacity)
+ assertEquals(2, l[0])
+ assertEquals(10, l[1])
+ assertEquals(-1, l[2])
+ }
+
+ @Test
+ fun mutableObjectListOfFourValues() {
+ val l = mutableObjectListOf(2, 10, -1, 10)
+ assertEquals(4, l.size)
+ assertEquals(4, l.capacity)
+ assertEquals(2, l[0])
+ assertEquals(10, l[1])
+ assertEquals(-1, l[2])
+ assertEquals(10, l[3])
+ }
+
+ @Test
+ fun iterator() {
+ val l = mutableObjectListOf(1, 2, 3, 4, 5)
+ val iterator = l.asMutableList().iterator()
+ assertTrue(iterator.hasNext())
+ assertEquals(1, iterator.next())
+ assertTrue(iterator.hasNext())
+ assertEquals(2, iterator.next())
+ assertTrue(iterator.hasNext())
+ assertEquals(3, iterator.next())
+ assertTrue(iterator.hasNext())
+ iterator.remove()
+ assertTrue(iterator.hasNext())
+ assertEquals(l, mutableObjectListOf(1, 2, 4, 5))
+
+ assertEquals(4, iterator.next())
+ assertTrue(iterator.hasNext())
+ assertEquals(5, iterator.next())
+ assertFalse(iterator.hasNext())
+ iterator.remove()
+ assertEquals(l, mutableObjectListOf(1, 2, 4))
+ }
+
+ @Test
+ fun listIterator() {
+ val l = mutableObjectListOf(1, 2, 3, 4, 5)
+ val iterator = l.asMutableList().listIterator()
+ assertEquals(1, iterator.next())
+ assertEquals(1, iterator.previous())
+ assertEquals(0, iterator.nextIndex())
+ iterator.add(6)
+ assertEquals(1, iterator.nextIndex())
+ assertEquals(0, iterator.previousIndex())
+ assertEquals(6, iterator.previous())
+ assertEquals(l, mutableObjectListOf(6, 1, 2, 3, 4, 5))
+ }
+
+ @Test
+ fun listIteratorInitialIndex() {
+ val iterator = list.asMutableList().listIterator(2)
+ assertEquals(2, iterator.nextIndex())
+ }
+
+ @Test
+ fun subList() {
+ val l = list.asMutableList().subList(1, 4)
+ assertEquals(3, l.size)
+ assertEquals(2, l[0])
+ assertEquals(3, l[1])
+ assertEquals(4, l[2])
+ }
+
+ @Test
+ fun subListContains() {
+ val l = list.asMutableList().subList(1, 4)
+ assertTrue(l.contains(2))
+ assertTrue(l.contains(3))
+ assertTrue(l.contains(4))
+ assertFalse(l.contains(5))
+ assertFalse(l.contains(1))
+ }
+
+ @Test
+ fun subListContainsAll() {
+ val l = list.asMutableList().subList(1, 4)
+ val smallList = listOf(2, 3, 4)
+ assertTrue(l.containsAll(smallList))
+ val largeList = listOf(3, 4, 5)
+ assertFalse(l.containsAll(largeList))
+ }
+
+ @Test
+ fun subListIndexOf() {
+ val l = list.asMutableList().subList(1, 4)
+ assertEquals(0, l.indexOf(2))
+ assertEquals(2, l.indexOf(4))
+ assertEquals(-1, l.indexOf(1))
+ val l2 = mutableObjectListOf(2, 1, 1, 3).asMutableList().subList(1, 2)
+ assertEquals(0, l2.indexOf(1))
+ }
+
+ @Test
+ fun subListIsEmpty() {
+ val l = list.asMutableList().subList(1, 4)
+ assertFalse(l.isEmpty())
+ assertTrue(list.asMutableList().subList(4, 4).isEmpty())
+ }
+
+ @Test
+ fun subListIterator() {
+ val l = list.asMutableList().subList(1, 4)
+ val l2 = mutableListOf<Int>()
+ l.forEach { l2 += it }
+ assertEquals(3, l2.size)
+ assertEquals(2, l2[0])
+ assertEquals(3, l2[1])
+ assertEquals(4, l2[2])
+ }
+
+ @Test
+ fun subListLastIndexOf() {
+ val l = list.asMutableList().subList(1, 4)
+ assertEquals(0, l.lastIndexOf(2))
+ assertEquals(2, l.lastIndexOf(4))
+ assertEquals(-1, l.lastIndexOf(1))
+ val l2 = mutableObjectListOf(2, 1, 1, 3).asMutableList().subList(1, 3)
+ assertEquals(1, l2.lastIndexOf(1))
+ }
+
+ @Test
+ fun subListAdd() {
+ val v = mutableObjectListOf(1, 2, 3)
+ val l = v.asMutableList().subList(1, 2)
+ assertTrue(l.add(4))
+ assertEquals(2, l.size)
+ assertEquals(4, v.size)
+ assertEquals(2, l[0])
+ assertEquals(4, l[1])
+ assertEquals(2, v[1])
+ assertEquals(4, v[2])
+ assertEquals(3, v[3])
+ }
+
+ @Test
+ fun subListAddIndex() {
+ val v = mutableObjectListOf(6, 1, 2, 3)
+ val l = v.asMutableList().subList(1, 3)
+ l.add(1, 4)
+ assertEquals(3, l.size)
+ assertEquals(5, v.size)
+ assertEquals(1, l[0])
+ assertEquals(4, l[1])
+ assertEquals(2, l[2])
+ assertEquals(1, v[1])
+ assertEquals(4, v[2])
+ assertEquals(2, v[3])
+ }
+
+ @Test
+ fun subListAddAllAtIndex() {
+ val v = mutableObjectListOf(6, 1, 2, 3)
+ val l = v.asMutableList().subList(1, 3)
+ l.addAll(1, listOf(4, 5))
+ assertEquals(4, l.size)
+ assertEquals(6, v.size)
+ assertEquals(1, l[0])
+ assertEquals(4, l[1])
+ assertEquals(5, l[2])
+ assertEquals(2, l[3])
+ assertEquals(1, v[1])
+ assertEquals(4, v[2])
+ assertEquals(5, v[3])
+ assertEquals(2, v[4])
+ }
+
+ @Test
+ fun subListAddAll() {
+ val v = mutableObjectListOf(6, 1, 2, 3)
+ val l = v.asMutableList().subList(1, 3)
+ l.addAll(listOf(4, 5))
+ assertEquals(4, l.size)
+ assertEquals(6, v.size)
+ assertEquals(1, l[0])
+ assertEquals(2, l[1])
+ assertEquals(4, l[2])
+ assertEquals(5, l[3])
+ assertEquals(1, v[1])
+ assertEquals(2, v[2])
+ assertEquals(4, v[3])
+ assertEquals(5, v[4])
+ assertEquals(3, v[5])
+ }
+
+ @Test
+ fun subListClear() {
+ val v = mutableObjectListOf(1, 2, 3, 4, 5)
+ val l = v.asMutableList().subList(1, 4)
+ l.clear()
+ assertEquals(0, l.size)
+ assertEquals(2, v.size)
+ assertEquals(1, v[0])
+ assertEquals(5, v[1])
+ kotlin.test.assertNull(v.content[2])
+ kotlin.test.assertNull(v.content[3])
+ kotlin.test.assertNull(v.content[4])
+ }
+
+ @Test
+ fun subListListIterator() {
+ val l = list.asMutableList().subList(1, 4)
+ val listIterator = l.listIterator()
+ assertTrue(listIterator.hasNext())
+ assertFalse(listIterator.hasPrevious())
+ assertEquals(0, listIterator.nextIndex())
+ assertEquals(2, listIterator.next())
+ }
+
+ @Test
+ fun subListListIteratorWithIndex() {
+ val l = list.asMutableList().subList(1, 4)
+ val listIterator = l.listIterator(1)
+ assertTrue(listIterator.hasNext())
+ assertTrue(listIterator.hasPrevious())
+ assertEquals(1, listIterator.nextIndex())
+ assertEquals(3, listIterator.next())
+ }
+
+ @Test
+ fun subListRemove() {
+ val l = mutableObjectListOf(1, 2, 3, 4, 5)
+ val l2 = l.asMutableList().subList(1, 4)
+ assertTrue(l2.remove(3))
+ assertEquals(l, mutableObjectListOf(1, 2, 4, 5))
+ assertEquals(2, l2.size)
+ assertEquals(2, l2[0])
+ assertEquals(4, l2[1])
+ assertFalse(l2.remove(3))
+ assertEquals(l, mutableObjectListOf(1, 2, 4, 5))
+ assertEquals(2, l2.size)
+ }
+
+ @Test
+ fun subListRemoveAll() {
+ val l = mutableObjectListOf(1, 2, 3, 4, 5)
+ val l2 = l.asMutableList().subList(1, 4)
+ assertFalse(l2.removeAll(listOf(1, 5, -1)))
+ assertEquals(5, l.size)
+ assertEquals(3, l2.size)
+ assertTrue(l2.removeAll(listOf(3, 4, 5)))
+ assertEquals(3, l.size)
+ assertEquals(1, l2.size)
+ }
+
+ @Test
+ fun subListRemoveAt() {
+ val l = mutableObjectListOf(1, 2, 3, 4, 5)
+ val l2 = l.asMutableList().subList(1, 4)
+ assertEquals(3, l2.removeAt(1))
+ assertEquals(4, l.size)
+ assertEquals(2, l2.size)
+ assertEquals(4, l2.removeAt(1))
+ assertEquals(1, l2.size)
+ }
+
+ @Test
+ fun subListRetainAll() {
+ val l = mutableObjectListOf(1, 2, 3, 4, 5)
+ val l2 = l.asMutableList().subList(1, 4)
+ val l3 = objectListOf(1, 2, 3, 4, 5)
+ assertFalse(l2.retainAll(l3.asList()))
+ assertFalse(l2.retainAll(listOf(2, 3, 4)))
+ assertEquals(3, l2.size)
+ assertEquals(5, l.size)
+ assertTrue(l2.retainAll(setOf(1, 2, 4)))
+ assertEquals(4, l.size)
+ assertEquals(2, l2.size)
+ assertEquals(l, objectListOf(1, 2, 4, 5))
+ }
+
+ @Test
+ fun subListSet() {
+ val l = mutableObjectListOf(1, 2, 3, 4, 5)
+ val l2 = l.asMutableList().subList(1, 4)
+ l2[1] = 10
+ assertEquals(10, l2[1])
+ assertEquals(3, l2.size)
+ assertEquals(10, l[2])
+ }
+
+ @Test
+ fun subListSubList() {
+ val l = objectListOf(1, 2, 3, 4, 5).asList().subList(1, 5)
+ val l2 = l.subList(1, 3)
+ assertEquals(2, l2.size)
+ assertEquals(3, l2[0])
+ }
+
+ @Suppress("KotlinConstantConditions")
+ @Test
+ fun list_outOfBounds_Get_Below() {
+ assertFailsWith(IndexOutOfBoundsException::class) {
+ val l = mutableObjectListOf(1, 2, 3, 4).asMutableList()
+ l[-1]
+ }
+ }
+
+ @Suppress("KotlinConstantConditions")
+ @Test
+ fun sublist_outOfBounds_Get_Below() {
+ assertFailsWith(IndexOutOfBoundsException::class) {
+ val l = mutableObjectListOf(1, 2, 3, 4).asMutableList().subList(1, 2)
+ l[-1]
+ }
+ }
+
+ @Test
+ fun list_outOfBounds_Get_Above() {
+ assertFailsWith(IndexOutOfBoundsException::class) {
+ val l = mutableObjectListOf(1, 2, 3, 4).asMutableList()
+ l[4]
+ }
+ }
+
+ @Test
+ fun sublist_outOfBounds_Get_Above() {
+ assertFailsWith(IndexOutOfBoundsException::class) {
+ val l = mutableObjectListOf(1, 2, 3, 4).asMutableList().subList(1, 2)
+ l[1]
+ }
+ }
+
+ @Test
+ fun list_outOfBounds_RemoveAt_Below() {
+ assertFailsWith(IndexOutOfBoundsException::class) {
+ val l = mutableObjectListOf(0, 1, 2, 3).asMutableList()
+ l.removeAt(-1)
+ }
+ }
+
+ @Test
+ fun sublist_outOfBounds_RemoveAt_Below() {
+ assertFailsWith(IndexOutOfBoundsException::class) {
+ val l = mutableObjectListOf(0, 1, 2, 3).asMutableList().subList(1, 2)
+ l.removeAt(-1)
+ }
+ }
+
+ @Test
+ fun list_outOfBounds_RemoveAt_Above() {
+ assertFailsWith(IndexOutOfBoundsException::class) {
+ val l = mutableObjectListOf(0, 1, 2, 3).asMutableList()
+ l.removeAt(4)
+ }
+ }
+
+ @Test
+ fun sublist_outOfBounds_RemoveAt_Above() {
+ assertFailsWith(IndexOutOfBoundsException::class) {
+ val l = mutableObjectListOf(0, 1, 2, 3).asMutableList().subList(1, 2)
+ l.removeAt(1)
+ }
+ }
+
+ @Suppress("KotlinConstantConditions")
+ @Test
+ fun list_outOfBounds_Set_Below() {
+ assertFailsWith(IndexOutOfBoundsException::class) {
+ val l = mutableObjectListOf(0, 1, 2, 3).asMutableList()
+ l[-1] = 1
+ }
+ }
+
+ @Suppress("KotlinConstantConditions")
+ @Test
+ fun sublist_outOfBounds_Set_Below() {
+ assertFailsWith(IndexOutOfBoundsException::class) {
+ val l = mutableObjectListOf(0, 1, 2, 3).asMutableList().subList(1, 2)
+ l[-1] = 1
+ }
+ }
+
+ @Test
+ fun list_outOfBounds_Set_Above() {
+ assertFailsWith(IndexOutOfBoundsException::class) {
+ val l = mutableObjectListOf(0, 1, 2, 3).asMutableList()
+ l[4] = 1
+ }
+ }
+
+ @Test
+ fun sublist_outOfBounds_Set_Above() {
+ assertFailsWith(IndexOutOfBoundsException::class) {
+ val l = mutableObjectListOf(0, 1, 2, 3).asMutableList().subList(1, 2)
+ l[1] = 1
+ }
+ }
+
+ @Test
+ fun list_outOfBounds_SubList_Below() {
+ assertFailsWith(IndexOutOfBoundsException::class) {
+ val l = mutableObjectListOf(0, 1, 2, 3).asMutableList()
+ l.subList(-1, 1)
+ }
+ }
+
+ @Test
+ fun sublist_outOfBounds_SubList_Below() {
+ assertFailsWith(IndexOutOfBoundsException::class) {
+ val l = mutableObjectListOf(0, 1, 2, 3).asMutableList().subList(1, 2)
+ l.subList(-1, 1)
+ }
+ }
+
+ @Test
+ fun list_outOfBounds_SubList_Above() {
+ assertFailsWith(IndexOutOfBoundsException::class) {
+ val l = mutableObjectListOf(0, 1, 2, 3).asMutableList()
+ l.subList(5, 5)
+ }
+ }
+
+ @Test
+ fun sublist_outOfBounds_SubList_Above() {
+ assertFailsWith(IndexOutOfBoundsException::class) {
+ val l = mutableObjectListOf(0, 1, 2, 3).asMutableList().subList(1, 2)
+ l.subList(1, 2)
+ }
+ }
+
+ @Test
+ fun list_outOfBounds_SubList_Order() {
+ assertFailsWith(IllegalArgumentException::class) {
+ val l = mutableObjectListOf(0, 1, 2, 3).asMutableList()
+ l.subList(3, 2)
+ }
+ }
+
+ @Test
+ fun sublist_outOfBounds_SubList_Order() {
+ assertFailsWith(IllegalArgumentException::class) {
+ val l = mutableObjectListOf(0, 1, 2, 3).asMutableList().subList(1, 2)
+ l.subList(1, 0)
+ }
+ }
+}
diff --git a/compose/animation/animation/src/androidAndroidTest/kotlin/androidx/compose/animation/AnimatedContentTest.kt b/compose/animation/animation/src/androidAndroidTest/kotlin/androidx/compose/animation/AnimatedContentTest.kt
index 2767e5aa..67921dc 100644
--- a/compose/animation/animation/src/androidAndroidTest/kotlin/androidx/compose/animation/AnimatedContentTest.kt
+++ b/compose/animation/animation/src/androidAndroidTest/kotlin/androidx/compose/animation/AnimatedContentTest.kt
@@ -28,11 +28,13 @@
import androidx.compose.animation.core.updateTransition
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.TabRow
@@ -1115,6 +1117,77 @@
}
@Test
+ fun testRightEnterExitTransitionIsChosenDuringInterruption() {
+ var flag by mutableStateOf(false)
+ var fixedPosition: Offset? = null
+ var slidePosition: Offset? = null
+ rule.setContent {
+ AnimatedContent(
+ targetState = flag,
+ label = "",
+ transitionSpec = {
+ if (false isTransitioningTo true) {
+ ContentTransform(
+ targetContentEnter = EnterTransition.None,
+ initialContentExit = slideOutOfContainer(
+ AnimatedContentTransitionScope.SlideDirection.Start,
+ animationSpec = tween(durationMillis = 500)
+ ),
+ targetContentZIndex = -1.0f,
+ sizeTransform = SizeTransform(clip = false)
+ )
+ } else {
+ ContentTransform(
+ targetContentEnter = slideIntoContainer(
+ AnimatedContentTransitionScope.SlideDirection.End
+ ),
+ initialContentExit = ExitTransition.Hold,
+ targetContentZIndex = 0.0f,
+ sizeTransform = SizeTransform(clip = false)
+ )
+ }
+ },
+ modifier = Modifier.fillMaxSize()
+ ) { flag ->
+ Spacer(
+ modifier = Modifier
+ .wrapContentSize(Alignment.Center)
+ .size(256.dp)
+ .onGloballyPositioned {
+ if (flag) {
+ fixedPosition = it.positionInRoot()
+ } else {
+ slidePosition = it.positionInRoot()
+ }
+ }
+ )
+ }
+ }
+
+ rule.runOnIdle {
+ flag = true
+ }
+ rule.waitUntil { fixedPosition != null }
+ val initialFixedPosition = fixedPosition
+ // Advance 10 frames
+ repeat(10) {
+ val lastSlidePos = slidePosition
+ rule.waitUntil { slidePosition != lastSlidePos }
+ assertEquals(initialFixedPosition, fixedPosition)
+ }
+
+ // Change the target state amid transition, creating an interruption
+ flag = false
+ // Advance 10 frames
+ repeat(10) {
+ val lastSlidePos = slidePosition
+ rule.waitUntil { slidePosition != lastSlidePos }
+ assertEquals(initialFixedPosition, fixedPosition)
+ }
+ rule.waitForIdle()
+ }
+
+ @Test
fun testScaleToFitWithFitHeight() {
var target by mutableStateOf(true)
var box1Coords: LayoutCoordinates? = null
diff --git a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedContent.kt b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedContent.kt
index b82c044..e557851 100644
--- a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedContent.kt
+++ b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedContent.kt
@@ -820,13 +820,14 @@
}
if (!contentMap.containsKey(targetState) || !contentMap.containsKey(currentState)) {
contentMap.clear()
- val enter = transitionSpec(rootScope).targetContentEnter
- val exit = rootScope.transitionSpec().initialContentExit
- val zIndex = transitionSpec(rootScope).targetContentZIndex
currentlyVisible.fastForEach { stateForContent ->
contentMap[stateForContent] = {
+ // Only update content transform when enter/exit _direction_ changes.
+ val contentTransform = remember(stateForContent == targetState) {
+ rootScope.transitionSpec()
+ }
PopulateContentFor(
- stateForContent, rootScope, enter, exit, zIndex, currentlyVisible, content
+ stateForContent, rootScope, contentTransform, currentlyVisible, content
)
}
}
@@ -871,33 +872,32 @@
private inline fun <S> Transition<S>.PopulateContentFor(
stateForContent: S,
rootScope: AnimatedContentRootScope<S>,
- enter: EnterTransition,
- exit: ExitTransition,
- zIndex: Float,
+ contentTransform: ContentTransform,
currentlyVisible: SnapshotStateList<S>,
crossinline content: @Composable() AnimatedContentScope.(targetState: S) -> Unit
) {
- var activeEnter by remember { mutableStateOf(enter) }
+ var activeEnter by remember { mutableStateOf(contentTransform.targetContentEnter) }
var activeExit by remember { mutableStateOf(ExitTransition.None) }
- val targetZIndex = remember { zIndex }
+ val targetZIndex = remember { contentTransform.targetContentZIndex }
val isEntering = targetState == stateForContent
if (targetState == currentState) {
// Transition finished, reset active enter & exit.
- activeEnter = androidx.compose.animation.EnterTransition.None
- activeExit = androidx.compose.animation.ExitTransition.None
+ activeEnter = EnterTransition.None
+ activeExit = ExitTransition.None
} else if (isEntering) {
// If the previous enter transition never finishes when multiple
// interruptions happen, avoid adding new enter transitions for simplicity.
- if (activeEnter == androidx.compose.animation.EnterTransition.None)
- activeEnter += enter
+ if (activeEnter == EnterTransition.None)
+ activeEnter += contentTransform.targetContentEnter
} else {
// If the previous exit transition never finishes when multiple
// interruptions happen, avoid adding new enter transitions for simplicity.
- if (activeExit == androidx.compose.animation.ExitTransition.None) {
- activeExit += exit
+ if (activeExit == ExitTransition.None) {
+ activeExit += contentTransform.initialContentExit
}
}
+
val childData = remember { AnimatedContentRootScope.ChildData(stateForContent) }
AnimatedEnterExitImpl(
this,
@@ -915,16 +915,15 @@
.then(
if (isEntering) {
activeEnter[ScaleToFitTransitionKey]
- ?: activeExit[ScaleToFitTransitionKey] ?: androidx.compose.ui.Modifier
+ ?: activeExit[ScaleToFitTransitionKey] ?: Modifier
} else {
activeExit[ScaleToFitTransitionKey]
- ?: activeEnter[ScaleToFitTransitionKey] ?: androidx.compose.ui.Modifier
+ ?: activeEnter[ScaleToFitTransitionKey] ?: Modifier
}
),
shouldDisposeBlock = { currentState, targetState ->
- currentState == androidx.compose.animation.EnterExitState.PostExit &&
- targetState == androidx.compose.animation.EnterExitState.PostExit &&
- !activeExit.data.hold
+ currentState == EnterExitState.PostExit &&
+ targetState == EnterExitState.PostExit && !activeExit.data.hold
},
onLookaheadMeasured = {
if (isEntering) rootScope.targetSizeMap.getOrPut(targetState) {
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt
index d428ebd..2eaec85 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt
@@ -302,7 +302,8 @@
}
A(%composer, 0)
M3({ %composer: Composer?, %changed: Int ->
- sourceInformationMarkerStart(%composer, <>, "C<A()>,<A()>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C<A()>,<A()>:Test.kt")
A(%composer, 0)
if (condition) {
%composer.endToMarker(tmp0_marker)
@@ -315,7 +316,7 @@
return
}
A(%composer, 0)
- sourceInformationMarkerEnd(%composer)
+ %composer.endReplaceableGroup()
}, %composer, 0)
A(%composer, 0)
if (isTraceInProgress()) {
@@ -374,7 +375,8 @@
}
A(%composer, 0)
M3({ %composer: Composer?, %changed: Int ->
- sourceInformationMarkerStart(%composer, <>, "C<A()>,<A()>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C<A()>,<A()>:Test.kt")
A(%composer, 0)
if (a) {
%composer.endToMarker(tmp0_marker)
@@ -387,10 +389,11 @@
return
}
A(%composer, 0)
- sourceInformationMarkerEnd(%composer)
+ %composer.endReplaceableGroup()
}, %composer, 0)
M3({ %composer: Composer?, %changed: Int ->
- sourceInformationMarkerStart(%composer, <>, "C<A()>,<A()>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C<A()>,<A()>:Test.kt")
A(%composer, 0)
if (b) {
%composer.endToMarker(tmp0_marker)
@@ -403,7 +406,7 @@
return
}
A(%composer, 0)
- sourceInformationMarkerEnd(%composer)
+ %composer.endReplaceableGroup()
}, %composer, 0)
A(%composer, 0)
if (isTraceInProgress()) {
@@ -451,14 +454,15 @@
}
A(%composer, 0)
M3({ %composer: Composer?, %changed: Int ->
- sourceInformationMarkerStart(%composer, <>, "C<A()>,<A()>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C<A()>,<A()>:Test.kt")
A(%composer, 0)
if (condition) {
- sourceInformationMarkerEnd(%composer)
+ %composer.endReplaceableGroup()
return@M3
}
A(%composer, 0)
- sourceInformationMarkerEnd(%composer)
+ %composer.endReplaceableGroup()
}, %composer, 0)
A(%composer, 0)
if (isTraceInProgress()) {
@@ -498,7 +502,8 @@
traceEventStart(<>, %changed, -1, <>)
}
M1({ %composer: Composer?, %changed: Int ->
- sourceInformationMarkerStart(%composer, <>, "C:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C:Test.kt")
if (condition) {
%composer.endToMarker(tmp0_marker)
if (isTraceInProgress()) {
@@ -506,7 +511,7 @@
}
return@composableLambdaInstance
}
- sourceInformationMarkerEnd(%composer)
+ %composer.endReplaceableGroup()
}, %composer, 0)
if (isTraceInProgress()) {
traceEventEnd()
@@ -572,12 +577,13 @@
sourceInformationMarkerStart(%composer, <>, "C<A()>,<M1>,<A()>:Test.kt")
A(%composer, 0)
M1({ %composer: Composer?, %changed: Int ->
- sourceInformationMarkerStart(%composer, <>, "C:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C:Test.kt")
if (condition) {
- sourceInformationMarkerEnd(%composer)
+ %composer.endReplaceableGroup()
return@M1
}
- sourceInformationMarkerEnd(%composer)
+ %composer.endReplaceableGroup()
}, %composer, 0)
A(%composer, 0)
sourceInformationMarkerEnd(%composer)
@@ -630,18 +636,20 @@
A(%composer, 0)
M3({ %composer: Composer?, %changed: Int ->
val tmp0_marker = %composer.currentMarker
- sourceInformationMarkerStart(%composer, <>, "C<A()>,<M1>,<A()>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C<A()>,<M1>,<A()>:Test.kt")
A(%composer, 0)
M1({ %composer: Composer?, %changed: Int ->
- sourceInformationMarkerStart(%composer, <>, "C:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C:Test.kt")
if (condition) {
%composer.endToMarker(tmp0_marker)
return@M3
}
- sourceInformationMarkerEnd(%composer)
+ %composer.endReplaceableGroup()
}, %composer, 0)
A(%composer, 0)
- sourceInformationMarkerEnd(%composer)
+ %composer.endReplaceableGroup()
}, %composer, 0)
A(%composer, 0)
if (isTraceInProgress()) {
@@ -694,7 +702,8 @@
}
A(%composer, 0)
M1({ %composer: Composer?, %changed: Int ->
- sourceInformationMarkerStart(%composer, <>, "C<A()>,<A()>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C<A()>,<A()>:Test.kt")
A(%composer, 0)
%composer.startReplaceableGroup(<>)
sourceInformation(%composer, "*<A()>,<A()>")
@@ -714,7 +723,7 @@
}
%composer.endReplaceableGroup()
A(%composer, 0)
- sourceInformationMarkerEnd(%composer)
+ %composer.endReplaceableGroup()
}, %composer, 0)
A(%composer, 0)
if (isTraceInProgress()) {
@@ -769,24 +778,26 @@
}
A(%composer, 0)
M3({ %composer: Composer?, %changed: Int ->
- sourceInformationMarkerStart(%composer, <>, "C<A()>,<A()>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C<A()>,<A()>:Test.kt")
A(%composer, 0)
if (condition) {
- sourceInformationMarkerEnd(%composer)
+ %composer.endReplaceableGroup()
return@M3
}
A(%composer, 0)
- sourceInformationMarkerEnd(%composer)
+ %composer.endReplaceableGroup()
}, %composer, 0)
M3({ %composer: Composer?, %changed: Int ->
- sourceInformationMarkerStart(%composer, <>, "C<A()>,<A()>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C<A()>,<A()>:Test.kt")
A(%composer, 0)
if (condition) {
- sourceInformationMarkerEnd(%composer)
+ %composer.endReplaceableGroup()
return@M3
}
A(%composer, 0)
- sourceInformationMarkerEnd(%composer)
+ %composer.endReplaceableGroup()
}, %composer, 0)
A(%composer, 0)
if (isTraceInProgress()) {
@@ -839,14 +850,16 @@
}
Text("Root - before", %composer, 0b0110)
M1({ %composer: Composer?, %changed: Int ->
- sourceInformationMarkerStart(%composer, <>, "C<Text("...>,<Text("...>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C<Text("...>,<Text("...>:Test.kt")
Text("M1 - begin", %composer, 0b0110)
%composer.startReplaceableGroup(<>)
sourceInformation(%composer, "<Text("...>,<M1>")
if (condition) {
Text("if - begin", %composer, 0b0110)
M1({ %composer: Composer?, %changed: Int ->
- sourceInformationMarkerStart(%composer, <>, "C<Text("...>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C<Text("...>:Test.kt")
Text("In CCM1", %composer, 0b0110)
%composer.endToMarker(tmp0_marker)
if (isTraceInProgress()) {
@@ -856,12 +869,12 @@
test_CM1_CCM1_RetFun(condition, %composer, updateChangedFlags(%changed or 0b0001))
}
return
- sourceInformationMarkerEnd(%composer)
+ %composer.endReplaceableGroup()
}, %composer, 0)
}
%composer.endReplaceableGroup()
Text("M1 - end", %composer, 0b0110)
- sourceInformationMarkerEnd(%composer)
+ %composer.endReplaceableGroup()
}, %composer, 0)
Text("Root - end", %composer, 0b0110)
if (isTraceInProgress()) {
@@ -905,13 +918,14 @@
traceEventStart(<>, %changed, -1, <>)
}
FakeBox({ %composer: Composer?, %changed: Int ->
- sourceInformationMarkerStart(%composer, <>, "C<A()>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C<A()>:Test.kt")
if (condition) {
- sourceInformationMarkerEnd(%composer)
+ %composer.endReplaceableGroup()
return@FakeBox
}
A(%composer, 0)
- sourceInformationMarkerEnd(%composer)
+ %composer.endReplaceableGroup()
}, %composer, 0)
if (isTraceInProgress()) {
traceEventEnd()
@@ -1087,13 +1101,14 @@
traceEventStart(<>, %dirty, -1, <>)
}
IW({ %composer: Composer?, %changed: Int ->
- sourceInformationMarkerStart(%composer, <>, "C<A()>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C<A()>:Test.kt")
if (condition) {
- sourceInformationMarkerEnd(%composer)
+ %composer.endReplaceableGroup()
return@IW
}
A(%composer, 0)
- sourceInformationMarkerEnd(%composer)
+ %composer.endReplaceableGroup()
}, %composer, 0)
if (isTraceInProgress()) {
traceEventEnd()
@@ -1189,7 +1204,8 @@
}
Text("Some text", %composer, 0b0110)
M1({ %composer: Composer?, %changed: Int ->
- sourceInformationMarkerStart(%composer, <>, "C:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C:Test.kt")
Identity {
if (condition) {
%composer.endToMarker(tmp0_marker)
@@ -1202,7 +1218,7 @@
return
}
}
- sourceInformationMarkerEnd(%composer)
+ %composer.endReplaceableGroup()
}, %composer, 0)
Text("Some more text", %composer, 0b0110)
if (isTraceInProgress()) {
@@ -1247,14 +1263,15 @@
}
Text("Some text", %composer, 0b0110)
M1({ %composer: Composer?, %changed: Int ->
- sourceInformationMarkerStart(%composer, <>, "C:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C:Test.kt")
Identity {
if (condition) {
- sourceInformationMarkerEnd(%composer)
+ %composer.endReplaceableGroup()
return@M1
}
}
- sourceInformationMarkerEnd(%composer)
+ %composer.endReplaceableGroup()
}, %composer, 0)
Text("Some more text", %composer, 0b0110)
if (isTraceInProgress()) {
@@ -1271,6 +1288,154 @@
)
@Test
+ fun verifyEarlyExitFromNestedInlineFunction() = verifyComposeIrTransform(
+ source = """
+ import androidx.compose.runtime.*
+
+ @Composable
+ @NonRestartableComposable
+ fun Test(condition: Boolean) {
+ Text("Before outer")
+ InlineLinearA {
+ Text("Before inner")
+ InlineLinearB inner@{
+ Text("Before return")
+ if (condition) return@inner
+ Text("After return")
+ }
+ Text("After inner")
+ }
+ Text("Before outer")
+ }
+ """,
+ expectedTransformed = """
+ @Composable
+ @NonRestartableComposable
+ fun Test(condition: Boolean, %composer: Composer?, %changed: Int) {
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Test)<Text("...>,<Inline...>,<Text("...>:Test.kt")
+ if (isTraceInProgress()) {
+ traceEventStart(<>, %changed, -1, <>)
+ }
+ Text("Before outer", %composer, 0b0110)
+ InlineLinearA({ %composer: Composer?, %changed: Int ->
+ sourceInformationMarkerStart(%composer, <>, "C<Text("...>,<Inline...>,<Text("...>:Test.kt")
+ Text("Before inner", %composer, 0b0110)
+ InlineLinearB({ %composer: Composer?, %changed: Int ->
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C<Text("...>,<Text("...>:Test.kt")
+ Text("Before return", %composer, 0b0110)
+ if (condition) {
+ %composer.endReplaceableGroup()
+ return@InlineLinearB
+ }
+ Text("After return", %composer, 0b0110)
+ %composer.endReplaceableGroup()
+ }, %composer, 0)
+ Text("After inner", %composer, 0b0110)
+ sourceInformationMarkerEnd(%composer)
+ }, %composer, 0)
+ Text("Before outer", %composer, 0b0110)
+ if (isTraceInProgress()) {
+ traceEventEnd()
+ }
+ %composer.endReplaceableGroup()
+ }
+ """,
+ extra = """
+ import androidx.compose.runtime.*
+
+ @Composable
+ fun Text(value: String) { }
+
+ @Composable
+ inline fun InlineLinearA(content: @Composable () -> Unit) {
+ content()
+ }
+
+ @Composable
+ inline fun InlineLinearB(content: @Composable () -> Unit) {
+ content()
+ }
+ """
+ )
+
+ @Test
+ fun verifyEarlyExitFromMultiLevelNestedInlineFunction() = verifyComposeIrTransform(
+ source = """
+ import androidx.compose.runtime.*
+
+ @Composable
+ @NonRestartableComposable
+ fun Test(condition: Boolean) {
+ Text("Before outer")
+ InlineLinearA outer@{
+ Text("Before inner")
+ InlineLinearB {
+ Text("Before return")
+ if (condition) return@outer
+ Text("After return")
+ }
+ Text("After inner")
+ }
+ Text("Before outer")
+ }
+ """,
+ expectedTransformed = """
+ @Composable
+ @NonRestartableComposable
+ fun Test(condition: Boolean, %composer: Composer?, %changed: Int) {
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Test)<Text("...>,<Inline...>,<Text("...>:Test.kt")
+ if (isTraceInProgress()) {
+ traceEventStart(<>, %changed, -1, <>)
+ }
+ Text("Before outer", %composer, 0b0110)
+ InlineLinearA({ %composer: Composer?, %changed: Int ->
+ val tmp0_marker = %composer.currentMarker
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C<Text("...>,<Inline...>,<Text("...>:Test.kt")
+ Text("Before inner", %composer, 0b0110)
+ InlineLinearB({ %composer: Composer?, %changed: Int ->
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C<Text("...>,<Text("...>:Test.kt")
+ Text("Before return", %composer, 0b0110)
+ if (condition) {
+ %composer.endToMarker(tmp0_marker)
+ return@InlineLinearA
+ }
+ Text("After return", %composer, 0b0110)
+ %composer.endReplaceableGroup()
+ }, %composer, 0)
+ Text("After inner", %composer, 0b0110)
+ %composer.endReplaceableGroup()
+ }, %composer, 0)
+ Text("Before outer", %composer, 0b0110)
+ if (isTraceInProgress()) {
+ traceEventEnd()
+ }
+ %composer.endReplaceableGroup()
+ }
+ """,
+ extra = """
+ import androidx.compose.runtime.*
+
+ @Composable
+ fun Text(value: String) { }
+
+ @Composable
+ inline fun InlineLinearA(content: @Composable () -> Unit) {
+ content()
+ }
+
+ @Composable
+ inline fun InlineLinearB(content: @Composable () -> Unit) {
+ content()
+ }
+ """
+ )
+
+ @Test
fun testEnsureRuntimeTestWillCompile_CL() {
classLoader(
"""
@@ -1332,7 +1497,8 @@
}
Text("Root - before", %composer, 0b0110)
M1({ %composer: Composer?, %changed: Int ->
- sourceInformationMarkerStart(%composer, <>, "C<Text("...>,<Text("...>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C<Text("...>,<Text("...>:Test.kt")
Text("M1 - before", %composer, 0b0110)
if (condition) {
%composer.endToMarker(tmp0_marker)
@@ -1345,7 +1511,7 @@
return
}
Text("M1 - after", %composer, 0b0110)
- sourceInformationMarkerEnd(%composer)
+ %composer.endReplaceableGroup()
}, %composer, 0)
Text("Root - after", %composer, 0b0110)
if (isTraceInProgress()) {
@@ -6182,16 +6348,18 @@
}
Inline1({ %composer: Composer?, %changed: Int ->
val tmp0_marker = %composer.currentMarker
- sourceInformationMarkerStart(%composer, <>, "C<Inline...>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C<Inline...>:Test.kt")
Inline2({ %composer: Composer?, %changed: Int ->
- sourceInformationMarkerStart(%composer, <>, "C:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C:Test.kt")
if (true) {
%composer.endToMarker(tmp0_marker)
return@Inline1
}
- sourceInformationMarkerEnd(%composer)
+ %composer.endReplaceableGroup()
}, %composer, 0)
- sourceInformationMarkerEnd(%composer)
+ %composer.endReplaceableGroup()
}, %composer, 0)
if (isTraceInProgress()) {
traceEventEnd()
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTestsNoSource.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTestsNoSource.kt
index 4fb99f6..a3a2934 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTestsNoSource.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTestsNoSource.kt
@@ -174,4 +174,77 @@
}
"""
)
+
+ @Test
+ fun verifyEarlyExitFromMultiLevelNestedInlineFunction() = verifyComposeIrTransform(
+ source = """
+ import androidx.compose.runtime.*
+
+ @Composable
+ @NonRestartableComposable
+ fun Test(condition: Boolean) {
+ Text("Before outer")
+ InlineLinearA outer@{
+ Text("Before inner")
+ InlineLinearB {
+ Text("Before return")
+ if (condition) return@outer
+ Text("After return")
+ }
+ Text("After inner")
+ }
+ Text("Before outer")
+ }
+ """,
+ expectedTransformed = """
+ @Composable
+ @NonRestartableComposable
+ fun Test(condition: Boolean, %composer: Composer?, %changed: Int) {
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C(Test)")
+ if (isTraceInProgress()) {
+ traceEventStart(<>, %changed, -1, <>)
+ }
+ Text("Before outer", %composer, 0b0110)
+ InlineLinearA({ %composer: Composer?, %changed: Int ->
+ val tmp0_marker = %composer.currentMarker
+ %composer.startReplaceableGroup(<>)
+ Text("Before inner", %composer, 0b0110)
+ InlineLinearB({ %composer: Composer?, %changed: Int ->
+ %composer.startReplaceableGroup(<>)
+ Text("Before return", %composer, 0b0110)
+ if (condition) {
+ %composer.endToMarker(tmp0_marker)
+ return@InlineLinearA
+ }
+ Text("After return", %composer, 0b0110)
+ %composer.endReplaceableGroup()
+ }, %composer, 0)
+ Text("After inner", %composer, 0b0110)
+ %composer.endReplaceableGroup()
+ }, %composer, 0)
+ Text("Before outer", %composer, 0b0110)
+ if (isTraceInProgress()) {
+ traceEventEnd()
+ }
+ %composer.endReplaceableGroup()
+ }
+ """,
+ extra = """
+ import androidx.compose.runtime.*
+
+ @Composable
+ fun Text(value: String) { }
+
+ @Composable
+ inline fun InlineLinearA(content: @Composable () -> Unit) {
+ content()
+ }
+
+ @Composable
+ inline fun InlineLinearB(content: @Composable () -> Unit) {
+ content()
+ }
+ """
+ )
}
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/TraceInformationTest.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/TraceInformationTest.kt
index 2aa05c6..aaaba3b2 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/TraceInformationTest.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/TraceInformationTest.kt
@@ -175,7 +175,8 @@
}
A(%composer, 0)
Wrapper({ %composer: Composer?, %changed: Int ->
- sourceInformationMarkerStart(%composer, <>, "C<A()>,<A()>:Test.kt")
+ %composer.startReplaceableGroup(<>)
+ sourceInformation(%composer, "C<A()>,<A()>:Test.kt")
A(%composer, 0)
if (!condition) {
%composer.endToMarker(tmp0_marker)
@@ -188,7 +189,7 @@
return
}
A(%composer, 0)
- sourceInformationMarkerEnd(%composer)
+ %composer.endReplaceableGroup()
}, %composer, 0)
A(%composer, 0)
if (isTraceInProgress()) {
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt
index b1b54c0..1b4c6c2f 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt
@@ -978,17 +978,10 @@
val bodyPreamble = mutableStatementContainer()
val bodyEpilogue = mutableStatementContainer()
- // First generate the source information call
val isInlineLambda = scope.isInlinedLambda
- if (collectSourceInformation) {
- if (isInlineLambda) {
- sourceInformationPreamble.statements.add(
- irSourceInformationMarkerStart(body, scope)
- )
- bodyEpilogue.statements.add(irSourceInformationMarkerEnd(body, scope))
- } else {
- sourceInformationPreamble.statements.add(irSourceInformation(scope))
- }
+
+ if (collectSourceInformation && !isInlineLambda) {
+ sourceInformationPreamble.statements.add(irSourceInformation(scope))
}
// we start off assuming that we *can* skip execution of the function
@@ -1028,7 +1021,12 @@
// are using the dispatchReceiverParameter or the extensionReceiverParameter
val transformed = nonReturningBody.apply {
transformChildrenVoid()
+ }.let {
+ if (isInlineLambda) {
+ it.asSourceOrEarlyExitGroup(scope)
+ } else it
}
+
canSkipExecution = buildPreambleStatementsAndReturnIfSkippingPossible(
body,
skipPreamble,
@@ -1058,12 +1056,6 @@
}
}
- if (collectSourceInformation && isInlineLambda) {
- scope.realizeEndCalls {
- irSourceInformationMarkerEnd(body, scope)
- }
- }
-
if (canSkipExecution) {
// We CANNOT skip if any of the following conditions are met
// 1. if any of the stable parameters have *differences* from last execution.
@@ -1290,7 +1282,6 @@
returnVar?.let { irReturnVar(declaration.symbol, it) }
)
)
-
scope.metrics.recordFunction(
composable = true,
restartable = true,
@@ -2290,7 +2281,7 @@
}
}
- fun irTemporary(
+ private fun irTemporary(
value: IrExpression,
nameHint: String? = null,
irType: IrType = value.type,
@@ -2307,7 +2298,9 @@
name,
irType,
isVar
- )
+ ).also {
+ it.parent = currentFunctionScope.function.parent
+ }
}
private fun IrBlock.withReplaceableGroupStatements(scope: Scope.BlockScope): IrExpression {
@@ -2404,7 +2397,7 @@
private fun IrExpression.wrap(
before: List<IrExpression> = emptyList(),
after: List<IrExpression> = emptyList()
- ): IrExpression {
+ ): IrContainerExpression {
return if (after.isEmpty() || type.isNothing() || type.isUnit()) {
wrap(startOffset, endOffset, type, before, after)
} else {
@@ -2425,7 +2418,7 @@
type: IrType,
before: List<IrExpression> = emptyList(),
after: List<IrExpression> = emptyList()
- ): IrExpression {
+ ): IrContainerExpression {
return IrBlockImpl(
startOffset,
endOffset,
@@ -2462,6 +2455,58 @@
)
}
+ private fun IrContainerExpression.asSourceOrEarlyExitGroup(
+ scope: Scope.FunctionScope
+ ): IrContainerExpression {
+ if (scope.hasEarlyReturn) {
+ currentFunctionScope.metrics.recordGroup()
+ } else if (!collectSourceInformation) {
+ // If we are not generating source information and the lambda does not contain an
+ // early exit this we don't need a group or source markers.
+ return this
+ }
+ // if the scope has no composable calls, then the only important thing is that a
+ // start/end call gets executed. as a result, we can just put them both at the top of
+ // the group, and we don't have to deal with any of the complicated jump logic that
+ // could be inside of the block
+ val makeStart = {
+ if (scope.hasEarlyReturn) irStartReplaceableGroup(
+ this,
+ scope,
+ startOffset = startOffset,
+ endOffset = endOffset
+ )
+ else irSourceInformationMarkerStart(this, scope)
+ }
+ val makeEnd = {
+ if (scope.hasEarlyReturn) irEndReplaceableGroup(scope = scope)
+ else irSourceInformationMarkerEnd(this, scope)
+ }
+ if (!scope.hasComposableCalls && !scope.hasReturn && !scope.hasJump) {
+ return wrap(
+ before = listOf(makeStart()),
+ after = listOf(makeEnd()),
+ )
+ }
+ scope.realizeGroup(makeEnd)
+ return when {
+ // if the scope ends with a return call, then it will get properly ended if we
+ // just push the end call on the scope because of the way returns get transformed in
+ // this class. As a result, here we can safely just "prepend" the start call
+ endsWithReturnOrJump() -> {
+ wrap(before = listOf(makeStart()))
+ }
+ // otherwise, we want to push an end call for any early returns/jumps, but also add
+ // an end call to the end of the group
+ else -> {
+ wrap(
+ before = listOf(makeStart()),
+ after = listOf(makeEnd()),
+ )
+ }
+ }
+ }
+
private fun mutableStatementContainer() = mutableStatementContainer(context)
private fun encounteredComposableCall(withGroups: Boolean, isCached: Boolean) {
@@ -2574,12 +2619,16 @@
it.markReturn(extraEndLocation)
}
scope.markReturn(extraEndLocation)
+ if (scope.isInlinedLambda && scope.inComposableCall) {
+ scope.hasEarlyReturn = true
+ }
} else {
val functionScope = scope
val targetScope = currentScope as? Scope.BlockScope ?: functionScope
if (functionScope.isInlinedLambda) {
val marker = irGet(functionScope.allocateMarker())
extraEndLocation(irEndToMarker(marker, targetScope))
+ scope.hasEarlyReturn = true
} else {
val marker = functionScope.allocateMarker()
functionScope.markReturn {
@@ -2591,8 +2640,10 @@
scope.updateIntrinsiceRememberSafety(false)
break@loop
}
- if (scope.isInlinedLambda && scope.inComposableCall)
+ if (scope.isInlinedLambda && scope.inComposableCall) {
leavingInlinedLambda = true
+ scope.hasEarlyReturn = true
+ }
}
is Scope.BlockScope -> {
blockScopeMarks.add(scope)
@@ -3708,6 +3759,8 @@
val metrics: FunctionMetrics = transformer.metricsFor(function)
+ var hasEarlyReturn: Boolean = false
+
private var lastTemporaryIndex: Int = 0
private fun nextTemporaryIndex(): Int = lastTemporaryIndex++
@@ -4038,13 +4091,12 @@
makeEnd: () -> IrExpression
) {
addProvisionalSourceLocations(scope.sourceLocations)
- coalescableChilds.add(
- CoalescableGroupInfo(
- scope,
- realizeGroup,
- makeEnd
- )
+ val groupInfo = CoalescableGroupInfo(
+ scope,
+ realizeGroup,
+ makeEnd
)
+ coalescableChilds.add(groupInfo)
}
open fun calculateHasSourceInformation(sourceInformationEnabled: Boolean): Boolean =
@@ -4442,6 +4494,7 @@
isConst = false,
isLateinit = false
).apply {
+ parent = currentFunctionScope.function.parent
initializer = irGet(param)
}
}
diff --git a/compose/foundation/foundation/api/current.txt b/compose/foundation/foundation/api/current.txt
index 66ef5ba..166ecd1 100644
--- a/compose/foundation/foundation/api/current.txt
+++ b/compose/foundation/foundation/api/current.txt
@@ -111,6 +111,30 @@
method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.ui.Modifier onFocusedBoundsChanged(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.layout.LayoutCoordinates,kotlin.Unit> onPositioned);
}
+ public final class GraphicsSurfaceKt {
+ method @androidx.compose.runtime.Composable public static void EmbeddedGraphicsSurface(optional androidx.compose.ui.Modifier modifier, optional boolean isOpaque, optional long surfaceSize, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.GraphicsSurfaceScope,kotlin.Unit> onInit);
+ method @androidx.compose.runtime.Composable public static void GraphicsSurface(optional androidx.compose.ui.Modifier modifier, optional boolean isOpaque, optional int zOrder, optional long surfaceSize, optional boolean isSecure, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.GraphicsSurfaceScope,kotlin.Unit> onInit);
+ }
+
+ public interface GraphicsSurfaceScope {
+ method public void onSurface(kotlin.jvm.functions.Function5<? super androidx.compose.foundation.SurfaceCoroutineScope,? super android.view.Surface,? super java.lang.Integer,? super java.lang.Integer,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> onSurface);
+ }
+
+ @kotlin.jvm.JvmInline public final value class GraphicsSurfaceZOrder {
+ method public int getZOrder();
+ property public final int zOrder;
+ field public static final androidx.compose.foundation.GraphicsSurfaceZOrder.Companion Companion;
+ }
+
+ public static final class GraphicsSurfaceZOrder.Companion {
+ method public int getBehind();
+ method public int getMediaOverlay();
+ method public int getOnTop();
+ property public final int Behind;
+ property public final int MediaOverlay;
+ property public final int OnTop;
+ }
+
public final class HoverableKt {
method public static androidx.compose.ui.Modifier hoverable(androidx.compose.ui.Modifier, androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional boolean enabled);
}
@@ -246,6 +270,14 @@
property public final androidx.compose.runtime.saveable.Saver<androidx.compose.foundation.ScrollState,?> Saver;
}
+ public interface SurfaceCoroutineScope extends androidx.compose.foundation.SurfaceScope kotlinx.coroutines.CoroutineScope {
+ }
+
+ public interface SurfaceScope {
+ method public void onChanged(android.view.Surface, kotlin.jvm.functions.Function3<? super android.view.Surface,? super java.lang.Integer,? super java.lang.Integer,kotlin.Unit> onChanged);
+ method public void onDestroyed(android.view.Surface, kotlin.jvm.functions.Function1<? super android.view.Surface,kotlin.Unit> onDestroyed);
+ }
+
public final class SystemGestureExclusionKt {
method public static androidx.compose.ui.Modifier systemGestureExclusion(androidx.compose.ui.Modifier);
method public static androidx.compose.ui.Modifier systemGestureExclusion(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.layout.LayoutCoordinates,androidx.compose.ui.geometry.Rect> exclusion);
diff --git a/compose/foundation/foundation/api/restricted_current.txt b/compose/foundation/foundation/api/restricted_current.txt
index 3118943..5c2e81b 100644
--- a/compose/foundation/foundation/api/restricted_current.txt
+++ b/compose/foundation/foundation/api/restricted_current.txt
@@ -111,6 +111,30 @@
method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.ui.Modifier onFocusedBoundsChanged(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.layout.LayoutCoordinates,kotlin.Unit> onPositioned);
}
+ public final class GraphicsSurfaceKt {
+ method @androidx.compose.runtime.Composable public static void EmbeddedGraphicsSurface(optional androidx.compose.ui.Modifier modifier, optional boolean isOpaque, optional long surfaceSize, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.GraphicsSurfaceScope,kotlin.Unit> onInit);
+ method @androidx.compose.runtime.Composable public static void GraphicsSurface(optional androidx.compose.ui.Modifier modifier, optional boolean isOpaque, optional int zOrder, optional long surfaceSize, optional boolean isSecure, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.GraphicsSurfaceScope,kotlin.Unit> onInit);
+ }
+
+ public interface GraphicsSurfaceScope {
+ method public void onSurface(kotlin.jvm.functions.Function5<? super androidx.compose.foundation.SurfaceCoroutineScope,? super android.view.Surface,? super java.lang.Integer,? super java.lang.Integer,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> onSurface);
+ }
+
+ @kotlin.jvm.JvmInline public final value class GraphicsSurfaceZOrder {
+ method public int getZOrder();
+ property public final int zOrder;
+ field public static final androidx.compose.foundation.GraphicsSurfaceZOrder.Companion Companion;
+ }
+
+ public static final class GraphicsSurfaceZOrder.Companion {
+ method public int getBehind();
+ method public int getMediaOverlay();
+ method public int getOnTop();
+ property public final int Behind;
+ property public final int MediaOverlay;
+ property public final int OnTop;
+ }
+
public final class HoverableKt {
method public static androidx.compose.ui.Modifier hoverable(androidx.compose.ui.Modifier, androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional boolean enabled);
}
@@ -248,6 +272,14 @@
property public final androidx.compose.runtime.saveable.Saver<androidx.compose.foundation.ScrollState,?> Saver;
}
+ public interface SurfaceCoroutineScope extends androidx.compose.foundation.SurfaceScope kotlinx.coroutines.CoroutineScope {
+ }
+
+ public interface SurfaceScope {
+ method public void onChanged(android.view.Surface, kotlin.jvm.functions.Function3<? super android.view.Surface,? super java.lang.Integer,? super java.lang.Integer,kotlin.Unit> onChanged);
+ method public void onDestroyed(android.view.Surface, kotlin.jvm.functions.Function1<? super android.view.Surface,kotlin.Unit> onDestroyed);
+ }
+
public final class SystemGestureExclusionKt {
method public static androidx.compose.ui.Modifier systemGestureExclusion(androidx.compose.ui.Modifier);
method public static androidx.compose.ui.Modifier systemGestureExclusion(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.layout.LayoutCoordinates,androidx.compose.ui.geometry.Rect> exclusion);
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/FoundationDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/FoundationDemos.kt
index b50584f..4a07721 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/FoundationDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/FoundationDemos.kt
@@ -66,6 +66,7 @@
ComposableDemo("Vertical scroll") { VerticalScrollExample() },
ComposableDemo("Controlled Scrollable Row") { ControlledScrollableRowSample() },
ComposableDemo("Draw Modifiers") { DrawModifiersDemo() },
+ ComposableDemo("Graphics Surfaces") { GraphicsSurfaceDemo() },
DemoCategory("Lazy lists", LazyListDemos),
DemoCategory("Snapping", SnappingDemos),
DemoCategory("Pagers", PagerDemos),
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/GraphicsSurfaceDemo.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/GraphicsSurfaceDemo.kt
new file mode 100644
index 0000000..4cc8c77
--- /dev/null
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/GraphicsSurfaceDemo.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.foundation.demos
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.samples.EmbeddedGraphicsSurfaceColors
+import androidx.compose.foundation.samples.GraphicsSurfaceColors
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun GraphicsSurfaceDemo() {
+ Column(Modifier.verticalScroll(rememberScrollState())) {
+ Text("GraphicsSurface:")
+ GraphicsSurfaceColors()
+ Spacer(Modifier.height(50.dp))
+ Text("EmbeddedGraphicsSurface:")
+ EmbeddedGraphicsSurfaceColors()
+ }
+}
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/GraphicsSurfaceSamples.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/GraphicsSurfaceSamples.kt
new file mode 100644
index 0000000..dc63c5f
--- /dev/null
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/GraphicsSurfaceSamples.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.samples
+
+import android.graphics.Rect
+import androidx.annotation.Sampled
+import androidx.compose.foundation.EmbeddedGraphicsSurface
+import androidx.compose.foundation.GraphicsSurface
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.withFrameNanos
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.lerp
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.unit.dp
+import kotlin.math.sin
+
+@Sampled
+@Composable
+fun GraphicsSurfaceColors() {
+ GraphicsSurface(
+ modifier = Modifier.fillMaxWidth().height(400.dp)
+ ) {
+ // Resources can be initialized/cached here
+
+ // A surface is available, we can start rendering
+ onSurface { surface, width, height ->
+ var w = width
+ var h = height
+
+ // Initial draw to avoid a black frame
+ surface.lockCanvas(Rect(0, 0, w, h)).apply {
+ drawColor(Color.Blue.toArgb())
+ surface.unlockCanvasAndPost(this)
+ }
+
+ // React to surface dimension changes
+ surface.onChanged { newWidth, newHeight ->
+ w = newWidth
+ h = newHeight
+ }
+
+ // Cleanup if needed
+ surface.onDestroyed {
+ }
+
+ // Render loop, automatically cancelled by GraphicsSurface
+ // on surface destruction
+ while (true) {
+ withFrameNanos { time ->
+ surface.lockCanvas(Rect(0, 0, w, h)).apply {
+ val timeMs = time / 1_000_000L
+ val t = 0.5f + 0.5f * sin(timeMs / 1_000.0f)
+ drawColor(lerp(Color.Blue, Color.Green, t).toArgb())
+ surface.unlockCanvasAndPost(this)
+ }
+ }
+ }
+ }
+ }
+}
+
+@Sampled
+@Composable
+fun EmbeddedGraphicsSurfaceColors() {
+ EmbeddedGraphicsSurface(
+ modifier = Modifier.fillMaxWidth().height(400.dp)
+ ) {
+ // Resources can be initialized/cached here
+
+ // A surface is available, we can start rendering
+ onSurface { surface, width, height ->
+ var w = width
+ var h = height
+
+ // Initial draw to avoid a black frame
+ surface.lockCanvas(Rect(0, 0, w, h)).apply {
+ drawColor(Color.Yellow.toArgb())
+ surface.unlockCanvasAndPost(this)
+ }
+
+ // React to surface dimension changes
+ surface.onChanged { newWidth, newHeight ->
+ w = newWidth
+ h = newHeight
+ }
+
+ // Cleanup if needed
+ surface.onDestroyed {
+ }
+
+ // Render loop, automatically cancelled by EmbeddedGraphicsSurface
+ // on surface destruction
+ while (true) {
+ withFrameNanos { time ->
+ surface.lockCanvas(Rect(0, 0, w, h)).apply {
+ val timeMs = time / 1_000_000L
+ val t = 0.5f + 0.5f * sin(timeMs / 1_000.0f)
+ drawColor(lerp(Color.Yellow, Color.Red, t).toArgb())
+ surface.unlockCanvasAndPost(this)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/EmbeddedGraphicsSurfaceTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/EmbeddedGraphicsSurfaceTest.kt
new file mode 100644
index 0000000..6a10cd1
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/EmbeddedGraphicsSurfaceTest.kt
@@ -0,0 +1,306 @@
+/*
+ * 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
+
+import android.graphics.PorterDuff
+import android.os.Build
+import android.view.Surface
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.testutils.assertPixels
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.assertHeightIsEqualTo
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertWidthIsEqualTo
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.unit.dp
+import androidx.core.graphics.ColorUtils
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import kotlin.math.roundToInt
+import kotlin.test.assertEquals
+import kotlin.test.assertNotEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+@RunWith(AndroidJUnit4::class)
+class EmbeddedGraphicsSurfaceTest {
+ @get:Rule
+ val rule = createComposeRule()
+
+ val size = 48.dp
+
+ @Test
+ fun testOnSurface() {
+ var surfaceRef: Surface? = null
+ var surfaceWidth = 0
+ var surfaceHeight = 0
+ var expectedSize = 0
+
+ rule.setContent {
+ expectedSize = with(LocalDensity.current) {
+ size.toPx().roundToInt()
+ }
+
+ EmbeddedGraphicsSurface(modifier = Modifier.size(size)) {
+ onSurface { surface, width, height ->
+ surfaceRef = surface
+ surfaceWidth = width
+ surfaceHeight = height
+ }
+ }
+ }
+
+ rule.onRoot()
+ .assertWidthIsEqualTo(size)
+ .assertHeightIsEqualTo(size)
+ .assertIsDisplayed()
+
+ rule.runOnIdle {
+ assertNotNull(surfaceRef)
+ assertEquals(expectedSize, surfaceWidth)
+ assertEquals(expectedSize, surfaceHeight)
+ }
+ }
+
+ @Test
+ fun testOnSurfaceChanged() {
+ var surfaceWidth = 0
+ var surfaceHeight = 0
+ var expectedSize = 0
+
+ var desiredSize by mutableStateOf(size)
+
+ rule.setContent {
+ expectedSize = with(LocalDensity.current) {
+ desiredSize.toPx().roundToInt()
+ }
+
+ EmbeddedGraphicsSurface(modifier = Modifier.size(desiredSize)) {
+ onSurface { surface, initWidth, initHeight ->
+ surfaceWidth = initWidth
+ surfaceHeight = initHeight
+
+ surface.onChanged { newWidth, newHeight ->
+ surfaceWidth = newWidth
+ surfaceHeight = newHeight
+ }
+ }
+ }
+ }
+
+ rule.onRoot()
+ .assertWidthIsEqualTo(desiredSize)
+ .assertHeightIsEqualTo(desiredSize)
+
+ rule.runOnIdle {
+ assertEquals(expectedSize, surfaceWidth)
+ assertEquals(expectedSize, surfaceHeight)
+ }
+
+ desiredSize = size * 2
+ val prevSurfaceWidth = surfaceWidth
+ val prevSurfaceHeight = surfaceHeight
+
+ rule.onRoot()
+ .assertWidthIsEqualTo(desiredSize)
+ .assertHeightIsEqualTo(desiredSize)
+
+ rule.runOnIdle {
+ assertNotEquals(prevSurfaceWidth, surfaceWidth)
+ assertNotEquals(prevSurfaceHeight, surfaceHeight)
+ assertEquals(expectedSize, surfaceWidth)
+ assertEquals(expectedSize, surfaceHeight)
+ }
+ }
+
+ @Test
+ fun testOnSurfaceDestroyed() {
+ var surfaceRef: Surface? = null
+ var visible by mutableStateOf(true)
+
+ rule.setContent {
+ if (visible) {
+ EmbeddedGraphicsSurface(modifier = Modifier.size(size)) {
+ onSurface { surface, _, _ ->
+ surfaceRef = surface
+
+ surface.onDestroyed {
+ surfaceRef = null
+ }
+ }
+ }
+ }
+ }
+
+ rule.runOnIdle {
+ assertNotNull(surfaceRef)
+ }
+
+ visible = false
+
+ rule.runOnIdle {
+ assertNull(surfaceRef)
+ }
+ }
+
+ @Test
+ fun testOnSurfaceRecreated() {
+ var surfaceCreatedCount = 0
+ var surfaceDestroyedCount = 0
+ var visible by mutableStateOf(true)
+
+ // NOTE: TextureView only destroys the surface when TextureView is detached from
+ // the window, and only creates when it gets attached to the window
+ rule.setContent {
+ if (visible) {
+ EmbeddedGraphicsSurface(modifier = Modifier.size(size)) {
+ onSurface { surface, _, _ ->
+ surfaceCreatedCount++
+ surface.onDestroyed {
+ surfaceDestroyedCount++
+ }
+ }
+ }
+ }
+ }
+
+ rule.runOnIdle {
+ assertEquals(1, surfaceCreatedCount)
+ assertEquals(0, surfaceDestroyedCount)
+ visible = false
+ }
+
+ rule.runOnIdle {
+ assertEquals(1, surfaceCreatedCount)
+ assertEquals(1, surfaceDestroyedCount)
+ visible = true
+ }
+
+ rule.runOnIdle {
+ assertEquals(2, surfaceCreatedCount)
+ assertEquals(1, surfaceDestroyedCount)
+ }
+ }
+
+ @Test
+ fun testRender() {
+ var surfaceRef: Surface? = null
+ var expectedSize = 0
+
+ rule.setContent {
+ expectedSize = with(LocalDensity.current) {
+ size.toPx().roundToInt()
+ }
+ EmbeddedGraphicsSurface(modifier = Modifier.size(size)) {
+ onSurface { surface, _, _ ->
+ surfaceRef = surface
+ surface.lockHardwareCanvas().apply {
+ drawColor(Color.Blue.toArgb())
+ surface.unlockCanvasAndPost(this)
+ }
+ }
+ }
+ }
+
+ rule.runOnIdle {
+ assertNotNull(surfaceRef)
+ }
+
+ surfaceRef!!
+ .captureToImage(expectedSize, expectedSize)
+ .assertPixels { Color.Blue }
+ }
+
+ @Test
+ fun testNotOpaque() {
+ val translucentRed = Color(1.0f, 0.0f, 0.0f, 0.5f).toArgb()
+
+ rule.setContent {
+ Box(modifier = Modifier.size(size)) {
+ Canvas(modifier = Modifier.size(size)) {
+ drawRect(Color.White)
+ }
+ EmbeddedGraphicsSurface(
+ modifier = Modifier
+ .size(size)
+ .testTag("EmbeddedGraphicSurface"),
+ isOpaque = false
+ ) {
+ onSurface { surface, _, _ ->
+ surface.lockHardwareCanvas().apply {
+ drawColor(0x00000000, PorterDuff.Mode.CLEAR)
+ drawColor(translucentRed)
+ surface.unlockCanvasAndPost(this)
+ }
+ }
+ }
+ }
+ }
+
+ val expectedColor = Color(ColorUtils.compositeColors(translucentRed, Color.White.toArgb()))
+
+ rule
+ .onNodeWithTag("EmbeddedGraphicSurface")
+ .captureToImage()
+ .assertPixels { expectedColor }
+ }
+
+ @Test
+ fun testOpaque() {
+ rule.setContent {
+ Box(modifier = Modifier.size(size)) {
+ Canvas(modifier = Modifier.size(size)) {
+ drawRect(Color.Green)
+ }
+ EmbeddedGraphicsSurface(
+ modifier = Modifier
+ .size(size)
+ .testTag("EmbeddedGraphicSurface")
+ ) {
+ onSurface { surface, _, _ ->
+ surface.lockHardwareCanvas().apply {
+ drawColor(Color.Red.toArgb())
+ surface.unlockCanvasAndPost(this)
+ }
+ }
+ }
+ }
+ }
+
+ rule
+ .onNodeWithTag("EmbeddedGraphicSurface")
+ .captureToImage()
+ .assertPixels { Color.Red }
+ }
+}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/GraphicsSurfaceTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/GraphicsSurfaceTest.kt
new file mode 100644
index 0000000..a850dd8
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/GraphicsSurfaceTest.kt
@@ -0,0 +1,554 @@
+/*
+ * 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
+
+import android.graphics.Bitmap
+import android.graphics.PorterDuff
+import android.graphics.Rect
+import android.os.Build
+import android.os.Handler
+import android.os.Looper
+import android.view.PixelCopy
+import android.view.Surface
+import android.view.View
+import androidx.annotation.RequiresApi
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.withFrameNanos
+import androidx.compose.testutils.assertPixels
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.platform.ViewRootForTest
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.assertHeightIsEqualTo
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertWidthIsEqualTo
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.unit.dp
+import androidx.core.graphics.ColorUtils
+import androidx.core.graphics.createBitmap
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import kotlin.math.roundToInt
+import kotlin.test.assertEquals
+import kotlin.test.assertNotEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+@RunWith(AndroidJUnit4::class)
+class GraphicsSurfaceTest {
+ @get:Rule
+ val rule = createComposeRule()
+
+ val size = 48.dp
+
+ @Test
+ fun testOnSurface() {
+ var surfaceRef: Surface? = null
+ var surfaceWidth = 0
+ var surfaceHeight = 0
+ var expectedSize = 0
+
+ rule.setContent {
+ expectedSize = with(LocalDensity.current) {
+ size.toPx().roundToInt()
+ }
+
+ GraphicsSurface(modifier = Modifier.size(size)) {
+ onSurface { surface, width, height ->
+ surfaceRef = surface
+ surfaceWidth = width
+ surfaceHeight = height
+ }
+ }
+ }
+
+ rule.onRoot()
+ .assertWidthIsEqualTo(size)
+ .assertHeightIsEqualTo(size)
+ .assertIsDisplayed()
+
+ rule.runOnIdle {
+ assertNotNull(surfaceRef)
+ assertEquals(expectedSize, surfaceWidth)
+ assertEquals(expectedSize, surfaceHeight)
+ }
+ }
+
+ @Test
+ fun testOnSurfaceChanged() {
+ var surfaceWidth = 0
+ var surfaceHeight = 0
+ var expectedSize = 0
+
+ var desiredSize by mutableStateOf(size)
+
+ rule.setContent {
+ expectedSize = with(LocalDensity.current) {
+ desiredSize.toPx().roundToInt()
+ }
+
+ GraphicsSurface(modifier = Modifier.size(desiredSize)) {
+ onSurface { surface, _, _ ->
+ surface.onChanged { newWidth, newHeight ->
+ surfaceWidth = newWidth
+ surfaceHeight = newHeight
+ }
+ }
+ }
+ }
+
+ rule.onRoot()
+ .assertWidthIsEqualTo(desiredSize)
+ .assertHeightIsEqualTo(desiredSize)
+
+ // onChanged() hasn't been called yet
+ rule.runOnIdle {
+ assertEquals(0, surfaceWidth)
+ assertEquals(0, surfaceHeight)
+ }
+
+ desiredSize = size * 2
+ val prevSurfaceWidth = surfaceWidth
+ val prevSurfaceHeight = surfaceHeight
+
+ rule.onRoot()
+ .assertWidthIsEqualTo(desiredSize)
+ .assertHeightIsEqualTo(desiredSize)
+
+ rule.runOnIdle {
+ assertNotEquals(prevSurfaceWidth, surfaceWidth)
+ assertNotEquals(prevSurfaceHeight, surfaceHeight)
+ assertEquals(expectedSize, surfaceWidth)
+ assertEquals(expectedSize, surfaceHeight)
+ }
+ }
+
+ @Test
+ fun testOnSurfaceDestroyed() {
+ var surfaceRef: Surface? = null
+ var visible by mutableStateOf(true)
+
+ rule.setContent {
+ if (visible) {
+ GraphicsSurface(modifier = Modifier.size(size)) {
+ onSurface { surface, _, _ ->
+ surfaceRef = surface
+
+ surface.onDestroyed {
+ surfaceRef = null
+ }
+ }
+ }
+ }
+ }
+
+ rule.runOnIdle {
+ assertNotNull(surfaceRef)
+ }
+
+ visible = false
+
+ rule.runOnIdle {
+ assertNull(surfaceRef)
+ }
+ }
+
+ @Test
+ fun testOnSurfaceRecreated() {
+ var surfaceCreatedCount = 0
+ var surfaceDestroyedCount = 0
+
+ var view: View? = null
+
+ rule.setContent {
+ view = LocalView.current
+ GraphicsSurface(modifier = Modifier.size(size)) {
+ onSurface { surface, _, _ ->
+ surfaceCreatedCount++
+ surface.onDestroyed {
+ surfaceDestroyedCount++
+ }
+ }
+ }
+ }
+
+ // NOTE: SurfaceView only triggers a Surface destroy/create cycle on visibility
+ // change if its *own* visibility or the visibility of the window changes. Here
+ // we change the visibility of the window by setting the visibility of the root
+ // view (the host view in ViewRootImpl).
+ rule.runOnIdle {
+ assertEquals(1, surfaceCreatedCount)
+ assertEquals(0, surfaceDestroyedCount)
+ view?.rootView?.visibility = View.INVISIBLE
+ }
+
+ rule.runOnIdle {
+ assertEquals(1, surfaceCreatedCount)
+ assertEquals(1, surfaceDestroyedCount)
+ view?.rootView?.visibility = View.VISIBLE
+ }
+
+ rule.runOnIdle {
+ assertEquals(2, surfaceCreatedCount)
+ assertEquals(1, surfaceDestroyedCount)
+ }
+ }
+
+ @Test
+ fun testRender() {
+ var surfaceRef: Surface? = null
+ var expectedSize = 0
+
+ rule.setContent {
+ expectedSize = with(LocalDensity.current) {
+ size.toPx().roundToInt()
+ }
+ GraphicsSurface(modifier = Modifier.size(size)) {
+ onSurface { surface, _, _ ->
+ surfaceRef = surface
+ surface.lockHardwareCanvas().apply {
+ drawColor(Color.Blue.toArgb())
+ surface.unlockCanvasAndPost(this)
+ }
+ }
+ }
+ }
+
+ rule.runOnIdle {
+ assertNotNull(surfaceRef)
+ }
+
+ surfaceRef!!
+ .captureToImage(expectedSize, expectedSize)
+ .assertPixels { Color.Blue }
+ }
+
+ @Test
+ fun testZOrderDefault() {
+ val frameCount = 4
+ val latch = CountDownLatch(frameCount)
+
+ rule.setContent {
+ Box(modifier = Modifier.size(size)) {
+ GraphicsSurface(
+ modifier = Modifier
+ .size(size)
+ .testTag("GraphicSurface")
+ ) {
+ onSurface { surface, _, _ ->
+ // Draw > 3 frames to make sure the screenshot copy will pick up
+ // a SurfaceFlinger composition that includes our Surface
+ repeat(frameCount) {
+ withFrameNanos {
+ surface.lockHardwareCanvas().apply {
+ drawColor(Color.Blue.toArgb())
+ surface.unlockCanvasAndPost(this)
+ }
+ latch.countDown()
+ }
+ }
+ }
+ }
+ Canvas(modifier = Modifier.size(size)) {
+ drawRect(Color.Green)
+ }
+ }
+ }
+
+ if (!latch.await(5, TimeUnit.SECONDS)) {
+ throw AssertionError("Failed waiting for render")
+ }
+
+ rule
+ .onNodeWithTag("GraphicSurface")
+ .screenshotToImage()!!
+ .assertPixels { Color.Green }
+ }
+
+ @Test
+ fun testZOrderMediaOverlay() {
+ val frameCount = 6
+ val latch = CountDownLatch(frameCount)
+
+ rule.setContent {
+ Box(modifier = Modifier.size(size)) {
+ GraphicsSurface(
+ modifier = Modifier.size(size),
+ zOrder = GraphicsSurfaceZOrder.Behind
+ ) {
+ onSurface { surface, _, _ ->
+ // Draw > 3 frames to make sure the screenshot copy will pick up
+ // a SurfaceFlinger composition that includes our Surface
+ repeat(frameCount) {
+ withFrameNanos {
+ surface.lockHardwareCanvas().apply {
+ drawColor(Color.Blue.toArgb())
+ surface.unlockCanvasAndPost(this)
+ }
+ latch.countDown()
+ }
+ }
+ }
+ }
+ GraphicsSurface(
+ modifier = Modifier
+ .size(size)
+ .testTag("GraphicSurface"),
+ zOrder = GraphicsSurfaceZOrder.MediaOverlay
+ ) {
+ onSurface { surface, _, _ ->
+ surface.lockHardwareCanvas().apply {
+ drawColor(Color.Red.toArgb())
+ surface.unlockCanvasAndPost(this)
+ }
+ latch.countDown()
+ }
+ }
+ }
+ }
+
+ if (!latch.await(5, TimeUnit.SECONDS)) {
+ throw AssertionError("Failed waiting for render")
+ }
+
+ rule
+ .onNodeWithTag("GraphicSurface")
+ .screenshotToImage()!!
+ .assertPixels { Color.Red }
+ }
+
+ @Test
+ fun testZOrderOnTop() {
+ val frameCount = 4
+ val latch = CountDownLatch(frameCount)
+
+ rule.setContent {
+ Box(modifier = Modifier.size(size)) {
+ GraphicsSurface(
+ modifier = Modifier
+ .size(size)
+ .testTag("GraphicSurface"),
+ zOrder = GraphicsSurfaceZOrder.OnTop
+ ) {
+ onSurface { surface, _, _ ->
+ // Draw > 3 frames to make sure the screenshot copy will pick up
+ // a SurfaceFlinger composition that includes our Surface
+ repeat(frameCount) {
+ withFrameNanos {
+ surface.lockHardwareCanvas().apply {
+ drawColor(Color.Blue.toArgb())
+ surface.unlockCanvasAndPost(this)
+ }
+ latch.countDown()
+ }
+ }
+ }
+ }
+ Canvas(modifier = Modifier.size(size)) {
+ drawRect(Color.Green)
+ }
+ }
+ }
+
+ if (!latch.await(5, TimeUnit.SECONDS)) {
+ throw AssertionError("Failed waiting for render")
+ }
+
+ rule
+ .onNodeWithTag("GraphicSurface")
+ .screenshotToImage()!!
+ .assertPixels { Color.Blue }
+ }
+
+ @Test
+ fun testNotOpaque() {
+ val frameCount = 4
+ val latch = CountDownLatch(frameCount)
+ val translucentRed = Color(1.0f, 0.0f, 0.0f, 0.5f).toArgb()
+
+ rule.setContent {
+ Box(modifier = Modifier.size(size)) {
+ GraphicsSurface(
+ modifier = Modifier
+ .size(size)
+ .testTag("GraphicSurface"),
+ isOpaque = false,
+ zOrder = GraphicsSurfaceZOrder.OnTop
+ ) {
+ onSurface { surface, _, _ ->
+ // Draw > 3 frames to make sure the screenshot copy will pick up
+ // a SurfaceFlinger composition that includes our Surface
+ repeat(frameCount) {
+ withFrameNanos {
+ surface.lockHardwareCanvas().apply {
+ // Since we are drawing a translucent color we need to
+ // clear first
+ drawColor(0x00000000, PorterDuff.Mode.CLEAR)
+ drawColor(translucentRed)
+ surface.unlockCanvasAndPost(this)
+ }
+ latch.countDown()
+ }
+ }
+ }
+ }
+ Canvas(modifier = Modifier.size(size)) {
+ drawRect(Color.White)
+ }
+ }
+ }
+
+ if (!latch.await(5, TimeUnit.SECONDS)) {
+ throw AssertionError("Failed waiting for render")
+ }
+
+ val expectedColor = Color(ColorUtils.compositeColors(translucentRed, Color.White.toArgb()))
+
+ rule
+ .onNodeWithTag("GraphicSurface")
+ .screenshotToImage()!!
+ .assertPixels { expectedColor }
+ }
+
+ @Test
+ fun testSecure() {
+ val frameCount = 4
+ val latch = CountDownLatch(frameCount)
+
+ rule.setContent {
+ GraphicsSurface(
+ modifier = Modifier
+ .size(size)
+ .testTag("GraphicSurface"),
+ isSecure = true
+ ) {
+ onSurface { surface, _, _ ->
+ // Draw > 3 frames to make sure the screenshot copy will pick up
+ // a SurfaceFlinger composition that includes our Surface
+ repeat(frameCount) {
+ withFrameNanos {
+ surface.lockHardwareCanvas().apply {
+ drawColor(Color.Blue.toArgb())
+ surface.unlockCanvasAndPost(this)
+ }
+ latch.countDown()
+ }
+ }
+ }
+ }
+ }
+
+ if (!latch.await(5, TimeUnit.SECONDS)) {
+ throw AssertionError("Failed waiting for render")
+ }
+
+ val screen = rule
+ .onNodeWithTag("GraphicSurface")
+ .screenshotToImage()
+
+ // Before API 33 taking a screenshot with a secure surface returns null
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
+ assertNull(screen)
+ } else {
+ screen?.assertPixels { Color.Black }
+ }
+ }
+}
+
+/**
+ * Returns an ImageBitmap containing a screenshot of the device. On API < 33,
+ * a secure surface present on screen can cause this function to return null.
+ */
+private fun SemanticsNodeInteraction.screenshotToImage(): ImageBitmap? {
+ val node = fetchSemanticsNode()
+ val view = (node.root as ViewRootForTest).view
+
+ val location = IntArray(2)
+ view.getLocationOnScreen(location)
+
+ val bounds = node.boundsInRoot.translate(
+ location[0].toFloat(),
+ location[1].toFloat()
+ )
+
+ // TODO: On API 34+ we should use takeScreenshot(Window) instead
+ // It would be faster and less flaky than capturing the entire screen
+ val screen = InstrumentationRegistry
+ .getInstrumentation()
+ .uiAutomation
+ .takeScreenshot() ?: return null
+
+ return Bitmap.createBitmap(
+ screen,
+ bounds.left.toInt(),
+ bounds.top.toInt(),
+ bounds.width.roundToInt(),
+ bounds.height.roundToInt()
+ ).asImageBitmap()
+}
+
+@RequiresApi(Build.VERSION_CODES.O)
+internal fun Surface.captureToImage(width: Int, height: Int): ImageBitmap {
+ val bitmap = createBitmap(width, height)
+
+ val latch = CountDownLatch(1)
+ var copyResult = 0
+ val onCopyFinished = PixelCopy.OnPixelCopyFinishedListener { result ->
+ copyResult = result
+ latch.countDown()
+ android.util.Log.d("Test", Thread.currentThread().toString())
+ }
+
+ PixelCopy.request(
+ this,
+ Rect(0, 0, width, height),
+ bitmap,
+ onCopyFinished,
+ Handler(Looper.getMainLooper())
+ )
+
+ if (!latch.await(1, TimeUnit.SECONDS)) {
+ throw AssertionError("Failed waiting for PixelCopy!")
+ }
+
+ if (copyResult != PixelCopy.SUCCESS) {
+ throw AssertionError("PixelCopy failed!")
+ }
+
+ return bitmap.asImageBitmap()
+}
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/GraphicsSurface.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/GraphicsSurface.kt
new file mode 100644
index 0000000..17a6823
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/GraphicsSurface.kt
@@ -0,0 +1,438 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation
+
+import android.graphics.PixelFormat
+import android.graphics.SurfaceTexture
+import android.view.Surface
+import android.view.SurfaceHolder
+import android.view.SurfaceView
+import android.view.TextureView
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.viewinterop.AndroidView
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.cancelAndJoin
+import kotlinx.coroutines.launch
+
+/**
+ * [SurfaceScope] is a scoped environment provided by [GraphicsSurface] and
+ * [EmbeddedGraphicsSurface] to handle [Surface] lifecycle events.
+ *
+ * @sample androidx.compose.foundation.samples.GraphicsSurfaceColors
+ */
+interface SurfaceScope {
+ /**
+ * Invokes [onChanged] when the surface's geometry (width and height) changes.
+ * Always invoked on the main thread.
+ */
+ @Suppress("PrimitiveInLambda")
+ fun Surface.onChanged(onChanged: Surface.(width: Int, height: Int) -> Unit)
+
+ /**
+ * Invokes [onDestroyed] when the surface is destroyed. All rendering into
+ * the surface should stop immediately after [onDestroyed] is invoked.
+ * Always invoked on the main thread.
+ */
+ fun Surface.onDestroyed(onDestroyed: Surface.() -> Unit)
+}
+
+/**
+ * [SurfaceCoroutineScope] is a scoped environment provided by
+ * [GraphicsSurface] and [EmbeddedGraphicsSurface] when a new [Surface] is
+ * created. This environment is a coroutine scope that also provides access to
+ * a [SurfaceScope] environment which can itself be used to handle other [Surface]
+ * lifecycle events.
+ *
+ * @see SurfaceScope
+ * @see GraphicsSurfaceScope
+ *
+ * @sample androidx.compose.foundation.samples.GraphicsSurfaceColors
+ */
+interface SurfaceCoroutineScope : SurfaceScope, CoroutineScope
+
+/**
+ * [GraphicsSurfaceScope] is a scoped environment provided when a [GraphicsSurface]
+ * or [EmbeddedGraphicsSurface] is first initialized. This environment can be
+ * used to register a lambda to invoke when a new [Surface] associated with the
+ * [GraphicsSurface]/[EmbeddedGraphicsSurface] is created.
+ */
+interface GraphicsSurfaceScope {
+ /**
+ * Invokes [onSurface] when a new [Surface] is created. The [onSurface] lambda
+ * is invoked on the main thread as part of a [SurfaceCoroutineScope] to provide
+ * a coroutine context.
+ *
+ * @param onSurface Callback invoked when a new [Surface] is created. The initial
+ * dimensions of the surface are provided.
+ */
+ @Suppress("PrimitiveInLambda")
+ fun onSurface(
+ onSurface: suspend SurfaceCoroutineScope.(surface: Surface, width: Int, height: Int) -> Unit
+ )
+}
+
+/**
+ * Base class for [GraphicsSurface] and [EmbeddedGraphicsSurface] state. This class
+ * provides methods to properly dispatch lifecycle events on [Surface] creation,
+ * change, and destruction. Surface creation is treated as a coroutine launch,
+ * using the specified [scope] as the parent. This scope must be the main thread scope.
+ */
+private abstract class BaseGraphicsSurfaceState(val scope: CoroutineScope) :
+ GraphicsSurfaceScope, SurfaceScope {
+
+ private var onSurface:
+ (suspend SurfaceCoroutineScope.(surface: Surface, width: Int, height: Int) -> Unit)? = null
+ private var onSurfaceChanged: (Surface.(width: Int, height: Int) -> Unit)? = null
+ private var onSurfaceDestroyed: (Surface.() -> Unit)? = null
+
+ private var job: Job? = null
+
+ override fun onSurface(
+ onSurface: suspend SurfaceCoroutineScope.(surface: Surface, width: Int, height: Int) -> Unit
+ ) {
+ this.onSurface = onSurface
+ }
+
+ override fun Surface.onChanged(onChanged: Surface.(width: Int, height: Int) -> Unit) {
+ onSurfaceChanged = onChanged
+ }
+
+ override fun Surface.onDestroyed(onDestroyed: Surface.() -> Unit) {
+ onSurfaceDestroyed = onDestroyed
+ }
+
+ /**
+ * Dispatch a surface creation event by launching a new coroutine in [scope].
+ * Any previous job from a previous surface creation dispatch is cancelled.
+ */
+ fun dispatchSurfaceCreated(surface: Surface, width: Int, height: Int) {
+ if (onSurface != null) {
+ job = scope.launch(start = CoroutineStart.UNDISPATCHED) {
+ job?.cancelAndJoin()
+ val receiver =
+ object : SurfaceCoroutineScope, SurfaceScope by this@BaseGraphicsSurfaceState,
+ CoroutineScope by this {}
+ onSurface?.invoke(receiver, surface, width, height)
+ }
+ }
+ }
+
+ /**
+ * Dispatch a surface change event, providing the surface's new width and height.
+ * Must be invoked from the main thread.
+ */
+ fun dispatchSurfaceChanged(surface: Surface, width: Int, height: Int) {
+ onSurfaceChanged?.invoke(surface, width, height)
+ }
+
+ /**
+ * Dispatch a surface destruction event. Any pending job from [dispatchSurfaceCreated]
+ * is cancelled before dispatching the event. Must be invoked from the main thread.
+ */
+ fun dispatchSurfaceDestroyed(surface: Surface) {
+ onSurfaceDestroyed?.invoke(surface)
+ job?.cancel()
+ job = null
+ }
+}
+
+private class GraphicsSurfaceState(scope: CoroutineScope) : BaseGraphicsSurfaceState(scope),
+ SurfaceHolder.Callback {
+
+ var lastWidth = -1
+ var lastHeight = -1
+
+ override fun surfaceCreated(holder: SurfaceHolder) {
+ val frame = holder.surfaceFrame
+ lastWidth = frame.width()
+ lastHeight = frame.height()
+
+ dispatchSurfaceCreated(holder.surface, lastWidth, lastHeight)
+ }
+
+ override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
+ if (lastWidth != width || lastHeight != height) {
+ lastWidth = width
+ lastHeight = height
+
+ dispatchSurfaceChanged(holder.surface, width, height)
+ }
+ }
+
+ override fun surfaceDestroyed(holder: SurfaceHolder) {
+ dispatchSurfaceDestroyed(holder.surface)
+ }
+}
+
+@Composable
+private fun rememberGraphicsSurfaceState(): GraphicsSurfaceState {
+ val scope = rememberCoroutineScope()
+ return remember { GraphicsSurfaceState(scope) }
+}
+
+@JvmInline
+value class GraphicsSurfaceZOrder private constructor(val zOrder: Int) {
+ companion object {
+ val Behind = GraphicsSurfaceZOrder(0)
+ val MediaOverlay = GraphicsSurfaceZOrder(1)
+ val OnTop = GraphicsSurfaceZOrder(2)
+ }
+}
+
+/**
+ * Provides a dedicated drawing [Surface] as a separate layer positioned by default behind
+ * the window holding the [GraphicsSurface] composable. Because [GraphicsSurface] uses
+ * a separate window layer, graphics composition is handled by the system compositor which
+ * can bypass the GPU and provide better performance and power usage characteristics compared
+ * to [EmbeddedGraphicsSurface]. It is therefore recommended to use [GraphicsSurface]
+ * whenever possible.
+ *
+ * The z-ordering of the surface can be controlled using the [zOrder] parameter:
+ *
+ * - [GraphicsSurfaceZOrder.Behind]: positions the surface behind the window
+ * - [GraphicsSurfaceZOrder.MediaOverlay]: positions the surface behind the window but
+ * above other [GraphicsSurfaceZOrder.Behind] surfaces
+ * - [GraphicsSurfaceZOrder.OnTop]: positions the surface above the window
+ *
+ * The drawing surface is opaque by default, which can be controlled with the [isOpaque]
+ * parameter. When the surface is transparent, you may need to change the z-order to
+ * see something behind the surface.
+ *
+ * To start rendering, the caller must first acquire the [Surface] when it's created.
+ * This is achieved by providing the [onInit] lambda, which allows the caller to
+ * register an appropriate [GraphicsSurfaceScope.onSurface] callback. The [onInit]
+ * lambda can also be used to initialize/cache resources needed once a surface is
+ * available.
+ *
+ * After acquiring a surface, the caller can start rendering into it. Rendering into a
+ * surface can be done from any thread.
+ *
+ * It is recommended to register the [SurfaceScope.onChanged] and [SurfaceScope.onDestroyed]
+ * callbacks to properly handle the lifecycle of the surface and react to dimension
+ * changes. You must ensure that the rendering thread stops interacting with the surface
+ * when the [SurfaceScope.onDestroyed] callback is invoked.
+ *
+ * If a [surfaceSize] is specified (set to non-[IntSize.Zero]), the surface will use
+ * the specified size instead of the layout size of this composable. The surface will
+ * be stretched at render time to fit the layout size. This can be used for instance to
+ * render at a lower resolution for performance reasons.
+ *
+ * @param modifier Modifier to be applied to the [GraphicsSurface]
+ * @param isOpaque Whether the managed surface should be opaque or transparent.
+ * @param zOrder Sets the z-order of the surface relative to its parent window.
+ * @param surfaceSize Sets the surface size independently of the layout size of
+ * this [GraphicsSurface]. If set to [IntSize.Zero], the surface
+ * size will be equal to the [GraphicsSurface] layout size.
+ * @param isSecure Control whether the surface view's content should be treated as
+ * secure, preventing it from appearing in screenshots or from being
+ * viewed on non-secure displays.
+ * @param onInit Lambda invoked on first composition. This lambda can be used to
+ * declare a [GraphicsSurfaceScope.onSurface] callback that will be
+ * invoked when a surface is available.
+ *
+ * @sample androidx.compose.foundation.samples.GraphicsSurfaceColors
+ */
+@Composable
+fun GraphicsSurface(
+ modifier: Modifier = Modifier,
+ isOpaque: Boolean = true,
+ zOrder: GraphicsSurfaceZOrder = GraphicsSurfaceZOrder.Behind,
+ surfaceSize: IntSize = IntSize.Zero,
+ isSecure: Boolean = false,
+ onInit: GraphicsSurfaceScope.() -> Unit
+) {
+ val state = rememberGraphicsSurfaceState()
+
+ AndroidView(
+ factory = { context ->
+ SurfaceView(context).apply {
+ state.onInit()
+ holder.addCallback(state)
+ }
+ },
+ modifier = modifier,
+ onReset = { },
+ update = { view ->
+ if (surfaceSize != IntSize.Zero) {
+ view.holder.setFixedSize(surfaceSize.width, surfaceSize.height)
+ } else {
+ view.holder.setSizeFromLayout()
+ }
+
+ view.holder.setFormat(
+ if (isOpaque) {
+ PixelFormat.OPAQUE
+ } else {
+ PixelFormat.TRANSLUCENT
+ }
+ )
+
+ when (zOrder) {
+ GraphicsSurfaceZOrder.Behind -> view.setZOrderOnTop(false)
+ GraphicsSurfaceZOrder.MediaOverlay -> view.setZOrderMediaOverlay(true)
+ GraphicsSurfaceZOrder.OnTop -> view.setZOrderOnTop(true)
+ }
+
+ view.setSecure(isSecure)
+ }
+ )
+}
+
+private class EmbeddedGraphicsSurfaceState(scope: CoroutineScope) : BaseGraphicsSurfaceState(scope),
+ TextureView.SurfaceTextureListener {
+
+ var surfaceSize = IntSize.Zero
+
+ private var surfaceTextureSurface: Surface? = null
+
+ override fun onSurfaceTextureAvailable(
+ surfaceTexture: SurfaceTexture,
+ width: Int,
+ height: Int
+ ) {
+ var w = width
+ var h = height
+
+ if (surfaceSize != IntSize.Zero) {
+ w = surfaceSize.width
+ h = surfaceSize.height
+ surfaceTexture.setDefaultBufferSize(w, h)
+ }
+
+ val surface = Surface(surfaceTexture)
+ surfaceTextureSurface = surface
+
+ dispatchSurfaceCreated(surface, w, h)
+ }
+
+ override fun onSurfaceTextureSizeChanged(
+ surfaceTexture: SurfaceTexture,
+ width: Int,
+ height: Int
+ ) {
+ var w = width
+ var h = height
+
+ if (surfaceSize != IntSize.Zero) {
+ w = surfaceSize.width
+ h = surfaceSize.height
+ surfaceTexture.setDefaultBufferSize(w, h)
+ }
+
+ dispatchSurfaceChanged(surfaceTextureSurface!!, w, h)
+ }
+
+ override fun onSurfaceTextureDestroyed(surfaceTexture: SurfaceTexture): Boolean {
+ dispatchSurfaceDestroyed(surfaceTextureSurface!!)
+ surfaceTextureSurface = null
+ return true
+ }
+
+ override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {
+ // onSurfaceTextureUpdated is called when the content of the SurfaceTexture
+ // has changed, which is not relevant to us since we are the producer here
+ }
+}
+
+@Composable
+private fun rememberEmbeddedGraphicsSurfaceState(): EmbeddedGraphicsSurfaceState {
+ val scope = rememberCoroutineScope()
+ return remember { EmbeddedGraphicsSurfaceState(scope) }
+}
+
+/**
+ * Provides a dedicated drawing [Surface] embedded directly in the UI hierarchy.
+ * Unlike [GraphicsSurface], [EmbeddedGraphicsSurface] positions its surface as a
+ * regular element inside the composable hierarchy. This means that graphics
+ * composition is handled like any other UI widget, using the GPU. This can lead
+ * to increased power and memory bandwidth usage compared to [GraphicsSurface]. It
+ * is therefore recommended to use [GraphicsSurface] when possible.
+ *
+ * [EmbeddedGraphicsSurface] can however be useful when interactions with other widgets
+ * is necessary, for instance if the surface needs to be "sandwiched" between two
+ * other widgets, or if it must participate in visual effects driven by
+ * a `Modifier.graphicsLayer{}`.
+ *
+ * The drawing surface is opaque by default, which can be controlled with the [isOpaque]
+ * parameter.
+ *
+ * To start rendering, the caller must first acquire the [Surface] when it's created.
+ * This is achieved by providing the [onInit] lambda, which allows the caller to
+ * register an appropriate [GraphicsSurfaceScope.onSurface] callback. The [onInit]
+ * lambda can also be used to initialize/cache resources needed once a surface is
+ * available.
+ *
+ * After acquiring a surface, the caller can start rendering into it. Rendering into a
+ * surface can be done from any thread.
+ *
+ * It is recommended to register the [SurfaceScope.onChanged] and [SurfaceScope.onDestroyed]
+ * callbacks to properly handle the lifecycle of the surface and react to dimension
+ * changes. You must ensure that the rendering thread stops interacting with the surface
+ * when the [SurfaceScope.onDestroyed] callback is invoked.
+ *
+ * If a [surfaceSize] is specified (set to non-[IntSize.Zero]), the surface will use
+ * the specified size instead of the layout size of this composable. The surface will
+ * be stretched at render time to fit the layout size. This can be used for instance to
+ * render at a lower resolution for performance reasons.
+ *
+ * @param modifier Modifier to be applied to the [GraphicsSurface]
+ * @param isOpaque Whether the managed surface should be opaque or transparent. If
+ * transparent and [isMediaOverlay] is `false`, the surface will
+ * be positioned above the parent window.
+ * @param surfaceSize Sets the surface size independently of the layout size of
+ * this [GraphicsSurface]. If set to [IntSize.Zero], the surface
+ * size will be equal to the [GraphicsSurface] layout size.
+ * @param onInit Lambda invoked on first composition. This lambda can be used to
+ * declare a [GraphicsSurfaceScope.onSurface] callback that will be
+ * invoked when a surface is available.
+ *
+ * @sample androidx.compose.foundation.samples.EmbeddedGraphicsSurfaceColors
+ */
+@Composable
+fun EmbeddedGraphicsSurface(
+ modifier: Modifier = Modifier,
+ isOpaque: Boolean = true,
+ surfaceSize: IntSize = IntSize.Zero,
+ onInit: GraphicsSurfaceScope.() -> Unit
+) {
+ val state = rememberEmbeddedGraphicsSurfaceState()
+
+ AndroidView(
+ factory = { context ->
+ TextureView(context).apply {
+ state.surfaceSize = surfaceSize
+ state.onInit()
+ surfaceTextureListener = state
+ }
+ },
+ modifier = modifier,
+ onReset = { },
+ update = { view ->
+ if (surfaceSize != IntSize.Zero) {
+ view.surfaceTexture?.setDefaultBufferSize(surfaceSize.width, surfaceSize.height)
+ }
+ state.surfaceSize = surfaceSize
+ view.isOpaque = isOpaque
+ }
+ )
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewRequester.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewRequester.kt
index 8041415..8001053 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewRequester.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewRequester.kt
@@ -38,6 +38,9 @@
*
* Here is a sample where part of a composable is brought into view:
* @sample androidx.compose.foundation.samples.BringPartOfComposableIntoViewSample
+ *
+ * Note: this API is experimental while we optimise the performance and find the right API shape
+ * for it
*/
@ExperimentalFoundationApi
sealed interface BringIntoViewRequester {
@@ -73,6 +76,9 @@
*
* Here is a sample where a part of a composable is brought into view:
* @sample androidx.compose.foundation.samples.BringPartOfComposableIntoViewSample
+ *
+ * Note: this API is experimental while we optimise the performance and find the right API shape
+ * for it
*/
@ExperimentalFoundationApi
fun BringIntoViewRequester(): BringIntoViewRequester {
@@ -94,6 +100,9 @@
* hoisted object can be used to send
* [bringIntoView][BringIntoViewRequester.bringIntoView] requests to parents
* of the current composable.
+ *
+ * Note: this API is experimental while we optimise the performance and find the right API shape
+ * for it
*/
@Suppress("ModifierInspectorInfo")
@ExperimentalFoundationApi
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewResponder.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewResponder.kt
index 71820cc..770c8a06 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewResponder.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewResponder.kt
@@ -48,6 +48,9 @@
* @sample androidx.compose.foundation.samples.BringPartOfComposableIntoViewSample
*
* @see BringIntoViewRequester
+ *
+ * Note: this API is experimental while we optimise the performance and find the right API shape
+ * for it
*/
@ExperimentalFoundationApi
interface BringIntoViewResponder {
@@ -94,6 +97,9 @@
* @sample androidx.compose.foundation.samples.BringIntoViewSample
*
* @see BringIntoViewRequester
+ *
+ * Note: this API is experimental while we optimise the performance and find the right API shape
+ * for it
*/
@Suppress("ModifierInspectorInfo")
@ExperimentalFoundationApi
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextFieldBuffer.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextFieldBuffer.kt
index 10ac37e..a332068 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextFieldBuffer.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextFieldBuffer.kt
@@ -150,7 +150,7 @@
) {
require(start <= end) { "Expected start=$start <= end=$end" }
require(textStart <= textEnd) { "Expected textStart=$textStart <= textEnd=$textEnd" }
- onTextWillChange(TextRange(start, end), textEnd - textStart)
+ onTextWillChange(start, end, textEnd - textStart)
buffer.replace(start, end, text, textStart, textEnd)
}
@@ -168,7 +168,7 @@
// This append overload should be first so it ends up being the target of links to this method.
override fun append(text: CharSequence?): Appendable = apply {
if (text != null) {
- onTextWillChange(TextRange(length), text.length)
+ onTextWillChange(length, length, text.length)
buffer.replace(buffer.length, buffer.length, text)
}
}
@@ -176,30 +176,31 @@
// Doc inherited from Appendable.
override fun append(text: CharSequence?, start: Int, end: Int): Appendable = apply {
if (text != null) {
- onTextWillChange(TextRange(length), end - start)
+ onTextWillChange(length, length, end - start)
buffer.replace(buffer.length, buffer.length, text.subSequence(start, end))
}
}
// Doc inherited from Appendable.
override fun append(char: Char): Appendable = apply {
- onTextWillChange(TextRange(length), 1)
+ onTextWillChange(length, length, 1)
buffer.replace(buffer.length, buffer.length, char.toString())
}
/**
* Called just before the text contents are about to change.
*
- * @param rangeToBeReplaced The range in the current text that's about to be replaced.
+ * @param replaceStart The first offset to be replaced (inclusive).
+ * @param replaceEnd The last offset to be replaced (exclusive).
* @param newLength The length of the replacement.
*/
- private fun onTextWillChange(rangeToBeReplaced: TextRange, newLength: Int) {
+ private fun onTextWillChange(replaceStart: Int, replaceEnd: Int, newLength: Int) {
(changeTracker ?: ChangeTracker().also { changeTracker = it })
- .trackChange(rangeToBeReplaced, newLength)
+ .trackChange(replaceStart, replaceEnd, newLength)
// Adjust selection.
- val start = rangeToBeReplaced.min
- val end = rangeToBeReplaced.max
+ val start = minOf(replaceStart, replaceEnd)
+ val end = maxOf(replaceStart, replaceEnd)
var selStart = selectionInChars.min
var selEnd = selectionInChars.max
@@ -448,7 +449,7 @@
/**
* The ordered list of non-overlapping and discontinuous changes performed on a
* [TextFieldBuffer] during the current [edit][TextFieldState.edit] or
- * [filter][TextEditFilter.filter] operation. Changes are listed in the order they appear in the
+ * [filter][InputTransformation.transformInput] operation. Changes are listed in the order they appear in the
* text, not the order in which they were made. Overlapping changes are represented as a single
* change.
*/
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/ChangeTracker.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/ChangeTracker.kt
index a3f31a5..ad73c39 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/ChangeTracker.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/ChangeTracker.kt
@@ -64,15 +64,17 @@
* for the new text.
* 3. Offset all remaining changes are to account for the new text.
*/
- fun trackChange(preRange: TextRange, postLength: Int) {
- if (preRange.collapsed && postLength == 0) {
+ fun trackChange(preStart: Int, preEnd: Int, postLength: Int) {
+ if (preStart == preEnd && postLength == 0) {
// Ignore noop changes.
return
}
+ val preMin = minOf(preStart, preEnd)
+ val preMax = maxOf(preStart, preEnd)
var i = 0
var recordedNewChange = false
- val postDelta = postLength - preRange.length
+ val postDelta = postLength - (preMax - preStart)
var mergedOverlappingChange: Change? = null
while (i < _changes.size) {
@@ -80,8 +82,8 @@
// Merge adjacent and overlapping changes as we go.
if (
- change.preStart in preRange.min..preRange.max ||
- change.preEnd in preRange.min..preRange.max
+ change.preStart in preMin..preMax ||
+ change.preEnd in preMin..preMax
) {
if (mergedOverlappingChange == null) {
mergedOverlappingChange = change
@@ -94,10 +96,10 @@
continue
}
- if (change.preStart > preRange.max && !recordedNewChange) {
+ if (change.preStart > preMax && !recordedNewChange) {
// First non-overlapping change after the new one – record the change before
// proceeding.
- appendNewChange(mergedOverlappingChange, preRange, postDelta)
+ appendNewChange(mergedOverlappingChange, preMin, preMax, postDelta)
recordedNewChange = true
}
@@ -112,7 +114,7 @@
if (!recordedNewChange) {
// The new change is after or overlapping all previous changes so it hasn't been
// appended yet.
- appendNewChange(mergedOverlappingChange, preRange, postDelta)
+ appendNewChange(mergedOverlappingChange, preMin, preMax, postDelta)
}
// Swap the lists.
@@ -146,7 +148,8 @@
private fun appendNewChange(
mergedOverlappingChange: Change?,
- preRange: TextRange,
+ preMin: Int,
+ preMax: Int,
postDelta: Int
) {
var originalDelta = if (_changesTemp.isEmpty()) 0 else {
@@ -155,11 +158,11 @@
val newChange: Change
if (mergedOverlappingChange == null) {
// There were no overlapping changes, so allocate a new one.
- val originalStart = preRange.min - originalDelta
- val originalEnd = originalStart + preRange.length
+ val originalStart = preMin - originalDelta
+ val originalEnd = originalStart + (preMax - preMin)
newChange = Change(
- preStart = preRange.min,
- preEnd = preRange.max + postDelta,
+ preStart = preMin,
+ preEnd = preMax + postDelta,
originalStart = originalStart,
originalEnd = originalEnd
)
@@ -167,16 +170,16 @@
newChange = mergedOverlappingChange
// Convert the merged overlapping changes to the `post` space.
// Merge the new changed with the merged overlapping changes.
- if (newChange.preStart > preRange.min) {
+ if (newChange.preStart > preMin) {
// The new change starts before the merged overlapping set.
- newChange.preStart = preRange.min
- newChange.originalStart = preRange.min
+ newChange.preStart = preMin
+ newChange.originalStart = preMin
}
- if (preRange.max > newChange.preEnd) {
+ if (preMax > newChange.preEnd) {
// The new change ends after the merged overlapping set.
originalDelta = newChange.preEnd - newChange.originalEnd
- newChange.preEnd = preRange.max
- newChange.originalEnd = preRange.max - originalDelta
+ newChange.preEnd = preMax
+ newChange.originalEnd = preMax - originalDelta
}
newChange.preEnd += postDelta
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/EditingBuffer.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/EditingBuffer.kt
index 75ef12a..d869ec5 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/EditingBuffer.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/EditingBuffer.kt
@@ -151,7 +151,7 @@
val min = minOf(start, end)
val max = maxOf(start, end)
- changeTracker.trackChange(TextRange(start, end), text.length)
+ changeTracker.trackChange(start, end, text.length)
gapBuffer.replace(min, max, text)
@@ -180,7 +180,7 @@
checkRange(start, end)
val deleteRange = TextRange(start, end)
- changeTracker.trackChange(deleteRange, 0)
+ changeTracker.trackChange(start, end, 0)
gapBuffer.replace(deleteRange.min, deleteRange.max, "")
diff --git a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text2/input/internal/ChangeTrackerTest.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text2/input/internal/ChangeTrackerTest.kt
index 5c08053..3029a8e 100644
--- a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text2/input/internal/ChangeTrackerTest.kt
+++ b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text2/input/internal/ChangeTrackerTest.kt
@@ -190,7 +190,7 @@
val changes = ChangeTracker()
fun append(text: String) {
- changes.trackChange(TextRange(builder.length), text.length)
+ changes.trackChange(builder.length, builder.length, text.length)
builder.append(text)
}
@@ -198,7 +198,7 @@
val start = builder.indexOf(substring)
if (start != -1) {
val end = start + substring.length
- changes.trackChange(TextRange(start, end), text.length)
+ changes.trackChange(start, end, text.length)
builder.replace(start, end, text)
}
}
diff --git a/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/PagerAsCarouselBenchmark.kt b/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/PagerAsCarouselBenchmark.kt
index becf364..f6b8b9c 100644
--- a/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/PagerAsCarouselBenchmark.kt
+++ b/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/PagerAsCarouselBenchmark.kt
@@ -29,6 +29,7 @@
import androidx.test.uiautomator.Until
import androidx.testutils.createCompilationParams
import org.junit.Before
+import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -51,6 +52,7 @@
}
@Test
+ @Ignore("b/299155975")
fun scroll() {
val carousel = device.findObject(By.desc(ContentDescription))
benchmarkRule.performRepeatedScroll(PackageName, compilationMode, Action, carousel) {
diff --git a/compose/lint/internal-lint-checks/build.gradle b/compose/lint/internal-lint-checks/build.gradle
index 3039214..f2132be 100644
--- a/compose/lint/internal-lint-checks/build.gradle
+++ b/compose/lint/internal-lint-checks/build.gradle
@@ -26,6 +26,7 @@
compileOnly(libs.androidLintApi)
compileOnly(libs.kotlinStdlib)
implementation(project(":compose:lint:common"))
+ implementation(project(":collection:collection"))
testImplementation(project(":compose:lint:common-test"))
testImplementation(libs.kotlinStdlib)
diff --git a/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/AsCollectionDetector.kt b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/AsCollectionDetector.kt
new file mode 100644
index 0000000..2d470e0
--- /dev/null
+++ b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/AsCollectionDetector.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress("UnstableApiUsage")
+
+package androidx.compose.lint
+
+import androidx.collection.MutableObjectList
+import androidx.collection.MutableScatterMap
+import androidx.collection.MutableScatterSet
+import androidx.collection.ObjectList
+import androidx.collection.ScatterMap
+import androidx.collection.ScatterSet
+import androidx.collection.scatterSetOf
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.impl.source.PsiClassReferenceType
+import java.util.EnumSet
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UElement
+
+/**
+ * Using [ScatterMap.asMap], [ScatterSet.asSet], [ObjectList.asList], or their mutable
+ * counterparts indicates that the developer may be using the collection incorrectly.
+ * Using the interfaces is slower access. It is best to use those only for when it touches
+ * public API.
+ */
+class AsCollectionDetector : Detector(), SourceCodeScanner {
+ override fun getApplicableUastTypes() = listOf<Class<out UElement>>(
+ UCallExpression::class.java
+ )
+
+ override fun createUastHandler(context: JavaContext) = object : UElementHandler() {
+ override fun visitCallExpression(node: UCallExpression) {
+ val methodName = node.methodName ?: return
+ if (methodName in MethodNames) {
+ val receiverType = node.receiverType as? PsiClassReferenceType ?: return
+ val qualifiedName = receiverType.reference.qualifiedName ?: return
+ val indexOfAngleBracket = qualifiedName.indexOf('<')
+ if (indexOfAngleBracket > 0 &&
+ qualifiedName.substring(0, indexOfAngleBracket) in CollectionClasses
+ ) {
+ context.report(
+ ISSUE,
+ node,
+ context.getLocation(node),
+ "Use method $methodName() only for public API usage"
+ )
+ }
+ }
+ }
+ }
+
+ companion object {
+ private val MethodNames = scatterSetOf(
+ "asMap",
+ "asMutableMap",
+ "asSet",
+ "asMutableSet",
+ "asList",
+ "asMutableList"
+ )
+ private val CollectionClasses = scatterSetOf(
+ ScatterMap::class.qualifiedName,
+ MutableScatterMap::class.qualifiedName,
+ ScatterSet::class.qualifiedName,
+ MutableScatterSet::class.qualifiedName,
+ ObjectList::class.qualifiedName,
+ MutableObjectList::class.qualifiedName,
+ )
+
+ private val AsCollectionDetectorId = "AsCollectionCall"
+
+ val ISSUE = Issue.create(
+ id = AsCollectionDetectorId,
+ briefDescription = "High performance collections don't implement standard collection " +
+ "interfaces so that they can remain high performance. Converting to standard " +
+ "collections wraps the classes with another object. Use these interface " +
+ "wrappers only for exposing to public API.",
+ explanation = "ScatterMap, ScatterSet, and AnyList are written for high " +
+ "performance access. Using the standard collection interfaces for these classes " +
+ "forces slower performance access to these collections. The methods returning " +
+ "these interfaces should be limited to public API, where standard collection " +
+ "interfaces are expected.",
+ category = Category.PERFORMANCE, priority = 3, severity = Severity.ERROR,
+ implementation = Implementation(
+ AsCollectionDetector::class.java,
+ EnumSet.of(Scope.JAVA_FILE)
+ )
+ )
+ }
+}
diff --git a/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ComposeIssueRegistry.kt b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ComposeIssueRegistry.kt
index 7e43f93..f13628f 100644
--- a/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ComposeIssueRegistry.kt
+++ b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ComposeIssueRegistry.kt
@@ -28,6 +28,7 @@
override val api = 14
override val issues get(): List<Issue> {
return listOf(
+ AsCollectionDetector.ISSUE,
ExceptionMessageDetector.ISSUE,
ListIteratorDetector.ISSUE,
SteppedForLoopDetector.ISSUE,
diff --git a/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/AsCollectionDetectorTest.kt b/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/AsCollectionDetectorTest.kt
new file mode 100644
index 0000000..39ce2a1
--- /dev/null
+++ b/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/AsCollectionDetectorTest.kt
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.lint
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/* ktlint-disable max-line-length */
+@RunWith(Parameterized::class)
+class AsCollectionDetectorTest(
+ val types: CollectionType
+) : LintDetectorTest() {
+
+ override fun getDetector(): Detector = AsCollectionDetector()
+
+ override fun getIssues(): MutableList<Issue> =
+ mutableListOf(AsCollectionDetector.ISSUE)
+
+ private val collectionTilde = "~".repeat(types.collection.length)
+
+ @Test
+ fun immutableAsImmutable() {
+ lint().files(
+ ScatterMapClass,
+ ScatterSetClass,
+ ObjectListClass,
+ kotlin(
+ """
+ package androidx.compose.lint
+
+ import androidx.collection.${types.immutable}
+
+ fun foo(collection: ${types.immutable}${types.params}): ${types.collection}${types.params} =
+ collection.as${types.collection}()
+ """
+ )
+ ).run().expect(
+ """
+src/androidx/compose/lint/test.kt:7: Error: Use method as${types.collection}() only for public API usage [AsCollectionCall]
+ collection.as${types.collection}()
+ ~~~~~~~~~~~~~$collectionTilde~~
+1 errors, 0 warnings
+ """
+ )
+ }
+
+ @Test
+ fun mutableAsImmutable() {
+ lint().files(
+ ScatterMapClass,
+ ScatterSetClass,
+ ObjectListClass,
+ kotlin(
+ """
+ package androidx.compose.lint
+
+ import androidx.collection.Mutable${types.immutable}
+
+ fun foo(collection: Mutable${types.immutable}${types.params}): ${types.collection}${types.params} =
+ collection.as${types.collection}()
+ """
+ )
+ ).run().expect(
+ """
+src/androidx/compose/lint/test.kt:7: Error: Use method as${types.collection}() only for public API usage [AsCollectionCall]
+ collection.as${types.collection}()
+ ~~~~~~~~~~~~~$collectionTilde~~
+1 errors, 0 warnings
+ """
+ )
+ }
+
+ @Test
+ fun mutableAsMutable() {
+ lint().files(
+ ScatterMapClass,
+ ScatterSetClass,
+ ObjectListClass,
+ kotlin(
+ """
+ package androidx.compose.lint
+
+ import androidx.collection.Mutable${types.immutable}
+
+ fun foo(collection: Mutable${types.immutable}${types.params}): Mutable${types.collection}${types.params} =
+ collection.asMutable${types.collection}()
+ """
+ )
+ ).run().expect(
+ """
+src/androidx/compose/lint/test.kt:7: Error: Use method asMutable${types.collection}() only for public API usage [AsCollectionCall]
+ collection.asMutable${types.collection}()
+ ~~~~~~~~~~~~~~~~~~~~$collectionTilde~~
+1 errors, 0 warnings
+ """
+ )
+ }
+
+ @Test
+ fun nonCollectionAs() {
+ lint().files(
+ kotlin(
+ """
+ package androidx.compose.lint
+
+ fun foo(): ${types.collection}${types.params} =
+ WeirdCollection().as${types.collection}()
+
+ class WeirdCollection {
+ fun asList(): List<String>? = null
+ fun asSet(): Set<String>? = null
+ fun asMap(): Map<String, String>? = null
+ }
+ """
+ )
+ ).run().expectClean()
+ }
+
+ class CollectionType(
+ val immutable: String,
+ val collection: String,
+ val params: String
+ )
+
+ companion object {
+ @JvmStatic
+ @Parameterized.Parameters(name = "{0}")
+ fun initParameters() = listOf(
+ CollectionType("ScatterMap", "Map", "<String, String>"),
+ CollectionType("ScatterSet", "Set", "<String>"),
+ CollectionType("ObjectList", "List", "<String>")
+ )
+
+ val ScatterMapClass = kotlin(
+ """
+ package androidx.collection
+ sealed class ScatterMap<K, V> {
+ fun asMap(): Map<K, V> = mapOf()
+ }
+
+ class MutableScatterMap<K, V> : ScatterMap<K, V>() {
+ fun asMutableMap(): MutableMap<K, V> = mutableMapOf()
+ }
+ """.trimIndent()
+ )
+
+ val ScatterSetClass = kotlin(
+ """
+ package androidx.collection
+ sealed class ScatterSet<E> {
+ fun asSet(): Set<E> = setOf()
+ }
+
+ class MutableScatterSet<E> : ScatterSet<E>() {
+ fun asMutableSet(): MutableSet<E> = mutableSetOf()
+ }
+ """.trimIndent()
+ )
+
+ val ObjectListClass = kotlin(
+ """
+ package androidx.collection
+ sealed class ObjectList<E> {
+ fun asList(): List<E> = listOf()
+ }
+
+ class MutableObjectList<E> : ObjectList<E>() {
+ fun asMutableList(): MutableList<E> = mutableListOf()
+ }
+ """.trimIndent()
+ )
+ }
+}
diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt
index d665b1b..2e69826 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -29,13 +29,17 @@
}
public final class AppBarKt {
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void BottomAppBar(optional androidx.compose.ui.Modifier modifier, optional long containerColor, optional long contentColor, optional float tonalElevation, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.BottomAppBarScrollBehavior? scrollBehavior, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
method @androidx.compose.runtime.Composable public static void BottomAppBar(optional androidx.compose.ui.Modifier modifier, optional long containerColor, optional long contentColor, optional float tonalElevation, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.layout.WindowInsets windowInsets, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
method @androidx.compose.runtime.Composable public static void BottomAppBar(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? floatingActionButton, optional long containerColor, optional long contentColor, optional float tonalElevation, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.layout.WindowInsets windowInsets);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void BottomAppBar(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? floatingActionButton, optional long containerColor, optional long contentColor, optional float tonalElevation, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.BottomAppBarScrollBehavior? scrollBehavior);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.material3.BottomAppBarState BottomAppBarState(float initialHeightOffsetLimit, float initialHeightOffset, float initialContentOffset);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void CenterAlignedTopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void LargeTopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void MediumTopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void SmallTopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.BottomAppBarState rememberBottomAppBarState(optional float initialHeightOffsetLimit, optional float initialHeightOffset, optional float initialContentOffset);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.TopAppBarState rememberTopAppBarState(optional float initialHeightOffsetLimit, optional float initialHeightOffset, optional float initialContentOffset);
}
@@ -66,6 +70,7 @@
}
public final class BottomAppBarDefaults {
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public androidx.compose.material3.BottomAppBarScrollBehavior exitAlwaysScrollBehavior(optional androidx.compose.material3.BottomAppBarState state, optional kotlin.jvm.functions.Function0<java.lang.Boolean> canScroll, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float>? snapAnimationSpec, optional androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float>? flingAnimationSpec);
method @androidx.compose.runtime.Composable public long getBottomAppBarFabColor();
method @androidx.compose.runtime.Composable public long getContainerColor();
method public float getContainerElevation();
@@ -79,6 +84,39 @@
field public static final androidx.compose.material3.BottomAppBarDefaults INSTANCE;
}
+ @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public interface BottomAppBarScrollBehavior {
+ method public androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float>? getFlingAnimationSpec();
+ method public androidx.compose.ui.input.nestedscroll.NestedScrollConnection getNestedScrollConnection();
+ method public androidx.compose.animation.core.AnimationSpec<java.lang.Float>? getSnapAnimationSpec();
+ method public androidx.compose.material3.BottomAppBarState getState();
+ method public boolean isPinned();
+ property public abstract androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float>? flingAnimationSpec;
+ property public abstract boolean isPinned;
+ property public abstract androidx.compose.ui.input.nestedscroll.NestedScrollConnection nestedScrollConnection;
+ property public abstract androidx.compose.animation.core.AnimationSpec<java.lang.Float>? snapAnimationSpec;
+ property public abstract androidx.compose.material3.BottomAppBarState state;
+ }
+
+ @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public interface BottomAppBarState {
+ method public float getCollapsedFraction();
+ method public float getContentOffset();
+ method public float getHeightOffset();
+ method public float getHeightOffsetLimit();
+ method public void setContentOffset(float);
+ method public void setHeightOffset(float);
+ method public void setHeightOffsetLimit(float);
+ property public abstract float collapsedFraction;
+ property public abstract float contentOffset;
+ property public abstract float heightOffset;
+ property public abstract float heightOffsetLimit;
+ field public static final androidx.compose.material3.BottomAppBarState.Companion Companion;
+ }
+
+ public static final class BottomAppBarState.Companion {
+ method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.BottomAppBarState,?> getSaver();
+ property public final androidx.compose.runtime.saveable.Saver<androidx.compose.material3.BottomAppBarState,?> Saver;
+ }
+
@SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class BottomSheetDefaults {
method @androidx.compose.runtime.Composable public void DragHandle(optional androidx.compose.ui.Modifier modifier, optional float width, optional float height, optional androidx.compose.ui.graphics.Shape shape, optional long color);
method @androidx.compose.runtime.Composable public long getContainerColor();
@@ -677,9 +715,11 @@
public static final class FabPosition.Companion {
method public int getCenter();
method public int getEnd();
+ method public int getEndOverlay();
method public int getStart();
property public final int Center;
property public final int End;
+ property public final int EndOverlay;
property public final int Start;
}
@@ -829,6 +869,10 @@
property @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static final androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> LocalMinimumTouchTargetEnforcement;
}
+ public final class LabelKt {
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void Label(kotlin.jvm.functions.Function0<kotlin.Unit> label, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional boolean isPersistent, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ }
+
@androidx.compose.runtime.Immutable public final class ListItemColors {
ctor public ListItemColors(long containerColor, long headlineColor, long leadingIconColor, long overlineColor, long supportingTextColor, long trailingIconColor, long disabledHeadlineColor, long disabledLeadingIconColor, long disabledTrailingIconColor);
method public long getContainerColor();
diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt
index d665b1b..2e69826 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -29,13 +29,17 @@
}
public final class AppBarKt {
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void BottomAppBar(optional androidx.compose.ui.Modifier modifier, optional long containerColor, optional long contentColor, optional float tonalElevation, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.BottomAppBarScrollBehavior? scrollBehavior, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
method @androidx.compose.runtime.Composable public static void BottomAppBar(optional androidx.compose.ui.Modifier modifier, optional long containerColor, optional long contentColor, optional float tonalElevation, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.layout.WindowInsets windowInsets, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
method @androidx.compose.runtime.Composable public static void BottomAppBar(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? floatingActionButton, optional long containerColor, optional long contentColor, optional float tonalElevation, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.layout.WindowInsets windowInsets);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void BottomAppBar(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? floatingActionButton, optional long containerColor, optional long contentColor, optional float tonalElevation, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.BottomAppBarScrollBehavior? scrollBehavior);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.material3.BottomAppBarState BottomAppBarState(float initialHeightOffsetLimit, float initialHeightOffset, float initialContentOffset);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void CenterAlignedTopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void LargeTopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void MediumTopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void SmallTopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.BottomAppBarState rememberBottomAppBarState(optional float initialHeightOffsetLimit, optional float initialHeightOffset, optional float initialContentOffset);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.TopAppBarState rememberTopAppBarState(optional float initialHeightOffsetLimit, optional float initialHeightOffset, optional float initialContentOffset);
}
@@ -66,6 +70,7 @@
}
public final class BottomAppBarDefaults {
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public androidx.compose.material3.BottomAppBarScrollBehavior exitAlwaysScrollBehavior(optional androidx.compose.material3.BottomAppBarState state, optional kotlin.jvm.functions.Function0<java.lang.Boolean> canScroll, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float>? snapAnimationSpec, optional androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float>? flingAnimationSpec);
method @androidx.compose.runtime.Composable public long getBottomAppBarFabColor();
method @androidx.compose.runtime.Composable public long getContainerColor();
method public float getContainerElevation();
@@ -79,6 +84,39 @@
field public static final androidx.compose.material3.BottomAppBarDefaults INSTANCE;
}
+ @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public interface BottomAppBarScrollBehavior {
+ method public androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float>? getFlingAnimationSpec();
+ method public androidx.compose.ui.input.nestedscroll.NestedScrollConnection getNestedScrollConnection();
+ method public androidx.compose.animation.core.AnimationSpec<java.lang.Float>? getSnapAnimationSpec();
+ method public androidx.compose.material3.BottomAppBarState getState();
+ method public boolean isPinned();
+ property public abstract androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float>? flingAnimationSpec;
+ property public abstract boolean isPinned;
+ property public abstract androidx.compose.ui.input.nestedscroll.NestedScrollConnection nestedScrollConnection;
+ property public abstract androidx.compose.animation.core.AnimationSpec<java.lang.Float>? snapAnimationSpec;
+ property public abstract androidx.compose.material3.BottomAppBarState state;
+ }
+
+ @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public interface BottomAppBarState {
+ method public float getCollapsedFraction();
+ method public float getContentOffset();
+ method public float getHeightOffset();
+ method public float getHeightOffsetLimit();
+ method public void setContentOffset(float);
+ method public void setHeightOffset(float);
+ method public void setHeightOffsetLimit(float);
+ property public abstract float collapsedFraction;
+ property public abstract float contentOffset;
+ property public abstract float heightOffset;
+ property public abstract float heightOffsetLimit;
+ field public static final androidx.compose.material3.BottomAppBarState.Companion Companion;
+ }
+
+ public static final class BottomAppBarState.Companion {
+ method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.BottomAppBarState,?> getSaver();
+ property public final androidx.compose.runtime.saveable.Saver<androidx.compose.material3.BottomAppBarState,?> Saver;
+ }
+
@SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class BottomSheetDefaults {
method @androidx.compose.runtime.Composable public void DragHandle(optional androidx.compose.ui.Modifier modifier, optional float width, optional float height, optional androidx.compose.ui.graphics.Shape shape, optional long color);
method @androidx.compose.runtime.Composable public long getContainerColor();
@@ -677,9 +715,11 @@
public static final class FabPosition.Companion {
method public int getCenter();
method public int getEnd();
+ method public int getEndOverlay();
method public int getStart();
property public final int Center;
property public final int End;
+ property public final int EndOverlay;
property public final int Start;
}
@@ -829,6 +869,10 @@
property @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static final androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> LocalMinimumTouchTargetEnforcement;
}
+ public final class LabelKt {
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void Label(kotlin.jvm.functions.Function0<kotlin.Unit> label, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional boolean isPersistent, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ }
+
@androidx.compose.runtime.Immutable public final class ListItemColors {
ctor public ListItemColors(long containerColor, long headlineColor, long leadingIconColor, long overlineColor, long supportingTextColor, long trailingIconColor, long disabledHeadlineColor, long disabledLeadingIconColor, long disabledTrailingIconColor);
method public long getContainerColor();
diff --git a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
index c6d720e..2e8c6a2 100644
--- a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
+++ b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
@@ -57,6 +57,7 @@
import androidx.compose.material3.samples.ElevatedFilterChipSample
import androidx.compose.material3.samples.ElevatedSuggestionChipSample
import androidx.compose.material3.samples.EnterAlwaysTopAppBar
+import androidx.compose.material3.samples.ExitAlwaysBottomAppBar
import androidx.compose.material3.samples.ExitUntilCollapsedLargeTopAppBar
import androidx.compose.material3.samples.ExitUntilCollapsedMediumTopAppBar
import androidx.compose.material3.samples.ExposedDropdownMenuSample
@@ -465,7 +466,12 @@
name = ::BottomAppBarWithFAB.name,
description = BottomAppBarsExampleDescription,
sourceUrl = BottomAppBarsExampleSourceUrl,
- ) { BottomAppBarWithFAB() }
+ ) { BottomAppBarWithFAB() },
+ Example(
+ name = ::ExitAlwaysBottomAppBar.name,
+ description = BottomAppBarsExampleDescription,
+ sourceUrl = BottomAppBarsExampleSourceUrl,
+ ) { ExitAlwaysBottomAppBar() }
)
private const val TopAppBarExampleDescription = "Top app bar examples"
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/AppBarSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/AppBarSamples.kt
index 6cbc0aa..a6538f0 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/AppBarSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/AppBarSamples.kt
@@ -19,6 +19,7 @@
import androidx.annotation.Sampled
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
@@ -31,6 +32,7 @@
import androidx.compose.material3.BottomAppBarDefaults
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.FabPosition
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.FloatingActionButtonDefaults
import androidx.compose.material3.Icon
@@ -418,11 +420,13 @@
@Sampled
@Composable
fun SimpleBottomAppBar() {
- BottomAppBar {
- IconButton(onClick = { /* doSomething() */ }) {
- Icon(Icons.Filled.Menu, contentDescription = "Localized description")
+ BottomAppBar(
+ actions = {
+ IconButton(onClick = { /* doSomething() */ }) {
+ Icon(Icons.Filled.Menu, contentDescription = "Localized description")
+ }
}
- }
+ )
}
@Preview
@@ -452,3 +456,59 @@
}
)
}
+
+/**
+ * A sample for a [BottomAppBar] that collapses when the content is scrolled up, and
+ * appears when the content scrolled down.
+ */
+@OptIn(ExperimentalMaterial3Api::class)
+@Preview
+@Sampled
+@Composable
+fun ExitAlwaysBottomAppBar() {
+ val scrollBehavior = BottomAppBarDefaults.exitAlwaysScrollBehavior()
+ Scaffold(
+ modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
+ bottomBar = {
+ BottomAppBar(
+ actions = {
+ IconButton(onClick = { /* doSomething() */ }) {
+ Icon(Icons.Filled.Check, contentDescription = "Localized description")
+ }
+ IconButton(onClick = { /* doSomething() */ }) {
+ Icon(Icons.Filled.Edit, contentDescription = "Localized description")
+ }
+ },
+ scrollBehavior = scrollBehavior
+ )
+ },
+ floatingActionButton = {
+ FloatingActionButton(
+ modifier = Modifier.offset(y = 4.dp),
+ onClick = { /* do something */ },
+ containerColor = BottomAppBarDefaults.bottomAppBarFabColor,
+ elevation = FloatingActionButtonDefaults.bottomAppBarFabElevation()
+ ) {
+ Icon(Icons.Filled.Add, "Localized description")
+ }
+ },
+ floatingActionButtonPosition = FabPosition.EndOverlay,
+ content = { innerPadding ->
+ LazyColumn(
+ contentPadding = innerPadding,
+ verticalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ val list = (0..75).map { it.toString() }
+ items(count = list.size) {
+ Text(
+ text = list[it],
+ style = MaterialTheme.typography.bodyLarge,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 16.dp)
+ )
+ }
+ }
+ }
+ )
+}
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SliderSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SliderSamples.kt
index 77929ef..2528da4 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SliderSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SliderSamples.kt
@@ -19,12 +19,16 @@
import androidx.annotation.Sampled
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
+import androidx.compose.material3.Label
+import androidx.compose.material3.PlainTooltip
import androidx.compose.material3.RangeSlider
import androidx.compose.material3.RangeSliderState
import androidx.compose.material3.Slider
@@ -41,6 +45,8 @@
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import kotlin.math.roundToInt
@Preview
@Sampled
@@ -56,7 +62,6 @@
}
}
-@OptIn(ExperimentalMaterial3Api::class)
@Preview
@Sampled
@Composable
@@ -84,25 +89,40 @@
@Composable
fun SliderWithCustomThumbSample() {
var sliderPosition by remember { mutableStateOf(0f) }
+ val interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
Column {
- Text(text = sliderPosition.toString())
Slider(
modifier = Modifier.semantics { contentDescription = "Localized Description" },
value = sliderPosition,
onValueChange = { sliderPosition = it },
- valueRange = 0f..5f,
- steps = 10,
+ valueRange = 0f..100f,
+ interactionSource = interactionSource,
onValueChangeFinished = {
// launch some business logic update with the state you hold
// viewModel.updateSelectedSliderValue(sliderPosition)
},
thumb = {
- Icon(
- imageVector = Icons.Filled.Favorite,
- contentDescription = null,
- modifier = Modifier.size(ButtonDefaults.IconSize),
- tint = Color.Red
- )
+ Label(
+ label = {
+ PlainTooltip(
+ modifier = Modifier
+ .requiredSize(45.dp, 25.dp)
+ .wrapContentWidth()
+ ) {
+ val roundedEnd =
+ (sliderPosition * 100.0).roundToInt() / 100.0
+ Text(roundedEnd.toString())
+ }
+ },
+ interactionSource = interactionSource
+ ) {
+ Icon(
+ imageVector = Icons.Filled.Favorite,
+ contentDescription = null,
+ modifier = Modifier.size(ButtonDefaults.IconSize),
+ tint = Color.Red
+ )
+ }
}
)
}
@@ -221,23 +241,52 @@
)
val endThumbColors = SliderDefaults.colors(thumbColor = Color.Green)
Column {
- Text(text = (rangeSliderState.activeRangeStart..rangeSliderState.activeRangeEnd).toString())
RangeSlider(
state = rangeSliderState,
modifier = Modifier.semantics { contentDescription = "Localized Description" },
startInteractionSource = startInteractionSource,
endInteractionSource = endInteractionSource,
startThumb = {
- SliderDefaults.Thumb(
- interactionSource = startInteractionSource,
- colors = startThumbAndTrackColors
- )
+ Label(
+ label = {
+ PlainTooltip(
+ modifier = Modifier
+ .requiredSize(45.dp, 25.dp)
+ .wrapContentWidth()
+ ) {
+ val roundedStart =
+ (rangeSliderState.activeRangeStart * 100.0).roundToInt() / 100.0
+ Text(roundedStart.toString())
+ }
+ },
+ interactionSource = startInteractionSource
+ ) {
+ SliderDefaults.Thumb(
+ interactionSource = startInteractionSource,
+ colors = startThumbAndTrackColors
+ )
+ }
},
endThumb = {
- SliderDefaults.Thumb(
- interactionSource = endInteractionSource,
- colors = endThumbColors
- )
+ Label(
+ label = {
+ PlainTooltip(
+ modifier = Modifier
+ .requiredSize(45.dp, 25.dp)
+ .wrapContentWidth()
+ ) {
+ val roundedEnd =
+ (rangeSliderState.activeRangeEnd * 100.0).roundToInt() / 100.0
+ Text(roundedEnd.toString())
+ }
+ },
+ interactionSource = endInteractionSource
+ ) {
+ SliderDefaults.Thumb(
+ interactionSource = endInteractionSource,
+ colors = endThumbColors
+ )
+ }
},
track = { rangeSliderState ->
SliderDefaults.Track(
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/AppBarScreenshotTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/AppBarScreenshotTest.kt
index ddbb438..7b8d1a5 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/AppBarScreenshotTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/AppBarScreenshotTest.kt
@@ -24,6 +24,7 @@
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.Menu
+import androidx.compose.material3.BottomAppBarDefaults.bottomAppBarFabColor
import androidx.compose.material3.TopAppBarDefaults.enterAlwaysScrollBehavior
import androidx.compose.material3.tokens.TopAppBarSmallTokens
import androidx.compose.testutils.assertAgainstGolden
@@ -361,7 +362,7 @@
floatingActionButton = {
FloatingActionButton(
onClick = { /* do something */ },
- containerColor = BottomAppBarDefaults.bottomAppBarFabColor,
+ containerColor = bottomAppBarFabColor,
elevation = FloatingActionButtonDefaults.bottomAppBarFabElevation()
) {
Icon(Icons.Filled.Add, "Localized description")
@@ -393,7 +394,7 @@
floatingActionButton = {
FloatingActionButton(
onClick = { /* do something */ },
- containerColor = BottomAppBarDefaults.bottomAppBarFabColor,
+ containerColor = bottomAppBarFabColor,
elevation = FloatingActionButtonDefaults.bottomAppBarFabElevation()
) {
Icon(Icons.Filled.Add, "Localized description")
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/AppBarTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/AppBarTest.kt
index ef6ffe4..a913ceb 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/AppBarTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/AppBarTest.kt
@@ -23,6 +23,7 @@
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
@@ -1183,6 +1184,105 @@
.assertTopPositionInRootIsEqualTo(12.dp)
}
+ @Test
+ fun bottomAppBar_exitAlways_scaffoldWithFAB_default_positioning() {
+ rule.setMaterialContent(lightColorScheme()) {
+ val scrollBehavior = BottomAppBarDefaults.exitAlwaysScrollBehavior()
+ Scaffold(
+ modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
+ bottomBar = {
+ BottomAppBar(
+ modifier = Modifier.testTag(BottomAppBarTestTag),
+ scrollBehavior = scrollBehavior
+ ) {}
+ },
+ floatingActionButton = {
+ FloatingActionButton(
+ modifier = Modifier
+ .testTag("FAB")
+ .offset(y = 4.dp),
+ onClick = { /* do something */ },
+ ) {}
+ },
+ floatingActionButtonPosition = FabPosition.EndOverlay
+ ) {}
+ }
+
+ val appBarBounds = rule.onNodeWithTag(BottomAppBarTestTag).getUnclippedBoundsInRoot()
+ val fabBounds = rule.onNodeWithTag("FAB").getUnclippedBoundsInRoot()
+ rule.onNodeWithTag("FAB")
+ // FAB should be 16.dp from the end
+ .assertLeftPositionInRootIsEqualTo(appBarBounds.width - 16.dp - fabBounds.width)
+ // FAB should be 12.dp from the bottom
+ .assertTopPositionInRootIsEqualTo(rule.rootHeight() - 12.dp - fabBounds.height)
+ }
+
+ @Test
+ fun bottomAppBar_exitAlways_scaffoldWithFAB_scrolled_positioning() {
+ lateinit var scrollBehavior: BottomAppBarScrollBehavior
+ val scrollHeightOffsetDp = 20.dp
+ var scrollHeightOffsetPx = 0f
+
+ rule.setMaterialContent(lightColorScheme()) {
+ scrollBehavior = BottomAppBarDefaults.exitAlwaysScrollBehavior()
+ scrollHeightOffsetPx = with(LocalDensity.current) { scrollHeightOffsetDp.toPx() }
+ Scaffold(
+ modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
+ bottomBar = {
+ BottomAppBar(
+ modifier = Modifier.testTag(BottomAppBarTestTag),
+ scrollBehavior = scrollBehavior
+ ) {}
+ },
+ floatingActionButton = {
+ FloatingActionButton(
+ modifier = Modifier
+ .testTag("FAB")
+ .offset(y = 4.dp),
+ onClick = { /* do something */ },
+ ) {}
+ },
+ floatingActionButtonPosition = FabPosition.EndOverlay
+ ) {}
+ }
+
+ // Simulate scrolled content.
+ rule.runOnIdle {
+ scrollBehavior.state.heightOffset = -scrollHeightOffsetPx
+ scrollBehavior.state.contentOffset = -scrollHeightOffsetPx
+ }
+ rule.waitForIdle()
+ rule.onNodeWithTag(BottomAppBarTestTag)
+ .assertHeightIsEqualTo(BottomAppBarTokens.ContainerHeight - scrollHeightOffsetDp)
+
+ val appBarBounds = rule.onNodeWithTag(BottomAppBarTestTag).getUnclippedBoundsInRoot()
+ val fabBounds = rule.onNodeWithTag("FAB").getUnclippedBoundsInRoot()
+ rule.onNodeWithTag("FAB")
+ // FAB should be 16.dp from the end
+ .assertLeftPositionInRootIsEqualTo(appBarBounds.width - 16.dp - fabBounds.width)
+ // FAB should be 12.dp from the bottom
+ .assertTopPositionInRootIsEqualTo(rule.rootHeight() - 12.dp - fabBounds.height)
+ }
+
+ @Test
+ fun bottomAppBar_exitAlways_allowHorizontalScroll() {
+ lateinit var state: LazyListState
+ rule.setMaterialContent(lightColorScheme()) {
+ state = rememberLazyListState()
+ MultiPageContent(BottomAppBarDefaults.exitAlwaysScrollBehavior(), state)
+ }
+
+ rule.onNodeWithTag(LazyListTag).performTouchInput { swipeLeft() }
+ rule.runOnIdle {
+ assertThat(state.firstVisibleItemIndex).isEqualTo(1)
+ }
+
+ rule.onNodeWithTag(LazyListTag).performTouchInput { swipeRight() }
+ rule.runOnIdle {
+ assertThat(state.firstVisibleItemIndex).isEqualTo(0)
+ }
+ }
+
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun MultiPageContent(scrollBehavior: TopAppBarScrollBehavior, state: LazyListState) {
@@ -1218,6 +1318,39 @@
}
}
+ @Composable
+ private fun MultiPageContent(scrollBehavior: BottomAppBarScrollBehavior, state: LazyListState) {
+ Scaffold(
+ modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
+ bottomBar = {
+ BottomAppBar(
+ modifier = Modifier.testTag(BottomAppBarTestTag),
+ scrollBehavior = scrollBehavior
+ ) {}
+ }
+ ) { contentPadding ->
+ LazyRow(
+ Modifier
+ .fillMaxSize()
+ .testTag(LazyListTag), state
+ ) {
+ items(2) { page ->
+ LazyColumn(
+ modifier = Modifier.fillParentMaxSize(),
+ contentPadding = contentPadding
+ ) {
+ items(50) {
+ Text(
+ modifier = Modifier.fillParentMaxWidth(),
+ text = "Item #$page x $it"
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+
/**
* Checks the app bar's components positioning when it's a [TopAppBar], a
* [CenterAlignedTopAppBar], or a larger app bar that is scrolled up and collapsed into a small
@@ -1583,7 +1716,8 @@
(TopAppBarSmallTokens.ContainerHeight - FakeIconSize) / 2
private val LazyListTag = "lazyList"
- private val TopAppBarTestTag = "bar"
+ private val TopAppBarTestTag = "topAppBar"
+ private val BottomAppBarTestTag = "bottomAppBar"
private val NavigationIconTestTag = "navigationIcon"
private val TitleTestTag = "title"
private val ActionsTestTag = "actions"
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt
index 85a2ddc..6d2d0b7 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt
@@ -144,6 +144,42 @@
}
@Test
+ fun modalBottomSheet_isDismissedOnSwipeDown() {
+ var showBottomSheet by mutableStateOf(true)
+ val sheetState = SheetState(skipPartiallyExpanded = false, density = rule.density)
+
+ rule.setContent {
+ val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled)
+ WindowInsets(0) else BottomSheetDefaults.windowInsets
+
+ if (showBottomSheet) {
+ ModalBottomSheet(
+ sheetState = sheetState,
+ onDismissRequest = { showBottomSheet = false },
+ windowInsets = windowInsets
+ ) {
+ Box(
+ Modifier
+ .size(sheetHeight)
+ .testTag(sheetTag)
+ )
+ }
+ }
+ }
+
+ assertThat(sheetState.isVisible).isTrue()
+
+ // Swipe Down
+ rule.onNodeWithTag(sheetTag).performTouchInput {
+ swipeDown()
+ }
+ rule.waitForIdle()
+
+ // Bottom sheet should not exist
+ rule.onNodeWithTag(sheetTag).assertDoesNotExist()
+ }
+
+ @Test
fun modalBottomSheet_fillsScreenWidth() {
var boxWidth = 0
var screenWidth by mutableStateOf(0)
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/SliderScreenshotTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/SliderScreenshotTest.kt
index 36514dd..883e39b 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/SliderScreenshotTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/SliderScreenshotTest.kt
@@ -216,8 +216,8 @@
assertSliderAgainstGolden("slider_customColors_disabled")
}
+ @OptIn(ExperimentalMaterial3Api::class)
@Test
- @ExperimentalMaterial3Api
fun rangeSliderTest_middle_steps_disabled() {
rule.setMaterialContent(lightColorScheme()) {
Box(wrap.testTag(wrapperTestTag)) {
@@ -232,8 +232,8 @@
assertSliderAgainstGolden("rangeSlider_middle_steps_disabled")
}
+ @OptIn(ExperimentalMaterial3Api::class)
@Test
- @ExperimentalMaterial3Api
fun rangeSliderTest_middle_steps_enabled() {
rule.setMaterialContent(lightColorScheme()) {
Box(wrap.testTag(wrapperTestTag)) {
@@ -251,8 +251,8 @@
assertSliderAgainstGolden("rangeSlider_middle_steps_enabled")
}
+ @OptIn(ExperimentalMaterial3Api::class)
@Test
- @ExperimentalMaterial3Api
fun rangeSliderTest_middle_steps_dark_enabled() {
rule.setMaterialContent(darkColorScheme()) {
Box(wrap.testTag(wrapperTestTag)) {
@@ -270,8 +270,8 @@
assertSliderAgainstGolden("rangeSlider_middle_steps_dark_enabled")
}
+ @OptIn(ExperimentalMaterial3Api::class)
@Test
- @ExperimentalMaterial3Api
fun rangeSliderTest_middle_steps_dark_disabled() {
rule.setMaterialContent(darkColorScheme()) {
Box(wrap.testTag(wrapperTestTag)) {
@@ -286,8 +286,8 @@
assertSliderAgainstGolden("rangeSlider_middle_steps_dark_disabled")
}
+ @OptIn(ExperimentalMaterial3Api::class)
@Test
- @ExperimentalMaterial3Api
fun rangeSliderTest_overlapingThumbs() {
rule.setMaterialContent(lightColorScheme()) {
Box(wrap.testTag(wrapperTestTag)) {
@@ -301,8 +301,8 @@
assertSliderAgainstGolden("rangeSlider_overlapingThumbs")
}
+ @OptIn(ExperimentalMaterial3Api::class)
@Test
- @ExperimentalMaterial3Api
fun rangeSliderTest_fullRange() {
rule.setMaterialContent(lightColorScheme()) {
Box(wrap.testTag(wrapperTestTag)) {
@@ -316,8 +316,8 @@
assertSliderAgainstGolden("rangeSlider_fullRange")
}
+ @OptIn(ExperimentalMaterial3Api::class)
@Test
- @ExperimentalMaterial3Api
fun rangeSliderTest_steps_customColors() {
rule.setMaterialContent(lightColorScheme()) {
Box(wrap.testTag(wrapperTestTag)) {
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ModalBottomSheet.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ModalBottomSheet.android.kt
index 85e41b3..771d739 100644
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ModalBottomSheet.android.kt
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ModalBottomSheet.android.kt
@@ -28,6 +28,7 @@
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.gestures.draggable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
@@ -198,10 +199,12 @@
)
}
)
- .anchoredDraggable(
- state = sheetState.anchoredDraggableState,
+ .draggable(
+ state = sheetState.anchoredDraggableState.draggableState,
orientation = Orientation.Vertical,
- enabled = sheetState.isVisible
+ enabled = sheetState.isVisible,
+ startDragImmediately = sheetState.anchoredDraggableState.isAnimationRunning,
+ onDragStopped = { settleToDismiss(it) }
)
.modalBottomSheetAnchors(
sheetState = sheetState,
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt
index cd79a42..7d8c9f6 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt
@@ -73,6 +73,7 @@
import androidx.compose.ui.layout.AlignmentLine
import androidx.compose.ui.layout.LastBaseline
import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.layout
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.semantics.clearAndSetSemantics
@@ -391,6 +392,7 @@
* @param contentPadding the padding applied to the content of this BottomAppBar
* @param windowInsets a window insets that app bar will respect.
*/
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun BottomAppBar(
actions: @Composable RowScope.() -> Unit,
@@ -402,12 +404,76 @@
contentPadding: PaddingValues = BottomAppBarDefaults.ContentPadding,
windowInsets: WindowInsets = BottomAppBarDefaults.windowInsets,
) = BottomAppBar(
+ actions = actions,
+ modifier = modifier,
+ floatingActionButton = floatingActionButton,
+ containerColor = containerColor,
+ contentColor = contentColor,
+ tonalElevation = tonalElevation,
+ contentPadding = contentPadding,
+ windowInsets = windowInsets,
+ scrollBehavior = null
+)
+
+/**
+ * <a href="https://m3.material.io/components/bottom-app-bar/overview" class="external" target="_blank">Material Design bottom app bar</a>.
+ *
+ * A bottom app bar displays navigation and key actions at the bottom of mobile screens.
+ *
+ * ![Bottom app bar image](https://developer.android.com/images/reference/androidx/compose/material3/bottom-app-bar.png)
+ *
+ * @sample androidx.compose.material3.samples.SimpleBottomAppBar
+ *
+ * It can optionally display a [FloatingActionButton] embedded at the end of the BottomAppBar.
+ *
+ * @sample androidx.compose.material3.samples.BottomAppBarWithFAB
+ *
+ * A bottom app bar that uses a [scrollBehavior] to customize its nested scrolling behavior when
+ * working in conjunction with a scrolling content looks like:
+ *
+ * @sample androidx.compose.material3.samples.ExitAlwaysBottomAppBar
+ *
+ * Also see [NavigationBar].
+ *
+ * @param actions the icon content of this BottomAppBar. The default layout here is a [Row],
+ * so content inside will be placed horizontally.
+ * @param modifier the [Modifier] to be applied to this BottomAppBar
+ * @param floatingActionButton optional floating action button at the end of this BottomAppBar
+ * @param containerColor the color used for the background of this BottomAppBar. Use
+ * [Color.Transparent] to have no color.
+ * @param contentColor the preferred color for content inside this BottomAppBar. Defaults to either
+ * the matching content color for [containerColor], or to the current [LocalContentColor] if
+ * [containerColor] is not a color from the theme.
+ * @param tonalElevation when [containerColor] is [ColorScheme.surface], a translucent primary color
+ * overlay is applied on top of the container. A higher tonal elevation value will result in a
+ * darker color in light theme and lighter color in dark theme. See also: [Surface].
+ * @param contentPadding the padding applied to the content of this BottomAppBar
+ * @param windowInsets a window insets that app bar will respect.
+ * @param scrollBehavior a [BottomAppBarScrollBehavior] which holds various offset values that will
+ * be applied by this bottom app bar to set up its height. A scroll behavior is designed to
+ * work in conjunction with a scrolled content to change the bottom app bar appearance as the
+ * content scrolls. See [BottomAppBarScrollBehavior.nestedScrollConnection].
+ */
+@ExperimentalMaterial3Api
+@Composable
+fun BottomAppBar(
+ actions: @Composable RowScope.() -> Unit,
+ modifier: Modifier = Modifier,
+ floatingActionButton: @Composable (() -> Unit)? = null,
+ containerColor: Color = BottomAppBarDefaults.containerColor,
+ contentColor: Color = contentColorFor(containerColor),
+ tonalElevation: Dp = BottomAppBarDefaults.ContainerElevation,
+ contentPadding: PaddingValues = BottomAppBarDefaults.ContentPadding,
+ windowInsets: WindowInsets = BottomAppBarDefaults.windowInsets,
+ scrollBehavior: BottomAppBarScrollBehavior? = null,
+) = BottomAppBar(
modifier = modifier,
containerColor = containerColor,
contentColor = contentColor,
tonalElevation = tonalElevation,
windowInsets = windowInsets,
- contentPadding = contentPadding
+ contentPadding = contentPadding,
+ scrollBehavior = scrollBehavior
) {
Row(
modifier = Modifier.weight(1f),
@@ -455,6 +521,7 @@
* @param content the content of this BottomAppBar. The default layout here is a [Row],
* so content inside will be placed horizontally.
*/
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun BottomAppBar(
modifier: Modifier = Modifier,
@@ -464,7 +531,81 @@
contentPadding: PaddingValues = BottomAppBarDefaults.ContentPadding,
windowInsets: WindowInsets = BottomAppBarDefaults.windowInsets,
content: @Composable RowScope.() -> Unit
+) = BottomAppBar(
+ modifier = modifier,
+ containerColor = containerColor,
+ contentColor = contentColor,
+ tonalElevation = tonalElevation,
+ contentPadding = contentPadding,
+ windowInsets = windowInsets,
+ scrollBehavior = null,
+ content = content
+)
+
+/**
+ * <a href="https://m3.material.io/components/bottom-app-bar/overview" class="external" target="_blank">Material Design bottom app bar</a>.
+ *
+ * A bottom app bar displays navigation and key actions at the bottom of mobile screens.
+ *
+ * ![Bottom app bar image](https://developer.android.com/images/reference/androidx/compose/material3/bottom-app-bar.png)
+ *
+ * If you are interested in displaying a [FloatingActionButton], consider using another overload.
+ *
+ * Also see [NavigationBar].
+ *
+ * @param modifier the [Modifier] to be applied to this BottomAppBar
+ * @param containerColor the color used for the background of this BottomAppBar. Use
+ * [Color.Transparent] to have no color.
+ * @param contentColor the preferred color for content inside this BottomAppBar. Defaults to either
+ * the matching content color for [containerColor], or to the current [LocalContentColor] if
+ * [containerColor] is not a color from the theme.
+ * @param tonalElevation when [containerColor] is [ColorScheme.surface], a translucent primary color
+ * overlay is applied on top of the container. A higher tonal elevation value will result in a
+ * darker color in light theme and lighter color in dark theme. See also: [Surface].
+ * @param contentPadding the padding applied to the content of this BottomAppBar
+ * @param windowInsets a window insets that app bar will respect.
+ * @param scrollBehavior a [BottomAppBarScrollBehavior] which holds various offset values that will
+ * be applied by this bottom app bar to set up its height. A scroll behavior is designed to
+ * work in conjunction with a scrolled content to change the bottom app bar appearance as the
+ * content scrolls. See [BottomAppBarScrollBehavior.nestedScrollConnection].
+ * @param content the content of this BottomAppBar. The default layout here is a [Row],
+ * so content inside will be placed horizontally.
+ */
+@ExperimentalMaterial3Api
+@Composable
+fun BottomAppBar(
+ modifier: Modifier = Modifier,
+ containerColor: Color = BottomAppBarDefaults.containerColor,
+ contentColor: Color = contentColorFor(containerColor),
+ tonalElevation: Dp = BottomAppBarDefaults.ContainerElevation,
+ contentPadding: PaddingValues = BottomAppBarDefaults.ContentPadding,
+ windowInsets: WindowInsets = BottomAppBarDefaults.windowInsets,
+ scrollBehavior: BottomAppBarScrollBehavior? = null,
+ content: @Composable RowScope.() -> Unit
) {
+ // Set up support for resizing the bottom app bar when vertically dragging the bar itself.
+ val appBarDragModifier = if (scrollBehavior != null && !scrollBehavior.isPinned) {
+ Modifier.draggable(
+ orientation = Orientation.Vertical,
+ state = rememberDraggableState { delta ->
+ scrollBehavior.state.heightOffset -= delta
+ },
+ onDragStopped = { velocity ->
+ settleAppBarBottom(
+ scrollBehavior.state,
+ velocity,
+ scrollBehavior.flingAnimationSpec,
+ scrollBehavior.snapAnimationSpec
+ )
+ }
+ )
+ } else {
+ Modifier
+ }
+
+ // Compose a Surface with a Row content.
+ // The height of the app bar is determined by subtracting the bar's height offset from the
+ // app bar's defined constant height value (i.e. the ContainerHeight token).
Surface(
color = containerColor,
contentColor = contentColor,
@@ -472,6 +613,19 @@
// TODO(b/209583788): Consider adding a shape parameter if updated design guidance allows
shape = BottomAppBarTokens.ContainerShape.value,
modifier = modifier
+ .layout { measurable, constraints ->
+ // Sets the app bar's height offset to collapse the entire bar's height when content
+ // is scrolled.
+ scrollBehavior?.state?.heightOffsetLimit =
+ -BottomAppBarTokens.ContainerHeight.toPx()
+
+ val placeable = measurable.measure(constraints)
+ val height = placeable.height + (scrollBehavior?.state?.heightOffset ?: 0f)
+ layout(placeable.width, height.roundToInt()) {
+ placeable.place(0, 0)
+ }
+ }
+ .then(appBarDragModifier)
) {
Row(
Modifier
@@ -979,6 +1133,50 @@
}
}
+/**
+ * A BottomAppBarScrollBehavior defines how a bottom app bar should behave when the content under
+ * it is scrolled.
+ *
+ * @see [BottomAppBarDefaults.exitAlwaysScrollBehavior]
+ */
+@ExperimentalMaterial3Api
+@Stable
+interface BottomAppBarScrollBehavior {
+
+ /**
+ * A [BottomAppBarState] that is attached to this behavior and is read and updated when
+ * scrolling happens.
+ */
+ val state: BottomAppBarState
+
+ /**
+ * Indicates whether the bottom app bar is pinned.
+ *
+ * A pinned app bar will stay fixed in place when content is scrolled and will not react to any
+ * drag gestures.
+ */
+ val isPinned: Boolean
+
+ /**
+ * An optional [AnimationSpec] that defines how the bottom app bar snaps to either fully
+ * collapsed or fully extended state when a fling or a drag scrolled it into an intermediate
+ * position.
+ */
+ val snapAnimationSpec: AnimationSpec<Float>?
+
+ /**
+ * An optional [DecayAnimationSpec] that defined how to fling the bottom app bar when the user
+ * flings the app bar itself, or the content below it.
+ */
+ val flingAnimationSpec: DecayAnimationSpec<Float>?
+
+ /**
+ * A [NestedScrollConnection] that should be attached to a [Modifier.nestedScroll] in order to
+ * keep track of the scroll events.
+ */
+ val nestedScrollConnection: NestedScrollConnection
+}
+
/** Contains default values used for the bottom app bar implementations. */
object BottomAppBarDefaults {
@@ -1012,6 +1210,287 @@
val bottomAppBarFabColor: Color
@Composable get() =
FabSecondaryTokens.ContainerColor.value
+
+ /**
+ * Returns a [BottomAppBarScrollBehavior]. A bottom app bar that is set up with this
+ * [BottomAppBarScrollBehavior] will immediately collapse when the content is pulled up, and
+ * will immediately appear when the content is pulled down.
+ *
+ * @param state the state object to be used to control or observe the bottom app bar's scroll
+ * state. See [rememberBottomAppBarState] for a state that is remembered across compositions.
+ * @param canScroll a callback used to determine whether scroll events are to be
+ * handled by this [ExitAlwaysScrollBehavior]
+ * @param snapAnimationSpec an optional [AnimationSpec] that defines how the bottom app bar
+ * snaps to either fully collapsed or fully extended state when a fling or a drag scrolled it
+ * into an intermediate position
+ * @param flingAnimationSpec an optional [DecayAnimationSpec] that defined how to fling the
+ * bottom app bar when the user flings the app bar itself, or the content below it
+ */
+ @ExperimentalMaterial3Api
+ @Composable
+ fun exitAlwaysScrollBehavior(
+ state: BottomAppBarState = rememberBottomAppBarState(),
+ canScroll: () -> Boolean = { true },
+ snapAnimationSpec: AnimationSpec<Float>? = spring(stiffness = Spring.StiffnessMediumLow),
+ flingAnimationSpec: DecayAnimationSpec<Float>? = rememberSplineBasedDecay()
+ ): BottomAppBarScrollBehavior =
+ ExitAlwaysScrollBehavior(
+ state = state,
+ snapAnimationSpec = snapAnimationSpec,
+ flingAnimationSpec = flingAnimationSpec,
+ canScroll = canScroll
+ )
+}
+
+/**
+ * Creates a [BottomAppBarState] that is remembered across compositions.
+ *
+ * @param initialHeightOffsetLimit the initial value for [BottomAppBarState.heightOffsetLimit],
+ * which represents the pixel limit that a bottom app bar is allowed to collapse when the scrollable
+ * content is scrolled
+ * @param initialHeightOffset the initial value for [BottomAppBarState.heightOffset]. The initial
+ * offset height offset should be between zero and [initialHeightOffsetLimit].
+ * @param initialContentOffset the initial value for [BottomAppBarState.contentOffset]
+ */
+@ExperimentalMaterial3Api
+@Composable
+fun rememberBottomAppBarState(
+ initialHeightOffsetLimit: Float = -Float.MAX_VALUE,
+ initialHeightOffset: Float = 0f,
+ initialContentOffset: Float = 0f
+): BottomAppBarState {
+ return rememberSaveable(saver = BottomAppBarState.Saver) {
+ BottomAppBarState(
+ initialHeightOffsetLimit,
+ initialHeightOffset,
+ initialContentOffset
+ )
+ }
+}
+
+/**
+ * A state object that can be hoisted to control and observe the bottom app bar state. The state is
+ * read and updated by a [BottomAppBarScrollBehavior] implementation.
+ *
+ * In most cases, this state will be created via [rememberBottomAppBarState].
+ */
+@ExperimentalMaterial3Api
+interface BottomAppBarState {
+
+ /**
+ * The bottom app bar's height offset limit in pixels, which represents the limit that a bottom
+ * app bar is allowed to collapse to.
+ *
+ * Use this limit to coerce the [heightOffset] value when it's updated.
+ */
+ var heightOffsetLimit: Float
+
+ /**
+ * The bottom app bar's current height offset in pixels. This height offset is applied to the
+ * fixed height of the app bar to control the displayed height when content is being scrolled.
+ *
+ * Updates to the [heightOffset] value are coerced between zero and [heightOffsetLimit].
+ */
+ var heightOffset: Float
+
+ /**
+ * The total offset of the content scrolled under the bottom app bar.
+ *
+ * This value is updated by a [BottomAppBarScrollBehavior] whenever a nested scroll connection
+ * consumes scroll events. A common implementation would update the value to be the sum of all
+ * [NestedScrollConnection.onPostScroll] `consumed.y` values.
+ */
+ var contentOffset: Float
+
+ /**
+ * A value that represents the collapsed height percentage of the app bar.
+ *
+ * A `0.0` represents a fully expanded bar, and `1.0` represents a fully collapsed bar (computed
+ * as [heightOffset] / [heightOffsetLimit]).
+ */
+ val collapsedFraction: Float
+
+ companion object {
+ /**
+ * The default [Saver] implementation for [BottomAppBarState].
+ */
+ val Saver: Saver<BottomAppBarState, *> = listSaver(
+ save = { listOf(it.heightOffsetLimit, it.heightOffset, it.contentOffset) },
+ restore = {
+ BottomAppBarState(
+ initialHeightOffsetLimit = it[0],
+ initialHeightOffset = it[1],
+ initialContentOffset = it[2]
+ )
+ }
+ )
+ }
+}
+
+/**
+ * Creates a [BottomAppBarState].
+ *
+ * @param initialHeightOffsetLimit the initial value for [BottomAppBarState.heightOffsetLimit],
+ * which represents the pixel limit that a bottom app bar is allowed to collapse when the scrollable
+ * content is scrolled
+ * @param initialHeightOffset the initial value for [BottomAppBarState.heightOffset]. The initial
+ * offset height offset should be between zero and [initialHeightOffsetLimit].
+ * @param initialContentOffset the initial value for [BottomAppBarState.contentOffset]
+ */
+@ExperimentalMaterial3Api
+fun BottomAppBarState(
+ initialHeightOffsetLimit: Float,
+ initialHeightOffset: Float,
+ initialContentOffset: Float
+): BottomAppBarState = BottomAppBarStateImpl(
+ initialHeightOffsetLimit,
+ initialHeightOffset,
+ initialContentOffset
+)
+
+@ExperimentalMaterial3Api
+@Stable
+private class BottomAppBarStateImpl(
+ initialHeightOffsetLimit: Float,
+ initialHeightOffset: Float,
+ initialContentOffset: Float
+) : BottomAppBarState {
+
+ override var heightOffsetLimit by mutableFloatStateOf(initialHeightOffsetLimit)
+
+ override var heightOffset: Float
+ get() = _heightOffset.floatValue
+ set(newOffset) {
+ _heightOffset.floatValue = newOffset.coerceIn(
+ minimumValue = heightOffsetLimit,
+ maximumValue = 0f
+ )
+ }
+
+ override var contentOffset by mutableFloatStateOf(initialContentOffset)
+
+ override val collapsedFraction: Float
+ get() = if (heightOffsetLimit != 0f) {
+ heightOffset / heightOffsetLimit
+ } else {
+ 0f
+ }
+
+ private var _heightOffset = mutableFloatStateOf(initialHeightOffset)
+}
+
+/**
+ * A [BottomAppBarScrollBehavior] that adjusts its properties to affect the colors and height of a
+ * bottom app bar.
+ *
+ * A bottom app bar that is set up with this [BottomAppBarScrollBehavior] will immediately collapse
+ * when the nested content is pulled up, and will immediately appear when the content is pulled
+ * down.
+ *
+ * @param state a [BottomAppBarState]
+ * @param snapAnimationSpec an optional [AnimationSpec] that defines how the bottom app bar snaps to
+ * either fully collapsed or fully extended state when a fling or a drag scrolled it into an
+ * intermediate position
+ * @param flingAnimationSpec an optional [DecayAnimationSpec] that defined how to fling the bottom
+ * app bar when the user flings the app bar itself, or the content below it
+ * @param canScroll a callback used to determine whether scroll events are to be
+ * handled by this [ExitAlwaysScrollBehavior]
+ */
+@ExperimentalMaterial3Api
+private class ExitAlwaysScrollBehavior(
+ override val state: BottomAppBarState,
+ override val snapAnimationSpec: AnimationSpec<Float>?,
+ override val flingAnimationSpec: DecayAnimationSpec<Float>?,
+ val canScroll: () -> Boolean = { true }
+) : BottomAppBarScrollBehavior {
+ override val isPinned: Boolean = false
+ override var nestedScrollConnection =
+ object : NestedScrollConnection {
+ override fun onPostScroll(
+ consumed: Offset,
+ available: Offset,
+ source: NestedScrollSource
+ ): Offset {
+ if (!canScroll()) return Offset.Zero
+ state.contentOffset += consumed.y
+ if (state.heightOffset == 0f || state.heightOffset == state.heightOffsetLimit) {
+ if (consumed.y == 0f && available.y > 0f) {
+ // Reset the total content offset to zero when scrolling all the way down.
+ // This will eliminate some float precision inaccuracies.
+ state.contentOffset = 0f
+ }
+ }
+ state.heightOffset = state.heightOffset + consumed.y
+ return Offset.Zero
+ }
+
+ override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
+ val superConsumed = super.onPostFling(consumed, available)
+ return superConsumed + settleAppBarBottom(
+ state,
+ available.y,
+ flingAnimationSpec,
+ snapAnimationSpec
+ )
+ }
+ }
+}
+
+/**
+ * Settles the app bar by flinging, in case the given velocity is greater than zero, and snapping
+ * after the fling settles.
+ */
+@ExperimentalMaterial3Api
+private suspend fun settleAppBarBottom(
+ state: BottomAppBarState,
+ velocity: Float,
+ flingAnimationSpec: DecayAnimationSpec<Float>?,
+ snapAnimationSpec: AnimationSpec<Float>?
+): Velocity {
+ // Check if the app bar is completely collapsed/expanded. If so, no need to settle the app bar,
+ // and just return Zero Velocity.
+ // Note that we don't check for 0f due to float precision with the collapsedFraction
+ // calculation.
+ if (state.collapsedFraction < 0.01f || state.collapsedFraction == 1f) {
+ return Velocity.Zero
+ }
+ var remainingVelocity = velocity
+ // In case there is an initial velocity that was left after a previous user fling, animate to
+ // continue the motion to expand or collapse the app bar.
+ if (flingAnimationSpec != null && abs(velocity) > 1f) {
+ var lastValue = 0f
+ AnimationState(
+ initialValue = 0f,
+ initialVelocity = velocity,
+ )
+ .animateDecay(flingAnimationSpec) {
+ val delta = value - lastValue
+ val initialHeightOffset = state.heightOffset
+ state.heightOffset = initialHeightOffset + delta
+ val consumed = abs(initialHeightOffset - state.heightOffset)
+ lastValue = value
+ remainingVelocity = this.velocity
+ // avoid rounding errors and stop if anything is unconsumed
+ if (abs(delta - consumed) > 0.5f) this.cancelAnimation()
+ }
+ }
+ // Snap if animation specs were provided.
+ if (snapAnimationSpec != null) {
+ if (state.heightOffset < 0 &&
+ state.heightOffset > state.heightOffsetLimit
+ ) {
+ AnimationState(initialValue = state.heightOffset).animateTo(
+ if (state.collapsedFraction < 0.5f) {
+ 0f
+ } else {
+ state.heightOffsetLimit
+ },
+ animationSpec = snapAnimationSpec
+ ) { state.heightOffset = value }
+ }
+ }
+
+ return Velocity(0f, remainingVelocity)
}
// Padding minus IconButton's min touch target expansion
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Label.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Label.kt
new file mode 100644
index 0000000..1aedef2
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Label.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material3
+
+import androidx.compose.foundation.BasicTooltipBox
+import androidx.compose.foundation.BasicTooltipState
+import androidx.compose.foundation.MutatePriority
+import androidx.compose.foundation.MutatorMutex
+import androidx.compose.foundation.interaction.DragInteraction
+import androidx.compose.foundation.interaction.HoverInteraction
+import androidx.compose.foundation.interaction.Interaction
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.interaction.PressInteraction
+import androidx.compose.foundation.rememberBasicTooltipState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import kotlinx.coroutines.flow.collectLatest
+
+/**
+ * Label component that will append a [label] to [content].
+ * The positioning logic uses [TooltipDefaults.rememberPlainTooltipPositionProvider].
+ *
+ * Label appended to thumbs of Slider:
+ *
+ * @sample androidx.compose.material3.samples.SliderWithCustomThumbSample
+ *
+ * Label appended to thumbs of RangeSlider:
+ *
+ * @sample androidx.compose.material3.samples.RangeSliderWithCustomComponents
+ *
+ * @param label composable that will be appended to [content]
+ * @param modifier [Modifier] that will be applied to [content]
+ * @param interactionSource the [MutableInteractionSource] representing the
+ * stream of [Interaction]s for the [content].
+ * @param isPersistent boolean to determine if the label should be persistent.
+ * If true, then the label will always show and be anchored to [content].
+ * if false, then the label will only show when pressing down or hovering over the [content].
+ * @param content the composable that [label] will anchor to.
+ */
+@ExperimentalMaterial3Api
+@Composable
+fun Label(
+ label: @Composable () -> Unit,
+ modifier: Modifier = Modifier,
+ interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
+ isPersistent: Boolean = false,
+ content: @Composable () -> Unit
+) {
+ // Has the same positioning logic as PlainTooltips
+ val positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider()
+ val state = if (isPersistent)
+ remember { LabelStateImpl() }
+ else
+ rememberBasicTooltipState(mutatorMutex = MutatorMutex())
+ BasicTooltipBox(
+ positionProvider = positionProvider,
+ tooltip = label,
+ state = state,
+ modifier = modifier,
+ focusable = false,
+ enableUserInput = false,
+ content = content
+ )
+ HandleInteractions(
+ enabled = !isPersistent,
+ state = state,
+ interactionSource = interactionSource
+ )
+}
+
+@Composable
+private fun HandleInteractions(
+ enabled: Boolean,
+ state: BasicTooltipState,
+ interactionSource: MutableInteractionSource
+) {
+ if (enabled) {
+ LaunchedEffect(interactionSource) {
+ interactionSource.interactions.collectLatest { interaction ->
+ when (interaction) {
+ is PressInteraction.Press,
+ is DragInteraction.Start,
+ is HoverInteraction.Enter -> { state.show(MutatePriority.UserInput) }
+ is PressInteraction.Release,
+ is DragInteraction.Stop,
+ is HoverInteraction.Exit -> { state.dismiss() }
+ }
+ }
+ }
+ }
+}
+
+private class LabelStateImpl(
+ override val isVisible: Boolean = true,
+ override val isPersistent: Boolean = true
+) : BasicTooltipState {
+ override suspend fun show(mutatePriority: MutatePriority) {}
+
+ override fun dismiss() {}
+
+ override fun onDispose() {}
+}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Scaffold.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Scaffold.kt
index b346d44..fea69f4 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Scaffold.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Scaffold.kt
@@ -197,7 +197,7 @@
layoutWidth - FabSpacing.roundToPx() - fabWidth
}
}
- FabPosition.End -> {
+ FabPosition.End, FabPosition.EndOverlay -> {
if (layoutDirection == LayoutDirection.Ltr) {
layoutWidth - FabSpacing.roundToPx() - fabWidth
} else {
@@ -225,7 +225,7 @@
val bottomBarHeight = bottomBarPlaceables.fastMaxBy { it.height }?.height
val fabOffsetFromBottom = fabPlacement?.let {
- if (bottomBarHeight == null) {
+ if (bottomBarHeight == null || fabPosition == FabPosition.EndOverlay) {
it.height + FabSpacing.roundToPx() +
contentWindowInsets.getBottom(this@SubcomposeLayout)
} else {
@@ -329,13 +329,20 @@
* exists)
*/
val End = FabPosition(2)
+
+ /**
+ * Position FAB at the bottom of the screen at the end, overlaying the [NavigationBar] (if
+ * it exists)
+ */
+ val EndOverlay = FabPosition(3)
}
override fun toString(): String {
return when (this) {
Start -> "FabPosition.Start"
Center -> "FabPosition.Center"
- else -> "FabPosition.End"
+ End -> "FabPosition.End"
+ else -> "FabPosition.EndOverlay"
}
}
}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt
index 67bdfb5..21e04e7 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt
@@ -89,7 +89,7 @@
* relative to the anchor content.
* @param tooltip the composable that will be used to populate the tooltip's content.
* @param state handles the state of the tooltip's visibility.
- * @param modifier the [Modifier] to be applied to the tooltip.
+ * @param modifier the [Modifier] to be applied to the TooltipBox.
* @param focusable [Boolean] that determines if the tooltip is focusable. When true,
* the tooltip will consume touch events while it's shown and will have accessibility
* focus move to the first element of the component. When false, the tooltip
@@ -142,16 +142,16 @@
content: @Composable () -> Unit
) {
Surface(
- modifier = modifier
+ shape = shape,
+ color = containerColor
+ ) {
+ Box(modifier = modifier
.sizeIn(
minWidth = TooltipMinWidth,
maxWidth = PlainTooltipMaxWidth,
minHeight = TooltipMinHeight
- ),
- shape = shape,
- color = containerColor
- ) {
- Box(modifier = Modifier.padding(PlainTooltipContentPadding)) {
+ ).padding(PlainTooltipContentPadding)
+ ) {
val textStyle =
MaterialTheme.typography.fromToken(PlainTooltipTokens.SupportingTextFont)
CompositionLocalProvider(
diff --git a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/CompositionTests.kt b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/CompositionTests.kt
index 8f8615b..d93db8a 100644
--- a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/CompositionTests.kt
+++ b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/CompositionTests.kt
@@ -3424,13 +3424,17 @@
@Test // regression test for 264467571
fun test_returnConditionally_fromNodeLambda_local_initial_return() = compositionTest {
var condition by mutableStateOf(true)
+
compose {
+ currentComposer.disableSourceInformation()
Text("Before outer")
InlineLinear {
Text("Before inner")
InlineLinear inner@{
Text("Before return")
- if (condition) return@inner
+ if (condition) {
+ return@inner
+ }
Text("After return")
}
Text("After inner")
@@ -3463,6 +3467,7 @@
fun test_returnConditionally_fromNodeLambda_local_initial_no_return() = compositionTest {
var condition by mutableStateOf(true)
compose {
+ currentComposer.disableSourceInformation()
Text("Before outer")
InlineLinear {
Text("Before inner")
@@ -3501,6 +3506,7 @@
fun test_returnConditionally_fromNodeLambda_nonLocal_initial_return() = compositionTest {
var condition by mutableStateOf(true)
compose {
+ currentComposer.disableSourceInformation()
Text("Before outer")
InlineLinear outer@{
Text("Before inner")
@@ -3539,6 +3545,7 @@
fun test_returnConditionally_fromNodeLambda_nonLocal_initial_no_return() = compositionTest {
var condition by mutableStateOf(true)
compose {
+ currentComposer.disableSourceInformation()
Text("Before outer")
InlineLinear outer@{
Text("Before inner")
@@ -3578,6 +3585,7 @@
compositionTest {
var condition by mutableStateOf(true)
compose {
+ currentComposer.disableSourceInformation()
Text("Before outer")
InlineLinear outer@{
Text("Before inner")
diff --git a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTree.kt b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTree.kt
index 8ba40fa..a36e3d6 100644
--- a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTree.kt
+++ b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTree.kt
@@ -502,23 +502,26 @@
val box = context.bounds
val size = box.size.toSize()
val coordinates = layoutInfo.coordinates
- val topLeft = toIntOffset(coordinates.localToWindow(Offset.Zero))
- val topRight = toIntOffset(coordinates.localToWindow(Offset(size.width, 0f)))
- val bottomRight = toIntOffset(coordinates.localToWindow(Offset(size.width, size.height)))
- val bottomLeft = toIntOffset(coordinates.localToWindow(Offset(0f, size.height)))
var bounds: QuadBounds? = null
+ if (layoutInfo.isAttached) {
+ val topLeft = toIntOffset(coordinates.localToWindow(Offset.Zero))
+ val topRight = toIntOffset(coordinates.localToWindow(Offset(size.width, 0f)))
+ val bottomRight =
+ toIntOffset(coordinates.localToWindow(Offset(size.width, size.height)))
+ val bottomLeft = toIntOffset(coordinates.localToWindow(Offset(0f, size.height)))
- if (topLeft.x != box.left || topLeft.y != box.top ||
- topRight.x != box.right || topRight.y != box.top ||
- bottomRight.x != box.right || bottomRight.y != box.bottom ||
- bottomLeft.x != box.left || bottomLeft.y != box.bottom
- ) {
- bounds = QuadBounds(
- topLeft.x, topLeft.y,
- topRight.x, topRight.y,
- bottomRight.x, bottomRight.y,
- bottomLeft.x, bottomLeft.y,
- )
+ if (topLeft.x != box.left || topLeft.y != box.top ||
+ topRight.x != box.right || topRight.y != box.top ||
+ bottomRight.x != box.right || bottomRight.y != box.bottom ||
+ bottomLeft.x != box.left || bottomLeft.y != box.bottom
+ ) {
+ bounds = QuadBounds(
+ topLeft.x, topLeft.y,
+ topRight.x, topRight.y,
+ bottomRight.x, bottomRight.y,
+ bottomLeft.x, bottomLeft.y,
+ )
+ }
}
if (!includeNodesOutsizeOfWindow) {
// Ignore this node if the bounds are completely outside the window
diff --git a/compose/ui/ui-text/benchmark/src/androidTest/java/androidx/compose/ui/text/benchmark/HyphensLineBreakBenchmark.kt b/compose/ui/ui-text/benchmark/src/androidTest/java/androidx/compose/ui/text/benchmark/HyphensLineBreakBenchmark.kt
deleted file mode 100644
index 72f0dbc..0000000
--- a/compose/ui/ui-text/benchmark/src/androidTest/java/androidx/compose/ui/text/benchmark/HyphensLineBreakBenchmark.kt
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Copyright 2022 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.text.benchmark
-
-import android.graphics.Bitmap
-import android.graphics.Canvas
-import android.graphics.text.LineBreakConfig
-import android.graphics.text.LineBreaker
-import android.os.Build
-import android.text.Layout
-import android.text.TextPaint
-import androidx.benchmark.junit4.BenchmarkRule
-import androidx.benchmark.junit4.measureRepeated
-import androidx.compose.ui.text.android.InternalPlatformTextApi
-import androidx.compose.ui.text.android.StaticLayoutFactory
-import androidx.compose.ui.text.style.Hyphens
-import androidx.compose.ui.text.style.LineBreak
-import androidx.test.filters.LargeTest
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-
-@LargeTest
-@RunWith(Parameterized::class)
-@OptIn(InternalPlatformTextApi::class)
-class HyphensLineBreakBenchmark(
- private val textLength: Int,
- private val hyphensString: String,
- private val lineBreakString: String
-) {
- companion object {
- @JvmStatic
- @Parameterized.Parameters(name = "length={0} hyphens={1} lineBreak={2}")
- fun initParameters(): List<Array<Any?>> {
- return cartesian(
- arrayOf(32, 128, 512),
- arrayOf(
- Hyphens.None.toTestString(),
- Hyphens.Auto.toTestString()
- ),
- arrayOf(
- LineBreak.Paragraph.toTestString(),
- LineBreak.Simple.toTestString(),
- LineBreak.Heading.toTestString()
- )
- )
- }
- }
-
- @get:Rule
- val benchmarkRule = BenchmarkRule()
-
- @get:Rule
- val textBenchmarkRule = TextBenchmarkTestRule()
-
- private val width = 100
- private val textSize: Float = 10F
- private val hyphenationFrequency = toLayoutHyphenationFrequency(hyphensString.toHyphens())
- private val lineBreakStyle = toLayoutLineBreakStyle(lineBreakString.toLineBreak().strictness)
- private val breakStrategy = toLayoutBreakStrategy(lineBreakString.toLineBreak().strategy)
- private val lineBreakWordStyle =
- toLayoutLineBreakWordStyle(lineBreakString.toLineBreak().wordBreak)
-
- @Test
- fun constructLayout() {
- textBenchmarkRule.generator { textGenerator ->
- val text = textGenerator.nextParagraph(textLength)
- val textPaint = TextPaint()
- textPaint.textSize = textSize
- benchmarkRule.measureRepeated {
- StaticLayoutFactory.create(
- text = text,
- width = width,
- paint = textPaint,
- hyphenationFrequency = hyphenationFrequency,
- lineBreakStyle = lineBreakStyle,
- breakStrategy = breakStrategy,
- lineBreakWordStyle = lineBreakWordStyle
- )
- }
- }
- }
-
- @Test
- fun constructLayoutDraw() {
- textBenchmarkRule.generator { textGenerator ->
- val text = textGenerator.nextParagraph(textLength)
- val textPaint = TextPaint()
- textPaint.textSize = textSize
- val canvas = Canvas(Bitmap.createBitmap(width, 1000, Bitmap.Config.ARGB_8888))
- benchmarkRule.measureRepeated {
- val layout = StaticLayoutFactory.create(
- text = text,
- width = width,
- paint = textPaint,
- hyphenationFrequency = hyphenationFrequency,
- lineBreakStyle = lineBreakStyle,
- breakStrategy = breakStrategy,
- lineBreakWordStyle = lineBreakWordStyle
- )
- layout.draw(canvas)
- }
- }
- }
-
- private fun toLayoutHyphenationFrequency(hyphens: Hyphens?): Int = when (hyphens) {
- Hyphens.Auto -> if (Build.VERSION.SDK_INT <= 32) {
- Layout.HYPHENATION_FREQUENCY_NORMAL
- } else {
- Layout.HYPHENATION_FREQUENCY_NORMAL_FAST
- }
- Hyphens.None -> Layout.HYPHENATION_FREQUENCY_NONE
- else -> Layout.HYPHENATION_FREQUENCY_NONE
- }
-
- private fun toLayoutBreakStrategy(breakStrategy: LineBreak.Strategy?): Int =
- when (breakStrategy) {
- LineBreak.Strategy.Simple -> LineBreaker.BREAK_STRATEGY_SIMPLE
- LineBreak.Strategy.HighQuality -> LineBreaker.BREAK_STRATEGY_HIGH_QUALITY
- LineBreak.Strategy.Balanced -> LineBreaker.BREAK_STRATEGY_BALANCED
- else -> LineBreaker.BREAK_STRATEGY_SIMPLE
- }
-
- private fun toLayoutLineBreakStyle(lineBreakStrictness: LineBreak.Strictness?): Int =
- when (lineBreakStrictness) {
- LineBreak.Strictness.Default -> LineBreakConfig.LINE_BREAK_STYLE_NONE
- LineBreak.Strictness.Loose -> LineBreakConfig.LINE_BREAK_STYLE_LOOSE
- LineBreak.Strictness.Normal -> LineBreakConfig.LINE_BREAK_STYLE_NORMAL
- LineBreak.Strictness.Strict -> LineBreakConfig.LINE_BREAK_STYLE_STRICT
- else -> LineBreakConfig.LINE_BREAK_STYLE_NONE
- }
-
- private fun toLayoutLineBreakWordStyle(lineBreakWordStyle: LineBreak.WordBreak?): Int =
- when (lineBreakWordStyle) {
- LineBreak.WordBreak.Default -> LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE
- LineBreak.WordBreak.Phrase -> LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE
- else -> LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE
- }
-}
-
-/**
- * Required to make this test work due to a bug with value classes and Parameterized JUnit tests.
- * https://youtrack.jetbrains.com/issue/KT-35523
- *
- * However, it's not enough to use a wrapper because wrapper makes the test name unnecessarily
- * long which causes Perfetto to be unable to create output files with a very long name in some
- * file systems.
- *
- * Using a String instead of an Integer gives us a better test naming.
- */
-private fun String.toLineBreak(): LineBreak = when (this) {
- "Simple" -> LineBreak.Simple
- "Heading" -> LineBreak.Heading
- "Paragraph" -> LineBreak.Paragraph
- else -> throw IllegalArgumentException("Unrecognized LineBreak value for this test")
-}
-
-private fun LineBreak.toTestString(): String = when (this) {
- LineBreak.Simple -> "Simple"
- LineBreak.Heading -> "Heading"
- LineBreak.Paragraph -> "Paragraph"
- else -> throw IllegalArgumentException("Unrecognized LineBreak value for this test")
-}
-
-private fun String.toHyphens(): Hyphens = when (this) {
- "None" -> Hyphens.None
- "Auto" -> Hyphens.Auto
- else -> throw IllegalArgumentException("Unrecognized Hyphens value for this test")
-}
-
-private fun Hyphens.toTestString(): String = when (this) {
- Hyphens.None -> "None"
- Hyphens.Auto -> "Auto"
- else -> throw IllegalArgumentException("Unrecognized Hyphens value for this test")
-}
diff --git a/compose/ui/ui/api/current.txt b/compose/ui/ui/api/current.txt
index 59a93d9..e5c14c2 100644
--- a/compose/ui/ui/api/current.txt
+++ b/compose/ui/ui/api/current.txt
@@ -609,7 +609,7 @@
}
public final class FocusRestorerKt {
- method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static androidx.compose.ui.Modifier focusRestorer(androidx.compose.ui.Modifier);
+ method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static androidx.compose.ui.Modifier focusRestorer(androidx.compose.ui.Modifier, optional kotlin.jvm.functions.Function0<androidx.compose.ui.focus.FocusRequester>? onRestoreFailed);
}
public interface FocusState {
diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt
index 7608ff3..71da5eb 100644
--- a/compose/ui/ui/api/restricted_current.txt
+++ b/compose/ui/ui/api/restricted_current.txt
@@ -609,7 +609,7 @@
}
public final class FocusRestorerKt {
- method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static androidx.compose.ui.Modifier focusRestorer(androidx.compose.ui.Modifier);
+ method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static androidx.compose.ui.Modifier focusRestorer(androidx.compose.ui.Modifier, optional kotlin.jvm.functions.Function0<androidx.compose.ui.focus.FocusRequester>? onRestoreFailed);
}
public interface FocusState {
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/viewinterop/ViewInterop.kt b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/viewinterop/ViewInterop.kt
index 2ea6de9..22effa7 100644
--- a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/viewinterop/ViewInterop.kt
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/viewinterop/ViewInterop.kt
@@ -16,16 +16,27 @@
package androidx.compose.ui.demos.viewinterop
+import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Rect
+import android.os.Build
import android.view.View
import android.view.ViewGroup
+import android.widget.LinearLayout
import android.widget.TextView
+import androidx.annotation.DoNotInline
+import androidx.annotation.RequiresApi
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
@@ -38,9 +49,11 @@
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.node.Ref
+import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.ui.viewinterop.AndroidViewBinding
+@SuppressLint("ResourceType")
@Composable
fun ViewInteropDemo() {
Column {
@@ -85,6 +98,62 @@
) {
Text("Change color of Android view")
}
+
+ Column(modifier =
+ Modifier.fillMaxWidth().height(100.dp).verticalScroll(rememberScrollState())
+ ) {
+ AndroidView({ c ->
+ LinearLayout(c).apply {
+ val text1 = TextView(c).apply { text = "LinearLayout child 1"; id = 11 }
+ val text2 = TextView(c).apply { text = "LinearLayout child 2"; id = 22 }
+ val text3 = TextView(c).apply { text = "LinearLayout child 3"; id = 33 }
+ if (Build.VERSION.SDK_INT >= 26) {
+ Api26Impl.setAccessibilityTraversalAfter(text3, text2.getId());
+ Api26Impl.setAccessibilityTraversalAfter(text2, text1.getId());
+ }
+ addView(text3)
+ addView(text2)
+ addView(text1)
+ }
+ })
+ }
+
+ Spacer(Modifier.height(20.dp))
+
+ LazyColumn(
+ modifier = Modifier.fillMaxWidth().height(50.dp)
+ ) {
+ item {
+ AndroidView(::TextView) { it.text = "TextView in LazyColumn 1A" }
+ AndroidView(::TextView) { it.text = "TextView in LazyColumn 1B" }
+ }
+ item {
+ AndroidView(::TextView) { it.text = "TextView in LazyColumn 2A" }
+ AndroidView(::TextView) { it.text = "TextView in LazyColumn 2B" }
+ }
+ item {
+ AndroidView(::TextView) { it.text = "TextView in LazyColumn 3A" }
+ AndroidView(::TextView) { it.text = "TextView in LazyColumn 3B" }
+ }
+ item {
+ AndroidView(::TextView) { it.text = "TextView in LazyColumn 4A" }
+ AndroidView(::TextView) { it.text = "TextView in LazyColumn 4B" }
+ }
+ }
+ Spacer(Modifier.height(20.dp))
+ Column(
+ modifier = Modifier.fillMaxWidth().height(50.dp).verticalScroll(rememberScrollState())
+ ) {
+ AndroidView(::TextView) { it.text = "TextView in verticalScroll 1" }
+ AndroidView(::TextView) { it.text = "TextView in verticalScroll 2" }
+ AndroidView(::TextView) { it.text = "TextView in verticalScroll 3" }
+ AndroidView(::TextView) { it.text = "TextView in verticalScroll 4" }
+ AndroidView(::TextView) { it.text = "TextView in verticalScroll 5" }
+ AndroidView(::TextView) { it.text = "TextView in verticalScroll 6" }
+ AndroidView(::TextView) { it.text = "TextView in verticalScroll 7" }
+ AndroidView(::TextView) { it.text = "TextView in verticalScroll 8" }
+ AndroidView(::TextView) { it.text = "TextView in verticalScroll 9" }
+ }
}
}
@@ -120,3 +189,15 @@
canvas.drawRect(rect, paint)
}
}
+
+@RequiresApi(Build.VERSION_CODES.O)
+private object Api26Impl {
+ @DoNotInline
+ @JvmStatic
+ fun setAccessibilityTraversalAfter(
+ view: View,
+ id: Int
+ ) {
+ view.setAccessibilityTraversalAfter(id);
+ }
+}
diff --git a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/FocusSamples.kt b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/FocusSamples.kt
index 69035e5..4041e63 100644
--- a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/FocusSamples.kt
+++ b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/FocusSamples.kt
@@ -134,6 +134,27 @@
}
}
+@OptIn(ExperimentalComposeUiApi::class)
+@Sampled
+@Composable
+fun FocusRestorerCustomFallbackSample() {
+ val focusRequester = remember { FocusRequester() }
+ LazyRow(
+ // If restoration fails, focus would fallback to the item associated with focusRequester.
+ Modifier.focusRestorer { focusRequester }
+ ) {
+ item {
+ Button(
+ modifier = Modifier.focusRequester(focusRequester),
+ onClick = {}
+ ) { Text("1") }
+ }
+ item { Button(onClick = {}) { Text("2") } }
+ item { Button(onClick = {}) { Text("3") } }
+ item { Button(onClick = {}) { Text("4") } }
+ }
+}
+
@Sampled
@Composable
fun RequestFocusSample() {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusRestorerTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusRestorerTest.kt
index dbe8b98..469567e 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusRestorerTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusRestorerTest.kt
@@ -177,4 +177,48 @@
assertThat(grandChildState.isFocused).isFalse()
}
}
+
+ @Test
+ fun restorationFailed_fallbackToOnRestoreFailedDestination() {
+ // Arrange.
+ val (parent, child2) = FocusRequester.createRefs()
+ lateinit var child1State: FocusState
+ lateinit var child2State: FocusState
+ rule.setFocusableContent {
+ Box(
+ Modifier
+ .size(10.dp)
+ .focusRequester(parent)
+ .focusRestorer { child2 }
+ .focusGroup()
+ ) {
+ key(1) {
+ Box(
+ Modifier
+ .size(10.dp)
+ .onFocusChanged { child1State = it }
+ .focusTarget()
+ )
+ }
+ key(2) {
+ Box(
+ Modifier
+ .size(10.dp)
+ .focusRequester(child2)
+ .onFocusChanged { child2State = it }
+ .focusTarget()
+ )
+ }
+ }
+ }
+
+ // Act.
+ rule.runOnIdle { parent.requestFocus() }
+
+ // Assert.
+ rule.runOnIdle {
+ assertThat(child1State.isFocused).isFalse()
+ assertThat(child2State.isFocused).isTrue()
+ }
+ }
}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/viewinterop/AndroidViewTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/viewinterop/AndroidViewTest.kt
index ffe8b60..3225799 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/viewinterop/AndroidViewTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/viewinterop/AndroidViewTest.kt
@@ -237,6 +237,9 @@
}
val info: AccessibilityNodeInfo = AccessibilityNodeInfo()
delegate.onInitializeAccessibilityNodeInfo(view, info)
+ if (info.isVisibleToUser()) {
+ throw exception
+ }
if (!info.isScreenReaderFocusable()) {
throw exception
}
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
index 817bf68..5859ede 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
@@ -818,6 +818,10 @@
info: AccessibilityNodeInfoCompat
) {
super.onInitializeAccessibilityNodeInfo(host, info)
+
+ // Prevent TalkBack from trying to focus the AndroidViewHolder
+ info.setVisibleToUser(false)
+
var parentId = layoutNode
.findClosestParentNode { it.nodes.has(Nodes.Semantics) }
?.semanticsId
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidViewHolder.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidViewHolder.android.kt
index bef25e7e..12dd53e 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidViewHolder.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidViewHolder.android.kt
@@ -202,6 +202,10 @@
override val isValidOwnerScope: Boolean
get() = isAttachedToWindow
+ override fun getAccessibilityClassName(): CharSequence {
+ return javaClass.name
+ }
+
override fun onReuse() {
// We reset at the same time we remove the view. So if the view was removed, we can just
// re-add it and it's ready to go. If it's already attached, we didn't reset it and need
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRestorer.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRestorer.kt
index c490f79..bfa2812 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRestorer.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRestorer.kt
@@ -19,7 +19,6 @@
import androidx.compose.runtime.saveable.LocalSaveableStateRegistry
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
-import androidx.compose.ui.focus.FocusRestorerNode.Companion.FocusRestorerElement
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.node.Nodes
import androidx.compose.ui.node.currentValueOf
@@ -63,17 +62,25 @@
// TODO: Move focusRestorer to foundation after saveFocusedChild and restoreFocusedChild are stable.
/**
- * This modifier can be uses to save and restore focus to a focus group.
+ * This modifier can be used to save and restore focus to a focus group.
* When focus leaves the focus group, it stores a reference to the item that was previously focused.
* Then when focus re-enters this focus group, it restores focus to the previously focused item.
*
+ * @param onRestoreFailed callback provides a lambda that is invoked if focus restoration fails.
+ * This lambda can be used to return a custom fallback item by providing a [FocusRequester]
+ * attached to that item. This can be used to customize the initially focused item.
+ *
* @sample androidx.compose.ui.samples.FocusRestorerSample
+ * @sample androidx.compose.ui.samples.FocusRestorerCustomFallbackSample
*/
@ExperimentalComposeUiApi
-fun Modifier.focusRestorer(): Modifier = this then FocusRestorerElement
+fun Modifier.focusRestorer(
+ onRestoreFailed: (() -> FocusRequester)? = null
+): Modifier = this then FocusRestorerElement(onRestoreFailed)
-internal class FocusRestorerNode :
- FocusPropertiesModifierNode, FocusRequesterModifierNode, Modifier.Node() {
+internal class FocusRestorerNode(
+ var onRestoreFailed: (() -> FocusRequester)?
+) : FocusPropertiesModifierNode, FocusRequesterModifierNode, Modifier.Node() {
private val onExit: (FocusDirection) -> FocusRequester = {
@OptIn(ExperimentalComposeUiApi::class)
saveFocusedChild()
@@ -83,22 +90,32 @@
@OptIn(ExperimentalComposeUiApi::class)
private val onEnter: (FocusDirection) -> FocusRequester = {
@OptIn(ExperimentalComposeUiApi::class)
- if (restoreFocusedChild()) FocusRequester.Cancel else FocusRequester.Default
+ if (restoreFocusedChild()) {
+ FocusRequester.Cancel
+ } else {
+ onRestoreFailed?.invoke() ?: FocusRequester.Default
+ }
}
+
override fun applyFocusProperties(focusProperties: FocusProperties) {
@OptIn(ExperimentalComposeUiApi::class)
focusProperties.enter = onEnter
@OptIn(ExperimentalComposeUiApi::class)
focusProperties.exit = onExit
}
+}
- companion object {
- val FocusRestorerElement = object : ModifierNodeElement<FocusRestorerNode>() {
- override fun create() = FocusRestorerNode()
- override fun update(node: FocusRestorerNode) {}
- override fun InspectorInfo.inspectableProperties() { name = "focusRestorer" }
- override fun hashCode(): Int = "focusRestorer".hashCode()
- override fun equals(other: Any?) = other === this
- }
+private data class FocusRestorerElement(
+ val onRestoreFailed: (() -> FocusRequester)?
+) : ModifierNodeElement<FocusRestorerNode>() {
+ override fun create() = FocusRestorerNode(onRestoreFailed)
+
+ override fun update(node: FocusRestorerNode) {
+ node.onRestoreFailed = onRestoreFailed
+ }
+
+ override fun InspectorInfo.inspectableProperties() {
+ name = "focusRestorer"
+ properties["onRestoreFailed"] = onRestoreFailed
}
}
diff --git a/core/core-performance-play-services/build.gradle b/core/core-performance-play-services/build.gradle
index c7a44dd..5ae6276 100644
--- a/core/core-performance-play-services/build.gradle
+++ b/core/core-performance-play-services/build.gradle
@@ -29,23 +29,13 @@
implementation(libs.kotlinCoroutinesCore)
implementation(project(":core:core-performance"))
implementation("androidx.datastore:datastore-preferences:1.0.0")
- implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
- testImplementation(libs.robolectric)
androidTestImplementation(libs.junit)
androidTestImplementation(libs.testExtJunit)
androidTestImplementation(libs.testRunner)
androidTestImplementation(libs.truth)
- androidTestImplementation(libs.espressoCore, excludes.espresso)
- androidTestImplementation(libs.mockitoAndroid)
- testImplementation(libs.testCore)
- testImplementation(libs.kotlinStdlib)
- testImplementation(libs.kotlinCoroutinesTest)
- testImplementation(libs.junit)
- testImplementation(libs.truth)
-
}
android {
diff --git a/core/core-performance-play-services/src/androidTest/java/androidx/core/performance/play/services/PlayServiceDevicePerformanceAndroidTest.kt b/core/core-performance-play-services/src/androidTest/java/androidx/core/performance/play/services/PlayServiceDevicePerformanceAndroidTest.kt
index 033e68a..26ed265 100644
--- a/core/core-performance-play-services/src/androidTest/java/androidx/core/performance/play/services/PlayServiceDevicePerformanceAndroidTest.kt
+++ b/core/core-performance-play-services/src/androidTest/java/androidx/core/performance/play/services/PlayServiceDevicePerformanceAndroidTest.kt
@@ -28,27 +28,26 @@
import com.google.android.gms.common.api.internal.ApiKey
import com.google.android.gms.deviceperformance.DevicePerformanceClient
import com.google.android.gms.tasks.Task
-import com.google.android.gms.tasks.Tasks
+import com.google.android.gms.tasks.TaskCompletionSource
import com.google.common.truth.Truth
import java.util.concurrent.TimeUnit
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.`when`
/** Android Unit tests for [PlayServicesDevicePerformance]. */
@RunWith(AndroidJUnit4::class)
class PlayServicesDevicePerformanceTest {
- open class DevicePerformanceClientTest : DevicePerformanceClient {
+ class FakeDevicePerformanceClient() : DevicePerformanceClient {
+ val taskSource: TaskCompletionSource<Int> = TaskCompletionSource()
override fun getApiKey(): ApiKey<Api.ApiOptions.NoOptions> {
// method for testing purpose
return this.apiKey
}
override fun mediaPerformanceClass(): Task<Int> {
- return Tasks.forResult(0)
+ return taskSource.task
}
}
@@ -62,45 +61,26 @@
@Test
@MediumTest
- fun basePlayServiceDevicePerformanceClassTest() {
- val playServicesDevicePerformance = PlayServicesDevicePerformance(
- context
- )
- val pcScore = playServicesDevicePerformance.mediaPerformanceClass
- Truth.assertThat(pcScore).isEqualTo(defaultMediaPerformanceClass)
- }
+ fun mediaPerformanceClass_EmptyStore_33Client() {
+ val fakeDevicePerformanceClient = FakeDevicePerformanceClient()
- @Test
- @MediumTest
- fun mockPlayServiceDevicePerformanceClassTest() {
- val mockClient: DevicePerformanceClient = mock(DevicePerformanceClientTest::class.java)
- val mediaPerformanceClass = 33
- `when`(mockClient.mediaPerformanceClass()).thenAnswer {
- Tasks.forResult(mediaPerformanceClass)
- }
- val playServicesDevicePerformance = PlayServicesDevicePerformance(
+ val playServicesDevicePerformance = PlayServicesDevicePerformance.create(
context,
- mockClient
+ fakeDevicePerformanceClient
)
+ fakeDevicePerformanceClient.taskSource.setResult(33)
delayRead()
val pcScore = playServicesDevicePerformance.mediaPerformanceClass
- Truth.assertThat(pcScore).isEqualTo(mediaPerformanceClass)
+ Truth.assertThat(pcScore).isEqualTo(33)
}
@Test
@MediumTest
- fun delayMockPlayServiceDevicePerformanceClassTest() {
- val mockClient: DevicePerformanceClient = mock(DevicePerformanceClientTest::class.java)
-
- // Delay the response from mockClient.mediaPerformanceClass() so
- // response will be different that provided.
- `when`(mockClient.mediaPerformanceClass()).thenAnswer {
- TimeUnit.SECONDS.sleep(5)
- Tasks.forResult(defaultMediaPerformanceClass + 100)
- }
- val playServicesDevicePerformance = PlayServicesDevicePerformance(
+ fun mediaPerformanceClass_EmptyStore() {
+ val fakeDevicePerformanceClient = FakeDevicePerformanceClient()
+ val playServicesDevicePerformance = PlayServicesDevicePerformance.create(
context,
- mockClient
+ fakeDevicePerformanceClient
)
val pcScore = playServicesDevicePerformance.mediaPerformanceClass
Truth.assertThat(pcScore).isEqualTo(defaultMediaPerformanceClass)
@@ -108,32 +88,30 @@
@Test
@MediumTest
- fun playServiceCrashPerformanceClassTest() {
- val mockClient: DevicePerformanceClient = mock(DevicePerformanceClientTest::class.java)
- `when`(mockClient.mediaPerformanceClass()).thenReturn( // Throw an exception here.
- Tasks.forException(IllegalStateException())
- )
- val pc = PlayServicesDevicePerformance(
+ fun mediaPerformanceClass_EmptyStore_IllegalStateException() {
+ val fakeDevicePerformanceClient = FakeDevicePerformanceClient()
+ fakeDevicePerformanceClient.taskSource.setException(IllegalStateException())
+ val playServicesDevicePerformance = PlayServicesDevicePerformance.create(
context,
- mockClient
+ fakeDevicePerformanceClient
)
// Since the gms service has crashed, the library should still return default value.
- Truth.assertThat(pc.mediaPerformanceClass).isEqualTo(defaultMediaPerformanceClass)
+ Truth.assertThat(playServicesDevicePerformance.mediaPerformanceClass)
+ .isEqualTo(defaultMediaPerformanceClass)
}
@Test
@MediumTest
- fun playServiceNotStartPerformanceClassTest() {
- val mockClient: DevicePerformanceClient = mock(DevicePerformanceClientTest::class.java)
- `when`(mockClient.mediaPerformanceClass()).thenReturn( // Throw an exception here.
- Tasks.forException(ApiException(Status.RESULT_TIMEOUT))
- )
- val pc = PlayServicesDevicePerformance(
+ fun mediaPerformanceClass_EmptyStore_TimeOut() {
+ val fakeDevicePerformanceClient = FakeDevicePerformanceClient()
+ fakeDevicePerformanceClient.taskSource.setException(ApiException(Status.RESULT_TIMEOUT))
+ val playServicesDevicePerformance = PlayServicesDevicePerformance.create(
context,
- mockClient
+ fakeDevicePerformanceClient
)
// Since the gms service not started, the library should still return default value.
- Truth.assertThat(pc.mediaPerformanceClass).isEqualTo(defaultMediaPerformanceClass)
+ Truth.assertThat(playServicesDevicePerformance.mediaPerformanceClass)
+ .isEqualTo(defaultMediaPerformanceClass)
}
/* Add delay to make sure that value is written in Preference datastore before reading it */
diff --git a/core/core-performance-play-services/src/main/java/androidx/core/performance/play/services/PlayServicesDevicePerformance.kt b/core/core-performance-play-services/src/main/java/androidx/core/performance/play/services/PlayServicesDevicePerformance.kt
index 2fa0a804..03f2d6a 100644
--- a/core/core-performance-play-services/src/main/java/androidx/core/performance/play/services/PlayServicesDevicePerformance.kt
+++ b/core/core-performance-play-services/src/main/java/androidx/core/performance/play/services/PlayServicesDevicePerformance.kt
@@ -39,7 +39,9 @@
*
* @param context The application context value to use.
*/
-class PlayServicesDevicePerformance(private val context: Context) : DevicePerformance {
+class PlayServicesDevicePerformance
+private constructor(private val context: Context, client: DevicePerformanceClient) :
+ DevicePerformance {
private val tag = "PlayServicesDevicePerformance"
private val defaultMpc = DefaultDevicePerformance()
@@ -64,24 +66,32 @@
"Getting mediaPerformanceClass from " +
"com.google.android.gms.deviceperformance.DevicePerformanceClient"
)
- updatePerformanceStore(
- com.google.android.gms.deviceperformance.DevicePerformance.getClient(context)
- )
+ updatePerformanceStore(client)
}
- @VisibleForTesting
- internal constructor(context: Context, client: DevicePerformanceClient) : this(context) {
- // mock client should wait for the playServices client to finish,
- // so the test results are determined by the mock client.
- runBlocking {
- playServicesValueStoredDeferred.await()
- }
- updatePerformanceStore(client)
+ /**
+ * A DevicePerformance that uses Google Play Services to retrieve media performance class data.
+ *
+ * @param context The application context value to use.
+ */
+ constructor(context: Context) : this(
+ context,
+ com.google.android.gms.deviceperformance.DevicePerformance.getClient(context)
+ ) {
}
private val mpcKey = intPreferencesKey("mpc_value")
internal companion object {
+
+ @VisibleForTesting
+ fun create(
+ context: Context,
+ client: DevicePerformanceClient
+ ): PlayServicesDevicePerformance {
+ return PlayServicesDevicePerformance(context, client)
+ }
+
// To avoid creating multiple instance of datastore
private val Context.performanceStore by
preferencesDataStore(name = "media_performance_class")
@@ -115,16 +125,14 @@
launch {
savePerformanceClass(storedVal)
Log.v(tag, "Saved mediaPerformanceClass $storedVal")
- playServicesValueStoredDeferred.complete(true)
}
}
}.addOnFailureListener { e: Exception ->
if (e is ApiException) {
- Log.e(tag, "Error saving mediaPerformanceClass: $e")
+ Log.e(tag, "Error saving mediaPerformanceClass", e)
} else if (e is IllegalStateException) {
- Log.e(tag, "Error saving mediaPerformanceClass: $e")
+ Log.e(tag, "Error saving mediaPerformanceClass", e)
}
- playServicesValueStoredDeferred.complete(true)
}
}
}
diff --git a/core/core-telecom/src/androidTest/AndroidManifest.xml b/core/core-telecom/src/androidTest/AndroidManifest.xml
index 6e24bd3..4d94ecb2 100644
--- a/core/core-telecom/src/androidTest/AndroidManifest.xml
+++ b/core/core-telecom/src/androidTest/AndroidManifest.xml
@@ -16,6 +16,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
+ <uses-permission android:name="android.permission.READ_PHONE_NUMBERS"/>
<application>
<service
diff --git a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/E2ECallExtensionExtrasTests.kt b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/E2ECallExtensionExtrasTests.kt
new file mode 100644
index 0000000..458a920
--- /dev/null
+++ b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/E2ECallExtensionExtrasTests.kt
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.telecom.test
+
+import android.Manifest
+import android.os.Build
+import android.telecom.Call
+import android.telecom.DisconnectCause
+import androidx.annotation.RequiresApi
+import androidx.core.telecom.CallAttributesCompat
+import androidx.core.telecom.CallsManager
+import androidx.core.telecom.internal.InCallServiceCompat
+import androidx.core.telecom.internal.utils.Utils
+import androidx.core.telecom.test.utils.BaseTelecomTest
+import androidx.core.telecom.test.utils.TestUtils
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.rule.GrantPermissionRule
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import org.junit.After
+import org.junit.Assert
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * This test class helps verify the E2E behavior for calls added via Jetpack to ensure that the
+ * call details contain the appropriate extension extras that define the support for capability
+ * exchange between the VOIP app and ICS.
+ *
+ * Note: Currently, this test only verifies the presence of [CallsManager.PROPERTY_IS_TRANSACTIONAL]
+ * (only in V) in the call properties, if the phone account supports transactional ops (U+ devices),
+ * or if the [CallsManager.EXTRA_VOIP_BACKWARDS_COMPATIBILITY_SUPPORTED] key is present in the call
+ * extras (pre-U devices). In the future, this will be expanded to be provide more robust testing
+ * to verify binder functionality as well as supporting the case for auto
+ * ([CallsManager.EXTRA_VOIP_API_VERSION]).
+ */
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+@RequiresApi(Build.VERSION_CODES.O)
+@RunWith(AndroidJUnit4::class)
+class E2ECallExtensionExtrasTests : BaseTelecomTest() {
+ private lateinit var inCallServiceCompat: InCallServiceCompat
+
+ /**
+ * Grant READ_PHONE_NUMBERS permission as part of testing
+ * [InCallServiceCompat#resolveCallExtensionsType].
+ */
+ @get:Rule
+ val readPhoneNumbersRule: GrantPermissionRule =
+ GrantPermissionRule.grant(Manifest.permission.READ_PHONE_NUMBERS)!!
+
+ @Before
+ fun setUp() {
+ Utils.resetUtils()
+ inCallServiceCompat = InCallServiceCompat(mContext)
+ }
+
+ @After
+ fun onDestroy() {
+ Utils.resetUtils()
+ }
+
+ /***********************************************************************************************
+ * V2 APIs (Android U and above) tests
+ *********************************************************************************************/
+
+ /**
+ * For U+ devices using the v2 APIs, assert that the incoming call details either support
+ * the [CallsManager.PROPERTY_IS_TRANSACTIONAL] property (V) or the phone account supports
+ * transactional operations (U+).
+ */
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @LargeTest
+ @Test(timeout = 10000)
+ fun testCapabilityExchangeIncoming_V2() {
+ setUpV2Test()
+ addAndVerifyCallExtensionTypeE2E(TestUtils.INCOMING_CALL_ATTRIBUTES)
+ }
+
+ /**
+ * For U+ devices using the v2 APIs, assert that the outgoing call details either support
+ * the [CallsManager.PROPERTY_IS_TRANSACTIONAL] property (V) or the phone account supports
+ * transactional operations (U+).
+ */
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @LargeTest
+ @Test(timeout = 10000)
+ fun testCapabilityExchangeOutgoing_V2() {
+ setUpV2Test()
+ addAndVerifyCallExtensionTypeE2E(TestUtils.OUTGOING_CALL_ATTRIBUTES)
+ }
+
+ /***********************************************************************************************
+ * Backwards Compatibility Layer tests
+ *********************************************************************************************/
+
+ /**
+ * For pre-U devices using the backwards compatibility library, assert that the incoming call
+ * details contain the [CallsManager.EXTRA_VOIP_BACKWARDS_COMPATIBILITY_SUPPORTED] key
+ */
+ @LargeTest
+ @Test(timeout = 10000)
+ fun testCapabilityExchangeIncoming_BackwardsCompat() {
+ setUpBackwardsCompatTest()
+ addAndVerifyCallExtensionTypeE2E(
+ TestUtils.INCOMING_CALL_ATTRIBUTES,
+ waitForCallDetailExtras = true
+ )
+ }
+
+ /**
+ * For pre-U devices using the backwards compatibility library, assert that the outgoing call
+ * details contain the [CallsManager.EXTRA_VOIP_BACKWARDS_COMPATIBILITY_SUPPORTED] key
+ */
+ @LargeTest
+ @Test(timeout = 10000)
+ fun testCapabilityExchangeOutgoing_BackwardsCompat() {
+ setUpBackwardsCompatTest()
+ addAndVerifyCallExtensionTypeE2E(
+ TestUtils.OUTGOING_CALL_ATTRIBUTES,
+ waitForCallDetailExtras = true
+ )
+ }
+
+ /***********************************************************************************************
+ * Helpers
+ *********************************************************************************************/
+
+ /**
+ * Helper to add a call via CallsManager#addCall and block (if needed) until the connection
+ * extras are propagated into the call details.
+ *
+ * @param callAttributesCompat for the call.
+ * @param waitForCallDetailExtras used for waiting on the call details extras to be non-empty.
+ */
+ private fun addAndVerifyCallExtensionTypeE2E(
+ callAttributesCompat: CallAttributesCompat,
+ waitForCallDetailExtras: Boolean = false
+ ) {
+ runBlocking {
+ assertWithinTimeout_addCall(callAttributesCompat) {
+ launch {
+ val call = TestUtils.waitOnInCallServiceToReachXCalls(1)
+ Assert.assertNotNull("The returned Call object is <NULL>", call!!)
+
+ // Enforce waiting logic to ensure that the call details extras are populated.
+ if (waitForCallDetailExtras) {
+ TestUtils.waitOnCallExtras(call)
+ }
+
+ // Assert the call extra or call property from the details
+ assertCallExtraOrProperty(call)
+ // Always send disconnect signal if possible.
+ assertTrue(disconnect(DisconnectCause(DisconnectCause.LOCAL)))
+ }
+ }
+ }
+ }
+
+ /**
+ * Helper to assert the call extra or property set on the call coming from Telecom.
+ */
+ private fun assertCallExtraOrProperty(call: Call) {
+ // Call details should be present at this point
+ val callDetails = call.details!!
+ if (TestUtils.buildIsAtLeastU()) {
+ if (TestUtils.buildIsAtLeastV()) {
+ assertTrue(callDetails.hasProperty(CallsManager.PROPERTY_IS_TRANSACTIONAL))
+ } else if (Utils.hasPlatformV2Apis()) {
+ // We need to check the phone account, which requires accessing TelecomManager.
+ // Directly resolving the extension type via resolveCallExtensionsType() will
+ // provide that functionality so no need to rewrite it here.
+ assertEquals(
+ inCallServiceCompat.resolveCallExtensionsType(call),
+ InCallServiceCompat.CAPABILITY_EXCHANGE)
+ }
+ } else {
+ val containsBackwardsCompatKey = callDetails.extras != null && callDetails.extras
+ .containsKey(CallsManager.EXTRA_VOIP_BACKWARDS_COMPATIBILITY_SUPPORTED)
+ assertTrue(containsBackwardsCompatKey)
+ }
+ }
+}
diff --git a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/InCallServiceCompatTest.kt b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/InCallServiceCompatTest.kt
new file mode 100644
index 0000000..94f3a2e
--- /dev/null
+++ b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/InCallServiceCompatTest.kt
@@ -0,0 +1,224 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.telecom.test
+
+import android.Manifest
+import android.os.Build
+import android.telecom.Call
+import android.telecom.DisconnectCause
+import android.util.Log
+import androidx.annotation.RequiresApi
+import androidx.core.telecom.CallAttributesCompat
+import androidx.core.telecom.CallsManager
+import androidx.core.telecom.internal.InCallServiceCompat
+import androidx.core.telecom.internal.utils.Utils
+import androidx.core.telecom.test.utils.BaseTelecomTest
+import androidx.core.telecom.test.utils.TestUtils
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.rule.GrantPermissionRule
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import org.junit.After
+import org.junit.Assert
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * This test class verifies the [InCallServiceCompat] functionality around resolving the call
+ * extension type in order to determine the supported extensions between the VOIP app and the
+ * associated InCallServices. This test constructs calls via TelecomManager and modifies the call
+ * details (if required) to test each scenario. This is explained in more detail at the test level
+ * for each of the applicable cases below.
+ *
+ * Note: [Call] is package-private so we still need to leverage Telecom to create calls on our
+ * behalf for testing. The call properties and extras fields aren't mutable so we need to ensure
+ * that we wait for them to become available before accessing them.
+ */
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+@RequiresApi(Build.VERSION_CODES.O)
+@RunWith(AndroidJUnit4::class)
+class InCallServiceCompatTest : BaseTelecomTest() {
+ private lateinit var inCallServiceCompat: InCallServiceCompat
+
+ /**
+ * Grant READ_PHONE_NUMBERS permission as part of testing
+ * [InCallServiceCompat#resolveCallExtensionsType].
+ */
+ @get:Rule
+ val readPhoneNumbersRule: GrantPermissionRule =
+ GrantPermissionRule.grant(Manifest.permission.READ_PHONE_NUMBERS)!!
+
+ companion object {
+ /**
+ * Logging for within the test class.
+ */
+ internal val TAG = InCallServiceCompatTest::class.simpleName
+ }
+
+ @Before
+ fun setUp() {
+ Utils.resetUtils()
+ inCallServiceCompat = InCallServiceCompat(mContext)
+ }
+
+ @After
+ fun onDestroy() {
+ Utils.resetUtils()
+ }
+
+ /**
+ * Assert that EXTRAS is the extension type for calls made using the V1.5 ConnectionService +
+ * Extensions Library (Auto). The call should have the [CallsManager.EXTRA_VOIP_API_VERSION]
+ * defined in the extras.
+ *
+ * The contents of the call detail extras need to be modified to test calls using the V1.5
+ * ConnectionService + Extensions library (until E2E testing can be supported for it). This
+ * requires us to manually insert the [CallsManager.EXTRA_VOIP_API_VERSION] key into the bundle.
+ */
+ @LargeTest
+ @Test(timeout = 10000)
+ fun testResolveCallExtension_Extra() {
+ setUpBackwardsCompatTest()
+ val voipApiExtra = Pair(CallsManager.EXTRA_VOIP_API_VERSION, true)
+ addAndVerifyCallExtensionType(
+ TestUtils.OUTGOING_CALL_ATTRIBUTES,
+ InCallServiceCompat.EXTRAS,
+ extraToInclude = voipApiExtra)
+ }
+
+ /**
+ * Assert that CAPABILITY_EXCHANGE is the extension type for calls that either have the
+ * [CallsManager.PROPERTY_IS_TRANSACTIONAL] (V) defined as a property or the phone account
+ * supports transactional ops (U+). For pre-U devices, the call extras would define the
+ * [CallsManager.EXTRA_VOIP_BACKWARDS_COMPATIBILITY_SUPPORTED] key.
+ *
+ * Note: The version codes for V is not available so we need to enforce a strict manual check
+ * to ensure the V test path is not executed by incompatible devices.
+ */
+ @LargeTest
+ @Test(timeout = 10000)
+ fun testResolveCallExtension_CapabilityExchange() {
+ if (TestUtils.buildIsAtLeastU()) {
+ Log.w(TAG, "Setting up v2 tests for U+ device")
+ setUpV2Test()
+ } else {
+ Log.w(TAG, "Setting up backwards compatibility tests for pre-U device")
+ setUpBackwardsCompatTest()
+ }
+
+ // Add EXTRA_VOIP_BACKWARDS_COMPATIBILITY_SUPPORTED for pre-U testing
+ val backwardsCompatExtra = if (!TestUtils.buildIsAtLeastU())
+ Pair(CallsManager.EXTRA_VOIP_BACKWARDS_COMPATIBILITY_SUPPORTED, true)
+ else null
+ addAndVerifyCallExtensionType(
+ TestUtils.OUTGOING_CALL_ATTRIBUTES,
+ InCallServiceCompat.CAPABILITY_EXCHANGE,
+ // Waiting is not required for U+ testing
+ waitForCallDetailExtras = !TestUtils.buildIsAtLeastU(),
+ extraToInclude = backwardsCompatExtra
+ )
+ }
+
+ /**
+ * Assert that NONE is the extension type for calls with phone accounts that do not support
+ * transactional ops. Note that the caller must have had the read phone numbers permission.
+ *
+ * Note: Ensure that all extras are cleared before asserting extension type so that the phone
+ * account can be checked. For backwards compatibility tests, calls define the
+ * [CallsManager.EXTRA_VOIP_BACKWARDS_COMPATIBILITY_SUPPORTED] key in the details extras so this
+ * needs to be disregarded.
+ *
+ * We need to ensure that all extras/properties are ignored for testing so that the phone
+ * account can be checked to see if it supports transactional ops. In jetpack, this can only be
+ * verified on pre-U devices as those phone accounts are registered in Telecom without
+ * transactional ops. Keep in mind that because these calls are set up for backwards
+ * compatibility, they will have the [CallsManager.EXTRA_VOIP_BACKWARDS_COMPATIBILITY_SUPPORTED]
+ * extra in the details (which will need to be ignored during testing).
+ */
+ @LargeTest
+ @Test(timeout = 10000)
+ fun testResolveCallExtension_TransactionalOpsNotSupported() {
+ // Phone accounts that don't use the v2 APIs don't support transactional ops.
+ setUpBackwardsCompatTest()
+ addAndVerifyCallExtensionType(
+ TestUtils.OUTGOING_CALL_ATTRIBUTES,
+ InCallServiceCompat.NONE,
+ waitForCallDetailExtras = false
+ )
+ }
+
+ /***********************************************************************************************
+ * Helpers
+ *********************************************************************************************/
+
+ /**
+ * Helper to add a call via CallsManager#addCall and verify the extension type depending on
+ * the APIs that are leveraged.
+ *
+ * Note: The connection extras are not added into the call until the connection is successfully
+ * created. This is usually the case when the call moves from the CONNECTING state into either
+ * the DIALING/RINGING state. This would be the case for [CallsManager.EXTRA_VOIP_API_VERSION]
+ * (handled by auto) as well as for [CallsManager.EXTRA_VOIP_BACKWARDS_COMPATIBILITY_SUPPORTED]
+ * (see JetpackConnectionService#createSelfManagedConnection). Keep in mind that these extras
+ * would not be available in [InCalLService#onCallAdded], but after
+ * [Call#handleCreateConnectionSuccess] is invoked and the connection service extras are
+ * propagated into the call details via [Call#putConnectionServiceExtras].
+ *
+ * @param callAttributesCompat for the call.
+ * @param expectedType for call extension type.
+ * @param waitForCallDetailExtras used for waiting on the call details extras to be non-null.
+ * @param extraToInclude as part of the call extras.
+ */
+ private fun addAndVerifyCallExtensionType(
+ callAttributesCompat: CallAttributesCompat,
+ @InCallServiceCompat.Companion.CapabilityExchangeType expectedType: Int,
+ waitForCallDetailExtras: Boolean = true,
+ extraToInclude: Pair<String, Boolean>? = null
+ ) {
+ runBlocking {
+ assertWithinTimeout_addCall(callAttributesCompat) {
+ launch {
+ val call = TestUtils.waitOnInCallServiceToReachXCalls(1)
+ Assert.assertNotNull("The returned Call object is <NULL>", call!!)
+
+ // Enforce waiting logic to ensure that the call details extras are populated.
+ if (waitForCallDetailExtras) {
+ TestUtils.waitOnCallExtras(call)
+ }
+
+ val callDetails = call.details
+ // Clear out extras to isolate the testing scenarios.
+ call.details.extras?.clear()
+ // Add extraToInclude for testing.
+ if (extraToInclude != null) {
+ callDetails.extras?.putBoolean(extraToInclude.first, extraToInclude.second)
+ }
+
+ // Assert call extension type.
+ assertEquals(expectedType, inCallServiceCompat.resolveCallExtensionsType(call))
+ // Always send disconnect signal if possible.
+ Assert.assertTrue(disconnect(DisconnectCause(DisconnectCause.LOCAL)))
+ }
+ }
+ }
+ }
+}
diff --git a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/utils/TestUtils.kt b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/utils/TestUtils.kt
index 5aba448..c44414ca 100644
--- a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/utils/TestUtils.kt
+++ b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/utils/TestUtils.kt
@@ -19,6 +19,7 @@
import android.content.Context
import android.media.AudioManager
import android.net.Uri
+import android.os.Build
import android.os.Build.VERSION_CODES
import android.os.UserHandle
import android.os.UserManager
@@ -310,4 +311,39 @@
)
}
}
+
+ /**
+ * Helper to wait on the call detail extras to be populated from the connection service
+ */
+ suspend fun waitOnCallExtras(call: Call) {
+ try {
+ withTimeout(TestUtils.WAIT_ON_CALL_STATE_TIMEOUT) {
+ while (isActive /* aka within timeout window */ && call.details?.extras == null) {
+ yield() // another mechanism to stop the while loop if the coroutine is dead
+ delay(1) // sleep x millisecond(s) instead of spamming check
+ }
+ }
+ } catch (e: TimeoutCancellationException) {
+ Log.i(TestUtils.LOG_TAG, "waitOnCallExtras: timeout reached")
+ TestUtils.dumpTelecom()
+ MockInCallService.destroyAllCalls()
+ throw AssertionError("Expected call detail extras to be non-null.")
+ }
+ }
+
+ /**
+ * Used for testing in V. The build version is not available for referencing so this helper
+ * performs a manual check instead.
+ */
+ fun buildIsAtLeastV(): Boolean {
+ // V is not referencable as a valid build version yet. Enforce strict manual check instead.
+ return Build.VERSION.SDK_INT > 34
+ }
+
+ /**
+ * Determine if the current build supports at least U.
+ */
+ fun buildIsAtLeastU(): Boolean {
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
+ }
}
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/CallsManager.kt b/core/core-telecom/src/main/java/androidx/core/telecom/CallsManager.kt
index fd67da4..27df20c 100644
--- a/core/core-telecom/src/main/java/androidx/core/telecom/CallsManager.kt
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/CallsManager.kt
@@ -82,6 +82,11 @@
annotation class Capability
/**
+ * Set on Connections that are using ConnectionService+AUTO specific extension layer.
+ */
+ internal const val EXTRA_VOIP_API_VERSION = "android.telecom.extra.VOIP_API_VERSION"
+
+ /**
* Set on Jetpack Connections that are emulating the transactional APIs using
* ConnectionService.
*/
@@ -89,6 +94,15 @@
"android.telecom.extra.VOIP_BACKWARDS_COMPATIBILITY_SUPPORTED"
/**
+ * The connection is using transactional call APIs.
+ *
+ *
+ * The underlying connection was added as a transactional call via the
+ * [TelecomManager.addCall] API.
+ */
+ internal const val PROPERTY_IS_TRANSACTIONAL = 0x00008000
+
+ /**
* If your VoIP application does not want support any of the capabilities below, then your
* application can register with [CAPABILITY_BASELINE].
*
diff --git a/camera/camera-effects/src/main/java/androidx/camera/effects/Portrait.java b/core/core-telecom/src/main/java/androidx/core/telecom/internal/CallCompat.kt
similarity index 63%
copy from camera/camera-effects/src/main/java/androidx/camera/effects/Portrait.java
copy to core/core-telecom/src/main/java/androidx/core/telecom/internal/CallCompat.kt
index 7a355f8..890707d 100644
--- a/camera/camera-effects/src/main/java/androidx/camera/effects/Portrait.java
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/internal/CallCompat.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,15 +14,12 @@
* limitations under the License.
*/
-package androidx.camera.effects;
+package androidx.core.telecom.internal
-import androidx.annotation.RestrictTo;
+import android.telecom.Call
+import kotlinx.coroutines.CoroutineScope
-/**
- * Provides a portrait post-processing effect.
- *
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class Portrait {
- // TODO: implement this
+internal class CallCompat(call: Call, block: CoroutineScope.() -> Unit) {
+ private val mCall: Call = call
+ private val mBlock: CoroutineScope.() -> Unit = block
}
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/internal/InCallServiceCompat.kt b/core/core-telecom/src/main/java/androidx/core/telecom/internal/InCallServiceCompat.kt
new file mode 100644
index 0000000..f3063c0
--- /dev/null
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/internal/InCallServiceCompat.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.telecom.internal
+
+import android.Manifest
+import android.content.Context
+import android.content.pm.PackageManager
+import android.os.Build
+import android.os.Bundle
+import android.telecom.Call
+import android.telecom.InCallService
+import android.telecom.PhoneAccount
+import android.telecom.TelecomManager
+import android.util.Log
+import androidx.annotation.IntDef
+import androidx.annotation.RequiresApi
+import androidx.core.content.ContextCompat
+import androidx.core.telecom.CallsManager
+
+/**
+ * This class defines the Jetpack ICS layer which will be leveraged as part of supporting VOIP app
+ * actions.
+ */
+@RequiresApi(Build.VERSION_CODES.M)
+internal class InCallServiceCompat(context: Context) : InCallService() {
+ private val mContext: Context = context
+
+ companion object {
+ /**
+ * Constants used to denote the extension level supported by the VOIP app.
+ */
+ @Retention(AnnotationRetention.SOURCE)
+ @IntDef(NONE, EXTRAS, CAPABILITY_EXCHANGE, UNKNOWN)
+ internal annotation class CapabilityExchangeType
+
+ internal const val NONE = 0
+ internal const val EXTRAS = 1
+ internal const val CAPABILITY_EXCHANGE = 2
+ internal const val UNKNOWN = 3
+
+ private val TAG = InCallServiceCompat::class.simpleName
+ }
+
+ fun onCreateCall(call: Call): CallCompat {
+ Log.d(TAG, "onCreateCall: call = $call")
+ return with(this) {
+ CallCompat(call) {
+ }
+ }
+ }
+
+ fun onRemoveCall(call: CallCompat) {
+ Log.d(TAG, "onRemoveCall: call = $call")
+ }
+
+ /**
+ * Internal helper used by the [InCallService] to help resolve the call extension type. This
+ * is invoked before capability exchange between the [InCallService] and VOIP app starts to
+ * ensure the necessary features are enabled to support it.
+ *
+ * If the call is placed using the V1.5 ConnectionService + Extensions Library (Auto Case), the
+ * call will have the [CallsManager.EXTRA_VOIP_API_VERSION] defined in the extras. The call
+ * extension would be resolved as [InCallServiceCompat.EXTRAS].
+ *
+ * If the call is using the v2 APIs and the phone account associated with the call supports
+ * transactional ops (U+) or the call has the [CallsManager.PROPERTY_IS_TRANSACTIONAL] property
+ * defined (on V devices), then the extension type is [InCallServiceCompat.CAPABILITY_EXCHANGE].
+ *
+ * If the call is added via CallsManager#addCall on pre-U devices and the
+ * [CallsManager.EXTRA_VOIP_BACKWARDS_COMPATIBILITY_SUPPORTED] is present in the call extras,
+ * the extension type also resolves to [InCallServiceCompat.CAPABILITY_EXCHANGE].
+ *
+ * In the case that none of the cases above apply and the phone account is found not to support
+ * transactional ops (assumes that caller has [android.Manifest.permission.READ_PHONE_NUMBERS]
+ * permission), then the extension type is [InCallServiceCompat.NONE].
+ *
+ * If the caller does not have the required permission to retrieve the phone account, then
+ * the extension type will be [InCallServiceCompat.UNKNOWN], until it can be resolved.
+ *
+ * @param call to resolve the extension type for.
+ * @return the extension type [InCallServiceCompat.CapabilityExchangeType] resolved for the
+ * call.
+ */
+ @RequiresApi(Build.VERSION_CODES.O)
+ @CapabilityExchangeType
+ internal fun resolveCallExtensionsType(call: Call): Int {
+ var callDetails = call.details
+ val callExtras = callDetails?.extras ?: Bundle()
+
+ if (callExtras.containsKey(CallsManager.EXTRA_VOIP_API_VERSION)) {
+ return EXTRAS
+ }
+ if (callDetails?.hasProperty(CallsManager.PROPERTY_IS_TRANSACTIONAL) == true || callExtras
+ .containsKey(CallsManager.EXTRA_VOIP_BACKWARDS_COMPATIBILITY_SUPPORTED)) {
+ return CAPABILITY_EXCHANGE
+ }
+ // Verify read phone numbers permission to see if phone account supports transactional ops.
+ if (ContextCompat.checkSelfPermission(mContext, Manifest.permission.READ_PHONE_NUMBERS)
+ == PackageManager.PERMISSION_GRANTED) {
+ var telecomManager = mContext.getSystemService(Context.TELECOM_SERVICE)
+ as TelecomManager
+ var phoneAccount = telecomManager.getPhoneAccount(callDetails?.accountHandle)
+ if (phoneAccount?.hasCapabilities(
+ PhoneAccount.CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS) == true) {
+ return CAPABILITY_EXCHANGE
+ } else {
+ return NONE
+ }
+ }
+
+ Log.i(TAG, "Unable to resolve call extension type. Returning $UNKNOWN.")
+ return UNKNOWN
+ }
+}
diff --git a/core/haptics/haptics/api/current.txt b/core/haptics/haptics/api/current.txt
index 87cfe87..b4b7e54 100644
--- a/core/haptics/haptics/api/current.txt
+++ b/core/haptics/haptics/api/current.txt
@@ -3,7 +3,7 @@
public interface HapticManager {
method public static androidx.core.haptics.HapticManager create(android.content.Context context);
- method @RequiresPermission(android.Manifest.permission.VIBRATE) public void play(androidx.core.haptics.signal.PredefinedEffect effect);
+ method @RequiresPermission(android.Manifest.permission.VIBRATE) public void play(androidx.core.haptics.signal.HapticSignal signal);
field public static final androidx.core.haptics.HapticManager.Companion Companion;
}
@@ -15,15 +15,155 @@
package androidx.core.haptics.signal {
- public final class PredefinedEffect {
- field public static final androidx.core.haptics.signal.PredefinedEffect.Companion Companion;
- field public static final androidx.core.haptics.signal.PredefinedEffect PredefinedClick;
- field public static final androidx.core.haptics.signal.PredefinedEffect PredefinedDoubleClick;
- field public static final androidx.core.haptics.signal.PredefinedEffect PredefinedHeavyClick;
- field public static final androidx.core.haptics.signal.PredefinedEffect PredefinedTick;
+ public final class CompositionSignal extends androidx.core.haptics.signal.FiniteSignal {
+ ctor public CompositionSignal(java.util.List<? extends androidx.core.haptics.signal.CompositionSignal.Atom> atoms);
+ method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom click();
+ method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom click(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+ method public static androidx.core.haptics.signal.CompositionSignal compositionOf(androidx.core.haptics.signal.CompositionSignal.Atom... atoms);
+ method public java.util.List<androidx.core.haptics.signal.CompositionSignal.Atom> getAtoms();
+ method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom lowTick();
+ method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom lowTick(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) public static androidx.core.haptics.signal.CompositionSignal.OffAtom off(java.time.Duration duration);
+ method public static androidx.core.haptics.signal.CompositionSignal.OffAtom off(long durationMillis);
+ method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom quickFall();
+ method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom quickFall(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+ method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom quickRise();
+ method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom quickRise(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+ method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom slowRise();
+ method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom slowRise(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+ method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom spin();
+ method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom spin(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+ method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom thud();
+ method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom thud(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+ method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom tick();
+ method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom tick(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+ property public final java.util.List<androidx.core.haptics.signal.CompositionSignal.Atom> atoms;
+ field public static final androidx.core.haptics.signal.CompositionSignal.Companion Companion;
}
- public static final class PredefinedEffect.Companion {
+ public abstract static class CompositionSignal.Atom {
+ }
+
+ public static final class CompositionSignal.Companion {
+ method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom click();
+ method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom click(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+ method public androidx.core.haptics.signal.CompositionSignal compositionOf(androidx.core.haptics.signal.CompositionSignal.Atom... atoms);
+ method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom lowTick();
+ method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom lowTick(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) public androidx.core.haptics.signal.CompositionSignal.OffAtom off(java.time.Duration duration);
+ method public androidx.core.haptics.signal.CompositionSignal.OffAtom off(long durationMillis);
+ method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom quickFall();
+ method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom quickFall(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+ method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom quickRise();
+ method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom quickRise(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+ method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom slowRise();
+ method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom slowRise(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+ method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom spin();
+ method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom spin(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+ method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom thud();
+ method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom thud(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+ method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom tick();
+ method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom tick(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+ }
+
+ public static final class CompositionSignal.OffAtom extends androidx.core.haptics.signal.CompositionSignal.Atom {
+ method public long getDurationMillis();
+ property public final long durationMillis;
+ }
+
+ public static final class CompositionSignal.PrimitiveAtom extends androidx.core.haptics.signal.CompositionSignal.Atom {
+ method public float getAmplitudeScale();
+ method public int getType();
+ method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom withAmplitudeScale(@FloatRange(from=0.0, to=1.0) float newAmplitudeScale);
+ property public final float amplitudeScale;
+ property public final int type;
+ field public static final int CLICK = 1; // 0x1
+ field public static final androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom.Companion Companion;
+ field public static final int LOW_TICK = 8; // 0x8
+ field public static final int QUICK_FALL = 6; // 0x6
+ field public static final int QUICK_RISE = 4; // 0x4
+ field public static final int SLOW_RISE = 5; // 0x5
+ field public static final int SPIN = 3; // 0x3
+ field public static final int THUD = 2; // 0x2
+ field public static final int TICK = 7; // 0x7
+ }
+
+ public static final class CompositionSignal.PrimitiveAtom.Companion {
+ }
+
+ public abstract class FiniteSignal extends androidx.core.haptics.signal.HapticSignal {
+ }
+
+ public abstract class HapticSignal {
+ }
+
+ public abstract class InfiniteSignal extends androidx.core.haptics.signal.HapticSignal {
+ }
+
+ public final class PredefinedEffectSignal extends androidx.core.haptics.signal.FiniteSignal {
+ method public static androidx.core.haptics.signal.PredefinedEffectSignal predefinedClick();
+ method public static androidx.core.haptics.signal.PredefinedEffectSignal predefinedDoubleClick();
+ method public static androidx.core.haptics.signal.PredefinedEffectSignal predefinedHeavyClick();
+ method public static androidx.core.haptics.signal.PredefinedEffectSignal predefinedTick();
+ field public static final androidx.core.haptics.signal.PredefinedEffectSignal.Companion Companion;
+ }
+
+ public static final class PredefinedEffectSignal.Companion {
+ method public androidx.core.haptics.signal.PredefinedEffectSignal predefinedClick();
+ method public androidx.core.haptics.signal.PredefinedEffectSignal predefinedDoubleClick();
+ method public androidx.core.haptics.signal.PredefinedEffectSignal predefinedHeavyClick();
+ method public androidx.core.haptics.signal.PredefinedEffectSignal predefinedTick();
+ }
+
+ public final class RepeatingWaveformSignal extends androidx.core.haptics.signal.InfiniteSignal {
+ method public androidx.core.haptics.signal.WaveformSignal? getInitialWaveform();
+ method public androidx.core.haptics.signal.WaveformSignal getRepeatingWaveform();
+ property public final androidx.core.haptics.signal.WaveformSignal? initialWaveform;
+ property public final androidx.core.haptics.signal.WaveformSignal repeatingWaveform;
+ }
+
+ public final class WaveformSignal extends androidx.core.haptics.signal.FiniteSignal {
+ ctor public WaveformSignal(java.util.List<? extends androidx.core.haptics.signal.WaveformSignal.Atom> atoms);
+ method public java.util.List<androidx.core.haptics.signal.WaveformSignal.Atom> getAtoms();
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) public static androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom off(java.time.Duration duration);
+ method public static androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom off(long durationMillis);
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) public static androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom on(java.time.Duration duration);
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) public static androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom on(java.time.Duration duration, @FloatRange(from=0.0, to=1.0) float amplitude);
+ method public static androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom on(long durationMillis);
+ method public static androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom on(long durationMillis, @FloatRange(from=0.0, to=1.0) float amplitude);
+ method public androidx.core.haptics.signal.RepeatingWaveformSignal repeat();
+ method public static androidx.core.haptics.signal.RepeatingWaveformSignal repeatingWaveformOf(androidx.core.haptics.signal.WaveformSignal.Atom... atoms);
+ method public androidx.core.haptics.signal.RepeatingWaveformSignal thenRepeat(androidx.core.haptics.signal.WaveformSignal waveformToRepeat);
+ method public androidx.core.haptics.signal.RepeatingWaveformSignal thenRepeat(androidx.core.haptics.signal.WaveformSignal.Atom... atoms);
+ method public static androidx.core.haptics.signal.WaveformSignal waveformOf(androidx.core.haptics.signal.WaveformSignal.Atom... atoms);
+ property public final java.util.List<androidx.core.haptics.signal.WaveformSignal.Atom> atoms;
+ field public static final androidx.core.haptics.signal.WaveformSignal.Companion Companion;
+ }
+
+ public abstract static class WaveformSignal.Atom {
+ }
+
+ public static final class WaveformSignal.Companion {
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) public androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom off(java.time.Duration duration);
+ method public androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom off(long durationMillis);
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) public androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom on(java.time.Duration duration);
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) public androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom on(java.time.Duration duration, @FloatRange(from=0.0, to=1.0) float amplitude);
+ method public androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom on(long durationMillis);
+ method public androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom on(long durationMillis, @FloatRange(from=0.0, to=1.0) float amplitude);
+ method public androidx.core.haptics.signal.RepeatingWaveformSignal repeatingWaveformOf(androidx.core.haptics.signal.WaveformSignal.Atom... atoms);
+ method public androidx.core.haptics.signal.WaveformSignal waveformOf(androidx.core.haptics.signal.WaveformSignal.Atom... atoms);
+ }
+
+ public static final class WaveformSignal.ConstantVibrationAtom extends androidx.core.haptics.signal.WaveformSignal.Atom {
+ method public float getAmplitude();
+ method public long getDurationMillis();
+ property public final float amplitude;
+ property public final long durationMillis;
+ field public static final androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom.Companion Companion;
+ field public static final float DEFAULT_AMPLITUDE = -1.0f;
+ }
+
+ public static final class WaveformSignal.ConstantVibrationAtom.Companion {
}
}
diff --git a/core/haptics/haptics/api/restricted_current.txt b/core/haptics/haptics/api/restricted_current.txt
index 87cfe87..b4b7e54 100644
--- a/core/haptics/haptics/api/restricted_current.txt
+++ b/core/haptics/haptics/api/restricted_current.txt
@@ -3,7 +3,7 @@
public interface HapticManager {
method public static androidx.core.haptics.HapticManager create(android.content.Context context);
- method @RequiresPermission(android.Manifest.permission.VIBRATE) public void play(androidx.core.haptics.signal.PredefinedEffect effect);
+ method @RequiresPermission(android.Manifest.permission.VIBRATE) public void play(androidx.core.haptics.signal.HapticSignal signal);
field public static final androidx.core.haptics.HapticManager.Companion Companion;
}
@@ -15,15 +15,155 @@
package androidx.core.haptics.signal {
- public final class PredefinedEffect {
- field public static final androidx.core.haptics.signal.PredefinedEffect.Companion Companion;
- field public static final androidx.core.haptics.signal.PredefinedEffect PredefinedClick;
- field public static final androidx.core.haptics.signal.PredefinedEffect PredefinedDoubleClick;
- field public static final androidx.core.haptics.signal.PredefinedEffect PredefinedHeavyClick;
- field public static final androidx.core.haptics.signal.PredefinedEffect PredefinedTick;
+ public final class CompositionSignal extends androidx.core.haptics.signal.FiniteSignal {
+ ctor public CompositionSignal(java.util.List<? extends androidx.core.haptics.signal.CompositionSignal.Atom> atoms);
+ method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom click();
+ method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom click(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+ method public static androidx.core.haptics.signal.CompositionSignal compositionOf(androidx.core.haptics.signal.CompositionSignal.Atom... atoms);
+ method public java.util.List<androidx.core.haptics.signal.CompositionSignal.Atom> getAtoms();
+ method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom lowTick();
+ method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom lowTick(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) public static androidx.core.haptics.signal.CompositionSignal.OffAtom off(java.time.Duration duration);
+ method public static androidx.core.haptics.signal.CompositionSignal.OffAtom off(long durationMillis);
+ method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom quickFall();
+ method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom quickFall(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+ method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom quickRise();
+ method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom quickRise(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+ method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom slowRise();
+ method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom slowRise(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+ method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom spin();
+ method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom spin(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+ method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom thud();
+ method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom thud(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+ method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom tick();
+ method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom tick(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+ property public final java.util.List<androidx.core.haptics.signal.CompositionSignal.Atom> atoms;
+ field public static final androidx.core.haptics.signal.CompositionSignal.Companion Companion;
}
- public static final class PredefinedEffect.Companion {
+ public abstract static class CompositionSignal.Atom {
+ }
+
+ public static final class CompositionSignal.Companion {
+ method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom click();
+ method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom click(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+ method public androidx.core.haptics.signal.CompositionSignal compositionOf(androidx.core.haptics.signal.CompositionSignal.Atom... atoms);
+ method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom lowTick();
+ method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom lowTick(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) public androidx.core.haptics.signal.CompositionSignal.OffAtom off(java.time.Duration duration);
+ method public androidx.core.haptics.signal.CompositionSignal.OffAtom off(long durationMillis);
+ method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom quickFall();
+ method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom quickFall(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+ method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom quickRise();
+ method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom quickRise(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+ method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom slowRise();
+ method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom slowRise(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+ method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom spin();
+ method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom spin(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+ method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom thud();
+ method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom thud(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+ method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom tick();
+ method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom tick(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+ }
+
+ public static final class CompositionSignal.OffAtom extends androidx.core.haptics.signal.CompositionSignal.Atom {
+ method public long getDurationMillis();
+ property public final long durationMillis;
+ }
+
+ public static final class CompositionSignal.PrimitiveAtom extends androidx.core.haptics.signal.CompositionSignal.Atom {
+ method public float getAmplitudeScale();
+ method public int getType();
+ method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom withAmplitudeScale(@FloatRange(from=0.0, to=1.0) float newAmplitudeScale);
+ property public final float amplitudeScale;
+ property public final int type;
+ field public static final int CLICK = 1; // 0x1
+ field public static final androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom.Companion Companion;
+ field public static final int LOW_TICK = 8; // 0x8
+ field public static final int QUICK_FALL = 6; // 0x6
+ field public static final int QUICK_RISE = 4; // 0x4
+ field public static final int SLOW_RISE = 5; // 0x5
+ field public static final int SPIN = 3; // 0x3
+ field public static final int THUD = 2; // 0x2
+ field public static final int TICK = 7; // 0x7
+ }
+
+ public static final class CompositionSignal.PrimitiveAtom.Companion {
+ }
+
+ public abstract class FiniteSignal extends androidx.core.haptics.signal.HapticSignal {
+ }
+
+ public abstract class HapticSignal {
+ }
+
+ public abstract class InfiniteSignal extends androidx.core.haptics.signal.HapticSignal {
+ }
+
+ public final class PredefinedEffectSignal extends androidx.core.haptics.signal.FiniteSignal {
+ method public static androidx.core.haptics.signal.PredefinedEffectSignal predefinedClick();
+ method public static androidx.core.haptics.signal.PredefinedEffectSignal predefinedDoubleClick();
+ method public static androidx.core.haptics.signal.PredefinedEffectSignal predefinedHeavyClick();
+ method public static androidx.core.haptics.signal.PredefinedEffectSignal predefinedTick();
+ field public static final androidx.core.haptics.signal.PredefinedEffectSignal.Companion Companion;
+ }
+
+ public static final class PredefinedEffectSignal.Companion {
+ method public androidx.core.haptics.signal.PredefinedEffectSignal predefinedClick();
+ method public androidx.core.haptics.signal.PredefinedEffectSignal predefinedDoubleClick();
+ method public androidx.core.haptics.signal.PredefinedEffectSignal predefinedHeavyClick();
+ method public androidx.core.haptics.signal.PredefinedEffectSignal predefinedTick();
+ }
+
+ public final class RepeatingWaveformSignal extends androidx.core.haptics.signal.InfiniteSignal {
+ method public androidx.core.haptics.signal.WaveformSignal? getInitialWaveform();
+ method public androidx.core.haptics.signal.WaveformSignal getRepeatingWaveform();
+ property public final androidx.core.haptics.signal.WaveformSignal? initialWaveform;
+ property public final androidx.core.haptics.signal.WaveformSignal repeatingWaveform;
+ }
+
+ public final class WaveformSignal extends androidx.core.haptics.signal.FiniteSignal {
+ ctor public WaveformSignal(java.util.List<? extends androidx.core.haptics.signal.WaveformSignal.Atom> atoms);
+ method public java.util.List<androidx.core.haptics.signal.WaveformSignal.Atom> getAtoms();
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) public static androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom off(java.time.Duration duration);
+ method public static androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom off(long durationMillis);
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) public static androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom on(java.time.Duration duration);
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) public static androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom on(java.time.Duration duration, @FloatRange(from=0.0, to=1.0) float amplitude);
+ method public static androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom on(long durationMillis);
+ method public static androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom on(long durationMillis, @FloatRange(from=0.0, to=1.0) float amplitude);
+ method public androidx.core.haptics.signal.RepeatingWaveformSignal repeat();
+ method public static androidx.core.haptics.signal.RepeatingWaveformSignal repeatingWaveformOf(androidx.core.haptics.signal.WaveformSignal.Atom... atoms);
+ method public androidx.core.haptics.signal.RepeatingWaveformSignal thenRepeat(androidx.core.haptics.signal.WaveformSignal waveformToRepeat);
+ method public androidx.core.haptics.signal.RepeatingWaveformSignal thenRepeat(androidx.core.haptics.signal.WaveformSignal.Atom... atoms);
+ method public static androidx.core.haptics.signal.WaveformSignal waveformOf(androidx.core.haptics.signal.WaveformSignal.Atom... atoms);
+ property public final java.util.List<androidx.core.haptics.signal.WaveformSignal.Atom> atoms;
+ field public static final androidx.core.haptics.signal.WaveformSignal.Companion Companion;
+ }
+
+ public abstract static class WaveformSignal.Atom {
+ }
+
+ public static final class WaveformSignal.Companion {
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) public androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom off(java.time.Duration duration);
+ method public androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom off(long durationMillis);
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) public androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom on(java.time.Duration duration);
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) public androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom on(java.time.Duration duration, @FloatRange(from=0.0, to=1.0) float amplitude);
+ method public androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom on(long durationMillis);
+ method public androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom on(long durationMillis, @FloatRange(from=0.0, to=1.0) float amplitude);
+ method public androidx.core.haptics.signal.RepeatingWaveformSignal repeatingWaveformOf(androidx.core.haptics.signal.WaveformSignal.Atom... atoms);
+ method public androidx.core.haptics.signal.WaveformSignal waveformOf(androidx.core.haptics.signal.WaveformSignal.Atom... atoms);
+ }
+
+ public static final class WaveformSignal.ConstantVibrationAtom extends androidx.core.haptics.signal.WaveformSignal.Atom {
+ method public float getAmplitude();
+ method public long getDurationMillis();
+ property public final float amplitude;
+ property public final long durationMillis;
+ field public static final androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom.Companion Companion;
+ field public static final float DEFAULT_AMPLITUDE = -1.0f;
+ }
+
+ public static final class WaveformSignal.ConstantVibrationAtom.Companion {
}
}
diff --git a/core/haptics/haptics/build.gradle b/core/haptics/haptics/build.gradle
index 203d4db..7dc493a 100644
--- a/core/haptics/haptics/build.gradle
+++ b/core/haptics/haptics/build.gradle
@@ -15,7 +15,6 @@
*/
import androidx.build.LibraryType
-import androidx.build.RunApiTasks
plugins {
id("AndroidXPlugin")
@@ -33,8 +32,7 @@
androidTestImplementation(libs.testCore)
androidTestImplementation(libs.testRunner)
- androidTestImplementation(libs.mockitoCore4)
- androidTestImplementation(libs.dexmakerMockitoInline)
+ implementation(libs.truth)
}
android {
diff --git a/core/haptics/haptics/integration-tests/demos/src/main/AndroidManifest.xml b/core/haptics/haptics/integration-tests/demos/src/main/AndroidManifest.xml
index 4c19b57..ea32d19 100644
--- a/core/haptics/haptics/integration-tests/demos/src/main/AndroidManifest.xml
+++ b/core/haptics/haptics/integration-tests/demos/src/main/AndroidManifest.xml
@@ -22,7 +22,7 @@
android:theme="@style/AppTheme">
<activity
android:allowBackup="false"
- android:name=".HapticSamplesActivity"
+ android:name=".HapticDemosActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
diff --git a/core/haptics/haptics/integration-tests/demos/src/main/java/androidx/core/haptics/demos/HapticDemosActivity.kt b/core/haptics/haptics/integration-tests/demos/src/main/java/androidx/core/haptics/demos/HapticDemosActivity.kt
new file mode 100644
index 0000000..b147e5c
--- /dev/null
+++ b/core/haptics/haptics/integration-tests/demos/src/main/java/androidx/core/haptics/demos/HapticDemosActivity.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.haptics.demos
+
+import android.os.Bundle
+import android.widget.Button
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.haptics.HapticManager
+import androidx.core.haptics.signal.CompositionSignal.Companion.click
+import androidx.core.haptics.signal.CompositionSignal.Companion.compositionOf
+import androidx.core.haptics.signal.PredefinedEffectSignal.Companion.predefinedClick
+import androidx.core.haptics.signal.WaveformSignal.Companion.off
+import androidx.core.haptics.signal.WaveformSignal.Companion.on
+import androidx.core.haptics.signal.WaveformSignal.Companion.waveformOf
+
+/**
+ * Demonstrations of multiple haptic signal samples.
+ */
+class HapticDemosActivity : AppCompatActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.haptics_demos_activity)
+
+ val hapticManager = HapticManager.create(this)
+ findViewById<Button>(R.id.standard_click_btn).setOnClickListener {
+ hapticManager.play(predefinedClick())
+ }
+ findViewById<Button>(R.id.scaled_click_btn).setOnClickListener {
+ hapticManager.play(compositionOf(click().withAmplitudeScale(0.8f)))
+ }
+ findViewById<Button>(R.id.on_off_pattern_btn).setOnClickListener {
+ hapticManager.play(
+ waveformOf(
+ on(durationMillis = 350),
+ off(durationMillis = 250),
+ on(durationMillis = 350),
+ )
+ )
+ }
+ findViewById<Button>(R.id.repeating_waveform_btn).setOnClickListener {
+ hapticManager.play(
+ waveformOf(
+ on(durationMillis = 20),
+ off(durationMillis = 50),
+ on(durationMillis = 20),
+ ).thenRepeat(
+ // 500ms off
+ off(durationMillis = 500),
+ // 600ms ramp up with 50% increments
+ on(durationMillis = 100, amplitude = 0.1f),
+ on(durationMillis = 100, amplitude = 0.15f),
+ on(durationMillis = 100, amplitude = 0.22f),
+ on(durationMillis = 100, amplitude = 0.34f),
+ on(durationMillis = 100, amplitude = 0.51f),
+ on(durationMillis = 100, amplitude = 0.76f),
+ // 400ms at max amplitude
+ on(durationMillis = 400, amplitude = 1f),
+ )
+ )
+ }
+ }
+}
diff --git a/core/haptics/haptics/integration-tests/demos/src/main/java/androidx/core/haptics/demos/HapticSamplesActivity.kt b/core/haptics/haptics/integration-tests/demos/src/main/java/androidx/core/haptics/demos/HapticSamplesActivity.kt
deleted file mode 100644
index 5fb1076..0000000
--- a/core/haptics/haptics/integration-tests/demos/src/main/java/androidx/core/haptics/demos/HapticSamplesActivity.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.core.haptics.demos
-
-import android.os.Bundle
-import android.widget.Button
-import androidx.appcompat.app.AppCompatActivity
-import androidx.core.haptics.HapticManager
-import androidx.core.haptics.signal.PredefinedEffect.Companion.PredefinedClick
-
-/**
- * Demo with multiple selection of haptic effect samples.
- */
-class HapticSamplesActivity : AppCompatActivity() {
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.haptic_samples_activity)
-
- val hapticManager = HapticManager.create(this)
- findViewById<Button>(R.id.standard_click_btn).setOnClickListener {
- hapticManager.play(PredefinedClick)
- }
- }
-}
diff --git a/core/haptics/haptics/integration-tests/demos/src/main/res/layout/haptic_samples_activity.xml b/core/haptics/haptics/integration-tests/demos/src/main/res/layout/haptics_demos_activity.xml
similarity index 64%
rename from core/haptics/haptics/integration-tests/demos/src/main/res/layout/haptic_samples_activity.xml
rename to core/haptics/haptics/integration-tests/demos/src/main/res/layout/haptics_demos_activity.xml
index 24002ee..8b48bab 100644
--- a/core/haptics/haptics/integration-tests/demos/src/main/res/layout/haptic_samples_activity.xml
+++ b/core/haptics/haptics/integration-tests/demos/src/main/res/layout/haptics_demos_activity.xml
@@ -20,7 +20,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
- tools:context="androidx.core.haptics.demos.HapticSamplesActivity">
+ tools:context=".HapticDemosActivity">
<Button
android:id="@+id/standard_click_btn"
@@ -28,4 +28,22 @@
android:layout_height="wrap_content"
android:text="@string/standard_click" />
+ <Button
+ android:id="@+id/scaled_click_btn"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/scaled_click" />
+
+ <Button
+ android:id="@+id/on_off_pattern_btn"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/on_off_pattern" />
+
+ <Button
+ android:id="@+id/repeating_waveform_btn"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/repeating_waveform" />
+
</LinearLayout>
diff --git a/core/haptics/haptics/integration-tests/demos/src/main/res/values/donottranslate-strings.xml b/core/haptics/haptics/integration-tests/demos/src/main/res/values/donottranslate-strings.xml
index 903a0be..e79e071 100644
--- a/core/haptics/haptics/integration-tests/demos/src/main/res/values/donottranslate-strings.xml
+++ b/core/haptics/haptics/integration-tests/demos/src/main/res/values/donottranslate-strings.xml
@@ -17,4 +17,7 @@
<resources>
<string name="app_name">Haptic Demos</string>
<string name="standard_click">Standard Click</string>
+ <string name="scaled_click">Scaled Click</string>
+ <string name="on_off_pattern">On/Off Pattern</string>
+ <string name="repeating_waveform">Repeating Waveform</string>
</resources>
diff --git a/core/haptics/haptics/samples/src/main/java/androidx/core/haptics/samples/CompositionSignalSamples.kt b/core/haptics/haptics/samples/src/main/java/androidx/core/haptics/samples/CompositionSignalSamples.kt
new file mode 100644
index 0000000..856b999
--- /dev/null
+++ b/core/haptics/haptics/samples/src/main/java/androidx/core/haptics/samples/CompositionSignalSamples.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.haptics.samples
+
+import androidx.annotation.Sampled
+import androidx.core.haptics.signal.CompositionSignal.Companion.compositionOf
+import androidx.core.haptics.signal.CompositionSignal.Companion.off
+import androidx.core.haptics.signal.CompositionSignal.Companion.quickFall
+import androidx.core.haptics.signal.CompositionSignal.Companion.slowRise
+import androidx.core.haptics.signal.CompositionSignal.Companion.thud
+
+/**
+ * Sample showing how to create a composition signal with scaled effects and off atoms.
+ */
+@Sampled
+fun CompositionSignalOfScaledEffectsAndOff() {
+ compositionOf(
+ slowRise().withAmplitudeScale(0.7f),
+ quickFall().withAmplitudeScale(0.7f),
+ off(durationMillis = 50),
+ thud(),
+ )
+}
diff --git a/core/haptics/haptics/samples/src/main/java/androidx/core/haptics/samples/HapticManagerSamples.kt b/core/haptics/haptics/samples/src/main/java/androidx/core/haptics/samples/HapticManagerSamples.kt
index e1d3df4..83ce76d 100644
--- a/core/haptics/haptics/samples/src/main/java/androidx/core/haptics/samples/HapticManagerSamples.kt
+++ b/core/haptics/haptics/samples/src/main/java/androidx/core/haptics/samples/HapticManagerSamples.kt
@@ -19,7 +19,12 @@
import android.content.Context
import androidx.annotation.Sampled
import androidx.core.haptics.HapticManager
-import androidx.core.haptics.signal.PredefinedEffect.Companion.PredefinedClick
+import androidx.core.haptics.signal.CompositionSignal.Companion.compositionOf
+import androidx.core.haptics.signal.CompositionSignal.Companion.off
+import androidx.core.haptics.signal.CompositionSignal.Companion.quickFall
+import androidx.core.haptics.signal.CompositionSignal.Companion.slowRise
+import androidx.core.haptics.signal.CompositionSignal.Companion.thud
+import androidx.core.haptics.signal.PredefinedEffectSignal.Companion.predefinedClick
/**
* Sample showing how to play a standard click haptic effect on the system vibrator.
@@ -27,5 +32,20 @@
@Sampled
fun PlaySystemStandardClick(context: Context) {
val hapticManager = HapticManager.create(context)
- hapticManager.play(PredefinedClick)
+ hapticManager.play(predefinedClick())
+}
+
+/**
+ * Sample showing how to play a haptic signal on a vibrator.
+ */
+@Sampled
+fun PlayHapticSignal(hapticManager: HapticManager) {
+ hapticManager.play(
+ compositionOf(
+ slowRise(),
+ quickFall(),
+ off(durationMillis = 50),
+ thud(),
+ )
+ )
}
diff --git a/core/haptics/haptics/samples/src/main/java/androidx/core/haptics/samples/WaveformSignalSamples.kt b/core/haptics/haptics/samples/src/main/java/androidx/core/haptics/samples/WaveformSignalSamples.kt
new file mode 100644
index 0000000..3b7b597
--- /dev/null
+++ b/core/haptics/haptics/samples/src/main/java/androidx/core/haptics/samples/WaveformSignalSamples.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.haptics.samples
+
+import androidx.annotation.Sampled
+import androidx.core.haptics.signal.WaveformSignal
+import androidx.core.haptics.signal.WaveformSignal.Companion.off
+import androidx.core.haptics.signal.WaveformSignal.Companion.on
+import androidx.core.haptics.signal.WaveformSignal.Companion.repeatingWaveformOf
+import androidx.core.haptics.signal.WaveformSignal.Companion.waveformOf
+
+/**
+ * Sample showing how to create an on-off pattern.
+ */
+@Sampled
+fun PatternWaveform() {
+ waveformOf(
+ on(durationMillis = 250),
+ off(durationMillis = 350),
+ on(durationMillis = 250),
+ )
+}
+
+/**
+ * Sample showing how to create an infinite haptic signal a repeating step waveform.
+ */
+@Sampled
+fun PatternWaveformRepeat() {
+ waveformOf(
+ on(durationMillis = 100),
+ off(durationMillis = 50),
+ on(durationMillis = 100),
+ off(durationMillis = 50),
+ ).repeat()
+}
+
+/**
+ * Sample showing how to create an amplitude step waveform.
+ */
+@Sampled
+fun AmplitudeWaveform() {
+ waveformOf(
+ on(durationMillis = 10, amplitude = 0.2f),
+ on(durationMillis = 20, amplitude = 0.4f),
+ on(durationMillis = 30, amplitude = 0.8f),
+ on(durationMillis = 40, amplitude = 1f),
+ off(durationMillis = 50),
+ on(durationMillis = 50),
+ )
+}
+
+/**
+ * Sample showing how to create an amplitude step waveform.
+ */
+@Sampled
+fun RepeatingAmplitudeWaveform() {
+ repeatingWaveformOf(
+ on(durationMillis = 100),
+ off(durationMillis = 50),
+ )
+}
+
+/**
+ * Sample showing how to create an infinite haptic signal as repeating step waveform.
+ */
+@Sampled
+fun PatternThenRepeatExistingWaveform(waveformSignal: WaveformSignal) {
+ waveformOf(
+ on(durationMillis = 100),
+ off(durationMillis = 50),
+ on(durationMillis = 100),
+ off(durationMillis = 500),
+ ).thenRepeat(waveformSignal)
+}
+
+/**
+ * Sample showing how to create an infinite haptic signal as repeating step waveform.
+ */
+@Sampled
+fun PatternThenRepeatAmplitudeWaveform() {
+ waveformOf(
+ on(durationMillis = 100),
+ off(durationMillis = 50),
+ on(durationMillis = 100),
+ ).thenRepeat(
+ on(durationMillis = 500, amplitude = 0f),
+ on(durationMillis = 100, amplitude = 0.2f),
+ on(durationMillis = 200, amplitude = 0.4f),
+ on(durationMillis = 300, amplitude = 0.8f),
+ on(durationMillis = 400, amplitude = 1f),
+ )
+}
diff --git a/core/haptics/haptics/src/androidTest/java/androidx/core/haptics/PlayCompositionSignalTest.kt b/core/haptics/haptics/src/androidTest/java/androidx/core/haptics/PlayCompositionSignalTest.kt
new file mode 100644
index 0000000..584ffc637
--- /dev/null
+++ b/core/haptics/haptics/src/androidTest/java/androidx/core/haptics/PlayCompositionSignalTest.kt
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.haptics
+
+import android.os.Build
+import androidx.core.haptics.signal.CompositionSignal.Companion.click
+import androidx.core.haptics.signal.CompositionSignal.Companion.compositionOf
+import androidx.core.haptics.signal.CompositionSignal.Companion.lowTick
+import androidx.core.haptics.signal.CompositionSignal.Companion.off
+import androidx.core.haptics.signal.CompositionSignal.Companion.quickFall
+import androidx.core.haptics.signal.CompositionSignal.Companion.quickRise
+import androidx.core.haptics.signal.CompositionSignal.Companion.slowRise
+import androidx.core.haptics.signal.CompositionSignal.Companion.spin
+import androidx.core.haptics.signal.CompositionSignal.Companion.thud
+import androidx.core.haptics.signal.CompositionSignal.Companion.tick
+import androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom
+import androidx.core.haptics.testing.CompositionPrimitive
+import androidx.core.haptics.testing.FakeVibratorSubject.Companion.assertThat
+import androidx.core.haptics.testing.FullVibrator
+import androidx.core.haptics.testing.vibration
+import androidx.test.filters.SdkSuppress
+import androidx.test.filters.SmallTest
+import kotlin.time.Duration.Companion.milliseconds
+import org.junit.Assert.assertThrows
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.runners.Parameterized
+
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
+@RunWith(Parameterized::class)
+@SmallTest
+class PlayCompositionSignalSdk30AndAboveTest(
+ private val primitive: PrimitiveAtom,
+) {
+ private val fakeVibrator = FullVibrator()
+ private val hapticManager = HapticManager.createForVibrator(fakeVibrator)
+
+ @Test
+ fun play_vibratesWithSupportedPrimitives() {
+ hapticManager.play(
+ compositionOf(
+ primitive,
+ off(durationMillis = 50),
+ primitive.withAmplitudeScale(0.5f),
+ off(durationMillis = 100),
+ primitive.withAmplitudeScale(0.8f),
+ off(durationMillis = 200),
+ )
+ )
+ assertThat(fakeVibrator).vibratedExactly(
+ vibration(
+ CompositionPrimitive(primitive),
+ CompositionPrimitive(primitive, scale = 0.5f, delay = 50.milliseconds),
+ CompositionPrimitive(primitive, scale = 0.8f, delay = 100.milliseconds),
+ // Skips trailing 200ms delay from vibrate call
+ )
+ )
+ }
+
+ companion object {
+
+ @JvmStatic
+ @Parameterized.Parameters(name = "primitive:{0}")
+ fun data(): Collection<Any> {
+ val primitives = mutableListOf(
+ tick(),
+ click(),
+ slowRise(),
+ quickRise(),
+ quickFall(),
+ )
+ if (Build.VERSION.SDK_INT >= 31) {
+ primitives.apply {
+ add(lowTick())
+ add(spin())
+ add(thud())
+ }
+ }
+ return primitives
+ }
+ }
+}
+
+@SdkSuppress(maxSdkVersion = Build.VERSION_CODES.Q)
+@RunWith(Parameterized::class)
+@SmallTest
+class PlayCompositionSignalBelowSdk30Test(
+ private val primitive: PrimitiveAtom,
+) {
+ private val fakeVibrator = FullVibrator()
+ private val hapticManager = HapticManager.createForVibrator(fakeVibrator)
+
+ @Test
+ fun play_doesNotVibrate() {
+ hapticManager.play(compositionOf(primitive))
+ assertThat(fakeVibrator).neverVibrated()
+ }
+
+ companion object {
+
+ @JvmStatic
+ @Parameterized.Parameters(name = "primitive:{0}")
+ fun data(): Collection<Any> = mutableListOf(
+ tick(),
+ click(),
+ slowRise(),
+ quickRise(),
+ quickFall(),
+ lowTick(),
+ spin(),
+ thud(),
+ )
+ }
+}
+
+@RunWith(JUnit4::class)
+@SmallTest
+class PlayCompositionSignalPartialPrimitiveSdkSupportTest {
+ private val fakeVibrator = FullVibrator()
+ private val hapticManager = HapticManager.createForVibrator(fakeVibrator)
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.R, maxSdkVersion = Build.VERSION_CODES.R)
+ @Test
+ fun play_api30AndPrimitiveFromApi31AndAbove_doesNotVibrate() {
+ hapticManager.play(compositionOf(lowTick()))
+ hapticManager.play(compositionOf(thud()))
+ hapticManager.play(compositionOf(spin()))
+ // Mix supported/unsupported primitives
+ hapticManager.play(compositionOf(tick(), lowTick()))
+ assertThat(fakeVibrator).neverVibrated()
+ }
+}
+
+@RunWith(JUnit4::class)
+@SmallTest
+class PlayCompositionSignalAllSdksTest {
+
+ @Test
+ fun compositionOf_withNoAtom_throwsException() {
+ assertThrows(IllegalArgumentException::class.java) {
+ compositionOf()
+ }
+ }
+
+ @Test
+ fun off_withNegativeDuration_throwsException() {
+ assertThrows(IllegalArgumentException::class.java) {
+ off(durationMillis = -10)
+ }
+ }
+
+ @Test
+ fun withAmplitudeScale_withAmplitudeLargerThanOne_throwsException() {
+ assertThrows(IllegalArgumentException::class.java) {
+ click().withAmplitudeScale(2f)
+ }
+ }
+
+ @Test
+ fun withAmplitudeScale_withNegativeAmplitude_throwsException() {
+ assertThrows(IllegalArgumentException::class.java) {
+ click().withAmplitudeScale(-1f)
+ }
+ }
+}
diff --git a/core/haptics/haptics/src/androidTest/java/androidx/core/haptics/PlayPredefinedEffectSignalTest.kt b/core/haptics/haptics/src/androidTest/java/androidx/core/haptics/PlayPredefinedEffectSignalTest.kt
new file mode 100644
index 0000000..f5572c1
--- /dev/null
+++ b/core/haptics/haptics/src/androidTest/java/androidx/core/haptics/PlayPredefinedEffectSignalTest.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.haptics
+
+import android.os.Build
+import androidx.core.haptics.signal.PredefinedEffectSignal
+import androidx.core.haptics.signal.PredefinedEffectSignal.Companion.predefinedClick
+import androidx.core.haptics.signal.PredefinedEffectSignal.Companion.predefinedDoubleClick
+import androidx.core.haptics.signal.PredefinedEffectSignal.Companion.predefinedHeavyClick
+import androidx.core.haptics.signal.PredefinedEffectSignal.Companion.predefinedTick
+import androidx.core.haptics.testing.FakeVibratorSubject.Companion.assertThat
+import androidx.core.haptics.testing.PredefinedEffectsAndAmplitudeVibrator
+import androidx.core.haptics.testing.vibration
+import androidx.test.filters.SdkSuppress
+import androidx.test.filters.SmallTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@SmallTest
+class PlayPredefinedEffectSignalTest(
+ private val effect: PredefinedEffectSignal,
+ private val expectedFallbackPattern: LongArray,
+) {
+ private val fakeVibrator = PredefinedEffectsAndAmplitudeVibrator()
+ private val hapticManager = HapticManager.createForVibrator(fakeVibrator)
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
+ @Test
+ fun play_api29AndAbove_vibratesWithPredefinedEffect() {
+ hapticManager.play(effect)
+ assertThat(fakeVibrator).vibratedExactly(vibration(effect))
+ }
+
+ @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.P)
+ @Test
+ fun play_belowApi29_vibratesWithFallbackPattern() {
+ hapticManager.play(effect)
+ assertThat(fakeVibrator).vibratedExactly(vibration(expectedFallbackPattern))
+ }
+
+ companion object {
+
+ @JvmStatic
+ @Parameterized.Parameters(name = "effect:{0}, expectedFallbackPattern:{1}")
+ fun data(): Collection<Array<Any>> = listOf(
+ arrayOf(predefinedTick(), longArrayOf(0, 10)),
+ arrayOf(predefinedClick(), longArrayOf(0, 20)),
+ arrayOf(predefinedHeavyClick(), longArrayOf(0, 30)),
+ arrayOf(predefinedDoubleClick(), longArrayOf(0, 30, 100, 30)),
+ )
+ }
+}
diff --git a/core/haptics/haptics/src/androidTest/java/androidx/core/haptics/PlayPredefinedEffectTest.kt b/core/haptics/haptics/src/androidTest/java/androidx/core/haptics/PlayPredefinedEffectTest.kt
deleted file mode 100644
index fab916e..0000000
--- a/core/haptics/haptics/src/androidTest/java/androidx/core/haptics/PlayPredefinedEffectTest.kt
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.core.haptics
-
-import android.os.Build
-import android.os.VibrationEffect
-import android.os.Vibrator
-import androidx.core.haptics.signal.PredefinedEffect
-import androidx.core.haptics.signal.PredefinedEffect.Companion.PredefinedClick
-import androidx.core.haptics.signal.PredefinedEffect.Companion.PredefinedDoubleClick
-import androidx.core.haptics.signal.PredefinedEffect.Companion.PredefinedHeavyClick
-import androidx.core.haptics.signal.PredefinedEffect.Companion.PredefinedTick
-import androidx.test.filters.SdkSuppress
-import androidx.test.filters.SmallTest
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-import org.mockito.Mockito.eq
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.verify
-
-@RunWith(Parameterized::class)
-@SmallTest
-class PlayPredefinedEffectTest(
- private val effect: PredefinedEffect,
- private val expectedFallbackPattern: LongArray,
-) {
- // Vibrator has package-protected constructor and cannot be extended by a FakeVibrator
- // TODO(b/275084444): replace with a testable interface to allow all SDK levels
- private val vibrator = mock(Vibrator::class.java)
- private val hapticManager = HapticManager.createForVibrator(vibrator)
-
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
- @Test
- fun perform_api29AndAbove() {
- hapticManager.play(effect)
- verify(vibrator).vibrate(eq(VibrationEffect.createPredefined(effect.effectId)))
- }
-
- @Suppress("DEPRECATION") // Verifying deprecated APIs are triggered by this test
- @SdkSuppress(
- minSdkVersion = 28, // TODO(b/275084444): remove this once we introduce fake vibrator
- maxSdkVersion = Build.VERSION_CODES.P
- )
- @Test
- fun perform_belowApi29() {
- hapticManager.play(effect)
- verify(vibrator).vibrate(eq(expectedFallbackPattern), eq(-1))
- }
-
- companion object {
-
- @JvmStatic
- @Parameterized.Parameters(name = "effect:{0}, expectedFallbackPattern:{1}")
- fun data(): Collection<Array<Any>> = listOf(
- arrayOf(PredefinedTick, longArrayOf(0, 10)),
- arrayOf(PredefinedClick, longArrayOf(0, 20)),
- arrayOf(PredefinedHeavyClick, longArrayOf(0, 30)),
- arrayOf(PredefinedDoubleClick, longArrayOf(0, 30, 100, 30)),
- )
- }
-}
diff --git a/core/haptics/haptics/src/androidTest/java/androidx/core/haptics/PlayWaveformSignalTest.kt b/core/haptics/haptics/src/androidTest/java/androidx/core/haptics/PlayWaveformSignalTest.kt
new file mode 100644
index 0000000..cd7a70d
--- /dev/null
+++ b/core/haptics/haptics/src/androidTest/java/androidx/core/haptics/PlayWaveformSignalTest.kt
@@ -0,0 +1,279 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.haptics
+
+import android.os.Build
+import androidx.core.haptics.signal.WaveformSignal.Companion.off
+import androidx.core.haptics.signal.WaveformSignal.Companion.on
+import androidx.core.haptics.signal.WaveformSignal.Companion.repeatingWaveformOf
+import androidx.core.haptics.signal.WaveformSignal.Companion.waveformOf
+import androidx.core.haptics.testing.AmplitudeVibrator
+import androidx.core.haptics.testing.FakeVibratorSubject.Companion.assertThat
+import androidx.core.haptics.testing.PatternVibrator
+import androidx.core.haptics.testing.vibration
+import androidx.test.filters.SdkSuppress
+import androidx.test.filters.SmallTest
+import org.junit.Assert.assertThrows
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+@RunWith(JUnit4::class)
+@SmallTest
+class PlayWaveformSignalSdk26AndAboveTest {
+ private val fakeVibrator = AmplitudeVibrator()
+ private val hapticManager = HapticManager.createForVibrator(fakeVibrator)
+
+ @Test
+ fun play_withOneShot_vibratesWithOneShotEffect() {
+ hapticManager.play(waveformOf(on(durationMillis = 10)))
+ hapticManager.play(waveformOf(on(durationMillis = 20, amplitude = 0.2f)))
+ assertThat(fakeVibrator).vibratedExactly(
+ vibration(timings = longArrayOf(10), amplitudes = intArrayOf(-1)),
+ vibration(timings = longArrayOf(20), amplitudes = intArrayOf(51)),
+ ).inOrder()
+ }
+
+ @Test
+ fun play_withAmplitudes_vibratesWithAmplitudes() {
+ hapticManager.play(
+ waveformOf(
+ on(durationMillis = 10, amplitude = 0.2f),
+ on(durationMillis = 20, amplitude = 0.8f),
+ on(durationMillis = 30, amplitude = 0f),
+ on(durationMillis = 40, amplitude = 1f),
+ )
+ )
+ assertThat(fakeVibrator).vibratedExactly(
+ vibration(
+ timings = longArrayOf(10, 20, 30, 40),
+ amplitudes = intArrayOf(51, 204, 0, 255),
+ )
+ )
+ }
+
+ @Test
+ fun play_withOnOffPattern_vibratesWithAmplitudes() {
+ hapticManager.play(
+ waveformOf(
+ on(durationMillis = 10),
+ off(durationMillis = 20),
+ on(durationMillis = 30),
+ off(durationMillis = 40),
+ )
+ )
+ assertThat(fakeVibrator).vibratedExactly(
+ vibration(
+ timings = longArrayOf(10, 20, 30, 40),
+ amplitudes = intArrayOf(-1, 0, -1, 0),
+ )
+ )
+ }
+
+ @Test
+ fun play_withRepeatingAmplitudes_vibratesWithRepeatIndex() {
+ hapticManager.play(
+ waveformOf(
+ on(durationMillis = 10, amplitude = 0.2f),
+ on(durationMillis = 20, amplitude = 0.4f),
+ ).thenRepeat(
+ on(durationMillis = 30, amplitude = 0.6f),
+ on(durationMillis = 40, amplitude = 0.8f),
+ )
+ )
+ assertThat(fakeVibrator).vibratedExactly(
+ vibration(
+ timings = longArrayOf(10, 20, 30, 40),
+ amplitudes = intArrayOf(51, 102, 153, 204),
+ repeat = 2,
+ )
+ )
+ }
+}
+
+@SdkSuppress(maxSdkVersion = Build.VERSION_CODES.N_MR1)
+@RunWith(JUnit4::class)
+@SmallTest
+class PlayWaveformSignalBelowSdk26Test {
+ private val fakeVibrator = PatternVibrator()
+ private val hapticManager = HapticManager.createForVibrator(fakeVibrator)
+
+ @Test
+ fun play_withOneShot_vibratesWithPatternForDefaultAndMaxAmplitudes() {
+ hapticManager.play(waveformOf(on(durationMillis = 10)))
+ hapticManager.play(waveformOf(on(durationMillis = 20, amplitude = 1f)))
+ hapticManager.play(waveformOf(on(durationMillis = 30, amplitude = 0.2f)))
+ assertThat(fakeVibrator).vibratedExactly(
+ vibration(pattern = longArrayOf(0, 10)),
+ vibration(pattern = longArrayOf(0, 20)),
+ // Ignores last request with non-default amplitude
+ ).inOrder()
+ }
+
+ @Test
+ fun play_withAmplitudes_doesNotVibrate() {
+ hapticManager.play(
+ waveformOf(
+ on(durationMillis = 10, amplitude = 0.2f),
+ on(durationMillis = 20, amplitude = 0.8f),
+ on(durationMillis = 30, amplitude = 0f),
+ on(durationMillis = 40, amplitude = 1f),
+ )
+ )
+ assertThat(fakeVibrator).neverVibrated()
+ }
+
+ @Test
+ fun play_withOnOffPattern_vibratesWithFallbackPattern() {
+ hapticManager.play(
+ waveformOf(
+ on(durationMillis = 10),
+ off(durationMillis = 20),
+ on(durationMillis = 30),
+ on(durationMillis = 40),
+ off(durationMillis = 50),
+ off(durationMillis = 60),
+ )
+ )
+ assertThat(fakeVibrator).vibratedExactly(
+ // OFF(0ms), ON(10ms), OFF(20ms), ON(30+40ms), OFF(50+60ms)
+ vibration(pattern = longArrayOf(0, 10, 20, 70, 110))
+ )
+ }
+
+ @Test
+ fun play_withOnOffMaxAmplitudePattern_vibratesWithFallbackPattern() {
+ hapticManager.play(
+ waveformOf(
+ on(durationMillis = 10),
+ on(durationMillis = 20, amplitude = 1f),
+ off(durationMillis = 30),
+ on(durationMillis = 40, amplitude = 0f),
+ on(durationMillis = 50),
+ )
+ )
+ assertThat(fakeVibrator).vibratedExactly(
+ // OFF(0ms), ON(10+20ms), OFF(30+40ms), ON(50ms)
+ vibration(pattern = longArrayOf(0, 30, 70, 50))
+ )
+ }
+
+ @Test
+ fun play_withRepeatingPattern_vibratesWithRepeatIndex() {
+ hapticManager.play(
+ repeatingWaveformOf(
+ off(durationMillis = 10),
+ on(durationMillis = 20),
+ off(durationMillis = 30),
+ off(durationMillis = 40),
+ on(durationMillis = 50),
+ on(durationMillis = 60),
+ )
+ )
+ assertThat(fakeVibrator).vibratedExactly(
+ vibration(
+ // OFF(10ms), ON(20ms), OFF(30+40ms), ON(50+60ms)
+ pattern = longArrayOf(10, 20, 70, 110),
+ repeat = 0,
+ )
+ )
+ }
+
+ @Test
+ fun play_withInitialAndRepeatingPattern_doesNotMergeInitialWithRepeatingPattern() {
+ hapticManager.play(
+ waveformOf(
+ on(durationMillis = 10),
+ off(durationMillis = 20),
+ off(durationMillis = 30),
+ on(durationMillis = 40),
+ ).thenRepeat(
+ on(durationMillis = 50),
+ on(durationMillis = 60),
+ off(durationMillis = 70),
+ off(durationMillis = 80),
+ )
+ )
+ assertThat(fakeVibrator).vibratedExactly(
+ vibration(
+ // Does not merge consecutive ON steps 40 and 50 because of repeat index.
+ // OFF(0ms), ON(10ms), OFF(20+30ms), ON(40ms), OFF(+0ms), ON(50+60ms), OFF(70+80ms)
+ pattern = longArrayOf(0, 10, 50, 40, 0, 110, 150),
+ repeat = 4,
+ )
+ )
+ }
+}
+
+@RunWith(JUnit4::class)
+@SmallTest
+class PlayWaveformSignalAllSdksTest {
+ private val fakeVibrator = AmplitudeVibrator()
+ private val hapticManager = HapticManager.createForVibrator(fakeVibrator)
+
+ @Test
+ fun play_withZeroDurationSignal_doesNotVibrate() {
+ hapticManager.play(waveformOf(on(durationMillis = 0)))
+ hapticManager.play(waveformOf(on(durationMillis = 0, amplitude = 0.2f)))
+ hapticManager.play(
+ waveformOf(
+ on(durationMillis = 0, amplitude = 0.2f),
+ on(durationMillis = 0, amplitude = 0.8f),
+ on(durationMillis = 0, amplitude = 0f),
+ on(durationMillis = 0, amplitude = 1f),
+ off(durationMillis = 0),
+ )
+ )
+ assertThat(fakeVibrator).neverVibrated()
+ }
+
+ @Test
+ fun waveformOf_withNoAtom_throwsException() {
+ assertThrows(IllegalArgumentException::class.java) {
+ waveformOf()
+ }
+ }
+
+ @Test
+ fun on_withAmplitudeLargerThanOne_throwsException() {
+ assertThrows(IllegalArgumentException::class.java) {
+ on(durationMillis = 0, amplitude = 1.1f)
+ }
+ }
+
+ @Test
+ fun on_withNegativeAmplitude_throwsException() {
+ assertThrows(IllegalArgumentException::class.java) {
+ on(durationMillis = 0, amplitude = -0.5f)
+ }
+ }
+
+ @Test
+ fun on_withNegativeDuration_throwsException() {
+ assertThrows(IllegalArgumentException::class.java) {
+ on(durationMillis = -10)
+ }
+ }
+
+ @Test
+ fun off_withNegativeDuration_throwsException() {
+ assertThrows(IllegalArgumentException::class.java) {
+ off(durationMillis = -10)
+ }
+ }
+}
diff --git a/core/haptics/haptics/src/androidTest/java/androidx/core/haptics/testing/FakeVibrator.kt b/core/haptics/haptics/src/androidTest/java/androidx/core/haptics/testing/FakeVibrator.kt
new file mode 100644
index 0000000..2ff140a
--- /dev/null
+++ b/core/haptics/haptics/src/androidTest/java/androidx/core/haptics/testing/FakeVibrator.kt
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.haptics.testing
+
+import android.os.Build
+import android.os.VibrationEffect
+import androidx.annotation.RequiresApi
+import androidx.core.haptics.PatternVibrationWrapper
+import androidx.core.haptics.VibrationEffectWrapper
+import androidx.core.haptics.VibrationWrapper
+import androidx.core.haptics.VibratorWrapper
+import androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom
+import androidx.core.haptics.signal.PredefinedEffectSignal
+import kotlin.time.Duration
+
+/**
+ * Fake [VibratorWrapper] implementation for testing.
+ */
+internal sealed class FakeVibrator(
+ private val amplitudeControlSupported: Boolean,
+ private val effectsSupported: IntArray? = null,
+ private val primitivesSupported: IntArray = intArrayOf(),
+) : VibratorWrapper {
+ private val vibrations: MutableList<VibrationWrapper> = mutableListOf()
+
+ override fun hasVibrator(): Boolean = true
+
+ override fun hasAmplitudeControl(): Boolean = amplitudeControlSupported
+
+ override fun areEffectsSupported(
+ effects: IntArray,
+ ): Array<VibratorWrapper.EffectSupport> =
+ effects.map {
+ when {
+ effectsSupported == null -> VibratorWrapper.EffectSupport.UNKNOWN
+ effectsSupported.contains(it) -> VibratorWrapper.EffectSupport.YES
+ else -> VibratorWrapper.EffectSupport.NO
+ }
+ }.toTypedArray()
+
+ override fun arePrimitivesSupported(primitives: IntArray): BooleanArray =
+ primitives.map { primitivesSupported.contains(it) }.toBooleanArray()
+
+ override fun vibrate(vibration: VibrationWrapper) {
+ vibrations.add(vibration)
+ }
+
+ override fun cancel() {
+ // No-op
+ }
+
+ /** Returns all requests sent to the [android.os.Vibrator], in order. */
+ internal fun vibrations(): List<VibrationWrapper> = vibrations
+}
+
+/**
+ * Vibrator that only supports on-off patterns.
+ */
+internal class PatternVibrator : FakeVibrator(
+ amplitudeControlSupported = false,
+)
+
+/**
+ * Vibrator that only supports amplitude control.
+ */
+internal class AmplitudeVibrator : FakeVibrator(
+ amplitudeControlSupported = true,
+)
+
+/**
+ * Vibrator that supports amplitude control and all predefined effects.
+ */
+internal class PredefinedEffectsAndAmplitudeVibrator : FakeVibrator(
+ amplitudeControlSupported = true,
+ effectsSupported = intArrayOf(
+ PredefinedEffectSignal.TICK,
+ PredefinedEffectSignal.CLICK,
+ PredefinedEffectSignal.HEAVY_CLICK,
+ PredefinedEffectSignal.DOUBLE_CLICK,
+ ),
+)
+
+/**
+ * Vibrator that supports amplitude control and all predefined and primitive effects.
+ */
+internal class FullVibrator : FakeVibrator(
+ amplitudeControlSupported = true,
+ effectsSupported = intArrayOf(
+ PredefinedEffectSignal.TICK,
+ PredefinedEffectSignal.CLICK,
+ PredefinedEffectSignal.HEAVY_CLICK,
+ PredefinedEffectSignal.DOUBLE_CLICK,
+ ),
+ primitivesSupported = intArrayOf(
+ PrimitiveAtom.LOW_TICK,
+ PrimitiveAtom.TICK,
+ PrimitiveAtom.CLICK,
+ PrimitiveAtom.SLOW_RISE,
+ PrimitiveAtom.QUICK_RISE,
+ PrimitiveAtom.QUICK_FALL,
+ PrimitiveAtom.SPIN,
+ PrimitiveAtom.THUD,
+ ),
+)
+
+/** Helper to create [android.os.VibrationEffect.Composition] entries. */
+internal data class CompositionPrimitive(
+ val primitiveId: Int,
+ val scale: Float,
+ val delayMs: Int,
+) {
+ constructor(
+ primitive: PrimitiveAtom,
+ scale: Float = primitive.amplitudeScale,
+ delay: Duration = Duration.ZERO,
+ ) : this(primitive.type, scale, delay.inWholeMilliseconds.toInt())
+}
+
+/** Helper to create [VibrationWrapper] request for a on-off pattern. */
+internal fun vibration(
+ pattern: LongArray,
+ repeat: Int = -1,
+): VibrationWrapper =
+ PatternVibrationWrapper(pattern, repeat)
+
+/** Helper to create [VibrationWrapper] request for a predefined effect. */
+@RequiresApi(Build.VERSION_CODES.Q)
+internal fun vibration(effect: PredefinedEffectSignal): VibrationWrapper =
+ VibrationEffectWrapper(VibrationEffect.createPredefined(effect.type))
+
+/** Helper to create [VibrationWrapper] request for a waveform effect. */
+@RequiresApi(Build.VERSION_CODES.O)
+internal fun vibration(
+ timings: LongArray,
+ amplitudes: IntArray,
+ repeat: Int = -1,
+): VibrationWrapper =
+ VibrationEffectWrapper(VibrationEffect.createWaveform(timings, amplitudes, repeat))
+
+/** Helper to create [VibrationWrapper] request for a primitive composition effect. */
+@RequiresApi(Build.VERSION_CODES.R)
+internal fun vibration(vararg primitives: CompositionPrimitive): VibrationWrapper {
+ return VibrationEffectWrapper(
+ VibrationEffect.startComposition().apply {
+ primitives.forEach { addPrimitive(it.primitiveId, it.scale, it.delayMs) }
+ }.compose()
+ )
+}
diff --git a/core/haptics/haptics/src/androidTest/java/androidx/core/haptics/testing/FakeVibratorSubject.kt b/core/haptics/haptics/src/androidTest/java/androidx/core/haptics/testing/FakeVibratorSubject.kt
new file mode 100644
index 0000000..9ab2ca2
--- /dev/null
+++ b/core/haptics/haptics/src/androidTest/java/androidx/core/haptics/testing/FakeVibratorSubject.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.haptics.testing
+
+import androidx.core.haptics.VibrationWrapper
+import com.google.common.truth.FailureMetadata
+import com.google.common.truth.Ordered
+import com.google.common.truth.Subject
+import com.google.common.truth.Subject.Factory
+import com.google.common.truth.Truth.assertAbout
+
+/**
+ * Truth extension for [FakeVibrator].
+ */
+internal class FakeVibratorSubject private constructor(
+ metadata: FailureMetadata?,
+ private val actual: FakeVibrator,
+) : Subject(metadata, actual) {
+
+ companion object {
+ private val SUBJECT_FACTORY: Factory<FakeVibratorSubject?, FakeVibrator> =
+ Factory { failureMetadata, subject -> FakeVibratorSubject(failureMetadata, subject) }
+
+ internal fun assertThat(vibrator: FakeVibrator): FakeVibratorSubject =
+ requireNotNull(assertAbout(SUBJECT_FACTORY).that(vibrator))
+ }
+
+ /**
+ * Checks the subject was requested to vibrate with exactly the provided parameters.
+ *
+ * To also test that the requests appear in the given order, make a call to inOrder() on the
+ * object returned by this method.
+ */
+ fun vibratedExactly(vararg expected: VibrationWrapper): Ordered =
+ check("vibrations()").that(actual.vibrations()).containsExactly(*expected)
+
+ /**
+ * Checks the subject has never requested to vibrate.
+ */
+ fun neverVibrated(): Unit =
+ check("vibrations()").that(actual.vibrations()).isEmpty()
+}
diff --git a/core/haptics/haptics/src/main/java/androidx/core/haptics/HapticManager.kt b/core/haptics/haptics/src/main/java/androidx/core/haptics/HapticManager.kt
index 1ca0bd0..37b74bd 100644
--- a/core/haptics/haptics/src/main/java/androidx/core/haptics/HapticManager.kt
+++ b/core/haptics/haptics/src/main/java/androidx/core/haptics/HapticManager.kt
@@ -19,11 +19,14 @@
import android.content.Context
import android.os.Vibrator
import androidx.annotation.RequiresPermission
+import androidx.annotation.VisibleForTesting
+import androidx.core.content.ContextCompat
import androidx.core.haptics.impl.HapticManagerImpl
-import androidx.core.haptics.signal.PredefinedEffect
+import androidx.core.haptics.impl.VibratorWrapperImpl
+import androidx.core.haptics.signal.HapticSignal
/**
- * Manager for the vibrators of a device.
+ * Manager for interactions with a device vibrator.
*
* <p>If your process exits, any vibration you started will stop.
*/
@@ -32,35 +35,38 @@
companion object {
/**
- * Creates haptic manager for the system vibrators.
+ * Creates a haptic manager for the system vibrator.
*
- * Sample code:
* @sample androidx.core.haptics.samples.PlaySystemStandardClick
*
- * @param context Context to load the device vibrators.
- * @return a new instance of HapticManager for the system vibrators.
+ * @param context Context to load the device vibrator.
+ * @return a new instance of HapticManager for the system vibrator.
*/
@JvmStatic
fun create(context: Context): HapticManager {
- return HapticManagerImpl(context)
+ return HapticManagerImpl(
+ VibratorWrapperImpl(
+ requireNotNull(ContextCompat.getSystemService(context, Vibrator::class.java)) {
+ "Vibrator service not found"
+ }
+ )
+ )
}
- /** Creates haptic manager for given vibrator. */
- internal fun createForVibrator(vibrator: Vibrator): HapticManager {
+ /** Creates a haptic manager for the given vibrator. */
+ @VisibleForTesting
+ internal fun createForVibrator(vibrator: VibratorWrapper): HapticManager {
return HapticManagerImpl(vibrator)
}
}
/**
- * Play a [PredefinedEffect].
+ * Play a [HapticSignal].
*
- * The app should be in the foreground for the vibration to happen.
+ * @sample androidx.core.haptics.samples.PlayHapticSignal
*
- * Sample code:
- * @sample androidx.core.haptics.samples.PlaySystemStandardClick
- *
- * @param effect The predefined haptic effect to be played.
+ * @param signal The haptic signal to be played.
*/
@RequiresPermission(android.Manifest.permission.VIBRATE)
- fun play(effect: PredefinedEffect)
+ fun play(signal: HapticSignal)
}
diff --git a/core/haptics/haptics/src/main/java/androidx/core/haptics/VibratorWrapper.kt b/core/haptics/haptics/src/main/java/androidx/core/haptics/VibratorWrapper.kt
new file mode 100644
index 0000000..683c071
--- /dev/null
+++ b/core/haptics/haptics/src/main/java/androidx/core/haptics/VibratorWrapper.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.haptics
+
+import androidx.annotation.RequiresPermission
+
+/**
+ * Internal wrapper for [android.os.Vibrator] to enable fake implementations for testing.
+ */
+internal interface VibratorWrapper {
+ /** Check whether the hardware has a vibrator. */
+ fun hasVibrator(): Boolean
+
+ /** Check whether the vibrator has amplitude control. */
+ fun hasAmplitudeControl(): Boolean
+
+ /** Check whether the hardware supports each predefined effect type. */
+ fun areEffectsSupported(effects: IntArray): Array<EffectSupport>?
+
+ /** Check whether the hardware supports each primitive effect type. */
+ fun arePrimitivesSupported(primitives: IntArray): BooleanArray?
+
+ /** Vibrate with a given vibration effect or pattern. */
+ @RequiresPermission(android.Manifest.permission.VIBRATE)
+ fun vibrate(vibration: VibrationWrapper)
+
+ /** Cancel any ongoing vibration from this app and turns the vibrator off. */
+ @RequiresPermission(android.Manifest.permission.VIBRATE)
+ fun cancel()
+
+ /** Represents constants from [android.os.Vibrator.VIBRATION_EFFECT_SUPPORT_*]. */
+ enum class EffectSupport {
+ UNKNOWN, YES, NO
+ }
+}
+
+/**
+ * Represents different API levels of support for [android.os.Vibrator.vibrate] parameters.
+ */
+internal sealed interface VibrationWrapper
+
+/**
+ * Represents vibrations defined by on-off patterns.
+ */
+internal data class PatternVibrationWrapper(
+ val timings: LongArray,
+ val repeatIndex: Int,
+) : VibrationWrapper {
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as PatternVibrationWrapper
+
+ if (!timings.contentEquals(other.timings)) return false
+ if (repeatIndex != other.repeatIndex) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = timings.contentHashCode()
+ result = 31 * result + repeatIndex.hashCode()
+ return result
+ }
+}
+
+/**
+ * Represents vibrations defined by an instance of [android.os.VibrationEffect].
+ */
+internal data class VibrationEffectWrapper(
+ val vibrationEffect: Any,
+) : VibrationWrapper
diff --git a/core/haptics/haptics/src/main/java/androidx/core/haptics/impl/HapticManagerImpl.kt b/core/haptics/haptics/src/main/java/androidx/core/haptics/impl/HapticManagerImpl.kt
index a5c518c..bd249c9 100644
--- a/core/haptics/haptics/src/main/java/androidx/core/haptics/impl/HapticManagerImpl.kt
+++ b/core/haptics/haptics/src/main/java/androidx/core/haptics/impl/HapticManagerImpl.kt
@@ -16,72 +16,23 @@
package androidx.core.haptics.impl
-import android.content.Context
-import android.os.Build.VERSION
-import android.os.VibrationEffect
import android.os.Vibrator
-import androidx.annotation.DoNotInline
-import androidx.annotation.RequiresApi
import androidx.annotation.RequiresPermission
-import androidx.core.content.ContextCompat
import androidx.core.haptics.HapticManager
-import androidx.core.haptics.signal.PredefinedEffect
-import androidx.core.haptics.signal.PredefinedEffect.Companion.PredefinedClick
-import androidx.core.haptics.signal.PredefinedEffect.Companion.PredefinedDoubleClick
-import androidx.core.haptics.signal.PredefinedEffect.Companion.PredefinedHeavyClick
-import androidx.core.haptics.signal.PredefinedEffect.Companion.PredefinedTick
+import androidx.core.haptics.VibratorWrapper
+import androidx.core.haptics.signal.HapticSignal
/**
* [HapticManager] implementation for the [Vibrator] service.
*/
internal class HapticManagerImpl internal constructor(
- private val vibrator: Vibrator
+ private val vibrator: VibratorWrapper
) : HapticManager {
- internal constructor(context: Context) : this(
- requireNotNull(ContextCompat.getSystemService(context, Vibrator::class.java)) {
- "Vibrator service not found"
- }
- )
-
@RequiresPermission(android.Manifest.permission.VIBRATE)
- override fun play(effect: PredefinedEffect) {
- if (VERSION.SDK_INT >= 29) {
- Api29Impl.play(vibrator, effect)
- } else {
- ApiImpl.play(vibrator, effect)
- }
- }
-
- /** Version-specific static inner class. */
- @RequiresApi(29)
- private object Api29Impl {
-
- @JvmStatic
- @DoNotInline
- @RequiresPermission(android.Manifest.permission.VIBRATE)
- fun play(vibrator: Vibrator, effect: PredefinedEffect) {
- vibrator.vibrate(VibrationEffect.createPredefined(effect.effectId))
- }
- }
-
- /** Version-specific static inner class. */
- private object ApiImpl {
-
- private val predefinedEffectFallbackPatterns = mapOf(
- PredefinedTick to longArrayOf(0, 10),
- PredefinedClick to longArrayOf(0, 20),
- PredefinedHeavyClick to longArrayOf(0, 30),
- PredefinedDoubleClick to longArrayOf(0, 30, 100, 30)
- )
-
- @JvmStatic
- @Suppress("DEPRECATION") // ApkVariant for compatibility
- @RequiresPermission(android.Manifest.permission.VIBRATE)
- fun play(vibrator: Vibrator, effect: PredefinedEffect) {
- predefinedEffectFallbackPatterns[effect]?.let {
- vibrator.vibrate(/* pattern= */ it, /* repeat= */ -1)
- }
+ override fun play(signal: HapticSignal) {
+ signal.toVibration()?.let {
+ vibration -> vibrator.vibrate(vibration)
}
}
}
diff --git a/core/haptics/haptics/src/main/java/androidx/core/haptics/impl/HapticSignalConverter.kt b/core/haptics/haptics/src/main/java/androidx/core/haptics/impl/HapticSignalConverter.kt
new file mode 100644
index 0000000..705c6d1
--- /dev/null
+++ b/core/haptics/haptics/src/main/java/androidx/core/haptics/impl/HapticSignalConverter.kt
@@ -0,0 +1,269 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.haptics.impl
+
+import android.os.Build
+import android.os.VibrationEffect
+import androidx.annotation.DoNotInline
+import androidx.annotation.RequiresApi
+import androidx.core.haptics.PatternVibrationWrapper
+import androidx.core.haptics.VibrationEffectWrapper
+import androidx.core.haptics.VibrationWrapper
+import androidx.core.haptics.signal.CompositionSignal
+import androidx.core.haptics.signal.CompositionSignal.OffAtom
+import androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom
+import androidx.core.haptics.signal.PredefinedEffectSignal
+import androidx.core.haptics.signal.WaveformSignal
+import androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom
+import androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom.Companion.DEFAULT_AMPLITUDE
+import kotlin.math.roundToInt
+
+private const val VIBRATION_DEFAULT_AMPLITUDE: Int = -1 // VibrationEffect.DEFAULT_AMPLITUDE
+private const val VIBRATION_MAX_AMPLITUDE: Int = 255 // VibrationEffect.MAX_AMPLITUDE
+
+/** Returns true if amplitude is 0, 1 or [DEFAULT_AMPLITUDE]. */
+internal fun ConstantVibrationAtom.hasPatternAmplitude(): Boolean =
+ (amplitude == 0f) || (amplitude == 1f) || (amplitude == DEFAULT_AMPLITUDE)
+
+/** Returns the amplitude value in [0,255] or [android.os.VibrationEffect.DEFAULT_AMPLITUDE]. */
+internal fun ConstantVibrationAtom.getAmplitudeInt(): Int =
+ if (amplitude == DEFAULT_AMPLITUDE) {
+ VIBRATION_DEFAULT_AMPLITUDE
+ } else {
+ (amplitude * VIBRATION_MAX_AMPLITUDE).roundToInt()
+ }
+
+/**
+ * Helper class to convert haptic signals to platform types based on SDK support available.
+ */
+internal object HapticSignalConverter {
+
+ internal fun toVibration(effect: PredefinedEffectSignal): VibrationWrapper? =
+ if (Build.VERSION.SDK_INT >= 29) {
+ Api29Impl.toVibrationEffect(effect)
+ } else {
+ ApiImpl.toPatternVibration(effect)
+ }
+
+ internal fun toVibration(
+ initialWaveform: WaveformSignal?,
+ repeatingWaveform: WaveformSignal?
+ ): VibrationWrapper? =
+ if (Build.VERSION.SDK_INT >= 26) {
+ Api26Impl.toVibrationEffect(initialWaveform, repeatingWaveform)
+ } else {
+ ApiImpl.toPatternVibration(initialWaveform, repeatingWaveform)
+ }
+
+ internal fun toVibration(composition: CompositionSignal): VibrationWrapper? =
+ if (Build.VERSION.SDK_INT >= 31) {
+ Api31Impl.toVibrationEffect(composition)
+ } else if (Build.VERSION.SDK_INT >= 30) {
+ Api30Impl.toVibrationEffect(composition)
+ } else {
+ null
+ }
+
+ /** Version-specific static inner class. */
+ @RequiresApi(31)
+ private object Api31Impl {
+ @JvmStatic
+ @DoNotInline
+ fun toVibrationEffect(composition: CompositionSignal): VibrationEffectWrapper? =
+ if (composition.minSdk() <= 31) {
+ // Use same API to create composition from API 30, but allow constants from API 31.
+ Api30Impl.createComposition(composition)
+ } else {
+ null
+ }
+ }
+
+ /** Version-specific static inner class. */
+ @RequiresApi(30)
+ private object Api30Impl {
+ @JvmStatic
+ @DoNotInline
+ fun toVibrationEffect(composition: CompositionSignal): VibrationEffectWrapper? =
+ if (composition.minSdk() <= 30) {
+ createComposition(composition)
+ } else {
+ null
+ }
+
+ @JvmStatic
+ @DoNotInline
+ fun createComposition(composition: CompositionSignal): VibrationEffectWrapper? {
+ val platformComposition = VibrationEffect.startComposition()
+ var delayMs = 0
+
+ composition.atoms.forEach { atom ->
+ when (atom) {
+ is PrimitiveAtom -> {
+ platformComposition.addPrimitive(atom.type, atom.amplitudeScale, delayMs)
+ delayMs = 0
+ }
+
+ is OffAtom -> {
+ delayMs += atom.durationMillis.toInt()
+ }
+
+ else -> {
+ // Unsupported composition atom
+ return@createComposition null
+ }
+ }
+ }
+
+ return VibrationEffectWrapper(platformComposition.compose())
+ }
+ }
+
+ /** Version-specific static inner class. */
+ @RequiresApi(29)
+ private object Api29Impl {
+ @JvmStatic
+ @DoNotInline
+ fun toVibrationEffect(effect: PredefinedEffectSignal): VibrationEffectWrapper? =
+ if (effect.minSdk() <= 29) {
+ VibrationEffectWrapper(VibrationEffect.createPredefined(effect.type))
+ } else {
+ null
+ }
+ }
+
+ /** Version-specific static inner class. */
+ @RequiresApi(26)
+ private object Api26Impl {
+
+ @JvmStatic
+ @DoNotInline
+ fun toVibrationEffect(
+ initialWaveform: WaveformSignal? = null,
+ repeatingWaveform: WaveformSignal? = null,
+ ): VibrationEffectWrapper? {
+ if (initialWaveform?.atoms?.any { it !is ConstantVibrationAtom } == true ||
+ repeatingWaveform?.atoms?.any { it !is ConstantVibrationAtom } == true) {
+ // Unsupported waveform atoms
+ return null
+ }
+
+ val initialAtoms =
+ initialWaveform?.atoms?.filterIsInstance<ConstantVibrationAtom>().orEmpty()
+ val repeatingAtoms =
+ repeatingWaveform?.atoms?.filterIsInstance<ConstantVibrationAtom>().orEmpty()
+ val allAtoms = initialAtoms + repeatingAtoms
+
+ val timings = allAtoms.map { it.durationMillis }.toLongArray()
+ val amplitudes = allAtoms.map { it.getAmplitudeInt() }.toIntArray()
+ val repeatIndex = if (repeatingAtoms.isNotEmpty()) initialAtoms.size else -1
+
+ if (timings.isEmpty() || timings.sum() == 0L) {
+ // Empty or zero duration waveforms not supported by VibrationEffect.createWaveform
+ return null
+ }
+
+ return VibrationEffectWrapper(
+ VibrationEffect.createWaveform(timings, amplitudes, repeatIndex)
+ )
+ }
+ }
+
+ /** Version-specific static inner class. */
+ private object ApiImpl {
+
+ @JvmStatic
+ fun toPatternVibration(effect: PredefinedEffectSignal): PatternVibrationWrapper? =
+ // Fallback patterns for predefined effects in SDK < 29.
+ when (effect.type) {
+ PredefinedEffectSignal.TICK ->
+ PatternVibrationWrapper(longArrayOf(0, 10), repeatIndex = -1)
+ PredefinedEffectSignal.CLICK ->
+ PatternVibrationWrapper(longArrayOf(0, 20), repeatIndex = -1)
+ PredefinedEffectSignal.HEAVY_CLICK ->
+ PatternVibrationWrapper(longArrayOf(0, 30), repeatIndex = -1)
+ PredefinedEffectSignal.DOUBLE_CLICK ->
+ PatternVibrationWrapper(longArrayOf(0, 30, 100, 30), repeatIndex = -1)
+ else ->
+ null
+ }
+
+ @JvmStatic
+ fun toPatternVibration(
+ initialWaveform: WaveformSignal? = null,
+ repeatingWaveform: WaveformSignal? = null,
+ ): PatternVibrationWrapper? {
+ if (initialWaveform?.atoms?.any { it !is ConstantVibrationAtom } == true ||
+ repeatingWaveform?.atoms?.any { it !is ConstantVibrationAtom } == true) {
+ // Unsupported waveform entries
+ return null
+ }
+
+ val initialAtoms =
+ initialWaveform?.atoms?.filterIsInstance<ConstantVibrationAtom>().orEmpty()
+ .toMutableList()
+ val repeatingAtoms =
+ repeatingWaveform?.atoms?.filterIsInstance<ConstantVibrationAtom>().orEmpty()
+
+ if (!initialAtoms.all { it.hasPatternAmplitude() } ||
+ !repeatingAtoms.all { it.hasPatternAmplitude() }) {
+ // Not possible to represent all amplitudes by an on-off pattern.
+ return null
+ }
+
+ val allAtoms = initialAtoms + repeatingAtoms
+ val timings = mutableListOf<Long>()
+ var currentIsOff = true // Vibration pattern starts with an off entry.
+ var currentTiming = 0L
+ var repeatIndex = -1
+
+ for ((index, atom) in allAtoms.withIndex()) {
+ if (index == initialAtoms.size) { // This is the first repeating atom.
+ if (currentTiming > 0) {
+ // Make sure not to merge the last initial atom to the first repeating one.
+ timings.add(currentTiming)
+ currentTiming = 0
+ currentIsOff = !currentIsOff
+ }
+ // Mark the start of the repetition before adding the first repeating atom.
+ repeatIndex = timings.size
+ }
+ val atomIsOff = atom.amplitude == 0f
+ if (currentIsOff == atomIsOff) {
+ // Merge timings of same on/off state.
+ currentTiming += atom.durationMillis
+ } else {
+ // Start new timing with different on/off state.
+ timings.add(currentTiming)
+ currentTiming = atom.durationMillis
+ currentIsOff = atomIsOff
+ }
+ }
+
+ if (currentTiming > 0) {
+ // Add last timing entry to the pattern.
+ timings.add(currentTiming)
+ }
+
+ if (timings.isEmpty() || timings.sum() == 0L) {
+ // Empty or zero duration waveforms are not supported by pattern vibrations.
+ return null
+ }
+
+ return PatternVibrationWrapper(timings.toLongArray(), repeatIndex)
+ }
+ }
+}
diff --git a/core/haptics/haptics/src/main/java/androidx/core/haptics/impl/VibratorWrapperImpl.kt b/core/haptics/haptics/src/main/java/androidx/core/haptics/impl/VibratorWrapperImpl.kt
new file mode 100644
index 0000000..decc9e0
--- /dev/null
+++ b/core/haptics/haptics/src/main/java/androidx/core/haptics/impl/VibratorWrapperImpl.kt
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.haptics.impl
+
+import android.annotation.SuppressLint
+import android.os.Build
+import android.os.VibrationEffect
+import android.os.Vibrator
+import androidx.annotation.DoNotInline
+import androidx.annotation.RequiresApi
+import androidx.annotation.RequiresPermission
+import androidx.core.haptics.PatternVibrationWrapper
+import androidx.core.haptics.VibrationEffectWrapper
+import androidx.core.haptics.VibrationWrapper
+import androidx.core.haptics.VibratorWrapper
+
+/**
+ * [VibratorWrapper] implementation backed by a real [Vibrator] service.
+ */
+internal class VibratorWrapperImpl(
+ private val vibrator: Vibrator
+) : VibratorWrapper {
+
+ override fun hasVibrator(): Boolean = ApiImpl.hasVibrator(vibrator)
+
+ override fun hasAmplitudeControl(): Boolean =
+ if (Build.VERSION.SDK_INT >= 26) {
+ Api26Impl.hasAmplitudeControl(vibrator)
+ } else {
+ false
+ }
+
+ override fun areEffectsSupported(effects: IntArray): Array<VibratorWrapper.EffectSupport>? =
+ if (Build.VERSION.SDK_INT >= 30) {
+ Api30Impl.areEffectsSupported(vibrator, effects)
+ } else {
+ null
+ }
+
+ override fun arePrimitivesSupported(primitives: IntArray): BooleanArray? =
+ if (Build.VERSION.SDK_INT >= 30) {
+ Api30Impl.arePrimitivesSupported(vibrator, primitives)
+ } else {
+ null
+ }
+
+ @RequiresPermission(android.Manifest.permission.VIBRATE)
+ override fun vibrate(vibration: VibrationWrapper) {
+ when (vibration) {
+ is VibrationEffectWrapper -> {
+ check(Build.VERSION.SDK_INT >= 26) {
+ "Attempting to vibrate with VibrationEffect before Android O is not supported"
+ }
+ if (Build.VERSION.SDK_INT >= 26) {
+ Api26Impl.vibrate(vibrator, vibration)
+ }
+ }
+ is PatternVibrationWrapper ->
+ ApiImpl.vibrate(vibrator, vibration)
+ }
+ }
+
+ @RequiresPermission(android.Manifest.permission.VIBRATE)
+ override fun cancel() {
+ ApiImpl.cancel(vibrator)
+ }
+
+ /** Version-specific static inner class. */
+ @RequiresApi(30)
+ private object Api30Impl {
+
+ @SuppressLint("WrongConstant") // custom conversion between jetpack and framework
+ @JvmStatic
+ @DoNotInline
+ fun areEffectsSupported(
+ vibrator: Vibrator,
+ effects: IntArray,
+ ): Array<VibratorWrapper.EffectSupport> {
+ return vibrator.areEffectsSupported(*effects).map {
+ when (it) {
+ Vibrator.VIBRATION_EFFECT_SUPPORT_YES -> VibratorWrapper.EffectSupport.YES
+ Vibrator.VIBRATION_EFFECT_SUPPORT_NO -> VibratorWrapper.EffectSupport.NO
+ else -> VibratorWrapper.EffectSupport.UNKNOWN
+ }
+ }.toTypedArray()
+ }
+
+ @SuppressLint("WrongConstant") // custom conversion between jetpack and framework
+ @JvmStatic
+ @DoNotInline
+ fun arePrimitivesSupported(
+ vibrator: Vibrator,
+ primitives: IntArray,
+ ): BooleanArray {
+ return vibrator.arePrimitivesSupported(*primitives).toTypedArray().toBooleanArray()
+ }
+ }
+
+ /** Version-specific static inner class. */
+ @RequiresApi(26)
+ private object Api26Impl {
+
+ @JvmStatic
+ @DoNotInline
+ fun hasAmplitudeControl(vibrator: Vibrator) = vibrator.hasAmplitudeControl()
+
+ @JvmStatic
+ @DoNotInline
+ @RequiresPermission(android.Manifest.permission.VIBRATE)
+ fun vibrate(vibrator: Vibrator, effect: VibrationEffectWrapper) {
+ check(effect.vibrationEffect is VibrationEffect) {
+ "Attempting to vibrate with unexpected vibration effect ${effect.vibrationEffect}"
+ }
+ vibrator.vibrate(effect.vibrationEffect)
+ }
+ }
+
+ /** Version-specific static inner class. */
+ private object ApiImpl {
+
+ @JvmStatic
+ fun hasVibrator(vibrator: Vibrator) = vibrator.hasVibrator()
+
+ @JvmStatic
+ @Suppress("DEPRECATION") // ApkVariant for compatibility
+ @RequiresPermission(android.Manifest.permission.VIBRATE)
+ fun vibrate(vibrator: Vibrator, pattern: PatternVibrationWrapper) =
+ vibrator.vibrate(pattern.timings, pattern.repeatIndex)
+
+ @JvmStatic
+ @RequiresPermission(android.Manifest.permission.VIBRATE)
+ fun cancel(vibrator: Vibrator) = vibrator.cancel()
+ }
+}
diff --git a/core/haptics/haptics/src/main/java/androidx/core/haptics/signal/CompositionSignal.kt b/core/haptics/haptics/src/main/java/androidx/core/haptics/signal/CompositionSignal.kt
new file mode 100644
index 0000000..0197b93
--- /dev/null
+++ b/core/haptics/haptics/src/main/java/androidx/core/haptics/signal/CompositionSignal.kt
@@ -0,0 +1,444 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.haptics.signal
+
+import android.os.Build
+import androidx.annotation.FloatRange
+import androidx.annotation.IntDef
+import androidx.annotation.RequiresApi
+import androidx.annotation.RestrictTo
+import androidx.core.haptics.VibrationWrapper
+import androidx.core.haptics.impl.HapticSignalConverter
+import java.util.Objects
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.toKotlinDuration
+
+/**
+ * A composition of haptic elements that can be played as one single haptic effect.
+ *
+ * Composition signals may be defined as a composition of scalable primitive effects, which are
+ * tailored to the device hardware. The composition signal is based on the
+ * [android.os.VibrationEffect.Composition] platform API.
+ *
+ * @sample androidx.core.haptics.samples.CompositionSignalOfScaledEffectsAndOff
+ */
+class CompositionSignal(
+
+ /**
+ * The composition signal atoms that describes the haptic elements to be played in sequence.
+ */
+ val atoms: List<Atom>,
+
+) : FiniteSignal() {
+ init {
+ require(atoms.isNotEmpty()) { "Haptic signals cannot be empty" }
+ }
+
+ companion object {
+
+ /**
+ * Returns a [CompositionSignal] with given atoms.
+ *
+ * @sample androidx.core.haptics.samples.CompositionSignalOfScaledEffectsAndOff
+ *
+ * @param atoms The [CompositionSignal.Atom] instances that define the [CompositionSignal].
+ */
+ @JvmStatic
+ fun compositionOf(vararg atoms: Atom): CompositionSignal =
+ CompositionSignal(atoms.toList())
+
+ /**
+ * Returns a [CompositionSignal.Atom] for a very short low frequency tick effect.
+ *
+ * This effect should produce a light crisp sensation intended to be used repetitively
+ * for dynamic feedback.
+ *
+ * @param amplitudeScale The amplitude scale for the new [PrimitiveAtom]
+ */
+ @JvmOverloads
+ @JvmStatic
+ fun lowTick(@FloatRange(from = 0.0, to = 1.0) amplitudeScale: Float = 1f) =
+ PrimitiveAtom.LowTick.withAmplitudeScale(amplitudeScale)
+
+ /**
+ * Returns a [CompositionSignal.Atom] for a very short light tick effect.
+ *
+ * This effect should produce a light crisp sensation stronger than the [lowTick()], and is
+ * also intended to be used repetitively for dynamic feedback.
+ *
+ * @param amplitudeScale The amplitude scale for the new [PrimitiveAtom]
+ */
+ @JvmOverloads
+ @JvmStatic
+ fun tick(@FloatRange(from = 0.0, to = 1.0) amplitudeScale: Float = 1f) =
+ PrimitiveAtom.Tick.withAmplitudeScale(amplitudeScale)
+
+ /**
+ * Returns a [CompositionSignal.Atom] for a click effect.
+ *
+ * This effect should produce a sharp, crisp click sensation.
+ *
+ * @param amplitudeScale The amplitude scale for the new [PrimitiveAtom]
+ */
+ @JvmOverloads
+ @JvmStatic
+ fun click(@FloatRange(from = 0.0, to = 1.0) amplitudeScale: Float = 1f) =
+ PrimitiveAtom.Click.withAmplitudeScale(amplitudeScale)
+
+ /**
+ * Returns a [CompositionSignal.Atom] for an effect with increasing strength.
+ *
+ * This effect simulates quick upward movement against gravity.
+ *
+ * @param amplitudeScale The amplitude scale for the new [PrimitiveAtom]
+ */
+ @JvmOverloads
+ @JvmStatic
+ fun quickRise(@FloatRange(from = 0.0, to = 1.0) amplitudeScale: Float = 1f) =
+ PrimitiveAtom.QuickRise.withAmplitudeScale(amplitudeScale)
+
+ /**
+ * Returns a [CompositionSignal.Atom] for a longer effect with increasing strength.
+ *
+ * This effect simulates slow upward movement against gravity and is longer than the
+ * [quickRise()].
+ *
+ * @param amplitudeScale The amplitude scale for the new [PrimitiveAtom]
+ */
+ @JvmOverloads
+ @JvmStatic
+ fun slowRise(@FloatRange(from = 0.0, to = 1.0) amplitudeScale: Float = 1f) =
+ PrimitiveAtom.SlowRise.withAmplitudeScale(amplitudeScale)
+
+ /**
+ * Returns a [CompositionSignal.Atom] for an effect with decreasing strength.
+ *
+ * This effect simulates quick downwards movement against gravity.
+ *
+ * @param amplitudeScale The amplitude scale for the new [PrimitiveAtom]
+ */
+ @JvmOverloads
+ @JvmStatic
+ fun quickFall(@FloatRange(from = 0.0, to = 1.0) amplitudeScale: Float = 1f) =
+ PrimitiveAtom.QuickFall.withAmplitudeScale(amplitudeScale)
+
+ /**
+ * Returns a [CompositionSignal.Atom] for a spin effect.
+ *
+ * This effect simulates spinning momentum.
+ *
+ * @param amplitudeScale The amplitude scale for the new [PrimitiveAtom]
+ */
+ @JvmOverloads
+ @JvmStatic
+ fun spin(@FloatRange(from = 0.0, to = 1.0) amplitudeScale: Float = 1f) =
+ PrimitiveAtom.Spin.withAmplitudeScale(amplitudeScale)
+
+ /**
+ * Returns a [CompositionSignal.Atom] for a thud effect.
+ *
+ * This effect simulates downwards movement with gravity, often followed by extra energy
+ * of hitting and reverberation to augment physicality.
+ *
+ * @param amplitudeScale The amplitude scale for the new [PrimitiveAtom]
+ */
+ @JvmOverloads
+ @JvmStatic
+ fun thud(
+ @FloatRange(from = 0.0, to = 1.0) amplitudeScale: Float = 1f,
+ ) =
+ PrimitiveAtom.Thud.withAmplitudeScale(amplitudeScale)
+
+ /**
+ * Returns a [CompositionSignal.Atom] to turn the vibrator off for the specified duration.
+ *
+ * @sample androidx.core.haptics.samples.CompositionSignalOfScaledEffectsAndOff
+ *
+ * @param duration The duration the vibrator should be turned off.
+ */
+ @RequiresApi(Build.VERSION_CODES.O)
+ @JvmStatic
+ fun off(duration: java.time.Duration) =
+ OffAtom(duration.toKotlinDuration())
+
+ /**
+ * Returns a [CompositionSignal.Atom] to turn the vibrator off for the specified duration.
+ *
+ * @sample androidx.core.haptics.samples.CompositionSignalOfScaledEffectsAndOff
+ *
+ * @param durationMillis The duration the vibrator should be turned off, in milliseconds.
+ */
+ @JvmStatic
+ fun off(durationMillis: Long) =
+ OffAtom(durationMillis.milliseconds)
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is CompositionSignal) return false
+ if (atoms != other.atoms) return false
+ return true
+ }
+
+ override fun hashCode(): Int {
+ return atoms.hashCode()
+ }
+
+ override fun toString(): String {
+ return "CompositionSignal(${atoms.joinToString()})"
+ }
+
+ /**
+ * Returns the minimum SDK level required by the atoms of this signal.
+ */
+ internal fun minSdk(): Int = atoms.maxOf { it.minSdk() }
+
+ override fun toVibration(): VibrationWrapper? = HapticSignalConverter.toVibration(this)
+
+ /**
+ * A [CompositionSignal.Atom] is a building block for creating a [CompositionSignal].
+ *
+ * Composition signal atoms describe basic haptic elements to be played in sequence as a single
+ * haptic effect. They can describe haptic effects tailored to the device hardware, like click
+ * and tick effects, or then can represent pauses in the effect composition.
+ *
+ * @sample androidx.core.haptics.samples.CompositionSignalOfScaledEffectsAndOff
+ */
+ abstract class Atom internal constructor() {
+
+ /**
+ * The minimum SDK level where this atom is available in the platform.
+ */
+ internal abstract fun minSdk(): Int
+ }
+
+ /**
+ * A [PrimitiveAtom] plays a haptic effect with the specified vibration strength scale.
+ *
+ * Composition primitives are haptic effects tailored to the device hardware with configurable
+ * vibration strength. They can be used as building blocks to create more complex haptic
+ * effects. The primitive atoms are based on the
+ * [android.os.VibrationEffect.Composition.addPrimitive] platform API.
+ *
+ * A primitive effect will always be played with a non-zero vibration amplitude, but the actual
+ * vibration strength can be scaled by values in the range [0f..1f]. Zero [amplitudeScale]
+ * implies the vibrator will play it at the minimum strength required for the effect to be
+ * perceived on the device. The maximum [amplitudeScale] value of 1 implies the vibrator will
+ * play it at the maximum strength that preserves the effect's intended design. For instance, a
+ * [Click] effect with [amplitudeScale] of 1 will usually feel stronger than a [Tick] with same
+ * amplitude scale.
+ *
+ * @sample androidx.core.haptics.samples.CompositionSignalOfScaledEffectsAndOff
+ */
+ class PrimitiveAtom private constructor(
+
+ /**
+ * The type of haptic effect to be played.
+ */
+ @Type val type: Int,
+
+ /**
+ * The minimum SDK level where this effect type is available in the platform.
+ */
+ private val minSdk: Int,
+
+ /**
+ * The scale for the vibration strength.
+ *
+ * A primitive effect will always be played with a non-zero vibration strength. Zero values
+ * here represent minimum effect strength that can still be perceived on the device, and
+ * maximum values represent the maximum strength the effect can be played.
+ */
+ @FloatRange(from = 0.0, to = 1.0) val amplitudeScale: Float = 1f,
+
+ ) : Atom() {
+ init {
+ require(amplitudeScale in 0.0..1.0) {
+ "Primitive amplitude scale must be in [0,1]: $amplitudeScale"
+ }
+ }
+
+ /** Typedef for the [type] attribute. */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @Retention(AnnotationRetention.SOURCE)
+ @IntDef(
+ LOW_TICK,
+ TICK,
+ CLICK,
+ SLOW_RISE,
+ QUICK_RISE,
+ QUICK_FALL,
+ SPIN,
+ THUD,
+ )
+ annotation class Type
+
+ companion object {
+
+ /**
+ * A very short low frequency tick effect.
+ *
+ * This effect should produce a light crisp sensation intended to be used repetitively
+ * for dynamic feedback.
+ */
+ const val LOW_TICK = 8 // VibrationEffect.Composition.PRIMITIVE_LOW_TICK
+
+ /**
+ * A very short light tick effect.
+ *
+ * This effect should produce a light crisp sensation stronger than the [LowTick], and is
+ * also intended to be used repetitively for dynamic feedback.
+ */
+ const val TICK = 7 // VibrationEffect.Composition.PRIMITIVE_TICK
+
+ /**
+ * A click effect.
+ *
+ * This effect should produce a sharp, crisp click sensation.
+ */
+ const val CLICK = 1 // VibrationEffect.Composition.PRIMITIVE_CLICK
+
+ /**
+ * An effect with increasing strength.
+ *
+ * This effect simulates quick upward movement against gravity.
+ */
+ const val QUICK_RISE = 4 // VibrationEffect.Composition.PRIMITIVE_QUICK_RISE
+
+ /**
+ * A longer effect with increasing strength.
+ *
+ * This effect simulates slow upward movement against gravity and is longer than the
+ * [QuickRise].
+ */
+ const val SLOW_RISE = 5 // VibrationEffect.Composition.PRIMITIVE_SLOW_RISE
+
+ /**
+ * An effect with decreasing strength.
+ *
+ * This effect simulates quick downwards movement against gravity.
+ */
+ const val QUICK_FALL = 6 // VibrationEffect.Composition.PRIMITIVE_QUICK_FALL
+
+ /**
+ * A spin effect.
+ *
+ * This effect simulates spinning momentum.
+ */
+ const val SPIN = 3 // VibrationEffect.Composition.PRIMITIVE_SPIN
+
+ /**
+ * A thud effect.
+ *
+ * This effect simulates downwards movement with gravity, often followed by extra energy
+ * of hitting and reverberation to augment physicality.
+ */
+ const val THUD = 2 // VibrationEffect.Composition.PRIMITIVE_THUD
+
+ internal val LowTick = PrimitiveAtom(LOW_TICK, Build.VERSION_CODES.S)
+ internal val Tick = PrimitiveAtom(TICK, Build.VERSION_CODES.R)
+ internal val Click = PrimitiveAtom(CLICK, Build.VERSION_CODES.R)
+ internal val QuickRise = PrimitiveAtom(QUICK_RISE, Build.VERSION_CODES.R)
+ internal val SlowRise = PrimitiveAtom(SLOW_RISE, Build.VERSION_CODES.R)
+ internal val QuickFall = PrimitiveAtom(QUICK_FALL, Build.VERSION_CODES.R)
+ internal val Spin = PrimitiveAtom(SPIN, Build.VERSION_CODES.S)
+ internal val Thud = PrimitiveAtom(THUD, Build.VERSION_CODES.S)
+ }
+
+ /**
+ * Returns a [PrimitiveAtom] with same effect type and new [amplitudeScale].
+ *
+ * @sample androidx.core.haptics.samples.CompositionSignalOfScaledEffectsAndOff
+ *
+ * @param newAmplitudeScale The amplitude scale for the new [PrimitiveAtom]
+ * @return A new [PrimitiveAtom] with the same effect type and the new amplitude scale.
+ */
+ fun withAmplitudeScale(
+ @FloatRange(from = 0.0, to = 1.0) newAmplitudeScale: Float,
+ ): PrimitiveAtom =
+ if (amplitudeScale == newAmplitudeScale) {
+ this
+ } else {
+ PrimitiveAtom(type, minSdk, newAmplitudeScale)
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is PrimitiveAtom) return false
+ if (type != other.type) return false
+ if (minSdk != other.minSdk) return false
+ if (amplitudeScale != other.amplitudeScale) return false
+ return true
+ }
+
+ override fun hashCode(): Int = Objects.hash(type, amplitudeScale)
+
+ override fun toString(): String {
+ val typeStr = when (type) {
+ LOW_TICK -> "LowTick"
+ TICK -> "Tick"
+ CLICK -> "Click"
+ SLOW_RISE -> "SlowRise"
+ QUICK_RISE -> "QuickRise"
+ QUICK_FALL -> "QuickFall"
+ SPIN -> "Spin"
+ THUD -> "Thud"
+ else -> type.toString()
+ }
+ return "PrimitiveAtom(type=$typeStr, amplitude=$amplitudeScale)"
+ }
+
+ override fun minSdk(): Int = minSdk
+ }
+
+ /**
+ * A [OffAtom] turns off the vibrator for the specified duration.
+ *
+ * @sample androidx.core.haptics.samples.CompositionSignalOfScaledEffectsAndOff
+ */
+ class OffAtom internal constructor(duration: Duration) : Atom() {
+ /**
+ * The duration for the vibrator to be turned off, in milliseconds.
+ */
+ val durationMillis: Long
+
+ init {
+ require(duration.isFinite() && !duration.isNegative()) {
+ "Composition signal off atom duration must be finite and non-negative: $duration"
+ }
+ durationMillis = duration.inWholeMilliseconds
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is OffAtom) return false
+ if (durationMillis != other.durationMillis) return false
+ return true
+ }
+
+ override fun hashCode(): Int {
+ return durationMillis.hashCode()
+ }
+
+ override fun toString(): String {
+ return "OffAtom(durationMillis=$durationMillis)"
+ }
+
+ override fun minSdk(): Int = Build.VERSION_CODES.R
+ }
+}
diff --git a/core/haptics/haptics/src/main/java/androidx/core/haptics/signal/HapticSignal.kt b/core/haptics/haptics/src/main/java/androidx/core/haptics/signal/HapticSignal.kt
new file mode 100644
index 0000000..69ae2c5
--- /dev/null
+++ b/core/haptics/haptics/src/main/java/androidx/core/haptics/signal/HapticSignal.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.haptics.signal
+
+import androidx.core.haptics.VibrationWrapper
+
+/**
+ * A [HapticSignal] describes a generic vibration to be played by a vibrator.
+ *
+ * These signals may represent any number of things, from single shot vibrations to complex
+ * waveforms, device-specific predefined effects or custom vibration patterns.
+ *
+ * A haptic signal can be defined as a [FiniteSignal] or [InfiniteSignal]. Infinite signals will be
+ * played by the vibrator until canceled, while finite signals will stop playing once completed.
+ *
+ * Note: This is a library-restricted representation of a haptic signal that will be mapped to a
+ * platform representation available, like [android.os.VibrationEffect]. This class cannot be
+ * extended or supplemented outside the library, but they can be instantiated from custom extensions
+ * via factory methods.
+ */
+abstract class HapticSignal internal constructor() {
+
+ /**
+ * Returns a [VibrationWrapper] representing this signal, or null if not supported in this SDK
+ * level.
+ */
+ internal abstract fun toVibration(): VibrationWrapper?
+}
+
+/**
+ * A [FiniteSignal] describes a non-infinite haptic signal to be played by a vibrator.
+ */
+abstract class FiniteSignal internal constructor() : HapticSignal()
+
+/**
+ * A [InfiniteSignal] describes a haptic signal that will be played by a vibrator until canceled.
+ */
+abstract class InfiniteSignal internal constructor() : HapticSignal()
diff --git a/core/haptics/haptics/src/main/java/androidx/core/haptics/signal/PredefinedEffect.kt b/core/haptics/haptics/src/main/java/androidx/core/haptics/signal/PredefinedEffect.kt
deleted file mode 100644
index abe1b89..0000000
--- a/core/haptics/haptics/src/main/java/androidx/core/haptics/signal/PredefinedEffect.kt
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.core.haptics.signal
-
-/**
- * A [PredefinedEffect] describes a haptic effect to be played by a vibrator.
- *
- * Predefined effects represent common vibration effects that should be identical, regardless of
- * the app they come from, in order to provide a cohesive experience for users across the entire
- * device.
- *
- * They also may be custom tailored to the device hardware in order to provide a better
- * experience than you could otherwise build using the generic building blocks.
- *
- * This will fallback to a generic pattern if one exists and there is no hardware-specific
- * implementation of the effect available.
- */
-class PredefinedEffect private constructor(
-
- /** The id of the effect to be played. */
- internal val effectId: Int
-) {
-
- companion object {
-
- /**
- * A standard tick effect.
- *
- * This effect is less strong than the [PredefinedClick].
- */
- @JvmField
- val PredefinedTick = PredefinedEffect(2) // VibrationEffect.EFFECT_TICK
-
- /**
- * A standard click effect.
- *
- * Use this effect as a baseline, as it's the most common type of click effect.
- */
- @JvmField
- val PredefinedClick = PredefinedEffect(0) // VibrationEffect.EFFECT_CLICK
-
- /**
- * A heavy click effect.
- *
- * This effect is stronger than the [PredefinedClick].
- */
- @JvmField
- val PredefinedHeavyClick = PredefinedEffect(5) // VibrationEffect.EFFECT_HEAVY_CLICK
-
- /**
- * A double-click effect.
- */
- @JvmField
- val PredefinedDoubleClick = PredefinedEffect(1) // VibrationEffect.EFFECT_DOUBLE_CLICK
- }
-
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (other !is PredefinedEffect) return false
- if (effectId != other.effectId) return false
- return true
- }
-
- override fun hashCode(): Int {
- return effectId.hashCode()
- }
-}
diff --git a/core/haptics/haptics/src/main/java/androidx/core/haptics/signal/PredefinedEffectSignal.kt b/core/haptics/haptics/src/main/java/androidx/core/haptics/signal/PredefinedEffectSignal.kt
new file mode 100644
index 0000000..a81419a
--- /dev/null
+++ b/core/haptics/haptics/src/main/java/androidx/core/haptics/signal/PredefinedEffectSignal.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.haptics.signal
+
+import android.os.Build
+import androidx.annotation.IntDef
+import androidx.annotation.RestrictTo
+import androidx.core.haptics.VibrationWrapper
+import androidx.core.haptics.impl.HapticSignalConverter
+
+/**
+ * A predefined haptic effect that represents common vibration effects, like clicks and ticks.
+ *
+ * Predefined haptic effects should be identical, regardless of the app they come from, in order to
+ * provide a cohesive experience for users across the entire device. The predefined effects are
+ * based on the [android.os.VibrationEffect.createPredefined] platform API.
+ *
+ * @sample androidx.core.haptics.samples.PlaySystemStandardClick
+ */
+class PredefinedEffectSignal private constructor(
+
+ /**
+ * The type of haptic effect to be played.
+ */
+ @Type internal val type: Int,
+
+ /**
+ * The minimum SDK level where this effect type is available in the platform.
+ */
+ private val minSdk: Int,
+
+) : FiniteSignal() {
+
+ /** Typedef for the [type] attribute. */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @Retention(AnnotationRetention.SOURCE)
+ @IntDef(
+ TICK,
+ CLICK,
+ HEAVY_CLICK,
+ DOUBLE_CLICK,
+ )
+ annotation class Type
+
+ companion object {
+ internal const val TICK = 2 // VibrationEffect.EFFECT_TICK
+ internal const val CLICK = 0 // VibrationEffect.EFFECT_CLICK
+ internal const val HEAVY_CLICK = 5 // VibrationEffect.EFFECT_HEAVY_CLICK
+ internal const val DOUBLE_CLICK = 1 // VibrationEffect.EFFECT_DOUBLE_CLICK
+
+ private val Tick = PredefinedEffectSignal(TICK, Build.VERSION_CODES.Q)
+ private val Click = PredefinedEffectSignal(CLICK, Build.VERSION_CODES.Q)
+ private val HeavyClick = PredefinedEffectSignal(HEAVY_CLICK, Build.VERSION_CODES.Q)
+ private val DoubleClick = PredefinedEffectSignal(DOUBLE_CLICK, Build.VERSION_CODES.Q)
+
+ /**
+ * A standard tick effect.
+ *
+ * This effect is less strong than the [predefinedClick()].
+ */
+ @JvmStatic
+ fun predefinedTick() = Tick
+
+ /**
+ * A standard click effect.
+ *
+ * Use this effect as a baseline, as it's the most common type of click effect.
+ */
+ @JvmStatic
+ fun predefinedClick() = Click
+
+ /**
+ * A heavy click effect.
+ *
+ * This effect is stronger than the [predefinedClick()].
+ */
+ @JvmStatic
+ fun predefinedHeavyClick() = HeavyClick
+
+ /**
+ * A double-click effect.
+ */
+ @JvmStatic
+ fun predefinedDoubleClick() = DoubleClick
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is PredefinedEffectSignal) return false
+ if (type != other.type) return false
+ return true
+ }
+
+ override fun hashCode(): Int {
+ return type.hashCode()
+ }
+
+ override fun toString(): String {
+ val typeStr = when (type) {
+ TICK -> "Tick"
+ CLICK -> "Click"
+ HEAVY_CLICK -> "HeavyClick"
+ DOUBLE_CLICK -> "DoubleClick"
+ else -> type.toString()
+ }
+ return "PredefinedEffectSignal(type=$typeStr)"
+ }
+
+ /**
+ * Returns the minimum SDK level required by the effect type.
+ */
+ internal fun minSdk(): Int = minSdk
+
+ override fun toVibration(): VibrationWrapper? = HapticSignalConverter.toVibration(this)
+}
diff --git a/core/haptics/haptics/src/main/java/androidx/core/haptics/signal/WaveformSignal.kt b/core/haptics/haptics/src/main/java/androidx/core/haptics/signal/WaveformSignal.kt
new file mode 100644
index 0000000..3f1b87f
--- /dev/null
+++ b/core/haptics/haptics/src/main/java/androidx/core/haptics/signal/WaveformSignal.kt
@@ -0,0 +1,333 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.haptics.signal
+
+import android.os.Build
+import androidx.annotation.FloatRange
+import androidx.annotation.RequiresApi
+import androidx.core.haptics.VibrationWrapper
+import androidx.core.haptics.impl.HapticSignalConverter
+import androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom.Companion.DEFAULT_AMPLITUDE
+import java.util.Objects
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.toKotlinDuration
+
+/**
+ * A haptic signal where the vibration parameters change over time.
+ *
+ * Waveform signals may be used to describe step waveforms, defined by a sequence of constant
+ * vibrations played at different strengths. They can also be combined to define a
+ * [RepeatingWaveformSignal], which is an [InfiniteSignal] that repeats a waveform until the
+ * vibration is canceled.
+ *
+ * @sample androidx.core.haptics.samples.AmplitudeWaveform
+ * @sample androidx.core.haptics.samples.PatternThenRepeatAmplitudeWaveform
+ */
+class WaveformSignal(
+
+ /**
+ * The waveform signal atoms that describes the vibration parameters over time.
+ */
+ val atoms: List<Atom>,
+
+) : FiniteSignal() {
+ init {
+ require(atoms.isNotEmpty()) { "Haptic signals cannot be empty" }
+ }
+
+ companion object {
+
+ /**
+ * Returns a [WaveformSignal] created with given waveform atoms.
+ *
+ * Use [on] and [off] to create atoms.
+ *
+ * @sample androidx.core.haptics.samples.AmplitudeWaveform
+ *
+ * @param atoms The [WaveformSignal.Atom] instances that define the [WaveformSignal].
+ */
+ @JvmStatic
+ fun waveformOf(vararg atoms: Atom): WaveformSignal =
+ WaveformSignal(atoms.toList())
+
+ /**
+ * Returns a [RepeatingWaveformSignal] created with given waveform atoms.
+ *
+ * Repeating waveforms should include any desired loop delay as an [off] atom at the end of
+ * the atom list.
+ *
+ * @sample androidx.core.haptics.samples.RepeatingAmplitudeWaveform
+ *
+ * @param atoms The [WaveformSignal.Atom] instances that define the
+ * [RepeatingWaveformSignal].
+ */
+ @JvmStatic
+ fun repeatingWaveformOf(vararg atoms: Atom): RepeatingWaveformSignal =
+ waveformOf(*atoms).repeat()
+
+ /**
+ * Returns a [WaveformSignal.Atom] that turns off the vibrator for the specified duration.
+ *
+ * @sample androidx.core.haptics.samples.PatternWaveform
+ *
+ * @param duration The duration the vibrator should be turned off.
+ */
+ @RequiresApi(Build.VERSION_CODES.O)
+ @JvmStatic
+ fun off(duration: java.time.Duration) =
+ ConstantVibrationAtom(duration.toKotlinDuration(), amplitude = 0f)
+
+ /**
+ * Returns a [WaveformSignal.Atom] that turns off the vibrator for the specified duration.
+ *
+ * @sample androidx.core.haptics.samples.PatternWaveform
+ *
+ * @param durationMillis The duration the vibrator should be turned off, in milliseconds.
+ */
+ @JvmStatic
+ fun off(durationMillis: Long) =
+ ConstantVibrationAtom(durationMillis.milliseconds, amplitude = 0f)
+
+ /**
+ * Returns a [WaveformSignal.Atom] that turns on the vibrator for the specified duration at
+ * a device-specific default amplitude.
+ *
+ * @sample androidx.core.haptics.samples.PatternWaveform
+ *
+ * @param duration The duration for the vibration.
+ */
+ @RequiresApi(Build.VERSION_CODES.O)
+ @JvmStatic
+ fun on(duration: java.time.Duration) =
+ ConstantVibrationAtom(duration.toKotlinDuration(), DEFAULT_AMPLITUDE)
+
+ /**
+ * Returns a [WaveformSignal.Atom] that turns on the vibrator for the specified duration at
+ * a device-specific default amplitude.
+ *
+ * @sample androidx.core.haptics.samples.PatternWaveform
+ *
+ * @param durationMillis The duration for the vibration, in milliseconds.
+ */
+ @JvmStatic
+ fun on(durationMillis: Long) =
+ ConstantVibrationAtom(durationMillis.milliseconds, DEFAULT_AMPLITUDE)
+
+ /**
+ * Returns a [WaveformSignal.Atom] that turns on the vibrator for the specified duration at
+ * the specified amplitude.
+ *
+ * @sample androidx.core.haptics.samples.AmplitudeWaveform
+ *
+ * @param duration The duration for the vibration.
+ * @param amplitude The vibration strength, with 1 representing maximum amplitude, and 0
+ * representing off - equivalent to calling [off].
+ */
+ @RequiresApi(Build.VERSION_CODES.O)
+ @JvmStatic
+ fun on(duration: java.time.Duration, @FloatRange(from = 0.0, to = 1.0) amplitude: Float) =
+ ConstantVibrationAtom(duration.toKotlinDuration(), amplitude)
+
+ /**
+ * Returns a [WaveformSignal.Atom] that turns on the vibrator for the specified duration at
+ * the specified amplitude.
+ *
+ * @sample androidx.core.haptics.samples.AmplitudeWaveform
+ *
+ * @param durationMillis The duration for the vibration, in milliseconds.
+ * @param amplitude The vibration strength, with 1 representing maximum amplitude, and 0
+ * representing off - equivalent to calling [off].
+ */
+ @JvmStatic
+ fun on(durationMillis: Long, @FloatRange(from = 0.0, to = 1.0) amplitude: Float) =
+ ConstantVibrationAtom(durationMillis.milliseconds, amplitude)
+ }
+
+ /**
+ * Returns a [RepeatingWaveformSignal] to play this waveform on repeat until it's canceled.
+ *
+ * @sample androidx.core.haptics.samples.PatternWaveformRepeat
+ */
+ fun repeat(): RepeatingWaveformSignal =
+ RepeatingWaveformSignal(initialWaveform = null, repeatingWaveform = this)
+
+ /**
+ * Returns a [RepeatingWaveformSignal] that starts with this waveform signal then plays the
+ * given waveform signal on repeat until the vibration is canceled.
+ *
+ * @sample androidx.core.haptics.samples.PatternThenRepeatExistingWaveform
+ *
+ * @param waveformToRepeat The waveform to be played on repeat after this waveform.
+ */
+ fun thenRepeat(waveformToRepeat: WaveformSignal): RepeatingWaveformSignal =
+ RepeatingWaveformSignal(initialWaveform = this, repeatingWaveform = waveformToRepeat)
+
+ /**
+ * Returns a [RepeatingWaveformSignal] that starts with this waveform signal then plays the
+ * given waveform atoms on repeat until the vibration is canceled.
+ *
+ * @sample androidx.core.haptics.samples.PatternThenRepeatAmplitudeWaveform
+ *
+ * @param atoms The [WaveformSignal.Atom] instances that define the repeating [WaveformSignal]
+ * to be played after this waveform.
+ */
+ fun thenRepeat(vararg atoms: Atom): RepeatingWaveformSignal =
+ thenRepeat(waveformOf(*atoms))
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is WaveformSignal) return false
+ if (atoms != other.atoms) return false
+ return true
+ }
+
+ override fun hashCode(): Int {
+ return atoms.hashCode()
+ }
+
+ override fun toString(): String {
+ return "WaveformSignal(${atoms.joinToString()})"
+ }
+
+ override fun toVibration(): VibrationWrapper? =
+ HapticSignalConverter.toVibration(initialWaveform = this, repeatingWaveform = null)
+
+ /**
+ * A [WaveformSignal.Atom] is a building block for creating a [WaveformSignal].
+ *
+ * Waveform signal atoms describe how vibration parameters change over time. They can describe
+ * a constant vibration sustained for a fixed duration, for example, which can be used to create
+ * a step waveform. They can also be used to describe simpler on-off vibration patterns.
+ *
+ * @sample androidx.core.haptics.samples.PatternWaveform
+ * @sample androidx.core.haptics.samples.AmplitudeWaveform
+ */
+ abstract class Atom internal constructor()
+
+ /**
+ * A [ConstantVibrationAtom] plays a constant vibration for the specified period of time.
+ *
+ * Constant vibrations can be played in sequence to create custom waveform signals.
+ *
+ * The amplitude determines the strength of the vibration, defined as a value in the range
+ * [0f..1f]. Zero amplitude implies the vibrator motor should be off. The amplitude can also be
+ * defined by [DEFAULT_AMPLITUDE], which will vibrate constantly at a hardware-specific default
+ * vibration strength.
+ *
+ * @sample androidx.core.haptics.samples.PatternWaveform
+ * @sample androidx.core.haptics.samples.AmplitudeWaveform
+ */
+ class ConstantVibrationAtom internal constructor(
+
+ duration: Duration,
+
+ /**
+ * The vibration strength.
+ *
+ * Zero amplitude turns the vibrator off for the specified duration, and [DEFAULT_AMPLITUDE]
+ * uses a hardware-specific default vibration strength.
+ */
+ val amplitude: Float,
+
+ ) : Atom() {
+ /**
+ * The duration to sustain the constant vibration, in milliseconds.
+ */
+ val durationMillis: Long
+
+ init {
+ require(duration.isFinite() && !duration.isNegative()) {
+ "Constant vibration duration must be finite and non-negative: $duration"
+ }
+ require(amplitude in (0.0..1.0) || amplitude == DEFAULT_AMPLITUDE) {
+ "Constant vibration amplitude must be in [0,1]: $amplitude"
+ }
+ durationMillis = duration.inWholeMilliseconds
+ }
+
+ companion object {
+ /**
+ * The [amplitude] value that represents a hardware-specific default vibration strength.
+ */
+ const val DEFAULT_AMPLITUDE: Float = -1f
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is ConstantVibrationAtom) return false
+ if (durationMillis != other.durationMillis) return false
+ if (amplitude != other.amplitude) return false
+ return true
+ }
+
+ override fun hashCode(): Int {
+ return Objects.hash(durationMillis, amplitude)
+ }
+
+ override fun toString(): String {
+ return "ConstantVibrationAtom(durationMillis=$durationMillis" +
+ ", amplitude=${if (amplitude == DEFAULT_AMPLITUDE) "default" else amplitude})"
+ }
+ }
+}
+
+/**
+ * A [RepeatingWaveformSignal] describes an infinite haptic signal where a waveform signal is played
+ * on repeat until canceled.
+ *
+ * A repeating waveform signal has an optional initial [WaveformSignal] that plays once before the
+ * repeating waveform signal is played on repeat until the vibration is canceled.
+ *
+ * @sample androidx.core.haptics.samples.RepeatingAmplitudeWaveform
+ */
+class RepeatingWaveformSignal internal constructor(
+
+ /**
+ * The optional initial waveform signal to be played once at the beginning of the vibration.
+ */
+ val initialWaveform: WaveformSignal?,
+
+ /**
+ * The waveform signal to be repeated after the initial waveform.
+ */
+ val repeatingWaveform: WaveformSignal,
+
+) : InfiniteSignal() {
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is RepeatingWaveformSignal) return false
+ if (initialWaveform != other.initialWaveform) return false
+ if (repeatingWaveform != other.repeatingWaveform) return false
+ return true
+ }
+
+ override fun hashCode(): Int {
+ return Objects.hash(initialWaveform, repeatingWaveform)
+ }
+
+ override fun toString(): String {
+ return "RepeatingWaveformSignal(initial=$initialWaveform, repeating=$repeatingWaveform)"
+ }
+
+ override fun toVibration(): VibrationWrapper? =
+ HapticSignalConverter.toVibration(
+ initialWaveform = initialWaveform,
+ repeatingWaveform = repeatingWaveform,
+ )
+}
diff --git a/credentials/credentials-fido/credentials-fido/api/current.txt b/credentials/credentials-fido/credentials-fido/api/current.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/credentials/credentials-fido/credentials-fido/api/current.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/credentials/credentials-fido/credentials-fido/api/res-current.txt b/credentials/credentials-fido/credentials-fido/api/res-current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/credentials/credentials-fido/credentials-fido/api/res-current.txt
diff --git a/credentials/credentials-fido/credentials-fido/api/restricted_current.txt b/credentials/credentials-fido/credentials-fido/api/restricted_current.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/credentials/credentials-fido/credentials-fido/api/restricted_current.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/credentials/credentials-fido/credentials-fido/build.gradle b/credentials/credentials-fido/credentials-fido/build.gradle
new file mode 100644
index 0000000..400e738
--- /dev/null
+++ b/credentials/credentials-fido/credentials-fido/build.gradle
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import androidx.build.LibraryType
+
+plugins {
+ id("AndroidXPlugin")
+ id("com.android.library")
+ id("org.jetbrains.kotlin.android")
+}
+
+dependencies {
+ api(libs.kotlinStdlib)
+ api project(":credentials:credentials")
+
+ androidTestImplementation(libs.junit)
+ androidTestImplementation(libs.testExtJunit)
+ androidTestImplementation(libs.testCore)
+ androidTestImplementation(libs.testRunner)
+ androidTestImplementation(libs.testRules)
+ androidTestImplementation(libs.mockitoAndroid)
+ androidTestImplementation(libs.truth)
+ androidTestImplementation(libs.multidex)
+ androidTestImplementation(project(":internal-testutils-truth"))
+ androidTestImplementation(libs.kotlinCoroutinesAndroid)
+ androidTestImplementation("androidx.core:core-ktx:1.10.0")
+}
+
+android {
+ namespace "androidx.credentials.fido"
+
+ defaultConfig {
+ minSdkVersion 19
+ multiDexEnabled = true
+ }
+}
+
+androidx {
+ name = "credentials-fido"
+ type = LibraryType.PUBLISHED_LIBRARY
+ inceptionYear = "2023"
+ description = "Util library for apps using FIDO"
+}
diff --git a/credentials/credentials-fido/credentials-fido/proguard-rules.pro b/credentials/credentials-fido/credentials-fido/proguard-rules.pro
new file mode 100644
index 0000000..4cced1e
--- /dev/null
+++ b/credentials/credentials-fido/credentials-fido/proguard-rules.pro
@@ -0,0 +1,4 @@
+-if class androidx.credentials.CredentialManager
+-keep class androidx.credentials.passkeys.** {
+ *;
+}
\ No newline at end of file
diff --git a/credentials/credentials-fido/credentials-fido/src/androidTest/AndroidManifest.xml b/credentials/credentials-fido/credentials-fido/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..dc727a5
--- /dev/null
+++ b/credentials/credentials-fido/credentials-fido/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<!--
+ Copyright 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <application>
+ </application>
+</manifest>
\ No newline at end of file
diff --git a/credentials/credentials-fido/credentials-fido/src/main/AndroidManifest.xml b/credentials/credentials-fido/credentials-fido/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..d8a4ecd
--- /dev/null
+++ b/credentials/credentials-fido/credentials-fido/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<!--
+ Copyright 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<manifest xmlns:tools="http://schemas.android.com/tools"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <application>
+ </application>
+</manifest>
diff --git a/credentials/credentials-fido/credentials-fido/src/main/androidx/credentials/androidx-credentials-credentials-fido-documentation.md b/credentials/credentials-fido/credentials-fido/src/main/androidx/credentials/androidx-credentials-credentials-fido-documentation.md
new file mode 100644
index 0000000..b0450b7
--- /dev/null
+++ b/credentials/credentials-fido/credentials-fido/src/main/androidx/credentials/androidx-credentials-credentials-fido-documentation.md
@@ -0,0 +1,7 @@
+# Module root
+
+CREDENTIALS CREDENTIALS FIDO
+
+# Package androidx.credentials.fido
+
+This package contains utility functions for FIDO app developers.
\ No newline at end of file
diff --git a/camera/camera-effects/src/main/java/androidx/camera/effects/Portrait.java b/credentials/credentials-fido/credentials-fido/src/main/java/androidx/credentials/fido/EmptyActivity.kt
similarity index 62%
copy from camera/camera-effects/src/main/java/androidx/camera/effects/Portrait.java
copy to credentials/credentials-fido/credentials-fido/src/main/java/androidx/credentials/fido/EmptyActivity.kt
index 7a355f8..20839f9 100644
--- a/camera/camera-effects/src/main/java/androidx/camera/effects/Portrait.java
+++ b/credentials/credentials-fido/credentials-fido/src/main/java/androidx/credentials/fido/EmptyActivity.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,15 +14,12 @@
* limitations under the License.
*/
-package androidx.camera.effects;
+package androidx.credentials.fido
-import androidx.annotation.RestrictTo;
+import android.app.Activity
+import androidx.annotation.RestrictTo
-/**
- * Provides a portrait post-processing effect.
- *
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class Portrait {
- // TODO: implement this
-}
+/** Remove this class when we add the code (b/292103206). */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+@Suppress("Deprecation", "ForbiddenSuperClass")
+open class EmptyActivity : Activity()
diff --git a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/TestUtils.kt b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/TestUtils.kt
index f1cf823..4c5226d 100644
--- a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/TestUtils.kt
+++ b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/TestUtils.kt
@@ -75,13 +75,16 @@
val superValues = superset.get(key)
if ((values::class.java != superValues::class.java || values::class.java !=
- requiredValues::class.java) && requiredValues !is Boolean
+ requiredValues::class.java) && requiredValues !is Boolean
) {
return false
}
if (requiredValues is JSONObject) {
- if (!isSubsetJson(superValues as JSONObject, values as JSONObject,
- requiredValues)) {
+ if (!isSubsetJson(
+ superValues as JSONObject, values as JSONObject,
+ requiredValues
+ )
+ ) {
return false
}
} else if (values is JSONArray) {
@@ -147,6 +150,7 @@
ConnectionResult.SERVICE_INVALID, ConnectionResult.SERVICE_MISSING,
ConnectionResult.SERVICE_MISSING_PERMISSION, ConnectionResult.SERVICE_UPDATING,
ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED, ConnectionResult.SIGN_IN_FAILED,
- ConnectionResult.SIGN_IN_REQUIRED, ConnectionResult.TIMEOUT)
+ ConnectionResult.SIGN_IN_REQUIRED, ConnectionResult.TIMEOUT
+ )
}
}
diff --git a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpublickeycredential/CreatePublicKeyCredentialControllerTestUtils.kt b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpublickeycredential/CreatePublicKeyCredentialControllerTestUtils.kt
index de8ef2b..2dbf889 100644
--- a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpublickeycredential/CreatePublicKeyCredentialControllerTestUtils.kt
+++ b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpublickeycredential/CreatePublicKeyCredentialControllerTestUtils.kt
@@ -17,6 +17,7 @@
package androidx.credentials.playservices.createkeycredential
import androidx.credentials.playservices.TestUtils
+import androidx.credentials.playservices.controllers.CreatePublicKeyCredential.PublicKeyCredentialControllerUtility
import com.google.android.gms.fido.common.Transport
import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialCreationOptions
import org.json.JSONArray
@@ -26,6 +27,8 @@
class CreatePublicKeyCredentialControllerTestUtils {
companion object {
+ const val TAG = "PasskeyTestUtils"
+
// optional and not required key 'transports' is missing in the JSONObject that composes
// up the JSONArray found at key 'excludeCredentials'
const val OPTIONAL_FIELD_MISSING_OPTIONAL_SUBFIELD = ("{\"rp\": {\"name\": " +
@@ -323,5 +326,122 @@
}
}
}
+
+ /**
+ * Helps generate a PublicKeyCredential response json format to start tests with, locally
+ * for example.
+ *
+ * Usage details as follows:
+ *
+ * val byteArrayClientDataJson = byteArrayOf(0x48, 101, 108, 108, 111)
+ * val byteArrayAuthenticatorData = byteArrayOf(0x48, 101, 108, 108, 112)
+ * val byteArraySignature = byteArrayOf(0x48, 101, 108, 108, 113)
+ * val byteArrayUserHandle = byteArrayOf(0x48, 101, 108, 108, 114)
+ * val publicKeyCredId = "id"
+ * val publicKeyCredRawId = byteArrayOf(0x48, 101, 108, 108, 115)
+ * val publicKeyCredType = "type"
+ * val authenticatorAttachment = "platform"
+ * val hasClientExtensionOutputs = true
+ * val isDiscoverableCredential = true
+ * val expectedClientExtensions = "{\"credProps\":{\"rk\":true}}"
+ *
+ * val json = PublicKeyCredentialControllerUtility.beginSignInAssertionResponse(
+ * byteArrayClientDataJson,
+ * byteArrayAuthenticatorData,
+ * byteArraySignature,
+ * byteArrayUserHandle,
+ * publicKeyCredId,
+ * publicKeyCredRawId,
+ * publicKeyCredType,
+ * authenticatorAttachment,
+ * hasClientExtensionOutputs,
+ * isDiscoverableCredential
+ * )
+ *
+ * The json can be used as necessary, even if only to generate a log with which to pull
+ * the string from (to then further use that string in other test cases).
+ */
+ fun getPublicKeyCredentialResponseGenerator(
+ clientDataJSON: ByteArray,
+ authenticatorData: ByteArray,
+ signature: ByteArray,
+ userHandle: ByteArray?,
+ publicKeyCredId: String,
+ publicKeyCredRawId: ByteArray,
+ publicKeyCredType: String,
+ authenticatorAttachment: String?,
+ hasClientExtensionResults: Boolean,
+ isDiscoverableCredential: Boolean?
+ ): JSONObject {
+ val json = JSONObject()
+ val responseJson = JSONObject()
+ responseJson.put(
+ PublicKeyCredentialControllerUtility.JSON_KEY_CLIENT_DATA,
+ PublicKeyCredentialControllerUtility.b64Encode(clientDataJSON)
+ )
+ responseJson.put(
+ PublicKeyCredentialControllerUtility.JSON_KEY_AUTH_DATA,
+ PublicKeyCredentialControllerUtility.b64Encode(authenticatorData)
+ )
+ responseJson.put(
+ PublicKeyCredentialControllerUtility.JSON_KEY_SIGNATURE,
+ PublicKeyCredentialControllerUtility.b64Encode(signature)
+ )
+ userHandle?.let {
+ responseJson.put(
+ PublicKeyCredentialControllerUtility.JSON_KEY_USER_HANDLE,
+ PublicKeyCredentialControllerUtility.b64Encode(userHandle)
+ )
+ }
+ json.put(PublicKeyCredentialControllerUtility.JSON_KEY_RESPONSE, responseJson)
+ json.put(PublicKeyCredentialControllerUtility.JSON_KEY_ID, publicKeyCredId)
+ json.put(
+ PublicKeyCredentialControllerUtility.JSON_KEY_RAW_ID,
+ PublicKeyCredentialControllerUtility.b64Encode(publicKeyCredRawId)
+ )
+ json.put(PublicKeyCredentialControllerUtility.JSON_KEY_TYPE, publicKeyCredType)
+ addOptionalAuthenticatorAttachmentAndRequiredExtensions(
+ authenticatorAttachment,
+ hasClientExtensionResults,
+ isDiscoverableCredential,
+ json
+ )
+ return json;
+ }
+
+ // This can be shared by both get and create flow response parsers, fills 'json'.
+ private fun addOptionalAuthenticatorAttachmentAndRequiredExtensions(
+ authenticatorAttachment: String?,
+ hasClientExtensionResults: Boolean,
+ isDiscoverableCredential: Boolean?,
+ json: JSONObject
+ ) {
+
+ json.putOpt(
+ PublicKeyCredentialControllerUtility.JSON_KEY_AUTH_ATTACHMENT,
+ authenticatorAttachment
+ )
+
+ val clientExtensionsJson = JSONObject()
+
+ if (hasClientExtensionResults) {
+ if (isDiscoverableCredential != null) {
+ val credPropsObject = JSONObject()
+ credPropsObject.put(
+ PublicKeyCredentialControllerUtility.JSON_KEY_RK,
+ isDiscoverableCredential
+ )
+ clientExtensionsJson.put(
+ PublicKeyCredentialControllerUtility.JSON_KEY_CRED_PROPS,
+ credPropsObject
+ )
+ }
+ }
+
+ json.put(
+ PublicKeyCredentialControllerUtility.JSON_KEY_CLIENT_EXTENSION_RESULTS,
+ clientExtensionsJson
+ )
+ }
}
}
diff --git a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpublickeycredential/PublicKeyCredentialControllerUtilityTest.kt b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpublickeycredential/PublicKeyCredentialControllerUtilityTest.kt
index 80617b9..17af5dc 100644
--- a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpublickeycredential/PublicKeyCredentialControllerUtilityTest.kt
+++ b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpublickeycredential/PublicKeyCredentialControllerUtilityTest.kt
@@ -149,226 +149,6 @@
}
@Test
- fun toAssertPasskeyResponse_authenticatorAssertionResponse_success() {
- val byteArrayClientDataJson = byteArrayOf(0x48, 101, 108, 108, 111)
- val byteArrayAuthenticatorData = byteArrayOf(0x48, 101, 108, 108, 112)
- val byteArraySignature = byteArrayOf(0x48, 101, 108, 108, 113)
- val byteArrayUserHandle = byteArrayOf(0x48, 101, 108, 108, 114)
- val json = JSONObject()
- val publicKeyCredId = "id"
- val publicKeyCredRawId = byteArrayOf(0x48, 101, 108, 108, 115)
- val publicKeyCredType = "type"
- val authenticatorAttachment = "platform"
- val hasClientExtensionOutputs = true
- val isDiscoverableCredential = true
- val expectedClientExtensions = "{\"credProps\":{\"rk\":true}}"
-
- PublicKeyCredentialControllerUtility.beginSignInAssertionResponse(
- byteArrayClientDataJson,
- byteArrayAuthenticatorData,
- byteArraySignature,
- byteArrayUserHandle,
- json,
- publicKeyCredId,
- publicKeyCredRawId,
- publicKeyCredType,
- authenticatorAttachment,
- hasClientExtensionOutputs,
- isDiscoverableCredential
- )
-
- assertThat(json.get(PublicKeyCredentialControllerUtility.JSON_KEY_ID))
- .isEqualTo(publicKeyCredId)
- assertThat(json.get(PublicKeyCredentialControllerUtility.JSON_KEY_RAW_ID))
- .isEqualTo(PublicKeyCredentialControllerUtility.b64Encode(publicKeyCredRawId))
- assertThat(json.get(PublicKeyCredentialControllerUtility.JSON_KEY_TYPE))
- .isEqualTo(publicKeyCredType)
- assertThat(json.get(PublicKeyCredentialControllerUtility.JSON_KEY_AUTH_ATTACHMENT))
- .isEqualTo(authenticatorAttachment)
- assertThat(
- json
- .getJSONObject(PublicKeyCredentialControllerUtility.JSON_KEY_CLIENT_EXTENSION_RESULTS)
- .toString()
- )
- .isEqualTo(expectedClientExtensions)
-
- // There is some embedded JSON so we should make sure we test that.
- var embeddedResponse =
- json.getJSONObject(PublicKeyCredentialControllerUtility.JSON_KEY_RESPONSE)
- assertThat(embeddedResponse.get(PublicKeyCredentialControllerUtility.JSON_KEY_CLIENT_DATA))
- .isEqualTo(PublicKeyCredentialControllerUtility.b64Encode(byteArrayClientDataJson))
- assertThat(embeddedResponse.get(PublicKeyCredentialControllerUtility.JSON_KEY_AUTH_DATA))
- .isEqualTo(PublicKeyCredentialControllerUtility.b64Encode(byteArrayAuthenticatorData))
- assertThat(embeddedResponse.get(PublicKeyCredentialControllerUtility.JSON_KEY_SIGNATURE))
- .isEqualTo(PublicKeyCredentialControllerUtility.b64Encode(byteArraySignature))
- assertThat(embeddedResponse.get(PublicKeyCredentialControllerUtility.JSON_KEY_USER_HANDLE))
- .isEqualTo(PublicKeyCredentialControllerUtility.b64Encode(byteArrayUserHandle))
-
- // ClientExtensions are another group of embedded JSON
- var clientExtensions =
- json.getJSONObject(PublicKeyCredentialControllerUtility.JSON_KEY_CLIENT_EXTENSION_RESULTS)
- assertThat(clientExtensions.get(PublicKeyCredentialControllerUtility.JSON_KEY_CRED_PROPS))
- .isNotNull()
- assertThat(
- clientExtensions
- .getJSONObject(PublicKeyCredentialControllerUtility.JSON_KEY_CRED_PROPS)
- .getBoolean(PublicKeyCredentialControllerUtility.JSON_KEY_RK)
- )
- .isTrue()
- }
-
- @Test
- fun toAssertPasskeyResponse_authenticatorAssertionResponse_noUserHandle_success() {
- val byteArrayClientDataJson = byteArrayOf(0x48, 101, 108, 108, 111)
- val byteArrayAuthenticatorData = byteArrayOf(0x48, 101, 108, 108, 112)
- val byteArraySignature = byteArrayOf(0x48, 101, 108, 108, 113)
- val json = JSONObject()
- val publicKeyCredId = "id"
- val publicKeyCredRawId = byteArrayOf(0x48, 101, 108, 108, 115)
- val publicKeyCredType = "type"
- val authenticatorAttachment = "platform"
- val hasClientExtensionOutputs = false
-
- PublicKeyCredentialControllerUtility.beginSignInAssertionResponse(
- byteArrayClientDataJson,
- byteArrayAuthenticatorData,
- byteArraySignature,
- null,
- json,
- publicKeyCredId,
- publicKeyCredRawId,
- publicKeyCredType,
- authenticatorAttachment,
- hasClientExtensionOutputs,
- null
- )
-
- assertThat(json.get(PublicKeyCredentialControllerUtility.JSON_KEY_ID))
- .isEqualTo(publicKeyCredId)
- assertThat(json.get(PublicKeyCredentialControllerUtility.JSON_KEY_RAW_ID))
- .isEqualTo(PublicKeyCredentialControllerUtility.b64Encode(publicKeyCredRawId))
- assertThat(json.get(PublicKeyCredentialControllerUtility.JSON_KEY_TYPE))
- .isEqualTo(publicKeyCredType)
- assertThat(json.get(PublicKeyCredentialControllerUtility.JSON_KEY_AUTH_ATTACHMENT))
- .isEqualTo(authenticatorAttachment)
- assertThat(
- json
- .getJSONObject(PublicKeyCredentialControllerUtility.JSON_KEY_CLIENT_EXTENSION_RESULTS)
- .toString()
- )
- .isEqualTo(JSONObject().toString())
-
- // There is some embedded JSON so we should make sure we test that.
- var embeddedResponse =
- json.getJSONObject(PublicKeyCredentialControllerUtility.JSON_KEY_RESPONSE)
- assertThat(embeddedResponse.get(PublicKeyCredentialControllerUtility.JSON_KEY_CLIENT_DATA))
- .isEqualTo(PublicKeyCredentialControllerUtility.b64Encode(byteArrayClientDataJson))
- assertThat(embeddedResponse.get(PublicKeyCredentialControllerUtility.JSON_KEY_AUTH_DATA))
- .isEqualTo(PublicKeyCredentialControllerUtility.b64Encode(byteArrayAuthenticatorData))
- assertThat(embeddedResponse.get(PublicKeyCredentialControllerUtility.JSON_KEY_SIGNATURE))
- .isEqualTo(PublicKeyCredentialControllerUtility.b64Encode(byteArraySignature))
- assertThat(embeddedResponse.has(PublicKeyCredentialControllerUtility.JSON_KEY_USER_HANDLE))
- .isFalse()
- }
-
- @Test
- fun toAssertPasskeyResponse_authenticatorAssertionResponse_noAuthenticatorAttachment_success() {
- val byteArrayClientDataJson = byteArrayOf(0x48, 101, 108, 108, 111)
- val byteArrayAuthenticatorData = byteArrayOf(0x48, 101, 108, 108, 112)
- val byteArraySignature = byteArrayOf(0x48, 101, 108, 108, 113)
- val json = JSONObject()
- val publicKeyCredId = "id"
- val publicKeyCredRawId = byteArrayOf(0x48, 101, 108, 108, 115)
- val publicKeyCredType = "type"
- val hasClientExtensionOutputs = false
-
- PublicKeyCredentialControllerUtility.beginSignInAssertionResponse(
- byteArrayClientDataJson,
- byteArrayAuthenticatorData,
- byteArraySignature,
- null,
- json,
- publicKeyCredId,
- publicKeyCredRawId,
- publicKeyCredType,
- null,
- hasClientExtensionOutputs,
- null
- )
-
- assertThat(json.get(PublicKeyCredentialControllerUtility.JSON_KEY_ID))
- .isEqualTo(publicKeyCredId)
- assertThat(json.get(PublicKeyCredentialControllerUtility.JSON_KEY_RAW_ID))
- .isEqualTo(PublicKeyCredentialControllerUtility.b64Encode(publicKeyCredRawId))
- assertThat(json.get(PublicKeyCredentialControllerUtility.JSON_KEY_TYPE))
- .isEqualTo(publicKeyCredType)
- assertThat(json.optJSONObject(PublicKeyCredentialControllerUtility.JSON_KEY_AUTH_ATTACHMENT))
- .isNull()
- assertThat(
- json
- .getJSONObject(PublicKeyCredentialControllerUtility.JSON_KEY_CLIENT_EXTENSION_RESULTS)
- .toString()
- )
- .isEqualTo(JSONObject().toString())
-
- // There is some embedded JSON so we should make sure we test that.
- var embeddedResponse =
- json.getJSONObject(PublicKeyCredentialControllerUtility.JSON_KEY_RESPONSE)
- assertThat(embeddedResponse.get(PublicKeyCredentialControllerUtility.JSON_KEY_CLIENT_DATA))
- .isEqualTo(PublicKeyCredentialControllerUtility.b64Encode(byteArrayClientDataJson))
- assertThat(embeddedResponse.get(PublicKeyCredentialControllerUtility.JSON_KEY_AUTH_DATA))
- .isEqualTo(PublicKeyCredentialControllerUtility.b64Encode(byteArrayAuthenticatorData))
- assertThat(embeddedResponse.get(PublicKeyCredentialControllerUtility.JSON_KEY_SIGNATURE))
- .isEqualTo(PublicKeyCredentialControllerUtility.b64Encode(byteArraySignature))
- assertThat(embeddedResponse.has(PublicKeyCredentialControllerUtility.JSON_KEY_USER_HANDLE))
- .isFalse()
- }
-
- @Test
- fun toCreatePasskeyResponseJson_addOptionalAuthenticatorAttachmentAndRequiredExt() {
- val json = JSONObject()
-
- PublicKeyCredentialControllerUtility.addOptionalAuthenticatorAttachmentAndRequiredExtensions(
- "attachment",
- true,
- true,
- json
- )
-
- var clientExtensionResults =
- json.getJSONObject(PublicKeyCredentialControllerUtility.JSON_KEY_CLIENT_EXTENSION_RESULTS)
- var credPropsObject =
- clientExtensionResults.getJSONObject(PublicKeyCredentialControllerUtility.JSON_KEY_CRED_PROPS)
-
- assertThat(json.get(PublicKeyCredentialControllerUtility.JSON_KEY_AUTH_ATTACHMENT))
- .isEqualTo("attachment")
- assertThat(credPropsObject.get(PublicKeyCredentialControllerUtility.JSON_KEY_RK))
- .isEqualTo(true)
- }
-
- @Test
- fun toCreatePasskeyResponseJson_addOptionalAuthenticatorAttachmentAndRequiredExt_noClientExt() {
- val json = JSONObject()
-
- PublicKeyCredentialControllerUtility.addOptionalAuthenticatorAttachmentAndRequiredExtensions(
- "attachment",
- false,
- null,
- json
- )
-
- var clientExtensionResults =
- json.getJSONObject(PublicKeyCredentialControllerUtility.JSON_KEY_CLIENT_EXTENSION_RESULTS)
-
- assertThat(json.get(PublicKeyCredentialControllerUtility.JSON_KEY_AUTH_ATTACHMENT))
- .isEqualTo("attachment")
- assertThat(
- clientExtensionResults.optJSONObject(PublicKeyCredentialControllerUtility.JSON_KEY_RK)
- )
- .isNull()
- }
-
- @Test
fun toCreatePasskeyResponseJson_addAuthenticatorAttestationResponse_success() {
val json = JSONObject()
val byteArrayClientDataJson = byteArrayOf(0x48, 101, 108, 108, 111)
@@ -417,16 +197,16 @@
)
var output = PublicKeyCredentialControllerUtility.convertJSON(json)
- assertThat(output.getUser().getId()).isNotEmpty()
- assertThat(output.getUser().getName()).isEqualTo("Name of User")
- assertThat(output.getUser().getDisplayName()).isEqualTo("Display Name of User")
- assertThat(output.getUser().getIcon()).isEqualTo("icon.png")
- assertThat(output.getChallenge()).isNotEmpty()
- assertThat(output.getRp().getId()).isNotEmpty()
- assertThat(output.getRp().getName()).isEqualTo("Name of RP")
- assertThat(output.getRp().getIcon()).isEqualTo("rpicon.png")
- assertThat(output.getParameters().get(0).getAlgorithmIdAsInteger()).isEqualTo(-7)
- assertThat(output.getParameters().get(0).getTypeAsString()).isEqualTo("public-key")
+ assertThat(output.user.id).isNotEmpty()
+ assertThat(output.user.name).isEqualTo("Name of User")
+ assertThat(output.user.displayName).isEqualTo("Display Name of User")
+ assertThat(output.user.icon).isEqualTo("icon.png")
+ assertThat(output.challenge).isNotEmpty()
+ assertThat(output.rp.id).isNotEmpty()
+ assertThat(output.rp.name).isEqualTo("Name of RP")
+ assertThat(output.rp.icon).isEqualTo("rpicon.png")
+ assertThat(output.parameters[0].algorithmIdAsInteger).isEqualTo(-7)
+ assertThat(output.parameters[0].typeAsString).isEqualTo("public-key")
}
@Test
@@ -680,9 +460,8 @@
)
var output = PublicKeyCredentialControllerUtility.convertJSON(json)
- assertThat(output.getAuthenticationExtensions()
- !!.getFidoAppIdExtension()!!.getAppId()).isEqualTo("https://www.android.com/appid1")
- assertThat(output.getAuthenticationExtensions()
- !!.getUserVerificationMethodExtension()!!.getUvm()).isTrue()
+ assertThat(output.authenticationExtensions!!.fidoAppIdExtension!!.appId)
+ .isEqualTo("https://www.android.com/appid1")
+ assertThat(output.authenticationExtensions!!.userVerificationMethodExtension!!.uvm).isTrue()
}
}
diff --git a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePublicKeyCredential/CredentialProviderCreatePublicKeyCredentialController.kt b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePublicKeyCredential/CredentialProviderCreatePublicKeyCredentialController.kt
index 1a37158..e86df83 100644
--- a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePublicKeyCredential/CredentialProviderCreatePublicKeyCredentialController.kt
+++ b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePublicKeyCredential/CredentialProviderCreatePublicKeyCredentialController.kt
@@ -186,9 +186,12 @@
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
public override fun convertResponseToCredentialManager(response: PublicKeyCredential):
CreateCredentialResponse {
- return CreatePublicKeyCredentialResponse(
- PublicKeyCredentialControllerUtility.toCreatePasskeyResponseJson(response)
- )
+ try {
+ return CreatePublicKeyCredentialResponse(response.toJson())
+ } catch (t: Throwable) {
+ throw CreateCredentialUnknownException("The PublicKeyCredential response json " +
+ "had an unexpected exception when parsing: ${t.message}")
+ }
}
private fun JSONExceptionToPKCError(exception: JSONException):
diff --git a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePublicKeyCredential/PublicKeyCredentialControllerUtility.kt b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePublicKeyCredential/PublicKeyCredentialControllerUtility.kt
index 4b38772..b38500b 100644
--- a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePublicKeyCredential/PublicKeyCredentialControllerUtility.kt
+++ b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePublicKeyCredential/PublicKeyCredentialControllerUtility.kt
@@ -24,6 +24,7 @@
import androidx.credentials.exceptions.CreateCredentialException
import androidx.credentials.exceptions.GetCredentialCancellationException
import androidx.credentials.exceptions.GetCredentialException
+import androidx.credentials.exceptions.GetCredentialUnknownException
import androidx.credentials.exceptions.domerrors.AbortError
import androidx.credentials.exceptions.domerrors.ConstraintError
import androidx.credentials.exceptions.domerrors.DataError
@@ -45,7 +46,6 @@
import com.google.android.gms.fido.fido2.api.common.AttestationConveyancePreference
import com.google.android.gms.fido.fido2.api.common.AuthenticationExtensions
import com.google.android.gms.fido.fido2.api.common.AuthenticatorAssertionResponse
-import com.google.android.gms.fido.fido2.api.common.AuthenticatorAttestationResponse
import com.google.android.gms.fido.fido2.api.common.AuthenticatorErrorResponse
import com.google.android.gms.fido.fido2.api.common.AuthenticatorResponse
import com.google.android.gms.fido.fido2.api.common.AuthenticatorSelectionCriteria
@@ -136,39 +136,6 @@
return builder.build()
}
- /** Converts the response from fido back to json so it can be passed into CredentialManager. */
- fun toCreatePasskeyResponseJson(cred: PublicKeyCredential): String {
- val json = JSONObject()
- val authenticatorResponse = cred.response
- if (authenticatorResponse is AuthenticatorAttestationResponse) {
- val transportArray = convertToProperNamingScheme(authenticatorResponse)
- addAuthenticatorAttestationResponse(
- authenticatorResponse.clientDataJSON,
- authenticatorResponse.attestationObject,
- transportArray,
- json
- )
- } else {
- Log.e(
- TAG,
- "Authenticator response expected registration response but " +
- "got: ${authenticatorResponse.javaClass.name}"
- )
- }
-
- addOptionalAuthenticatorAttachmentAndRequiredExtensions(
- cred.authenticatorAttachment,
- cred.clientExtensionResults != null,
- cred.clientExtensionResults?.credProps?.isDiscoverableCredential,
- json
- )
-
- json.put(JSON_KEY_ID, cred.id)
- json.put(JSON_KEY_RAW_ID, b64Encode(cred.rawId))
- json.put(JSON_KEY_TYPE, cred.type)
- return json.toString()
- }
-
internal fun addAuthenticatorAttestationResponse(
clientDataJSON: ByteArray,
attestationObject: ByteArray,
@@ -182,52 +149,6 @@
json.put(JSON_KEY_RESPONSE, responseJson)
}
- private fun convertToProperNamingScheme(
- authenticatorResponse: AuthenticatorAttestationResponse
- ): Array<out String> {
- val transportArray = authenticatorResponse.transports
- var ix = 0
- for (transport in transportArray) {
- if (transport == "cable") {
- transportArray[ix] = "hybrid"
- }
- ix += 1
- }
- return transportArray
- }
-
- // This can be shared by both get and create flow response parsers
- internal fun addOptionalAuthenticatorAttachmentAndRequiredExtensions(
- authenticatorAttachment: String?,
- hasClientExtensionResults: Boolean,
- isDiscoverableCredential: Boolean?,
- json: JSONObject
- ) {
-
- if (authenticatorAttachment != null) {
- json.put(JSON_KEY_AUTH_ATTACHMENT, authenticatorAttachment)
- }
-
- val clientExtensionsJson = JSONObject()
-
- if (hasClientExtensionResults) {
- try {
- if (isDiscoverableCredential != null) {
- val credPropsObject = JSONObject()
- credPropsObject.put(JSON_KEY_RK, isDiscoverableCredential)
- clientExtensionsJson.put(JSON_KEY_CRED_PROPS, credPropsObject)
- }
- } catch (t: Throwable) {
- Log.e(
- TAG,
- "ClientExtensionResults faced possible implementation " +
- "inconsistency in uvmEntries - $t"
- )
- }
- }
- json.put(JSON_KEY_CLIENT_EXTENSION_RESULTS, clientExtensionsJson)
- }
-
fun toAssertPasskeyResponse(cred: SignInCredential): String {
var json = JSONObject()
val publicKeyCred = cred.publicKeyCredential
@@ -240,61 +161,24 @@
)
}
is AuthenticatorAssertionResponse -> {
- beginSignInAssertionResponse(
- authenticatorResponse.clientDataJSON,
- authenticatorResponse.authenticatorData,
- authenticatorResponse.signature,
- authenticatorResponse.userHandle,
- json,
- publicKeyCred.id,
- publicKeyCred.rawId,
- publicKeyCred.type,
- publicKeyCred.authenticatorAttachment,
- publicKeyCred.clientExtensionResults != null,
- publicKeyCred.clientExtensionResults?.credProps?.isDiscoverableCredential
- )
+ try {
+ return publicKeyCred.toJson()
+ } catch (t: Throwable) {
+ throw GetCredentialUnknownException("The PublicKeyCredential response json had " +
+ "an unexpected exception when parsing: ${t.message}")
+ }
}
else -> {
Log.e(
TAG,
"AuthenticatorResponse expected assertion response but " +
- "got: ${authenticatorResponse.javaClass.name}"
+ "got: ${authenticatorResponse.javaClass.name}"
)
}
}
return json.toString()
}
- internal fun beginSignInAssertionResponse(
- clientDataJSON: ByteArray,
- authenticatorData: ByteArray,
- signature: ByteArray,
- userHandle: ByteArray?,
- json: JSONObject,
- publicKeyCredId: String,
- publicKeyCredRawId: ByteArray,
- publicKeyCredType: String,
- authenticatorAttachment: String?,
- hasClientExtensionResults: Boolean,
- isDiscoverableCredential: Boolean?
- ) {
- val responseJson = JSONObject()
- responseJson.put(JSON_KEY_CLIENT_DATA, b64Encode(clientDataJSON))
- responseJson.put(JSON_KEY_AUTH_DATA, b64Encode(authenticatorData))
- responseJson.put(JSON_KEY_SIGNATURE, b64Encode(signature))
- userHandle?.let { responseJson.put(JSON_KEY_USER_HANDLE, b64Encode(userHandle)) }
- json.put(JSON_KEY_RESPONSE, responseJson)
- json.put(JSON_KEY_ID, publicKeyCredId)
- json.put(JSON_KEY_RAW_ID, b64Encode(publicKeyCredRawId))
- json.put(JSON_KEY_TYPE, publicKeyCredType)
- addOptionalAuthenticatorAttachmentAndRequiredExtensions(
- authenticatorAttachment,
- hasClientExtensionResults,
- isDiscoverableCredential,
- json
- )
- }
-
/**
* Converts from the Credential Manager public key credential option to the Play Auth Module
* passkey json option.
diff --git a/development/build_log_simplifier/messages.ignore b/development/build_log_simplifier/messages.ignore
index 027bd5a..01c1c3e 100644
--- a/development/build_log_simplifier/messages.ignore
+++ b/development/build_log_simplifier/messages.ignore
@@ -436,6 +436,14 @@
WARN: Missing @param tag for parameter `previousPageKey` of function androidx\.paging\/PageKeyedDataSource\.LoadInitialCallback\/onResult\/#kotlin\.collections\.List\[TypeParam\(bounds=\[kotlin\.Any\?\]\)\]#kotlin\.Int#kotlin\.Int#TypeParam\(bounds=\[kotlin\.Any\?\]\)\?#TypeParam\(bounds=\[kotlin\.Any\?\]\)\?\/PointingToDeclaration\/
WARN: Missing @param tag for parameter `priority` of function androidx\.camera\.camera2\.interop\/CaptureRequestOptions\/retrieveOptionWithPriority\/#androidx\.camera\.core\.impl\.Config\.Option<ValueT>#androidx\.camera\.core\.impl\.Config\.OptionPriority\/PointingToDeclaration\/
WARN: Missing @param tag for parameter `priority` of function androidx\.camera\.core\/CameraXConfig\/retrieveOptionWithPriority\/#androidx\.camera\.core\.impl\.Config\.Option<ValueT>#androidx\.camera\.core\.impl\.Config\.OptionPriority\/PointingToDeclaration\/
+WARN: Missing @param tag for parameter `width` of function androidx\.camera\.testing\.impl\.fakes/FakeImageReaderProxy/newInstance/\#int\#int\#int\#int\#long/PointingToDeclaration/
+WARN: Missing @param tag for parameter `height` of function androidx\.camera\.testing\.impl\.fakes/FakeImageReaderProxy/newInstance/\#int\#int\#int\#int\#long/PointingToDeclaration/
+WARN: Missing @param tag for parameter `format` of function androidx\.camera\.testing\.impl\.fakes/FakeImageReaderProxy/newInstance/\#int\#int\#int\#int\#long/PointingToDeclaration/
+WARN: Missing @param tag for parameter `usage` of function androidx\.camera\.testing\.impl\.fakes/FakeImageReaderProxy/newInstance/\#int\#int\#int\#int\#long/PointingToDeclaration/
+WARN: Missing @param tag for parameter `priority` of function androidx\.camera\.testing\.impl\.fakes/FakeUseCaseConfig/retrieveOptionWithPriority/\#androidx\.camera\.core\.impl\.Config\.Option<ValueT>\#androidx\.camera\.core\.impl\.Config\.OptionPriority/PointingToDeclaration/
+WARN: Missing @param tag for parameter `classType` of function androidx\.camera\.testing\.impl\.mocks/MockConsumer/verifyAcceptCall/\#java\.lang\.Class<\?>\#boolean\#androidx\.camera\.testing\.impl\.mocks\.helpers\.CallTimes\#androidx\.camera\.testing\.impl\.mocks\.helpers\.ArgumentCaptor<T>/PointingToDeclaration/
+WARN: Missing @param tag for parameter `inOrder` of function androidx\.camera\.testing\.impl\.mocks/MockConsumer/verifyAcceptCall/\#java\.lang\.Class<\?>\#boolean\#androidx\.camera\.testing\.impl\.mocks\.helpers\.CallTimes\#androidx\.camera\.testing\.impl\.mocks\.helpers\.ArgumentCaptor<T>/PointingToDeclaration/
+WARN: Missing @param tag for parameter `callTimes` of function androidx\.camera\.testing\.impl\.mocks/MockConsumer/verifyAcceptCall/\#java\.lang\.Class<\?>\#boolean\#androidx\.camera\.testing\.impl\.mocks\.helpers\.CallTimes\#androidx\.camera\.testing\.impl\.mocks\.helpers\.ArgumentCaptor<T>/PointingToDeclaration/
WARN: Missing @param tag for parameter `query` of function androidx\.leanback\.app\/SearchFragment\/createArgs\/#android\.os\.Bundle#java\.lang\.String\/PointingToDeclaration\/
WARN: Missing @param tag for parameter `query` of function androidx\.leanback\.app\/SearchSupportFragment\/createArgs\/#android\.os\.Bundle#java\.lang\.String\/PointingToDeclaration\/
WARN: Missing @param tag for parameter `radians` of function androidx\.compose\.ui\.graphics\/\/rotateRad\/androidx\.compose\.ui\.graphics\.Canvas#kotlin\.Float#kotlin\.Float#kotlin\.Float\/PointingToDeclaration\/
diff --git a/development/update-verification-metadata.sh b/development/update-verification-metadata.sh
index 7023439..d0a6593e 100755
--- a/development/update-verification-metadata.sh
+++ b/development/update-verification-metadata.sh
@@ -1,13 +1,38 @@
#!/bin/bash
set -e
+# This script updates trust entries in gradle/verification-metadata.xml
+
+# Usage: $0 [--no-dry-run] [<task>]
+
+# --no-dry-run
+# Don't pass --dry-run to Gradle, so Gradle executes the corresponding tasks.
+# This is not normally necessary but in some cases can be a useful workaround.
+# When https://github.com/gradle/gradle/issues/26289 is resolved, we should reevaluate this behavior
+#
+# <task>
+# The task to ask Gradle to run. By default this is 'bOS'
+# When --no-dry-run is removed, we should reevaluate this behavior
+
+dryrun=true
+task="bOS"
+
+while [ "$1" != "" ]; do
+ arg="$1"
+ shift
+ if [ "$arg" == "--no-dry-run" ]; then
+ dryrun=false
+ continue
+ fi
+ task="$arg"
+done
+
function runGradle() {
- kmpArgs="-Pandroidx.enabled.kmp.target.platforms=+native"
- echo running ./gradlew $kmpArgs "$@"
- if ./gradlew $kmpArgs "$@"; then
- echo succeeded: ./gradlew $kmpArgs "$@"
+ echo running ./gradlew "$@"
+ if ./gradlew "$@"; then
+ echo succeeded: ./gradlew "$@"
else
- echo failed: ./gradlew $kmpArgs "$@"
+ echo failed: ./gradlew "$@"
return 1
fi
}
@@ -18,20 +43,31 @@
# regenerate metadata
# Need to run a clean build, https://github.com/gradle/gradle/issues/19228
# Resolving Configurations before task execution is expected. b/297394547
- runGradle --stacktrace --write-verification-metadata pgp,sha256 --export-keys --dry-run --clean -Pandroidx.update.signatures=true -Pandroid.dependencyResolutionAtConfigurationTime.disallow=false bOS
+ dryrunArg=""
+ if [ "$dryrun" == "true" ]; then
+ dryrunArg="--dry-run"
+ fi
+ runGradle --stacktrace --write-verification-metadata pgp,sha256 --export-keys $dryrunArg --clean -Pandroidx.update.signatures=true -Pandroid.dependencyResolutionAtConfigurationTime.disallow=false -Pandroidx.enabled.kmp.target.platforms=+native $task
# update verification metadata file
- # also remove 'version=' lines, https://github.com/gradle/gradle/issues/20192
- cat gradle/verification-metadata.dryrun.xml | sed 's/ \(trusted-key.*\)version="[^"]*"/\1/' > gradle/verification-metadata.xml
+
+ # first, make sure the resulting file is named "verification-metadata.xml"
+ if [ "$dryrun" == "true" ]; then
+ mv gradle/verification-metadata.dryrun.xml gradle/verification-metadata.xml
+ fi
+
+ # next, remove 'version=' lines https://github.com/gradle/gradle/issues/20192
+ sed -i 's/ \(trusted-key.*\)version="[^"]*"/\1/' gradle/verification-metadata.xml
# rename keyring
- mv gradle/verification-keyring-dryrun.keys gradle/verification-keyring.keys
+ mv gradle/verification-keyring-dryrun.keys gradle/verification-keyring.keys 2>/dev/null || true
# remove temporary files
rm -f gradle/verification-keyring-dryrun.gpg
- rm -f gradle/verification-metadata.dryrun.xml
+ rm -f gradle/verification-keyring.gpg
}
regenerateVerificationMetadata
echo
echo 'Done. Please check that these changes look correct (`git diff`)'
+echo "If Gradle did not make all expected updates to verification-metadata.xml, you can try '--no-dry-run'. This is slow so you may also want to specify a task. Example: $0 --dry-run exportSboms"
diff --git a/docs-public/build.gradle b/docs-public/build.gradle
index b6b1688..2954cc7 100644
--- a/docs-public/build.gradle
+++ b/docs-public/build.gradle
@@ -1,5 +1,5 @@
plugins {
- id("com.android.library")
+id("com.android.library")
id("AndroidXDocsPlugin")
}
@@ -8,15 +8,15 @@
}
dependencies {
- docs("androidx.activity:activity:1.8.0-alpha07")
- docs("androidx.activity:activity-compose:1.8.0-alpha07")
- samples("androidx.activity:activity-compose-samples:1.8.0-alpha07")
- docs("androidx.activity:activity-ktx:1.8.0-alpha07")
+ docs("androidx.activity:activity:1.8.0-beta01")
+ docs("androidx.activity:activity-compose:1.8.0-beta01")
+ samples("androidx.activity:activity-compose-samples:1.8.0-beta01")
+ docs("androidx.activity:activity-ktx:1.8.0-beta01")
// ads-identifier is deprecated
docsWithoutApiSince("androidx.ads:ads-identifier:1.0.0-alpha05")
docsWithoutApiSince("androidx.ads:ads-identifier-common:1.0.0-alpha05")
docsWithoutApiSince("androidx.ads:ads-identifier-provider:1.0.0-alpha05")
- kmpDocs("androidx.annotation:annotation:1.7.0-rc01")
+ kmpDocs("androidx.annotation:annotation:1.7.0")
docs("androidx.annotation:annotation-experimental:1.4.0-alpha01")
docs("androidx.appcompat:appcompat:1.7.0-alpha03")
docs("androidx.appcompat:appcompat-resources:1.7.0-alpha03")
@@ -58,57 +58,57 @@
docs("androidx.car.app:app-projected:1.4.0-beta01")
docs("androidx.car.app:app-testing:1.4.0-beta01")
docs("androidx.cardview:cardview:1.0.0")
- kmpDocs("androidx.collection:collection:1.3.0-beta01")
- docs("androidx.collection:collection-ktx:1.3.0-beta01")
- kmpDocs("androidx.compose.animation:animation:1.6.0-alpha04")
- kmpDocs("androidx.compose.animation:animation-core:1.6.0-alpha04")
- kmpDocs("androidx.compose.animation:animation-graphics:1.6.0-alpha04")
- samples("androidx.compose.animation:animation-samples:1.6.0-alpha04")
- samples("androidx.compose.animation:animation-core-samples:1.6.0-alpha04")
- samples("androidx.compose.animation:animation-graphics-samples:1.6.0-alpha04")
- kmpDocs("androidx.compose.foundation:foundation:1.6.0-alpha04")
- kmpDocs("androidx.compose.foundation:foundation-layout:1.6.0-alpha04")
- samples("androidx.compose.foundation:foundation-layout-samples:1.6.0-alpha04")
- samples("androidx.compose.foundation:foundation-samples:1.6.0-alpha04")
- kmpDocs("androidx.compose.material3:material3:1.2.0-alpha06")
- samples("androidx.compose.material3:material3-samples:1.2.0-alpha06")
- kmpDocs("androidx.compose.material3:material3-window-size-class:1.2.0-alpha06")
- samples("androidx.compose.material3:material3-window-size-class-samples:1.2.0-alpha06")
- kmpDocs("androidx.compose.material:material:1.6.0-alpha04")
- kmpDocs("androidx.compose.material:material-icons-core:1.6.0-alpha04")
- samples("androidx.compose.material:material-icons-core-samples:1.6.0-alpha04")
- kmpDocs("androidx.compose.material:material-ripple:1.6.0-alpha04")
- samples("androidx.compose.material:material-samples:1.6.0-alpha04")
- kmpDocs("androidx.compose.runtime:runtime:1.6.0-alpha04")
- docs("androidx.compose.runtime:runtime-livedata:1.6.0-alpha04")
- samples("androidx.compose.runtime:runtime-livedata-samples:1.6.0-alpha04")
- docs("androidx.compose.runtime:runtime-rxjava2:1.6.0-alpha04")
- samples("androidx.compose.runtime:runtime-rxjava2-samples:1.6.0-alpha04")
- docs("androidx.compose.runtime:runtime-rxjava3:1.6.0-alpha04")
- samples("androidx.compose.runtime:runtime-rxjava3-samples:1.6.0-alpha04")
- kmpDocs("androidx.compose.runtime:runtime-saveable:1.6.0-alpha04")
- samples("androidx.compose.runtime:runtime-saveable-samples:1.6.0-alpha04")
- samples("androidx.compose.runtime:runtime-samples:1.6.0-alpha04")
- docs("androidx.compose.runtime:runtime-tracing:1.0.0-alpha04")
- kmpDocs("androidx.compose.ui:ui:1.6.0-alpha04")
- kmpDocs("androidx.compose.ui:ui-geometry:1.6.0-alpha04")
- kmpDocs("androidx.compose.ui:ui-graphics:1.6.0-alpha04")
- samples("androidx.compose.ui:ui-graphics-samples:1.6.0-alpha04")
- kmpDocs("androidx.compose.ui:ui-test:1.6.0-alpha04")
- kmpDocs("androidx.compose.ui:ui-test-junit4:1.6.0-alpha04")
- samples("androidx.compose.ui:ui-test-samples:1.6.0-alpha04")
- kmpDocs("androidx.compose.ui:ui-text:1.6.0-alpha04")
- docs("androidx.compose.ui:ui-text-google-fonts:1.6.0-alpha04")
- samples("androidx.compose.ui:ui-text-samples:1.6.0-alpha04")
- kmpDocs("androidx.compose.ui:ui-tooling:1.6.0-alpha04")
- kmpDocs("androidx.compose.ui:ui-tooling-data:1.6.0-alpha04")
- kmpDocs("androidx.compose.ui:ui-tooling-preview:1.6.0-alpha04")
- kmpDocs("androidx.compose.ui:ui-unit:1.6.0-alpha04")
- samples("androidx.compose.ui:ui-unit-samples:1.6.0-alpha04")
- kmpDocs("androidx.compose.ui:ui-util:1.6.0-alpha04")
- docs("androidx.compose.ui:ui-viewbinding:1.6.0-alpha04")
- samples("androidx.compose.ui:ui-viewbinding-samples:1.6.0-alpha04")
- samples("androidx.compose.ui:ui-samples:1.6.0-alpha04")
+ kmpDocs("androidx.collection:collection:1.3.0-rc01")
+ docs("androidx.collection:collection-ktx:1.3.0-rc01")
+ kmpDocs("androidx.compose.animation:animation:1.6.0-alpha05")
+ kmpDocs("androidx.compose.animation:animation-core:1.6.0-alpha05")
+ kmpDocs("androidx.compose.animation:animation-graphics:1.6.0-alpha05")
+ samples("androidx.compose.animation:animation-samples:1.6.0-alpha05")
+ samples("androidx.compose.animation:animation-core-samples:1.6.0-alpha05")
+ samples("androidx.compose.animation:animation-graphics-samples:1.6.0-alpha05")
+ kmpDocs("androidx.compose.foundation:foundation:1.6.0-alpha05")
+ kmpDocs("androidx.compose.foundation:foundation-layout:1.6.0-alpha05")
+ samples("androidx.compose.foundation:foundation-layout-samples:1.6.0-alpha05")
+ samples("androidx.compose.foundation:foundation-samples:1.6.0-alpha05")
+ kmpDocs("androidx.compose.material3:material3:1.2.0-alpha07")
+ samples("androidx.compose.material3:material3-samples:1.2.0-alpha07")
+ kmpDocs("androidx.compose.material3:material3-window-size-class:1.2.0-alpha07")
+ samples("androidx.compose.material3:material3-window-size-class-samples:1.2.0-alpha07")
+ kmpDocs("androidx.compose.material:material:1.6.0-alpha05")
+ kmpDocs("androidx.compose.material:material-icons-core:1.6.0-alpha05")
+ samples("androidx.compose.material:material-icons-core-samples:1.6.0-alpha05")
+ kmpDocs("androidx.compose.material:material-ripple:1.6.0-alpha05")
+ samples("androidx.compose.material:material-samples:1.6.0-alpha05")
+ kmpDocs("androidx.compose.runtime:runtime:1.6.0-alpha05")
+ docs("androidx.compose.runtime:runtime-livedata:1.6.0-alpha05")
+ samples("androidx.compose.runtime:runtime-livedata-samples:1.6.0-alpha05")
+ docs("androidx.compose.runtime:runtime-rxjava2:1.6.0-alpha05")
+ samples("androidx.compose.runtime:runtime-rxjava2-samples:1.6.0-alpha05")
+ docs("androidx.compose.runtime:runtime-rxjava3:1.6.0-alpha05")
+ samples("androidx.compose.runtime:runtime-rxjava3-samples:1.6.0-alpha05")
+ kmpDocs("androidx.compose.runtime:runtime-saveable:1.6.0-alpha05")
+ samples("androidx.compose.runtime:runtime-saveable-samples:1.6.0-alpha05")
+ samples("androidx.compose.runtime:runtime-samples:1.6.0-alpha05")
+ docs("androidx.compose.runtime:runtime-tracing:1.0.0-alpha05")
+ kmpDocs("androidx.compose.ui:ui:1.6.0-alpha05")
+ kmpDocs("androidx.compose.ui:ui-geometry:1.6.0-alpha05")
+ kmpDocs("androidx.compose.ui:ui-graphics:1.6.0-alpha05")
+ samples("androidx.compose.ui:ui-graphics-samples:1.6.0-alpha05")
+ kmpDocs("androidx.compose.ui:ui-test:1.6.0-alpha05")
+ kmpDocs("androidx.compose.ui:ui-test-junit4:1.6.0-alpha05")
+ samples("androidx.compose.ui:ui-test-samples:1.6.0-alpha05")
+ kmpDocs("androidx.compose.ui:ui-text:1.6.0-alpha05")
+ docs("androidx.compose.ui:ui-text-google-fonts:1.6.0-alpha05")
+ samples("androidx.compose.ui:ui-text-samples:1.6.0-alpha05")
+ kmpDocs("androidx.compose.ui:ui-tooling:1.6.0-alpha05")
+ kmpDocs("androidx.compose.ui:ui-tooling-data:1.6.0-alpha05")
+ kmpDocs("androidx.compose.ui:ui-tooling-preview:1.6.0-alpha05")
+ kmpDocs("androidx.compose.ui:ui-unit:1.6.0-alpha05")
+ samples("androidx.compose.ui:ui-unit-samples:1.6.0-alpha05")
+ kmpDocs("androidx.compose.ui:ui-util:1.6.0-alpha05")
+ docs("androidx.compose.ui:ui-viewbinding:1.6.0-alpha05")
+ samples("androidx.compose.ui:ui-viewbinding-samples:1.6.0-alpha05")
+ samples("androidx.compose.ui:ui-samples:1.6.0-alpha05")
docs("androidx.concurrent:concurrent-futures:1.2.0-alpha02")
docs("androidx.concurrent:concurrent-futures-ktx:1.2.0-alpha02")
docs("androidx.constraintlayout:constraintlayout:2.2.0-alpha12")
@@ -124,13 +124,13 @@
docs("androidx.core:core-i18n:1.0.0-alpha01")
docs("androidx.core:core-ktx:1.12.0-rc01")
docs("androidx.core:core-location-altitude:1.0.0-alpha01")
- docs("androidx.core:core-performance:1.0.0-alpha03")
- docs("androidx.core:core-performance-play-services:1.0.0-alpha03")
- samples("androidx.core:core-performance-samples:1.0.0-alpha03")
- docs("androidx.core:core-performance-testing:1.0.0-alpha03")
+ docs("androidx.core:core-performance:1.0.0-beta01")
+ docs("androidx.core:core-performance-play-services:1.0.0-beta01")
+ samples("androidx.core:core-performance-samples:1.0.0-beta01")
+ docs("androidx.core:core-performance-testing:1.0.0-beta01")
docs("androidx.core:core-remoteviews:1.0.0-rc01")
docs("androidx.core:core-role:1.2.0-alpha01")
- docs("androidx.core:core-splashscreen:1.1.0-alpha01")
+ docs("androidx.core:core-splashscreen:1.1.0-alpha02")
docs("androidx.core:core-telecom:1.0.0-alpha01")
docs("androidx.core:core-testing:1.12.0-rc01")
docs("androidx.core.uwb:uwb:1.0.0-alpha07")
@@ -142,15 +142,15 @@
docs("androidx.customview:customview:1.2.0-alpha02")
// TODO(b/294531403): Turn on apiSince for customview-poolingcontainer when it releases as alpha
docsWithoutApiSince("androidx.customview:customview-poolingcontainer:1.0.0-rc01")
- kmpDocs("androidx.datastore:datastore:1.1.0-alpha04")
- kmpDocs("androidx.datastore:datastore-core:1.1.0-alpha04")
- kmpDocs("androidx.datastore:datastore-core-okio:1.1.0-alpha04")
- kmpDocs("androidx.datastore:datastore-preferences:1.1.0-alpha04")
- kmpDocs("androidx.datastore:datastore-preferences-core:1.1.0-alpha04")
- docs("androidx.datastore:datastore-preferences-rxjava2:1.1.0-alpha04")
- docs("androidx.datastore:datastore-preferences-rxjava3:1.1.0-alpha04")
- docs("androidx.datastore:datastore-rxjava2:1.1.0-alpha04")
- docs("androidx.datastore:datastore-rxjava3:1.1.0-alpha04")
+ kmpDocs("androidx.datastore:datastore:1.1.0-alpha05")
+ kmpDocs("androidx.datastore:datastore-core:1.1.0-alpha05")
+ kmpDocs("androidx.datastore:datastore-core-okio:1.1.0-alpha05")
+ kmpDocs("androidx.datastore:datastore-preferences:1.1.0-alpha05")
+ kmpDocs("androidx.datastore:datastore-preferences-core:1.1.0-alpha05")
+ docs("androidx.datastore:datastore-preferences-rxjava2:1.1.0-alpha05")
+ docs("androidx.datastore:datastore-preferences-rxjava3:1.1.0-alpha05")
+ docs("androidx.datastore:datastore-rxjava2:1.1.0-alpha05")
+ docs("androidx.datastore:datastore-rxjava3:1.1.0-alpha05")
docs("androidx.documentfile:documentfile:1.1.0-alpha01")
docs("androidx.draganddrop:draganddrop:1.0.0")
docs("androidx.drawerlayout:drawerlayout:1.2.0")
@@ -167,23 +167,24 @@
docs("androidx.enterprise:enterprise-feedback:1.1.0")
docs("androidx.enterprise:enterprise-feedback-testing:1.1.0")
docs("androidx.exifinterface:exifinterface:1.3.6")
- docs("androidx.fragment:fragment:1.7.0-alpha03")
- docs("androidx.fragment:fragment-ktx:1.7.0-alpha03")
- docs("androidx.fragment:fragment-testing:1.7.0-alpha03")
- docs("androidx.glance:glance:1.0.0-rc01")
- docs("androidx.glance:glance-appwidget:1.0.0-rc01")
- samples("androidx.glance:glance-appwidget-samples:1.0.0-rc01")
+ docs("androidx.fragment:fragment:1.7.0-alpha04")
+ docs("androidx.fragment:fragment-ktx:1.7.0-alpha04")
+ docs("androidx.fragment:fragment-testing:1.7.0-alpha04")
+ docs("androidx.glance:glance:1.0.0")
+ docs("androidx.glance:glance-appwidget:1.0.0")
+ samples("androidx.glance:glance-appwidget-samples:1.0.0")
docs("androidx.glance:glance-appwidget-preview:1.0.0-alpha06")
- docs("androidx.glance:glance-material:1.0.0-rc01")
- docs("androidx.glance:glance-material3:1.0.0-rc01")
+ docs("androidx.glance:glance-material:1.0.0")
+ docs("androidx.glance:glance-material3:1.0.0")
docs("androidx.glance:glance-preview:1.0.0-alpha06")
docs("androidx.glance:glance-template:1.0.0-alpha06")
docs("androidx.glance:glance-wear-tiles:1.0.0-alpha06")
docs("androidx.glance:glance-wear-tiles-preview:1.0.0-alpha06")
- docs("androidx.graphics:graphics-core:1.0.0-alpha04")
+ docs("androidx.graphics:graphics-core:1.0.0-alpha05")
+ samples("androidx.graphics:graphics-core-samples:1.0.0-alpha05")
docs("androidx.gridlayout:gridlayout:1.1.0-beta01")
- docs("androidx.health.connect:connect-client:1.1.0-alpha03")
- samples("androidx.health.connect:connect-client-samples:1.1.0-alpha03")
+ docs("androidx.health.connect:connect-client:1.1.0-alpha04")
+ samples("androidx.health.connect:connect-client-samples:1.1.0-alpha04")
docs("androidx.health:health-services-client:1.1.0-alpha01")
docs("androidx.heifwriter:heifwriter:1.1.0-alpha02")
docs("androidx.hilt:hilt-common:1.1.0-alpha01")
@@ -195,32 +196,32 @@
docs("androidx.input:input-motionprediction:1.0.0-beta02")
docs("androidx.interpolator:interpolator:1.0.0")
docs("androidx.javascriptengine:javascriptengine:1.0.0-alpha05")
- docs("androidx.leanback:leanback:1.2.0-alpha02")
- docs("androidx.leanback:leanback-grid:1.0.0-alpha01")
- docs("androidx.leanback:leanback-paging:1.1.0-alpha09")
- docs("androidx.leanback:leanback-preference:1.2.0-alpha02")
+ docs("androidx.leanback:leanback:1.2.0-alpha03")
+ docs("androidx.leanback:leanback-grid:1.0.0-alpha02")
+ docs("androidx.leanback:leanback-paging:1.1.0-alpha10")
+ docs("androidx.leanback:leanback-preference:1.2.0-alpha03")
docs("androidx.leanback:leanback-tab:1.1.0-beta01")
- docs("androidx.lifecycle:lifecycle-common:2.7.0-alpha01")
- docs("androidx.lifecycle:lifecycle-common-java8:2.7.0-alpha01")
+ docs("androidx.lifecycle:lifecycle-common:2.7.0-alpha02")
+ docs("androidx.lifecycle:lifecycle-common-java8:2.7.0-alpha02")
docs("androidx.lifecycle:lifecycle-extensions:2.2.0")
- docs("androidx.lifecycle:lifecycle-livedata:2.7.0-alpha01")
- docs("androidx.lifecycle:lifecycle-livedata-core:2.7.0-alpha01")
- docs("androidx.lifecycle:lifecycle-livedata-core-ktx:2.7.0-alpha01")
- docs("androidx.lifecycle:lifecycle-livedata-ktx:2.7.0-alpha01")
- docs("androidx.lifecycle:lifecycle-process:2.7.0-alpha01")
- docs("androidx.lifecycle:lifecycle-reactivestreams:2.7.0-alpha01")
- docs("androidx.lifecycle:lifecycle-reactivestreams-ktx:2.7.0-alpha01")
- docs("androidx.lifecycle:lifecycle-runtime:2.7.0-alpha01")
- docs("androidx.lifecycle:lifecycle-runtime-compose:2.7.0-alpha01")
- samples("androidx.lifecycle:lifecycle-runtime-compose-samples:2.7.0-alpha01")
- docs("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0-alpha01")
- docs("androidx.lifecycle:lifecycle-runtime-testing:2.7.0-alpha01")
- docs("androidx.lifecycle:lifecycle-service:2.7.0-alpha01")
- docs("androidx.lifecycle:lifecycle-viewmodel:2.7.0-alpha01")
- docs("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0-alpha01")
- samples("androidx.lifecycle:lifecycle-viewmodel-compose-samples:2.7.0-alpha01")
- docs("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0-alpha01")
- docs("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.7.0-alpha01")
+ docs("androidx.lifecycle:lifecycle-livedata:2.7.0-alpha02")
+ docs("androidx.lifecycle:lifecycle-livedata-core:2.7.0-alpha02")
+ docs("androidx.lifecycle:lifecycle-livedata-core-ktx:2.7.0-alpha02")
+ docs("androidx.lifecycle:lifecycle-livedata-ktx:2.7.0-alpha02")
+ docs("androidx.lifecycle:lifecycle-process:2.7.0-alpha02")
+ docs("androidx.lifecycle:lifecycle-reactivestreams:2.7.0-alpha02")
+ docs("androidx.lifecycle:lifecycle-reactivestreams-ktx:2.7.0-alpha02")
+ docs("androidx.lifecycle:lifecycle-runtime:2.7.0-alpha02")
+ docs("androidx.lifecycle:lifecycle-runtime-compose:2.7.0-alpha02")
+ samples("androidx.lifecycle:lifecycle-runtime-compose-samples:2.7.0-alpha02")
+ docs("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0-alpha02")
+ docs("androidx.lifecycle:lifecycle-runtime-testing:2.7.0-alpha02")
+ docs("androidx.lifecycle:lifecycle-service:2.7.0-alpha02")
+ docs("androidx.lifecycle:lifecycle-viewmodel:2.7.0-alpha02")
+ docs("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0-alpha02")
+ samples("androidx.lifecycle:lifecycle-viewmodel-compose-samples:2.7.0-alpha02")
+ docs("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0-alpha02")
+ docs("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.7.0-alpha02")
docs("androidx.loader:loader:1.1.0")
// localbroadcastmanager is deprecated
docsWithoutApiSince("androidx.localbroadcastmanager:localbroadcastmanager:1.1.0")
@@ -258,31 +259,31 @@
docs("androidx.mediarouter:mediarouter:1.6.0-rc01")
docs("androidx.mediarouter:mediarouter-testing:1.6.0-rc01")
docs("androidx.metrics:metrics-performance:1.0.0-alpha04")
- docs("androidx.navigation:navigation-common:2.7.1")
- docs("androidx.navigation:navigation-common-ktx:2.7.1")
- docs("androidx.navigation:navigation-compose:2.7.1")
- samples("androidx.navigation:navigation-compose-samples:2.7.1")
- docs("androidx.navigation:navigation-dynamic-features-fragment:2.7.1")
- docs("androidx.navigation:navigation-dynamic-features-runtime:2.7.1")
- docs("androidx.navigation:navigation-fragment:2.7.1")
- docs("androidx.navigation:navigation-fragment-ktx:2.7.1")
- docs("androidx.navigation:navigation-runtime:2.7.1")
- docs("androidx.navigation:navigation-runtime-ktx:2.7.1")
- docs("androidx.navigation:navigation-testing:2.7.1")
- docs("androidx.navigation:navigation-ui:2.7.1")
- docs("androidx.navigation:navigation-ui-ktx:2.7.1")
- docs("androidx.paging:paging-common:3.2.0")
- docs("androidx.paging:paging-common-ktx:3.2.0")
- docs("androidx.paging:paging-compose:3.2.0")
- samples("androidx.paging:paging-compose-samples:3.2.0")
- docs("androidx.paging:paging-guava:3.2.0")
- docs("androidx.paging:paging-runtime:3.2.0")
- docs("androidx.paging:paging-runtime-ktx:3.2.0")
- docs("androidx.paging:paging-rxjava2:3.2.0")
- docs("androidx.paging:paging-rxjava2-ktx:3.2.0")
- docs("androidx.paging:paging-rxjava3:3.2.0")
- samples("androidx.paging:paging-samples:3.2.0")
- docs("androidx.paging:paging-testing:3.2.0")
+ docs("androidx.navigation:navigation-common:2.7.2")
+ docs("androidx.navigation:navigation-common-ktx:2.7.2")
+ docs("androidx.navigation:navigation-compose:2.7.2")
+ samples("androidx.navigation:navigation-compose-samples:2.7.2")
+ docs("androidx.navigation:navigation-dynamic-features-fragment:2.7.2")
+ docs("androidx.navigation:navigation-dynamic-features-runtime:2.7.2")
+ docs("androidx.navigation:navigation-fragment:2.7.2")
+ docs("androidx.navigation:navigation-fragment-ktx:2.7.2")
+ docs("androidx.navigation:navigation-runtime:2.7.2")
+ docs("androidx.navigation:navigation-runtime-ktx:2.7.2")
+ docs("androidx.navigation:navigation-testing:2.7.2")
+ docs("androidx.navigation:navigation-ui:2.7.2")
+ docs("androidx.navigation:navigation-ui-ktx:2.7.2")
+ docsWithoutApiSince("androidx.paging:paging-common:3.2.1")
+ docsWithoutApiSince("androidx.paging:paging-common-ktx:3.2.1")
+ docsWithoutApiSince("androidx.paging:paging-compose:3.2.1")
+ samples("androidx.paging:paging-compose-samples:3.2.1")
+ docsWithoutApiSince("androidx.paging:paging-guava:3.2.1")
+ docsWithoutApiSince("androidx.paging:paging-runtime:3.2.1")
+ docsWithoutApiSince("androidx.paging:paging-runtime-ktx:3.2.1")
+ docsWithoutApiSince("androidx.paging:paging-rxjava2:3.2.1")
+ docsWithoutApiSince("androidx.paging:paging-rxjava2-ktx:3.2.1")
+ docsWithoutApiSince("androidx.paging:paging-rxjava3:3.2.1")
+ samples("androidx.paging:paging-samples:3.2.1")
+ docsWithoutApiSince("androidx.paging:paging-testing:3.2.1")
docs("androidx.palette:palette:1.0.0")
docs("androidx.palette:palette-ktx:1.0.0")
docs("androidx.percentlayout:percentlayout:1.0.1")
@@ -293,7 +294,7 @@
docs("androidx.privacysandbox.ads:ads-adservices-java:1.1.0-beta01")
docs("androidx.privacysandbox.sdkruntime:sdkruntime-client:1.0.0-alpha08")
docs("androidx.privacysandbox.sdkruntime:sdkruntime-core:1.0.0-alpha08")
- docs("androidx.privacysandbox.tools:tools:1.0.0-alpha05")
+ docs("androidx.privacysandbox.tools:tools:1.0.0-alpha06")
docs("androidx.privacysandbox.ui:ui-client:1.0.0-alpha05")
docs("androidx.privacysandbox.ui:ui-core:1.0.0-alpha05")
docs("androidx.privacysandbox.ui:ui-provider:1.0.0-alpha05")
@@ -362,11 +363,11 @@
// TODO(243405142) clean-up
docsWithoutApiSince("androidx.tracing:tracing-perfetto-common:1.0.0-alpha16")
docs("androidx.tracing:tracing-perfetto-handshake:1.0.0-beta03")
- docs("androidx.transition:transition:1.5.0-alpha01")
- docs("androidx.transition:transition-ktx:1.5.0-alpha01")
- docs("androidx.tv:tv-foundation:1.0.0-alpha08")
- docs("androidx.tv:tv-material:1.0.0-alpha08")
- samples("androidx.tv:tv-samples:1.0.0-alpha08")
+ docs("androidx.transition:transition:1.5.0-alpha02")
+ docs("androidx.transition:transition-ktx:1.5.0-alpha02")
+ docs("androidx.tv:tv-foundation:1.0.0-alpha09")
+ docs("androidx.tv:tv-material:1.0.0-alpha09")
+ samples("androidx.tv:tv-samples:1.0.0-alpha09")
docs("androidx.tvprovider:tvprovider:1.1.0-alpha01")
docs("androidx.vectordrawable:vectordrawable:1.2.0-beta01")
docs("androidx.vectordrawable:vectordrawable-animated:1.2.0-alpha01")
@@ -374,16 +375,16 @@
docs("androidx.versionedparcelable:versionedparcelable:1.1.1")
docs("androidx.viewpager2:viewpager2:1.1.0-beta02")
docs("androidx.viewpager:viewpager:1.1.0-alpha01")
- docs("androidx.wear.compose:compose-foundation:1.3.0-alpha04")
- samples("androidx.wear.compose:compose-foundation-samples:1.3.0-alpha04")
- docs("androidx.wear.compose:compose-material:1.3.0-alpha04")
- docs("androidx.wear.compose:compose-material-core:1.3.0-alpha04")
- samples("androidx.wear.compose:compose-material-samples:1.3.0-alpha04")
- docs("androidx.wear.compose:compose-material3:1.0.0-alpha10")
- samples("androidx.wear.compose:compose-material3-samples:1.3.0-alpha04")
- docs("androidx.wear.compose:compose-navigation:1.3.0-alpha04")
- samples("androidx.wear.compose:compose-navigation-samples:1.3.0-alpha04")
- docs("androidx.wear.compose:compose-ui-tooling:1.3.0-alpha04")
+ docs("androidx.wear.compose:compose-foundation:1.3.0-alpha05")
+ samples("androidx.wear.compose:compose-foundation-samples:1.3.0-alpha05")
+ docs("androidx.wear.compose:compose-material:1.3.0-alpha05")
+ docs("androidx.wear.compose:compose-material-core:1.3.0-alpha05")
+ samples("androidx.wear.compose:compose-material-samples:1.3.0-alpha05")
+ docs("androidx.wear.compose:compose-material3:1.0.0-alpha11")
+ samples("androidx.wear.compose:compose-material3-samples:1.3.0-alpha05")
+ docs("androidx.wear.compose:compose-navigation:1.3.0-alpha05")
+ samples("androidx.wear.compose:compose-navigation-samples:1.3.0-alpha05")
+ docs("androidx.wear.compose:compose-ui-tooling:1.3.0-alpha05")
docs("androidx.wear.protolayout:protolayout:1.0.0")
docs("androidx.wear.protolayout:protolayout-expression:1.0.0")
docs("androidx.wear.protolayout:protolayout-expression-pipeline:1.0.0")
@@ -394,22 +395,22 @@
docs("androidx.wear.tiles:tiles-renderer:1.2.0")
docs("androidx.wear.tiles:tiles-testing:1.2.0")
docs("androidx.wear.tiles:tiles-tooling:1.2.0-alpha07")
- docs("androidx.wear.watchface:watchface:1.2.0-beta01")
- docs("androidx.wear.watchface:watchface-client:1.2.0-beta01")
- docs("androidx.wear.watchface:watchface-client-guava:1.2.0-beta01")
- docs("androidx.wear.watchface:watchface-complications:1.2.0-beta01")
- docs("androidx.wear.watchface:watchface-complications-data:1.2.0-beta01")
- docs("androidx.wear.watchface:watchface-complications-data-source:1.2.0-beta01")
- docs("androidx.wear.watchface:watchface-complications-data-source-ktx:1.2.0-beta01")
- samples("androidx.wear.watchface:watchface-complications-permission-dialogs-sample:1.2.0-beta01")
- docs("androidx.wear.watchface:watchface-complications-rendering:1.2.0-beta01")
- docs("androidx.wear.watchface:watchface-data:1.2.0-beta01")
- docs("androidx.wear.watchface:watchface-editor:1.2.0-beta01")
- docs("androidx.wear.watchface:watchface-editor-guava:1.2.0-beta01")
- samples("androidx.wear.watchface:watchface-editor-samples:1.2.0-beta01")
- docs("androidx.wear.watchface:watchface-guava:1.2.0-beta01")
- samples("androidx.wear.watchface:watchface-samples:1.2.0-beta01")
- docs("androidx.wear.watchface:watchface-style:1.2.0-beta01")
+ docs("androidx.wear.watchface:watchface:1.2.0-beta02")
+ docs("androidx.wear.watchface:watchface-client:1.2.0-beta02")
+ docs("androidx.wear.watchface:watchface-client-guava:1.2.0-beta02")
+ docs("androidx.wear.watchface:watchface-complications:1.2.0-beta02")
+ docs("androidx.wear.watchface:watchface-complications-data:1.2.0-beta02")
+ docs("androidx.wear.watchface:watchface-complications-data-source:1.2.0-beta02")
+ docs("androidx.wear.watchface:watchface-complications-data-source-ktx:1.2.0-beta02")
+ samples("androidx.wear.watchface:watchface-complications-permission-dialogs-sample:1.2.0-beta02")
+ docs("androidx.wear.watchface:watchface-complications-rendering:1.2.0-beta02")
+ docs("androidx.wear.watchface:watchface-data:1.2.0-beta02")
+ docs("androidx.wear.watchface:watchface-editor:1.2.0-beta02")
+ docs("androidx.wear.watchface:watchface-editor-guava:1.2.0-beta02")
+ samples("androidx.wear.watchface:watchface-editor-samples:1.2.0-beta02")
+ docs("androidx.wear.watchface:watchface-guava:1.2.0-beta02")
+ samples("androidx.wear.watchface:watchface-samples:1.2.0-beta02")
+ docs("androidx.wear.watchface:watchface-style:1.2.0-beta02")
// TODO(b/294531403): Turn on apiSince for wear when it releases as alpha
docsWithoutApiSince("androidx.wear:wear:1.3.0-rc01")
stubs(fileTree(dir: "../wear/wear_stubs/", include: ["com.google.android.wearable-stubs.jar"]))
@@ -420,7 +421,7 @@
docs("androidx.wear:wear-phone-interactions:1.1.0-alpha03")
docs("androidx.wear:wear-remote-interactions:1.1.0-alpha01")
docs("androidx.wear:wear-tooling-preview:1.0.0-alpha01")
- docs("androidx.webkit:webkit:1.8.0-rc01")
+ docs("androidx.webkit:webkit:1.8.0")
docs("androidx.window.extensions.core:core:1.0.0")
docs("androidx.window:window:1.2.0-beta01")
stubs(fileTree(dir: "../window/stubs/", include: ["window-sidecar-release-0.1.0-alpha01.aar"]))
@@ -431,12 +432,12 @@
docs("androidx.window:window-rxjava3:1.2.0-beta01")
samples("androidx.window:window-samples:1.2.0-beta01")
docs("androidx.window:window-testing:1.2.0-beta01")
- docs("androidx.work:work-gcm:2.9.0-alpha02")
- docs("androidx.work:work-multiprocess:2.9.0-alpha02")
- docs("androidx.work:work-runtime:2.9.0-alpha02")
- docs("androidx.work:work-runtime-ktx:2.9.0-alpha02")
- docs("androidx.work:work-rxjava2:2.9.0-alpha02")
- docs("androidx.work:work-rxjava3:2.9.0-alpha02")
- docs("androidx.work:work-testing:2.9.0-alpha02")
+ docs("androidx.work:work-gcm:2.9.0-beta01")
+ docs("androidx.work:work-multiprocess:2.9.0-beta01")
+ docs("androidx.work:work-runtime:2.9.0-beta01")
+ docs("androidx.work:work-runtime-ktx:2.9.0-beta01")
+ docs("androidx.work:work-rxjava2:2.9.0-beta01")
+ docs("androidx.work:work-rxjava3:2.9.0-beta01")
+ docs("androidx.work:work-testing:2.9.0-beta01")
}
diff --git a/docs-tip-of-tree/build.gradle b/docs-tip-of-tree/build.gradle
index d65c0f9..2af5022 100644
--- a/docs-tip-of-tree/build.gradle
+++ b/docs-tip-of-tree/build.gradle
@@ -63,6 +63,7 @@
docs(project(":camera:camera-lifecycle"))
docs(project(":camera:camera-mlkit-vision"))
// camera-previewview is not hosted in androidx
+ docsWithoutApiSince(project(":camera:camera-testing"))
docs(project(":camera:camera-video"))
docs(project(":camera:camera-view"))
docs(project(":camera:camera-viewfinder"))
diff --git a/docs/api_guidelines/dependencies.md b/docs/api_guidelines/dependencies.md
index 011fab9..5a43db3 100644
--- a/docs/api_guidelines/dependencies.md
+++ b/docs/api_guidelines/dependencies.md
@@ -107,9 +107,16 @@
image and are made available to developers through the `<uses-library>` manifest
tag.
-Examples include Wear OS extensions (`com.google.android.wearable`), Camera OEM
-extensions (`androidx.camera.extensions.impl`), and Window OEM extensions
-(`androix.window.extensions`).
+Examples include Camera OEM extensions (`androidx.camera.extensions.impl`) and
+Window OEM extensions (`androidx.window.extensions`).
+
+Extension libraries may be defined in AndroidX library projects (see
+`androidx.window.extensions`) or externally, ex. in AOSP alongside the platform.
+In either case, we recommend that libraries use extensions as pinned, rather
+than project-type, dependencies to facilitate versioning across repositories.
+
+*Do not* ship extension interfaces to Google Maven. Teams may choose to ship
+stub JARs publicly, but that is not covered by AndroidX workflows.
Project dependencies on extension libraries **must** use `compileOnly`:
diff --git a/docs/api_guidelines/deprecation.md b/docs/api_guidelines/deprecation.md
index f5dc6d8..2d7069e 100644
--- a/docs/api_guidelines/deprecation.md
+++ b/docs/api_guidelines/deprecation.md
@@ -91,8 +91,9 @@
artifact as `@Deprecated` and update the API files
([example CL](https://android-review.googlesource.com/c/platform/frameworks/support/+/1938773))
1. Schedule a release of the artifact as a new minor version. When you populate
- the release notes, explain that the entire artifact has been deprecated.
- Include the reason for deprecation and the migration strategy.
+ the release notes, explain that the entire artifact has been deprecated and
+ will no longer receive new features or bug fixes. Include the reason for
+ deprecation and the migration strategy.
1. After the artifact has been released, remove the artifact from the source
tree, versions file, and tip-of-tree docs configuration
([example CL](https://android-review.googlesource.com/c/platform/frameworks/support/+/2061731/))
@@ -107,3 +108,58 @@
After an artifact has been released as fully-deprecated, it can be removed from
the source tree.
+
+#### Long-term support
+
+Artifacts which have been fully deprecated and removed are not required to fix
+any bugs -- including security issues -- which are reported after the library
+has been removed from source control; however, library owners *may* utilize
+release branches to provide long-term support.
+
+When working on long-term support in a release branch, you may encounter the
+following issues:
+
+- Release metadata produced by the build system is not compatible with the
+ release scheduling tool
+- Build targets associated with the release branch do not match targets used
+ by the snapped build ID
+- Delta between last snapped build ID and proposed snap build ID is too large
+ and cannot be processed by the release branch management tool
+
+### Discouraging usage in Play Store
+
+[Google Play SDK Console](https://play.google.com/sdk-console/) allows library
+owners to annotate specific library versions with notes, which are shown to app
+developers in the Play Store Console, or permanently mark them as outdated,
+which shows a warning in Play Store Console asking app developers to upgrade.
+
+In both cases, library owners have the option to prevent app developers from
+releasing apps to Play Store that have been built against specific library
+versions.
+
+Generally, Jetpack discourages the use of either notes or marking versions as
+outdated. There are few cases that warrant pushing notifications to app
+developers, and it is easy to abuse notes as advertising to drive adoption. As a
+rule, upgrades to Jetpack libraries should be driven by the needs of app
+developers.
+
+Cases where notes may be used include:
+
+1. The library is used directly, rather than transitively, and contains `P0` or
+ `P1` (ship-blocking, from the app's perspective) issues
+ - Transitively-included libraries should instead urge their dependent
+ libraries to bump their pinned dependency versions
+1. The library contains ship-blocking security issues. In this case, we
+ recommend preventing app releases since developers may be less aware of
+ security issues.
+1. The library was built against a pre-release SDK which has been superseded by
+ a finalized SDK. In this case, we recommend preventing app releases since
+ the library may crash or show unexpected behavior.
+
+Cases where marking a version as outdated maybe used:
+
+1. The library has security implications and the version is no longer receiving
+ security updates, e.g. the release branch has moved to the next version.
+
+In all cases, there must be a newer stable or bugfix release of the library that
+app developers can use to replace the outdated version.
diff --git a/glance/glance-appwidget-testing/api/current.txt b/glance/glance-appwidget-testing/api/current.txt
index 5886053..7dfb9e9 100644
--- a/glance/glance-appwidget-testing/api/current.txt
+++ b/glance/glance-appwidget-testing/api/current.txt
@@ -42,6 +42,9 @@
method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasStartServiceAction(android.content.Intent intent, optional boolean isForegroundService);
method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasStartServiceAction(Class<? extends android.app.Service> serviceClass, optional boolean isForegroundService);
method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> isChecked();
+ method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> isIndeterminateCircularProgressIndicator();
+ method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> isIndeterminateLinearProgressIndicator();
+ method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> isLinearProgressIndicator(float progress);
method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> isNotChecked();
}
diff --git a/glance/glance-appwidget-testing/api/restricted_current.txt b/glance/glance-appwidget-testing/api/restricted_current.txt
index 5886053..7dfb9e9 100644
--- a/glance/glance-appwidget-testing/api/restricted_current.txt
+++ b/glance/glance-appwidget-testing/api/restricted_current.txt
@@ -42,6 +42,9 @@
method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasStartServiceAction(android.content.Intent intent, optional boolean isForegroundService);
method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasStartServiceAction(Class<? extends android.app.Service> serviceClass, optional boolean isForegroundService);
method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> isChecked();
+ method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> isIndeterminateCircularProgressIndicator();
+ method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> isIndeterminateLinearProgressIndicator();
+ method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> isLinearProgressIndicator(float progress);
method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> isNotChecked();
}
diff --git a/glance/glance-appwidget-testing/src/main/java/androidx/glance/appwidget/testing/unit/GlanceAppWidgetUnitTestEnvironment.kt b/glance/glance-appwidget-testing/src/main/java/androidx/glance/appwidget/testing/unit/GlanceAppWidgetUnitTestEnvironment.kt
index 674c8c5..6826664 100644
--- a/glance/glance-appwidget-testing/src/main/java/androidx/glance/appwidget/testing/unit/GlanceAppWidgetUnitTestEnvironment.kt
+++ b/glance/glance-appwidget-testing/src/main/java/androidx/glance/appwidget/testing/unit/GlanceAppWidgetUnitTestEnvironment.kt
@@ -34,8 +34,10 @@
import androidx.glance.appwidget.RemoteViewsRoot
import androidx.glance.session.globalSnapshotMonitor
import androidx.glance.testing.GlanceNodeAssertion
+import androidx.glance.testing.GlanceNodeAssertionCollection
import androidx.glance.testing.GlanceNodeMatcher
import androidx.glance.testing.TestContext
+import androidx.glance.testing.matcherToSelector
import androidx.glance.testing.unit.GlanceMappedNode
import androidx.glance.testing.unit.MappedNode
import kotlin.time.Duration
@@ -154,10 +156,17 @@
): GlanceNodeAssertion<MappedNode, GlanceMappedNode> {
// Always let all the enqueued tasks finish before inspecting the tree.
testScope.testScheduler.runCurrent()
- // Calling onNode resets the previously matched nodes and starts a new matching chain.
- testContext.reset()
// Delegates matching to the next assertion.
- return GlanceNodeAssertion(matcher, testContext)
+ return GlanceNodeAssertion(testContext, matcher.matcherToSelector())
+ }
+
+ override fun onAllNodes(
+ matcher: GlanceNodeMatcher<MappedNode>
+ ): GlanceNodeAssertionCollection<MappedNode, GlanceMappedNode> {
+ // Always let all the enqueued tasks finish before inspecting the tree.
+ testScope.testScheduler.runCurrent()
+ // Delegates matching to the next assertion.
+ return GlanceNodeAssertionCollection(testContext, matcher.matcherToSelector())
}
override fun setAppWidgetSize(size: DpSize) {
diff --git a/glance/glance-appwidget-testing/src/main/java/androidx/glance/appwidget/testing/unit/UnitTestAssertionExtensions.kt b/glance/glance-appwidget-testing/src/main/java/androidx/glance/appwidget/testing/unit/UnitTestAssertionExtensions.kt
index 7bf1b3b..1cddd3a 100644
--- a/glance/glance-appwidget-testing/src/main/java/androidx/glance/appwidget/testing/unit/UnitTestAssertionExtensions.kt
+++ b/glance/glance-appwidget-testing/src/main/java/androidx/glance/appwidget/testing/unit/UnitTestAssertionExtensions.kt
@@ -28,7 +28,8 @@
import androidx.glance.testing.unit.MappedNode
// This file contains (appWidget-specific) convenience assertion shorthands for unit tests that
-// delegate calls to "assert(matcher)". For assertions common to surfaces, see AssertionExtension
+// delegate calls to "assert(matcher)". For assertions common to surfaces, see equivalent file in
+// base layer testing library.
internal typealias UnitTestAssertion = GlanceNodeAssertion<MappedNode, GlanceMappedNode>
@@ -115,7 +116,7 @@
* Asserts that a given node has a clickable set with action that sends a broadcast.
*
* @param receiverClass class of the broadcast receiver that is expected to have been passed in the
- * actionSendBroadcast` method call.
+ * `actionSendBroadcast` method call.
* @throws AssertionError if the matcher does not match or the node can no longer be found.
*/
fun UnitTestAssertion.assertHasSendBroadcastClickAction(
@@ -128,7 +129,7 @@
* @param intentAction the intent action of the broadcast receiver that is expected to have been
* passed in the `actionSendBroadcast` method call.
* @param componentName optional [ComponentName] of the target broadcast receiver that is expected
- * to have been passed in the actionSendBroadcast` method call.
+ * to have been passed in the `actionSendBroadcast` method call.
* @throws AssertionError if the matcher does not match or the node can no longer be found.
*/
fun UnitTestAssertion.assertHasSendBroadcastClickAction(
@@ -140,7 +141,7 @@
* Asserts that a given node has a clickable set with action that sends a broadcast.
*
* @param componentName [ComponentName] of the target broadcast receiver that is expected to have
- * been passed in the actionSendBroadcast` method call.
+ * been passed in the `actionSendBroadcast` method call.
* @throws AssertionError if the matcher does not match or the node can no longer be found.
*/
fun UnitTestAssertion.assertHasSendBroadcastClickAction(
diff --git a/glance/glance-appwidget-testing/src/main/java/androidx/glance/appwidget/testing/unit/UnitTestFilters.kt b/glance/glance-appwidget-testing/src/main/java/androidx/glance/appwidget/testing/unit/UnitTestFilters.kt
index 0f3f231..95f5846 100644
--- a/glance/glance-appwidget-testing/src/main/java/androidx/glance/appwidget/testing/unit/UnitTestFilters.kt
+++ b/glance/glance-appwidget-testing/src/main/java/androidx/glance/appwidget/testing/unit/UnitTestFilters.kt
@@ -25,6 +25,8 @@
import androidx.glance.action.ActionModifier
import androidx.glance.action.ActionParameters
import androidx.glance.action.actionParametersOf
+import androidx.glance.appwidget.EmittableCircularProgressIndicator
+import androidx.glance.appwidget.EmittableLinearProgressIndicator
import androidx.glance.appwidget.action.SendBroadcastActionAction
import androidx.glance.appwidget.action.SendBroadcastClassAction
import androidx.glance.appwidget.action.SendBroadcastComponentAction
@@ -33,6 +35,7 @@
import androidx.glance.appwidget.action.StartServiceClassAction
import androidx.glance.appwidget.action.StartServiceComponentAction
import androidx.glance.appwidget.action.StartServiceIntentAction
+import androidx.glance.testing.GlanceNodeAssertionsProvider
import androidx.glance.testing.GlanceNodeMatcher
import androidx.glance.testing.unit.MappedNode
@@ -40,7 +43,8 @@
* Returns a matcher that matches if a node is checkable (e.g. radio button, switch, checkbox)
* and is checked.
*
- * This can be passed in "onNode" and "onNodeAll" functions on assertion providers to filter out
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
* matching node(s) or in assertions to validate that node(s) satisfy the condition.
*/
fun isChecked(): GlanceNodeMatcher<MappedNode> = GlanceNodeMatcher(
@@ -54,7 +58,8 @@
* Returns a matcher that matches if a node is checkable (e.g. radio button, switch, checkbox)
* but is not checked.
*
- * This can be passed in "onNode" and "onNodeAll" functions on assertion providers to filter out
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
* matching node(s) or in assertions to validate that node(s) satisfy the condition.
*/
fun isNotChecked(): GlanceNodeMatcher<MappedNode> = GlanceNodeMatcher(
@@ -67,7 +72,8 @@
/**
* Returns a matcher that matches if a node has a clickable set with action that starts an activity.
*
- * This can be passed in "onNode" and "onNodeAll" functions on assertion providers to filter out
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
* matching node(s) or in assertions to validate that node(s) satisfy the condition.
*
* @param intent the intent for launching an activity that is expected to have been passed in the
@@ -110,7 +116,8 @@
/**
* Returns a matcher that matches if a node has a clickable set with action that starts a service.
*
- * This can be passed in "onNode" and "onNodeAll" functions on assertion providers to filter out
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
* matching node(s) or in assertions to validate that node(s) satisfy the condition.
*
* @param serviceClass class of the service to launch that is expected to have been passed in the
@@ -143,7 +150,8 @@
/**
* Returns a matcher that matches if a node has a clickable set with action that starts a service.
*
- * This can be passed in "onNode" and "onNodeAll" functions on assertion providers to filter out
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
* matching node(s) or in assertions to validate that node(s) satisfy the condition.
*
* @param componentName component of the service to launch that is expected to have been passed in
@@ -176,7 +184,8 @@
/**
* Returns a matcher that matches if a node has a clickable set with action that starts a service.
*
- * This can be passed in "onNode" and "onNodeAll" functions on assertion providers to filter out
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
* matching node(s) or in assertions to validate that node(s) satisfy the condition.
*
* @param intent the intent for launching the service that is expected to have been passed in
@@ -209,7 +218,8 @@
/**
* Returns a matcher that matches if a node has a clickable set with action that sends a broadcast.
*
- * This can be passed in "onNode" and "onNodeAll" functions on assertion providers to filter out
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
* matching node(s) or in assertions to validate that node(s) satisfy the condition.
*
* @param receiverClass class of the broadcast receiver that is expected to have been passed in the
@@ -234,7 +244,8 @@
/**
* Returns a matcher that matches if a node has a clickable set with action that sends a broadcast.
*
- * This can be passed in "onNode" and "onNodeAll" functions on assertion providers to filter out
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
* matching node(s) or in assertions to validate that node(s) satisfy the condition.
*
* @param intentAction the intent action of the broadcast receiver that is expected to have been
@@ -271,7 +282,8 @@
/**
* Returns a matcher that matches if a node has a clickable set with action that sends a broadcast.
*
- * This can be passed in "onNode" and "onNodeAll" functions on assertion providers to filter out
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
* matching node(s) or in assertions to validate that node(s) satisfy the condition.
*
* @param componentName [ComponentName] of the target broadcast receiver that is expected to have
@@ -296,7 +308,8 @@
/**
* Returns a matcher that matches if a node has a clickable set with action that sends a broadcast.
*
- * This can be passed in "onNode" and "onNodeAll" functions on assertion providers to filter out
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
* matching node(s) or in assertions to validate that node(s) satisfy the condition.
*
* @param intent the intent for sending broadcast that is expected to have been passed in the
@@ -317,3 +330,52 @@
false
}
}
+
+/**
+ * Returns a matcher that matches if a given node is a linear progress indicator with given progress
+ * value.
+ *
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
+ * matching node(s) or in assertions to validate that node(s) satisfy the condition.
+ *
+ * @param progress the expected value of the current progress
+ */
+fun isLinearProgressIndicator(
+ /*@FloatRange(from = 0.0, to = 1.0)*/
+ progress: Float
+): GlanceNodeMatcher<MappedNode> = GlanceNodeMatcher(
+ description = "is a linear progress indicator with progress value: $progress"
+) { node ->
+ val emittable = node.value.emittable
+ emittable is EmittableLinearProgressIndicator &&
+ !emittable.indeterminate &&
+ emittable.progress == progress
+}
+
+/**
+ * Returns a matcher that matches if a given node is an indeterminate progress bar.
+ *
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
+ * matching node(s) or in assertions to validate that node(s) satisfy the condition.
+ */
+fun isIndeterminateLinearProgressIndicator(): GlanceNodeMatcher<MappedNode> = GlanceNodeMatcher(
+ description = "is an indeterminate linear progress indicator"
+) { node ->
+ val emittable = node.value.emittable
+ emittable is EmittableLinearProgressIndicator && emittable.indeterminate
+}
+
+/**
+ * Returns a matcher that matches if a given node is an indeterminate circular progress indicator.
+ *
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
+ * matching node(s) or in assertions to validate that node(s) satisfy the condition.
+ */
+fun isIndeterminateCircularProgressIndicator(): GlanceNodeMatcher<MappedNode> = GlanceNodeMatcher(
+ description = "is an indeterminate circular progress indicator"
+) { node ->
+ node.value.emittable is EmittableCircularProgressIndicator
+}
diff --git a/glance/glance-appwidget-testing/src/test/kotlin/androidx/glance/appwidget/testing/unit/GlanceAppWidgetUnitTestEnvironmentTest.kt b/glance/glance-appwidget-testing/src/test/kotlin/androidx/glance/appwidget/testing/unit/GlanceAppWidgetUnitTestEnvironmentTest.kt
index a1449d8..3c4e2d9 100644
--- a/glance/glance-appwidget-testing/src/test/kotlin/androidx/glance/appwidget/testing/unit/GlanceAppWidgetUnitTestEnvironmentTest.kt
+++ b/glance/glance-appwidget-testing/src/test/kotlin/androidx/glance/appwidget/testing/unit/GlanceAppWidgetUnitTestEnvironmentTest.kt
@@ -32,9 +32,13 @@
import androidx.glance.ImageProvider
import androidx.glance.LocalSize
import androidx.glance.appwidget.ImageProvider
+import androidx.glance.appwidget.lazy.GridCells
+import androidx.glance.appwidget.lazy.LazyColumn
+import androidx.glance.appwidget.lazy.LazyVerticalGrid
import androidx.glance.appwidget.testing.test.R
import androidx.glance.currentState
import androidx.glance.layout.Column
+import androidx.glance.layout.Row
import androidx.glance.layout.Spacer
import androidx.glance.semantics.semantics
import androidx.glance.semantics.testTag
@@ -164,6 +168,58 @@
onNode(hasTestTag("mutable-test")).assert(hasText("initial"))
}
+
+ @Test
+ fun runTest_onMultipleNodesMatchedAcrossHierarchy() = runGlanceAppWidgetUnitTest {
+ provideComposable {
+ Column {
+ Row {
+ Text("text-row")
+ }
+ Spacer()
+ Text("text-in-column")
+ }
+ }
+
+ onAllNodes(hasText(text = "text-")).assertCountEquals(2)
+ }
+
+ @Test
+ fun runTest_lazyColumnChildren() = runGlanceAppWidgetUnitTest {
+ provideComposable {
+ LazyColumn(modifier = GlanceModifier.semantics { testTag = "test-list" }) {
+ item { Text("text-1") }
+ item { Text("text-2") }
+ }
+ }
+
+ onNode(hasTestTag("test-list"))
+ .onChildren()
+ .assertCountEquals(2)
+ .filter(hasText("text-1"))
+ .assertCountEquals(1)
+ }
+
+ @Test
+ fun runTest_lazyGridChildren() = runGlanceAppWidgetUnitTest {
+ provideComposable {
+ LazyVerticalGrid(
+ modifier = GlanceModifier.semantics { testTag = "test-list" },
+ gridCells = GridCells.Fixed(2)
+ ) {
+ item { Text("text-1") }
+ item { Text("text-2") }
+ item { Text("text-3") }
+ item { Text("text-4") }
+ }
+ }
+
+ onNode(hasTestTag("test-list"))
+ .onChildren()
+ .assertCountEquals(4)
+ .filter(hasText("text-1"))
+ .assertCountEquals(1)
+ }
}
private val toggleKey = booleanPreferencesKey("title_toggled_key")
diff --git a/glance/glance-appwidget-testing/src/test/kotlin/androidx/glance/appwidget/testing/unit/UnitTestFiltersTest.kt b/glance/glance-appwidget-testing/src/test/kotlin/androidx/glance/appwidget/testing/unit/UnitTestFiltersTest.kt
new file mode 100644
index 0000000..b444af1
--- /dev/null
+++ b/glance/glance-appwidget-testing/src/test/kotlin/androidx/glance/appwidget/testing/unit/UnitTestFiltersTest.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.glance.appwidget.testing.unit
+
+import androidx.glance.appwidget.EmittableCircularProgressIndicator
+import androidx.glance.appwidget.EmittableLinearProgressIndicator
+import androidx.glance.layout.EmittableColumn
+import androidx.glance.testing.unit.getGlanceNodeAssertionFor
+import com.google.common.truth.ExpectFailure.assertThat
+import org.junit.Assert
+import org.junit.Test
+
+class UnitTestFiltersTest {
+ @Test
+ fun isCircularProgressIndicator_match() {
+ val nodeAssertion = getGlanceNodeAssertionFor(
+ emittable = EmittableColumn().apply {
+ children += EmittableCircularProgressIndicator()
+ },
+ onNodeMatcher = isIndeterminateCircularProgressIndicator()
+ )
+
+ nodeAssertion.assertExists()
+ // no error
+ }
+
+ @Test
+ fun isCircularProgressIndicator_noMatch_assertionError() {
+ val nodeAssertion = getGlanceNodeAssertionFor(
+ emittable = EmittableColumn().apply {
+ children += EmittableLinearProgressIndicator()
+ },
+ onNodeMatcher = isIndeterminateCircularProgressIndicator()
+ )
+
+ val assertionError = Assert.assertThrows(AssertionError::class.java) {
+ nodeAssertion.assertExists()
+ }
+
+ assertThat(assertionError)
+ .hasMessageThat()
+ .contains(
+ "Failed assertExists" +
+ "\nReason: Expected '1' node(s) matching condition: " +
+ "is an indeterminate circular progress indicator, but found '0'"
+ )
+ }
+
+ @Test
+ fun isIndeterminateLinearProgressIndicator_match() {
+ val nodeAssertion = getGlanceNodeAssertionFor(
+ emittable = EmittableColumn().apply {
+ children += EmittableLinearProgressIndicator().apply {
+ indeterminate = true
+ }
+ },
+ onNodeMatcher = isIndeterminateLinearProgressIndicator()
+ )
+
+ nodeAssertion.assertExists()
+ // no error
+ }
+
+ @Test
+ fun isIndeterminateLinearProgressIndicator_noMatch_assertionError() {
+ val nodeAssertion = getGlanceNodeAssertionFor(
+ emittable = EmittableColumn().apply {
+ children += EmittableCircularProgressIndicator()
+ },
+ onNodeMatcher = isIndeterminateLinearProgressIndicator()
+ )
+
+ val assertionError = Assert.assertThrows(AssertionError::class.java) {
+ nodeAssertion.assertExists()
+ }
+
+ assertThat(assertionError)
+ .hasMessageThat()
+ .contains(
+ "Failed assertExists" +
+ "\nReason: Expected '1' node(s) matching condition: " +
+ "is an indeterminate linear progress indicator, but found '0'"
+ )
+ }
+
+ @Test
+ fun isLinearProgressIndicator_match() {
+ val nodeAssertion = getGlanceNodeAssertionFor(
+ emittable = EmittableColumn().apply {
+ children += EmittableLinearProgressIndicator().apply {
+ progress = 10.0f
+ }
+ },
+ onNodeMatcher = isLinearProgressIndicator(10.0f)
+ )
+
+ nodeAssertion.assertExists()
+ // no error
+ }
+
+ @Test
+ fun isLinearProgressIndicator_progressValueNotMatch_assertionError() {
+ val nodeAssertion = getGlanceNodeAssertionFor(
+ emittable = EmittableColumn().apply {
+ children += EmittableLinearProgressIndicator().apply {
+ indeterminate = false
+ progress = 10.0f
+ }
+ },
+ onNodeMatcher = isLinearProgressIndicator(11.0f)
+ )
+
+ val assertionError = Assert.assertThrows(AssertionError::class.java) {
+ nodeAssertion.assertExists()
+ }
+
+ assertThat(assertionError)
+ .hasMessageThat()
+ .contains(
+ "Failed assertExists" +
+ "\nReason: Expected '1' node(s) matching condition: " +
+ "is a linear progress indicator with progress value: 11.0, but found '0'"
+ )
+ }
+}
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/CircularProgressIndicator.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/CircularProgressIndicator.kt
index 9e13d34..bf622db 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/CircularProgressIndicator.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/CircularProgressIndicator.kt
@@ -16,6 +16,7 @@
package androidx.glance.appwidget
+import androidx.annotation.RestrictTo
import androidx.compose.runtime.Composable
import androidx.glance.Emittable
import androidx.glance.GlanceModifier
@@ -42,7 +43,8 @@
)
}
-internal class EmittableCircularProgressIndicator : Emittable {
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+class EmittableCircularProgressIndicator : Emittable {
override var modifier: GlanceModifier = GlanceModifier
var color: ColorProvider = ProgressIndicatorDefaults.IndicatorColorProvider
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/LinearProgressIndicator.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/LinearProgressIndicator.kt
index e314c1a..a300ca5 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/LinearProgressIndicator.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/LinearProgressIndicator.kt
@@ -16,6 +16,7 @@
package androidx.glance.appwidget
+import androidx.annotation.RestrictTo
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.glance.Emittable
@@ -76,7 +77,8 @@
)
}
-internal class EmittableLinearProgressIndicator : Emittable {
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+class EmittableLinearProgressIndicator : Emittable {
override var modifier: GlanceModifier = GlanceModifier
var progress: Float = 0.0f
var indeterminate: Boolean = false
diff --git a/glance/glance-testing/api/current.txt b/glance/glance-testing/api/current.txt
index f12cf50..7524595 100644
--- a/glance/glance-testing/api/current.txt
+++ b/glance/glance-testing/api/current.txt
@@ -12,16 +12,29 @@
method public androidx.glance.testing.GlanceNodeAssertion<R,T> assert(androidx.glance.testing.GlanceNodeMatcher<R> matcher, optional kotlin.jvm.functions.Function0<java.lang.String>? messagePrefixOnError);
method public androidx.glance.testing.GlanceNodeAssertion<R,T> assertDoesNotExist();
method public androidx.glance.testing.GlanceNodeAssertion<R,T> assertExists();
+ method public androidx.glance.testing.GlanceNodeAssertionCollection<R,T> onChildren();
+ }
+
+ public final class GlanceNodeAssertionCollection<R, T extends androidx.glance.testing.GlanceNode<R>> {
+ method public androidx.glance.testing.GlanceNodeAssertionCollection<R,T> assertAll(androidx.glance.testing.GlanceNodeMatcher<R> matcher);
+ method public androidx.glance.testing.GlanceNodeAssertionCollection<R,T> assertAny(androidx.glance.testing.GlanceNodeMatcher<R> matcher);
+ method public androidx.glance.testing.GlanceNodeAssertionCollection<R,T> assertCountEquals(int expectedCount);
+ method public androidx.glance.testing.GlanceNodeAssertionCollection<R,T> filter(androidx.glance.testing.GlanceNodeMatcher<R> matcher);
+ method public operator androidx.glance.testing.GlanceNodeAssertion<R,T> get(int index);
}
public interface GlanceNodeAssertionsProvider<R, T extends androidx.glance.testing.GlanceNode<R>> {
+ method public androidx.glance.testing.GlanceNodeAssertionCollection<R,T> onAllNodes(androidx.glance.testing.GlanceNodeMatcher<R> matcher);
method public androidx.glance.testing.GlanceNodeAssertion<R,T> onNode(androidx.glance.testing.GlanceNodeMatcher<R> matcher);
}
public final class GlanceNodeMatcher<R> {
ctor public GlanceNodeMatcher(String description, kotlin.jvm.functions.Function1<? super androidx.glance.testing.GlanceNode<R>,java.lang.Boolean> matcher);
+ method public infix androidx.glance.testing.GlanceNodeMatcher<R> and(androidx.glance.testing.GlanceNodeMatcher<R> other);
method public boolean matches(androidx.glance.testing.GlanceNode<R> node);
method public boolean matchesAny(Iterable<? extends androidx.glance.testing.GlanceNode<R>> nodes);
+ method public operator androidx.glance.testing.GlanceNodeMatcher<R> not();
+ method public infix androidx.glance.testing.GlanceNodeMatcher<R> or(androidx.glance.testing.GlanceNodeMatcher<R> other);
}
}
@@ -40,8 +53,9 @@
public final class UnitTestAssertionExtensionsKt {
method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>);
method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasContentDescription(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String value);
- method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasContentDescription(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String value, optional boolean substring);
- method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasContentDescription(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String value, optional boolean substring, optional boolean ignoreCase);
+ method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasContentDescription(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String value, optional boolean ignoreCase);
+ method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasContentDescriptionEqualTo(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String value);
+ method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasContentDescriptionEqualTo(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String value, optional boolean ignoreCase);
method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasNoClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>);
method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasStartActivityClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, android.content.ComponentName componentName);
method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasStartActivityClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, android.content.ComponentName componentName, optional androidx.glance.action.ActionParameters parameters);
@@ -49,13 +63,19 @@
method public static <T extends android.app.Activity> androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasStartActivityClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, Class<T> activityClass, optional androidx.glance.action.ActionParameters parameters);
method public static <T extends android.app.Activity> androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasStartActivityClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, Class<T> activityClass, optional androidx.glance.action.ActionParameters parameters, optional android.os.Bundle? activityOptions);
method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasTestTag(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String testTag);
+ method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasText(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String text);
+ method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasText(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String text, optional boolean ignoreCase);
+ method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasTextEqualTo(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String text);
+ method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasTextEqualTo(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String text, optional boolean ignoreCase);
}
public final class UnitTestFiltersKt {
+ method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasAnyDescendant(androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> matcher);
method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasClickAction();
method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasContentDescription(String value);
- method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasContentDescription(String value, optional boolean substring);
- method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasContentDescription(String value, optional boolean substring, optional boolean ignoreCase);
+ method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasContentDescription(String value, optional boolean ignoreCase);
+ method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasContentDescriptionEqualTo(String value);
+ method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasContentDescriptionEqualTo(String value, optional boolean ignoreCase);
method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasNoClickAction();
method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasStartActivityClickAction(android.content.ComponentName componentName);
method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasStartActivityClickAction(android.content.ComponentName componentName, optional androidx.glance.action.ActionParameters parameters);
@@ -64,8 +84,9 @@
method public static <T extends android.app.Activity> androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasStartActivityClickAction(Class<T> activityClass, optional androidx.glance.action.ActionParameters parameters, optional android.os.Bundle? activityOptions);
method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasTestTag(String testTag);
method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasText(String text);
- method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasText(String text, optional boolean substring);
- method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasText(String text, optional boolean substring, optional boolean ignoreCase);
+ method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasText(String text, optional boolean ignoreCase);
+ method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasTextEqualTo(String text);
+ method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasTextEqualTo(String text, optional boolean ignoreCase);
}
}
diff --git a/glance/glance-testing/api/restricted_current.txt b/glance/glance-testing/api/restricted_current.txt
index f12cf50..7524595 100644
--- a/glance/glance-testing/api/restricted_current.txt
+++ b/glance/glance-testing/api/restricted_current.txt
@@ -12,16 +12,29 @@
method public androidx.glance.testing.GlanceNodeAssertion<R,T> assert(androidx.glance.testing.GlanceNodeMatcher<R> matcher, optional kotlin.jvm.functions.Function0<java.lang.String>? messagePrefixOnError);
method public androidx.glance.testing.GlanceNodeAssertion<R,T> assertDoesNotExist();
method public androidx.glance.testing.GlanceNodeAssertion<R,T> assertExists();
+ method public androidx.glance.testing.GlanceNodeAssertionCollection<R,T> onChildren();
+ }
+
+ public final class GlanceNodeAssertionCollection<R, T extends androidx.glance.testing.GlanceNode<R>> {
+ method public androidx.glance.testing.GlanceNodeAssertionCollection<R,T> assertAll(androidx.glance.testing.GlanceNodeMatcher<R> matcher);
+ method public androidx.glance.testing.GlanceNodeAssertionCollection<R,T> assertAny(androidx.glance.testing.GlanceNodeMatcher<R> matcher);
+ method public androidx.glance.testing.GlanceNodeAssertionCollection<R,T> assertCountEquals(int expectedCount);
+ method public androidx.glance.testing.GlanceNodeAssertionCollection<R,T> filter(androidx.glance.testing.GlanceNodeMatcher<R> matcher);
+ method public operator androidx.glance.testing.GlanceNodeAssertion<R,T> get(int index);
}
public interface GlanceNodeAssertionsProvider<R, T extends androidx.glance.testing.GlanceNode<R>> {
+ method public androidx.glance.testing.GlanceNodeAssertionCollection<R,T> onAllNodes(androidx.glance.testing.GlanceNodeMatcher<R> matcher);
method public androidx.glance.testing.GlanceNodeAssertion<R,T> onNode(androidx.glance.testing.GlanceNodeMatcher<R> matcher);
}
public final class GlanceNodeMatcher<R> {
ctor public GlanceNodeMatcher(String description, kotlin.jvm.functions.Function1<? super androidx.glance.testing.GlanceNode<R>,java.lang.Boolean> matcher);
+ method public infix androidx.glance.testing.GlanceNodeMatcher<R> and(androidx.glance.testing.GlanceNodeMatcher<R> other);
method public boolean matches(androidx.glance.testing.GlanceNode<R> node);
method public boolean matchesAny(Iterable<? extends androidx.glance.testing.GlanceNode<R>> nodes);
+ method public operator androidx.glance.testing.GlanceNodeMatcher<R> not();
+ method public infix androidx.glance.testing.GlanceNodeMatcher<R> or(androidx.glance.testing.GlanceNodeMatcher<R> other);
}
}
@@ -40,8 +53,9 @@
public final class UnitTestAssertionExtensionsKt {
method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>);
method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasContentDescription(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String value);
- method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasContentDescription(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String value, optional boolean substring);
- method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasContentDescription(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String value, optional boolean substring, optional boolean ignoreCase);
+ method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasContentDescription(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String value, optional boolean ignoreCase);
+ method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasContentDescriptionEqualTo(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String value);
+ method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasContentDescriptionEqualTo(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String value, optional boolean ignoreCase);
method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasNoClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>);
method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasStartActivityClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, android.content.ComponentName componentName);
method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasStartActivityClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, android.content.ComponentName componentName, optional androidx.glance.action.ActionParameters parameters);
@@ -49,13 +63,19 @@
method public static <T extends android.app.Activity> androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasStartActivityClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, Class<T> activityClass, optional androidx.glance.action.ActionParameters parameters);
method public static <T extends android.app.Activity> androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasStartActivityClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, Class<T> activityClass, optional androidx.glance.action.ActionParameters parameters, optional android.os.Bundle? activityOptions);
method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasTestTag(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String testTag);
+ method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasText(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String text);
+ method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasText(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String text, optional boolean ignoreCase);
+ method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasTextEqualTo(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String text);
+ method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasTextEqualTo(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String text, optional boolean ignoreCase);
}
public final class UnitTestFiltersKt {
+ method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasAnyDescendant(androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> matcher);
method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasClickAction();
method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasContentDescription(String value);
- method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasContentDescription(String value, optional boolean substring);
- method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasContentDescription(String value, optional boolean substring, optional boolean ignoreCase);
+ method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasContentDescription(String value, optional boolean ignoreCase);
+ method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasContentDescriptionEqualTo(String value);
+ method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasContentDescriptionEqualTo(String value, optional boolean ignoreCase);
method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasNoClickAction();
method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasStartActivityClickAction(android.content.ComponentName componentName);
method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasStartActivityClickAction(android.content.ComponentName componentName, optional androidx.glance.action.ActionParameters parameters);
@@ -64,8 +84,9 @@
method public static <T extends android.app.Activity> androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasStartActivityClickAction(Class<T> activityClass, optional androidx.glance.action.ActionParameters parameters, optional android.os.Bundle? activityOptions);
method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasTestTag(String testTag);
method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasText(String text);
- method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasText(String text, optional boolean substring);
- method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasText(String text, optional boolean substring, optional boolean ignoreCase);
+ method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasText(String text, optional boolean ignoreCase);
+ method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasTextEqualTo(String text);
+ method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasTextEqualTo(String text, optional boolean ignoreCase);
}
}
diff --git a/glance/glance-testing/lint-baseline.xml b/glance/glance-testing/lint-baseline.xml
index c7ffb94..bc6854b 100644
--- a/glance/glance-testing/lint-baseline.xml
+++ b/glance/glance-testing/lint-baseline.xml
@@ -1,11 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.2.0-alpha15" type="baseline" client="gradle" dependencies="false" name="AGP (8.2.0-alpha15)" variant="all" version="8.2.0-alpha15">
+<issues format="6" by="lint 8.2.0-beta01" type="baseline" client="gradle" dependencies="false" name="AGP (8.2.0-beta01)" variant="all" version="8.2.0-beta01">
<issue
id="ListIterator"
message="Creating an unnecessary Iterator to iterate through a List"
- errorLine1=" return emittable.children.map { child ->"
- errorLine2=" ~~~">
+ errorLine1=" nodes.forEachIndexed { index, glanceNode ->"
+ errorLine2=" ~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/glance/testing/AssertionErrorMessages.kt"/>
+ </issue>
+
+ <issue
+ id="ListIterator"
+ message="Creating an unnecessary Iterator to iterate through a List"
+ errorLine1=" children.forEach { child ->"
+ errorLine2=" ~~~~~~~">
<location
file="src/main/java/androidx/glance/testing/unit/GlanceMappedNode.kt"/>
</issue>
@@ -13,19 +22,28 @@
<issue
id="ListIterator"
message="Creating an unnecessary Iterator to iterate through a List"
- errorLine1=" for (child in node.children()) {"
- errorLine2=" ~~">
+ errorLine1=" return mappedNodes.toList()"
+ errorLine2=" ~~~~~~">
<location
- file="src/main/java/androidx/glance/testing/GlanceNodeAssertion.kt"/>
+ file="src/main/java/androidx/glance/testing/unit/GlanceMappedNode.kt"/>
</issue>
<issue
id="ListIterator"
message="Creating an unnecessary Iterator to iterate through a List"
- errorLine1=" return matching.toList()"
- errorLine2=" ~~~~~~">
+ errorLine1=" val violations = filteredNodes.filter {"
+ errorLine2=" ~~~~~~">
<location
- file="src/main/java/androidx/glance/testing/GlanceNodeAssertion.kt"/>
+ file="src/main/java/androidx/glance/testing/GlanceNodeAssertionCollection.kt"/>
+ </issue>
+
+ <issue
+ id="ListIterator"
+ message="Creating an unnecessary Iterator to iterate through a List"
+ errorLine1=" this.allNodes = allNodes.toList()"
+ errorLine2=" ~~~~~~">
+ <location
+ file="src/main/java/androidx/glance/testing/TestContext.kt"/>
</issue>
<issue
@@ -37,4 +55,13 @@
file="src/main/java/androidx/glance/testing/unit/UnitTestFilters.kt"/>
</issue>
+ <issue
+ id="ListIterator"
+ message="Creating an unnecessary Iterator to iterate through a List"
+ errorLine1=" return node.children().any { checkIfSubtreeMatchesRecursive(matcher, it) }"
+ errorLine2=" ~~~">
+ <location
+ file="src/main/java/androidx/glance/testing/unit/UnitTestFilters.kt"/>
+ </issue>
+
</issues>
diff --git a/glance/glance-testing/src/main/java/androidx/glance/testing/AssertionErrorMessages.kt b/glance/glance-testing/src/main/java/androidx/glance/testing/AssertionErrorMessages.kt
index 671a692..16155f6 100644
--- a/glance/glance-testing/src/main/java/androidx/glance/testing/AssertionErrorMessages.kt
+++ b/glance/glance-testing/src/main/java/androidx/glance/testing/AssertionErrorMessages.kt
@@ -19,23 +19,40 @@
import java.lang.StringBuilder
/**
- * Builds error message for case where expected amount of matching nodes does not match reality.
+ * Builds error message with reason appended.
*
- * Provide [errorMessage] to explain which operation you were about to perform. This makes it
- * easier for developer to find where the failure happened.
+ * @param errorMessageOnFail message explaining which operation you were about to perform. This
+ * makes it easier for developer to find where the failure happened.
+ * @param reason the reason for failure
*/
-internal fun buildErrorMessageForCountMismatch(
- errorMessage: String,
+internal fun buildErrorMessageWithReason(errorMessageOnFail: String, reason: String): String {
+ return "${errorMessageOnFail}\nReason: $reason"
+}
+
+/**
+ * Builds error reason for case where amount of matching nodes are less than needed to query given
+ * index and perform assertions on (e.g. if getting a node at index 2 but only 2 nodes exist in
+ * the collection).
+ */
+internal fun buildErrorReasonForIndexOutOfMatchedNodeBounds(
+ matcherDescription: String,
+ requestedIndex: Int,
+ actualCount: Int
+): String {
+ return "Not enough node(s) matching condition: ($matcherDescription) " +
+ "to get node at index '$requestedIndex'. Found '$actualCount' matching node(s)"
+}
+
+/**
+ * Builds error reason for case where expected amount of matching nodes does not match reality.
+ */
+internal fun buildErrorReasonForCountMismatch(
matcherDescription: String,
expectedCount: Int,
actualCount: Int
): String {
val sb = StringBuilder()
- sb.append(errorMessage)
- sb.append("\n")
-
- sb.append("Reason: ")
when (expectedCount) {
0 -> {
sb.append("Did not expect any node matching condition: $matcherDescription")
@@ -52,20 +69,54 @@
}
/**
- * Builds error message for general assertion errors.
+ * Builds error reason for assertions where at least one node was expected to be present to make
+ * assertions on (e.g. assertAny).
+ */
+internal fun buildErrorReasonForAtLeastOneNodeExpected(
+ matcherDescription: String
+): String {
+ return "Expected to receive at least 1 node " +
+ "but 0 nodes were found for condition: ($matcherDescription)"
+}
+
+/**
+ * Builds error message for general assertion errors involving a single node.
*
* <p>Provide [errorMessage] to explain which operation you were about to perform. This makes it
* easier for developer to find where the failure happened.
*/
internal fun <R> buildGeneralErrorMessage(
errorMessage: String,
- glanceNode: GlanceNode<R>
+ node: GlanceNode<R>
): String {
val sb = StringBuilder()
sb.append(errorMessage)
sb.append("\n")
- sb.append("Glance Node: ${glanceNode.toDebugString()}")
+ sb.append("Glance Node: ${node.toDebugString()}")
+
+ return sb.toString()
+}
+
+/**
+ * Builds error message for general assertion errors for multiple nodes.
+ *
+ * <p>Provide [errorMessage] to explain which operation you were about to perform. This makes it
+ * easier for developer to find where the failure happened.
+ */
+internal fun <R> buildGeneralErrorMessage(
+ errorMessage: String,
+ nodes: List<GlanceNode<R>>
+): String {
+ val sb = StringBuilder()
+ sb.append(errorMessage)
+
+ sb.append("\n")
+ sb.append("Found ${nodes.size} node(s) that don't match.")
+
+ nodes.forEachIndexed { index, glanceNode ->
+ sb.append("\nNon-matching Glance Node #${index + 1}: ${glanceNode.toDebugString()}")
+ }
return sb.toString()
}
diff --git a/glance/glance-testing/src/main/java/androidx/glance/testing/GlanceNodeAssertion.kt b/glance/glance-testing/src/main/java/androidx/glance/testing/GlanceNodeAssertion.kt
index 31a1c99..4095626 100644
--- a/glance/glance-testing/src/main/java/androidx/glance/testing/GlanceNodeAssertion.kt
+++ b/glance/glance-testing/src/main/java/androidx/glance/testing/GlanceNodeAssertion.kt
@@ -22,12 +22,12 @@
/**
* Represents a Glance node from the tree that can be asserted on.
*
- * An instance of [GlanceNodeAssertion] can be obtained from {@code onNode} and equivalent methods
- * on a GlanceNodeAssertionProvider
+ * An instance of [GlanceNodeAssertion] can be obtained from `onNode` and equivalent methods
+ * on a [GlanceNodeAssertionsProvider]
*/
class GlanceNodeAssertion<R, T : GlanceNode<R>> @RestrictTo(Scope.LIBRARY_GROUP) constructor(
- private val matcher: GlanceNodeMatcher<R>,
private val testContext: TestContext<R, T>,
+ private val selector: GlanceNodeSelector<R>,
) {
/**
* Asserts that the node was found.
@@ -35,7 +35,7 @@
* @throws [AssertionError] if the assert fails.
*/
fun assertExists(): GlanceNodeAssertion<R, T> {
- findSingleMatchingNode(finalErrorMessageOnFail = "Failed assertExists")
+ findSingleMatchingNode(errorMessageOnFail = "Failed assertExists")
return this
}
@@ -45,14 +45,17 @@
* @throws [AssertionError] if the assert fails.
*/
fun assertDoesNotExist(): GlanceNodeAssertion<R, T> {
- val matchedNodesCount = findMatchingNodes().size
+ val errorMessageOnFail = "Failed assertDoesNotExist"
+ val matchedNodesCount = testContext.findMatchingNodes(selector, errorMessageOnFail).size
if (matchedNodesCount != 0) {
throw AssertionError(
- buildErrorMessageForCountMismatch(
- errorMessage = "Failed assertDoesNotExist",
- matcherDescription = matcher.description,
- expectedCount = 0,
- actualCount = matchedNodesCount
+ buildErrorMessageWithReason(
+ errorMessageOnFail = errorMessageOnFail,
+ reason = buildErrorReasonForCountMismatch(
+ matcherDescription = selector.description,
+ expectedCount = 0,
+ actualCount = matchedNodesCount
+ )
)
)
}
@@ -93,39 +96,28 @@
return this
}
- private fun findSingleMatchingNode(finalErrorMessageOnFail: String): GlanceNode<R> {
- val matchingNodes = findMatchingNodes()
- val matchedNodesCount = matchingNodes.size
- if (matchedNodesCount != 1) {
+ /**
+ * Returns [GlanceNodeAssertionCollection] that allows performing assertions on the children of
+ * the node selected by this [GlanceNodeAssertion].
+ */
+ fun onChildren(): GlanceNodeAssertionCollection<R, T> {
+ return GlanceNodeAssertionCollection(testContext, selector.addChildrenSelector())
+ }
+
+ private fun findSingleMatchingNode(errorMessageOnFail: String): GlanceNode<R> {
+ val matchingNodes = testContext.findMatchingNodes(selector, errorMessageOnFail)
+ if (matchingNodes.size != 1) {
throw AssertionError(
- buildErrorMessageForCountMismatch(
- finalErrorMessageOnFail,
- matcher.description,
- expectedCount = 1,
- actualCount = matchedNodesCount
+ buildErrorMessageWithReason(
+ errorMessageOnFail = errorMessageOnFail,
+ reason = buildErrorReasonForCountMismatch(
+ matcherDescription = selector.description,
+ expectedCount = 1,
+ actualCount = matchingNodes.size
+ )
)
)
}
return matchingNodes.single()
}
-
- private fun findMatchingNodes(): List<GlanceNode<R>> {
- if (testContext.cachedMatchedNodes.isEmpty()) {
- val rootGlanceNode =
- checkNotNull(testContext.rootGlanceNode) { "No root GlanceNode found." }
- testContext.cachedMatchedNodes = findMatchingNodes(rootGlanceNode)
- }
- return testContext.cachedMatchedNodes
- }
-
- private fun findMatchingNodes(node: GlanceNode<R>): List<GlanceNode<R>> {
- val matching = mutableListOf<GlanceNode<R>>()
- if (matcher.matches(node)) {
- matching.add(node)
- }
- for (child in node.children()) {
- matching.addAll(findMatchingNodes(child))
- }
- return matching.toList()
- }
}
diff --git a/glance/glance-testing/src/main/java/androidx/glance/testing/GlanceNodeAssertionCollection.kt b/glance/glance-testing/src/main/java/androidx/glance/testing/GlanceNodeAssertionCollection.kt
new file mode 100644
index 0000000..cbe4572
--- /dev/null
+++ b/glance/glance-testing/src/main/java/androidx/glance/testing/GlanceNodeAssertionCollection.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.glance.testing
+
+import androidx.annotation.RestrictTo
+
+/**
+ * Represents a collection of Glance nodes from the tree that can be asserted on.
+ *
+ * An instance of [GlanceNodeAssertionCollection] can be obtained from
+ * [GlanceNodeAssertionsProvider.onAllNodes] and equivalent methods.
+ */
+// Equivalent to SemanticsNodeInteractionCollection in compose.
+class GlanceNodeAssertionCollection<R, T : GlanceNode<R>>
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) constructor(
+ private val testContext: TestContext<R, T>,
+ private val selector: GlanceNodeSelector<R>
+) {
+ /**
+ * Asserts that this collection of nodes is equal to the given [expectedCount].
+ *
+ * @throws AssertionError if the size is not equal to [expectedCount]
+ */
+ fun assertCountEquals(
+ expectedCount: Int
+ ): GlanceNodeAssertionCollection<R, T> {
+ val errorMessageOnFail = "Failed to assert count of nodes"
+
+ val actualCount = testContext.findMatchingNodes(selector, errorMessageOnFail).size
+ if (actualCount != expectedCount) {
+ throw AssertionError(
+ buildErrorMessageWithReason(
+ errorMessageOnFail = errorMessageOnFail,
+ reason = buildErrorReasonForCountMismatch(
+ matcherDescription = selector.description,
+ expectedCount = expectedCount,
+ actualCount = actualCount
+ )
+ )
+ )
+ }
+ return this
+ }
+
+ /**
+ * Asserts that all the nodes in this collection satisfy the given [matcher].
+ *
+ * Doesn't throw error if the collection is empty. Use [assertCountEquals] to assert on expected
+ * size of the collection.
+ *
+ * @param matcher Matcher that has to be satisfied by all the nodes in the collection.
+ * @throws AssertionError if the collection contains at least one element that does not satisfy
+ * the given matcher.
+ */
+ fun assertAll(
+ matcher: GlanceNodeMatcher<R>,
+ ): GlanceNodeAssertionCollection<R, T> {
+ val errorMessageOnFail = "Failed to assertAll(${matcher.description})"
+
+ val filteredNodes = testContext.findMatchingNodes(selector, errorMessageOnFail)
+ val violations = filteredNodes.filter {
+ !matcher.matches(it)
+ }
+ if (violations.isNotEmpty()) {
+ throw AssertionError(buildGeneralErrorMessage(errorMessageOnFail, violations))
+ }
+ return this
+ }
+
+ /**
+ * Asserts that this collection contains at least one element that satisfies the given
+ * [matcher].
+ *
+ * @param matcher Matcher that has to be satisfied by at least one of the nodes in the
+ * collection.
+ * @throws AssertionError if not at least one matching node was found.
+ */
+ fun assertAny(
+ matcher: GlanceNodeMatcher<R>,
+ ): GlanceNodeAssertionCollection<R, T> {
+ val errorMessageOnFail = "Failed to assertAny(${matcher.description})"
+ val filteredNodes = testContext.findMatchingNodes(selector, errorMessageOnFail)
+
+ if (filteredNodes.isEmpty()) {
+ throw AssertionError(
+ buildErrorMessageWithReason(
+ errorMessageOnFail = errorMessageOnFail,
+ reason = buildErrorReasonForAtLeastOneNodeExpected(selector.description)
+ )
+ )
+ }
+
+ if (!matcher.matchesAny(filteredNodes)) {
+ throw AssertionError(buildGeneralErrorMessage(errorMessageOnFail, filteredNodes))
+ }
+ return this
+ }
+
+ /**
+ * Returns a [GlanceNodeAssertion] that can assert on the node at the given index of this
+ * collection.
+ *
+ * Any subsequent assertion on its result will throw error if index is out of bounds of the
+ * matching nodes found from previous operations.
+ */
+ operator fun get(index: Int): GlanceNodeAssertion<R, T> {
+ return GlanceNodeAssertion(
+ testContext = testContext,
+ selector = selector.addIndexedSelector(index)
+ )
+ }
+
+ /**
+ * Returns a new collection of nodes by filtering the given nodes using the provided [matcher].
+ */
+ fun filter(matcher: GlanceNodeMatcher<R>): GlanceNodeAssertionCollection<R, T> {
+ return GlanceNodeAssertionCollection(
+ testContext,
+ selector.addMatcherSelector(
+ selectorName = "filter",
+ matcher = matcher
+ )
+ )
+ }
+}
diff --git a/glance/glance-testing/src/main/java/androidx/glance/testing/GlanceNodeAssertionsProvider.kt b/glance/glance-testing/src/main/java/androidx/glance/testing/GlanceNodeAssertionsProvider.kt
index 624b529..8adddc0 100644
--- a/glance/glance-testing/src/main/java/androidx/glance/testing/GlanceNodeAssertionsProvider.kt
+++ b/glance/glance-testing/src/main/java/androidx/glance/testing/GlanceNodeAssertionsProvider.kt
@@ -30,4 +30,11 @@
* @param matcher Matcher used for filtering
*/
fun onNode(matcher: GlanceNodeMatcher<R>): GlanceNodeAssertion<R, T>
+
+ /**
+ * Finds all Glance nodes that matches the given condition.
+ *
+ * @param matcher Matcher used for filtering
+ */
+ fun onAllNodes(matcher: GlanceNodeMatcher<R>): GlanceNodeAssertionCollection<R, T>
}
diff --git a/glance/glance-testing/src/main/java/androidx/glance/testing/GlanceNodeMatcher.kt b/glance/glance-testing/src/main/java/androidx/glance/testing/GlanceNodeMatcher.kt
index 0e9fe2a..6de4dc5 100644
--- a/glance/glance-testing/src/main/java/androidx/glance/testing/GlanceNodeMatcher.kt
+++ b/glance/glance-testing/src/main/java/androidx/glance/testing/GlanceNodeMatcher.kt
@@ -38,4 +38,35 @@
fun matchesAny(nodes: Iterable<GlanceNode<R>>): Boolean {
return nodes.any(matcher)
}
+
+ /**
+ * Returns whether the given node is matched by this and the [other] matcher.
+ *
+ * @param other matcher that should also match in addition to current matcher
+ */
+ infix fun and(other: GlanceNodeMatcher<R>): GlanceNodeMatcher<R> {
+ return GlanceNodeMatcher("($description) && (${other.description})") {
+ matcher(it) && other.matches(it)
+ }
+ }
+
+ /**
+ * Returns whether the given node is matched by this or the [other] matcher.
+ *
+ * @param other matcher that can be tested to match if the current matcher doesn't.
+ */
+ infix fun or(other: GlanceNodeMatcher<R>): GlanceNodeMatcher<R> {
+ return GlanceNodeMatcher("($description) || (${other.description})") {
+ matcher(it) || other.matches(it)
+ }
+ }
+
+ /**
+ * Returns whether the given node does not match the matcher.
+ */
+ operator fun not(): GlanceNodeMatcher<R> {
+ return GlanceNodeMatcher(("NOT ($description)")) {
+ !matcher(it)
+ }
+ }
}
diff --git a/glance/glance-testing/src/main/java/androidx/glance/testing/GlanceNodeSelector.kt b/glance/glance-testing/src/main/java/androidx/glance/testing/GlanceNodeSelector.kt
new file mode 100644
index 0000000..f06d499
--- /dev/null
+++ b/glance/glance-testing/src/main/java/androidx/glance/testing/GlanceNodeSelector.kt
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.glance.testing
+
+import androidx.annotation.RestrictTo
+
+/**
+ * A chainable selector that allows specifying how to select nodes from a collection.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+class GlanceNodeSelector<R>(
+ val description: String,
+ private val previousChainedSelector: GlanceNodeSelector<R>? = null,
+ private val selector: (Iterable<GlanceNode<R>>) -> SelectionResult<R>
+) {
+
+ /**
+ * Returns nodes selected by previous chained selectors followed by the current selector.
+ */
+ fun map(nodes: Iterable<GlanceNode<R>>): SelectionResult<R> {
+ val previousSelectionResult = previousChainedSelector?.map(nodes)
+ val inputNodes = previousSelectionResult?.selectedNodes ?: nodes
+ return selector(inputNodes)
+ }
+}
+
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+class SelectionResult<R>(
+ val selectedNodes: List<GlanceNode<R>>,
+ val errorMessageOnNoMatch: String? = null
+)
+
+/**
+ * Constructs an entry-point selector that selects nodes satisfying the matcher condition. Used at
+ * the entry points such as [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] where there is no previous chained selector.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+fun <R> GlanceNodeMatcher<R>.matcherToSelector(): GlanceNodeSelector<R> {
+ return GlanceNodeSelector(
+ description = description,
+ previousChainedSelector = null
+ ) { glanceNodes ->
+ SelectionResult(
+ selectedNodes = glanceNodes.filter { matches(it) }
+ )
+ }
+}
+
+/**
+ * Wraps the current selector with a chained selector that selects a node at a given index from the
+ * the result of current selection.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+fun <R> GlanceNodeSelector<R>.addIndexedSelector(index: Int): GlanceNodeSelector<R> {
+ return GlanceNodeSelector(
+ description = "(${this.description})[$index]",
+ previousChainedSelector = this
+ ) { nodes ->
+ val nodesList = nodes.toList()
+ val minimumExpectedCount = index + 1
+ if (index >= 0 && index < nodesList.size) {
+ SelectionResult(
+ selectedNodes = listOf(nodesList[index])
+ )
+ } else {
+ SelectionResult(
+ selectedNodes = emptyList(),
+ errorMessageOnNoMatch = buildErrorReasonForIndexOutOfMatchedNodeBounds(
+ description,
+ requestedIndex = minimumExpectedCount,
+ actualCount = nodesList.size
+ )
+ )
+ }
+ }
+}
+
+/**
+ * Wraps the current selector with a chained matcher-based selector that filters the list of nodes
+ * to return ones matched by the matcher.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+fun <R> GlanceNodeSelector<R>.addMatcherSelector(
+ selectorName: String,
+ matcher: GlanceNodeMatcher<R>
+): GlanceNodeSelector<R> {
+ return GlanceNodeSelector(
+ description = "(${this.description}).$selectorName(${matcher.description})",
+ previousChainedSelector = this
+ ) { nodes ->
+ SelectionResult(
+ selectedNodes = nodes.filter { matcher.matches(it) }
+ )
+ }
+}
+
+/**
+ * Wraps the current selector with a chained matcher-based selector that ensures only one node is
+ * returned by current selector and selects children of that node.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+fun <R> GlanceNodeSelector<R>.addChildrenSelector(): GlanceNodeSelector<R> {
+ return GlanceNodeSelector(
+ description = "($description).children()",
+ previousChainedSelector = this
+ ) { nodes ->
+ if (nodes.count() != 1) {
+ SelectionResult(
+ selectedNodes = emptyList(),
+ errorMessageOnNoMatch = buildErrorReasonForCountMismatch(
+ matcherDescription = description,
+ expectedCount = 1,
+ actualCount = nodes.count()
+ )
+ )
+ } else {
+ SelectionResult(
+ selectedNodes = nodes.single().children()
+ )
+ }
+ }
+}
diff --git a/glance/glance-testing/src/main/java/androidx/glance/testing/TestContext.kt b/glance/glance-testing/src/main/java/androidx/glance/testing/TestContext.kt
index 1ab76a3..419a304 100644
--- a/glance/glance-testing/src/main/java/androidx/glance/testing/TestContext.kt
+++ b/glance/glance-testing/src/main/java/androidx/glance/testing/TestContext.kt
@@ -23,13 +23,55 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
class TestContext<R, T : GlanceNode<R>> {
+ var rootGlanceNode: T? = null
+ private var allNodes: List<GlanceNode<R>> = emptyList()
+
/**
- * To be called on every onNode to restart matching and clear cache.
+ * Returns all nodes in single flat list (either from cache or by traversing the hierarchy from
+ * root glance node).
*/
- fun reset() {
- cachedMatchedNodes = emptyList()
+ private fun getAllNodes(): List<GlanceNode<R>> {
+ val rootGlanceNode =
+ checkNotNull(rootGlanceNode) { "No root GlanceNode found." }
+ if (this.allNodes.isEmpty()) {
+ val allNodes = mutableListOf<GlanceNode<R>>()
+
+ fun collectAllNodesRecursive(currentNode: GlanceNode<R>) {
+ allNodes.add(currentNode)
+ val children = currentNode.children()
+ for (index in children.indices) {
+ collectAllNodesRecursive(children[index])
+ }
+ }
+
+ collectAllNodesRecursive(rootGlanceNode)
+ this.allNodes = allNodes.toList()
+ }
+
+ return this.allNodes
}
- var rootGlanceNode: T? = null
- var cachedMatchedNodes: List<GlanceNode<R>> = emptyList()
+ /**
+ * Finds nodes matching the given selector from the list of all nodes in the hierarchy.
+ *
+ * @throws AssertionError if provided selector results in an error due to no match.
+ */
+ fun findMatchingNodes(
+ selector: GlanceNodeSelector<R>,
+ errorMessageOnFail: String
+ ): List<GlanceNode<R>> {
+ val allNodes = getAllNodes()
+ val selectionResult = selector.map(allNodes)
+
+ if (selectionResult.errorMessageOnNoMatch != null) {
+ throw AssertionError(
+ buildErrorMessageWithReason(
+ errorMessageOnFail = errorMessageOnFail,
+ reason = selectionResult.errorMessageOnNoMatch
+ )
+ )
+ }
+
+ return selectionResult.selectedNodes
+ }
}
diff --git a/glance/glance-testing/src/main/java/androidx/glance/testing/unit/GlanceMappedNode.kt b/glance/glance-testing/src/main/java/androidx/glance/testing/unit/GlanceMappedNode.kt
index 8b20029..929fc80 100644
--- a/glance/glance-testing/src/main/java/androidx/glance/testing/unit/GlanceMappedNode.kt
+++ b/glance/glance-testing/src/main/java/androidx/glance/testing/unit/GlanceMappedNode.kt
@@ -19,6 +19,7 @@
import androidx.annotation.RestrictTo
import androidx.annotation.RestrictTo.Scope
import androidx.glance.Emittable
+import androidx.glance.EmittableLazyItemWithChildren
import androidx.glance.EmittableWithChildren
import androidx.glance.testing.GlanceNode
@@ -60,13 +61,23 @@
override fun children(): List<GlanceNode<MappedNode>> {
val emittable = mappedNode.emittable
if (emittable is EmittableWithChildren) {
- return emittable.children.map { child ->
- GlanceMappedNode(child)
- }
+ return emittable.toMappedNodes()
}
return emptyList()
}
+ private fun EmittableWithChildren.toMappedNodes(): List<GlanceMappedNode> {
+ val mappedNodes = mutableListOf<GlanceMappedNode>()
+ children.forEach { child ->
+ if (child is EmittableLazyItemWithChildren) {
+ mappedNodes.addAll(child.toMappedNodes())
+ } else {
+ mappedNodes.add(GlanceMappedNode(child))
+ }
+ }
+ return mappedNodes.toList()
+ }
+
@RestrictTo(Scope.LIBRARY_GROUP)
override fun toDebugString(): String {
// TODO(b/201779038): map to a more readable format.
diff --git a/glance/glance-testing/src/main/java/androidx/glance/testing/unit/TestUtils.kt b/glance/glance-testing/src/main/java/androidx/glance/testing/unit/TestUtils.kt
index 40539aa..17935b0 100644
--- a/glance/glance-testing/src/main/java/androidx/glance/testing/unit/TestUtils.kt
+++ b/glance/glance-testing/src/main/java/androidx/glance/testing/unit/TestUtils.kt
@@ -19,9 +19,12 @@
import androidx.annotation.RestrictTo
import androidx.glance.Emittable
import androidx.glance.testing.GlanceNodeAssertion
+import androidx.glance.testing.GlanceNodeAssertionCollection
import androidx.glance.testing.GlanceNodeMatcher
import androidx.glance.testing.TestContext
+import androidx.glance.testing.matcherToSelector
+// Equivalent to calling GlanceNodeAssertionsProvider.onNode
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
fun getGlanceNodeAssertionFor(
emittable: Emittable,
@@ -30,7 +33,21 @@
val testContext = TestContext<MappedNode, GlanceMappedNode>()
testContext.rootGlanceNode = GlanceMappedNode(emittable)
return GlanceNodeAssertion(
- matcher = onNodeMatcher,
- testContext = testContext
+ testContext = testContext,
+ selector = onNodeMatcher.matcherToSelector()
+ )
+}
+
+// Equivalent to calling GlanceNodeAssertionsProvider.onAllNodes
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+fun getGlanceNodeAssertionCollectionFor(
+ emittable: Emittable,
+ onAllNodesMatcher: GlanceNodeMatcher<MappedNode>
+): GlanceNodeAssertionCollection<MappedNode, GlanceMappedNode> {
+ val testContext = TestContext<MappedNode, GlanceMappedNode>()
+ testContext.rootGlanceNode = GlanceMappedNode(emittable)
+ return GlanceNodeAssertionCollection(
+ testContext = testContext,
+ selector = onAllNodesMatcher.matcherToSelector()
)
}
diff --git a/glance/glance-testing/src/main/java/androidx/glance/testing/unit/UnitTestAssertionExtensions.kt b/glance/glance-testing/src/main/java/androidx/glance/testing/unit/UnitTestAssertionExtensions.kt
index 2aa1aca..715910f 100644
--- a/glance/glance-testing/src/main/java/androidx/glance/testing/unit/UnitTestAssertionExtensions.kt
+++ b/glance/glance-testing/src/main/java/androidx/glance/testing/unit/UnitTestAssertionExtensions.kt
@@ -30,6 +30,35 @@
internal typealias UnitTestAssertion = GlanceNodeAssertion<MappedNode, GlanceMappedNode>
/**
+ * Asserts that text on the given node is a text node and its text contains the provided [text] as
+ * substring.
+ *
+ * @param text value to match.
+ * @param ignoreCase whether to perform case insensitive matching
+ */
+@JvmOverloads
+fun UnitTestAssertion.assertHasText(
+ text: String,
+ ignoreCase: Boolean = false
+): UnitTestAssertion {
+ return assert(hasText(text, ignoreCase))
+}
+
+/**
+ * Asserts that text on the given node is text node and its text is equal to the provided [text].
+ *
+ * @param text value to match.
+ * @param ignoreCase whether to perform case insensitive matching
+ */
+@JvmOverloads
+fun UnitTestAssertion.assertHasTextEqualTo(
+ text: String,
+ ignoreCase: Boolean = false
+): UnitTestAssertion {
+ return assert(hasTextEqualTo(text, ignoreCase))
+}
+
+/**
* Asserts that a given node is annotated by the given test tag.
*
* @param testTag value to match against the free form string specified in the `testTag` semantics
@@ -41,11 +70,10 @@
}
/**
- * Asserts that a given node matches content description with the provided [value]
+ * Asserts that the content description set on the node contains the provided [value] as substring.
*
- * @param value value to match as one of the items in the list of content descriptions.
- * @param substring whether to use substring matching.
- * @param ignoreCase whether case should be ignored.
+ * @param value value that should be matched as a substring of the node's content description.
+ * @param ignoreCase whether case should be ignored. Defaults to case sensitive.
*
* @see SemanticsProperties.ContentDescription
*
@@ -54,10 +82,27 @@
@JvmOverloads
fun UnitTestAssertion.assertHasContentDescription(
value: String,
- substring: Boolean = false,
ignoreCase: Boolean = false
): UnitTestAssertion {
- return assert(hasContentDescription(value, substring, ignoreCase))
+ return assert(hasContentDescription(value, ignoreCase))
+}
+
+/**
+ * Asserts that the content description set on the node is equal to the provided [value]
+ *
+ * @param value value that should be matched to be equal to the node's content description.
+ * @param ignoreCase whether case should be ignored. Defaults to case sensitive.
+ *
+ * @see SemanticsProperties.ContentDescription
+ *
+ * @throws AssertionError if the matcher does not match or the node can no longer be found.
+ */
+@JvmOverloads
+fun UnitTestAssertion.assertHasContentDescriptionEqualTo(
+ value: String,
+ ignoreCase: Boolean = false
+): UnitTestAssertion {
+ return assert(hasContentDescriptionEqualTo(value, ignoreCase))
}
/**
@@ -81,9 +126,6 @@
/**
* Asserts that a given node has a clickable set with action that starts an activity.
*
- * This can be passed in "onNode" and "onNodeAll" functions on assertion providers to filter out
- * matching node(s) or in assertions to validate that node(s) satisfy the condition.
- *
* @param activityClass class of the activity that is expected to have been passed in the
* `actionStartActivity` method call
* @param parameters the parameters associated with the action that are expected to have been passed
diff --git a/glance/glance-testing/src/main/java/androidx/glance/testing/unit/UnitTestFilters.kt b/glance/glance-testing/src/main/java/androidx/glance/testing/unit/UnitTestFilters.kt
index 1e3d858..6d83d4c 100644
--- a/glance/glance-testing/src/main/java/androidx/glance/testing/unit/UnitTestFilters.kt
+++ b/glance/glance-testing/src/main/java/androidx/glance/testing/unit/UnitTestFilters.kt
@@ -28,6 +28,8 @@
import androidx.glance.semantics.SemanticsModifier
import androidx.glance.semantics.SemanticsProperties
import androidx.glance.semantics.SemanticsPropertyKey
+import androidx.glance.testing.GlanceNode
+import androidx.glance.testing.GlanceNodeAssertionsProvider
import androidx.glance.testing.GlanceNodeMatcher
// This file contains common filters that can be passed in "onNode", "onAllNodes" or
@@ -37,7 +39,8 @@
/**
* Returns a matcher that matches if a node is annotated by the given test tag.
*
- * <p>This can be passed in "onNode" and "onNodeAll" functions on assertion providers to filter out
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
* matching node(s) or in assertions to validate that node(s) satisfy the condition.
*
* @param testTag value to match against the free form string specified in the `testTag` semantics
@@ -59,31 +62,67 @@
}
/**
- * Returns whether the node matches content description with the provided [value]
+ * Returns whether the content description set directly on the node contains the provided [value].
*
- * @param value value to match as one of the items in the list of content descriptions.
- * @param substring whether to use substring matching.
- * @param ignoreCase whether case should be ignored.
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
+ * matching node(s) or in assertions to validate that node(s) satisfy the condition.
+ *
+ * @param value value that should be substring of the content description set directly on the node.
+ * @param ignoreCase whether case should be ignored. Default is case sensitive.
*
* @see SemanticsProperties.ContentDescription
*/
@JvmOverloads
fun hasContentDescription(
value: String,
- substring: Boolean = false,
ignoreCase: Boolean = false
): GlanceNodeMatcher<MappedNode> =
GlanceNodeMatcher(
- description = if (substring) {
+ description =
"${SemanticsProperties.ContentDescription.name} contains '$value'" +
- " (ignoreCase: '$ignoreCase')"
- } else {
- "${SemanticsProperties.ContentDescription.name} = '$value' (ignoreCase: '$ignoreCase')"
- }
+ " (ignoreCase: '$ignoreCase') as substring"
) { node ->
node.value.emittable.modifier.any {
it is SemanticsModifier &&
- hasContentDescription(it, value, substring, ignoreCase)
+ hasContentDescription(
+ semanticsModifier = it,
+ value = value,
+ substring = true,
+ ignoreCase = ignoreCase)
+ }
+ }
+
+/**
+ * Returns whether the content description set directly on the node is equal to the provided
+ * [value].
+ *
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
+ * matching node(s) or in assertions to validate that node(s) satisfy the condition.
+ *
+ * @param value value that should match exactly with content description set directly on the node.
+ * @param ignoreCase whether case should be ignored. Default is case sensitive.
+ *
+ * @see SemanticsProperties.ContentDescription
+ */
+@JvmOverloads
+fun hasContentDescriptionEqualTo(
+ value: String,
+ ignoreCase: Boolean = false
+): GlanceNodeMatcher<MappedNode> =
+ GlanceNodeMatcher(
+ description =
+ "${SemanticsProperties.ContentDescription.name} == '$value' (ignoreCase: '$ignoreCase')"
+ ) { node ->
+ node.value.emittable.modifier.any {
+ it is SemanticsModifier &&
+ hasContentDescription(
+ semanticsModifier = it,
+ value = value,
+ substring = false,
+ ignoreCase = ignoreCase
+ )
}
}
@@ -105,43 +144,54 @@
}
/**
- * Returns a matcher that matches if text on node matches the provided text.
+ * Returns a matcher that matches if text on node contains the provided text as its substring.
*
- * This can be passed in "onNode" and "onNodeAll" functions on assertion providers to filter out
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
* matching node(s) or in assertions to validate that node(s) satisfy the condition.
*
- * @param text value to match.
- * @param substring whether to perform substring matching
- * @param ignoreCase whether to perform case insensitive matching
+ * @param text value that should be matched as a substring of the node's text.
+ * @param ignoreCase whether to perform case insensitive matching. Defaults to case sensitive
+ * matching.
*/
@JvmOverloads
fun hasText(
text: String,
- substring: Boolean = false,
ignoreCase: Boolean = false
): GlanceNodeMatcher<MappedNode> = GlanceNodeMatcher(
- if (substring) {
- "contains '$text' (ignoreCase: $ignoreCase) as substring"
- } else {
- "has text = '$text' (ignoreCase: '$ignoreCase')"
- }
+ description = "contains text '$text' (ignoreCase: '$ignoreCase') as substring"
) { node ->
val emittable = node.value.emittable
- if (emittable is EmittableWithText) {
- if (substring) {
- emittable.text.contains(text, ignoreCase)
- } else {
- emittable.text.equals(text, ignoreCase)
- }
- } else {
- false
- }
+ emittable is EmittableWithText && emittable.text.contains(text, ignoreCase)
+}
+
+/**
+ * Returns a matcher that matches if node is a text node and its text is equal to the provided text.
+ *
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
+ * matching node(s) or in assertions to validate that node(s) satisfy the condition.
+ *
+ * @param text value that should exactly match the node's text.
+ * @param ignoreCase whether to perform case insensitive matching. Defaults to case sensitive
+ * matching.
+ */
+@JvmOverloads
+fun hasTextEqualTo(
+ text: String,
+ ignoreCase: Boolean = false
+): GlanceNodeMatcher<MappedNode> = GlanceNodeMatcher(
+ description = "text == '$text' (ignoreCase: '$ignoreCase')"
+) { node ->
+ val emittable = node.value.emittable
+ emittable is EmittableWithText && emittable.text.equals(text, ignoreCase)
}
/**
* Returns a matcher that matches if the given node has clickable modifier set.
*
- * This can be passed in "onNode" and "onNodeAll" functions on assertion providers to filter out
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
* matching node(s) or in assertions to validate that node(s) satisfy the condition.
*/
fun hasClickAction(): GlanceNodeMatcher<MappedNode> = GlanceNodeMatcher(
@@ -156,7 +206,8 @@
* Returns a matcher that matches if the given node doesn't have a clickable modifier or `onClick`
* set.
*
- * This can be passed in "onNode" and "onNodeAll" functions on assertion providers to filter out
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
* matching node(s) or in assertions to validate that node(s) satisfy the condition.
*/
fun hasNoClickAction(): GlanceNodeMatcher<MappedNode> = GlanceNodeMatcher(
@@ -171,7 +222,8 @@
* Returns a matcher that matches if a given node has a clickable set with action that starts an
* activity.
*
- * This can be passed in "onNode" and "onNodeAll" functions on assertion providers to filter out
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
* matching node(s) or in assertions to validate that node(s) satisfy the condition.
*
* @param activityClass class of the activity that is expected to have been passed in the
@@ -213,10 +265,39 @@
}
/**
+ * Returns a matcher that matches if a given node has a descendant node somewhere in its
+ * sub-hierarchy that the matches the provided matcher.
+ *
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
+ * matching node(s) or in assertions to validate that node(s) satisfy the condition.
+ *
+ * @param matcher a matcher that needs to be satisfied for the descendant node to be matched
+ */
+fun hasAnyDescendant(matcher: GlanceNodeMatcher<MappedNode>): GlanceNodeMatcher<MappedNode> {
+
+ fun checkIfSubtreeMatchesRecursive(
+ matcher: GlanceNodeMatcher<MappedNode>,
+ node: GlanceNode<MappedNode>
+ ): Boolean {
+ if (matcher.matchesAny(node.children())) {
+ return true
+ }
+
+ return node.children().any { checkIfSubtreeMatchesRecursive(matcher, it) }
+ }
+
+ return GlanceNodeMatcher("hasAnyDescendantThat(${matcher.description})") {
+ checkIfSubtreeMatchesRecursive(matcher, it)
+ }
+}
+
+/**
* Returns a matcher that matches if a given node has a clickable set with action that starts an
* activity.
*
- * This can be passed in "onNode" and "onNodeAll" functions on assertion providers to filter out
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
* matching node(s) or in assertions to validate that node(s) satisfy the condition.
*
* @param componentName component of the activity that is expected to have been passed in the
diff --git a/glance/glance-testing/src/test/kotlin/androidx/glance/testing/AssertionErrorMessagesTest.kt b/glance/glance-testing/src/test/kotlin/androidx/glance/testing/AssertionErrorMessagesTest.kt
index e4bebe09..e82031c 100644
--- a/glance/glance-testing/src/test/kotlin/androidx/glance/testing/AssertionErrorMessagesTest.kt
+++ b/glance/glance-testing/src/test/kotlin/androidx/glance/testing/AssertionErrorMessagesTest.kt
@@ -24,11 +24,13 @@
class AssertionErrorMessagesTest {
@Test
fun countMismatch_expectedNone() {
- val resultMessage = buildErrorMessageForCountMismatch(
- errorMessage = "Failed assert",
- matcherDescription = "testTag = 'my-node'",
- expectedCount = 0,
- actualCount = 1
+ val resultMessage = buildErrorMessageWithReason(
+ errorMessageOnFail = "Failed assert",
+ reason = buildErrorReasonForCountMismatch(
+ matcherDescription = "testTag = 'my-node'",
+ expectedCount = 0,
+ actualCount = 1
+ )
)
assertThat(resultMessage).isEqualTo(
@@ -40,11 +42,13 @@
@Test
fun countMismatch_expectedButFoundNone() {
- val resultMessage = buildErrorMessageForCountMismatch(
- errorMessage = "Failed assert",
- matcherDescription = "testTag = 'my-node'",
- expectedCount = 2,
- actualCount = 0
+ val resultMessage = buildErrorMessageWithReason(
+ errorMessageOnFail = "Failed assert",
+ reason = buildErrorReasonForCountMismatch(
+ matcherDescription = "testTag = 'my-node'",
+ expectedCount = 2,
+ actualCount = 0
+ )
)
assertThat(resultMessage).isEqualTo(
@@ -55,14 +59,14 @@
}
@Test
- fun generalErrorMessage() {
+ fun generalErrorMessage_singleNode() {
val node = GlanceMappedNode(
EmittableText().also { it.text = "test text" }
)
val resultMessage = buildGeneralErrorMessage(
errorMessage = "Failed to match the condition: (testTag = 'my-node')",
- glanceNode = node
+ node = node
)
assertThat(resultMessage).isEqualTo(
@@ -70,4 +74,26 @@
"\nGlance Node: ${node.toDebugString()}"
)
}
+
+ @Test
+ fun generalErrorMessage_multipleNodes() {
+ val node1 = GlanceMappedNode(
+ EmittableText().also { it.text = "text1" }
+ )
+ val node2 = GlanceMappedNode(
+ EmittableText().also { it.text = "text2" }
+ )
+
+ val resultMessage = buildGeneralErrorMessage(
+ errorMessage = "Failed to match the condition: (testTag = 'my-node')",
+ nodes = listOf(node1, node2)
+ )
+
+ assertThat(resultMessage).isEqualTo(
+ "Failed to match the condition: (testTag = 'my-node')" +
+ "\nFound 2 node(s) that don't match." +
+ "\nNon-matching Glance Node #1: ${node1.toDebugString()}" +
+ "\nNon-matching Glance Node #2: ${node2.toDebugString()}"
+ )
+ }
}
diff --git a/glance/glance-testing/src/test/kotlin/androidx/glance/testing/GlanceNodeAssertionCollectionTest.kt b/glance/glance-testing/src/test/kotlin/androidx/glance/testing/GlanceNodeAssertionCollectionTest.kt
new file mode 100644
index 0000000..7d1202a
--- /dev/null
+++ b/glance/glance-testing/src/test/kotlin/androidx/glance/testing/GlanceNodeAssertionCollectionTest.kt
@@ -0,0 +1,600 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.glance.testing
+
+import androidx.glance.GlanceModifier
+import androidx.glance.action.ActionModifier
+import androidx.glance.action.LambdaAction
+import androidx.glance.layout.EmittableColumn
+import androidx.glance.layout.EmittableSpacer
+import androidx.glance.semantics.semantics
+import androidx.glance.semantics.testTag
+import androidx.glance.testing.unit.assertHasText
+import androidx.glance.testing.unit.getGlanceNodeAssertionCollectionFor
+import androidx.glance.testing.unit.getGlanceNodeAssertionFor
+import androidx.glance.testing.unit.hasClickAction
+import androidx.glance.testing.unit.hasTestTag
+import androidx.glance.testing.unit.hasText
+import androidx.glance.testing.unit.hasTextEqualTo
+import androidx.glance.text.EmittableText
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertThrows
+import org.junit.Test
+
+class GlanceNodeAssertionCollectionTest {
+ @Test
+ fun assertAll_noNodesToAssertOn() {
+ // This is the object that in real usage a onAllNodes(matcher) would return.
+ val assertion = getGlanceNodeAssertionCollectionFor(
+ onAllNodesMatcher = hasClickAction(),
+ emittable = EmittableColumn().apply {
+ children.add(EmittableText().apply { text = "some text" })
+ children.add(EmittableSpacer())
+ children.add(EmittableText().apply { text = "another text" })
+ }
+ )
+
+ assertion.assertAll(hasText("substring"))
+ // no error even if no nodes with click action were available to perform assertAll; on the
+ // other hand calling assertCountEquals(x) before assertAll would have thrown an error
+ }
+
+ @Test
+ fun assertAll_allMatchingNodes() {
+ val assertion = getGlanceNodeAssertionCollectionFor(
+ onAllNodesMatcher = hasClickAction(),
+ emittable = EmittableColumn().apply {
+ children.add(EmittableText().apply {
+ text = "some substring text"
+ modifier = ActionModifier(LambdaAction("1") {})
+ })
+ children.add(EmittableSpacer())
+ children.add(EmittableText().apply {
+ text = "another substring text"
+ modifier = ActionModifier(LambdaAction("2") {})
+ })
+ }
+ )
+
+ assertion.assertAll(hasText("substring"))
+ // no error
+ }
+
+ @Test
+ fun assertAll_noneMatch_throwsError() {
+ val assertion = getGlanceNodeAssertionCollectionFor(
+ onAllNodesMatcher = hasClickAction(),
+ emittable = EmittableColumn().apply {
+ children.add(EmittableText().apply {
+ text = "some text"
+ modifier = ActionModifier(LambdaAction("1") {})
+ })
+ children.add(EmittableSpacer())
+ children.add(EmittableText().apply {
+ text = "another text"
+ modifier = ActionModifier(LambdaAction("2") {})
+ })
+ }
+ )
+
+ val assertionError = assertThrows(AssertionError::class.java) {
+ assertion.assertAll(hasText("substring"))
+ }
+
+ assertThat(assertionError).hasMessageThat().contains(
+ "Failed to assertAll(contains text 'substring' (ignoreCase: 'false') as substring)" +
+ "\nFound 2 node(s) that don't match."
+ )
+ }
+
+ @Test
+ fun assertAll_someNotMatch_throwsError() {
+ val assertion = getGlanceNodeAssertionCollectionFor(
+ onAllNodesMatcher = hasClickAction(),
+ emittable = EmittableColumn().apply {
+ children.add(EmittableText().apply {
+ text = "some substring text"
+ modifier = ActionModifier(LambdaAction("1") {})
+ })
+ children.add(EmittableSpacer())
+ children.add(EmittableText().apply {
+ text = "another text"
+ modifier = ActionModifier(LambdaAction("2") {})
+ })
+ }
+ )
+
+ val assertionError = assertThrows(AssertionError::class.java) {
+ assertion.assertAll(hasText("substring"))
+ }
+
+ assertThat(assertionError).hasMessageThat().contains(
+ "Failed to assertAll(contains text 'substring' (ignoreCase: 'false') as substring)" +
+ "\nFound 1 node(s) that don't match."
+ )
+ }
+
+ @Test
+ fun assertAny_noNodesToAssertOn_assertionError() {
+ val assertion = getGlanceNodeAssertionCollectionFor(
+ onAllNodesMatcher = hasClickAction(),
+ emittable = EmittableColumn().apply {
+ children.add(EmittableText().apply { text = "some text" })
+ children.add(EmittableSpacer())
+ children.add(EmittableText().apply { text = "another text" })
+ }
+ )
+
+ val assertionError = assertThrows(AssertionError::class.java) {
+ assertion.assertAny(hasText("substring"))
+ }
+
+ assertThat(assertionError).hasMessageThat().contains(
+ "Failed to assertAny(contains text 'substring' (ignoreCase: 'false') as substring)" +
+ "\nReason: Expected to receive at least 1 node " +
+ "but 0 nodes were found for condition: (has click action)"
+ )
+ }
+
+ @Test
+ fun assertAny_noneMatch_assertionError() {
+ val assertion = getGlanceNodeAssertionCollectionFor(
+ onAllNodesMatcher = hasClickAction(),
+ emittable = EmittableColumn().apply {
+ children.add(EmittableText().apply {
+ text = "some text"
+ modifier = ActionModifier(LambdaAction("1") {})
+ })
+ children.add(EmittableSpacer())
+ children.add(EmittableText().apply {
+ text = "another text"
+ modifier = ActionModifier(LambdaAction("2") {})
+ })
+ }
+ )
+
+ val assertionError = assertThrows(AssertionError::class.java) {
+ assertion.assertAny(hasText("expected-substring"))
+ }
+
+ assertThat(assertionError).hasMessageThat().contains(
+ "Failed to assertAny(contains text 'expected-substring' " +
+ "(ignoreCase: 'false') as substring)" +
+ "\nFound 2 node(s) that don't match."
+ )
+ }
+
+ @Test
+ fun assertAny_oneMatch() {
+ val assertion = getGlanceNodeAssertionCollectionFor(
+ onAllNodesMatcher = hasClickAction(),
+ emittable = EmittableColumn().apply {
+ children.add(EmittableText().apply {
+ text = "some text"
+ modifier = ActionModifier(LambdaAction("1") {})
+ })
+ children.add(EmittableSpacer())
+ children.add(EmittableText().apply {
+ text = "another substring text"
+ modifier = ActionModifier(LambdaAction("2") {})
+ })
+ }
+ )
+
+ assertion.assertAny(hasText("substring"))
+ }
+
+ @Test
+ fun assertAny_multipleMatch() {
+ val assertion = getGlanceNodeAssertionCollectionFor(
+ onAllNodesMatcher = hasClickAction(),
+ emittable = EmittableColumn().apply {
+ children.add(EmittableText().apply {
+ text = "some text"
+ modifier = ActionModifier(LambdaAction("1") {})
+ })
+ children.add(EmittableSpacer())
+ children.add(EmittableText().apply {
+ text = "another substring text"
+ modifier = ActionModifier(LambdaAction("2") {})
+ })
+ children.add(EmittableSpacer())
+ children.add(EmittableText().apply {
+ text = "yet another substring text"
+ modifier = ActionModifier(LambdaAction("3") {})
+ })
+ }
+ )
+
+ assertion.assertAny(hasText("substring"))
+ }
+
+ @Test
+ fun assertAllAfterFilter_matchingNodes() {
+ val assertion = getGlanceNodeAssertionCollectionFor(
+ onAllNodesMatcher = hasClickAction(),
+ emittable = EmittableColumn().apply {
+ children.add(EmittableText().apply {
+ text = "some substring text"
+ modifier = ActionModifier(LambdaAction("1") {})
+ })
+ children.add(EmittableSpacer())
+ children.add(EmittableText().apply {
+ text = "another text"
+ modifier = ActionModifier(LambdaAction("2") {})
+ })
+ children.add(EmittableSpacer())
+ children.add(EmittableText().apply {
+ text = "yet another substring text"
+ modifier = ActionModifier(LambdaAction("3") {})
+ })
+ }
+ )
+
+ assertion
+ .filter(hasText("substring"))
+ .assertAll(hasText("text"))
+ }
+
+ @Test
+ fun assertAllAfterFilter_noFilteredNodes_noError() {
+ val assertion = getGlanceNodeAssertionCollectionFor(
+ onAllNodesMatcher = hasClickAction(),
+ emittable = EmittableColumn().apply {
+ children.add(EmittableText().apply {
+ text = "some substring text"
+ modifier = ActionModifier(LambdaAction("1") {})
+ })
+ children.add(EmittableSpacer())
+ children.add(EmittableText().apply {
+ text = "another text"
+ modifier = ActionModifier(LambdaAction("2") {})
+ })
+ children.add(EmittableSpacer())
+ children.add(EmittableText().apply {
+ text = "yet another substring text"
+ modifier = ActionModifier(LambdaAction("3") {})
+ })
+ }
+ )
+
+ assertion
+ .filter(hasText("word"))
+ .assertAll(hasText("text"))
+ }
+
+ @Test
+ fun assertAnyAfterFilter_matchingNodes() {
+ val assertion = getGlanceNodeAssertionCollectionFor(
+ onAllNodesMatcher = hasClickAction(),
+ emittable = EmittableColumn().apply {
+ children.add(EmittableText().apply {
+ text = "some substring"
+ modifier = ActionModifier(LambdaAction("1") {})
+ })
+ children.add(EmittableSpacer())
+ children.add(EmittableText().apply {
+ text = "another text"
+ modifier = ActionModifier(LambdaAction("2") {})
+ })
+ children.add(EmittableSpacer())
+ children.add(EmittableText().apply {
+ text = "yet another substring text"
+ modifier = ActionModifier(LambdaAction("3") {})
+ })
+ }
+ )
+
+ assertion
+ .filter(hasText("substring"))
+ .assertAny(hasText("text"))
+ }
+
+ @Test
+ fun assertAnyAfterFilter_noFilteredNodes_assertionError() {
+ val assertion = getGlanceNodeAssertionCollectionFor(
+ onAllNodesMatcher = hasClickAction(),
+ emittable = EmittableColumn().apply {
+ children.add(EmittableText().apply {
+ text = "some substring text"
+ modifier = ActionModifier(LambdaAction("1") {})
+ })
+ children.add(EmittableSpacer())
+ children.add(EmittableText().apply {
+ text = "another text"
+ modifier = ActionModifier(LambdaAction("2") {})
+ })
+ children.add(EmittableSpacer())
+ children.add(EmittableText().apply {
+ text = "yet another substring text"
+ modifier = ActionModifier(LambdaAction("3") {})
+ })
+ }
+ )
+
+ val assertionError = assertThrows(AssertionError::class.java) {
+ assertion
+ .filter(hasText("word"))
+ .assertAny(hasText("text"))
+ }
+
+ assertThat(assertionError).hasMessageThat().contains(
+ "Failed to assertAny(contains text 'text' (ignoreCase: 'false') as substring)" +
+ "\nReason: Expected to receive at least 1 node but 0 nodes were found for " +
+ "condition: " +
+ "((has click action).filter(contains text 'word' (ignoreCase: 'false') " +
+ "as substring))"
+ )
+ }
+
+ @Test
+ fun assertCountEqualsAfterFilter_matchingNodes() {
+ val assertion = getGlanceNodeAssertionCollectionFor(
+ onAllNodesMatcher = hasClickAction(),
+ emittable = EmittableColumn().apply {
+ children.add(EmittableText().apply {
+ text = "some substring text"
+ modifier = ActionModifier(LambdaAction("1") {})
+ })
+ children.add(EmittableSpacer())
+ children.add(EmittableText().apply {
+ text = "another text"
+ modifier = ActionModifier(LambdaAction("2") {})
+ })
+ children.add(EmittableSpacer())
+ children.add(EmittableText().apply {
+ text = "yet another substring text"
+ modifier = ActionModifier(LambdaAction("3") {})
+ })
+ }
+ )
+
+ assertion
+ .filter(hasText("text"))
+ .assertCountEquals(3)
+ }
+
+ @Test
+ fun assertCountEqualsAfterFilter_noFilteredNodes_assertionError() {
+ val assertion = getGlanceNodeAssertionCollectionFor(
+ onAllNodesMatcher = hasClickAction(),
+ emittable = EmittableColumn().apply {
+ children.add(EmittableText().apply {
+ text = "some substring text"
+ modifier = ActionModifier(LambdaAction("1") {})
+ })
+ children.add(EmittableSpacer())
+ children.add(EmittableText().apply {
+ text = "another text"
+ modifier = ActionModifier(LambdaAction("2") {})
+ })
+ children.add(EmittableSpacer())
+ children.add(EmittableText().apply {
+ text = "yet another substring text"
+ modifier = ActionModifier(LambdaAction("3") {})
+ })
+ }
+ )
+
+ val assertionError = assertThrows(AssertionError::class.java) {
+ assertion
+ .filter(hasText("word"))
+ .assertCountEquals(1)
+ }
+
+ assertThat(assertionError).hasMessageThat().contains(
+ "Failed to assert count of nodes" +
+ "\nReason: Expected '1' node(s) matching condition: " +
+ "(has click action).filter(contains text 'word' " +
+ "(ignoreCase: 'false') as substring), " +
+ "but found '0'"
+ )
+ }
+
+ @Test
+ fun multipleFilters() {
+ val assertion = getGlanceNodeAssertionCollectionFor(
+ onAllNodesMatcher = hasClickAction(),
+ emittable = EmittableColumn().apply {
+ children.add(EmittableText().apply {
+ text = "some text"
+ modifier = ActionModifier(LambdaAction("1") {})
+ })
+ children.add(EmittableSpacer())
+ children.add(EmittableText().apply {
+ text = "another substring"
+ modifier = ActionModifier(LambdaAction("2") {})
+ })
+ children.add(EmittableSpacer())
+ children.add(EmittableText().apply {
+ text = "yet another substring text"
+ modifier = ActionModifier(LambdaAction("3") {})
+ })
+ }
+ )
+
+ assertion
+ .filter(hasText("text"))
+ .assertCountEquals(2)
+ .filter(hasText("substring"))
+ .assertCountEquals(1)
+ }
+
+ @Test
+ fun getIndexOnFilter() {
+ val assertion = getGlanceNodeAssertionCollectionFor(
+ onAllNodesMatcher = hasClickAction(),
+ emittable = EmittableColumn().apply {
+ children.add(EmittableText().apply {
+ text = "some text"
+ modifier = ActionModifier(LambdaAction("1") {})
+ })
+ children.add(EmittableSpacer())
+ children.add(EmittableText().apply {
+ text = "another substring"
+ modifier = ActionModifier(LambdaAction("2") {})
+ })
+ children.add(EmittableSpacer())
+ children.add(EmittableText().apply {
+ text = "yet another substring text"
+ modifier = ActionModifier(LambdaAction("3") {})
+ })
+ }
+ )
+
+ assertion
+ .filter(hasText("text"))
+ .assertCountEquals(2)
+ .get(index = 1)
+ .assert(hasTextEqualTo("yet another substring text"))
+ }
+
+ @Test
+ fun collectionGetIndex_notEnoughNodes_assertionError() {
+ val assertion = getGlanceNodeAssertionCollectionFor(
+ onAllNodesMatcher = hasClickAction(),
+ emittable = EmittableColumn().apply {
+ children.add(EmittableText().apply {
+ text = "some text"
+ modifier = ActionModifier(LambdaAction("1") {})
+ })
+ children.add(EmittableSpacer())
+ children.add(EmittableText().apply {
+ text = "another substring"
+ modifier = ActionModifier(LambdaAction("2") {})
+ })
+ children.add(EmittableSpacer())
+ children.add(EmittableText().apply {
+ text = "yet another substring text"
+ modifier = ActionModifier(LambdaAction("3") {})
+ })
+ }
+ )
+
+ val assertionError = assertThrows(AssertionError::class.java) {
+ assertion
+ .filter(hasText("text"))
+ .assertCountEquals(2)
+ .get(index = 3) // index out of bounds.
+ .assert(hasTextEqualTo("yet another substring text"))
+ }
+
+ assertThat(assertionError).hasMessageThat().contains(
+ "Failed to assert condition: " +
+ "(text == 'yet another substring text' (ignoreCase: 'false'))" +
+ "\nReason: Not enough node(s) matching condition: " +
+ "((has click action).filter(contains text 'text' " +
+ "(ignoreCase: 'false') as substring)) " +
+ "to get node at index '4'. Found '2' matching node(s)"
+ )
+ }
+
+ @Test
+ fun assertOnChildren_multipleChildren() {
+ val assertion = getGlanceNodeAssertionFor(
+ onNodeMatcher = hasTestTag("test-list"),
+ emittable = EmittableColumn().apply {
+ modifier = GlanceModifier.semantics { testTag = "test-list" }
+ children.add(EmittableText().apply {
+ text = "some text"
+ modifier = ActionModifier(LambdaAction("1") {})
+ })
+ children.add(EmittableText().apply {
+ text = "another substring"
+ modifier = ActionModifier(LambdaAction("2") {})
+ })
+ }
+ )
+
+ assertion
+ .onChildren()
+ .assertCountEquals(2)
+ .assertAll(hasClickAction())
+ }
+
+ @Test
+ fun assertOnChildren_noChildren() {
+ val assertion = getGlanceNodeAssertionFor(
+ onNodeMatcher = hasTestTag("test-list"),
+ emittable = EmittableColumn().apply {
+ modifier = GlanceModifier.semantics { testTag = "test-list" }
+ }
+ )
+
+ assertion.onChildren().assertCountEquals(0)
+ }
+
+ @Test
+ fun assertAnyOnChildren_noChildren_assertionError() {
+ val assertion = getGlanceNodeAssertionFor(
+ onNodeMatcher = hasTestTag("test-list"),
+ emittable = EmittableColumn().apply {
+ modifier = GlanceModifier.semantics { testTag = "test-list" }
+ }
+ )
+
+ val assertionError = assertThrows(AssertionError::class.java) {
+ assertion.onChildren().assertCountEquals(1)
+ }
+
+ assertThat(assertionError).hasMessageThat().contains(
+ "Failed to assert count of nodes" +
+ "\nReason: Expected '1' node(s) matching condition: " +
+ "(TestTag = 'test-list').children(), but found '0'"
+ )
+ }
+
+ @Test
+ fun filterOnChildren() {
+ val assertion = getGlanceNodeAssertionFor(
+ onNodeMatcher = hasTestTag("test-list"),
+ emittable = EmittableColumn().apply {
+ modifier = GlanceModifier.semantics { testTag = "test-list" }
+ children.add(EmittableText().apply {
+ text = "some text"
+ modifier = ActionModifier(LambdaAction("1") {})
+ })
+ children.add(EmittableText().apply {
+ text = "another substring"
+ modifier = ActionModifier(LambdaAction("2") {})
+ })
+ }
+ )
+
+ assertion
+ .onChildren()
+ .filter(hasText("substring"))
+ .assertCountEquals(1)
+ }
+
+ @Test
+ fun getIndexOnChildren() {
+ val assertion = getGlanceNodeAssertionFor(
+ onNodeMatcher = hasTestTag("test-list"),
+ emittable = EmittableColumn().apply {
+ modifier = GlanceModifier.semantics { testTag = "test-list" }
+ children.add(EmittableText().apply { text = "text-1" })
+ children.add(EmittableText().apply { text = "text-2" })
+ }
+ )
+
+ assertion
+ .onChildren()
+ .get(index = 0)
+ .assertHasText("text-1")
+ }
+}
diff --git a/glance/glance-testing/src/test/kotlin/androidx/glance/testing/GlanceNodeAssertionTest.kt b/glance/glance-testing/src/test/kotlin/androidx/glance/testing/GlanceNodeAssertionTest.kt
index c27c769..6718627 100644
--- a/glance/glance-testing/src/test/kotlin/androidx/glance/testing/GlanceNodeAssertionTest.kt
+++ b/glance/glance-testing/src/test/kotlin/androidx/glance/testing/GlanceNodeAssertionTest.kt
@@ -21,8 +21,7 @@
import androidx.glance.layout.EmittableSpacer
import androidx.glance.semantics.semantics
import androidx.glance.semantics.testTag
-import androidx.glance.testing.unit.GlanceMappedNode
-import androidx.glance.testing.unit.MappedNode
+import androidx.glance.testing.unit.getGlanceNodeAssertionFor
import androidx.glance.testing.unit.hasTestTag
import androidx.glance.testing.unit.hasText
import androidx.glance.text.EmittableText
@@ -35,22 +34,17 @@
class GlanceNodeAssertionTest {
@Test
fun assertExists_success() {
- val testContext = TestContext<MappedNode, GlanceMappedNode>()
- // set root node of test tree to be traversed
- testContext.rootGlanceNode = GlanceMappedNode(
- EmittableColumn().apply {
+ // This is the object that in real usage a rule.onNode(matcher) would return.
+ val assertion = getGlanceNodeAssertionFor(
+ emittable = EmittableColumn().apply {
children.add(EmittableText().apply { text = "some text" })
children.add(EmittableSpacer())
children.add(EmittableText().apply {
text = "another text"
modifier = GlanceModifier.semantics { testTag = "existing-test-tag" }
})
- }
- )
- // This is the object that in real usage a rule.onNode(matcher) would return.
- val assertion = GlanceNodeAssertion(
- matcher = hasTestTag(testTag = "existing-test-tag"),
- testContext = testContext
+ },
+ onNodeMatcher = hasTestTag(testTag = "existing-test-tag"),
)
assertion.assertExists()
@@ -59,23 +53,16 @@
@Test
fun assertExists_error() {
- val testContext = TestContext<MappedNode, GlanceMappedNode>()
- // set root node of test tree to be traversed
- testContext.rootGlanceNode =
- GlanceMappedNode(
- EmittableColumn().apply {
- children.add(EmittableText().apply { text = "some text" })
- children.add(EmittableSpacer())
- children.add(EmittableText().apply {
- text = "another text"
- modifier = GlanceModifier.semantics { testTag = "existing-test-tag" }
- })
- }
- )
- // This is the object that in real usage a rule.onNode(matcher) would return.
- val assertion = GlanceNodeAssertion(
- matcher = hasTestTag(testTag = "non-existing-test-tag"),
- testContext = testContext
+ val assertion = getGlanceNodeAssertionFor(
+ emittable = EmittableColumn().apply {
+ children.add(EmittableText().apply { text = "some text" })
+ children.add(EmittableSpacer())
+ children.add(EmittableText().apply {
+ text = "another text"
+ modifier = GlanceModifier.semantics { testTag = "existing-test-tag" }
+ })
+ },
+ onNodeMatcher = hasTestTag(testTag = "non-existing-test-tag")
)
val assertionError = assertThrows(AssertionError::class.java) {
@@ -91,24 +78,16 @@
@Test
fun assertDoesNotExist_success() {
- val testContext = TestContext<MappedNode, GlanceMappedNode>()
- // set root node of test tree to be traversed
- testContext.rootGlanceNode =
- GlanceMappedNode(
- EmittableColumn().apply {
- children.add(EmittableText().apply { text = "some text" })
- children.add(EmittableSpacer())
- children.add(EmittableText().apply {
- text = "another text"
- modifier = GlanceModifier.semantics { testTag = "existing-test-tag" }
- })
- }
- )
-
- // This is the object that in real usage a rule.onNode(matcher) would return.
- val assertion = GlanceNodeAssertion(
- matcher = hasTestTag(testTag = "non-existing-test-tag"),
- testContext = testContext
+ val assertion = getGlanceNodeAssertionFor(
+ emittable = EmittableColumn().apply {
+ children.add(EmittableText().apply { text = "some text" })
+ children.add(EmittableSpacer())
+ children.add(EmittableText().apply {
+ text = "another text"
+ modifier = GlanceModifier.semantics { testTag = "existing-test-tag" }
+ })
+ },
+ onNodeMatcher = hasTestTag(testTag = "non-existing-test-tag")
)
assertion.assertDoesNotExist()
@@ -117,23 +96,16 @@
@Test
fun assertDoesNotExist_error() {
- val testContext = TestContext<MappedNode, GlanceMappedNode>()
- // set root node of test tree to be traversed
- testContext.rootGlanceNode =
- GlanceMappedNode(
- EmittableColumn().apply {
- children.add(EmittableText().apply { text = "some text" })
- children.add(EmittableSpacer())
- children.add(EmittableText().apply {
- text = "another text"
- modifier = GlanceModifier.semantics { testTag = "existing-test-tag" }
- })
- }
- )
- // This is the object that in real usage a rule.onNode(matcher) would return.
- val assertion = GlanceNodeAssertion(
- matcher = hasTestTag(testTag = "existing-test-tag"),
- testContext = testContext
+ val assertion = getGlanceNodeAssertionFor(
+ emittable = EmittableColumn().apply {
+ children.add(EmittableText().apply { text = "some text" })
+ children.add(EmittableSpacer())
+ children.add(EmittableText().apply {
+ text = "another text"
+ modifier = GlanceModifier.semantics { testTag = "existing-test-tag" }
+ })
+ },
+ onNodeMatcher = hasTestTag(testTag = "existing-test-tag")
)
val assertionError = assertThrows(AssertionError::class.java) {
@@ -149,23 +121,16 @@
@Test
fun assert_withMatcher_success() {
- val testContext = TestContext<MappedNode, GlanceMappedNode>()
- // set root node of test tree to be traversed
- testContext.rootGlanceNode =
- GlanceMappedNode(
- EmittableColumn().apply {
- children.add(EmittableText().apply { text = "some text" })
- children.add(EmittableSpacer())
- children.add(EmittableText().apply {
- text = "another text"
- modifier = GlanceModifier.semantics { testTag = "existing-test-tag" }
- })
- }
- )
- // This is the object that in real usage a rule.onNode(matcher) would return.
- val assertion = GlanceNodeAssertion(
- matcher = hasTestTag(testTag = "existing-test-tag"),
- testContext = testContext
+ val assertion = getGlanceNodeAssertionFor(
+ emittable = EmittableColumn().apply {
+ children.add(EmittableText().apply { text = "some text" })
+ children.add(EmittableSpacer())
+ children.add(EmittableText().apply {
+ text = "another text"
+ modifier = GlanceModifier.semantics { testTag = "existing-test-tag" }
+ })
+ },
+ onNodeMatcher = hasTestTag(testTag = "existing-test-tag")
)
assertion.assert(hasText(text = "another text"))
@@ -174,24 +139,16 @@
@Test
fun chainAssertions() {
- val testContext = TestContext<MappedNode, GlanceMappedNode>()
- // set root node of test tree to be traversed
- testContext.rootGlanceNode =
- GlanceMappedNode(
- MappedNode(
- EmittableColumn().apply {
- children.add(EmittableText().apply { text = "some text" })
- children.add(EmittableSpacer())
- children.add(EmittableText().apply {
- text = "another text"
- modifier = GlanceModifier.semantics { testTag = "existing-test-tag" }
- })
- })
- )
- // This is the object that in real usage a rule.onNode(matcher) would return.
- val assertion = GlanceNodeAssertion(
- matcher = hasTestTag(testTag = "existing-test-tag"),
- testContext = testContext
+ val assertion = getGlanceNodeAssertionFor(
+ emittable = EmittableColumn().apply {
+ children.add(EmittableText().apply { text = "some text" })
+ children.add(EmittableSpacer())
+ children.add(EmittableText().apply {
+ text = "another text"
+ modifier = GlanceModifier.semantics { testTag = "existing-test-tag" }
+ })
+ },
+ onNodeMatcher = hasTestTag(testTag = "existing-test-tag")
)
assertion
@@ -202,25 +159,16 @@
@Test
fun chainAssertion_failureInFirst() {
- val testContext = TestContext<MappedNode, GlanceMappedNode>()
- // set root node of test tree to be traversed
- testContext.rootGlanceNode =
- GlanceMappedNode(
- MappedNode(
- EmittableColumn().apply {
- children.add(EmittableText().apply { text = "some text" })
- children.add(EmittableSpacer())
- children.add(EmittableText().apply {
- text = "another text"
- modifier = GlanceModifier.semantics { testTag = "existing-test-tag" }
- })
- }
- )
- )
- // This is the object that in real usage a rule.onNode(matcher) would return.
- val assertion = GlanceNodeAssertion(
- matcher = hasTestTag(testTag = "existing-test-tag"),
- testContext = testContext
+ val assertion = getGlanceNodeAssertionFor(
+ emittable = EmittableColumn().apply {
+ children.add(EmittableText().apply { text = "some text" })
+ children.add(EmittableSpacer())
+ children.add(EmittableText().apply {
+ text = "another text"
+ modifier = GlanceModifier.semantics { testTag = "existing-test-tag" }
+ })
+ },
+ onNodeMatcher = hasTestTag(testTag = "existing-test-tag")
)
val assertionError = assertThrows(AssertionError::class.java) {
@@ -240,25 +188,16 @@
@Test
fun chainAssertion_failureInSecond() {
- val testContext = TestContext<MappedNode, GlanceMappedNode>()
- // set root node of test tree to be traversed
- testContext.rootGlanceNode =
- GlanceMappedNode(
- MappedNode(
- EmittableColumn().apply {
- children.add(EmittableText().apply { text = "some text" })
- children.add(EmittableSpacer())
- children.add(EmittableText().apply {
- text = "another text"
- modifier = GlanceModifier.semantics { testTag = "existing-test-tag" }
- })
- }
- )
- )
- // This is the object that in real usage a rule.onNode(matcher) would return.
- val assertion = GlanceNodeAssertion(
- matcher = hasTestTag(testTag = "existing-test-tag"),
- testContext = testContext
+ val assertion = getGlanceNodeAssertionFor(
+ emittable = EmittableColumn().apply {
+ children.add(EmittableText().apply { text = "some text" })
+ children.add(EmittableSpacer())
+ children.add(EmittableText().apply {
+ text = "another text"
+ modifier = GlanceModifier.semantics { testTag = "existing-test-tag" }
+ })
+ },
+ onNodeMatcher = hasTestTag(testTag = "existing-test-tag")
)
val assertionError = assertThrows(AssertionError::class.java) {
@@ -271,7 +210,7 @@
.hasMessageThat()
.startsWith(
"Failed to assert condition: " +
- "(has text = 'non-existing text' (ignoreCase: 'false'))" +
+ "(contains text 'non-existing text' (ignoreCase: 'false') as substring)" +
"\nGlance Node:"
)
}
diff --git a/glance/glance-testing/src/test/kotlin/androidx/glance/testing/unit/GlanceMappedNodeMatcherTest.kt b/glance/glance-testing/src/test/kotlin/androidx/glance/testing/unit/GlanceMappedNodeMatcherTest.kt
new file mode 100644
index 0000000..880393f
--- /dev/null
+++ b/glance/glance-testing/src/test/kotlin/androidx/glance/testing/unit/GlanceMappedNodeMatcherTest.kt
@@ -0,0 +1,333 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.glance.testing.unit
+
+import androidx.glance.GlanceModifier
+import androidx.glance.layout.EmittableColumn
+import androidx.glance.semantics.semantics
+import androidx.glance.semantics.testTag
+import androidx.glance.text.EmittableText
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+class GlanceMappedNodeMatcherTest {
+ @Test
+ fun matchAny_match_returnsTrue() {
+ val node1 = GlanceMappedNode(
+ EmittableText().apply {
+ text = "node1"
+ }
+ )
+ val node2 = GlanceMappedNode(
+ EmittableColumn().apply {
+ modifier = GlanceModifier.semantics { testTag = "existing-test-tag" }
+ EmittableText().apply { text = "node2" }
+ }
+ )
+
+ val result = hasTestTag("existing-test-tag").matchesAny(listOf(node1, node2))
+
+ assertThat(result).isTrue()
+ }
+
+ @Test
+ fun matchAny_noMatch_returnsFalse() {
+ val node1 = GlanceMappedNode(
+ EmittableText().apply {
+ text = "node1"
+ }
+ )
+ val node2 = GlanceMappedNode(
+ EmittableColumn().apply {
+ children += EmittableText().apply {
+ text = "node2"
+ // this won't be inspected, as EmittableColumn node is being run against
+ // matcher, not its children
+ modifier = GlanceModifier.semantics { testTag = "existing-test-tag" }
+ }
+ }
+ )
+
+ val result = hasTestTag("existing-test-tag").matchesAny(listOf(node1, node2))
+
+ assertThat(result).isFalse()
+ }
+
+ @Test
+ fun andBetweenMatchers_match_returnsTrue() {
+ val node = GlanceMappedNode(
+ EmittableText().apply {
+ text = "node"
+ modifier = GlanceModifier.semantics { testTag = "test-tag" }
+ }
+ )
+
+ val result = (hasTestTag("test-tag") and hasText("node")).matches(node)
+
+ assertThat(result).isTrue()
+ }
+
+ @Test
+ fun andBetweenMatchers_partialMatch_returnsFalse() {
+ val node = GlanceMappedNode(
+ EmittableText().apply {
+ text = "node"
+ modifier = GlanceModifier.semantics { testTag = "test-tag" }
+ }
+ )
+
+ val result = (hasTestTag("test-tag") and hasText("non-existing-node"))
+ .matches(node)
+
+ assertThat(result).isFalse()
+ }
+
+ @Test
+ fun andBetweenMatchers_noMatch_returnsFalse() {
+ val node = GlanceMappedNode(
+ EmittableText().apply {
+ text = "node"
+ modifier = GlanceModifier.semantics { testTag = "test-tag" }
+ }
+ )
+
+ val result = (hasTestTag("non-existing") and hasText("non-existing-node"))
+ .matches(node)
+
+ assertThat(result).isFalse()
+ }
+
+ @Test
+ fun orBetweenMatchers_bothMatch_returnsTrue() {
+ val node = GlanceMappedNode(
+ EmittableText().apply {
+ text = "node"
+ modifier = GlanceModifier.semantics { testTag = "test-tag" }
+ }
+ )
+
+ val result = (hasTestTag("test-tag") or hasText("node"))
+ .matches(node)
+
+ assertThat(result).isTrue()
+ }
+
+ @Test
+ fun orBetweenMatchers_secondMatch_returnsTrue() {
+ val node = GlanceMappedNode(
+ EmittableText().apply {
+ text = "node"
+ modifier = GlanceModifier.semantics { testTag = "test-tag" }
+ }
+ )
+
+ val result = (hasTestTag("non-existing-tag") or hasText("node"))
+ .matches(node)
+
+ assertThat(result).isTrue()
+ }
+
+ @Test
+ fun orBetweenMatchers_noneMatch_returnsFalse() {
+ val node = GlanceMappedNode(
+ EmittableText().apply {
+ text = "node"
+ modifier = GlanceModifier.semantics { testTag = "test-tag" }
+ }
+ )
+
+ val result = (hasTestTag("non-existing-tag") or hasText("non-existing-node"))
+ .matches(node)
+
+ assertThat(result).isFalse()
+ }
+
+ @Test
+ fun not_match_returnsTrue() {
+ val node = GlanceMappedNode(
+ EmittableText().apply {
+ text = "node"
+ modifier = GlanceModifier.semantics { testTag = "test-tag" }
+ }
+ )
+
+ val result = (!hasTestTag("non-existing-test-tag")).matches(node)
+
+ assertThat(result).isTrue()
+ }
+
+ @Test
+ fun not_noMatch_returnsFalse() {
+ val node = GlanceMappedNode(
+ EmittableText().apply {
+ text = "node"
+ modifier = GlanceModifier.semantics { testTag = "test-tag" }
+ }
+ )
+
+ val result = (!hasTestTag("test-tag")).matches(node)
+
+ assertThat(result).isFalse()
+ }
+
+ @Test
+ fun hasTestTag_match_returnsTrue() {
+ // a single node that will be matched against matcher returned by the filter under test
+ val testSingleNode = GlanceMappedNode(
+ EmittableText().apply {
+ text = "some text"
+ modifier = GlanceModifier.semantics { testTag = "existing-test-tag" }
+ }
+ )
+
+ val result = hasTestTag("existing-test-tag").matches(testSingleNode)
+
+ assertThat(result).isTrue()
+ }
+
+ @Test
+ fun hasTestTag_noMatch_returnsFalse() {
+ val testSingleNode = GlanceMappedNode(
+ EmittableText().apply {
+ text = "some text"
+ modifier = GlanceModifier.semantics { testTag = "existing-test-tag" }
+ }
+ )
+
+ val result = hasTestTag("non-existing-test-tag").matches(testSingleNode)
+
+ assertThat(result).isFalse()
+ }
+
+ @Test
+ fun hasTextEqualTo_match_returnsTrue() {
+ val testSingleNode = GlanceMappedNode(
+ EmittableText().apply {
+ text = "existing text"
+ }
+ )
+
+ val result = hasTextEqualTo("existing text").matches(testSingleNode)
+
+ assertThat(result).isTrue()
+ }
+
+ @Test
+ fun hasTextEqualTo_noMatch_returnsFalse() {
+ val testSingleNode = GlanceMappedNode(
+ EmittableText().apply {
+ text = "existing text"
+ }
+ )
+
+ val result = hasTextEqualTo("non-existing text").matches(testSingleNode)
+
+ assertThat(result).isFalse()
+ }
+
+ @Test
+ fun hasTextEqualTo_caseInsensitiveMatch_returnsTrue() {
+ val testSingleNode = GlanceMappedNode(
+ EmittableText().apply {
+ text = "some EXISTING text"
+ }
+ )
+
+ val result =
+ hasTextEqualTo(
+ text = "SOME existing TEXT",
+ ignoreCase = true
+ ).matches(testSingleNode)
+
+ assertThat(result).isTrue()
+ }
+
+ @Test
+ fun hasTextEqualTo_caseInsensitiveButNoMatch_returnsFalse() {
+ val testSingleNode = GlanceMappedNode(
+ EmittableText().apply {
+ text = "some EXISTING text"
+ }
+ )
+
+ val result =
+ hasTextEqualTo(
+ text = "SOME non-existing TEXT",
+ ignoreCase = true
+ ).matches(testSingleNode)
+
+ assertThat(result).isFalse()
+ }
+
+ @Test
+ fun hasText_match_returnsTrue() {
+ val testSingleNode = GlanceMappedNode(
+ EmittableText().apply {
+ text = "some existing text"
+ }
+ )
+
+ val result = hasText("existing").matches(testSingleNode)
+
+ assertThat(result).isTrue()
+ }
+
+ @Test
+ fun hasText_noMatch_returnsFalse() {
+ val testSingleNode = GlanceMappedNode(
+ EmittableText().apply {
+ text = "some existing text"
+ }
+ )
+
+ val result = hasText("non-existing").matches(testSingleNode)
+
+ assertThat(result).isFalse()
+ }
+
+ @Test
+ fun hasText_insensitiveMatch_returnsTrue() {
+ val testSingleNode = GlanceMappedNode(
+ EmittableText().apply {
+ text = "some EXISTING text"
+ }
+ )
+
+ val result = hasText(
+ text = "existing",
+ ignoreCase = true
+ ).matches(testSingleNode)
+
+ assertThat(result).isTrue()
+ }
+
+ @Test
+ fun hasText_caseInsensitiveButNoMatch_returnsFalse() {
+ val testSingleNode = GlanceMappedNode(
+ EmittableText().apply {
+ text = "some EXISTING text"
+ }
+ )
+
+ val result = hasText(
+ text = "non-EXISTING",
+ ignoreCase = true
+ ).matches(testSingleNode)
+
+ assertThat(result).isFalse()
+ }
+}
diff --git a/glance/glance-testing/src/test/kotlin/androidx/glance/testing/unit/UnitTestAssertionExtensionsTest.kt b/glance/glance-testing/src/test/kotlin/androidx/glance/testing/unit/UnitTestAssertionExtensionsTest.kt
index b404ec7..d4800b2 100644
--- a/glance/glance-testing/src/test/kotlin/androidx/glance/testing/unit/UnitTestAssertionExtensionsTest.kt
+++ b/glance/glance-testing/src/test/kotlin/androidx/glance/testing/unit/UnitTestAssertionExtensionsTest.kt
@@ -30,6 +30,162 @@
// and relevant to unit tests
class UnitTestAssertionExtensionsTest {
@Test
+ fun assertHasTextEqualTo_matching() {
+ val nodeAssertion = getGlanceNodeAssertionFor(
+ emittable = EmittableColumn().apply {
+ children.add(EmittableText().apply {
+ text = "test text"
+ modifier = GlanceModifier.semantics { testTag = "test-tag" }
+ })
+ },
+ onNodeMatcher = hasTestTag("test-tag")
+ )
+
+ nodeAssertion.assertHasTextEqualTo("test text")
+ }
+
+ @Test
+ fun assertHasTextEqualTo_ignoreCase_matching() {
+ val nodeAssertion = getGlanceNodeAssertionFor(
+ emittable = EmittableColumn().apply {
+ children.add(EmittableText().apply {
+ text = "test text"
+ modifier = GlanceModifier.semantics { testTag = "test-tag" }
+ })
+ },
+ onNodeMatcher = hasTestTag("test-tag")
+ )
+
+ nodeAssertion.assertHasTextEqualTo(text = "TEST TEXT", ignoreCase = true)
+ }
+
+ @Test
+ fun assertHasTextEqualTo_notMatching_assertionError() {
+ val nodeAssertion = getGlanceNodeAssertionFor(
+ emittable = EmittableColumn().apply {
+ children.add(EmittableText().apply {
+ text = "test text"
+ modifier = GlanceModifier.semantics { testTag = "test-tag" }
+ })
+ },
+ onNodeMatcher = hasTestTag("test-tag")
+ )
+
+ val assertionError = assertThrows(AssertionError::class.java) {
+ nodeAssertion.assertHasTextEqualTo("non-existing text")
+ }
+
+ assertThat(assertionError)
+ .hasMessageThat()
+ .contains(
+ "Failed to assert condition: " +
+ "(text == 'non-existing text' (ignoreCase: 'false'))"
+ )
+ }
+
+ @Test
+ fun assertHasTextEqualTo_ignoreCaseAndNotMatching_assertionError() {
+ val nodeAssertion = getGlanceNodeAssertionFor(
+ emittable = EmittableColumn().apply {
+ children.add(EmittableText().apply {
+ text = "test text"
+ modifier = GlanceModifier.semantics { testTag = "test-tag" }
+ })
+ },
+ onNodeMatcher = hasTestTag("test-tag")
+ )
+
+ val assertionError = assertThrows(AssertionError::class.java) {
+ nodeAssertion.assertHasTextEqualTo("NON-EXISTING TEXT", ignoreCase = true)
+ }
+
+ assertThat(assertionError)
+ .hasMessageThat()
+ .contains(
+ "Failed to assert condition: " +
+ "(text == 'NON-EXISTING TEXT' (ignoreCase: 'true'))"
+ )
+ }
+
+ @Test
+ fun assertHasText_matching() {
+ val nodeAssertion = getGlanceNodeAssertionFor(
+ emittable = EmittableColumn().apply {
+ children.add(EmittableText().apply {
+ text = "test text"
+ modifier = GlanceModifier.semantics { testTag = "test-tag" }
+ })
+ },
+ onNodeMatcher = hasTestTag("test-tag")
+ )
+
+ nodeAssertion.assertHasText(text = "text")
+ }
+
+ @Test
+ fun assertHasText_ignoreCase_matching() {
+ val nodeAssertion = getGlanceNodeAssertionFor(
+ emittable = EmittableColumn().apply {
+ children.add(EmittableText().apply {
+ text = "test text"
+ modifier = GlanceModifier.semantics { testTag = "test-tag" }
+ })
+ },
+ onNodeMatcher = hasTestTag("test-tag")
+ )
+
+ nodeAssertion.assertHasText(text = "TEXT", ignoreCase = true)
+ }
+
+ @Test
+ fun assertHasText_notMatching_assertionError() {
+ val nodeAssertion = getGlanceNodeAssertionFor(
+ emittable = EmittableColumn().apply {
+ children.add(EmittableText().apply {
+ text = "test text"
+ modifier = GlanceModifier.semantics { testTag = "test-tag" }
+ })
+ },
+ onNodeMatcher = hasTestTag("test-tag")
+ )
+
+ val assertionError = assertThrows(AssertionError::class.java) {
+ nodeAssertion.assertHasText("non-existing")
+ }
+
+ assertThat(assertionError)
+ .hasMessageThat()
+ .contains(
+ "Failed to assert condition: " +
+ "(contains text 'non-existing' (ignoreCase: 'false') as substring)"
+ )
+ }
+
+ @Test
+ fun assertHasText_ignoreCaseAndNotMatching_assertionError() {
+ val nodeAssertion = getGlanceNodeAssertionFor(
+ emittable = EmittableColumn().apply {
+ children.add(EmittableText().apply {
+ text = "test text"
+ modifier = GlanceModifier.semantics { testTag = "test-tag" }
+ })
+ },
+ onNodeMatcher = hasTestTag("test-tag")
+ )
+
+ val assertionError = assertThrows(AssertionError::class.java) {
+ nodeAssertion.assertHasText(text = "NON-EXISTING", ignoreCase = true)
+ }
+
+ assertThat(assertionError)
+ .hasMessageThat()
+ .contains(
+ "Failed to assert condition: " +
+ "(contains text 'NON-EXISTING' (ignoreCase: 'true') as substring)"
+ )
+ }
+
+ @Test
fun assertHasTestTag_matching() {
val nodeAssertion = getGlanceNodeAssertionFor(
emittable = EmittableColumn().apply {
@@ -66,6 +222,99 @@
}
@Test
+ fun assertHasContentDescriptionEqualTo_matching() {
+ val nodeAssertion = getGlanceNodeAssertionFor(
+ emittable = EmittableColumn().apply {
+ children.add(EmittableText().apply {
+ text = "test text"
+ modifier = GlanceModifier.semantics {
+ testTag = "test-tag"
+ contentDescription = "test text description"
+ }
+ })
+ },
+ onNodeMatcher = hasTestTag("test-tag")
+ )
+
+ nodeAssertion.assertHasContentDescriptionEqualTo("test text description")
+ }
+
+ @Test
+ fun assertHasContentDescriptionEqualTo_ignoreCaseMatching() {
+ val nodeAssertion = getGlanceNodeAssertionFor(
+ emittable = EmittableColumn().apply {
+ children.add(EmittableText().apply {
+ text = "test text"
+ modifier = GlanceModifier.semantics {
+ testTag = "test-tag"
+ contentDescription = "test text description"
+ }
+ })
+ },
+ onNodeMatcher = hasTestTag("test-tag")
+ )
+
+ nodeAssertion.assertHasContentDescriptionEqualTo(
+ value = "TEST TEXT DESCRIPTION",
+ ignoreCase = true
+ )
+ }
+
+ @Test
+ fun assertHasContentDescriptionEqualTo_notMatching_assertionError() {
+ val nodeAssertion = getGlanceNodeAssertionFor(
+ emittable = EmittableColumn().apply {
+ children.add(EmittableText().apply {
+ text = "test text"
+ modifier = GlanceModifier.semantics { testTag = "test-tag" }
+ })
+ },
+ onNodeMatcher = hasTestTag("test-tag")
+ )
+
+ val assertionError = assertThrows(AssertionError::class.java) {
+ nodeAssertion.assertHasContentDescriptionEqualTo("text description")
+ }
+
+ assertThat(assertionError)
+ .hasMessageThat()
+ .contains(
+ "Failed to assert condition: " +
+ "(ContentDescription == 'text description' (ignoreCase: 'false'))"
+ )
+ }
+
+ @Test
+ fun assertHasContentDescriptionEqualTo_ignoreCaseAndNotMatching_assertionError() {
+ val nodeAssertion = getGlanceNodeAssertionFor(
+ emittable = EmittableColumn().apply {
+ children.add(EmittableText().apply {
+ text = "test text"
+ modifier = GlanceModifier.semantics {
+ testTag = "test-tag"
+ contentDescription = "test text description"
+ }
+ })
+ },
+ onNodeMatcher = hasTestTag("test-tag")
+ )
+
+ val assertionError = assertThrows(AssertionError::class.java) {
+ nodeAssertion.assertHasContentDescriptionEqualTo(
+ value = "TEST DESCRIPTION",
+ ignoreCase = true
+ )
+ }
+
+ assertThat(assertionError)
+ .hasMessageThat()
+ .contains(
+ "Failed to assert condition: " +
+ "(ContentDescription == 'TEST DESCRIPTION' (ignoreCase: 'true'))"
+ )
+ }
+
+ @Test
fun assertHasContentDescription_matching() {
val nodeAssertion = getGlanceNodeAssertionFor(
emittable = EmittableColumn().apply {
@@ -80,7 +329,28 @@
onNodeMatcher = hasTestTag("test-tag")
)
- nodeAssertion.assertHasContentDescription("test text description")
+ nodeAssertion.assertHasContentDescription("text")
+ }
+
+ @Test
+ fun assertHasContentDescription_ignoreCaseMatching() {
+ val nodeAssertion = getGlanceNodeAssertionFor(
+ emittable = EmittableColumn().apply {
+ children.add(EmittableText().apply {
+ text = "test text"
+ modifier = GlanceModifier.semantics {
+ testTag = "test-tag"
+ contentDescription = "test text description"
+ }
+ })
+ },
+ onNodeMatcher = hasTestTag("test-tag")
+ )
+
+ nodeAssertion.assertHasContentDescription(
+ value = "TEXT",
+ ignoreCase = true
+ )
}
@Test
@@ -103,7 +373,39 @@
.hasMessageThat()
.contains(
"Failed to assert condition: " +
- "(ContentDescription = 'test text description' (ignoreCase: 'false'))"
+ "(ContentDescription contains 'test text description' " +
+ "(ignoreCase: 'false') as substring)"
+ )
+ }
+
+ @Test
+ fun assertHasContentDescription_ignoreCaseAndNotMatching_assertionError() {
+ val nodeAssertion = getGlanceNodeAssertionFor(
+ emittable = EmittableColumn().apply {
+ children.add(EmittableText().apply {
+ text = "test text"
+ modifier = GlanceModifier.semantics {
+ testTag = "test-tag"
+ contentDescription = "text"
+ }
+ })
+ },
+ onNodeMatcher = hasTestTag("test-tag")
+ )
+
+ val assertionError = assertThrows(AssertionError::class.java) {
+ nodeAssertion.assertHasContentDescription(
+ value = "TEXT DESCRIPTION",
+ ignoreCase = true
+ )
+ }
+
+ assertThat(assertionError)
+ .hasMessageThat()
+ .contains(
+ "Failed to assert condition: " +
+ "(ContentDescription contains 'TEXT DESCRIPTION' (ignoreCase: 'true') " +
+ "as substring)"
)
}
}
diff --git a/glance/glance-testing/src/test/kotlin/androidx/glance/testing/unit/GlanceMappedNodeFiltersAndMatcherTest.kt b/glance/glance-testing/src/test/kotlin/androidx/glance/testing/unit/UnitTestFiltersTest.kt
similarity index 62%
rename from glance/glance-testing/src/test/kotlin/androidx/glance/testing/unit/GlanceMappedNodeFiltersAndMatcherTest.kt
rename to glance/glance-testing/src/test/kotlin/androidx/glance/testing/unit/UnitTestFiltersTest.kt
index 2fb3c52..69aa1a9 100644
--- a/glance/glance-testing/src/test/kotlin/androidx/glance/testing/unit/GlanceMappedNodeFiltersAndMatcherTest.kt
+++ b/glance/glance-testing/src/test/kotlin/androidx/glance/testing/unit/UnitTestFiltersTest.kt
@@ -24,49 +24,7 @@
import com.google.common.truth.Truth.assertThat
import org.junit.Test
-class GlanceMappedNodeFiltersAndMatcherTest {
- @Test
- fun matchAny_match_returnsTrue() {
- val node1 = GlanceMappedNode(
- EmittableText().apply {
- text = "node1"
- }
- )
- val node2 = GlanceMappedNode(
- EmittableColumn().apply {
- modifier = GlanceModifier.semantics { testTag = "existing-test-tag" }
- EmittableText().apply { text = "node2" }
- }
- )
-
- val result = hasTestTag("existing-test-tag").matchesAny(listOf(node1, node2))
-
- assertThat(result).isTrue()
- }
-
- @Test
- fun matchAny_noMatch_returnsFalse() {
- val node1 = GlanceMappedNode(
- EmittableText().apply {
- text = "node1"
- }
- )
- val node2 = GlanceMappedNode(
- EmittableColumn().apply {
- EmittableText().apply {
- text = "node2"
- // this won't be inspected, as EmittableColumn node is being run against
- // matcher, not its children
- modifier = GlanceModifier.semantics { testTag = "existing-test-tag" }
- }
- }
- )
-
- val result = hasTestTag("existing-test-tag").matchesAny(listOf(node1, node2))
-
- assertThat(result).isFalse()
- }
-
+class UnitTestFiltersTest {
@Test
fun hasTestTag_match_returnsTrue() {
// a single node that will be matched against matcher returned by the filter under test
@@ -97,14 +55,74 @@
}
@Test
- fun hasText_match_returnsTrue() {
+ fun hasTextEqualTo_match_returnsTrue() {
val testSingleNode = GlanceMappedNode(
EmittableText().apply {
text = "existing text"
}
)
- val result = hasText("existing text").matches(testSingleNode)
+ val result = hasTextEqualTo("existing text").matches(testSingleNode)
+
+ assertThat(result).isTrue()
+ }
+
+ @Test
+ fun hasTextEqualTo_noMatch_returnsFalse() {
+ val testSingleNode = GlanceMappedNode(
+ EmittableText().apply {
+ text = "existing text"
+ }
+ )
+
+ val result = hasTextEqualTo("non-existing text").matches(testSingleNode)
+
+ assertThat(result).isFalse()
+ }
+
+ @Test
+ fun hasTextEqualTo_caseInsensitiveMatch_returnsTrue() {
+ val testSingleNode = GlanceMappedNode(
+ EmittableText().apply {
+ text = "some EXISTING text"
+ }
+ )
+
+ val result =
+ hasTextEqualTo(
+ text = "SOME existing TEXT",
+ ignoreCase = true
+ ).matches(testSingleNode)
+
+ assertThat(result).isTrue()
+ }
+
+ @Test
+ fun hasTextEqualTo_caseInsensitiveButNoMatch_returnsFalse() {
+ val testSingleNode = GlanceMappedNode(
+ EmittableText().apply {
+ text = "some EXISTING text"
+ }
+ )
+
+ val result =
+ hasTextEqualTo(
+ text = "SOME non-existing TEXT",
+ ignoreCase = true
+ ).matches(testSingleNode)
+
+ assertThat(result).isFalse()
+ }
+
+ @Test
+ fun hasText_match_returnsTrue() {
+ val testSingleNode = GlanceMappedNode(
+ EmittableText().apply {
+ text = "some existing text"
+ }
+ )
+
+ val result = hasText("existing").matches(testSingleNode)
assertThat(result).isTrue()
}
@@ -113,94 +131,91 @@
fun hasText_noMatch_returnsFalse() {
val testSingleNode = GlanceMappedNode(
EmittableText().apply {
- text = "existing text"
- }
- )
-
- val result = hasText("non-existing text").matches(testSingleNode)
-
- assertThat(result).isFalse()
- }
-
- @Test
- fun hasText_subStringMatch_returnsTrue() {
- val testSingleNode = GlanceMappedNode(
- EmittableText().apply {
text = "some existing text"
}
)
- val result = hasText(text = "existing", substring = true).matches(testSingleNode)
-
- assertThat(result).isTrue()
- }
-
- @Test
- fun hasText_subStringNoMatch_returnsFalse() {
- val testSingleNode = GlanceMappedNode(
- EmittableText().apply {
- text = "some existing text"
- }
- )
-
- val result = hasText(text = "non-existing", substring = true).matches(testSingleNode)
+ val result = hasText("non-existing").matches(testSingleNode)
assertThat(result).isFalse()
}
@Test
- fun hasText_subStringCaseInsensitiveMatch_returnsTrue() {
+ fun hasText_insensitiveMatch_returnsTrue() {
val testSingleNode = GlanceMappedNode(
EmittableText().apply {
text = "some EXISTING text"
}
)
- val result =
- hasText(text = "existing", substring = true, ignoreCase = true).matches(testSingleNode)
+ val result = hasText(
+ text = "existing",
+ ignoreCase = true
+ ).matches(testSingleNode)
assertThat(result).isTrue()
}
@Test
- fun hasText_subStringCaseInsensitiveNoMatch_returnsFalse() {
+ fun hasText_caseInsensitiveButNoMatch_returnsFalse() {
val testSingleNode = GlanceMappedNode(
EmittableText().apply {
text = "some EXISTING text"
}
)
- val result =
- hasText(text = "non-EXISTING", substring = true, ignoreCase = true)
- .matches(testSingleNode)
+ val result = hasText(
+ text = "non-EXISTING",
+ ignoreCase = true
+ ).matches(testSingleNode)
assertThat(result).isFalse()
}
@Test
- fun hasText_caseInsensitiveMatch_returnsTrue() {
- val testSingleNode = GlanceMappedNode(
- EmittableText().apply {
- text = "some EXISTING text"
+ fun hasAnyDescendant_match_returnsTrue() {
+ val testNode = GlanceMappedNode(
+ EmittableColumn().apply {
+ children += EmittableText().apply {
+ text = "node1"
+ }
+ children += EmittableColumn().apply {
+ children += EmittableText().apply {
+ text = "node2-a"
+ }
+ children += EmittableText().apply {
+ text = "node2-b"
+ }
+ }
}
)
val result =
- hasText(text = "SOME existing TEXT", ignoreCase = true).matches(testSingleNode)
+ hasAnyDescendant(hasText("node2-b")).matches(testNode)
assertThat(result).isTrue()
}
@Test
- fun hasText_caseInsensitiveNoMatch_returnsFalse() {
- val testSingleNode = GlanceMappedNode(
- EmittableText().apply {
- text = "some EXISTING text"
+ fun hasAnyDescendant_noMatch_returnsFalse() {
+ val testNode = GlanceMappedNode(
+ EmittableColumn().apply {
+ children += EmittableText().apply {
+ text = "node1"
+ }
+ children += EmittableColumn().apply {
+ children += EmittableText().apply {
+ text = "node2-a"
+ }
+ children += EmittableText().apply {
+ text = "node2-b"
+ }
+ }
}
)
val result =
- hasText(text = "SOME non-existing TEXT", ignoreCase = true).matches(testSingleNode)
+ hasAnyDescendant(hasText("node3-a")).matches(testNode)
assertThat(result).isFalse()
}
diff --git a/gradle/README.md b/gradle/README.md
index e945e4e..bd7a571 100644
--- a/gradle/README.md
+++ b/gradle/README.md
@@ -24,24 +24,13 @@
[Configuration file for Gradle dependency verification](https://docs.gradle.org/current/userguide/dependency_verification.html#sub:verification-metadata) used by androidx to make sure dependencies are [signed with trusted signatures](https://docs.gradle.org/current/userguide/dependency_verification.html#sec:signature-verificationn) and that unsigned artifacts have [expected checksums](https://docs.gradle.org/current/userguide/dependency_verification.html#sec:checksum-verification).
-When adding a new artifact
-- if it is signed, then run:
+When adding a new artifact, first run:
```
development/update-verification-metadata.sh
```
-to trust the signature of the new artifact.
+to trust the signature (or checksum) of the new artifact.
-- if it is not signed, then run the following to add generated checksums to `verification-metadata.xml`:
-
-```
-./gradlew -M sha256 buildOnServer --dry-run
-```
-
-Then you will want to diff `gradle/verification-metadata.dryrun.xml` and
-`gradle/verification-metadata.xml` using your favorite tool (e.g. meld) can copy over the entries
-that are relevant to your new artifacts.
-
-Each new checksum that you copy over in this way must be associated with a bug that is tracking
+Then, if any checksums were added, make sure they're associated with a bug that is tracking
an effort to build or acquire a signed version of this dependency. To associate with a bug,
please add an `androidx:reason` attribute to a string that contains a URL for a bug filed
either in buganizer or github:
@@ -57,8 +46,6 @@
</component>
```
-After doing this, you can then delete all the `verification-*-dryrun.*` files.
-
### If that doesn't work.
If the artifact is not signed, and does not get automatically added to
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index db17c37..ea083dd 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -26,14 +26,14 @@
byteBuddy = "1.12.10"
asm = "9.3"
cmake = "3.22.1"
-dagger = "2.46.1"
+dagger = "2.48"
dexmaker = "2.28.3"
dokka = "1.8.20-dev-214"
espresso = "3.6.0-alpha01"
espressoDevice = "1.0.0-alpha05"
grpc = "1.52.0"
guavaJre = "31.1-jre"
-hilt = "2.46.1"
+hilt = "2.48"
incap = "0.2"
jcodec = "0.2.5"
kotlin17 = "1.7.10"
@@ -230,11 +230,11 @@
opentest4j = { module = "org.opentest4j:opentest4j", version = "1.2.0" }
playFeatureDelivery = { module = "com.google.android.play:feature-delivery", version = "2.0.1" }
playCore = { module = "com.google.android.play:core", version = "1.10.3" }
-playServicesAuth = {module = "com.google.android.gms:play-services-auth", version = "20.5.0"}
+playServicesAuth = {module = "com.google.android.gms:play-services-auth", version = "20.7.0"}
playServicesBase = { module = "com.google.android.gms:play-services-base", version = "17.0.0" }
playServicesBasement = { module = "com.google.android.gms:play-services-basement", version = "17.0.0" }
playServicesDevicePerformance = { module = "com.google.android.gms:play-services-deviceperformance", version = "16.0.0" }
-playServicesFido = {module = "com.google.android.gms:play-services-fido", version = "20.0.1"}
+playServicesFido = {module = "com.google.android.gms:play-services-fido", version = "20.1.0"}
playServicesWearable = { module = "com.google.android.gms:play-services-wearable", version = "17.1.0" }
paparazzi = { module = "app.cash.paparazzi:paparazzi", version.ref = "paparazzi" }
paparazziNativeJvm = { module = "app.cash.paparazzi:layoutlib-native-jdk11", version.ref = "paparazziNative" }
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index 18337be..0b3b82b 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -511,6 +511,11 @@
<sha256 value="32001db2443b339dd21f5b79ff29d1ade722d1ba080c214bde819f0f72d1604d" origin="Generated by Gradle"/>
</artifact>
</component>
+ <component group="com.github.johnrengelman.shadow" name="com.github.johnrengelman.shadow.gradle.plugin" version="8.1.1">
+ <artifact name="com.github.johnrengelman.shadow.gradle.plugin-8.1.1.pom">
+ <sha256 value="3cb3886b97df6e066f108c316b219f262c97c3cb2df6da78927e645deb643cb0" origin="Generated by Gradle" reason="Artifact is not signed"/>
+ </artifact>
+ </component>
<component group="com.google" name="google" version="1">
<artifact name="google-1.pom">
<sha256 value="cd6db17a11a31ede794ccbd1df0e4d9750f640234731f21cff885a9997277e81" origin="Generated by Gradle" reason="Artifact is not signed"/>
@@ -678,11 +683,6 @@
</sha256>
</artifact>
</component>
- <component group="com.github.johnrengelman.shadow" name="com.github.johnrengelman.shadow.gradle.plugin" version="8.1.1">
- <artifact name="com.github.johnrengelman.shadow.gradle.plugin-8.1.1.pom">
- <sha256 value="3cb3886b97df6e066f108c316b219f262c97c3cb2df6da78927e645deb643cb0" origin="Generated by Gradle" reason="Artifact is not signed"/>
- </artifact>
- </component>
<component group="javax.annotation" name="jsr250-api" version="1.0">
<artifact name="jsr250-api-1.0.jar">
<sha256 value="a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f" origin="Generated by Gradle"/>
diff --git a/health/connect/connect-client/api/current.txt b/health/connect/connect-client/api/current.txt
index 3e9c371..7b0863a 100644
--- a/health/connect/connect-client/api/current.txt
+++ b/health/connect/connect-client/api/current.txt
@@ -9,6 +9,7 @@
method public suspend Object? deleteRecords(kotlin.reflect.KClass<? extends androidx.health.connect.client.records.Record> recordType, java.util.List<java.lang.String> recordIdsList, java.util.List<java.lang.String> clientRecordIdsList, kotlin.coroutines.Continuation<? super kotlin.Unit>);
method public suspend Object? getChanges(String changesToken, kotlin.coroutines.Continuation<? super androidx.health.connect.client.response.ChangesResponse>);
method public suspend Object? getChangesToken(androidx.health.connect.client.request.ChangesTokenRequest request, kotlin.coroutines.Continuation<? super java.lang.String>);
+ method public static String getHealthConnectManageDataAction(android.content.Context context);
method public static String getHealthConnectSettingsAction();
method public static androidx.health.connect.client.HealthConnectClient getOrCreate(android.content.Context context);
method public static androidx.health.connect.client.HealthConnectClient getOrCreate(android.content.Context context, optional String providerPackageName);
@@ -28,6 +29,7 @@
}
public static final class HealthConnectClient.Companion {
+ method public String getHealthConnectManageDataAction(android.content.Context context);
method public String getHealthConnectSettingsAction();
method public androidx.health.connect.client.HealthConnectClient getOrCreate(android.content.Context context);
method public androidx.health.connect.client.HealthConnectClient getOrCreate(android.content.Context context, optional String providerPackageName);
diff --git a/health/connect/connect-client/api/restricted_current.txt b/health/connect/connect-client/api/restricted_current.txt
index 7bccf0e..beb3500f 100644
--- a/health/connect/connect-client/api/restricted_current.txt
+++ b/health/connect/connect-client/api/restricted_current.txt
@@ -9,6 +9,7 @@
method public suspend Object? deleteRecords(kotlin.reflect.KClass<? extends androidx.health.connect.client.records.Record> recordType, java.util.List<java.lang.String> recordIdsList, java.util.List<java.lang.String> clientRecordIdsList, kotlin.coroutines.Continuation<? super kotlin.Unit>);
method public suspend Object? getChanges(String changesToken, kotlin.coroutines.Continuation<? super androidx.health.connect.client.response.ChangesResponse>);
method public suspend Object? getChangesToken(androidx.health.connect.client.request.ChangesTokenRequest request, kotlin.coroutines.Continuation<? super java.lang.String>);
+ method public static String getHealthConnectManageDataAction(android.content.Context context);
method public static String getHealthConnectSettingsAction();
method public static androidx.health.connect.client.HealthConnectClient getOrCreate(android.content.Context context);
method public static androidx.health.connect.client.HealthConnectClient getOrCreate(android.content.Context context, optional String providerPackageName);
@@ -28,6 +29,7 @@
}
public static final class HealthConnectClient.Companion {
+ method public String getHealthConnectManageDataAction(android.content.Context context);
method public String getHealthConnectSettingsAction();
method public androidx.health.connect.client.HealthConnectClient getOrCreate(android.content.Context context);
method public androidx.health.connect.client.HealthConnectClient getOrCreate(android.content.Context context, optional String providerPackageName);
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/HealthConnectClient.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/HealthConnectClient.kt
index fce572a..4f74132 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/HealthConnectClient.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/HealthConnectClient.kt
@@ -320,6 +320,13 @@
"androidx.health.ACTION_HEALTH_CONNECT_SETTINGS"
/**
+ * The minimum version code of the default provider APK that supports manage data intent
+ * action.
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ internal const val ACTION_MANAGE_DATA_MIN_SUPPORTED_VERSION_CODE = 82932
+
+ /**
* Intent action to open Health Connect settings on this phone. Developers should use this
* if they want to re-direct the user to Health Connect.
*/
@@ -461,6 +468,29 @@
)
}
+ /**
+ * Intent action to open Health Connect data management screen on this phone. Developers
+ * should use this if they want to re-direct the user to Health Connect data management.
+ *
+ * @param context the context
+ * @return Intent action to open Health Connect data management screen.
+ */
+ @JvmStatic
+ fun getHealthConnectManageDataAction(context: Context): String {
+ val action = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ "android.health.connect.action.MANAGE_HEALTH_DATA"
+ } else if (isProviderAvailable(
+ context = context,
+ providerVersionCode = ACTION_MANAGE_DATA_MIN_SUPPORTED_VERSION_CODE
+ )
+ ) {
+ "androidx.health.ACTION_MANAGE_HEALTH_DATA"
+ } else {
+ ACTION_HEALTH_CONNECT_SETTINGS
+ }
+ return action
+ }
+
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.P)
internal fun isSdkVersionSufficient() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
@@ -471,11 +501,16 @@
internal fun isProviderAvailable(
context: Context,
providerPackageName: String = DEFAULT_PROVIDER_PACKAGE_NAME,
+ providerVersionCode: Int = DEFAULT_PROVIDER_MIN_VERSION_CODE
): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
return true
}
- return isPackageInstalled(context.packageManager, providerPackageName)
+ return isPackageInstalled(
+ context.packageManager,
+ providerPackageName,
+ providerVersionCode
+ )
}
internal fun isProviderAvailableLegacy(
@@ -488,6 +523,7 @@
private fun isPackageInstalled(
packageManager: PackageManager,
packageName: String,
+ versionCode: Int = DEFAULT_PROVIDER_MIN_VERSION_CODE
): Boolean {
val packageInfo: PackageInfo =
try {
@@ -498,8 +534,7 @@
}
return packageInfo.applicationInfo.enabled &&
(packageName != DEFAULT_PROVIDER_PACKAGE_NAME ||
- PackageInfoCompat.getLongVersionCode(packageInfo) >=
- DEFAULT_PROVIDER_MIN_VERSION_CODE) &&
+ PackageInfoCompat.getLongVersionCode(packageInfo) >= versionCode) &&
hasBindableService(packageManager, packageName)
}
diff --git a/health/connect/connect-client/src/test/java/androidx/health/connect/client/HealthConnectClientTest.kt b/health/connect/connect-client/src/test/java/androidx/health/connect/client/HealthConnectClientTest.kt
index 2b6a910..81036fd 100644
--- a/health/connect/connect-client/src/test/java/androidx/health/connect/client/HealthConnectClientTest.kt
+++ b/health/connect/connect-client/src/test/java/androidx/health/connect/client/HealthConnectClientTest.kt
@@ -178,6 +178,45 @@
}
}
+ @Test
+ @Config(sdk = [Build.VERSION_CODES.P])
+ fun getHealthConnectManageDataAction_unsupportedClient_returnsDefaultIntent() {
+ installPackage(
+ context,
+ HealthConnectClient.DEFAULT_PROVIDER_PACKAGE_NAME,
+ versionCode = HealthConnectClient.DEFAULT_PROVIDER_MIN_VERSION_CODE,
+ enabled = true
+ )
+
+ assertThat(HealthConnectClient.getHealthConnectManageDataAction(context)).isEqualTo(
+ HealthConnectClient.ACTION_HEALTH_CONNECT_SETTINGS
+ )
+ }
+
+ @Test
+ @Config(sdk = [Build.VERSION_CODES.P])
+ fun getHealthConnectManageDataAction_supportedClient() {
+ installPackage(
+ context,
+ HealthConnectClient.DEFAULT_PROVIDER_PACKAGE_NAME,
+ versionCode = HealthConnectClient.ACTION_MANAGE_DATA_MIN_SUPPORTED_VERSION_CODE,
+ enabled = true
+ )
+ installService(context, HealthConnectClient.DEFAULT_PROVIDER_PACKAGE_NAME)
+
+ assertThat(HealthConnectClient.getHealthConnectManageDataAction(context)).isEqualTo(
+ "androidx.health.ACTION_MANAGE_HEALTH_DATA"
+ )
+ }
+
+ @Test
+ @Config(minSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ fun getHealthConnectManageDataAction_platformSupported() {
+ assertThat(HealthConnectClient.getHealthConnectManageDataAction(context)).isEqualTo(
+ "android.health.connect.action.MANAGE_HEALTH_DATA"
+ )
+ }
+
private fun installPackage(
context: Context,
packageName: String,
diff --git a/javascriptengine/javascriptengine/api/aidlRelease/current/org/chromium/android_webview/js_sandbox/common/IJsSandboxIsolateClient.aidl b/javascriptengine/javascriptengine/api/aidlRelease/current/org/chromium/android_webview/js_sandbox/common/IJsSandboxIsolateClient.aidl
new file mode 100644
index 0000000..b00a1ca
--- /dev/null
+++ b/javascriptengine/javascriptengine/api/aidlRelease/current/org/chromium/android_webview/js_sandbox/common/IJsSandboxIsolateClient.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package org.chromium.android_webview.js_sandbox.common;
+/* @hide */
+interface IJsSandboxIsolateClient {
+ void onTerminated(int status, String message) = 1;
+ const int TERMINATE_UNKNOWN_ERROR = 1;
+ const int TERMINATE_SANDBOX_DEAD = 2;
+ const int TERMINATE_MEMORY_LIMIT_EXCEEDED = 3;
+}
diff --git a/javascriptengine/javascriptengine/api/aidlRelease/current/org/chromium/android_webview/js_sandbox/common/IJsSandboxService.aidl b/javascriptengine/javascriptengine/api/aidlRelease/current/org/chromium/android_webview/js_sandbox/common/IJsSandboxService.aidl
index cd3aae0..78a0831 100644
--- a/javascriptengine/javascriptengine/api/aidlRelease/current/org/chromium/android_webview/js_sandbox/common/IJsSandboxService.aidl
+++ b/javascriptengine/javascriptengine/api/aidlRelease/current/org/chromium/android_webview/js_sandbox/common/IJsSandboxService.aidl
@@ -37,9 +37,11 @@
org.chromium.android_webview.js_sandbox.common.IJsSandboxIsolate createIsolate() = 0;
List<String> getSupportedFeatures() = 1;
org.chromium.android_webview.js_sandbox.common.IJsSandboxIsolate createIsolateWithMaxHeapSizeBytes(long maxHeapSize) = 2;
+ org.chromium.android_webview.js_sandbox.common.IJsSandboxIsolate createIsolate2(long maxHeapSize, org.chromium.android_webview.js_sandbox.common.IJsSandboxIsolateClient isolateClient) = 3;
const String ISOLATE_TERMINATION = "ISOLATE_TERMINATION";
const String ISOLATE_MAX_HEAP_SIZE_LIMIT = "ISOLATE_MAX_HEAP_SIZE_LIMIT";
const String WASM_FROM_ARRAY_BUFFER = "WASM_FROM_ARRAY_BUFFER";
const String EVALUATE_WITHOUT_TRANSACTION_LIMIT = "EVALUATE_WITHOUT_TRANSACTION_LIMIT";
const String CONSOLE_MESSAGING = "CONSOLE_MESSAGING";
+ const String ISOLATE_CLIENT = "ISOLATE_CLIENT";
}
diff --git a/javascriptengine/javascriptengine/api/current.txt b/javascriptengine/javascriptengine/api/current.txt
index e523e0b..77b4206c 100644
--- a/javascriptengine/javascriptengine/api/current.txt
+++ b/javascriptengine/javascriptengine/api/current.txt
@@ -20,7 +20,7 @@
field public static final int DEFAULT_MAX_EVALUATION_RETURN_SIZE_BYTES = 20971520; // 0x1400000
}
- public final class IsolateTerminatedException extends androidx.javascriptengine.JavaScriptException {
+ public class IsolateTerminatedException extends androidx.javascriptengine.JavaScriptException {
ctor public IsolateTerminatedException();
}
@@ -74,12 +74,12 @@
field public static final String JS_FEATURE_WASM_COMPILATION = "JS_FEATURE_WASM_COMPILATION";
}
- public final class MemoryLimitExceededException extends androidx.javascriptengine.JavaScriptException {
+ public final class MemoryLimitExceededException extends androidx.javascriptengine.IsolateTerminatedException {
ctor public MemoryLimitExceededException();
ctor public MemoryLimitExceededException(String);
}
- public final class SandboxDeadException extends androidx.javascriptengine.JavaScriptException {
+ public final class SandboxDeadException extends androidx.javascriptengine.IsolateTerminatedException {
ctor public SandboxDeadException();
}
diff --git a/javascriptengine/javascriptengine/api/restricted_current.txt b/javascriptengine/javascriptengine/api/restricted_current.txt
index e523e0b..77b4206c 100644
--- a/javascriptengine/javascriptengine/api/restricted_current.txt
+++ b/javascriptengine/javascriptengine/api/restricted_current.txt
@@ -20,7 +20,7 @@
field public static final int DEFAULT_MAX_EVALUATION_RETURN_SIZE_BYTES = 20971520; // 0x1400000
}
- public final class IsolateTerminatedException extends androidx.javascriptengine.JavaScriptException {
+ public class IsolateTerminatedException extends androidx.javascriptengine.JavaScriptException {
ctor public IsolateTerminatedException();
}
@@ -74,12 +74,12 @@
field public static final String JS_FEATURE_WASM_COMPILATION = "JS_FEATURE_WASM_COMPILATION";
}
- public final class MemoryLimitExceededException extends androidx.javascriptengine.JavaScriptException {
+ public final class MemoryLimitExceededException extends androidx.javascriptengine.IsolateTerminatedException {
ctor public MemoryLimitExceededException();
ctor public MemoryLimitExceededException(String);
}
- public final class SandboxDeadException extends androidx.javascriptengine.JavaScriptException {
+ public final class SandboxDeadException extends androidx.javascriptengine.IsolateTerminatedException {
ctor public SandboxDeadException();
}
diff --git a/javascriptengine/javascriptengine/src/androidTest/java/androidx/javascriptengine/WebViewJavaScriptSandboxTest.java b/javascriptengine/javascriptengine/src/androidTest/java/androidx/javascriptengine/WebViewJavaScriptSandboxTest.java
index 8252afd..d1f5af0 100644
--- a/javascriptengine/javascriptengine/src/androidTest/java/androidx/javascriptengine/WebViewJavaScriptSandboxTest.java
+++ b/javascriptengine/javascriptengine/src/androidTest/java/androidx/javascriptengine/WebViewJavaScriptSandboxTest.java
@@ -17,12 +17,9 @@
package androidx.javascriptengine;
import android.content.Context;
-import android.content.pm.PackageInfo;
-import android.webkit.WebView;
import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
-import androidx.core.content.pm.PackageInfoCompat;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
@@ -58,18 +55,6 @@
Assume.assumeTrue(JavaScriptSandbox.isSupported());
}
- // Get the current WebView provider version. In a versionCode of AAAABBBCD, AAAA is the build
- // number and BBB is the patch number. C and D may usually be ignored.
- //
- // Strongly prefer using feature flags over version checks if possible.
- public long getWebViewVersion() {
- PackageInfo systemWebViewPackage = WebView.getCurrentWebViewPackage();
- if (systemWebViewPackage == null) {
- Assert.fail("No current WebView provider");
- }
- return PackageInfoCompat.getLongVersionCode(systemWebViewPackage);
- }
-
@Test
@MediumTest
public void testSimpleJsEvaluation() throws Throwable {
@@ -584,14 +569,6 @@
@Test
@LargeTest
public void testHeapSizeEnforced() throws Throwable {
- // WebView versions < 110.0.5438.0 do not contain OOM crashes to a single isolate and
- // instead crash the whole sandbox process. This change is not tracked in a feature flag.
- // Versions < 110.0.5438.0 are not considered to be broken, but their behavior is not
- // of interest for this test.
- // See Chromium change: https://chromium-review.googlesource.com/c/chromium/src/+/4047785
- Assume.assumeTrue("WebView version does not support per-isolate OOM handling",
- getWebViewVersion() >= 5438_000_00L);
-
final long maxHeapSize = REASONABLE_HEAP_SIZE;
// We need to beat the v8 optimizer to ensure it really allocates the required memory. Note
// that we're allocating an array of elements - not bytes. Filling will ensure that the
@@ -608,9 +585,11 @@
try (JavaScriptSandbox jsSandbox = jsSandboxFuture1.get(5, TimeUnit.SECONDS)) {
Assume.assumeTrue(jsSandbox.isFeatureSupported(
JavaScriptSandbox.JS_FEATURE_ISOLATE_MAX_HEAP_SIZE));
-
Assume.assumeTrue(
jsSandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_PROMISE_RETURN));
+ Assume.assumeTrue(
+ jsSandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_ISOLATE_CLIENT));
+
IsolateStartupParameters isolateStartupParameters = new IsolateStartupParameters();
isolateStartupParameters.setMaxHeapSizeBytes(maxHeapSize);
try (JavaScriptIsolate jsIsolate1 = jsSandbox.createIsolate(isolateStartupParameters);
@@ -625,8 +604,7 @@
// Wait for jsIsolate2 to fully initialize before using jsIsolate1.
jsIsolate2.evaluateJavaScriptAsync(stableCode).get(5, TimeUnit.SECONDS);
- // Check that the heap limit is enforced and that it reports this was the evaluation
- // that exceeded the limit.
+ // Check that the heap limit is enforced.
try {
// Use a generous timeout for OOM, as it may involve multiple rounds of garbage
// collection.
@@ -638,13 +616,22 @@
}
}
+ // Wait for termination, but don't close the isolate.
+ final CountDownLatch latch = new CountDownLatch(1);
+ jsIsolate1.addOnTerminatedCallback(Runnable::run, info -> {
+ Assert.assertEquals(TerminationInfo.STATUS_MEMORY_LIMIT_EXCEEDED,
+ info.getStatus());
+ latch.countDown();
+ });
+ Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+
// Check that the previously submitted (but unresolved) promise evaluation reports a
// crash
try {
earlyUnresolvedResultFuture.get(5, TimeUnit.SECONDS);
Assert.fail("Should have thrown.");
} catch (ExecutionException e) {
- if (!(e.getCause() instanceof IsolateTerminatedException)) {
+ if (!(e.getCause() instanceof MemoryLimitExceededException)) {
throw e;
}
}
@@ -667,11 +654,18 @@
}
}
- // Check that other pre-existing isolates can still be used.
+ // Check that other pre-existing isolate in the same sandbox can no longer be used.
+ // (That the sandbox as a whole is dead.)
ListenableFuture<String> otherIsolateResultFuture =
jsIsolate2.evaluateJavaScriptAsync(stableCode);
- String otherIsolateResult = otherIsolateResultFuture.get(5, TimeUnit.SECONDS);
- Assert.assertEquals(stableExpected, otherIsolateResult);
+ try {
+ otherIsolateResultFuture.get(5, TimeUnit.SECONDS);
+ Assert.fail("Should have thrown.");
+ } catch (ExecutionException e) {
+ if (!(e.getCause() instanceof SandboxDeadException)) {
+ throw e;
+ }
+ }
}
}
}
@@ -679,14 +673,6 @@
@Test
@LargeTest
public void testIsolateCreationAfterCrash() throws Throwable {
- // WebView versions < 110.0.5438.0 do not contain OOM crashes to a single isolate and
- // instead crash the whole sandbox process. This change is not tracked in a feature flag.
- // Versions < 110.0.5438.0 are not considered to be broken, but their behavior is not
- // of interest for this test.
- // See Chromium change: https://chromium-review.googlesource.com/c/chromium/src/+/4047785
- Assume.assumeTrue("WebView version does not support per-isolate OOM handling",
- getWebViewVersion() >= 5438_000_00L);
-
final long maxHeapSize = REASONABLE_HEAP_SIZE;
// We need to beat the v8 optimizer to ensure it really allocates the required memory. Note
// that we're allocating an array of elements - not bytes. Filling will ensure that the
@@ -704,14 +690,15 @@
JavaScriptSandbox.JS_FEATURE_ISOLATE_MAX_HEAP_SIZE));
Assume.assumeTrue(
jsSandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_PROMISE_RETURN));
+ Assume.assumeTrue(
+ jsSandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_ISOLATE_CLIENT));
IsolateStartupParameters isolateStartupParameters = new IsolateStartupParameters();
isolateStartupParameters.setMaxHeapSizeBytes(maxHeapSize);
try (JavaScriptIsolate jsIsolate1 = jsSandbox.createIsolate(isolateStartupParameters)) {
ListenableFuture<String> oomResultFuture =
jsIsolate1.evaluateJavaScriptAsync(oomingCode);
- // Check that the heap limit is enforced and that it reports this was the evaluation
- // that exceeded the limit.
+ // Check that the heap limit is enforced.
try {
// Use a generous timeout for OOM, as it may involve multiple rounds of garbage
// collection.
@@ -723,28 +710,22 @@
}
}
- // Check that other isolates can still be created and used (without closing
- // jsIsolate1).
- try (JavaScriptIsolate jsIsolate2 =
- jsSandbox.createIsolate(isolateStartupParameters)) {
- ListenableFuture<String> resultFuture =
- jsIsolate2.evaluateJavaScriptAsync(stableCode);
- String result = resultFuture.get(5, TimeUnit.SECONDS);
- Assert.assertEquals(stableExpected, result);
- }
- }
+ final CountDownLatch latch = new CountDownLatch(1);
+ jsIsolate1.addOnTerminatedCallback(Runnable::run, info -> {
+ Assert.assertEquals(TerminationInfo.STATUS_MEMORY_LIMIT_EXCEEDED,
+ info.getStatus());
+ latch.countDown();
+ });
+ Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
- // Check that other isolates can still be created and used (after closing jsIsolate1).
- try (JavaScriptIsolate jsIsolate = jsSandbox.createIsolate(isolateStartupParameters)) {
- ListenableFuture<String> resultFuture =
- jsIsolate.evaluateJavaScriptAsync(stableCode);
- String result = resultFuture.get(5, TimeUnit.SECONDS);
- Assert.assertEquals(stableExpected, result);
+ // Check that new isolates can no longer be created in the same sandbox.
+ Assert.assertThrows(IllegalStateException.class,
+ () -> jsSandbox.createIsolate(isolateStartupParameters));
}
}
- // Check that the old sandbox with the "crashed" isolate can be torn down and that a new
- // sandbox and isolate can be spun up.
+ // Check that after the old OOMed sandbox is closed and torn down that a new sandbox and
+ // isolate can be spun up.
ListenableFuture<JavaScriptSandbox> jsSandboxFuture2 =
JavaScriptSandbox.createConnectedInstanceAsync(context);
try (JavaScriptSandbox jsSandbox = jsSandboxFuture2.get(5, TimeUnit.SECONDS);
@@ -1144,4 +1125,80 @@
Assert.assertEquals(expected, result);
}
}
+
+ @Test
+ @LargeTest
+ public void testTerminationNotificationForSandboxDeath() throws Throwable {
+ final Context context = ApplicationProvider.getApplicationContext();
+ final ListenableFuture<JavaScriptSandbox> jsSandboxFuture =
+ JavaScriptSandbox.createConnectedInstanceAsync(context);
+ try (JavaScriptSandbox jsSandbox = jsSandboxFuture.get(5, TimeUnit.SECONDS)) {
+ try (JavaScriptIsolate jsIsolate = jsSandbox.createIsolate()) {
+ final ListenableFuture<String> loopFuture =
+ jsIsolate.evaluateJavaScriptAsync("while(true);");
+
+ final CountDownLatch latch = new CountDownLatch(2);
+
+ final Runnable futureCallback = () -> {
+ try {
+ loopFuture.get();
+ Assert.fail("Should have thrown.");
+ } catch (ExecutionException e) {
+ if (!(e.getCause() instanceof SandboxDeadException)) {
+ Assert.fail("Wrong exception for evaluation: " + e);
+ }
+ } catch (InterruptedException e) {
+ Assert.fail("Interrupted: " + e);
+ }
+ latch.countDown();
+ };
+ loopFuture.addListener(futureCallback, Runnable::run);
+
+ jsIsolate.addOnTerminatedCallback(Runnable::run, info -> {
+ Assert.assertEquals(TerminationInfo.STATUS_SANDBOX_DEAD, info.getStatus());
+ latch.countDown();
+ });
+
+ jsSandbox.close();
+
+ Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+ }
+ }
+ }
+
+ @Test
+ @LargeTest
+ public void testOomOutsideOfEvaluation() throws Throwable {
+ final Context context = ApplicationProvider.getApplicationContext();
+ final ListenableFuture<JavaScriptSandbox> jsSandboxFuture =
+ JavaScriptSandbox.createConnectedInstanceAsync(context);
+ try (JavaScriptSandbox jsSandbox = jsSandboxFuture.get(5, TimeUnit.SECONDS)) {
+ Assume.assumeTrue(jsSandbox.isFeatureSupported(
+ JavaScriptSandbox.JS_FEATURE_ISOLATE_MAX_HEAP_SIZE));
+ Assume.assumeTrue(
+ jsSandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_ISOLATE_CLIENT));
+ IsolateStartupParameters isolateStartupParameters = new IsolateStartupParameters();
+ isolateStartupParameters.setMaxHeapSizeBytes(REASONABLE_HEAP_SIZE);
+ try (JavaScriptIsolate jsIsolate = jsSandbox.createIsolate(isolateStartupParameters)) {
+ // OOM should occur in a microtask, not during this evaluation, so we should
+ // never get a MemoryLimitExceededException from the evaluation future.
+ final String code = ""
+ + "const bytes = [0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00];"
+ + "WebAssembly.compile(new Uint8Array(bytes)).then(() => {"
+ + " this.array ="
+ + " Array(" + REASONABLE_HEAP_SIZE + ").fill(Math.random(), 0);"
+ + "});"
+ + "'PASS'";
+ jsIsolate.evaluateJavaScriptAsync(code).get(5, TimeUnit.SECONDS);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ jsIsolate.addOnTerminatedCallback(Runnable::run, info -> {
+ Assert.assertEquals(
+ TerminationInfo.STATUS_MEMORY_LIMIT_EXCEEDED, info.getStatus());
+ latch.countDown();
+ });
+ Assert.assertTrue(latch.await(60, TimeUnit.SECONDS));
+ }
+ }
+ }
}
diff --git a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/EnvironmentDeadState.java b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/EnvironmentDeadState.java
index 0c99deb..c10d56fd 100644
--- a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/EnvironmentDeadState.java
+++ b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/EnvironmentDeadState.java
@@ -18,6 +18,7 @@
import androidx.annotation.NonNull;
import androidx.concurrent.futures.CallbackToFutureAdapter;
+import androidx.core.util.Consumer;
import com.google.common.util.concurrent.ListenableFuture;
@@ -25,15 +26,19 @@
/**
* Covers the case where the environment is dead.
- *
+ * <p>
* This state covers cases where the developer explicitly closes the sandbox or sandbox/isolate
* being dead outside of the control of the developer.
+ * <p>
+ * Although being in this state is considered terminated from the app perspective, the service
+ * side may still technically be running.
*/
final class EnvironmentDeadState implements IsolateState {
- private final JavaScriptException mException;
+ @NonNull
+ private final TerminationInfo mTerminationInfo;
- EnvironmentDeadState(JavaScriptException e) {
- mException = e;
+ EnvironmentDeadState(@NonNull TerminationInfo terminationInfo) {
+ mTerminationInfo = terminationInfo;
}
@NonNull
@@ -41,7 +46,7 @@
public ListenableFuture<String> evaluateJavaScriptAsync(@NonNull String code) {
return CallbackToFutureAdapter.getFuture(completer -> {
final String futureDebugMessage = "evaluateJavascript Future";
- completer.setException(mException);
+ completer.setException(mTerminationInfo.toJavaScriptException());
return futureDebugMessage;
});
}
@@ -69,12 +74,16 @@
}
@Override
- public IsolateState setSandboxDead() {
- return new EnvironmentDeadState(new SandboxDeadException());
+ public boolean canDie() {
+ return false;
}
@Override
- public IsolateState setIsolateDead() {
- return this;
+ public void addOnTerminatedCallback(@NonNull Executor executor,
+ @NonNull Consumer<TerminationInfo> callback) {
+ executor.execute(() -> callback.accept(mTerminationInfo));
}
+
+ @Override
+ public void removeOnTerminatedCallback(@NonNull Consumer<TerminationInfo> callback) {}
}
diff --git a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateClosedState.java b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateClosedState.java
index 927bbe0..ba944b1 100644
--- a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateClosedState.java
+++ b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateClosedState.java
@@ -17,48 +17,55 @@
package androidx.javascriptengine;
import androidx.annotation.NonNull;
+import androidx.core.util.Consumer;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.concurrent.Executor;
/**
- * Covers the case where the isolate is explicitly closed by the developer.
+ * Covers cases where the isolate is explicitly closed or uninitialized.
+ * <p>
+ * Although being in this state is considered terminated from the app perspective, the service
+ * side may still technically be running.
*/
final class IsolateClosedState implements IsolateState {
- IsolateClosedState() {
+ @NonNull
+ private final String mDescription;
+ IsolateClosedState(@NonNull String description) {
+ mDescription = description;
}
@NonNull
@Override
public ListenableFuture<String> evaluateJavaScriptAsync(@NonNull String code) {
- throw new IllegalStateException("Calling evaluateJavaScriptAsync() after closing the"
- + "Isolate");
+ throw new IllegalStateException(
+ "Calling evaluateJavaScriptAsync() when " + mDescription);
}
@Override
public void setConsoleCallback(@NonNull Executor executor,
@NonNull JavaScriptConsoleCallback callback) {
throw new IllegalStateException(
- "Calling setConsoleCallback() after closing the Isolate");
+ "Calling setConsoleCallback() when " + mDescription);
}
@Override
public void setConsoleCallback(@NonNull JavaScriptConsoleCallback callback) {
throw new IllegalStateException(
- "Calling setConsoleCallback() after closing the Isolate");
+ "Calling setConsoleCallback() when " + mDescription);
}
@Override
public void clearConsoleCallback() {
throw new IllegalStateException(
- "Calling clearConsoleCallback() after closing the Isolate");
+ "Calling clearConsoleCallback() when " + mDescription);
}
@Override
public boolean provideNamedData(@NonNull String name, @NonNull byte[] inputBytes) {
throw new IllegalStateException(
- "Calling provideNamedData() after closing the Isolate");
+ "Calling provideNamedData() when " + mDescription);
}
@Override
@@ -66,12 +73,20 @@
}
@Override
- public IsolateState setSandboxDead() {
- return this;
+ public boolean canDie() {
+ return false;
}
@Override
- public IsolateState setIsolateDead() {
- return this;
+ public void addOnTerminatedCallback(@NonNull Executor executor,
+ @NonNull Consumer<TerminationInfo> callback) {
+ throw new IllegalStateException(
+ "Calling addOnTerminatedCallback() when " + mDescription);
+ }
+
+ @Override
+ public void removeOnTerminatedCallback(@NonNull Consumer<TerminationInfo> callback) {
+ throw new IllegalStateException(
+ "Calling removeOnTerminatedCallback() when " + mDescription);
}
}
diff --git a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateStartupParameters.java b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateStartupParameters.java
index a1f5013..fe7714f 100644
--- a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateStartupParameters.java
+++ b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateStartupParameters.java
@@ -47,10 +47,17 @@
* some multiple of bytes, be increased to some minimum value, or reduced to some maximum
* supported value.
* <p>
- * Exceeding this limit will usually result in a {@link MemoryLimitExceededException},
- * but beware that not all JavaScript sandbox service implementations (particularly older ones)
- * handle memory exhaustion equally gracefully, and may crash the entire sandbox (see
- * {@link SandboxDeadException}).
+ * Exceeding this limit will usually result in all unfinished and future evaluations failing
+ * with {@link MemoryLimitExceededException} and the isolate terminating with a status of
+ * {@link TerminationInfo#STATUS_MEMORY_LIMIT_EXCEEDED}. Note that exceeding the memory limit
+ * will take down the entire sandbox - not just the responsible isolate - and all other
+ * isolates will receive generic {@link SandboxDeadException} and
+ * {@link TerminationInfo#STATUS_SANDBOX_DEAD} errors.
+ * <p>
+ * Not all JavaScript sandbox service implementations (particularly older ones) handle memory
+ * exhaustion equally, and may crash the sandbox without attributing the failure to memory
+ * exhaustion in a particular isolate.
+ *
* @param size heap size in bytes
*/
@RequiresFeature(name = JavaScriptSandbox.JS_FEATURE_ISOLATE_MAX_HEAP_SIZE,
diff --git a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateState.java b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateState.java
index 5a44f48..6188bc9 100644
--- a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateState.java
+++ b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateState.java
@@ -17,6 +17,7 @@
package androidx.javascriptengine;
import androidx.annotation.NonNull;
+import androidx.core.util.Consumer;
import com.google.common.util.concurrent.ListenableFuture;
@@ -24,17 +25,16 @@
/**
* Interface for State design pattern.
- *
+ * <p>
* Isolates can be in different states due to events within/outside the control of the developer.
* This pattern allows us to extract out the state related behaviour without maintaining it all in
* the JavaScriptIsolate class which proved to be error-prone and hard to read.
- *
+ * <p>
* State specific behaviour are implemented in concrete classes that implements this interface.
- *
+ * <p>
* Refer: https://en.wikipedia.org/wiki/State_pattern
*/
interface IsolateState {
-
@NonNull
ListenableFuture<String> evaluateJavaScriptAsync(@NonNull String code);
@@ -49,7 +49,22 @@
void close();
- IsolateState setIsolateDead();
+ /**
+ * Check whether the current state is permitted to transition to a dead state
+ *
+ * @return true iff a transition to a dead state is permitted.
+ */
+ boolean canDie();
- IsolateState setSandboxDead();
+ /**
+ * Method to run after this state has been replaced by a dead state.
+ *
+ * @param terminationInfo The termination info describing the death.
+ */
+ default void onDied(@NonNull TerminationInfo terminationInfo) {}
+
+ void addOnTerminatedCallback(@NonNull Executor executor,
+ @NonNull Consumer<TerminationInfo> callback);
+
+ void removeOnTerminatedCallback(@NonNull Consumer<TerminationInfo> callback);
}
diff --git a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateTerminatedException.java b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateTerminatedException.java
index b7547ecb..8548c19 100644
--- a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateTerminatedException.java
+++ b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateTerminatedException.java
@@ -16,27 +16,42 @@
package androidx.javascriptengine;
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.core.util.Consumer;
+
+import java.util.concurrent.Executor;
+
/**
* Exception thrown when evaluation is terminated due to the {@link JavaScriptIsolate} being closed
- * or crashing.
+ * or due to some crash.
* <p>
* Calling {@link JavaScriptIsolate#close()} will cause this exception to be thrown for all
* previously requested but pending evaluations.
* <p>
- * If the individual isolate has crashed, for example, due to exceeding a memory limit, this
- * exception will also be thrown for all pending and future evaluations (until
- * {@link JavaScriptIsolate#close()} is called).
- * <p>
- * Note that if the sandbox as a whole has crashed or been closed, {@link SandboxDeadException} will
- * be thrown instead.
+ * If an isolate has crashed (but not been closed), subsequently requested evaluations will fail
+ * immediately with an IsolateTerminatedException (or a subclass) consistent with that
+ * used for evaluations submitted before the crash.
* <p>
* Note that this exception will not be thrown if the isolate has been explicitly closed before a
* call to {@link JavaScriptIsolate#evaluateJavaScriptAsync(String)}, which will instead immediately
* throw an IllegalStateException (and not asynchronously via a future). This applies even if the
* isolate was closed following a crash.
+ * <p>
+ * Do not attempt to parse the information in this exception's message as it may change between
+ * JavaScriptEngine versions.
+ * <p>
+ * Note that it is possible for an isolate to crash outside of submitted evaluations, in which
+ * case an IsolateTerminatedException may not be observed. Consider instead using
+ * {@link JavaScriptIsolate#addOnTerminatedCallback(Executor, Consumer)} if you need to reliably
+ * or immediately detect isolate crashes rather than evaluation failures.
*/
-public final class IsolateTerminatedException extends JavaScriptException {
+public class IsolateTerminatedException extends JavaScriptException {
public IsolateTerminatedException() {
super();
}
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ public IsolateTerminatedException(@NonNull String message) {
+ super(message);
+ }
}
diff --git a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateUsableState.java b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateUsableState.java
index e253c86..8f7f2f9 100644
--- a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateUsableState.java
+++ b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateUsableState.java
@@ -24,6 +24,7 @@
import androidx.annotation.NonNull;
import androidx.concurrent.futures.CallbackToFutureAdapter;
+import androidx.core.util.Consumer;
import androidx.javascriptengine.common.LengthLimitExceededException;
import androidx.javascriptengine.common.Utils;
@@ -37,6 +38,7 @@
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
@@ -65,6 +67,11 @@
@GuardedBy("mLock")
private Set<CallbackToFutureAdapter.Completer<String>> mPendingCompleterSet =
new HashSet<>();
+ // mOnTerminatedCallbacks does not require this.mLock, as all accesses should be performed
+ // whilst holding the mLock of the JavaScriptIsolate that owns this state object.
+ @NonNull
+ private final HashMap<Consumer<TerminationInfo>, Executor> mOnTerminatedCallbacks =
+ new HashMap<>();
private class IJsSandboxIsolateSyncCallbackStubWrapper extends
IJsSandboxIsolateSyncCallback.Stub {
@@ -79,6 +86,9 @@
@Override
public void reportResultWithFd(AssetFileDescriptor afd) {
Objects.requireNonNull(afd);
+ // The completer needs to be removed before offloading to the executor, otherwise there
+ // is a race to complete it if all evaluations are cancelled.
+ removePending(mCompleter);
mJsIsolate.mJsSandbox.mThreadPoolTaskExecutor.execute(
() -> {
String result;
@@ -87,13 +97,11 @@
mMaxEvaluationReturnSizeBytes,
/*truncate=*/false);
} catch (IOException | UnsupportedOperationException ex) {
- removePending(mCompleter);
mCompleter.setException(
new JavaScriptException(
"Retrieving result failed: " + ex.getMessage()));
return;
} catch (LengthLimitExceededException ex) {
- removePending(mCompleter);
if (ex.getMessage() != null) {
mCompleter.setException(
new EvaluationResultSizeLimitExceededException(
@@ -111,6 +119,9 @@
@Override
public void reportErrorWithFd(@ExecutionErrorTypes int type, AssetFileDescriptor afd) {
Objects.requireNonNull(afd);
+ // The completer needs to be removed before offloading to the executor, otherwise there
+ // is a race to complete it if all evaluations are cancelled.
+ removePending(mCompleter);
mJsIsolate.mJsSandbox.mThreadPoolTaskExecutor.execute(
() -> {
String error;
@@ -119,7 +130,6 @@
mMaxEvaluationReturnSizeBytes,
/*truncate=*/true);
} catch (IOException | UnsupportedOperationException ex) {
- removePending(mCompleter);
mCompleter.setException(
new JavaScriptException(
"Retrieving error failed: " + ex.getMessage()));
@@ -144,6 +154,7 @@
@Override
public void reportResult(String result) {
Objects.requireNonNull(result);
+ removePending(mCompleter);
final long identityToken = Binder.clearCallingIdentity();
try {
handleEvaluationResult(mCompleter, result);
@@ -155,6 +166,7 @@
@Override
public void reportError(@ExecutionErrorTypes int type, String error) {
Objects.requireNonNull(error);
+ removePending(mCompleter);
final long identityToken = Binder.clearCallingIdentity();
try {
handleEvaluationError(mCompleter, type, error);
@@ -239,11 +251,12 @@
new IJsSandboxIsolateCallbackStubWrapper(completer);
try {
mJsIsolateStub.evaluateJavascript(code, callbackStub);
- addToPendingCompleterSet(completer);
+ addPending(completer);
} catch (DeadObjectException e) {
// The sandbox process has died.
- mJsIsolate.maybeSetSandboxDead();
- completer.setException(new SandboxDeadException());
+ final TerminationInfo terminationInfo = mJsIsolate.maybeSetSandboxDead();
+ Objects.requireNonNull(terminationInfo);
+ completer.setException(terminationInfo.toJavaScriptException());
} catch (RemoteException e) {
completer.setException(new RuntimeException(e));
}
@@ -309,49 +322,52 @@
} catch (RemoteException e) {
Log.e(TAG, "RemoteException was thrown during close()", e);
}
- cancelAllPendingEvaluations(new IsolateTerminatedException());
+ cancelAllPendingEvaluations(new IsolateTerminatedException("isolate closed"));
}
@Override
- public IsolateState setIsolateDead() {
- IsolateTerminatedException exception = new IsolateTerminatedException();
- cancelAllPendingEvaluations(exception);
- return new EnvironmentDeadState(exception);
+ public boolean canDie() {
+ return true;
}
@Override
- public IsolateState setSandboxDead() {
- SandboxDeadException exception = new SandboxDeadException();
- cancelAllPendingEvaluations(exception);
- return new EnvironmentDeadState(exception);
+ public void onDied(@NonNull TerminationInfo terminationInfo) {
+ cancelAllPendingEvaluations(terminationInfo.toJavaScriptException());
+ mOnTerminatedCallbacks.forEach(
+ (callback, executor) -> executor.execute(() -> callback.accept(terminationInfo)));
}
+ // Caller should call mJsIsolate.removePending(mCompleter) first
void handleEvaluationError(@NonNull CallbackToFutureAdapter.Completer<String> completer,
int type, @NonNull String error) {
- removePending(completer);
- boolean crashing = false;
switch (type) {
case IJsSandboxIsolateSyncCallback.JS_EVALUATION_ERROR:
completer.setException(new EvaluationFailedException(error));
break;
case IJsSandboxIsolateSyncCallback.MEMORY_LIMIT_EXCEEDED:
- completer.setException(new MemoryLimitExceededException(error));
- crashing = true;
+ // Note that we won't ever receive a MEMORY_LIMIT_EXCEEDED evaluation error if
+ // the service side supports termination notifications, so this only handles the
+ // case where it doesn't.
+ final TerminationInfo terminationInfo =
+ new TerminationInfo(TerminationInfo.STATUS_MEMORY_LIMIT_EXCEEDED, error);
+ mJsIsolate.maybeSetIsolateDead(terminationInfo);
+ // The completer was already removed from the set, so we're responsible for it.
+ // Use our exception even if the isolate was already dead or closed. This might
+ // result in an exception which is inconsistent with everything else if there was
+ // a death or close before we called maybeSetIsolateDead above, but that requires
+ // the app to have already set up a race condition.
+ completer.setException(terminationInfo.toJavaScriptException());
break;
default:
completer.setException(new JavaScriptException(
- "Crashing due to unknown JavaScriptException: " + error));
- // Assume the worst
- crashing = true;
- }
- if (crashing) {
- mJsIsolate.maybeSetIsolateDead();
+ "Unknown error: code " + type + ": " + error));
+ break;
}
}
+ // Caller should call mJsIsolate.removePending(mCompleter) first
void handleEvaluationResult(@NonNull CallbackToFutureAdapter.Completer<String> completer,
@NonNull String result) {
- removePending(completer);
completer.set(result);
}
@@ -361,7 +377,7 @@
}
}
- void addToPendingCompleterSet(@NonNull CallbackToFutureAdapter.Completer<String> completer) {
+ void addPending(@NonNull CallbackToFutureAdapter.Completer<String> completer) {
synchronized (mLock) {
mPendingCompleterSet.add(completer);
}
@@ -394,11 +410,12 @@
mJsIsolateStub.evaluateJavascriptWithFd(codeAfd,
callbackStub);
}
- addToPendingCompleterSet(completer);
+ addPending(completer);
} catch (DeadObjectException e) {
// The sandbox process has died.
- mJsIsolate.maybeSetSandboxDead();
- completer.setException(new SandboxDeadException());
+ final TerminationInfo terminationInfo = mJsIsolate.maybeSetSandboxDead();
+ Objects.requireNonNull(terminationInfo);
+ completer.setException(terminationInfo.toJavaScriptException());
} catch (RemoteException | IOException e) {
completer.setException(new RuntimeException(e));
}
@@ -406,4 +423,19 @@
return futureDebugMessage;
});
}
+
+ @Override
+ public void addOnTerminatedCallback(@NonNull Executor executor,
+ @NonNull Consumer<TerminationInfo> callback) {
+ if (mOnTerminatedCallbacks.putIfAbsent(callback, executor) != null) {
+ throw new IllegalStateException("Termination callback already registered");
+ }
+ }
+
+ @Override
+ public void removeOnTerminatedCallback(@NonNull Consumer<TerminationInfo> callback) {
+ synchronized (mLock) {
+ mOnTerminatedCallbacks.remove(callback);
+ }
+ }
}
diff --git a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/JavaScriptIsolate.java b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/JavaScriptIsolate.java
index df63307..ab19538 100644
--- a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/JavaScriptIsolate.java
+++ b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/JavaScriptIsolate.java
@@ -16,12 +16,20 @@
package androidx.javascriptengine;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.util.Log;
+
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.RequiresFeature;
+import androidx.annotation.RestrictTo;
+import androidx.core.util.Consumer;
import com.google.common.util.concurrent.ListenableFuture;
import org.chromium.android_webview.js_sandbox.common.IJsSandboxIsolate;
+import org.chromium.android_webview.js_sandbox.common.IJsSandboxIsolateClient;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -49,44 +57,106 @@
private final Object mLock = new Object();
private final CloseGuardHelper mGuard = CloseGuardHelper.create();
+ @NonNull
final JavaScriptSandbox mJsSandbox;
@GuardedBy("mLock")
@NonNull
private IsolateState mIsolateState;
- JavaScriptIsolate(@NonNull IJsSandboxIsolate jsIsolateStub, @NonNull JavaScriptSandbox sandbox,
- @NonNull IsolateStartupParameters settings) {
+ private final class JsSandboxIsolateClient extends IJsSandboxIsolateClient.Stub {
+ JsSandboxIsolateClient() {}
+
+ @Override
+ public void onTerminated(int status, String message) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ // If we're already closed, this will do nothing
+ maybeSetIsolateDead(new TerminationInfo(status, message));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ @NonNull
+ static JavaScriptIsolate create(@NonNull JavaScriptSandbox sandbox,
+ IsolateStartupParameters settings) throws RemoteException {
+ final JavaScriptIsolate isolate = new JavaScriptIsolate(sandbox);
+ isolate.initialize(settings);
+ return isolate;
+ }
+
+ private JavaScriptIsolate(@NonNull JavaScriptSandbox sandbox) {
+ mJsSandbox = sandbox;
synchronized (mLock) {
+ mIsolateState = new IsolateClosedState("isolate not initialized");
+ }
+ }
+
+ // Create an isolate on the service side and complete initialization.
+ // This is done outside of the constructor to avoid leaking a partially constructed
+ // JavaScriptIsolate to the service (which would complicate thread-safety).
+ private void initialize(@NonNull IsolateStartupParameters settings) throws RemoteException {
+ synchronized (mLock) {
+ final IJsSandboxIsolateClient instanceCallback;
+ if (mJsSandbox.isFeatureSupported(
+ JavaScriptSandbox.JS_FEATURE_ISOLATE_CLIENT)) {
+ instanceCallback = new JsSandboxIsolateClient();
+ } else {
+ instanceCallback = null;
+ }
+ IJsSandboxIsolate jsIsolateStub = mJsSandbox.createIsolateOnService(settings,
+ instanceCallback);
mIsolateState = new IsolateUsableState(this, jsIsolateStub,
settings.getMaxEvaluationReturnSizeBytes());
+ mGuard.open("close");
}
- mJsSandbox = sandbox;
- mGuard.open("close");
- // This should be at the end of the constructor.
}
/**
* Changes the state to denote that the isolate is dead.
- *
+ * <p>
* {@link IsolateClosedState} takes precedence so it will not change state if the current state
- * is {@link IsolateClosedState}
+ * is {@link IsolateClosedState}.
+ * <p>
+ * If the isolate is already dead, the existing dead state is preserved.
+ *
+ * @return true iff the state was changed to a new EnvironmentDeadState
*/
- void maybeSetIsolateDead() {
+ boolean maybeSetIsolateDead(@NonNull TerminationInfo terminationInfo) {
synchronized (mLock) {
- mIsolateState = mIsolateState.setIsolateDead();
+ if (terminationInfo.getStatus() == TerminationInfo.STATUS_MEMORY_LIMIT_EXCEEDED) {
+ Log.e(TAG, "isolate exceeded its heap memory limit - killing sandbox");
+ mJsSandbox.kill();
+ }
+ final IsolateState oldState = mIsolateState;
+ if (oldState.canDie()) {
+ mIsolateState = new EnvironmentDeadState(terminationInfo);
+ oldState.onDied(terminationInfo);
+ return true;
+ }
}
+ return false;
}
/**
* Changes the state to denote that the sandbox is dead.
+ * <p>
+ * See {@link #maybeSetIsolateDead(TerminationInfo)} for additional information.
*
- * {@link IsolateClosedState} takes precedence so it will not change state if the current state
- * is {@link IsolateClosedState}
+ * @return the generated termination info if it was set, or null if the state did not change.
*/
- void maybeSetSandboxDead() {
+ @Nullable
+ TerminationInfo maybeSetSandboxDead() {
synchronized (mLock) {
- mIsolateState = mIsolateState.setSandboxDead();
+ final TerminationInfo terminationInfo =
+ new TerminationInfo(TerminationInfo.STATUS_SANDBOX_DEAD, "sandbox dead");
+ if (maybeSetIsolateDead(terminationInfo)) {
+ return terminationInfo;
+ } else {
+ return null;
+ }
}
}
@@ -143,23 +213,28 @@
/**
* Closes the {@link JavaScriptIsolate} object and renders it unusable.
* <p>
- * Once closed, no more method calls should be made. Pending evaluations resolve with
- * {@link IsolateTerminatedException} immediately.
+ * Once closed, no more method calls should be made. Pending evaluations will reject with
+ * an {@link IsolateTerminatedException} immediately.
* <p>
* If {@link JavaScriptSandbox#isFeatureSupported(String)} is {@code true} for {@link
- * JavaScriptSandbox#JS_FEATURE_ISOLATE_TERMINATION}, then any pending evaluation is immediately
- * terminated and memory is freed. If it is {@code false}, the isolate will not get cleaned
+ * JavaScriptSandbox#JS_FEATURE_ISOLATE_TERMINATION}, then any pending evaluations are
+ * terminated. If it is {@code false}, the isolate will not get cleaned
* up until the pending evaluations have run to completion and will consume resources until
* then.
+ * <p>
+ * Closing an isolate via this method does not wait on the isolate to clean up. Resources
+ * held by the isolate may remain in use for a duration after this method returns.
*/
@Override
public void close() {
synchronized (mLock) {
mIsolateState.close();
- mIsolateState = new IsolateClosedState();
- mJsSandbox.removeFromIsolateSet(this);
- mGuard.close();
+ mIsolateState = new IsolateClosedState("isolate closed");
}
+ // Do not hold mLock whilst calling into JavaScriptSandbox, as JavaScriptSandbox also has
+ // its own lock and may want to call into JavaScriptIsolate from another thread.
+ mJsSandbox.removeFromIsolateSet(this);
+ mGuard.close();
}
/**
@@ -296,4 +371,58 @@
mIsolateState.clearConsoleCallback();
}
}
+
+ /**
+ * Add a callback to listen for isolate crashes.
+ * <p>
+ * There is no guaranteed order to when these callbacks are triggered and unfinished
+ * evaluations' futures are rejected.
+ * <p>
+ * Registering a callback after the isolate has crashed will result in it being executed
+ * immediately on the supplied executor with the isolate's {@link TerminationInfo} as an
+ * argument.
+ * <p>
+ * Closing an isolate via {@link #close()} is not considered a crash, even if there are
+ * unresolved evaluations, and will not trigger termination callbacks.
+ *
+ * @param executor Executor with which to run callback.
+ * @param callback Consumer to be called with TerminationInfo when a crash occurs.
+ * @throws IllegalStateException if the callback is already registered (using any executor).
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ public void addOnTerminatedCallback(@NonNull Executor executor,
+ @NonNull Consumer<TerminationInfo> callback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+ synchronized (mLock) {
+ mIsolateState.addOnTerminatedCallback(executor, callback);
+ }
+ }
+
+ /**
+ * Add a callback to listen for isolate crashes.
+ * <p>
+ * This is the same as calling {@link #addOnTerminatedCallback(Executor, Consumer)} using the
+ * main executor of the context used to create the {@link JavaScriptSandbox} object.
+ *
+ * @param callback Consumer to be called with TerminationInfo when a crash occurs.
+ * @throws IllegalStateException if the callback is already registered (using any executor).
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ public void addOnTerminatedCallback(@NonNull Consumer<TerminationInfo> callback) {
+ addOnTerminatedCallback(mJsSandbox.getMainExecutor(), callback);
+ }
+
+ /**
+ * Remove a callback previously registered with addOnTerminatedCallback.
+ *
+ * @param callback The callback to unregister, if currently registered.
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ public void removeOnTerminatedCallback(@NonNull Consumer<TerminationInfo> callback) {
+ Objects.requireNonNull(callback);
+ synchronized (mLock) {
+ mIsolateState.removeOnTerminatedCallback(callback);
+ }
+ }
}
diff --git a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/JavaScriptSandbox.java b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/JavaScriptSandbox.java
index 834502a..8a5c048 100644
--- a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/JavaScriptSandbox.java
+++ b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/JavaScriptSandbox.java
@@ -37,21 +37,25 @@
import com.google.common.util.concurrent.ListenableFuture;
import org.chromium.android_webview.js_sandbox.common.IJsSandboxIsolate;
+import org.chromium.android_webview.js_sandbox.common.IJsSandboxIsolateClient;
import org.chromium.android_webview.js_sandbox.common.IJsSandboxService;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.concurrent.GuardedBy;
@@ -88,14 +92,24 @@
private final Object mLock = new Object();
private final CloseGuardHelper mGuard = CloseGuardHelper.create();
+ // This is null iff the sandbox is closed.
@Nullable
@GuardedBy("mLock")
private IJsSandboxService mJsSandboxService;
- private final ConnectionSetup mConnection;
+ // Don't use mLock for the connection, allowing it to be severed at any time, regardless of
+ // the status of the main mLock. Use an AtomicReference instead.
+ //
+ // The underlying ConnectionSetup is nullable, and is null iff the service has been unbound
+ // (which should also imply dead or closed).
+ @NonNull
+ private final AtomicReference<ConnectionSetup> mConnection;
+ @NonNull
+ private final Context mContext;
@GuardedBy("mLock")
- private final HashSet<JavaScriptIsolate> mActiveIsolateSet = new HashSet<>();
+ @NonNull
+ private Set<JavaScriptIsolate> mActiveIsolateSet;
final ExecutorService mThreadPoolTaskExecutor =
Executors.newCachedThreadPool(new ThreadFactory() {
@@ -120,6 +134,7 @@
JS_FEATURE_ISOLATE_MAX_HEAP_SIZE,
JS_FEATURE_EVALUATE_WITHOUT_TRANSACTION_LIMIT,
JS_FEATURE_CONSOLE_MESSAGING,
+ JS_FEATURE_ISOLATE_CLIENT,
})
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.PARAMETER, ElementType.METHOD})
@@ -198,6 +213,15 @@
*/
public static final String JS_FEATURE_CONSOLE_MESSAGING = "JS_FEATURE_CONSOLE_MESSAGING";
+ /**
+ * Feature for {@link #isFeatureSupported(String)}.
+ * <p>
+ * When this feature is present, the service can be provided with a Binder interface for
+ * calling into the client, independent of callbacks.
+ */
+ static final String JS_FEATURE_ISOLATE_CLIENT =
+ "JS_FEATURE_ISOLATE_CLIENT";
+
// This set must not be modified after JavaScriptSandbox construction.
@NonNull
private final HashSet<String> mClientSideFeatureSet;
@@ -207,7 +231,8 @@
private CallbackToFutureAdapter.Completer<JavaScriptSandbox> mCompleter;
@Nullable
private JavaScriptSandbox mJsSandbox;
- final Context mContext;
+ @NonNull
+ private final Context mContext;
@Override
@SuppressWarnings("NullAway")
@@ -222,7 +247,7 @@
IJsSandboxService jsSandboxService =
IJsSandboxService.Stub.asInterface(service);
try {
- mJsSandbox = new JavaScriptSandbox(this, jsSandboxService);
+ mJsSandbox = new JavaScriptSandbox(mContext, this, jsSandboxService);
} catch (RemoteException e) {
runShutdownTasks(e);
return;
@@ -386,12 +411,14 @@
// We prevent direct initializations of this class.
// Use JavaScriptSandbox.createConnectedInstance().
- JavaScriptSandbox(@NonNull ConnectionSetup connectionSetup,
+ JavaScriptSandbox(@NonNull Context context, @NonNull ConnectionSetup connectionSetup,
@NonNull IJsSandboxService jsSandboxService) throws RemoteException {
- mConnection = connectionSetup;
+ mContext = context;
+ mConnection = new AtomicReference<>(connectionSetup);
mJsSandboxService = jsSandboxService;
final List<String> features = mJsSandboxService.getSupportedFeatures();
mClientSideFeatureSet = buildClientSideFeatureSet(features);
+ mActiveIsolateSet = new HashSet<>();
mGuard.open("close");
// This should be at the end of the constructor.
}
@@ -415,27 +442,39 @@
public JavaScriptIsolate createIsolate(@NonNull IsolateStartupParameters settings) {
Objects.requireNonNull(settings);
synchronized (mLock) {
- if (mJsSandboxService == null) {
+ // TODO(b/290750354, b/290749782): Implement separate closed vs dead state and use
+ // that instead of a mConnection nullness check.
+ if (mJsSandboxService == null || mConnection.get() == null) {
throw new IllegalStateException(
"Attempting to createIsolate on a service that isn't connected");
}
- IJsSandboxIsolate isolateStub;
+ final JavaScriptIsolate isolate;
try {
- if (settings.getMaxHeapSizeBytes()
- == IsolateStartupParameters.DEFAULT_ISOLATE_HEAP_SIZE) {
- isolateStub = mJsSandboxService.createIsolate();
- } else {
- isolateStub = mJsSandboxService.createIsolateWithMaxHeapSizeBytes(
- settings.getMaxHeapSizeBytes());
- if (isolateStub == null) {
- throw new RuntimeException(
- "Service implementation doesn't support setting maximum heap size");
- }
- }
+ isolate = JavaScriptIsolate.create(this, settings);
} catch (RemoteException e) {
+ // TODO(b/286055647): Handle sandbox dying during createIsolate more sensibly.
throw new RuntimeException(e);
}
- return createJsIsolateLocked(isolateStub, settings);
+ mActiveIsolateSet.add(isolate);
+ return isolate;
+ }
+ }
+
+ // In practice, this method should only be called whilst already holding mLock, but it is
+ // called via JavaScriptIsolate and this constraint cannot be cleanly expressed via GuardedBy.
+ IJsSandboxIsolate createIsolateOnService(@NonNull IsolateStartupParameters settings,
+ @Nullable IJsSandboxIsolateClient isolateInstanceCallback) throws RemoteException {
+ synchronized (mLock) {
+ Objects.requireNonNull(mJsSandboxService);
+ if (isFeatureSupported(JavaScriptSandbox.JS_FEATURE_ISOLATE_CLIENT)) {
+ return mJsSandboxService.createIsolate2(settings.getMaxHeapSizeBytes(),
+ isolateInstanceCallback);
+ } else if (isFeatureSupported(JavaScriptSandbox.JS_FEATURE_ISOLATE_MAX_HEAP_SIZE)) {
+ return mJsSandboxService.createIsolateWithMaxHeapSizeBytes(
+ settings.getMaxHeapSizeBytes());
+ } else {
+ return mJsSandboxService.createIsolate();
+ }
}
}
@@ -459,19 +498,12 @@
if (features.contains(IJsSandboxService.CONSOLE_MESSAGING)) {
featureSet.add(JS_FEATURE_CONSOLE_MESSAGING);
}
+ if (features.contains(IJsSandboxService.ISOLATE_CLIENT + ":DEV")) {
+ featureSet.add(JS_FEATURE_ISOLATE_CLIENT);
+ }
return featureSet;
}
- @GuardedBy("mLock")
- @NonNull
- @SuppressWarnings("NullAway")
- private JavaScriptIsolate createJsIsolateLocked(@NonNull IJsSandboxIsolate isolateStub,
- @NonNull IsolateStartupParameters settings) {
- JavaScriptIsolate isolate = new JavaScriptIsolate(isolateStub, this, settings);
- mActiveIsolateSet.add(isolate);
- return isolate;
- }
-
/**
* Checks whether a given feature is supported by the JS Sandbox implementation.
* <p>
@@ -512,28 +544,51 @@
if (mJsSandboxService == null) {
return;
}
- // This is the closest thing to a .close() method for ExecutorServices. This doesn't
- // force the threads or their Runnables to immediately terminate, but will ensure
- // that once the
- // worker threads finish their current runnable (if any) that the thread pool terminates
- // them, preventing a leak of threads.
- mThreadPoolTaskExecutor.shutdownNow();
- notifyIsolatesAboutClosureLocked();
- mConnection.mContext.unbindService(mConnection);
+ unbindService();
// Currently we consider that we are ready for a new connection once we unbind. This
// might not be true if the process is not immediately killed by ActivityManager once it
// is unbound.
sIsReadyToConnect.set(true);
mJsSandboxService = null;
}
+ notifyIsolatesAboutClosure();
+ // This is the closest thing to a .close() method for ExecutorServices. This doesn't
+ // force the threads or their Runnables to immediately terminate, but will ensure
+ // that once the worker threads finish their current runnable (if any) that the thread
+ // pool terminates them, preventing a leak of threads.
+ mThreadPoolTaskExecutor.shutdownNow();
}
- @GuardedBy("mLock")
- private void notifyIsolatesAboutClosureLocked() {
- for (JavaScriptIsolate ele : mActiveIsolateSet) {
- ele.maybeSetSandboxDead();
+ // Unbind the service if it hasn't been unbound already.
+ private void unbindService() {
+ final ConnectionSetup connection = mConnection.getAndSet(null);
+ if (connection != null) {
+ mContext.unbindService(connection);
}
- mActiveIsolateSet.clear();
+ }
+
+ // Kill the sandbox immediately.
+ //
+ // This will unbind the sandbox service so that any future IPC will fail immediately.
+ // However, isolates will be notified asynchronously, from the main thread.
+ void kill() {
+ unbindService();
+ // TODO(b/290750354, b/290749782): Implement separate closed vs dead state.
+ getMainExecutor().execute(this::close);
+ }
+
+ private void notifyIsolatesAboutClosure() {
+ // Do not hold mLock whilst calling into JavaScriptIsolate, as JavaScriptIsolate also has
+ // its own lock and may want to call into JavaScriptSandbox from another thread.
+ final Set<JavaScriptIsolate> activeIsolateSet;
+ synchronized (mLock) {
+ activeIsolateSet = mActiveIsolateSet;
+ mActiveIsolateSet = Collections.emptySet();
+ }
+ for (JavaScriptIsolate isolate : activeIsolateSet) {
+ isolate.maybeSetIsolateDead(new TerminationInfo(TerminationInfo.STATUS_SANDBOX_DEAD,
+ "sandbox closed"));
+ }
}
@Override
@@ -553,6 +608,6 @@
@NonNull
Executor getMainExecutor() {
- return ContextCompat.getMainExecutor(mConnection.mContext);
+ return ContextCompat.getMainExecutor(mContext);
}
}
diff --git a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/MemoryLimitExceededException.java b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/MemoryLimitExceededException.java
index f4330cc..415b2d7 100644
--- a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/MemoryLimitExceededException.java
+++ b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/MemoryLimitExceededException.java
@@ -17,31 +17,34 @@
package androidx.javascriptengine;
import androidx.annotation.NonNull;
+import androidx.core.util.Consumer;
+
+import java.util.concurrent.Executor;
/**
- * Indicates that a JavaScriptIsolate's evaluation failed due to exceeding its heap size limit.
+ * Indicates that a JavaScriptIsolate's evaluation failed due to the isolate exceeding its heap
+ * size limit.
* <p>
* This exception may be thrown when exceeding the heap size limit configured for the isolate via
* {@link IsolateStartupParameters}, or the default limit. Beware that it will not be thrown if the
* Android system as a whole has run out of memory before the JavaScript environment has reached
* its configured heap limit.
* <p>
- * The isolate may not continue to be used after this exception has been thrown, and other pending
- * evaluations for the isolate will fail. The isolate may continue to hold onto resources (even if
- * explicitly closed) until the sandbox has been shutdown. Therefore, it is recommended that the
- * sandbox be restarted at the earliest opportunity in order to reclaim these resources.
+ * If an evaluation fails with a MemoryLimitExceededException, it does not imply that that
+ * particular evaluation was in any way responsible for any excessive memory usage.
+ * MemoryLimitExceededException will be raised for all unresolved and future requested evaluations
+ * regardless of their culpability.
* <p>
- * Other isolates within the same sandbox may continue to be used, created, and closed as normal.
- * <p>
- * Beware that not all JavaScript sandbox service implementations (particularly older ones)
- * handle memory exhaustion equally gracefully, and may instead crash the entire sandbox (see
- * {@link SandboxDeadException}).
+ * An isolate may run out of memory outside of an explicit evaluation (such as in a microtask), so
+ * you should generally not use this exception to detect out of memory issues - instead, use
+ * {@link JavaScriptIsolate#addOnTerminatedCallback(Executor, Consumer)} and check
+ * for an isolate termination status of {@link TerminationInfo#STATUS_MEMORY_LIMIT_EXCEEDED}.
*/
-public final class MemoryLimitExceededException extends JavaScriptException {
- public MemoryLimitExceededException(@NonNull String error) {
- super(error);
- }
+public final class MemoryLimitExceededException extends IsolateTerminatedException {
public MemoryLimitExceededException() {
super();
}
+ public MemoryLimitExceededException(@NonNull String error) {
+ super(error);
+ }
}
diff --git a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/SandboxDeadException.java b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/SandboxDeadException.java
index 229dee08..f3439b7 100644
--- a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/SandboxDeadException.java
+++ b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/SandboxDeadException.java
@@ -16,19 +16,20 @@
package androidx.javascriptengine;
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+
/**
* Exception thrown when evaluation is terminated due the {@link JavaScriptSandbox} being dead.
* This can happen when {@link JavaScriptSandbox#close()} is called or when the sandbox process
* is killed by the framework.
- * <p>
- * This is different from {@link IsolateTerminatedException} which occurs when the
- * {@link JavaScriptIsolate} within a {@link JavaScriptSandbox} is terminated.
- * <p>
- * This exception will continue to be thrown for all future evaluation requests on unclosed
- * isolates.
*/
-public final class SandboxDeadException extends JavaScriptException {
+public final class SandboxDeadException extends IsolateTerminatedException {
public SandboxDeadException() {
super();
}
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ public SandboxDeadException(@NonNull String message) {
+ super(message);
+ }
}
diff --git a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/TerminationInfo.java b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/TerminationInfo.java
new file mode 100644
index 0000000..fc828de
--- /dev/null
+++ b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/TerminationInfo.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.javascriptengine;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+
+import org.chromium.android_webview.js_sandbox.common.IJsSandboxIsolateClient;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Information about how and why an isolate has terminated.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class TerminationInfo {
+ /**
+ * Termination status code for an isolate.
+ */
+ @IntDef({STATUS_UNKNOWN_ERROR, STATUS_SANDBOX_DEAD, STATUS_MEMORY_LIMIT_EXCEEDED})
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Status {
+ }
+
+ /**
+ * The isolate (but not necessarily the sandbox) has crashed for an unknown reason.
+ */
+ public static final int STATUS_UNKNOWN_ERROR = IJsSandboxIsolateClient.TERMINATE_UNKNOWN_ERROR;
+ /**
+ * The whole sandbox died (or was closed), taking this isolate with it.
+ */
+ public static final int STATUS_SANDBOX_DEAD = IJsSandboxIsolateClient.TERMINATE_SANDBOX_DEAD;
+ /**
+ * The isolate exceeded its heap size limit.
+ * <p>
+ * The isolate may continue to hold onto resources (even if explicitly closed) until the
+ * sandbox has been shutdown. If necessary, restart the sandbox at the earliest opportunity in
+ * order to reclaim these resources.
+ * <p>
+ * Note that memory exhaustion will kill the whole sandbox, so any other isolates within the
+ * same sandbox will be terminated with {@link #STATUS_SANDBOX_DEAD}.
+ */
+ public static final int STATUS_MEMORY_LIMIT_EXCEEDED =
+ IJsSandboxIsolateClient.TERMINATE_MEMORY_LIMIT_EXCEEDED;
+ @Status
+ private final int mStatus;
+ @NonNull
+ private final String mMessage;
+
+ TerminationInfo(@Status int status, @NonNull String message) {
+ mStatus = status;
+ mMessage = message;
+ }
+
+ /**
+ * Get the status code of the termination.
+ * <p>
+ * New status codes may be added with new JavaScriptEngine versions.
+ *
+ * @return status code of the termination.
+ */
+ @Status
+ public int getStatus() {
+ return mStatus;
+ }
+
+ /**
+ * Describe the status code of the termination.
+ * These strings are not stable between JavaScriptEngine versions.
+ *
+ * @return description of status code of the termination.
+ */
+ @NonNull
+ public String getStatusString() {
+ switch (mStatus) {
+ case STATUS_UNKNOWN_ERROR:
+ return "unknown error";
+ case STATUS_SANDBOX_DEAD:
+ return "sandbox dead";
+ case STATUS_MEMORY_LIMIT_EXCEEDED:
+ return "memory limit exceeded";
+ default:
+ return "unknown error code " + mStatus;
+ }
+ }
+
+ /**
+ * Get the message associated with this termination.
+ * The content or format of these messages is not stable between JavaScriptEngine versions.
+ *
+ * @return Human-readable message about the termination.
+ */
+ @NonNull
+ public String getMessage() {
+ return mMessage;
+ }
+
+ /**
+ * Describe the termination.
+ * The content or format of this description is not stable between JavaScriptEngine versions.
+ *
+ * @return Human-readable description of the termination.
+ */
+ @NonNull
+ @Override
+ public String toString() {
+ return getStatusString() + ": " + getMessage();
+ }
+
+ @NonNull
+ IsolateTerminatedException toJavaScriptException() {
+ switch (mStatus) {
+ case STATUS_SANDBOX_DEAD:
+ return new SandboxDeadException(this.toString());
+ case STATUS_MEMORY_LIMIT_EXCEEDED:
+ return new MemoryLimitExceededException(this.toString());
+ default:
+ return new IsolateTerminatedException(this.toString());
+ }
+ }
+}
diff --git a/javascriptengine/javascriptengine/src/main/stableAidl/org/chromium/android_webview/js_sandbox/common/IJsSandboxIsolateClient.aidl b/javascriptengine/javascriptengine/src/main/stableAidl/org/chromium/android_webview/js_sandbox/common/IJsSandboxIsolateClient.aidl
new file mode 100644
index 0000000..6574ae4
--- /dev/null
+++ b/javascriptengine/javascriptengine/src/main/stableAidl/org/chromium/android_webview/js_sandbox/common/IJsSandboxIsolateClient.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.chromium.android_webview.js_sandbox.common;
+
+/**
+ * Callbacks for isolate events, not specific to evaluations.
+ * @hide
+ */
+interface IJsSandboxIsolateClient {
+ // These crash codes may be generated on either the client or service side.
+
+ // The isolate terminated for an unknown reason.
+ const int TERMINATE_UNKNOWN_ERROR = 1;
+ // The sandbox died.
+ //
+ // This is typically generated client-side as the service may die before it gets a chance to
+ // send a message to the client.
+ const int TERMINATE_SANDBOX_DEAD = 2;
+ // The isolate exceeded its heap size limit.
+ const int TERMINATE_MEMORY_LIMIT_EXCEEDED = 3;
+
+ /**
+ * Informs the client that the isolate should now be considered terminated.
+ *
+ * @param status A status code describing the reason for the termination. Must be one of the
+ * constants beginning "TERMINATE_".
+ * @param message Unstructured information about the termination. May be null.
+ */
+ void onTerminated(int status, String message) = 1;
+}
diff --git a/javascriptengine/javascriptengine/src/main/stableAidl/org/chromium/android_webview/js_sandbox/common/IJsSandboxService.aidl b/javascriptengine/javascriptengine/src/main/stableAidl/org/chromium/android_webview/js_sandbox/common/IJsSandboxService.aidl
index 0453988..8afe48c 100644
--- a/javascriptengine/javascriptengine/src/main/stableAidl/org/chromium/android_webview/js_sandbox/common/IJsSandboxService.aidl
+++ b/javascriptengine/javascriptengine/src/main/stableAidl/org/chromium/android_webview/js_sandbox/common/IJsSandboxService.aidl
@@ -16,6 +16,7 @@
package org.chromium.android_webview.js_sandbox.common;
import org.chromium.android_webview.js_sandbox.common.IJsSandboxIsolate;
+import org.chromium.android_webview.js_sandbox.common.IJsSandboxIsolateClient;
/**
* Used by the embedding app to execute JavaScript in a sandboxed environment.
@@ -62,9 +63,21 @@
const String CONSOLE_MESSAGING = "CONSOLE_MESSAGING";
/**
+ * Feature flag indicating that the client may provide the service side with an
+ * IJsSandboxIsolateClient, allowing the service to call into the client regardless of ongoing
+ * evaluations.
+ */
+ const String ISOLATE_CLIENT = "ISOLATE_CLIENT";
+
+ /**
* @return A list of feature names supported by this implementation.
*/
List<String> getSupportedFeatures() = 1;
IJsSandboxIsolate createIsolateWithMaxHeapSizeBytes(long maxHeapSize) = 2;
+
+ /**
+ * Create an isolate with a given heap size and service-to-client interface.
+ */
+ IJsSandboxIsolate createIsolate2(long maxHeapSize, IJsSandboxIsolateClient isolateClient) = 3;
}
diff --git a/libraryversions.toml b/libraryversions.toml
index 0e3bb10..55a1d9a 100644
--- a/libraryversions.toml
+++ b/libraryversions.toml
@@ -44,6 +44,7 @@
CORE_TELECOM = "1.0.0-alpha01"
CORE_UWB = "1.0.0-alpha08"
CREDENTIALS = "1.2.0-beta04"
+CREDENTIALS_FIDO_QUARANTINE = "1.0.0-alpha01"
CURSORADAPTER = "1.1.0-alpha01"
CUSTOMVIEW = "1.2.0-alpha03"
CUSTOMVIEW_POOLINGCONTAINER = "1.1.0-alpha01"
@@ -201,6 +202,7 @@
CORE_HAPTICS = { group = "androidx.core.haptics", atomicGroupVersion = "versions.CORE_HAPTICS" }
CORE_UWB = { group = "androidx.core.uwb", atomicGroupVersion = "versions.CORE_UWB" }
CREDENTIALS = { group = "androidx.credentials", atomicGroupVersion = "versions.CREDENTIALS" }
+CREDENTIALS_FIDO = { group = "androidx.credentials.credentials-fido", atomicGroupVersion = "versions.CREDENTIALS_FIDO_QUARANTINE" }
CURSORADAPTER = { group = "androidx.cursoradapter", atomicGroupVersion = "versions.CURSORADAPTER" }
CUSTOMVIEW = { group = "androidx.customview" }
DATASTORE = { group = "androidx.datastore", atomicGroupVersion = "versions.DATASTORE" }
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/build.gradle b/privacysandbox/sdkruntime/sdkruntime-client/build.gradle
index 5de128a..e8985b4 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/build.gradle
+++ b/privacysandbox/sdkruntime/sdkruntime-client/build.gradle
@@ -92,7 +92,6 @@
dependencies {
api(libs.kotlinStdlib)
api(libs.kotlinCoroutinesCore)
- implementation(libs.multidex)
implementation("androidx.core:core-ktx:1.12.0-alpha05")
api project(path: ':privacysandbox:sdkruntime:sdkruntime-core')
diff --git a/privacysandbox/ui/ui-provider/src/main/java/androidx/privacysandbox/ui/provider/BinderAdapterDelegate.kt b/privacysandbox/ui/ui-provider/src/main/java/androidx/privacysandbox/ui/provider/BinderAdapterDelegate.kt
index 1acb8cc..6639148 100644
--- a/privacysandbox/ui/ui-provider/src/main/java/androidx/privacysandbox/ui/provider/BinderAdapterDelegate.kt
+++ b/privacysandbox/ui/ui-provider/src/main/java/androidx/privacysandbox/ui/provider/BinderAdapterDelegate.kt
@@ -26,6 +26,7 @@
import android.os.Handler
import android.os.IBinder
import android.os.Looper
+import android.util.Log
import android.view.SurfaceControlViewHost
import androidx.annotation.RequiresApi
import androidx.annotation.VisibleForTesting
@@ -56,6 +57,11 @@
private val adapter: SandboxedUiAdapter
) : ISandboxedUiAdapter.Stub() {
+ companion object {
+ private const val TAG = "BinderAdapterDelegate"
+ private const val FRAME_TIMEOUT_MILLIS = 1000.toLong()
+ }
+
fun openSession(
context: Context,
windowInputToken: IBinder,
@@ -121,13 +127,25 @@
surfaceControlViewHost.setView(view, initialWidth, initialHeight)
}
- val surfacePackage = surfaceControlViewHost.surfacePackage
- val remoteSessionController =
- RemoteSessionController(surfaceControlViewHost, session)
- remoteSessionClient.onRemoteSessionOpened(
- surfacePackage, remoteSessionController,
- /* isZOrderOnTop= */ true
- )
+ // This var is not locked as it will be set to false by the first event that can trigger
+ // sending the remote session opened callback.
+ var alreadyOpenedSession = false
+ view.viewTreeObserver.registerFrameCommitCallback {
+ if (!alreadyOpenedSession) {
+ alreadyOpenedSession = true
+ sendRemoteSessionOpened(session)
+ }
+ }
+
+ // If a frame commit callback is not triggered within the timeout (such as when the
+ // screen is off), open the session anyway.
+ Handler(Looper.getMainLooper()).postDelayed({
+ if (!alreadyOpenedSession) {
+ Log.w(TAG, "Frame not committed within $FRAME_TIMEOUT_MILLIS ms.")
+ alreadyOpenedSession = true
+ sendRemoteSessionOpened(session)
+ }
+ }, FRAME_TIMEOUT_MILLIS)
}
override fun onSessionError(throwable: Throwable) {
@@ -138,6 +156,16 @@
remoteSessionClient.onResizeRequested(width, height)
}
+ private fun sendRemoteSessionOpened(session: SandboxedUiAdapter.Session) {
+ val surfacePackage = surfaceControlViewHost.surfacePackage
+ val remoteSessionController =
+ RemoteSessionController(surfaceControlViewHost, session)
+ remoteSessionClient.onRemoteSessionOpened(
+ surfacePackage, remoteSessionController,
+ /* isZOrderOnTop= */ true
+ )
+ }
+
@VisibleForTesting
private inner class RemoteSessionController(
val surfaceControlViewHost: SurfaceControlViewHost,
diff --git a/privacysandbox/ui/ui-tests/src/androidTest/java/androidx/privacysandbox/ui/tests/endtoend/IntegrationTests.kt b/privacysandbox/ui/ui-tests/src/androidTest/java/androidx/privacysandbox/ui/tests/endtoend/IntegrationTests.kt
index ae63942..e1ba4e8 100644
--- a/privacysandbox/ui/ui-tests/src/androidTest/java/androidx/privacysandbox/ui/tests/endtoend/IntegrationTests.kt
+++ b/privacysandbox/ui/ui-tests/src/androidTest/java/androidx/privacysandbox/ui/tests/endtoend/IntegrationTests.kt
@@ -39,6 +39,7 @@
import androidx.test.filters.MediumTest
import androidx.test.platform.app.InstrumentationRegistry
import androidx.testutils.withActivity
+import com.google.common.truth.Truth.assertThat
import java.util.concurrent.CountDownLatch
import java.util.concurrent.Executor
import java.util.concurrent.TimeUnit
@@ -77,7 +78,7 @@
errorLatch = CountDownLatch(1)
stateChangeListener = TestStateChangeListener(errorLatch)
view.addStateChangedListener(stateChangeListener)
- activity.runOnUiThread(Runnable {
+ activity.runOnUiThread {
val linearLayout = LinearLayout(context)
linearLayout.layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
@@ -86,7 +87,7 @@
activity.setContentView(linearLayout)
view.layoutParams = LinearLayout.LayoutParams(100, 100)
linearLayout.addView(view)
- })
+ }
}
@Ignore // b/271299184
@@ -166,7 +167,7 @@
val adapter = TestSandboxedUiAdapter(openSessionLatch, null, false)
val coreLibInfo = adapter.toCoreLibInfo(context)
val adapterFromCoreLibInfo = SandboxedUiAdapterFactory.createFromCoreLibInfo(coreLibInfo)
- var testSessionClient = TestSandboxedUiAdapter.TestSessionClient()
+ val testSessionClient = TestSandboxedUiAdapter.TestSessionClient()
adapterFromCoreLibInfo.openSession(
context,
@@ -178,10 +179,9 @@
testSessionClient
)
- openSessionLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)
- assertTrue(openSessionLatch.count == 0.toLong())
- assertTrue(adapter.isOpenSessionCalled)
- assertTrue(testSessionClient.isSessionOpened)
+ assertThat(openSessionLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
+ assertThat(adapter.isOpenSessionCalled).isTrue()
+ assertThat(testSessionClient.isSessionOpened).isTrue()
}
@Test
@@ -312,11 +312,12 @@
}
class TestSessionClient : SandboxedUiAdapter.SessionClient {
-
- var isSessionOpened = false
+ private val latch = CountDownLatch(1)
+ val isSessionOpened: Boolean
+ get() = latch.await(TIMEOUT, TimeUnit.MILLISECONDS)
override fun onSessionOpened(session: SandboxedUiAdapter.Session) {
- isSessionOpened = true
+ latch.countDown()
}
override fun onSessionError(throwable: Throwable) {
diff --git a/settings.gradle b/settings.gradle
index 75096da..7459ae1 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -707,6 +707,7 @@
includeProject(":core:uwb:uwb-rxjava3", [BuildType.MAIN])
includeProject(":credentials:credentials", [BuildType.MAIN])
includeProject(":credentials:credentials-samples", "credentials/credentials/samples", [BuildType.MAIN])
+includeProject(":credentials:credentials-fido:credentials-fido", [BuildType.MAIN])
includeProject(":credentials:credentials-play-services-auth", [BuildType.MAIN])
includeProject(":credentials:credentials-provider", [BuildType.MAIN])
includeProject(":cursoradapter:cursoradapter", [BuildType.MAIN])
diff --git a/tv/integration-tests/playground/src/main/baseline-prof.txt b/tv/integration-tests/playground/src/main/baseline-prof.txt
new file mode 100644
index 0000000..970cd8b
--- /dev/null
+++ b/tv/integration-tests/playground/src/main/baseline-prof.txt
@@ -0,0 +1,5623 @@
+HPLandroidx/collection/ArrayMap$EntrySet;-><init>(Ljava/util/Map;I)V
+HPLandroidx/compose/foundation/layout/SizeElement;->equals(Ljava/lang/Object;)Z
+HPLandroidx/compose/runtime/ComposerImpl$CompositionContextImpl;->unregisterComposer$runtime_release(Landroidx/compose/runtime/Composer;)V
+HPLandroidx/compose/runtime/ComposerImpl$CompositionContextImpl;->unregisterComposition$runtime_release(Landroidx/compose/runtime/CompositionImpl;)V
+HPLandroidx/compose/runtime/ComposerImpl;->dispose$runtime_release()V
+HPLandroidx/compose/runtime/CompositionImpl;->dispose()V
+HPLandroidx/compose/runtime/Recomposer;->unregisterComposition$runtime_release(Landroidx/compose/runtime/CompositionImpl;)V
+HPLandroidx/compose/runtime/SlotWriter$groupSlots$1;-><init>(IILandroidx/compose/runtime/SlotWriter;)V
+HPLandroidx/compose/runtime/SlotWriter$groupSlots$1;->hasNext()Z
+HPLandroidx/compose/runtime/SlotWriter$groupSlots$1;->next()Ljava/lang/Object;
+HPLandroidx/compose/runtime/SlotWriter;->removeGroup()Z
+HPLandroidx/compose/runtime/SlotWriter;->removeGroups(II)Z
+HPLandroidx/compose/runtime/SlotWriter;->removeSlots(III)V
+HPLandroidx/compose/runtime/SlotWriter;->skipGroup()I
+HPLandroidx/compose/runtime/internal/ComposableLambdaImpl;->trackRead(Landroidx/compose/runtime/Composer;)V
+HPLandroidx/compose/runtime/snapshots/TransparentObserverMutableSnapshot;->getCurrentSnapshot()Landroidx/compose/runtime/snapshots/MutableSnapshot;
+HPLandroidx/compose/ui/focus/FocusOwnerImpl$moveFocus$foundNextItem$1;->invoke(Landroidx/compose/ui/layout/BeyondBoundsLayout$BeyondBoundsScope;)Ljava/lang/Boolean;
+HPLandroidx/compose/ui/layout/Placeable$PlacementScope;->placeWithLayer-aW-9-wM$default(Landroidx/compose/ui/layout/Placeable$PlacementScope;Landroidx/compose/ui/layout/Placeable;J)V
+HPLandroidx/compose/ui/modifier/BackwardsCompatLocalMap;->get$ui_release(Landroidx/compose/ui/modifier/ProvidableModifierLocal;)Ljava/lang/Object;
+HPLandroidx/compose/ui/node/LayoutNode;->detach$ui_release()V
+HPLandroidx/compose/ui/node/LayoutNode;->onChildRemoved(Landroidx/compose/ui/node/LayoutNode;)V
+HPLandroidx/compose/ui/node/LayoutNode;->removeAll$ui_release()V
+HPLandroidx/compose/ui/node/LayoutNode;->removeAt$ui_release(II)V
+HPLandroidx/compose/ui/node/MeasureAndLayoutDelegate;->requestRemeasure(Landroidx/compose/ui/node/LayoutNode;Z)Z
+HPLandroidx/compose/ui/node/UiApplier;->clear()V
+HPLandroidx/compose/ui/platform/AndroidComposeView;->getFocusedRect(Landroid/graphics/Rect;)V
+HPLandroidx/tv/foundation/lazy/layout/LazyLayoutBeyondBoundsInfo$Interval;-><init>(II)V
+HPLandroidx/tv/foundation/lazy/list/LazyLayoutBeyondBoundsModifierLocal$layout$2;-><init>(Landroidx/tv/foundation/lazy/list/LazyLayoutBeyondBoundsModifierLocal;Lkotlin/jvm/internal/Ref$ObjectRef;I)V
+HPLandroidx/tv/foundation/lazy/list/LazyLayoutBeyondBoundsModifierLocal$layout$2;->getHasMoreContent()Z
+HPLandroidx/tv/foundation/lazy/list/LazyLayoutBeyondBoundsModifierLocal;->hasMoreContent-FR3nfPY(Landroidx/tv/foundation/lazy/layout/LazyLayoutBeyondBoundsInfo$Interval;I)Z
+HPLandroidx/tv/foundation/lazy/list/LazyLayoutBeyondBoundsModifierLocal;->isForward-4vf7U8o(I)Z
+HPLandroidx/tv/foundation/lazy/list/LazyListBeyondBoundsState;->getFirstPlacedIndex()I
+HPLandroidx/tv/foundation/lazy/list/LazyListBeyondBoundsState;->getHasVisibleItems()Z
+HPLandroidx/tv/foundation/lazy/list/LazyListBeyondBoundsState;->getItemCount()I
+HPLandroidx/tv/foundation/lazy/list/LazyListBeyondBoundsState;->getLastPlacedIndex()I
+HPLandroidx/tv/foundation/lazy/list/LazyListBeyondBoundsState;->remeasure()V
+HPLcom/example/tvcomposebasedtests/JankStatsAggregator;->issueJankReport(Ljava/lang/String;)V
+HPLcom/google/gson/Gson$3;->write(Lcom/google/gson/stream/JsonWriter;Ljava/lang/Boolean;)V
+HPLcom/google/gson/Gson$3;->write(Lcom/google/gson/stream/JsonWriter;Ljava/lang/Number;)V
+HPLcom/google/gson/Gson$3;->write(Lcom/google/gson/stream/JsonWriter;Ljava/lang/Object;)V
+HPLcom/google/gson/Gson;->getAdapter(Lcom/google/gson/reflect/TypeToken;)Lcom/google/gson/TypeAdapter;
+HPLcom/google/gson/JsonArray;-><init>()V
+HPLcom/google/gson/JsonArray;->iterator()Ljava/util/Iterator;
+HPLcom/google/gson/JsonObject;-><init>()V
+HPLcom/google/gson/JsonPrimitive;-><init>(Ljava/lang/Boolean;)V
+HPLcom/google/gson/JsonPrimitive;-><init>(Ljava/lang/Number;)V
+HPLcom/google/gson/JsonPrimitive;->getAsNumber()Ljava/lang/Number;
+HPLcom/google/gson/internal/LinkedTreeMap$LinkedTreeMapIterator;-><init>(Lcom/google/gson/internal/LinkedTreeMap;)V
+HPLcom/google/gson/internal/LinkedTreeMap$LinkedTreeMapIterator;->hasNext()Z
+HPLcom/google/gson/internal/LinkedTreeMap$Node;-><init>(Z)V
+HPLcom/google/gson/internal/LinkedTreeMap$Node;-><init>(ZLcom/google/gson/internal/LinkedTreeMap$Node;Ljava/lang/Object;Lcom/google/gson/internal/LinkedTreeMap$Node;Lcom/google/gson/internal/LinkedTreeMap$Node;)V
+HPLcom/google/gson/internal/LinkedTreeMap$Node;->getValue()Ljava/lang/Object;
+HPLcom/google/gson/internal/LinkedTreeMap;-><init>(Z)V
+HPLcom/google/gson/internal/LinkedTreeMap;->entrySet()Ljava/util/Set;
+HPLcom/google/gson/internal/LinkedTreeMap;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HPLcom/google/gson/internal/LinkedTreeMap;->rebalance(Lcom/google/gson/internal/LinkedTreeMap$Node;Z)V
+HPLcom/google/gson/internal/LinkedTreeMap;->replaceInParent(Lcom/google/gson/internal/LinkedTreeMap$Node;Lcom/google/gson/internal/LinkedTreeMap$Node;)V
+HPLcom/google/gson/internal/LinkedTreeMap;->rotateLeft(Lcom/google/gson/internal/LinkedTreeMap$Node;)V
+HPLcom/google/gson/internal/LinkedTreeMap;->rotateRight(Lcom/google/gson/internal/LinkedTreeMap$Node;)V
+HPLcom/google/gson/internal/bind/JsonTreeWriter;-><init>()V
+HPLcom/google/gson/internal/bind/JsonTreeWriter;->beginArray()V
+HPLcom/google/gson/internal/bind/JsonTreeWriter;->endArray()V
+HPLcom/google/gson/internal/bind/JsonTreeWriter;->endObject()V
+HPLcom/google/gson/internal/bind/JsonTreeWriter;->get()Lcom/google/gson/JsonElement;
+HPLcom/google/gson/internal/bind/JsonTreeWriter;->name(Ljava/lang/String;)V
+HPLcom/google/gson/internal/bind/JsonTreeWriter;->peek()Lcom/google/gson/JsonElement;
+HPLcom/google/gson/internal/bind/JsonTreeWriter;->put(Lcom/google/gson/JsonElement;)V
+HPLcom/google/gson/internal/bind/JsonTreeWriter;->value(J)V
+HPLcom/google/gson/internal/bind/ReflectiveTypeAdapterFactory$1;->write(Lcom/google/gson/stream/JsonWriter;Ljava/lang/Object;)V
+HPLcom/google/gson/internal/bind/ReflectiveTypeAdapterFactory$Adapter;->write(Lcom/google/gson/stream/JsonWriter;Ljava/lang/Object;)V
+HPLcom/google/gson/internal/bind/TypeAdapters$34$1;->write(Lcom/google/gson/stream/JsonWriter;Ljava/lang/Object;)V
+HPLcom/google/gson/internal/bind/TypeAdapters$EnumTypeAdapter;-><init>(Lcom/google/gson/Gson;Lcom/google/gson/TypeAdapter;Ljava/lang/reflect/Type;)V
+HPLcom/google/gson/reflect/TypeToken;-><init>(Ljava/lang/reflect/Type;)V
+HPLcom/google/gson/reflect/TypeToken;->equals(Ljava/lang/Object;)Z
+HPLcom/google/gson/reflect/TypeToken;->hashCode()I
+HPLcom/google/gson/stream/JsonWriter;-><init>(Ljava/io/Writer;)V
+HPLcom/google/gson/stream/JsonWriter;->beforeValue()V
+HPLcom/google/gson/stream/JsonWriter;->beginArray()V
+HPLcom/google/gson/stream/JsonWriter;->beginObject()V
+HPLcom/google/gson/stream/JsonWriter;->close(IIC)V
+HPLcom/google/gson/stream/JsonWriter;->endObject()V
+HPLcom/google/gson/stream/JsonWriter;->peek()I
+HPLcom/google/gson/stream/JsonWriter;->string(Ljava/lang/String;)V
+HPLcom/google/gson/stream/JsonWriter;->value(Ljava/lang/Number;)V
+HPLcom/google/gson/stream/JsonWriter;->value(Z)V
+HPLcom/google/gson/stream/JsonWriter;->writeDeferredName()V
+HPLkotlin/ResultKt;->canonicalize(Ljava/lang/reflect/Type;)Ljava/lang/reflect/Type;
+HPLkotlin/ResultKt;->getRawType(Ljava/lang/reflect/Type;)Ljava/lang/Class;
+HPLkotlin/TuplesKt;->fastFilter(Ljava/util/ArrayList;Lkotlin/jvm/functions/Function1;)Ljava/util/ArrayList;
+HPLkotlin/TuplesKt;->removeCurrentGroup(Landroidx/compose/runtime/SlotWriter;Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;)V
+HPLkotlin/collections/ArrayDeque;->add(ILjava/lang/Object;)V
+HPLkotlin/collections/CollectionsKt___CollectionsKt;->first(Ljava/util/List;)Ljava/lang/Object;
+HSPL_COROUTINE/ArtificialStackFrames;-><init>(I)V
+HSPL_COROUTINE/ArtificialStackFrames;-><init>(II)V
+HSPLandroidx/activity/ComponentActivity$$ExternalSyntheticLambda0;-><init>(ILjava/lang/Object;)V
+HSPLandroidx/activity/ComponentActivity$$ExternalSyntheticLambda1;-><init>(Landroidx/activity/ComponentActivity;)V
+HSPLandroidx/activity/ComponentActivity$$ExternalSyntheticLambda2;-><init>(ILjava/lang/Object;)V
+HSPLandroidx/activity/ComponentActivity$$ExternalSyntheticLambda3;-><init>(Landroidx/activity/ComponentActivity;)V
+HSPLandroidx/activity/ComponentActivity$$ExternalSyntheticLambda3;->onContextAvailable()V
+HSPLandroidx/activity/ComponentActivity$1;-><init>(Landroid/view/KeyEvent$Callback;I)V
+HSPLandroidx/activity/ComponentActivity$2;-><init>()V
+HSPLandroidx/activity/ComponentActivity$3;-><init>(Landroidx/activity/ComponentActivity;)V
+HSPLandroidx/activity/ComponentActivity$3;->onStateChanged(Landroidx/lifecycle/LifecycleOwner;Landroidx/lifecycle/Lifecycle$Event;)V
+HSPLandroidx/activity/ComponentActivity$4;-><init>(Landroidx/activity/ComponentActivity;)V
+HSPLandroidx/activity/ComponentActivity$4;->onStateChanged(Landroidx/lifecycle/LifecycleOwner;Landroidx/lifecycle/Lifecycle$Event;)V
+HSPLandroidx/activity/ComponentActivity$5;-><init>(Landroidx/activity/ComponentActivity;)V
+HSPLandroidx/activity/ComponentActivity$5;->onStateChanged(Landroidx/lifecycle/LifecycleOwner;Landroidx/lifecycle/Lifecycle$Event;)V
+HSPLandroidx/activity/ComponentActivity$ReportFullyDrawnExecutorApi16Impl;-><init>(Landroidx/activity/ComponentActivity;)V
+HSPLandroidx/activity/ComponentActivity$ReportFullyDrawnExecutorApi16Impl;->onDraw()V
+HSPLandroidx/activity/ComponentActivity$ReportFullyDrawnExecutorApi16Impl;->viewCreated(Landroid/view/View;)V
+HSPLandroidx/activity/ComponentActivity;-><init>()V
+HSPLandroidx/activity/ComponentActivity;->ensureViewModelStore()V
+HSPLandroidx/activity/ComponentActivity;->getLifecycle()Landroidx/lifecycle/LifecycleRegistry;
+HSPLandroidx/activity/ComponentActivity;->getViewModelStore()Landroidx/lifecycle/ViewModelStore;
+HSPLandroidx/activity/ComponentActivity;->initViewTreeOwners()V
+HSPLandroidx/activity/ComponentActivity;->onCreate(Landroid/os/Bundle;)V
+HSPLandroidx/activity/ComponentActivity;->setContentView(Landroid/view/View;Landroid/view/ViewGroup$LayoutParams;)V
+HSPLandroidx/activity/FullyDrawnReporter;-><init>(Landroidx/activity/ComponentActivity$ReportFullyDrawnExecutorApi16Impl;Landroidx/activity/ComponentActivity$$ExternalSyntheticLambda1;)V
+HSPLandroidx/activity/OnBackPressedDispatcher;-><init>(Landroidx/activity/ComponentActivity$1;)V
+HSPLandroidx/activity/compose/ComponentActivityKt;-><clinit>()V
+HSPLandroidx/activity/compose/ComponentActivityKt;->setContent$default(Landroidx/activity/ComponentActivity;Landroidx/compose/runtime/internal/ComposableLambdaImpl;)V
+HSPLandroidx/activity/contextaware/ContextAwareHelper;-><init>()V
+HSPLandroidx/activity/result/ActivityResult$1;-><init>(I)V
+HSPLandroidx/arch/core/executor/ArchTaskExecutor;-><init>()V
+HSPLandroidx/arch/core/executor/ArchTaskExecutor;->getInstance()Landroidx/arch/core/executor/ArchTaskExecutor;
+HSPLandroidx/arch/core/executor/DefaultTaskExecutor$1;-><init>()V
+HSPLandroidx/arch/core/executor/DefaultTaskExecutor;-><init>()V
+HSPLandroidx/arch/core/internal/FastSafeIterableMap;-><init>()V
+HSPLandroidx/arch/core/internal/FastSafeIterableMap;->get(Ljava/lang/Object;)Landroidx/arch/core/internal/SafeIterableMap$Entry;
+HSPLandroidx/arch/core/internal/FastSafeIterableMap;->remove(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/arch/core/internal/SafeIterableMap$AscendingIterator;-><init>(Landroidx/arch/core/internal/SafeIterableMap$Entry;Landroidx/arch/core/internal/SafeIterableMap$Entry;I)V
+HSPLandroidx/arch/core/internal/SafeIterableMap$Entry;-><init>(Ljava/lang/Object;Ljava/lang/Object;)V
+HSPLandroidx/arch/core/internal/SafeIterableMap$Entry;->getKey()Ljava/lang/Object;
+HSPLandroidx/arch/core/internal/SafeIterableMap$Entry;->getValue()Ljava/lang/Object;
+HSPLandroidx/arch/core/internal/SafeIterableMap$IteratorWithAdditions;-><init>(Landroidx/arch/core/internal/SafeIterableMap;)V
+HSPLandroidx/arch/core/internal/SafeIterableMap$IteratorWithAdditions;->hasNext()Z
+HSPLandroidx/arch/core/internal/SafeIterableMap$IteratorWithAdditions;->next()Ljava/lang/Object;
+HSPLandroidx/arch/core/internal/SafeIterableMap$IteratorWithAdditions;->supportRemove(Landroidx/arch/core/internal/SafeIterableMap$Entry;)V
+HSPLandroidx/arch/core/internal/SafeIterableMap$ListIterator;-><init>(Landroidx/arch/core/internal/SafeIterableMap$Entry;Landroidx/arch/core/internal/SafeIterableMap$Entry;)V
+HSPLandroidx/arch/core/internal/SafeIterableMap$ListIterator;->hasNext()Z
+HSPLandroidx/arch/core/internal/SafeIterableMap;-><init>()V
+HSPLandroidx/arch/core/internal/SafeIterableMap;->get(Ljava/lang/Object;)Landroidx/arch/core/internal/SafeIterableMap$Entry;
+HSPLandroidx/arch/core/internal/SafeIterableMap;->iterator()Ljava/util/Iterator;
+HSPLandroidx/arch/core/internal/SafeIterableMap;->remove(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/collection/ArraySet;-><clinit>()V
+HSPLandroidx/collection/ArraySet;-><init>()V
+HSPLandroidx/collection/ArraySet;->add(Ljava/lang/Object;)Z
+HSPLandroidx/collection/ArraySet;->allocArrays(I)V
+HSPLandroidx/collection/ArraySet;->clear()V
+HSPLandroidx/collection/ArraySet;->freeArrays([I[Ljava/lang/Object;I)V
+HSPLandroidx/collection/ArraySet;->indexOf(ILjava/lang/Object;)I
+HSPLandroidx/collection/ArraySet;->toArray()[Ljava/lang/Object;
+HSPLandroidx/collection/LongSparseArray;-><clinit>()V
+HSPLandroidx/collection/LongSparseArray;-><init>(I)V
+HSPLandroidx/collection/SimpleArrayMap;-><init>()V
+HSPLandroidx/collection/SparseArrayCompat;-><clinit>()V
+HSPLandroidx/collection/SparseArrayCompat;-><init>()V
+HSPLandroidx/compose/animation/FlingCalculator;-><init>(FLandroidx/compose/ui/unit/Density;)V
+HSPLandroidx/compose/animation/FlingCalculatorKt;-><clinit>()V
+HSPLandroidx/compose/animation/SingleValueAnimationKt;-><clinit>()V
+HSPLandroidx/compose/animation/SingleValueAnimationKt;->animateColorAsState-euL9pac(JLjava/lang/String;Landroidx/compose/runtime/Composer;I)Landroidx/compose/runtime/State;
+HSPLandroidx/compose/animation/SplineBasedFloatDecayAnimationSpec;-><init>(Landroidx/compose/ui/unit/Density;)V
+HSPLandroidx/compose/animation/SplineBasedFloatDecayAnimationSpec_androidKt;-><clinit>()V
+HSPLandroidx/compose/animation/core/Animatable$runAnimation$2;-><init>(Landroidx/compose/animation/core/Animatable;Ljava/lang/Object;Landroidx/compose/animation/core/Animation;JLkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/animation/core/Animatable$runAnimation$2;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/animation/core/Animatable$runAnimation$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/animation/core/Animatable;-><init>(Ljava/lang/Object;Landroidx/compose/animation/core/TwoWayConverterImpl;Ljava/lang/Object;)V
+HSPLandroidx/compose/animation/core/Animatable;->animateTo$default(Landroidx/compose/animation/core/Animatable;Ljava/lang/Object;Landroidx/compose/animation/core/AnimationSpec;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLandroidx/compose/animation/core/Animatable;->getValue()Ljava/lang/Object;
+HSPLandroidx/compose/animation/core/AnimateAsStateKt$animateValueAsState$2;-><init>(Lkotlinx/coroutines/channels/Channel;Ljava/lang/Object;)V
+HSPLandroidx/compose/animation/core/AnimateAsStateKt$animateValueAsState$2;->invoke()Ljava/lang/Object;
+HSPLandroidx/compose/animation/core/AnimateAsStateKt$animateValueAsState$3$1;-><init>(Ljava/lang/Object;Landroidx/compose/animation/core/Animatable;Landroidx/compose/runtime/State;Landroidx/compose/runtime/State;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/animation/core/AnimateAsStateKt$animateValueAsState$3$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/animation/core/AnimateAsStateKt$animateValueAsState$3$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/animation/core/AnimateAsStateKt$animateValueAsState$3;-><init>(Lkotlinx/coroutines/channels/Channel;Landroidx/compose/animation/core/Animatable;Landroidx/compose/runtime/State;Landroidx/compose/runtime/State;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/animation/core/AnimateAsStateKt$animateValueAsState$3;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/animation/core/AnimateAsStateKt$animateValueAsState$3;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/animation/core/AnimateAsStateKt;-><clinit>()V
+HSPLandroidx/compose/animation/core/AnimateAsStateKt;->animateDpAsState-AjpBEmI(FLjava/lang/String;Landroidx/compose/runtime/Composer;II)Landroidx/compose/runtime/State;
+HSPLandroidx/compose/animation/core/AnimateAsStateKt;->animateFloatAsState(FLandroidx/compose/animation/core/TweenSpec;Ljava/lang/String;Landroidx/compose/runtime/Composer;II)Landroidx/compose/runtime/State;
+HSPLandroidx/compose/animation/core/AnimateAsStateKt;->animateValueAsState(Ljava/lang/Object;Landroidx/compose/animation/core/TwoWayConverterImpl;Landroidx/compose/animation/core/AnimationSpec;Ljava/lang/Float;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)Landroidx/compose/runtime/State;
+HSPLandroidx/compose/animation/core/Animation;->isFinishedFromNanos(J)Z
+HSPLandroidx/compose/animation/core/AnimationEndReason$EnumUnboxingLocalUtility;-><clinit>()V
+HSPLandroidx/compose/animation/core/AnimationEndReason$EnumUnboxingLocalUtility;->compareTo(II)I
+HSPLandroidx/compose/animation/core/AnimationEndReason$EnumUnboxingLocalUtility;->ordinal(I)I
+HSPLandroidx/compose/animation/core/AnimationEndReason$EnumUnboxingLocalUtility;->values(I)[I
+HSPLandroidx/compose/animation/core/AnimationResult;-><init>(Landroidx/compose/animation/core/AnimationState;I)V
+HSPLandroidx/compose/animation/core/AnimationScope;-><init>(Ljava/lang/Object;Landroidx/compose/animation/core/TwoWayConverterImpl;Landroidx/compose/animation/core/AnimationVector;JLjava/lang/Object;JLandroidx/compose/animation/core/SuspendAnimationKt$animate$7;)V
+HSPLandroidx/compose/animation/core/AnimationScope;->getValue()Ljava/lang/Object;
+HSPLandroidx/compose/animation/core/AnimationState;-><init>(Landroidx/compose/animation/core/TwoWayConverterImpl;Ljava/lang/Object;Landroidx/compose/animation/core/AnimationVector;I)V
+HSPLandroidx/compose/animation/core/AnimationState;-><init>(Landroidx/compose/animation/core/TwoWayConverterImpl;Ljava/lang/Object;Landroidx/compose/animation/core/AnimationVector;JJZ)V
+HSPLandroidx/compose/animation/core/AnimationState;->getValue()Ljava/lang/Object;
+HSPLandroidx/compose/animation/core/AnimationVector1D;-><init>(F)V
+HSPLandroidx/compose/animation/core/AnimationVector1D;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/animation/core/AnimationVector1D;->get$animation_core_release(I)F
+HSPLandroidx/compose/animation/core/AnimationVector1D;->getSize$animation_core_release()I
+HSPLandroidx/compose/animation/core/AnimationVector1D;->newVector$animation_core_release()Landroidx/compose/animation/core/AnimationVector;
+HSPLandroidx/compose/animation/core/AnimationVector1D;->reset$animation_core_release()V
+HSPLandroidx/compose/animation/core/AnimationVector1D;->set$animation_core_release(FI)V
+HSPLandroidx/compose/animation/core/AnimationVector2D;-><init>(FF)V
+HSPLandroidx/compose/animation/core/AnimationVector3D;-><init>(FFF)V
+HSPLandroidx/compose/animation/core/AnimationVector4D;-><init>(FFFF)V
+HSPLandroidx/compose/animation/core/AnimationVector4D;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/animation/core/AnimationVector4D;->get$animation_core_release(I)F
+HSPLandroidx/compose/animation/core/AnimationVector4D;->getSize$animation_core_release()I
+HSPLandroidx/compose/animation/core/AnimationVector4D;->newVector$animation_core_release()Landroidx/compose/animation/core/AnimationVector;
+HSPLandroidx/compose/animation/core/AnimationVector4D;->reset$animation_core_release()V
+HSPLandroidx/compose/animation/core/AnimationVector4D;->set$animation_core_release(FI)V
+HSPLandroidx/compose/animation/core/ComplexDouble;-><init>(DD)V
+HSPLandroidx/compose/animation/core/CubicBezierEasing;-><init>(FFF)V
+HSPLandroidx/compose/animation/core/CubicBezierEasing;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/animation/core/CubicBezierEasing;->transform(F)F
+HSPLandroidx/compose/animation/core/DecayAnimationSpecImpl;-><init>(Landroidx/compose/animation/SplineBasedFloatDecayAnimationSpec;)V
+HSPLandroidx/compose/animation/core/EasingKt;-><clinit>()V
+HSPLandroidx/compose/animation/core/FloatSpringSpec;-><init>(FFF)V
+HSPLandroidx/compose/animation/core/FloatSpringSpec;->getDurationNanos(FFF)J
+HSPLandroidx/compose/animation/core/FloatSpringSpec;->getEndVelocity(FFF)F
+HSPLandroidx/compose/animation/core/FloatSpringSpec;->getValueFromNanos(JFFF)F
+HSPLandroidx/compose/animation/core/FloatSpringSpec;->getVelocityFromNanos(JFFF)F
+HSPLandroidx/compose/animation/core/FloatTweenSpec;-><init>(IILandroidx/compose/animation/core/Easing;)V
+HSPLandroidx/compose/animation/core/FloatTweenSpec;->getValueFromNanos(JFFF)F
+HSPLandroidx/compose/animation/core/FloatTweenSpec;->getVelocityFromNanos(JFFF)F
+HSPLandroidx/compose/animation/core/MutatorMutex$Mutator;-><init>(ILkotlinx/coroutines/Job;)V
+HSPLandroidx/compose/animation/core/MutatorMutex$mutate$2;-><init>(ILandroidx/compose/animation/core/MutatorMutex;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/animation/core/MutatorMutex$mutate$2;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/animation/core/MutatorMutex$mutate$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/animation/core/MutatorMutex$mutate$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/animation/core/MutatorMutex;-><init>()V
+HSPLandroidx/compose/animation/core/SpringSimulation;-><init>()V
+HSPLandroidx/compose/animation/core/SpringSimulation;->updateValues-IJZedt4$animation_core_release(FFJ)J
+HSPLandroidx/compose/animation/core/SpringSpec;-><init>(FFLjava/lang/Object;)V
+HSPLandroidx/compose/animation/core/SpringSpec;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/animation/core/SpringSpec;->vectorize(Landroidx/compose/animation/core/TwoWayConverterImpl;)Landroidx/compose/animation/core/VectorizedFiniteAnimationSpec;
+HSPLandroidx/compose/animation/core/SuspendAnimationKt$animate$4;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/animation/core/SuspendAnimationKt$animate$6;-><init>(Lkotlin/jvm/internal/Ref$ObjectRef;Ljava/lang/Object;Landroidx/compose/animation/core/Animation;Landroidx/compose/animation/core/AnimationVector;Landroidx/compose/animation/core/AnimationState;FLkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/animation/core/SuspendAnimationKt$animate$6;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/animation/core/SuspendAnimationKt$animate$7;-><init>(Landroidx/compose/animation/core/AnimationState;I)V
+HSPLandroidx/compose/animation/core/SuspendAnimationKt$animate$9;-><init>(Lkotlin/jvm/internal/Ref$ObjectRef;FLandroidx/compose/animation/core/Animation;Landroidx/compose/animation/core/AnimationState;Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/animation/core/SuspendAnimationKt$animate$9;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/animation/core/TargetBasedAnimation;-><init>(Landroidx/compose/animation/core/AnimationSpec;Landroidx/compose/animation/core/TwoWayConverterImpl;Ljava/lang/Object;Ljava/lang/Object;Landroidx/compose/animation/core/AnimationVector;)V
+HSPLandroidx/compose/animation/core/TargetBasedAnimation;->getDurationNanos()J
+HSPLandroidx/compose/animation/core/TargetBasedAnimation;->getTargetValue()Ljava/lang/Object;
+HSPLandroidx/compose/animation/core/TargetBasedAnimation;->getTypeConverter()Landroidx/compose/animation/core/TwoWayConverterImpl;
+HSPLandroidx/compose/animation/core/TargetBasedAnimation;->getValueFromNanos(J)Ljava/lang/Object;
+HSPLandroidx/compose/animation/core/TargetBasedAnimation;->getVelocityVectorFromNanos(J)Landroidx/compose/animation/core/AnimationVector;
+HSPLandroidx/compose/animation/core/TargetBasedAnimation;->isInfinite()Z
+HSPLandroidx/compose/animation/core/TweenSpec;-><init>(IILandroidx/compose/animation/core/Easing;)V
+HSPLandroidx/compose/animation/core/TweenSpec;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/animation/core/TweenSpec;->vectorize(Landroidx/compose/animation/core/TwoWayConverterImpl;)Landroidx/compose/animation/core/VectorizedFiniteAnimationSpec;
+HSPLandroidx/compose/animation/core/TwoWayConverterImpl;-><init>(Landroidx/compose/foundation/ImageKt$Image$1$1;Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/animation/core/VectorConvertersKt;-><clinit>()V
+HSPLandroidx/compose/animation/core/VectorizedFloatAnimationSpec;-><init>(Landroidx/compose/animation/core/FloatAnimationSpec;)V
+HSPLandroidx/compose/animation/core/VectorizedFloatAnimationSpec;-><init>(Landroidx/compose/ui/input/pointer/util/PointerIdArray;)V
+HSPLandroidx/compose/animation/core/VectorizedFloatAnimationSpec;->getDurationNanos(Landroidx/compose/animation/core/AnimationVector;Landroidx/compose/animation/core/AnimationVector;Landroidx/compose/animation/core/AnimationVector;)J
+HSPLandroidx/compose/animation/core/VectorizedFloatAnimationSpec;->getEndVelocity(Landroidx/compose/animation/core/AnimationVector;Landroidx/compose/animation/core/AnimationVector;Landroidx/compose/animation/core/AnimationVector;)Landroidx/compose/animation/core/AnimationVector;
+HSPLandroidx/compose/animation/core/VectorizedFloatAnimationSpec;->getValueFromNanos(JLandroidx/compose/animation/core/AnimationVector;Landroidx/compose/animation/core/AnimationVector;Landroidx/compose/animation/core/AnimationVector;)Landroidx/compose/animation/core/AnimationVector;
+HSPLandroidx/compose/animation/core/VectorizedFloatAnimationSpec;->getVelocityFromNanos(JLandroidx/compose/animation/core/AnimationVector;Landroidx/compose/animation/core/AnimationVector;Landroidx/compose/animation/core/AnimationVector;)Landroidx/compose/animation/core/AnimationVector;
+HSPLandroidx/compose/animation/core/VectorizedSpringSpec;-><init>(FFLandroidx/compose/animation/core/AnimationVector;)V
+HSPLandroidx/compose/animation/core/VectorizedSpringSpec;->getDurationNanos(Landroidx/compose/animation/core/AnimationVector;Landroidx/compose/animation/core/AnimationVector;Landroidx/compose/animation/core/AnimationVector;)J
+HSPLandroidx/compose/animation/core/VectorizedSpringSpec;->getEndVelocity(Landroidx/compose/animation/core/AnimationVector;Landroidx/compose/animation/core/AnimationVector;Landroidx/compose/animation/core/AnimationVector;)Landroidx/compose/animation/core/AnimationVector;
+HSPLandroidx/compose/animation/core/VectorizedSpringSpec;->getValueFromNanos(JLandroidx/compose/animation/core/AnimationVector;Landroidx/compose/animation/core/AnimationVector;Landroidx/compose/animation/core/AnimationVector;)Landroidx/compose/animation/core/AnimationVector;
+HSPLandroidx/compose/animation/core/VectorizedSpringSpec;->getVelocityFromNanos(JLandroidx/compose/animation/core/AnimationVector;Landroidx/compose/animation/core/AnimationVector;Landroidx/compose/animation/core/AnimationVector;)Landroidx/compose/animation/core/AnimationVector;
+HSPLandroidx/compose/animation/core/VectorizedSpringSpec;->isInfinite()V
+HSPLandroidx/compose/animation/core/VectorizedTweenSpec;-><init>(IILandroidx/compose/animation/core/Easing;)V
+HSPLandroidx/compose/animation/core/VectorizedTweenSpec;->getValueFromNanos(JLandroidx/compose/animation/core/AnimationVector;Landroidx/compose/animation/core/AnimationVector;Landroidx/compose/animation/core/AnimationVector;)Landroidx/compose/animation/core/AnimationVector;
+HSPLandroidx/compose/animation/core/VectorizedTweenSpec;->getVelocityFromNanos(JLandroidx/compose/animation/core/AnimationVector;Landroidx/compose/animation/core/AnimationVector;Landroidx/compose/animation/core/AnimationVector;)Landroidx/compose/animation/core/AnimationVector;
+HSPLandroidx/compose/animation/core/VisibilityThresholdsKt;-><clinit>()V
+HSPLandroidx/compose/foundation/AndroidEdgeEffectOverscrollEffect$effectModifier$1;-><init>(Landroidx/compose/foundation/AndroidEdgeEffectOverscrollEffect;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/foundation/AndroidEdgeEffectOverscrollEffect$onNewSize$1;-><init>(Landroidx/compose/foundation/AndroidEdgeEffectOverscrollEffect;)V
+HSPLandroidx/compose/foundation/AndroidEdgeEffectOverscrollEffect$onNewSize$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/AndroidEdgeEffectOverscrollEffect;-><init>(Landroid/content/Context;Landroidx/compose/foundation/OverscrollConfiguration;)V
+HSPLandroidx/compose/foundation/AndroidEdgeEffectOverscrollEffect;->animateToRelease()V
+HSPLandroidx/compose/foundation/AndroidEdgeEffectOverscrollEffect;->getEffectModifier()Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/foundation/AndroidEdgeEffectOverscrollEffect;->invalidateOverscroll()V
+HSPLandroidx/compose/foundation/AndroidOverscrollKt;-><clinit>()V
+HSPLandroidx/compose/foundation/Api31Impl;-><clinit>()V
+HSPLandroidx/compose/foundation/Api31Impl;->create(Landroid/content/Context;Landroid/util/AttributeSet;)Landroid/widget/EdgeEffect;
+HSPLandroidx/compose/foundation/Api31Impl;->getDistance(Landroid/widget/EdgeEffect;)F
+HSPLandroidx/compose/foundation/BackgroundElement;-><init>(JLandroidx/compose/ui/graphics/Shape;)V
+HSPLandroidx/compose/foundation/BackgroundElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/foundation/BackgroundElement;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/foundation/BackgroundElement;->update(Landroidx/compose/ui/Modifier$Node;)V
+HSPLandroidx/compose/foundation/BackgroundNode;-><init>(JLandroidx/compose/ui/graphics/Brush;FLandroidx/compose/ui/graphics/Shape;)V
+HSPLandroidx/compose/foundation/BackgroundNode;->draw(Landroidx/compose/ui/graphics/drawscope/ContentDrawScope;)V
+HSPLandroidx/compose/foundation/BorderKt$drawRectBorder$1;-><init>(Landroidx/compose/ui/graphics/Brush;JJLkotlin/ResultKt;)V
+HSPLandroidx/compose/foundation/BorderKt$drawRectBorder$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/BorderModifierNode;-><init>(FLandroidx/compose/ui/graphics/Brush;Landroidx/compose/ui/graphics/Shape;)V
+HSPLandroidx/compose/foundation/BorderModifierNodeElement;-><init>(FLandroidx/compose/ui/graphics/Brush;Landroidx/compose/ui/graphics/Shape;)V
+HSPLandroidx/compose/foundation/BorderModifierNodeElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/foundation/BorderModifierNodeElement;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/foundation/BorderModifierNodeElement;->update(Landroidx/compose/ui/Modifier$Node;)V
+HSPLandroidx/compose/foundation/BorderStroke;-><init>(FLandroidx/compose/ui/graphics/SolidColor;)V
+HSPLandroidx/compose/foundation/ClipScrollableContainerKt;-><clinit>()V
+HSPLandroidx/compose/foundation/DrawOverscrollModifier;-><init>(Landroidx/compose/foundation/AndroidEdgeEffectOverscrollEffect;)V
+HSPLandroidx/compose/foundation/DrawOverscrollModifier;->draw(Landroidx/compose/ui/graphics/drawscope/ContentDrawScope;)V
+HSPLandroidx/compose/foundation/FocusableElement;-><init>(Landroidx/compose/foundation/interaction/MutableInteractionSourceImpl;)V
+HSPLandroidx/compose/foundation/FocusableElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/foundation/FocusableElement;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/foundation/FocusableInteractionNode$emitWithFallback$1;-><init>(Landroidx/compose/foundation/interaction/MutableInteractionSourceImpl;Landroidx/compose/foundation/interaction/Interaction;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/foundation/FocusableInteractionNode$emitWithFallback$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/foundation/FocusableInteractionNode$emitWithFallback$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/FocusableInteractionNode;-><init>(Landroidx/compose/foundation/interaction/MutableInteractionSourceImpl;)V
+HSPLandroidx/compose/foundation/FocusableInteractionNode;->emitWithFallback(Landroidx/compose/foundation/interaction/MutableInteractionSourceImpl;Landroidx/compose/foundation/interaction/Interaction;)V
+HSPLandroidx/compose/foundation/FocusableKt$FocusableInNonTouchModeElement$1;-><init>()V
+HSPLandroidx/compose/foundation/FocusableKt;-><clinit>()V
+HSPLandroidx/compose/foundation/FocusableKt;->focusable$default(Landroidx/compose/ui/Modifier;Landroidx/compose/foundation/interaction/MutableInteractionSourceImpl;I)Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/foundation/FocusableNode$onFocusEvent$1;-><init>(Landroidx/compose/foundation/FocusableNode;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/foundation/FocusableNode$onFocusEvent$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/foundation/FocusableNode$onFocusEvent$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/FocusableNode;-><init>(Landroidx/compose/foundation/interaction/MutableInteractionSourceImpl;)V
+HSPLandroidx/compose/foundation/FocusableNode;->onFocusEvent(Landroidx/compose/ui/focus/FocusStateImpl;)V
+HSPLandroidx/compose/foundation/FocusableNode;->onGloballyPositioned(Landroidx/compose/ui/node/NodeCoordinator;)V
+HSPLandroidx/compose/foundation/FocusableNode;->onPlaced(Landroidx/compose/ui/node/NodeCoordinator;)V
+HSPLandroidx/compose/foundation/FocusablePinnableContainerNode;->onReset()V
+HSPLandroidx/compose/foundation/FocusableSemanticsNode;-><init>()V
+HSPLandroidx/compose/foundation/FocusedBoundsKt;-><clinit>()V
+HSPLandroidx/compose/foundation/FocusedBoundsNode;->onGloballyPositioned(Landroidx/compose/ui/node/NodeCoordinator;)V
+HSPLandroidx/compose/foundation/FocusedBoundsObserverNode;-><init>(Lkotlin/collections/AbstractMap$toString$1;)V
+HSPLandroidx/compose/foundation/FocusedBoundsObserverNode;->getProvidedValues()Landroidx/tv/material3/TabKt;
+HSPLandroidx/compose/foundation/FocusedBoundsObserverNode;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/ImageKt$Image$1$1;-><clinit>()V
+HSPLandroidx/compose/foundation/ImageKt$Image$1$1;-><init>(I)V
+HSPLandroidx/compose/foundation/ImageKt$Image$1$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/ImageKt$Image$1;-><clinit>()V
+HSPLandroidx/compose/foundation/ImageKt$Image$1;->measure-3p2s80s(Landroidx/compose/ui/layout/MeasureScope;Ljava/util/List;J)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/foundation/ImageKt;->Image(Landroidx/compose/ui/graphics/painter/Painter;Ljava/lang/String;Landroidx/compose/ui/Modifier;Landroidx/compose/ui/Alignment;Landroidx/compose/ui/layout/ContentScale;FLandroidx/compose/ui/graphics/BlendModeColorFilter;Landroidx/compose/runtime/Composer;II)V
+HSPLandroidx/compose/foundation/ImageKt;->background-bw27NRU(Landroidx/compose/ui/Modifier;JLandroidx/compose/ui/graphics/Shape;)Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/foundation/ImageKt;->checkScrollableContainerConstraints-K40F9xA(JLandroidx/compose/foundation/gestures/Orientation;)V
+HSPLandroidx/compose/foundation/ImageKt;->create(Landroid/content/Context;)Landroid/widget/EdgeEffect;
+HSPLandroidx/compose/foundation/ImageKt;->getDistanceCompat(Landroid/widget/EdgeEffect;)F
+HSPLandroidx/compose/foundation/IndicationKt$indication$2;-><init>(Ljava/lang/Object;ILjava/lang/Object;)V
+HSPLandroidx/compose/foundation/IndicationKt$indication$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/IndicationKt;-><clinit>()V
+HSPLandroidx/compose/foundation/IndicationModifier;-><init>(Landroidx/compose/foundation/IndicationInstance;)V
+HSPLandroidx/compose/foundation/IndicationModifier;->draw(Landroidx/compose/ui/graphics/drawscope/ContentDrawScope;)V
+HSPLandroidx/compose/foundation/MutatePriority;-><clinit>()V
+HSPLandroidx/compose/foundation/MutatePriority;-><init>(ILjava/lang/String;)V
+HSPLandroidx/compose/foundation/MutatorMutex$Mutator;-><init>(Landroidx/compose/foundation/MutatePriority;Lkotlinx/coroutines/Job;)V
+HSPLandroidx/compose/foundation/MutatorMutex$mutateWith$2;-><init>(Landroidx/compose/foundation/MutatePriority;Landroidx/compose/foundation/MutatorMutex;Lkotlin/jvm/functions/Function2;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/foundation/MutatorMutex$mutateWith$2;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/foundation/MutatorMutex$mutateWith$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/MutatorMutex$mutateWith$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/MutatorMutex;-><init>()V
+HSPLandroidx/compose/foundation/OverscrollConfiguration;-><init>()V
+HSPLandroidx/compose/foundation/OverscrollConfigurationKt;-><clinit>()V
+HSPLandroidx/compose/foundation/ScrollKt$rememberScrollState$1$1;-><init>(I)V
+HSPLandroidx/compose/foundation/ScrollKt$rememberScrollState$1$1;->invoke()Ljava/lang/Object;
+HSPLandroidx/compose/foundation/ScrollKt$scroll$2$semantics$1$1;-><init>(ZLkotlinx/coroutines/CoroutineScope;Landroidx/tv/foundation/lazy/layout/LazyLayoutSemanticState;)V
+HSPLandroidx/compose/foundation/ScrollKt$scroll$2$semantics$1;-><init>(ZZZLandroidx/compose/foundation/ScrollState;Lkotlinx/coroutines/CoroutineScope;)V
+HSPLandroidx/compose/foundation/ScrollKt$scroll$2;-><init>(Landroidx/compose/foundation/ScrollState;Landroidx/compose/foundation/gestures/FlingBehavior;ZZ)V
+HSPLandroidx/compose/foundation/ScrollKt$scroll$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/ScrollState$canScrollForward$2;-><init>(Landroidx/compose/foundation/ScrollState;I)V
+HSPLandroidx/compose/foundation/ScrollState;-><clinit>()V
+HSPLandroidx/compose/foundation/ScrollState;-><init>(I)V
+HSPLandroidx/compose/foundation/ScrollState;->getValue()I
+HSPLandroidx/compose/foundation/ScrollingLayoutElement;-><init>(Landroidx/compose/foundation/ScrollState;ZZ)V
+HSPLandroidx/compose/foundation/ScrollingLayoutElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/foundation/ScrollingLayoutNode;-><init>(Landroidx/compose/foundation/ScrollState;ZZ)V
+HSPLandroidx/compose/foundation/ScrollingLayoutNode;->measure-3p2s80s(Landroidx/compose/ui/layout/MeasureScope;Landroidx/compose/ui/layout/Measurable;J)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/foundation/gestures/BringIntoViewRequestPriorityQueue;-><init>()V
+HSPLandroidx/compose/foundation/gestures/BringIntoViewRequestPriorityQueue;->cancelAndRemoveAll(Ljava/util/concurrent/CancellationException;)V
+HSPLandroidx/compose/foundation/gestures/BringIntoViewSpec$Companion$DefaultBringIntoViewSpec$1;-><init>()V
+HSPLandroidx/compose/foundation/gestures/BringIntoViewSpec$Companion$DefaultBringIntoViewSpec$1;->calculateScrollDistance(FFF)F
+HSPLandroidx/compose/foundation/gestures/BringIntoViewSpec$Companion$DefaultBringIntoViewSpec$1;->getScrollAnimationSpec()Landroidx/compose/animation/core/AnimationSpec;
+HSPLandroidx/compose/foundation/gestures/BringIntoViewSpec$Companion;-><clinit>()V
+HSPLandroidx/compose/foundation/gestures/BringIntoViewSpec;-><clinit>()V
+HSPLandroidx/compose/foundation/gestures/ContentInViewNode$Request;-><init>(Landroidx/compose/foundation/relocation/BringIntoViewResponderNode$bringChildIntoView$2$1$1;Lkotlinx/coroutines/CancellableContinuationImpl;)V
+HSPLandroidx/compose/foundation/gestures/ContentInViewNode$launchAnimation$2$1;-><init>(Landroidx/compose/foundation/gestures/ContentInViewNode;Lkotlinx/coroutines/Job;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/foundation/gestures/ContentInViewNode$launchAnimation$2$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/foundation/gestures/ContentInViewNode$launchAnimation$2$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/gestures/ContentInViewNode$launchAnimation$2$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/gestures/ContentInViewNode$launchAnimation$2;-><init>(Landroidx/compose/foundation/gestures/ContentInViewNode;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/foundation/gestures/ContentInViewNode$launchAnimation$2;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/foundation/gestures/ContentInViewNode$launchAnimation$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/gestures/ContentInViewNode$launchAnimation$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/gestures/ContentInViewNode;-><init>(Landroidx/compose/foundation/gestures/Orientation;Landroidx/compose/foundation/gestures/ScrollableState;ZLandroidx/compose/foundation/gestures/BringIntoViewSpec;)V
+HSPLandroidx/compose/foundation/gestures/ContentInViewNode;->access$calculateScrollDelta(Landroidx/compose/foundation/gestures/ContentInViewNode;)F
+HSPLandroidx/compose/foundation/gestures/ContentInViewNode;->isMaxVisible-O0kMr_c(Landroidx/compose/ui/geometry/Rect;J)Z
+HSPLandroidx/compose/foundation/gestures/ContentInViewNode;->launchAnimation()V
+HSPLandroidx/compose/foundation/gestures/ContentInViewNode;->onPlaced(Landroidx/compose/ui/node/NodeCoordinator;)V
+HSPLandroidx/compose/foundation/gestures/ContentInViewNode;->onRemeasured-ozmzZPI(J)V
+HSPLandroidx/compose/foundation/gestures/ContentInViewNode;->relocationOffset-BMxPBkI(Landroidx/compose/ui/geometry/Rect;J)J
+HSPLandroidx/compose/foundation/gestures/DefaultFlingBehavior;-><init>(Landroidx/compose/animation/core/DecayAnimationSpecImpl;)V
+HSPLandroidx/compose/foundation/gestures/DefaultScrollableState$scroll$2$1;-><init>(Landroidx/compose/foundation/gestures/DefaultScrollableState;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/foundation/gestures/DefaultScrollableState$scroll$2$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/foundation/gestures/DefaultScrollableState$scroll$2$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/gestures/DefaultScrollableState$scroll$2$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/gestures/DefaultScrollableState$scroll$2;-><init>(Landroidx/compose/foundation/gestures/DefaultScrollableState;Landroidx/compose/foundation/MutatePriority;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/foundation/gestures/DefaultScrollableState$scroll$2;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/foundation/gestures/DefaultScrollableState$scroll$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/gestures/DefaultScrollableState$scroll$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/gestures/DefaultScrollableState$scrollScope$1;-><init>(Landroidx/compose/foundation/gestures/DefaultScrollableState;)V
+HSPLandroidx/compose/foundation/gestures/DefaultScrollableState$scrollScope$1;->scrollBy(F)F
+HSPLandroidx/compose/foundation/gestures/DefaultScrollableState;-><init>(Lkotlin/collections/AbstractMap$toString$1;)V
+HSPLandroidx/compose/foundation/gestures/DefaultScrollableState;->scroll(Landroidx/compose/foundation/MutatePriority;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/gestures/DraggableKt$awaitDrag$2;-><init>(ILjava/lang/Object;Ljava/lang/Object;Z)V
+HSPLandroidx/compose/foundation/gestures/DraggableKt$awaitDrag$2;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/gestures/DraggableNode$onAttach$1;-><init>(Landroidx/compose/foundation/gestures/DraggableNode;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/foundation/gestures/DraggableNode$onAttach$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/foundation/gestures/DraggableNode$onAttach$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/gestures/DraggableNode$pointerInputNode$1;-><init>(Landroidx/compose/foundation/gestures/DraggableNode;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/foundation/gestures/DraggableNode;-><init>(Landroidx/compose/foundation/gestures/ScrollDraggableState;Landroidx/compose/foundation/gestures/Orientation;ZLandroidx/compose/foundation/interaction/MutableInteractionSourceImpl;Landroidx/compose/ui/node/LayoutNode$_foldedChildren$1;Landroidx/compose/foundation/gestures/ScrollableKt$NoOpOnDragStarted$1;Landroidx/compose/foundation/gestures/ScrollableGesturesNode$onDragStopped$1;)V
+HSPLandroidx/compose/foundation/gestures/DraggableNode;->onAttach()V
+HSPLandroidx/compose/foundation/gestures/ModifierLocalScrollableContainerProvider;-><init>(Z)V
+HSPLandroidx/compose/foundation/gestures/ModifierLocalScrollableContainerProvider;->getProvidedValues()Landroidx/tv/material3/TabKt;
+HSPLandroidx/compose/foundation/gestures/MouseWheelScrollNode$1;-><init>(Landroidx/compose/foundation/gestures/MouseWheelScrollNode;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/foundation/gestures/MouseWheelScrollNode;-><init>(Landroidx/compose/foundation/gestures/ScrollingLogic;)V
+HSPLandroidx/compose/foundation/gestures/MouseWheelScrollNode;->onAttach()V
+HSPLandroidx/compose/foundation/gestures/Orientation;-><clinit>()V
+HSPLandroidx/compose/foundation/gestures/Orientation;-><init>(ILjava/lang/String;)V
+HSPLandroidx/compose/foundation/gestures/ScrollDraggableState;-><init>(Landroidx/compose/foundation/gestures/ScrollingLogic;)V
+HSPLandroidx/compose/foundation/gestures/ScrollableElement;-><init>(Landroidx/compose/foundation/gestures/ScrollableState;Landroidx/compose/foundation/gestures/Orientation;Landroidx/compose/foundation/OverscrollEffect;ZZLandroidx/compose/foundation/gestures/FlingBehavior;Landroidx/compose/foundation/interaction/MutableInteractionSourceImpl;Landroidx/compose/foundation/gestures/BringIntoViewSpec;)V
+HSPLandroidx/compose/foundation/gestures/ScrollableElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/foundation/gestures/ScrollableGesturesNode$onDragStopped$1;-><init>(Landroidx/compose/foundation/gestures/ScrollableGesturesNode;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/foundation/gestures/ScrollableGesturesNode;-><init>(Landroidx/compose/foundation/gestures/ScrollingLogic;Landroidx/compose/foundation/gestures/Orientation;ZLandroidx/compose/ui/input/nestedscroll/NestedScrollDispatcher;Landroidx/compose/foundation/interaction/MutableInteractionSourceImpl;)V
+HSPLandroidx/compose/foundation/gestures/ScrollableKt$NoOpOnDragStarted$1;-><init>(ILkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/foundation/gestures/ScrollableKt$UnityDensity$1;->getDensity()F
+HSPLandroidx/compose/foundation/gestures/ScrollableKt;-><clinit>()V
+HSPLandroidx/compose/foundation/gestures/ScrollableKt;->scrollable$default(Landroidx/compose/foundation/gestures/ScrollableState;Landroidx/compose/foundation/gestures/Orientation;Landroidx/compose/foundation/OverscrollEffect;ZZLandroidx/compose/foundation/gestures/FlingBehavior;Landroidx/compose/foundation/interaction/MutableInteractionSourceImpl;Landroidx/tv/foundation/TvBringIntoViewSpec;I)Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/foundation/gestures/ScrollableNestedScrollConnection;-><init>(Landroidx/compose/foundation/gestures/ScrollingLogic;Z)V
+HSPLandroidx/compose/foundation/gestures/ScrollableNode;-><init>(Landroidx/compose/foundation/gestures/ScrollableState;Landroidx/compose/foundation/gestures/Orientation;Landroidx/compose/foundation/OverscrollEffect;ZZLandroidx/compose/foundation/gestures/FlingBehavior;Landroidx/compose/foundation/interaction/MutableInteractionSourceImpl;Landroidx/compose/foundation/gestures/BringIntoViewSpec;)V
+HSPLandroidx/compose/foundation/gestures/ScrollableNode;->applyFocusProperties(Landroidx/compose/ui/focus/FocusProperties;)V
+HSPLandroidx/compose/foundation/gestures/ScrollableNode;->onAttach()V
+HSPLandroidx/compose/foundation/gestures/ScrollableState;->scroll$default(Landroidx/compose/foundation/gestures/ScrollableState;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/gestures/ScrollingLogic;-><init>(Landroidx/compose/foundation/gestures/ScrollableState;Landroidx/compose/foundation/gestures/Orientation;Landroidx/compose/foundation/OverscrollEffect;ZLandroidx/compose/foundation/gestures/FlingBehavior;Landroidx/compose/ui/input/nestedscroll/NestedScrollDispatcher;)V
+HSPLandroidx/compose/foundation/gestures/UpdatableAnimationState$animateToZero$1;-><init>(Landroidx/compose/foundation/gestures/UpdatableAnimationState;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/foundation/gestures/UpdatableAnimationState$animateToZero$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/gestures/UpdatableAnimationState$animateToZero$4;-><init>(Landroidx/compose/foundation/gestures/UpdatableAnimationState;FLkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/foundation/gestures/UpdatableAnimationState$animateToZero$4;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/gestures/UpdatableAnimationState;-><clinit>()V
+HSPLandroidx/compose/foundation/gestures/UpdatableAnimationState;-><init>(Landroidx/compose/animation/core/AnimationSpec;)V
+HSPLandroidx/compose/foundation/gestures/UpdatableAnimationState;->animateToZero(Landroidx/compose/foundation/layout/OffsetNode$measure$1;Landroidx/compose/ui/node/LayoutNode$_foldedChildren$1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/interaction/FocusInteraction$Unfocus;-><init>(Landroidx/compose/foundation/interaction/FocusInteraction$Focus;)V
+HSPLandroidx/compose/foundation/interaction/FocusInteractionKt$collectIsFocusedAsState$1$1$1;-><init>(Ljava/util/ArrayList;Landroidx/compose/runtime/MutableState;I)V
+HSPLandroidx/compose/foundation/interaction/FocusInteractionKt$collectIsFocusedAsState$1$1$1;->emit(Landroidx/compose/foundation/interaction/Interaction;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/interaction/FocusInteractionKt$collectIsFocusedAsState$1$1$1;->emit(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/interaction/FocusInteractionKt$collectIsFocusedAsState$1$1;-><init>(Landroidx/compose/foundation/interaction/InteractionSource;Landroidx/compose/runtime/MutableState;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/foundation/interaction/FocusInteractionKt$collectIsFocusedAsState$1$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/foundation/interaction/FocusInteractionKt$collectIsFocusedAsState$1$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/interaction/MutableInteractionSourceImpl;-><init>()V
+HSPLandroidx/compose/foundation/interaction/MutableInteractionSourceImpl;->emit(Landroidx/compose/foundation/interaction/Interaction;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/interaction/PressInteraction$Press;-><init>(J)V
+HSPLandroidx/compose/foundation/interaction/PressInteractionKt$collectIsPressedAsState$1$1;-><init>(Landroidx/compose/foundation/interaction/InteractionSource;Landroidx/compose/runtime/MutableState;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/foundation/interaction/PressInteractionKt$collectIsPressedAsState$1$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/foundation/interaction/PressInteractionKt$collectIsPressedAsState$1$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/layout/Arrangement$Center$1;-><init>(I)V
+HSPLandroidx/compose/foundation/layout/Arrangement$Center$1;->arrange(ILandroidx/compose/ui/unit/Density;Landroidx/compose/ui/unit/LayoutDirection;[I[I)V
+HSPLandroidx/compose/foundation/layout/Arrangement$Center$1;->getSpacing-D9Ej5fM()F
+HSPLandroidx/compose/foundation/layout/Arrangement$End$1;-><init>(I)V
+HSPLandroidx/compose/foundation/layout/Arrangement$SpacedAligned;-><init>(F)V
+HSPLandroidx/compose/foundation/layout/Arrangement$SpacedAligned;->arrange(ILandroidx/compose/ui/unit/Density;Landroidx/compose/ui/unit/LayoutDirection;[I[I)V
+HSPLandroidx/compose/foundation/layout/Arrangement$SpacedAligned;->arrange(Landroidx/compose/ui/unit/Density;I[I[I)V
+HSPLandroidx/compose/foundation/layout/Arrangement$SpacedAligned;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/foundation/layout/Arrangement$SpacedAligned;->getSpacing-D9Ej5fM()F
+HSPLandroidx/compose/foundation/layout/Arrangement$Top$1;-><init>(I)V
+HSPLandroidx/compose/foundation/layout/Arrangement$Top$1;->arrange(Landroidx/compose/ui/unit/Density;I[I[I)V
+HSPLandroidx/compose/foundation/layout/Arrangement;-><clinit>()V
+HSPLandroidx/compose/foundation/layout/Arrangement;->placeCenter$foundation_layout_release(I[I[IZ)V
+HSPLandroidx/compose/foundation/layout/Arrangement;->placeLeftOrTop$foundation_layout_release([I[IZ)V
+HSPLandroidx/compose/foundation/layout/BoxKt$Box$2;-><init>(IILjava/lang/Object;)V
+HSPLandroidx/compose/foundation/layout/BoxKt$Box$2;->invoke(Landroidx/compose/runtime/Composer;I)V
+HSPLandroidx/compose/foundation/layout/BoxKt$Box$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/layout/BoxKt$EmptyBoxMeasurePolicy$1;-><clinit>()V
+HSPLandroidx/compose/foundation/layout/BoxKt$EmptyBoxMeasurePolicy$1;->measure-3p2s80s(Landroidx/compose/ui/layout/MeasureScope;Ljava/util/List;J)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/foundation/layout/BoxKt$boxMeasurePolicy$1$2;-><init>(Landroidx/compose/ui/layout/Placeable;Landroidx/compose/ui/layout/Measurable;Landroidx/compose/ui/layout/MeasureScope;IILandroidx/compose/ui/Alignment;)V
+HSPLandroidx/compose/foundation/layout/BoxKt$boxMeasurePolicy$1$2;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/layout/BoxKt$boxMeasurePolicy$1;-><init>(Z)V
+HSPLandroidx/compose/foundation/layout/BoxKt$boxMeasurePolicy$1;->measure-3p2s80s(Landroidx/compose/ui/layout/MeasureScope;Ljava/util/List;J)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/foundation/layout/BoxKt;-><clinit>()V
+HSPLandroidx/compose/foundation/layout/BoxKt;->Box(Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;I)V
+HSPLandroidx/compose/foundation/layout/BoxKt;->access$placeInBox(Landroidx/compose/ui/layout/Placeable$PlacementScope;Landroidx/compose/ui/layout/Placeable;Landroidx/compose/ui/layout/Measurable;Landroidx/compose/ui/unit/LayoutDirection;IILandroidx/compose/ui/Alignment;)V
+HSPLandroidx/compose/foundation/layout/BoxKt;->rememberBoxMeasurePolicy(ZLandroidx/compose/runtime/Composer;)Landroidx/compose/ui/layout/MeasurePolicy;
+HSPLandroidx/compose/foundation/layout/BoxScopeInstance;-><clinit>()V
+HSPLandroidx/compose/foundation/layout/ColumnKt;-><clinit>()V
+HSPLandroidx/compose/foundation/layout/ColumnKt;->columnMeasurePolicy(Landroidx/compose/foundation/layout/Arrangement$Vertical;Landroidx/compose/runtime/Composer;)Landroidx/compose/ui/layout/MeasurePolicy;
+HSPLandroidx/compose/foundation/layout/CrossAxisAlignment$VerticalCrossAxisAlignment;-><init>(ILjava/lang/Object;)V
+HSPLandroidx/compose/foundation/layout/CrossAxisAlignment$VerticalCrossAxisAlignment;->align$foundation_layout_release(ILandroidx/compose/ui/unit/LayoutDirection;)I
+HSPLandroidx/compose/foundation/layout/FillElement;-><init>(IF)V
+HSPLandroidx/compose/foundation/layout/FillElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/foundation/layout/FillElement;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/foundation/layout/FillNode;-><init>(IF)V
+HSPLandroidx/compose/foundation/layout/FillNode;->measure-3p2s80s(Landroidx/compose/ui/layout/MeasureScope;Landroidx/compose/ui/layout/Measurable;J)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/foundation/layout/HorizontalAlignElement;-><init>()V
+HSPLandroidx/compose/foundation/layout/HorizontalAlignElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/foundation/layout/HorizontalAlignElement;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/foundation/layout/HorizontalAlignNode;-><init>(Landroidx/compose/ui/Alignment$Horizontal;)V
+HSPLandroidx/compose/foundation/layout/HorizontalAlignNode;->modifyParentData(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/layout/OffsetElement;-><init>(FF)V
+HSPLandroidx/compose/foundation/layout/OffsetElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/foundation/layout/OffsetElement;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/foundation/layout/OffsetKt;->Spacer(Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;)V
+HSPLandroidx/compose/foundation/layout/OffsetKt;->access$intrinsicSize(Ljava/util/List;Landroidx/compose/ui/CombinedModifier$toString$1;Landroidx/compose/ui/CombinedModifier$toString$1;IIII)I
+HSPLandroidx/compose/foundation/layout/OffsetKt;->getRowColumnParentData(Landroidx/compose/ui/layout/Measurable;)Landroidx/compose/foundation/layout/RowColumnParentData;
+HSPLandroidx/compose/foundation/layout/OffsetKt;->getWeight(Landroidx/compose/foundation/layout/RowColumnParentData;)F
+HSPLandroidx/compose/foundation/layout/OffsetKt;->offset-VpY3zN4(Landroidx/compose/ui/Modifier;FF)Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/foundation/layout/OffsetKt;->padding-3ABfNKs(Landroidx/compose/ui/Modifier;F)Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/foundation/layout/OffsetKt;->padding-VpY3zN4(FF)Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/foundation/layout/OffsetKt;->size(Landroidx/compose/ui/Alignment;Z)Landroidx/compose/foundation/layout/WrapContentElement;
+HSPLandroidx/compose/foundation/layout/OffsetKt;->toBoxConstraints-OenEA2s(JI)J
+HSPLandroidx/compose/foundation/layout/OffsetNode$measure$1;-><init>(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;I)V
+HSPLandroidx/compose/foundation/layout/OffsetNode$measure$1;->invoke(Landroidx/compose/ui/layout/Placeable$PlacementScope;)V
+HSPLandroidx/compose/foundation/layout/OffsetNode$measure$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/layout/OffsetNode;-><init>(FFZ)V
+HSPLandroidx/compose/foundation/layout/OffsetNode;->measure-3p2s80s(Landroidx/compose/ui/layout/MeasureScope;Landroidx/compose/ui/layout/Measurable;J)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/foundation/layout/PaddingElement;-><init>(FFFF)V
+HSPLandroidx/compose/foundation/layout/PaddingElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/foundation/layout/PaddingElement;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/foundation/layout/PaddingNode;-><init>(FFFFZ)V
+HSPLandroidx/compose/foundation/layout/PaddingNode;->measure-3p2s80s(Landroidx/compose/ui/layout/MeasureScope;Landroidx/compose/ui/layout/Measurable;J)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/foundation/layout/PaddingValuesImpl;-><init>(FFFF)V
+HSPLandroidx/compose/foundation/layout/RowColumnImplKt$rowColumnMeasurePolicy$1;-><init>(ILkotlin/jvm/functions/Function5;FLandroidx/compose/foundation/layout/CrossAxisAlignment$VerticalCrossAxisAlignment;)V
+HSPLandroidx/compose/foundation/layout/RowColumnImplKt$rowColumnMeasurePolicy$1;->maxIntrinsicHeight(Landroidx/compose/ui/node/NodeCoordinator;Ljava/util/List;I)I
+HSPLandroidx/compose/foundation/layout/RowColumnImplKt$rowColumnMeasurePolicy$1;->measure-3p2s80s(Landroidx/compose/ui/layout/MeasureScope;Ljava/util/List;J)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/foundation/layout/RowColumnMeasureHelperResult;-><init>(III[I)V
+HSPLandroidx/compose/foundation/layout/RowColumnMeasurementHelper;-><init>(ILkotlin/jvm/functions/Function5;FILandroidx/compose/foundation/layout/OffsetKt;Ljava/util/List;[Landroidx/compose/ui/layout/Placeable;)V
+HSPLandroidx/compose/foundation/layout/RowColumnParentData;-><init>()V
+HSPLandroidx/compose/foundation/layout/RowKt$DefaultRowMeasurePolicy$1;-><clinit>()V
+HSPLandroidx/compose/foundation/layout/RowKt$DefaultRowMeasurePolicy$1;-><init>(I)V
+HSPLandroidx/compose/foundation/layout/RowKt$DefaultRowMeasurePolicy$1;->invoke(ILandroidx/compose/ui/unit/Density;Landroidx/compose/ui/unit/LayoutDirection;[I[I)V
+HSPLandroidx/compose/foundation/layout/RowKt$DefaultRowMeasurePolicy$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/io/Serializable;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/layout/RowKt$rowMeasurePolicy$1$1;-><init>(ILjava/lang/Object;)V
+HSPLandroidx/compose/foundation/layout/RowKt$rowMeasurePolicy$1$1;->invoke(ILandroidx/compose/ui/unit/Density;Landroidx/compose/ui/unit/LayoutDirection;[I[I)V
+HSPLandroidx/compose/foundation/layout/RowKt$rowMeasurePolicy$1$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/io/Serializable;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/layout/RowKt;-><clinit>()V
+HSPLandroidx/compose/foundation/layout/RowKt;->rowMeasurePolicy(Landroidx/compose/foundation/layout/Arrangement$Horizontal;Landroidx/compose/ui/BiasAlignment$Vertical;Landroidx/compose/runtime/Composer;)Landroidx/compose/ui/layout/MeasurePolicy;
+HSPLandroidx/compose/foundation/layout/RowScopeInstance;-><clinit>()V
+HSPLandroidx/compose/foundation/layout/SizeElement;-><init>(FFFFI)V
+HSPLandroidx/compose/foundation/layout/SizeElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/foundation/layout/SizeKt;-><clinit>()V
+HSPLandroidx/compose/foundation/layout/SizeKt;->fillMaxWidth$default(Landroidx/compose/ui/Modifier;)Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/foundation/layout/SizeKt;->height-3ABfNKs(Landroidx/compose/ui/Modifier;F)Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/foundation/layout/SizeKt;->width-3ABfNKs(Landroidx/compose/ui/Modifier;F)Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/foundation/layout/SizeKt;->wrapContentSize$default(Landroidx/compose/ui/Modifier;)Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/foundation/layout/SizeNode;-><init>(FFFFZ)V
+HSPLandroidx/compose/foundation/layout/SizeNode;->getTargetConstraints-OenEA2s(Landroidx/compose/ui/unit/Density;)J
+HSPLandroidx/compose/foundation/layout/SizeNode;->measure-3p2s80s(Landroidx/compose/ui/layout/MeasureScope;Landroidx/compose/ui/layout/Measurable;J)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/foundation/layout/SpacerMeasurePolicy;-><clinit>()V
+HSPLandroidx/compose/foundation/layout/SpacerMeasurePolicy;->measure-3p2s80s(Landroidx/compose/ui/layout/MeasureScope;Ljava/util/List;J)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/foundation/layout/WrapContentElement;-><init>(IZLcom/example/tvcomposebasedtests/tvComponents/AppKt$App$1;Ljava/lang/Object;)V
+HSPLandroidx/compose/foundation/layout/WrapContentElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/foundation/layout/WrapContentElement;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/foundation/layout/WrapContentNode$measure$1;-><init>(Landroidx/compose/foundation/layout/WrapContentNode;ILandroidx/compose/ui/layout/Placeable;ILandroidx/compose/ui/layout/MeasureScope;)V
+HSPLandroidx/compose/foundation/layout/WrapContentNode$measure$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/layout/WrapContentNode;-><init>(IZLkotlin/jvm/functions/Function2;)V
+HSPLandroidx/compose/foundation/layout/WrapContentNode;->measure-3p2s80s(Landroidx/compose/ui/layout/MeasureScope;Landroidx/compose/ui/layout/Measurable;J)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/foundation/lazy/layout/DefaultLazyKey;-><clinit>()V
+HSPLandroidx/compose/foundation/lazy/layout/DefaultLazyKey;-><init>(I)V
+HSPLandroidx/compose/foundation/lazy/layout/DefaultLazyKey;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/foundation/lazy/layout/DefaultLazyKey;->hashCode()I
+HSPLandroidx/compose/foundation/lazy/layout/IntervalList$Interval;-><init>(IILandroidx/compose/foundation/lazy/layout/LazyLayoutIntervalContent$Interval;)V
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutItemContentFactory$CachedItemContent;-><init>(Landroidx/compose/foundation/lazy/layout/LazyLayoutItemContentFactory;ILjava/lang/Object;Ljava/lang/Object;)V
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutItemContentFactory$CachedItemContent;->getContent()Lkotlin/jvm/functions/Function2;
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutItemContentFactory;-><init>(Landroidx/compose/runtime/saveable/SaveableStateHolder;Landroidx/compose/foundation/lazy/layout/LazyLayoutKt$LazyLayout$3$itemContentFactory$1$1;)V
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutItemContentFactory;->getContent(Ljava/lang/Object;ILjava/lang/Object;)Lkotlin/jvm/functions/Function2;
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutItemContentFactory;->getContentType(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutItemReusePolicy;-><init>(Landroidx/compose/foundation/lazy/layout/LazyLayoutItemContentFactory;)V
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutItemReusePolicy;->areCompatible(Ljava/lang/Object;Ljava/lang/Object;)Z
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutItemReusePolicy;->getSlotsToRetain(Landroidx/compose/ui/layout/SubcomposeSlotReusePolicy$SlotIdsSet;)V
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutKt$LazyLayout$3$2$1;-><init>(Ljava/lang/Object;ILjava/lang/Object;)V
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutKt$LazyLayout$3$2$1;->invoke(Landroidx/compose/runtime/Composer;I)V
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutKt$LazyLayout$3$2$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutKt$LazyLayout$3$itemContentFactory$1$1;-><init>(Landroidx/compose/runtime/State;I)V
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutKt$LazyLayout$3$itemContentFactory$1$1;->invoke()Ljava/lang/Object;
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutKt$LazyLayout$3;-><init>(Landroidx/compose/foundation/lazy/layout/LazyLayoutPrefetchState;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function2;ILandroidx/compose/runtime/MutableState;)V
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutKt$LazyLayout$3;->invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutMeasureScopeImpl;-><init>(Landroidx/compose/foundation/lazy/layout/LazyLayoutItemContentFactory;Landroidx/compose/ui/layout/SubcomposeMeasureScope;)V
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutMeasureScopeImpl;->getLayoutDirection()Landroidx/compose/ui/unit/LayoutDirection;
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutMeasureScopeImpl;->isLookingAhead()Z
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutMeasureScopeImpl;->layout(IILjava/util/Map;Lkotlin/jvm/functions/Function1;)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutMeasureScopeImpl;->measure-0kLqBqw(JI)Ljava/util/List;
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutMeasureScopeImpl;->roundToPx-0680j_4(F)I
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutPinnableItem;-><init>(Ljava/lang/Object;Landroidx/compose/foundation/lazy/layout/LazyLayoutPinnedItemList;)V
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutPinnableItem;->getPinsCount()I
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutPinnableItem;->pin()Landroidx/compose/foundation/lazy/layout/LazyLayoutPinnableItem;
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutPinnableItem;->release()V
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutPinnedItemList;-><init>()V
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutPinnedItemList;->get(I)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutPinnedItemList;->isEmpty()Z
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutPinnedItemList;->size()I
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutPrefetchState;->schedulePrefetch-0kLqBqw(JI)Landroidx/compose/foundation/lazy/layout/LazyLayoutPrefetchState$PrefetchHandle;
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutPrefetcher$PrefetchRequest;-><init>(IJ)V
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutPrefetcher$PrefetchRequest;->cancel()V
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutPrefetcher;-><init>(Landroidx/compose/foundation/lazy/layout/LazyLayoutPrefetchState;Landroidx/compose/ui/layout/SubcomposeLayoutState;Landroidx/compose/foundation/lazy/layout/LazyLayoutItemContentFactory;Landroid/view/View;)V
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutPrefetcher;->doFrame(J)V
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutPrefetcher;->onRemembered()V
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutPrefetcher;->run()V
+HSPLandroidx/compose/foundation/lazy/layout/LazySaveableStateHolder$1;-><init>(Landroidx/compose/runtime/saveable/SaveableStateRegistry;I)V
+HSPLandroidx/compose/foundation/lazy/layout/LazySaveableStateHolder$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/lazy/layout/LazySaveableStateHolder$SaveableStateProvider$2$invoke$$inlined$onDispose$1;-><init>(Ljava/lang/Object;ILjava/lang/Object;)V
+HSPLandroidx/compose/foundation/lazy/layout/LazySaveableStateHolder$SaveableStateProvider$2$invoke$$inlined$onDispose$1;->dispose()V
+HSPLandroidx/compose/foundation/lazy/layout/LazySaveableStateHolder;-><init>(Landroidx/compose/runtime/saveable/SaveableStateRegistry;Ljava/util/Map;)V
+HSPLandroidx/compose/foundation/lazy/layout/LazySaveableStateHolder;->SaveableStateProvider(Ljava/lang/Object;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;I)V
+HSPLandroidx/compose/foundation/lazy/layout/LazySaveableStateHolder;->canBeSaved(Ljava/lang/Object;)Z
+HSPLandroidx/compose/foundation/lazy/layout/LazySaveableStateHolder;->consumeRestored(Ljava/lang/String;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/lazy/layout/LazySaveableStateHolder;->performSave()Ljava/util/Map;
+HSPLandroidx/compose/foundation/lazy/layout/LazySaveableStateHolder;->registerProvider(Ljava/lang/String;Lkotlin/jvm/functions/Function0;)Landroidx/compose/runtime/saveable/SaveableStateRegistryImpl$registerProvider$3;
+HSPLandroidx/compose/foundation/lazy/layout/MutableIntervalList;-><init>()V
+HSPLandroidx/compose/foundation/lazy/layout/MutableIntervalList;->addInterval(ILandroidx/compose/foundation/lazy/layout/LazyLayoutIntervalContent$Interval;)V
+HSPLandroidx/compose/foundation/lazy/layout/MutableIntervalList;->checkIndexBounds(I)V
+HSPLandroidx/compose/foundation/lazy/layout/MutableIntervalList;->get(I)Landroidx/compose/foundation/lazy/layout/IntervalList$Interval;
+HSPLandroidx/compose/foundation/relocation/BringIntoViewChildNode;-><init>()V
+HSPLandroidx/compose/foundation/relocation/BringIntoViewChildNode;->getLayoutCoordinates()Landroidx/compose/ui/layout/LayoutCoordinates;
+HSPLandroidx/compose/foundation/relocation/BringIntoViewChildNode;->onPlaced(Landroidx/compose/ui/node/NodeCoordinator;)V
+HSPLandroidx/compose/foundation/relocation/BringIntoViewKt;-><clinit>()V
+HSPLandroidx/compose/foundation/relocation/BringIntoViewRequesterImpl$bringIntoView$1;-><init>(Landroidx/compose/foundation/relocation/BringIntoViewRequesterImpl;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/foundation/relocation/BringIntoViewRequesterImpl$bringIntoView$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/relocation/BringIntoViewRequesterImpl;-><init>()V
+HSPLandroidx/compose/foundation/relocation/BringIntoViewRequesterImpl;->bringIntoView(Landroidx/compose/ui/geometry/Rect;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/relocation/BringIntoViewRequesterNode;-><init>(Landroidx/compose/foundation/relocation/BringIntoViewRequesterImpl;)V
+HSPLandroidx/compose/foundation/relocation/BringIntoViewRequesterNode;->onAttach()V
+HSPLandroidx/compose/foundation/relocation/BringIntoViewRequesterNode;->onDetach()V
+HSPLandroidx/compose/foundation/relocation/BringIntoViewResponderNode$bringChildIntoView$2$1$1;-><init>(Landroidx/compose/foundation/relocation/BringIntoViewResponderNode;Landroidx/compose/ui/layout/LayoutCoordinates;Lkotlin/jvm/functions/Function0;)V
+HSPLandroidx/compose/foundation/relocation/BringIntoViewResponderNode$bringChildIntoView$2$1$1;->invoke()Ljava/lang/Object;
+HSPLandroidx/compose/foundation/relocation/BringIntoViewResponderNode$bringChildIntoView$2$1;-><init>(Landroidx/compose/foundation/relocation/BringIntoViewResponderNode;Landroidx/compose/ui/layout/LayoutCoordinates;Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/foundation/relocation/BringIntoViewResponderNode$bringChildIntoView$2$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/foundation/relocation/BringIntoViewResponderNode$bringChildIntoView$2$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/relocation/BringIntoViewResponderNode$bringChildIntoView$2$2;-><init>(Landroidx/compose/foundation/relocation/BringIntoViewResponderNode;Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/foundation/relocation/BringIntoViewResponderNode$bringChildIntoView$2$2;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/foundation/relocation/BringIntoViewResponderNode$bringChildIntoView$2$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/relocation/BringIntoViewResponderNode$bringChildIntoView$2;-><init>(Landroidx/compose/foundation/relocation/BringIntoViewResponderNode;Landroidx/compose/ui/layout/LayoutCoordinates;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/foundation/relocation/BringIntoViewResponderNode$bringChildIntoView$2;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/foundation/relocation/BringIntoViewResponderNode$bringChildIntoView$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/relocation/BringIntoViewResponderNode$bringChildIntoView$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/relocation/BringIntoViewResponderNode$bringChildIntoView$parentRect$1;-><init>(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;I)V
+HSPLandroidx/compose/foundation/relocation/BringIntoViewResponderNode$bringChildIntoView$parentRect$1;->invoke()Ljava/lang/Object;
+HSPLandroidx/compose/foundation/relocation/BringIntoViewResponderNode;-><init>(Landroidx/compose/foundation/gestures/ContentInViewNode;)V
+HSPLandroidx/compose/foundation/relocation/BringIntoViewResponderNode;->access$bringChildIntoView$localRect(Landroidx/compose/foundation/relocation/BringIntoViewResponderNode;Landroidx/compose/ui/layout/LayoutCoordinates;Lkotlin/jvm/functions/Function0;)Landroidx/compose/ui/geometry/Rect;
+HSPLandroidx/compose/foundation/relocation/BringIntoViewResponderNode;->bringChildIntoView(Landroidx/compose/ui/layout/LayoutCoordinates;Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/relocation/BringIntoViewResponderNode;->getProvidedValues()Landroidx/tv/material3/TabKt;
+HSPLandroidx/compose/foundation/relocation/BringIntoViewResponder_androidKt$defaultBringIntoViewParent$1;-><init>(Landroidx/compose/ui/node/CompositionLocalConsumerModifierNode;)V
+HSPLandroidx/compose/foundation/relocation/BringIntoViewResponder_androidKt$defaultBringIntoViewParent$1;->bringChildIntoView(Landroidx/compose/ui/layout/LayoutCoordinates;Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/shape/CornerBasedShape;-><init>(Landroidx/compose/foundation/shape/CornerSize;Landroidx/compose/foundation/shape/CornerSize;Landroidx/compose/foundation/shape/CornerSize;Landroidx/compose/foundation/shape/CornerSize;)V
+HSPLandroidx/compose/foundation/shape/CornerBasedShape;->createOutline-Pq9zytI(JLandroidx/compose/ui/unit/LayoutDirection;Landroidx/compose/ui/unit/Density;)Landroidx/compose/ui/graphics/BrushKt;
+HSPLandroidx/compose/foundation/shape/DpCornerSize;-><init>(F)V
+HSPLandroidx/compose/foundation/shape/PercentCornerSize;-><init>(F)V
+HSPLandroidx/compose/foundation/shape/PercentCornerSize;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/foundation/shape/PercentCornerSize;->toPx-TmRCtEA(JLandroidx/compose/ui/unit/Density;)F
+HSPLandroidx/compose/foundation/shape/RoundedCornerShape;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/foundation/shape/RoundedCornerShapeKt;-><clinit>()V
+HSPLandroidx/compose/foundation/shape/RoundedCornerShapeKt;->RoundedCornerShape-0680j_4(F)Landroidx/compose/foundation/shape/RoundedCornerShape;
+HSPLandroidx/compose/foundation/text/EmptyMeasurePolicy;-><clinit>()V
+HSPLandroidx/compose/foundation/text/EmptyMeasurePolicy;->measure-3p2s80s(Landroidx/compose/ui/layout/MeasureScope;Ljava/util/List;J)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/foundation/text/modifiers/InlineDensity;-><clinit>()V
+HSPLandroidx/compose/foundation/text/modifiers/MultiParagraphLayoutCache;-><init>(Landroidx/compose/ui/text/AnnotatedString;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/font/FontFamily$Resolver;IZIILjava/util/List;)V
+HSPLandroidx/compose/foundation/text/modifiers/MultiParagraphLayoutCache;->intrinsicHeight(ILandroidx/compose/ui/unit/LayoutDirection;)I
+HSPLandroidx/compose/foundation/text/modifiers/MultiParagraphLayoutCache;->layoutText-K40F9xA(JLandroidx/compose/ui/unit/LayoutDirection;)Landroidx/compose/ui/text/MultiParagraph;
+HSPLandroidx/compose/foundation/text/modifiers/MultiParagraphLayoutCache;->setDensity$foundation_release(Landroidx/compose/ui/unit/Density;)V
+HSPLandroidx/compose/foundation/text/modifiers/MultiParagraphLayoutCache;->setLayoutDirection(Landroidx/compose/ui/unit/LayoutDirection;)Landroidx/compose/ui/text/MultiParagraphIntrinsics;
+HSPLandroidx/compose/foundation/text/modifiers/TextAnnotatedStringElement;-><init>(Landroidx/compose/ui/text/AnnotatedString;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/font/FontFamily$Resolver;Lkotlin/jvm/functions/Function1;IZII)V
+HSPLandroidx/compose/foundation/text/modifiers/TextAnnotatedStringElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/foundation/text/modifiers/TextAnnotatedStringElement;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/foundation/text/modifiers/TextAnnotatedStringElement;->update(Landroidx/compose/ui/Modifier$Node;)V
+HSPLandroidx/compose/foundation/text/modifiers/TextAnnotatedStringNode;-><init>(Landroidx/compose/ui/text/AnnotatedString;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/font/FontFamily$Resolver;Lkotlin/jvm/functions/Function1;IZIILjava/util/List;Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/foundation/text/modifiers/TextAnnotatedStringNode;->doInvalidations(ZZZZ)V
+HSPLandroidx/compose/foundation/text/modifiers/TextAnnotatedStringNode;->draw(Landroidx/compose/ui/graphics/drawscope/ContentDrawScope;)V
+HSPLandroidx/compose/foundation/text/modifiers/TextAnnotatedStringNode;->getLayoutCache()Landroidx/compose/foundation/text/modifiers/MultiParagraphLayoutCache;
+HSPLandroidx/compose/foundation/text/modifiers/TextAnnotatedStringNode;->getLayoutCache(Landroidx/compose/ui/unit/Density;)Landroidx/compose/foundation/text/modifiers/MultiParagraphLayoutCache;
+HSPLandroidx/compose/foundation/text/modifiers/TextAnnotatedStringNode;->getTextSubstitution()Landroidx/compose/foundation/text/modifiers/TextAnnotatedStringNode$TextSubstitutionValue;
+HSPLandroidx/compose/foundation/text/modifiers/TextAnnotatedStringNode;->maxIntrinsicHeight(Landroidx/compose/ui/layout/IntrinsicMeasureScope;Landroidx/compose/ui/layout/Measurable;I)I
+HSPLandroidx/compose/foundation/text/modifiers/TextAnnotatedStringNode;->maxIntrinsicWidth(Landroidx/compose/ui/layout/IntrinsicMeasureScope;Landroidx/compose/ui/layout/Measurable;I)I
+HSPLandroidx/compose/foundation/text/modifiers/TextAnnotatedStringNode;->measure-3p2s80s(Landroidx/compose/ui/layout/MeasureScope;Landroidx/compose/ui/layout/Measurable;J)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/foundation/text/modifiers/TextAnnotatedStringNode;->updateCallbacks(Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Z
+HSPLandroidx/compose/foundation/text/modifiers/TextAnnotatedStringNode;->updateLayoutRelatedArgs-MPT68mk(Landroidx/compose/ui/text/TextStyle;Ljava/util/List;IIZLandroidx/compose/ui/text/font/FontFamily$Resolver;I)Z
+HSPLandroidx/compose/foundation/text/selection/SelectionRegistrarKt;-><clinit>()V
+HSPLandroidx/compose/foundation/text/selection/TextSelectionColors;-><init>(JJ)V
+HSPLandroidx/compose/foundation/text/selection/TextSelectionColorsKt;-><clinit>()V
+HSPLandroidx/compose/material/ripple/PlatformRipple;-><init>(ZFLandroidx/compose/runtime/MutableState;)V
+HSPLandroidx/compose/material/ripple/RippleAlpha;-><init>(FFFF)V
+HSPLandroidx/compose/material/ripple/RippleKt;-><clinit>()V
+HSPLandroidx/compose/material/ripple/RippleThemeKt;-><clinit>()V
+HSPLandroidx/compose/material3/ColorScheme;-><init>(JJJJJJJJJJJJJJJJJJJJJJJJJJJJJ)V
+HSPLandroidx/compose/material3/ColorScheme;->getBackground-0d7_KjU()J
+HSPLandroidx/compose/material3/ColorScheme;->getPrimary-0d7_KjU()J
+HSPLandroidx/compose/material3/ColorScheme;->getSurface-0d7_KjU()J
+HSPLandroidx/compose/material3/ColorSchemeKt;-><clinit>()V
+HSPLandroidx/compose/material3/ColorSchemeKt;->darkColorScheme-G1PFc-w$default(JJJI)Landroidx/compose/material3/ColorScheme;
+HSPLandroidx/compose/material3/MaterialRippleTheme;-><clinit>()V
+HSPLandroidx/compose/material3/MaterialThemeKt$MaterialTheme$2;-><init>(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;III)V
+HSPLandroidx/compose/material3/ShapeDefaults;-><clinit>()V
+HSPLandroidx/compose/material3/Shapes;-><init>(Landroidx/compose/foundation/shape/RoundedCornerShape;Landroidx/compose/foundation/shape/RoundedCornerShape;Landroidx/compose/foundation/shape/RoundedCornerShape;I)V
+HSPLandroidx/compose/material3/ShapesKt$LocalShapes$1;-><clinit>()V
+HSPLandroidx/compose/material3/ShapesKt$LocalShapes$1;-><init>(I)V
+HSPLandroidx/compose/material3/ShapesKt$LocalShapes$1;->invoke()Landroidx/compose/ui/node/LayoutNode;
+HSPLandroidx/compose/material3/ShapesKt$LocalShapes$1;->invoke()Ljava/lang/Object;
+HSPLandroidx/compose/material3/ShapesKt;-><clinit>()V
+HSPLandroidx/compose/material3/TextKt$ProvideTextStyle$1;-><init>(IILjava/lang/Object;Ljava/lang/Object;)V
+HSPLandroidx/compose/material3/TextKt$ProvideTextStyle$1;->invoke(Landroidx/compose/runtime/Composer;I)V
+HSPLandroidx/compose/material3/TextKt$ProvideTextStyle$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/material3/TextKt$Text$1;-><clinit>()V
+HSPLandroidx/compose/material3/TextKt$Text$1;-><init>()V
+HSPLandroidx/compose/material3/TextKt$Text$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/material3/TextKt;-><clinit>()V
+HSPLandroidx/compose/material3/TextKt;->ProvideTextStyle(Landroidx/compose/ui/text/TextStyle;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;I)V
+HSPLandroidx/compose/material3/TextKt;->Text-fLXpl1I(Ljava/lang/String;Landroidx/compose/ui/Modifier;JJLandroidx/compose/ui/text/font/FontStyle;Landroidx/compose/ui/text/font/FontWeight;Landroidx/compose/ui/text/font/FontFamily;JLandroidx/compose/ui/text/style/TextDecoration;Landroidx/compose/ui/text/style/TextAlign;JIZILkotlin/jvm/functions/Function1;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/runtime/Composer;III)V
+HSPLandroidx/compose/material3/Typography;-><init>(Landroidx/compose/ui/text/TextStyle;I)V
+HSPLandroidx/compose/material3/TypographyKt;-><clinit>()V
+HSPLandroidx/compose/material3/tokens/ColorDarkTokens;-><clinit>()V
+HSPLandroidx/compose/material3/tokens/PaletteTokens;-><clinit>()V
+HSPLandroidx/compose/material3/tokens/ShapeTokens;-><clinit>()V
+HSPLandroidx/compose/material3/tokens/TypeScaleTokens;-><clinit>()V
+HSPLandroidx/compose/material3/tokens/TypefaceTokens;-><clinit>()V
+HSPLandroidx/compose/material3/tokens/TypographyTokens;-><clinit>()V
+HSPLandroidx/compose/runtime/Anchor;-><init>(I)V
+HSPLandroidx/compose/runtime/BroadcastFrameClock$FrameAwaiter;-><init>(Lkotlin/jvm/functions/Function1;Lkotlinx/coroutines/CancellableContinuationImpl;)V
+HSPLandroidx/compose/runtime/BroadcastFrameClock;-><init>(Landroidx/compose/runtime/Pending$keyMap$2;)V
+HSPLandroidx/compose/runtime/BroadcastFrameClock;->fold(Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/BroadcastFrameClock;->get(Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element;
+HSPLandroidx/compose/runtime/BroadcastFrameClock;->sendFrame(J)V
+HSPLandroidx/compose/runtime/BroadcastFrameClock;->withFrameNanos(Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/ComposableSingletons$CompositionKt;-><clinit>()V
+HSPLandroidx/compose/runtime/ComposerImpl$CompositionContextHolder;-><init>(Landroidx/compose/runtime/ComposerImpl$CompositionContextImpl;)V
+HSPLandroidx/compose/runtime/ComposerImpl$CompositionContextHolder;->onRemembered()V
+HSPLandroidx/compose/runtime/ComposerImpl$CompositionContextImpl;-><init>(Landroidx/compose/runtime/ComposerImpl;IZLandroidx/compose/runtime/CompositionObserverHolder;)V
+HSPLandroidx/compose/runtime/ComposerImpl$CompositionContextImpl;->composeInitial$runtime_release(Landroidx/compose/runtime/CompositionImpl;Landroidx/compose/runtime/internal/ComposableLambdaImpl;)V
+HSPLandroidx/compose/runtime/ComposerImpl$CompositionContextImpl;->doneComposing$runtime_release()V
+HSPLandroidx/compose/runtime/ComposerImpl$CompositionContextImpl;->getCollectingParameterInformation$runtime_release()Z
+HSPLandroidx/compose/runtime/ComposerImpl$CompositionContextImpl;->getCompositionLocalScope$runtime_release()Landroidx/compose/runtime/PersistentCompositionLocalMap;
+HSPLandroidx/compose/runtime/ComposerImpl$CompositionContextImpl;->getCompoundHashKey$runtime_release()I
+HSPLandroidx/compose/runtime/ComposerImpl$CompositionContextImpl;->getEffectCoroutineContext()Lkotlin/coroutines/CoroutineContext;
+HSPLandroidx/compose/runtime/ComposerImpl$CompositionContextImpl;->getObserverHolder$runtime_release()Landroidx/compose/runtime/CompositionObserverHolder;
+HSPLandroidx/compose/runtime/ComposerImpl$CompositionContextImpl;->invalidate$runtime_release(Landroidx/compose/runtime/CompositionImpl;)V
+HSPLandroidx/compose/runtime/ComposerImpl$CompositionContextImpl;->registerComposer$runtime_release(Landroidx/compose/runtime/ComposerImpl;)V
+HSPLandroidx/compose/runtime/ComposerImpl$CompositionContextImpl;->startComposing$runtime_release()V
+HSPLandroidx/compose/runtime/ComposerImpl$derivedStateObserver$1;-><init>(ILjava/lang/Object;)V
+HSPLandroidx/compose/runtime/ComposerImpl$derivedStateObserver$1;->done()V
+HSPLandroidx/compose/runtime/ComposerImpl$derivedStateObserver$1;->start()V
+HSPLandroidx/compose/runtime/ComposerImpl;-><init>(Landroidx/compose/ui/node/UiApplier;Landroidx/compose/runtime/CompositionContext;Landroidx/compose/runtime/SlotTable;Ljava/util/HashSet;Landroidx/compose/runtime/changelist/ChangeList;Landroidx/compose/runtime/changelist/ChangeList;Landroidx/compose/runtime/CompositionImpl;)V
+HSPLandroidx/compose/runtime/ComposerImpl;->apply(Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)V
+HSPLandroidx/compose/runtime/ComposerImpl;->changed(F)Z
+HSPLandroidx/compose/runtime/ComposerImpl;->changed(I)Z
+HSPLandroidx/compose/runtime/ComposerImpl;->changed(J)Z
+HSPLandroidx/compose/runtime/ComposerImpl;->changed(Ljava/lang/Object;)Z
+HSPLandroidx/compose/runtime/ComposerImpl;->changed(Z)Z
+HSPLandroidx/compose/runtime/ComposerImpl;->changedInstance(Ljava/lang/Object;)Z
+HSPLandroidx/compose/runtime/ComposerImpl;->cleanUpCompose()V
+HSPLandroidx/compose/runtime/ComposerImpl;->compoundKeyOf(III)I
+HSPLandroidx/compose/runtime/ComposerImpl;->consume(Landroidx/compose/runtime/ProvidableCompositionLocal;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/ComposerImpl;->createFreshInsertTable()V
+HSPLandroidx/compose/runtime/ComposerImpl;->createNode(Lkotlin/jvm/functions/Function0;)V
+HSPLandroidx/compose/runtime/ComposerImpl;->currentCompositionLocalScope()Landroidx/compose/runtime/PersistentCompositionLocalMap;
+HSPLandroidx/compose/runtime/ComposerImpl;->deactivateToEndGroup(Z)V
+HSPLandroidx/compose/runtime/ComposerImpl;->doCompose(Landroidx/core/content/res/ComplexColorCompat;Landroidx/compose/runtime/internal/ComposableLambdaImpl;)V
+HSPLandroidx/compose/runtime/ComposerImpl;->doRecordDownsFor(II)V
+HSPLandroidx/compose/runtime/ComposerImpl;->endDefaults()V
+HSPLandroidx/compose/runtime/ComposerImpl;->endRestartGroup()Landroidx/compose/runtime/RecomposeScopeImpl;
+HSPLandroidx/compose/runtime/ComposerImpl;->endReusableGroup()V
+HSPLandroidx/compose/runtime/ComposerImpl;->endRoot()V
+HSPLandroidx/compose/runtime/ComposerImpl;->enterGroup(ZLandroidx/compose/runtime/Pending;)V
+HSPLandroidx/compose/runtime/ComposerImpl;->getCurrentRecomposeScope$runtime_release()Landroidx/compose/runtime/RecomposeScopeImpl;
+HSPLandroidx/compose/runtime/ComposerImpl;->getDefaultsInvalid()Z
+HSPLandroidx/compose/runtime/ComposerImpl;->getSkipping()Z
+HSPLandroidx/compose/runtime/ComposerImpl;->nextSlot()Ljava/lang/Object;
+HSPLandroidx/compose/runtime/ComposerImpl;->recompose$runtime_release(Landroidx/core/content/res/ComplexColorCompat;)Z
+HSPLandroidx/compose/runtime/ComposerImpl;->recomposeToGroupEnd()V
+HSPLandroidx/compose/runtime/ComposerImpl;->recordUpsAndDowns(III)V
+HSPLandroidx/compose/runtime/ComposerImpl;->skipCurrentGroup()V
+HSPLandroidx/compose/runtime/ComposerImpl;->skipReaderToGroupEnd()V
+HSPLandroidx/compose/runtime/ComposerImpl;->skipToGroupEnd()V
+HSPLandroidx/compose/runtime/ComposerImpl;->start-BaiHCIY(IILandroidx/compose/runtime/OpaqueKey;Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/ComposerImpl;->startDefaults()V
+HSPLandroidx/compose/runtime/ComposerImpl;->startGroup(ILandroidx/compose/runtime/OpaqueKey;)V
+HSPLandroidx/compose/runtime/ComposerImpl;->startReaderGroup(Ljava/lang/Object;Z)V
+HSPLandroidx/compose/runtime/ComposerImpl;->startReplaceableGroup(I)V
+HSPLandroidx/compose/runtime/ComposerImpl;->startRestartGroup(I)Landroidx/compose/runtime/ComposerImpl;
+HSPLandroidx/compose/runtime/ComposerImpl;->startReusableGroup(Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/ComposerImpl;->startReusableNode()V
+HSPLandroidx/compose/runtime/ComposerImpl;->startRoot()V
+HSPLandroidx/compose/runtime/ComposerImpl;->tryImminentInvalidation$runtime_release(Landroidx/compose/runtime/RecomposeScopeImpl;Ljava/lang/Object;)Z
+HSPLandroidx/compose/runtime/ComposerImpl;->updateCompoundKeyWhenWeEnterGroup(Ljava/lang/Object;ILjava/lang/Object;)V
+HSPLandroidx/compose/runtime/ComposerImpl;->updateCompoundKeyWhenWeEnterGroupKeyHash(I)V
+HSPLandroidx/compose/runtime/ComposerImpl;->updateCompoundKeyWhenWeExitGroup(Ljava/lang/Object;ILjava/lang/Object;)V
+HSPLandroidx/compose/runtime/ComposerImpl;->updateCompoundKeyWhenWeExitGroupKeyHash(I)V
+HSPLandroidx/compose/runtime/ComposerImpl;->updateNodeCount(II)V
+HSPLandroidx/compose/runtime/ComposerImpl;->updateNodeCountOverrides(II)V
+HSPLandroidx/compose/runtime/ComposerImpl;->updateProviderMapGroup(Landroidx/compose/runtime/PersistentCompositionLocalMap;Landroidx/compose/runtime/internal/PersistentCompositionLocalHashMap;)Landroidx/compose/runtime/internal/PersistentCompositionLocalHashMap;
+HSPLandroidx/compose/runtime/ComposerImpl;->updatedNodeCount(I)I
+HSPLandroidx/compose/runtime/ComposerImpl;->useNode()V
+HSPLandroidx/compose/runtime/CompositionContext;->doneComposing$runtime_release()V
+HSPLandroidx/compose/runtime/CompositionContext;->getCompositionLocalScope$runtime_release()Landroidx/compose/runtime/PersistentCompositionLocalMap;
+HSPLandroidx/compose/runtime/CompositionContext;->getObserverHolder$runtime_release()Landroidx/compose/runtime/CompositionObserverHolder;
+HSPLandroidx/compose/runtime/CompositionContext;->registerComposer$runtime_release(Landroidx/compose/runtime/ComposerImpl;)V
+HSPLandroidx/compose/runtime/CompositionContext;->startComposing$runtime_release()V
+HSPLandroidx/compose/runtime/CompositionContextKt;-><clinit>()V
+HSPLandroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;-><init>(Ljava/util/HashSet;)V
+HSPLandroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;->dispatchAbandons()V
+HSPLandroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;->dispatchRememberObservers()V
+HSPLandroidx/compose/runtime/CompositionImpl;-><init>(Landroidx/compose/runtime/CompositionContext;Landroidx/compose/ui/node/UiApplier;)V
+HSPLandroidx/compose/runtime/CompositionImpl;->addPendingInvalidationsLocked(Ljava/util/HashSet;Ljava/lang/Object;Z)Ljava/util/HashSet;
+HSPLandroidx/compose/runtime/CompositionImpl;->addPendingInvalidationsLocked(Ljava/util/Set;Z)V
+HSPLandroidx/compose/runtime/CompositionImpl;->applyChanges()V
+HSPLandroidx/compose/runtime/CompositionImpl;->applyChangesInLocked(Landroidx/compose/runtime/changelist/ChangeList;)V
+HSPLandroidx/compose/runtime/CompositionImpl;->applyLateChanges()V
+HSPLandroidx/compose/runtime/CompositionImpl;->changesApplied()V
+HSPLandroidx/compose/runtime/CompositionImpl;->cleanUpDerivedStateObservations()V
+HSPLandroidx/compose/runtime/CompositionImpl;->composeContent(Landroidx/compose/runtime/internal/ComposableLambdaImpl;)V
+HSPLandroidx/compose/runtime/CompositionImpl;->drainPendingModificationsForCompositionLocked()V
+HSPLandroidx/compose/runtime/CompositionImpl;->drainPendingModificationsLocked()V
+HSPLandroidx/compose/runtime/CompositionImpl;->getHasInvalidations()Z
+HSPLandroidx/compose/runtime/CompositionImpl;->invalidate$enumunboxing$(Landroidx/compose/runtime/RecomposeScopeImpl;Ljava/lang/Object;)I
+HSPLandroidx/compose/runtime/CompositionImpl;->invalidateChecked$enumunboxing$(Landroidx/compose/runtime/RecomposeScopeImpl;Landroidx/compose/runtime/Anchor;Ljava/lang/Object;)I
+HSPLandroidx/compose/runtime/CompositionImpl;->invalidateScopeOfLocked(Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/CompositionImpl;->isDisposed()Z
+HSPLandroidx/compose/runtime/CompositionImpl;->observer()V
+HSPLandroidx/compose/runtime/CompositionImpl;->observesAnyOf(Landroidx/compose/runtime/collection/IdentityArraySet;)Z
+HSPLandroidx/compose/runtime/CompositionImpl;->recompose()Z
+HSPLandroidx/compose/runtime/CompositionImpl;->recomposeScopeReleased()V
+HSPLandroidx/compose/runtime/CompositionImpl;->recordModificationsOf(Landroidx/compose/runtime/collection/IdentityArraySet;)V
+HSPLandroidx/compose/runtime/CompositionImpl;->recordReadOf(Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/CompositionImpl;->recordWriteOf(Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/CompositionImpl;->setContent(Lkotlin/jvm/functions/Function2;)V
+HSPLandroidx/compose/runtime/CompositionKt;-><clinit>()V
+HSPLandroidx/compose/runtime/CompositionLocal;-><init>(Lkotlin/jvm/functions/Function0;)V
+HSPLandroidx/compose/runtime/CompositionLocalMap$Companion;-><clinit>()V
+HSPLandroidx/compose/runtime/CompositionLocalMap;-><clinit>()V
+HSPLandroidx/compose/runtime/CompositionObserverHolder;-><init>()V
+HSPLandroidx/compose/runtime/CompositionScopedCoroutineScopeCanceller;-><init>(Lkotlinx/coroutines/internal/ContextScope;)V
+HSPLandroidx/compose/runtime/CompositionScopedCoroutineScopeCanceller;->onRemembered()V
+HSPLandroidx/compose/runtime/DerivedSnapshotState$ResultRecord;-><clinit>()V
+HSPLandroidx/compose/runtime/DerivedSnapshotState$ResultRecord;-><init>()V
+HSPLandroidx/compose/runtime/DerivedSnapshotState$ResultRecord;->assign(Landroidx/compose/runtime/snapshots/StateRecord;)V
+HSPLandroidx/compose/runtime/DerivedSnapshotState$ResultRecord;->create()Landroidx/compose/runtime/snapshots/StateRecord;
+HSPLandroidx/compose/runtime/DerivedSnapshotState$ResultRecord;->isValid(Landroidx/compose/runtime/DerivedSnapshotState;Landroidx/compose/runtime/snapshots/Snapshot;)Z
+HSPLandroidx/compose/runtime/DerivedSnapshotState$ResultRecord;->readableHash(Landroidx/compose/runtime/DerivedSnapshotState;Landroidx/compose/runtime/snapshots/Snapshot;)I
+HSPLandroidx/compose/runtime/DerivedSnapshotState;-><init>(Landroidx/compose/runtime/ReferentialEqualityPolicy;Lkotlin/jvm/functions/Function0;)V
+HSPLandroidx/compose/runtime/DerivedSnapshotState;->currentRecord(Landroidx/compose/runtime/DerivedSnapshotState$ResultRecord;Landroidx/compose/runtime/snapshots/Snapshot;ZLkotlin/jvm/functions/Function0;)Landroidx/compose/runtime/DerivedSnapshotState$ResultRecord;
+HSPLandroidx/compose/runtime/DerivedSnapshotState;->getCurrentRecord()Landroidx/compose/runtime/DerivedSnapshotState$ResultRecord;
+HSPLandroidx/compose/runtime/DerivedSnapshotState;->getFirstStateRecord()Landroidx/compose/runtime/snapshots/StateRecord;
+HSPLandroidx/compose/runtime/DerivedSnapshotState;->getValue()Ljava/lang/Object;
+HSPLandroidx/compose/runtime/DerivedSnapshotState;->prependStateRecord(Landroidx/compose/runtime/snapshots/StateRecord;)V
+HSPLandroidx/compose/runtime/DisposableEffectImpl;-><init>(Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/runtime/DisposableEffectImpl;->onForgotten()V
+HSPLandroidx/compose/runtime/DisposableEffectImpl;->onRemembered()V
+HSPLandroidx/compose/runtime/DynamicProvidableCompositionLocal;-><init>(Landroidx/compose/runtime/SnapshotMutationPolicy;Lkotlin/jvm/functions/Function0;)V
+HSPLandroidx/compose/runtime/DynamicProvidableCompositionLocal;->updatedStateOf$runtime_release(Ljava/lang/Object;Landroidx/compose/runtime/State;)Landroidx/compose/runtime/State;
+HSPLandroidx/compose/runtime/GroupInfo;-><init>(III)V
+HSPLandroidx/compose/runtime/IntStack;-><init>()V
+HSPLandroidx/compose/runtime/IntStack;-><init>(I)V
+HSPLandroidx/compose/runtime/IntStack;->getSize()I
+HSPLandroidx/compose/runtime/IntStack;->pop()I
+HSPLandroidx/compose/runtime/IntStack;->push(I)V
+HSPLandroidx/compose/runtime/IntStack;->pushDiagonal(III)V
+HSPLandroidx/compose/runtime/IntStack;->pushRange(IIII)V
+HSPLandroidx/compose/runtime/IntStack;->quickSort(II)V
+HSPLandroidx/compose/runtime/IntStack;->swapDiagonal(II)V
+HSPLandroidx/compose/runtime/Invalidation;-><init>(Landroidx/compose/runtime/RecomposeScopeImpl;ILandroidx/compose/runtime/collection/IdentityArraySet;)V
+HSPLandroidx/compose/runtime/KeyInfo;-><init>(ILjava/lang/Object;II)V
+HSPLandroidx/compose/runtime/Latch$await$2$2;-><init>(Ljava/lang/Object;ILjava/lang/Object;)V
+HSPLandroidx/compose/runtime/Latch$await$2$2;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/Latch$await$2$2;->invoke(Ljava/lang/Throwable;)V
+HSPLandroidx/compose/runtime/Latch;-><init>()V
+HSPLandroidx/compose/runtime/Latch;-><init>(I)V
+HSPLandroidx/compose/runtime/Latch;->add(Landroidx/compose/ui/node/LayoutNode;)V
+HSPLandroidx/compose/runtime/Latch;->contains(Landroidx/compose/ui/node/LayoutNode;)Z
+HSPLandroidx/compose/runtime/Latch;->pop()Landroidx/compose/ui/node/LayoutNode;
+HSPLandroidx/compose/runtime/Latch;->remove(Landroidx/compose/ui/node/LayoutNode;)Z
+HSPLandroidx/compose/runtime/LaunchedEffectImpl;-><init>(Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)V
+HSPLandroidx/compose/runtime/LaunchedEffectImpl;->onForgotten()V
+HSPLandroidx/compose/runtime/LaunchedEffectImpl;->onRemembered()V
+HSPLandroidx/compose/runtime/LazyValueHolder;-><init>(Lkotlin/jvm/functions/Function0;)V
+HSPLandroidx/compose/runtime/LazyValueHolder;->getValue()Ljava/lang/Object;
+HSPLandroidx/compose/runtime/MonotonicFrameClock;->getKey()Lkotlin/coroutines/CoroutineContext$Key;
+HSPLandroidx/compose/runtime/OpaqueKey;-><init>(Ljava/lang/String;)V
+HSPLandroidx/compose/runtime/OpaqueKey;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/runtime/OpaqueKey;->hashCode()I
+HSPLandroidx/compose/runtime/ParcelableSnapshotMutableFloatState;-><clinit>()V
+HSPLandroidx/compose/runtime/ParcelableSnapshotMutableIntState;-><clinit>()V
+HSPLandroidx/compose/runtime/ParcelableSnapshotMutableState;-><clinit>()V
+HSPLandroidx/compose/runtime/PausableMonotonicFrameClock$withFrameNanos$1;-><init>(Landroidx/compose/runtime/PausableMonotonicFrameClock;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/runtime/PausableMonotonicFrameClock$withFrameNanos$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/PausableMonotonicFrameClock;-><init>(Landroidx/compose/runtime/MonotonicFrameClock;)V
+HSPLandroidx/compose/runtime/PausableMonotonicFrameClock;->fold(Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/PausableMonotonicFrameClock;->get(Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element;
+HSPLandroidx/compose/runtime/PausableMonotonicFrameClock;->minusKey(Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext;
+HSPLandroidx/compose/runtime/PausableMonotonicFrameClock;->withFrameNanos(Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/Pending$keyMap$2;-><init>(ILjava/lang/Object;)V
+HSPLandroidx/compose/runtime/Pending$keyMap$2;->invoke()Ljava/lang/Object;
+HSPLandroidx/compose/runtime/Pending;-><init>(ILjava/util/ArrayList;)V
+HSPLandroidx/compose/runtime/ProduceStateScopeImpl;-><init>(Landroidx/compose/runtime/MutableState;Lkotlin/coroutines/CoroutineContext;)V
+HSPLandroidx/compose/runtime/ProduceStateScopeImpl;->setValue(Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/ProvidableCompositionLocal;->provides(Ljava/lang/Object;)Landroidx/compose/runtime/ProvidedValue;
+HSPLandroidx/compose/runtime/ProvidedValue;-><init>(Landroidx/compose/runtime/CompositionLocal;Ljava/lang/Object;Z)V
+HSPLandroidx/compose/runtime/RecomposeScopeImpl$end$1$2;-><init>(IILjava/lang/Object;Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/RecomposeScopeImpl$end$1$2;-><init>(Landroidx/compose/runtime/DerivedSnapshotState;Landroidx/core/content/res/ComplexColorCompat;I)V
+HSPLandroidx/compose/runtime/RecomposeScopeImpl$end$1$2;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/RecomposeScopeImpl;-><init>(Landroidx/compose/runtime/CompositionImpl;)V
+HSPLandroidx/compose/runtime/RecomposeScopeImpl;->invalidateForResult$enumunboxing$(Ljava/lang/Object;)I
+HSPLandroidx/compose/runtime/Recomposer$State;-><clinit>()V
+HSPLandroidx/compose/runtime/Recomposer$State;-><init>(ILjava/lang/String;)V
+HSPLandroidx/compose/runtime/Recomposer$effectJob$1$1;-><init>(ILjava/lang/Object;)V
+HSPLandroidx/compose/runtime/Recomposer$effectJob$1$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/Recomposer$join$2;-><init>(Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/runtime/Recomposer$join$2;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/runtime/Recomposer$join$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/Recomposer$join$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/Recomposer$performRecompose$1$1;-><init>(Ljava/lang/Object;ILjava/lang/Object;)V
+HSPLandroidx/compose/runtime/Recomposer$performRecompose$1$1;->invoke()Ljava/lang/Object;
+HSPLandroidx/compose/runtime/Recomposer$performRecompose$1$1;->invoke()V
+HSPLandroidx/compose/runtime/Recomposer$recompositionRunner$2$3;-><init>(Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/MonotonicFrameClock;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/runtime/Recomposer$recompositionRunner$2$3;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/runtime/Recomposer$recompositionRunner$2$3;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/Recomposer$recompositionRunner$2$3;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/Recomposer$recompositionRunner$2$unregisterApplyObserver$1;-><init>(ILjava/lang/Object;)V
+HSPLandroidx/compose/runtime/Recomposer$recompositionRunner$2$unregisterApplyObserver$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/Recomposer$recompositionRunner$2$unregisterApplyObserver$1;->invoke(Ljava/util/Set;)V
+HSPLandroidx/compose/runtime/Recomposer$recompositionRunner$2;-><init>(Landroidx/compose/runtime/Recomposer;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/MonotonicFrameClock;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/runtime/Recomposer$recompositionRunner$2;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/runtime/Recomposer$recompositionRunner$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/Recomposer$recompositionRunner$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2$1;-><init>(Landroidx/compose/runtime/Recomposer;Landroidx/compose/runtime/collection/IdentityArraySet;Landroidx/compose/runtime/collection/IdentityArraySet;Ljava/util/List;Ljava/util/List;Ljava/util/Set;Ljava/util/List;Ljava/util/Set;)V
+HSPLandroidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2;-><init>(Landroidx/compose/runtime/Recomposer;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/Recomposer;-><clinit>()V
+HSPLandroidx/compose/runtime/Recomposer;-><init>(Lkotlin/coroutines/CoroutineContext;)V
+HSPLandroidx/compose/runtime/Recomposer;->access$performRecompose(Landroidx/compose/runtime/Recomposer;Landroidx/compose/runtime/CompositionImpl;Landroidx/compose/runtime/collection/IdentityArraySet;)Landroidx/compose/runtime/CompositionImpl;
+HSPLandroidx/compose/runtime/Recomposer;->access$recordComposerModifications(Landroidx/compose/runtime/Recomposer;)Z
+HSPLandroidx/compose/runtime/Recomposer;->applyAndCheck(Landroidx/compose/runtime/snapshots/MutableSnapshot;)V
+HSPLandroidx/compose/runtime/Recomposer;->composeInitial$runtime_release(Landroidx/compose/runtime/CompositionImpl;Landroidx/compose/runtime/internal/ComposableLambdaImpl;)V
+HSPLandroidx/compose/runtime/Recomposer;->deriveStateLocked()Lkotlinx/coroutines/CancellableContinuation;
+HSPLandroidx/compose/runtime/Recomposer;->getCollectingParameterInformation$runtime_release()Z
+HSPLandroidx/compose/runtime/Recomposer;->getCompoundHashKey$runtime_release()I
+HSPLandroidx/compose/runtime/Recomposer;->getEffectCoroutineContext()Lkotlin/coroutines/CoroutineContext;
+HSPLandroidx/compose/runtime/Recomposer;->getHasBroadcastFrameClockAwaitersLocked()Z
+HSPLandroidx/compose/runtime/Recomposer;->getHasSchedulingWork()Z
+HSPLandroidx/compose/runtime/Recomposer;->getKnownCompositions()Ljava/util/List;
+HSPLandroidx/compose/runtime/Recomposer;->invalidate$runtime_release(Landroidx/compose/runtime/CompositionImpl;)V
+HSPLandroidx/compose/runtime/Recomposer;->performInitialMovableContentInserts(Landroidx/compose/runtime/CompositionImpl;)V
+HSPLandroidx/compose/runtime/ReferentialEqualityPolicy;-><clinit>()V
+HSPLandroidx/compose/runtime/ReferentialEqualityPolicy;->equivalent(Ljava/lang/Object;Ljava/lang/Object;)Z
+HSPLandroidx/compose/runtime/SkippableUpdater;-><init>(Landroidx/compose/runtime/Composer;)V
+HSPLandroidx/compose/runtime/SlotReader;-><init>(Landroidx/compose/runtime/SlotTable;)V
+HSPLandroidx/compose/runtime/SlotReader;->anchor(I)Landroidx/compose/runtime/Anchor;
+HSPLandroidx/compose/runtime/SlotReader;->aux([II)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/SlotReader;->close()V
+HSPLandroidx/compose/runtime/SlotReader;->endGroup()V
+HSPLandroidx/compose/runtime/SlotReader;->getGroupAux()Ljava/lang/Object;
+HSPLandroidx/compose/runtime/SlotReader;->getGroupKey()I
+HSPLandroidx/compose/runtime/SlotReader;->groupGet(II)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/SlotReader;->groupSize(I)I
+HSPLandroidx/compose/runtime/SlotReader;->isNode(I)Z
+HSPLandroidx/compose/runtime/SlotReader;->node(I)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/SlotReader;->nodeCount(I)I
+HSPLandroidx/compose/runtime/SlotReader;->objectKey([II)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/SlotReader;->parent(I)I
+HSPLandroidx/compose/runtime/SlotReader;->reposition(I)V
+HSPLandroidx/compose/runtime/SlotReader;->skipGroup()I
+HSPLandroidx/compose/runtime/SlotReader;->skipToGroupEnd()V
+HSPLandroidx/compose/runtime/SlotReader;->startGroup()V
+HSPLandroidx/compose/runtime/SlotTable;-><init>()V
+HSPLandroidx/compose/runtime/SlotTable;->anchorIndex(Landroidx/compose/runtime/Anchor;)I
+HSPLandroidx/compose/runtime/SlotTable;->openReader()Landroidx/compose/runtime/SlotReader;
+HSPLandroidx/compose/runtime/SlotTable;->openWriter()Landroidx/compose/runtime/SlotWriter;
+HSPLandroidx/compose/runtime/SlotTable;->ownsAnchor(Landroidx/compose/runtime/Anchor;)Z
+HSPLandroidx/compose/runtime/SlotWriter;-><clinit>()V
+HSPLandroidx/compose/runtime/SlotWriter;-><init>(Landroidx/compose/runtime/SlotTable;)V
+HSPLandroidx/compose/runtime/SlotWriter;->advanceBy(I)V
+HSPLandroidx/compose/runtime/SlotWriter;->anchor(I)Landroidx/compose/runtime/Anchor;
+HSPLandroidx/compose/runtime/SlotWriter;->auxIndex([II)I
+HSPLandroidx/compose/runtime/SlotWriter;->beginInsert()V
+HSPLandroidx/compose/runtime/SlotWriter;->close()V
+HSPLandroidx/compose/runtime/SlotWriter;->dataIndex([II)I
+HSPLandroidx/compose/runtime/SlotWriter;->dataIndexToDataAddress(I)I
+HSPLandroidx/compose/runtime/SlotWriter;->endInsert()V
+HSPLandroidx/compose/runtime/SlotWriter;->getSize$runtime_release()I
+HSPLandroidx/compose/runtime/SlotWriter;->groupIndexToAddress(I)I
+HSPLandroidx/compose/runtime/SlotWriter;->groupSize(I)I
+HSPLandroidx/compose/runtime/SlotWriter;->markGroup$default(Landroidx/compose/runtime/SlotWriter;)V
+HSPLandroidx/compose/runtime/SlotWriter;->moveFrom(Landroidx/compose/runtime/SlotTable;I)V
+HSPLandroidx/compose/runtime/SlotWriter;->moveGroupGapTo(I)V
+HSPLandroidx/compose/runtime/SlotWriter;->moveSlotGapTo(II)V
+HSPLandroidx/compose/runtime/SlotWriter;->node(I)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/SlotWriter;->parent(I)I
+HSPLandroidx/compose/runtime/SlotWriter;->parent([II)I
+HSPLandroidx/compose/runtime/SlotWriter;->recalculateMarks()V
+HSPLandroidx/compose/runtime/SlotWriter;->set(IILjava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/SlotWriter;->skipToGroupEnd()V
+HSPLandroidx/compose/runtime/SlotWriter;->slotIndex([II)I
+HSPLandroidx/compose/runtime/SlotWriter;->updateAux(Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/SlotWriter;->updateContainsMark(I)V
+HSPLandroidx/compose/runtime/SlotWriter;->updateNodeOfGroup(ILjava/lang/Object;)V
+HSPLandroidx/compose/runtime/SnapshotMutableFloatStateImpl$FloatStateStateRecord;-><init>(F)V
+HSPLandroidx/compose/runtime/SnapshotMutableFloatStateImpl;-><init>(F)V
+HSPLandroidx/compose/runtime/SnapshotMutableFloatStateImpl;->setFloatValue(F)V
+HSPLandroidx/compose/runtime/SnapshotMutableIntStateImpl$IntStateStateRecord;-><init>(I)V
+HSPLandroidx/compose/runtime/SnapshotMutableIntStateImpl$IntStateStateRecord;->assign(Landroidx/compose/runtime/snapshots/StateRecord;)V
+HSPLandroidx/compose/runtime/SnapshotMutableIntStateImpl$IntStateStateRecord;->create()Landroidx/compose/runtime/snapshots/StateRecord;
+HSPLandroidx/compose/runtime/SnapshotMutableIntStateImpl;-><init>(I)V
+HSPLandroidx/compose/runtime/SnapshotMutableIntStateImpl;->getFirstStateRecord()Landroidx/compose/runtime/snapshots/StateRecord;
+HSPLandroidx/compose/runtime/SnapshotMutableIntStateImpl;->getIntValue()I
+HSPLandroidx/compose/runtime/SnapshotMutableIntStateImpl;->prependStateRecord(Landroidx/compose/runtime/snapshots/StateRecord;)V
+HSPLandroidx/compose/runtime/SnapshotMutableIntStateImpl;->setIntValue(I)V
+HSPLandroidx/compose/runtime/SnapshotMutableStateImpl$StateStateRecord;-><init>(Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/SnapshotMutableStateImpl$StateStateRecord;->assign(Landroidx/compose/runtime/snapshots/StateRecord;)V
+HSPLandroidx/compose/runtime/SnapshotMutableStateImpl$StateStateRecord;->create()Landroidx/compose/runtime/snapshots/StateRecord;
+HSPLandroidx/compose/runtime/SnapshotMutableStateImpl;-><init>(Ljava/lang/Object;Landroidx/compose/runtime/SnapshotMutationPolicy;)V
+HSPLandroidx/compose/runtime/SnapshotMutableStateImpl;->getFirstStateRecord()Landroidx/compose/runtime/snapshots/StateRecord;
+HSPLandroidx/compose/runtime/SnapshotMutableStateImpl;->getValue()Ljava/lang/Object;
+HSPLandroidx/compose/runtime/SnapshotMutableStateImpl;->prependStateRecord(Landroidx/compose/runtime/snapshots/StateRecord;)V
+HSPLandroidx/compose/runtime/SnapshotMutableStateImpl;->setValue(Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/SnapshotStateKt__DerivedStateKt;-><clinit>()V
+HSPLandroidx/compose/runtime/SnapshotStateKt__ProduceStateKt$produceState$3;-><init>(Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/MutableState;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/runtime/SnapshotStateKt__ProduceStateKt$produceState$3;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/runtime/SnapshotStateKt__ProduceStateKt$produceState$3;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/SnapshotStateKt__SnapshotFlowKt$collectAsState$1$1;-><init>(Landroidx/compose/runtime/ProduceStateScopeImpl;I)V
+HSPLandroidx/compose/runtime/SnapshotStateKt__SnapshotFlowKt$collectAsState$1$1;->emit(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/SnapshotStateKt__SnapshotFlowKt$collectAsState$1;-><init>(Lkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/runtime/SnapshotStateKt__SnapshotFlowKt$collectAsState$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/runtime/SnapshotStateKt__SnapshotFlowKt$collectAsState$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/SnapshotStateKt__SnapshotFlowKt$collectAsState$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/Stack;-><init>()V
+HSPLandroidx/compose/runtime/Stack;-><init>(I)V
+HSPLandroidx/compose/runtime/Stack;-><init>(II)V
+HSPLandroidx/compose/runtime/Stack;-><init>(ILjava/lang/Object;)V
+HSPLandroidx/compose/runtime/Stack;-><init>(Landroid/content/Context;)V
+HSPLandroidx/compose/runtime/Stack;-><init>(Ljava/nio/ByteBuffer;)V
+HSPLandroidx/compose/runtime/Stack;->clear()V
+HSPLandroidx/compose/runtime/Stack;->load(Lokhttp3/MediaType;)V
+HSPLandroidx/compose/runtime/Stack;->pop()Ljava/lang/Object;
+HSPLandroidx/compose/runtime/Stack;->readUnsignedInt()J
+HSPLandroidx/compose/runtime/Stack;->skip(I)V
+HSPLandroidx/compose/runtime/StaticProvidableCompositionLocal;->updatedStateOf$runtime_release(Ljava/lang/Object;Landroidx/compose/runtime/State;)Landroidx/compose/runtime/State;
+HSPLandroidx/compose/runtime/StaticValueHolder;-><init>(Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/StaticValueHolder;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/runtime/StaticValueHolder;->getValue()Ljava/lang/Object;
+HSPLandroidx/compose/runtime/StructuralEqualityPolicy;-><clinit>()V
+HSPLandroidx/compose/runtime/StructuralEqualityPolicy;->equivalent(Ljava/lang/Object;Ljava/lang/Object;)Z
+HSPLandroidx/compose/runtime/changelist/ChangeList;-><init>()V
+HSPLandroidx/compose/runtime/changelist/ChangeList;->executeAndFlushAllPendingChanges(Landroidx/compose/runtime/Applier;Landroidx/compose/runtime/SlotWriter;Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;)V
+HSPLandroidx/compose/runtime/changelist/ChangeList;->isEmpty()Z
+HSPLandroidx/compose/runtime/changelist/ComposerChangeListWriter;-><init>(Landroidx/compose/runtime/ComposerImpl;Landroidx/compose/runtime/changelist/ChangeList;)V
+HSPLandroidx/compose/runtime/changelist/ComposerChangeListWriter;->moveUp()V
+HSPLandroidx/compose/runtime/changelist/ComposerChangeListWriter;->pushPendingUpsAndDowns()V
+HSPLandroidx/compose/runtime/changelist/ComposerChangeListWriter;->realizeNodeMovementOperations()V
+HSPLandroidx/compose/runtime/changelist/ComposerChangeListWriter;->realizeOperationLocation(Z)V
+HSPLandroidx/compose/runtime/changelist/FixupList;-><init>()V
+HSPLandroidx/compose/runtime/changelist/Operation$AdvanceSlotsBy;-><clinit>()V
+HSPLandroidx/compose/runtime/changelist/Operation$AdvanceSlotsBy;-><init>()V
+HSPLandroidx/compose/runtime/changelist/Operation$AdvanceSlotsBy;->execute(Landroidx/compose/runtime/changelist/Operations$OpIterator;Landroidx/compose/runtime/Applier;Landroidx/compose/runtime/SlotWriter;Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;)V
+HSPLandroidx/compose/runtime/changelist/Operation$DeactivateCurrentGroup;-><clinit>()V
+HSPLandroidx/compose/runtime/changelist/Operation$DeactivateCurrentGroup;-><init>()V
+HSPLandroidx/compose/runtime/changelist/Operation$DeactivateCurrentGroup;->execute(Landroidx/compose/runtime/changelist/Operations$OpIterator;Landroidx/compose/runtime/Applier;Landroidx/compose/runtime/SlotWriter;Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;)V
+HSPLandroidx/compose/runtime/changelist/Operation$Downs;-><clinit>()V
+HSPLandroidx/compose/runtime/changelist/Operation$Downs;-><init>()V
+HSPLandroidx/compose/runtime/changelist/Operation$Downs;->execute(Landroidx/compose/runtime/changelist/Operations$OpIterator;Landroidx/compose/runtime/Applier;Landroidx/compose/runtime/SlotWriter;Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;)V
+HSPLandroidx/compose/runtime/changelist/Operation$InsertNodeFixup;-><clinit>()V
+HSPLandroidx/compose/runtime/changelist/Operation$InsertNodeFixup;-><init>()V
+HSPLandroidx/compose/runtime/changelist/Operation$InsertNodeFixup;->execute(Landroidx/compose/runtime/changelist/Operations$OpIterator;Landroidx/compose/runtime/Applier;Landroidx/compose/runtime/SlotWriter;Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;)V
+HSPLandroidx/compose/runtime/changelist/Operation$InsertSlotsWithFixups;-><clinit>()V
+HSPLandroidx/compose/runtime/changelist/Operation$InsertSlotsWithFixups;-><init>()V
+HSPLandroidx/compose/runtime/changelist/Operation$InsertSlotsWithFixups;->execute(Landroidx/compose/runtime/changelist/Operations$OpIterator;Landroidx/compose/runtime/Applier;Landroidx/compose/runtime/SlotWriter;Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;)V
+HSPLandroidx/compose/runtime/changelist/Operation$PostInsertNodeFixup;-><clinit>()V
+HSPLandroidx/compose/runtime/changelist/Operation$PostInsertNodeFixup;-><init>()V
+HSPLandroidx/compose/runtime/changelist/Operation$PostInsertNodeFixup;->execute(Landroidx/compose/runtime/changelist/Operations$OpIterator;Landroidx/compose/runtime/Applier;Landroidx/compose/runtime/SlotWriter;Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;)V
+HSPLandroidx/compose/runtime/changelist/Operation$Remember;-><clinit>()V
+HSPLandroidx/compose/runtime/changelist/Operation$Remember;-><init>()V
+HSPLandroidx/compose/runtime/changelist/Operation$Remember;->execute(Landroidx/compose/runtime/changelist/Operations$OpIterator;Landroidx/compose/runtime/Applier;Landroidx/compose/runtime/SlotWriter;Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;)V
+HSPLandroidx/compose/runtime/changelist/Operation$SideEffect;-><clinit>()V
+HSPLandroidx/compose/runtime/changelist/Operation$SideEffect;-><init>()V
+HSPLandroidx/compose/runtime/changelist/Operation$SideEffect;->execute(Landroidx/compose/runtime/changelist/Operations$OpIterator;Landroidx/compose/runtime/Applier;Landroidx/compose/runtime/SlotWriter;Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;)V
+HSPLandroidx/compose/runtime/changelist/Operation$UpdateAuxData;-><clinit>()V
+HSPLandroidx/compose/runtime/changelist/Operation$UpdateAuxData;-><init>()V
+HSPLandroidx/compose/runtime/changelist/Operation$UpdateAuxData;->execute(Landroidx/compose/runtime/changelist/Operations$OpIterator;Landroidx/compose/runtime/Applier;Landroidx/compose/runtime/SlotWriter;Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;)V
+HSPLandroidx/compose/runtime/changelist/Operation$UpdateNode;-><clinit>()V
+HSPLandroidx/compose/runtime/changelist/Operation$UpdateNode;-><init>()V
+HSPLandroidx/compose/runtime/changelist/Operation$UpdateNode;->execute(Landroidx/compose/runtime/changelist/Operations$OpIterator;Landroidx/compose/runtime/Applier;Landroidx/compose/runtime/SlotWriter;Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;)V
+HSPLandroidx/compose/runtime/changelist/Operation$UpdateValue;-><clinit>()V
+HSPLandroidx/compose/runtime/changelist/Operation$UpdateValue;-><init>()V
+HSPLandroidx/compose/runtime/changelist/Operation$UpdateValue;->execute(Landroidx/compose/runtime/changelist/Operations$OpIterator;Landroidx/compose/runtime/Applier;Landroidx/compose/runtime/SlotWriter;Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;)V
+HSPLandroidx/compose/runtime/changelist/Operation$Ups;-><clinit>()V
+HSPLandroidx/compose/runtime/changelist/Operation$Ups;-><init>()V
+HSPLandroidx/compose/runtime/changelist/Operation$Ups;->execute(Landroidx/compose/runtime/changelist/Operations$OpIterator;Landroidx/compose/runtime/Applier;Landroidx/compose/runtime/SlotWriter;Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;)V
+HSPLandroidx/compose/runtime/changelist/Operation$UseCurrentNode;-><clinit>()V
+HSPLandroidx/compose/runtime/changelist/Operation$UseCurrentNode;-><init>()V
+HSPLandroidx/compose/runtime/changelist/Operation$UseCurrentNode;->execute(Landroidx/compose/runtime/changelist/Operations$OpIterator;Landroidx/compose/runtime/Applier;Landroidx/compose/runtime/SlotWriter;Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;)V
+HSPLandroidx/compose/runtime/changelist/Operation;-><init>(II)V
+HSPLandroidx/compose/runtime/changelist/Operation;-><init>(III)V
+HSPLandroidx/compose/runtime/changelist/Operations$OpIterator;-><init>(Landroidx/compose/runtime/changelist/Operations;)V
+HSPLandroidx/compose/runtime/changelist/Operations$OpIterator;->getInt-w8GmfQM(I)I
+HSPLandroidx/compose/runtime/changelist/Operations$OpIterator;->getObject-31yXWZQ(I)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/changelist/Operations;-><init>()V
+HSPLandroidx/compose/runtime/changelist/Operations;->access$createExpectedArgMask(Landroidx/compose/runtime/changelist/Operations;I)I
+HSPLandroidx/compose/runtime/changelist/Operations;->clear()V
+HSPLandroidx/compose/runtime/changelist/Operations;->executeAndFlushAllPendingOperations(Landroidx/compose/runtime/Applier;Landroidx/compose/runtime/SlotWriter;Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;)V
+HSPLandroidx/compose/runtime/changelist/Operations;->peekOperation()Landroidx/compose/runtime/changelist/Operation;
+HSPLandroidx/compose/runtime/changelist/Operations;->push(Landroidx/compose/runtime/changelist/Operation;)V
+HSPLandroidx/compose/runtime/changelist/Operations;->pushOp(Landroidx/compose/runtime/changelist/Operation;)V
+HSPLandroidx/compose/runtime/collection/IdentityArrayIntMap;-><init>()V
+HSPLandroidx/compose/runtime/collection/IdentityArrayIntMap;->add(ILjava/lang/Object;)I
+HSPLandroidx/compose/runtime/collection/IdentityArrayMap$asMap$1$entries$1$iterator$1$1;-><init>(Ljava/lang/Object;Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/collection/IdentityArrayMap$asMap$1$entries$1$iterator$1$1;->getKey()Ljava/lang/Object;
+HSPLandroidx/compose/runtime/collection/IdentityArrayMap$asMap$1$entries$1$iterator$1$1;->getValue()Ljava/lang/Object;
+HSPLandroidx/compose/runtime/collection/IdentityArraySet;-><init>()V
+HSPLandroidx/compose/runtime/collection/IdentityArraySet;->add(Ljava/lang/Object;)Z
+HSPLandroidx/compose/runtime/collection/IdentityArraySet;->addAll(Ljava/util/Collection;)V
+HSPLandroidx/compose/runtime/collection/IdentityArraySet;->clear()V
+HSPLandroidx/compose/runtime/collection/IdentityArraySet;->contains(Ljava/lang/Object;)Z
+HSPLandroidx/compose/runtime/collection/IdentityArraySet;->find(Ljava/lang/Object;)I
+HSPLandroidx/compose/runtime/collection/IdentityArraySet;->isEmpty()Z
+HSPLandroidx/compose/runtime/collection/IdentityArraySet;->isNotEmpty()Z
+HSPLandroidx/compose/runtime/collection/IdentityArraySet;->iterator()Ljava/util/Iterator;
+HSPLandroidx/compose/runtime/collection/IdentityArraySet;->remove(Ljava/lang/Object;)Z
+HSPLandroidx/compose/runtime/collection/MutableVector$MutableVectorList;-><init>(Landroidx/compose/runtime/collection/MutableVector;)V
+HSPLandroidx/compose/runtime/collection/MutableVector$MutableVectorList;->get(I)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/collection/MutableVector$MutableVectorList;->indexOf(Ljava/lang/Object;)I
+HSPLandroidx/compose/runtime/collection/MutableVector$MutableVectorList;->isEmpty()Z
+HSPLandroidx/compose/runtime/collection/MutableVector$MutableVectorList;->iterator()Ljava/util/Iterator;
+HSPLandroidx/compose/runtime/collection/MutableVector$MutableVectorList;->size()I
+HSPLandroidx/compose/runtime/collection/MutableVector$VectorListIterator;-><init>(ILjava/util/List;)V
+HSPLandroidx/compose/runtime/collection/MutableVector$VectorListIterator;->hasNext()Z
+HSPLandroidx/compose/runtime/collection/MutableVector$VectorListIterator;->next()Ljava/lang/Object;
+HSPLandroidx/compose/runtime/collection/MutableVector;-><init>([Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/collection/MutableVector;->add(ILjava/lang/Object;)V
+HSPLandroidx/compose/runtime/collection/MutableVector;->add(Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/collection/MutableVector;->addAll(ILandroidx/compose/runtime/collection/MutableVector;)V
+HSPLandroidx/compose/runtime/collection/MutableVector;->asMutableList()Ljava/util/List;
+HSPLandroidx/compose/runtime/collection/MutableVector;->clear()V
+HSPLandroidx/compose/runtime/collection/MutableVector;->contains(Ljava/lang/Object;)Z
+HSPLandroidx/compose/runtime/collection/MutableVector;->ensureCapacity(I)V
+HSPLandroidx/compose/runtime/collection/MutableVector;->indexOf(Ljava/lang/Object;)I
+HSPLandroidx/compose/runtime/collection/MutableVector;->isEmpty()Z
+HSPLandroidx/compose/runtime/collection/MutableVector;->isNotEmpty()Z
+HSPLandroidx/compose/runtime/collection/MutableVector;->remove(Ljava/lang/Object;)Z
+HSPLandroidx/compose/runtime/collection/MutableVector;->removeAt(I)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/collection/MutableVector;->removeRange(II)V
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableList/SmallPersistentVector;-><clinit>()V
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableList/SmallPersistentVector;-><init>([Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableList/SmallPersistentVector;->add(Ljava/lang/Object;)Landroidx/compose/runtime/external/kotlinx/collections/immutable/PersistentList;
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableList/SmallPersistentVector;->get(I)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableList/SmallPersistentVector;->getSize()I
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableList/SmallPersistentVector;->indexOf(Ljava/lang/Object;)I
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableList/SmallPersistentVector;->removeAt(I)Landroidx/compose/runtime/external/kotlinx/collections/immutable/PersistentList;
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMap;-><clinit>()V
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMap;-><init>(Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;I)V
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMap;->containsKey(Ljava/lang/Object;)Z
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMap;->get(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMap;->put(Ljava/lang/Object;Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/persistentOrderedSet/Links;)Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMap;
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMapBaseIterator;-><init>(Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;[Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNodeBaseIterator;)V
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMapBaseIterator;->ensureNextEntryIsReady()V
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMapBaseIterator;->hasNext()Z
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMapBaseIterator;->moveToNextNodeWithData(I)I
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMapBaseIterator;->next()Ljava/lang/Object;
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMapBuilder;-><init>(Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMap;)V
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMapBuilder;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMapBuilder;->putAll(Ljava/util/Map;)V
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMapBuilder;->setSize(I)V
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMapKeys;-><init>(Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMap;I)V
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMapKeys;->getSize()I
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMapKeys;->iterator()Ljava/util/Iterator;
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMapKeysIterator;-><init>(Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;I)V
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;-><clinit>()V
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;-><init>(II[Ljava/lang/Object;L_COROUTINE/ArtificialStackFrames;)V
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;->bufferMoveEntryToNode(IIILjava/lang/Object;Ljava/lang/Object;IL_COROUTINE/ArtificialStackFrames;)[Ljava/lang/Object;
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;->containsKey(IILjava/lang/Object;)Z
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;->elementsIdentityEquals(Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;)Z
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;->entryKeyIndex$runtime_release(I)I
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;->get(IILjava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;->hasEntryAt$runtime_release(I)Z
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;->hasNodeAt(I)Z
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;->makeNode(ILjava/lang/Object;Ljava/lang/Object;ILjava/lang/Object;Ljava/lang/Object;IL_COROUTINE/ArtificialStackFrames;)Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;->mutablePut(ILjava/lang/Object;Ljava/lang/Object;ILandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMapBuilder;)Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;->mutablePutAll(Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;ILandroidx/compose/runtime/external/kotlinx/collections/immutable/internal/DeltaCounter;Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMapBuilder;)Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;->nodeAtIndex$runtime_release(I)Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;->nodeIndex$runtime_release(I)I
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;->put(IILjava/lang/Object;Ljava/lang/Object;)Landroidx/compose/ui/input/pointer/util/PointerIdArray;
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;->valueAtKeyIndex(I)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNodeBaseIterator;-><init>()V
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNodeBaseIterator;->reset(II[Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNodeKeysIterator;-><init>(I)V
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNodeKeysIterator;->next()Ljava/lang/Object;
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/persistentOrderedSet/Links;-><init>(Ljava/lang/Object;Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/persistentOrderedSet/PersistentOrderedSet;-><clinit>()V
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/persistentOrderedSet/PersistentOrderedSet;-><init>(Ljava/lang/Object;Ljava/lang/Object;Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMap;)V
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/persistentOrderedSet/PersistentOrderedSet;->getSize()I
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/internal/DeltaCounter;-><init>()V
+HSPLandroidx/compose/runtime/internal/ComposableLambdaImpl;-><init>(IZ)V
+HSPLandroidx/compose/runtime/internal/ComposableLambdaImpl;->invoke(Ljava/lang/Object;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/internal/ComposableLambdaImpl;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/internal/ComposableLambdaImpl;->invoke(Ljava/lang/Object;Ljava/lang/Object;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/internal/ComposableLambdaImpl;->invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/internal/ComposableLambdaImpl;->invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/internal/ComposableLambdaImpl;->update(Lkotlin/jvm/internal/Lambda;)V
+HSPLandroidx/compose/runtime/internal/PersistentCompositionLocalHashMap$Builder;-><init>(Landroidx/compose/runtime/internal/PersistentCompositionLocalHashMap;)V
+HSPLandroidx/compose/runtime/internal/PersistentCompositionLocalHashMap$Builder;->build()Landroidx/compose/runtime/internal/PersistentCompositionLocalHashMap;
+HSPLandroidx/compose/runtime/internal/PersistentCompositionLocalHashMap;-><clinit>()V
+HSPLandroidx/compose/runtime/internal/PersistentCompositionLocalHashMap;->containsKey(Ljava/lang/Object;)Z
+HSPLandroidx/compose/runtime/internal/PersistentCompositionLocalHashMap;->get(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/internal/PersistentCompositionLocalHashMap;->putValue(Landroidx/compose/runtime/CompositionLocal;Landroidx/compose/runtime/State;)Landroidx/compose/runtime/internal/PersistentCompositionLocalHashMap;
+HSPLandroidx/compose/runtime/internal/ThreadMap;-><init>(I[J[Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/internal/ThreadMap;->find(J)I
+HSPLandroidx/compose/runtime/internal/ThreadMap;->newWith(JLjava/lang/Object;)Landroidx/compose/runtime/internal/ThreadMap;
+HSPLandroidx/compose/runtime/saveable/ListSaverKt$listSaver$1;-><init>(Lkotlin/coroutines/CoroutineContext$plus$1;)V
+HSPLandroidx/compose/runtime/saveable/ListSaverKt$listSaver$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/saveable/RememberSaveableKt$rememberSaveable$1;-><init>(Landroidx/compose/runtime/saveable/SaveableHolder;Landroidx/compose/runtime/saveable/SaverKt$Saver$1;Landroidx/compose/runtime/saveable/SaveableStateRegistry;Ljava/lang/String;Ljava/lang/Object;[Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/saveable/RememberSaveableKt$rememberSaveable$1;->invoke()Ljava/lang/Object;
+HSPLandroidx/compose/runtime/saveable/SaveableHolder;-><init>(Landroidx/compose/runtime/saveable/SaverKt$Saver$1;Landroidx/compose/runtime/saveable/SaveableStateRegistry;Ljava/lang/String;Ljava/lang/Object;[Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/saveable/SaveableHolder;->invoke()Ljava/lang/Object;
+HSPLandroidx/compose/runtime/saveable/SaveableHolder;->onRemembered()V
+HSPLandroidx/compose/runtime/saveable/SaveableHolder;->register()V
+HSPLandroidx/compose/runtime/saveable/SaveableStateHolderImpl$RegistryHolder$registry$1;-><init>(Landroidx/compose/runtime/saveable/SaveableStateHolderImpl;)V
+HSPLandroidx/compose/runtime/saveable/SaveableStateHolderImpl$RegistryHolder;-><init>(Landroidx/compose/runtime/saveable/SaveableStateHolderImpl;Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/saveable/SaveableStateHolderImpl$SaveableStateProvider$1$1$invoke$$inlined$onDispose$1;-><init>(Landroidx/compose/runtime/saveable/SaveableStateHolderImpl$RegistryHolder;Landroidx/compose/runtime/saveable/SaveableStateHolderImpl;Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/saveable/SaveableStateHolderImpl$SaveableStateProvider$1$1$invoke$$inlined$onDispose$1;->dispose()V
+HSPLandroidx/compose/runtime/saveable/SaveableStateHolderImpl;-><clinit>()V
+HSPLandroidx/compose/runtime/saveable/SaveableStateHolderImpl;-><init>(Ljava/util/Map;)V
+HSPLandroidx/compose/runtime/saveable/SaveableStateHolderImpl;->SaveableStateProvider(Ljava/lang/Object;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;I)V
+HSPLandroidx/compose/runtime/saveable/SaveableStateRegistryImpl$registerProvider$3;-><init>(Landroidx/compose/runtime/saveable/SaveableStateRegistryImpl;Ljava/lang/String;Lkotlin/jvm/functions/Function0;)V
+HSPLandroidx/compose/runtime/saveable/SaveableStateRegistryImpl;-><init>(Ljava/util/Map;Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/runtime/saveable/SaveableStateRegistryImpl;->canBeSaved(Ljava/lang/Object;)Z
+HSPLandroidx/compose/runtime/saveable/SaveableStateRegistryImpl;->consumeRestored(Ljava/lang/String;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/saveable/SaveableStateRegistryImpl;->performSave()Ljava/util/Map;
+HSPLandroidx/compose/runtime/saveable/SaveableStateRegistryImpl;->registerProvider(Ljava/lang/String;Lkotlin/jvm/functions/Function0;)Landroidx/compose/runtime/saveable/SaveableStateRegistryImpl$registerProvider$3;
+HSPLandroidx/compose/runtime/saveable/SaveableStateRegistryKt;-><clinit>()V
+HSPLandroidx/compose/runtime/saveable/SaverKt$Saver$1;-><init>(Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/runtime/saveable/SaverKt;-><clinit>()V
+HSPLandroidx/compose/runtime/snapshots/GlobalSnapshot$1$1$1;-><init>(ILjava/util/List;)V
+HSPLandroidx/compose/runtime/snapshots/GlobalSnapshot$1$1$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/snapshots/GlobalSnapshot;-><init>(ILandroidx/compose/runtime/snapshots/SnapshotIdSet;)V
+HSPLandroidx/compose/runtime/snapshots/GlobalSnapshot;->dispose()V
+HSPLandroidx/compose/runtime/snapshots/GlobalSnapshot;->notifyObjectsInitialized$runtime_release()V
+HSPLandroidx/compose/runtime/snapshots/GlobalSnapshot;->takeNestedMutableSnapshot(Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Landroidx/compose/runtime/snapshots/MutableSnapshot;
+HSPLandroidx/compose/runtime/snapshots/MutableSnapshot;-><clinit>()V
+HSPLandroidx/compose/runtime/snapshots/MutableSnapshot;-><init>(ILandroidx/compose/runtime/snapshots/SnapshotIdSet;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/runtime/snapshots/MutableSnapshot;->advance$runtime_release()V
+HSPLandroidx/compose/runtime/snapshots/MutableSnapshot;->apply()Lokhttp3/MediaType;
+HSPLandroidx/compose/runtime/snapshots/MutableSnapshot;->closeLocked$runtime_release()V
+HSPLandroidx/compose/runtime/snapshots/MutableSnapshot;->dispose()V
+HSPLandroidx/compose/runtime/snapshots/MutableSnapshot;->getModified$runtime_release()Landroidx/compose/runtime/collection/IdentityArraySet;
+HSPLandroidx/compose/runtime/snapshots/MutableSnapshot;->getReadObserver$runtime_release()Lkotlin/jvm/functions/Function1;
+HSPLandroidx/compose/runtime/snapshots/MutableSnapshot;->getReadOnly()Z
+HSPLandroidx/compose/runtime/snapshots/MutableSnapshot;->getWriteCount$runtime_release()I
+HSPLandroidx/compose/runtime/snapshots/MutableSnapshot;->getWriteObserver$runtime_release()Lkotlin/jvm/functions/Function1;
+HSPLandroidx/compose/runtime/snapshots/MutableSnapshot;->innerApplyLocked$runtime_release(ILjava/util/HashMap;Landroidx/compose/runtime/snapshots/SnapshotIdSet;)Lokhttp3/MediaType;
+HSPLandroidx/compose/runtime/snapshots/MutableSnapshot;->nestedDeactivated$runtime_release()V
+HSPLandroidx/compose/runtime/snapshots/MutableSnapshot;->notifyObjectsInitialized$runtime_release()V
+HSPLandroidx/compose/runtime/snapshots/MutableSnapshot;->recordModified$runtime_release(Landroidx/compose/runtime/snapshots/StateObject;)V
+HSPLandroidx/compose/runtime/snapshots/MutableSnapshot;->recordPrevious$runtime_release(I)V
+HSPLandroidx/compose/runtime/snapshots/MutableSnapshot;->releasePinnedSnapshotsForCloseLocked$runtime_release()V
+HSPLandroidx/compose/runtime/snapshots/MutableSnapshot;->setModified(Landroidx/compose/runtime/collection/IdentityArraySet;)V
+HSPLandroidx/compose/runtime/snapshots/MutableSnapshot;->setWriteCount$runtime_release(I)V
+HSPLandroidx/compose/runtime/snapshots/Snapshot$Companion$$ExternalSyntheticLambda0;-><init>(Lkotlin/jvm/internal/Lambda;I)V
+HSPLandroidx/compose/runtime/snapshots/Snapshot;-><init>(ILandroidx/compose/runtime/snapshots/SnapshotIdSet;)V
+HSPLandroidx/compose/runtime/snapshots/Snapshot;->getId()I
+HSPLandroidx/compose/runtime/snapshots/Snapshot;->getInvalid$runtime_release()Landroidx/compose/runtime/snapshots/SnapshotIdSet;
+HSPLandroidx/compose/runtime/snapshots/Snapshot;->makeCurrent()Landroidx/compose/runtime/snapshots/Snapshot;
+HSPLandroidx/compose/runtime/snapshots/Snapshot;->restoreCurrent(Landroidx/compose/runtime/snapshots/Snapshot;)V
+HSPLandroidx/compose/runtime/snapshots/Snapshot;->setId$runtime_release(I)V
+HSPLandroidx/compose/runtime/snapshots/Snapshot;->setInvalid$runtime_release(Landroidx/compose/runtime/snapshots/SnapshotIdSet;)V
+HSPLandroidx/compose/runtime/snapshots/SnapshotApplyResult$Success;-><clinit>()V
+HSPLandroidx/compose/runtime/snapshots/SnapshotDoubleIndexHeap;-><init>()V
+HSPLandroidx/compose/runtime/snapshots/SnapshotDoubleIndexHeap;->add(I)I
+HSPLandroidx/compose/runtime/snapshots/SnapshotDoubleIndexHeap;->swap(II)V
+HSPLandroidx/compose/runtime/snapshots/SnapshotIdSet;-><clinit>()V
+HSPLandroidx/compose/runtime/snapshots/SnapshotIdSet;-><init>(JJI[I)V
+HSPLandroidx/compose/runtime/snapshots/SnapshotIdSet;->andNot(Landroidx/compose/runtime/snapshots/SnapshotIdSet;)Landroidx/compose/runtime/snapshots/SnapshotIdSet;
+HSPLandroidx/compose/runtime/snapshots/SnapshotIdSet;->clear(I)Landroidx/compose/runtime/snapshots/SnapshotIdSet;
+HSPLandroidx/compose/runtime/snapshots/SnapshotIdSet;->get(I)Z
+HSPLandroidx/compose/runtime/snapshots/SnapshotIdSet;->or(Landroidx/compose/runtime/snapshots/SnapshotIdSet;)Landroidx/compose/runtime/snapshots/SnapshotIdSet;
+HSPLandroidx/compose/runtime/snapshots/SnapshotIdSet;->set(I)Landroidx/compose/runtime/snapshots/SnapshotIdSet;
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt$mergedReadObserver$1;-><init>(Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;I)V
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt$mergedReadObserver$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt$mergedReadObserver$1;->invoke(Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt;-><clinit>()V
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt;->access$advanceGlobalSnapshot()V
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt;->access$mergedWriteObserver(Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lkotlin/jvm/functions/Function1;
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt;->access$optimisticMerges(Landroidx/compose/runtime/snapshots/MutableSnapshot;Landroidx/compose/runtime/snapshots/MutableSnapshot;Landroidx/compose/runtime/snapshots/SnapshotIdSet;)Ljava/util/HashMap;
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt;->access$validateOpen(Landroidx/compose/runtime/snapshots/Snapshot;)V
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt;->advanceGlobalSnapshot(Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt;->checkAndOverwriteUnusedRecordsLocked()V
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt;->createTransparentSnapshotWithNoParentReadObserver(Landroidx/compose/runtime/snapshots/Snapshot;Lkotlin/jvm/functions/Function1;Z)Landroidx/compose/runtime/snapshots/Snapshot;
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt;->current(Landroidx/compose/runtime/snapshots/StateRecord;)Landroidx/compose/runtime/snapshots/StateRecord;
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt;->currentSnapshot()Landroidx/compose/runtime/snapshots/Snapshot;
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt;->mergedReadObserver(Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Z)Lkotlin/jvm/functions/Function1;
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt;->newOverwritableRecordLocked(Landroidx/compose/runtime/snapshots/StateRecord;Landroidx/compose/runtime/snapshots/StateObject;)Landroidx/compose/runtime/snapshots/StateRecord;
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt;->notifyWrite(Landroidx/compose/runtime/snapshots/Snapshot;Landroidx/compose/runtime/snapshots/StateObject;)V
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt;->overwritableRecord(Landroidx/compose/runtime/snapshots/StateRecord;Landroidx/compose/runtime/snapshots/StateObject;Landroidx/compose/runtime/snapshots/Snapshot;Landroidx/compose/runtime/snapshots/StateRecord;)Landroidx/compose/runtime/snapshots/StateRecord;
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt;->overwriteUnusedRecordsLocked(Landroidx/compose/runtime/snapshots/StateObject;)Z
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt;->processForUnusedRecordsLocked(Landroidx/compose/runtime/snapshots/StateObject;)V
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt;->readable(Landroidx/compose/runtime/snapshots/StateRecord;ILandroidx/compose/runtime/snapshots/SnapshotIdSet;)Landroidx/compose/runtime/snapshots/StateRecord;
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt;->readable(Landroidx/compose/runtime/snapshots/StateRecord;Landroidx/compose/runtime/snapshots/StateObject;)Landroidx/compose/runtime/snapshots/StateRecord;
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt;->releasePinningLocked(I)V
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt;->takeNewGlobalSnapshot(Landroidx/compose/runtime/snapshots/Snapshot;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt;->writableRecord(Landroidx/compose/runtime/snapshots/StateRecord;Landroidx/compose/runtime/snapshots/StateObject;Landroidx/compose/runtime/snapshots/Snapshot;)Landroidx/compose/runtime/snapshots/StateRecord;
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateList$StateListStateRecord;-><init>(Landroidx/compose/runtime/external/kotlinx/collections/immutable/PersistentList;)V
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateList$StateListStateRecord;->assign(Landroidx/compose/runtime/snapshots/StateRecord;)V
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateList$StateListStateRecord;->create()Landroidx/compose/runtime/snapshots/StateRecord;
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateList$addAll$1;-><init>(Landroidx/compose/ui/layout/Placeable;II)V
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateList$addAll$1;->invoke(Landroidx/compose/ui/layout/Placeable$PlacementScope;)V
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateList$addAll$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateList;-><init>()V
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateList;->add(Ljava/lang/Object;)Z
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateList;->get(I)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateList;->getFirstStateRecord()Landroidx/compose/runtime/snapshots/StateRecord;
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateList;->getReadable$runtime_release()Landroidx/compose/runtime/snapshots/SnapshotStateList$StateListStateRecord;
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateList;->isEmpty()Z
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateList;->prependStateRecord(Landroidx/compose/runtime/snapshots/StateRecord;)V
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateList;->remove(Ljava/lang/Object;)Z
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateList;->size()I
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateListKt;-><clinit>()V
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateObserver$ObservedScopeMap;-><init>(Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateObserver$ObservedScopeMap;->observe(Ljava/lang/Object;Lkotlin/collections/AbstractMap$toString$1;Lkotlin/jvm/functions/Function0;)V
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateObserver$ObservedScopeMap;->recordInvalidation(Ljava/util/Set;)Z
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateObserver$ObservedScopeMap;->recordRead(Ljava/lang/Object;ILjava/lang/Object;Landroidx/compose/runtime/collection/IdentityArrayIntMap;)V
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateObserver$ObservedScopeMap;->removeScopeIf()V
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateObserver;-><init>(Landroidx/compose/ui/platform/AndroidComposeView$focusOwner$1;)V
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateObserver;->access$drainChanges(Landroidx/compose/runtime/snapshots/SnapshotStateObserver;)Z
+HSPLandroidx/compose/runtime/snapshots/StateRecord;-><init>()V
+HSPLandroidx/compose/runtime/snapshots/TransparentObserverMutableSnapshot;-><init>(Landroidx/compose/runtime/snapshots/MutableSnapshot;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ZZ)V
+HSPLandroidx/compose/runtime/snapshots/TransparentObserverMutableSnapshot;->apply()Lokhttp3/MediaType;
+HSPLandroidx/compose/runtime/snapshots/TransparentObserverMutableSnapshot;->dispose()V
+HSPLandroidx/compose/runtime/snapshots/TransparentObserverMutableSnapshot;->getId()I
+HSPLandroidx/compose/runtime/snapshots/TransparentObserverMutableSnapshot;->getInvalid$runtime_release()Landroidx/compose/runtime/snapshots/SnapshotIdSet;
+HSPLandroidx/compose/runtime/snapshots/TransparentObserverMutableSnapshot;->getReadOnly()Z
+HSPLandroidx/compose/runtime/snapshots/TransparentObserverMutableSnapshot;->getWriteCount$runtime_release()I
+HSPLandroidx/compose/runtime/snapshots/TransparentObserverMutableSnapshot;->notifyObjectsInitialized$runtime_release()V
+HSPLandroidx/compose/runtime/snapshots/TransparentObserverMutableSnapshot;->recordModified$runtime_release(Landroidx/compose/runtime/snapshots/StateObject;)V
+HSPLandroidx/compose/runtime/snapshots/TransparentObserverMutableSnapshot;->setWriteCount$runtime_release(I)V
+HSPLandroidx/compose/runtime/snapshots/TransparentObserverMutableSnapshot;->takeNestedMutableSnapshot(Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Landroidx/compose/runtime/snapshots/MutableSnapshot;
+HSPLandroidx/compose/runtime/tooling/InspectionTablesKt;-><clinit>()V
+HSPLandroidx/compose/ui/BiasAlignment$Horizontal;-><init>(F)V
+HSPLandroidx/compose/ui/BiasAlignment$Horizontal;->align(IILandroidx/compose/ui/unit/LayoutDirection;)I
+HSPLandroidx/compose/ui/BiasAlignment$Horizontal;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/BiasAlignment$Vertical;-><init>(F)V
+HSPLandroidx/compose/ui/BiasAlignment$Vertical;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/BiasAlignment;-><init>(FF)V
+HSPLandroidx/compose/ui/BiasAlignment;->align-KFBX0sM(JJLandroidx/compose/ui/unit/LayoutDirection;)J
+HSPLandroidx/compose/ui/BiasAlignment;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/CombinedModifier$toString$1;-><clinit>()V
+HSPLandroidx/compose/ui/CombinedModifier$toString$1;-><init>(I)V
+HSPLandroidx/compose/ui/CombinedModifier$toString$1;->invoke(Landroidx/compose/ui/layout/Measurable;I)Ljava/lang/Integer;
+HSPLandroidx/compose/ui/CombinedModifier$toString$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/CombinedModifier;-><init>(Landroidx/compose/ui/Modifier;Landroidx/compose/ui/Modifier;)V
+HSPLandroidx/compose/ui/CombinedModifier;->all(Lkotlin/jvm/functions/Function1;)Z
+HSPLandroidx/compose/ui/CombinedModifier;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/CombinedModifier;->foldIn(Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/ComposedModifier;-><init>(Lkotlin/jvm/functions/Function3;)V
+HSPLandroidx/compose/ui/Modifier$Companion;-><clinit>()V
+HSPLandroidx/compose/ui/Modifier$Companion;->all(Lkotlin/jvm/functions/Function1;)Z
+HSPLandroidx/compose/ui/Modifier$Companion;->then(Landroidx/compose/ui/Modifier;)Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/ui/Modifier$Element;->all(Lkotlin/jvm/functions/Function1;)Z
+HSPLandroidx/compose/ui/Modifier$Element;->foldIn(Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/Modifier$Node;-><init>()V
+HSPLandroidx/compose/ui/Modifier$Node;->getCoroutineScope()Lkotlinx/coroutines/CoroutineScope;
+HSPLandroidx/compose/ui/Modifier$Node;->getShouldAutoInvalidate()Z
+HSPLandroidx/compose/ui/Modifier$Node;->markAsAttached$ui_release()V
+HSPLandroidx/compose/ui/Modifier$Node;->markAsDetached$ui_release()V
+HSPLandroidx/compose/ui/Modifier$Node;->onAttach()V
+HSPLandroidx/compose/ui/Modifier$Node;->onDetach()V
+HSPLandroidx/compose/ui/Modifier$Node;->onReset()V
+HSPLandroidx/compose/ui/Modifier$Node;->reset$ui_release()V
+HSPLandroidx/compose/ui/Modifier$Node;->runAttachLifecycle$ui_release()V
+HSPLandroidx/compose/ui/Modifier$Node;->runDetachLifecycle$ui_release()V
+HSPLandroidx/compose/ui/Modifier$Node;->updateCoordinator$ui_release(Landroidx/compose/ui/node/NodeCoordinator;)V
+HSPLandroidx/compose/ui/Modifier;->then(Landroidx/compose/ui/Modifier;)Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/ui/MotionDurationScale;->getKey()Lkotlin/coroutines/CoroutineContext$Key;
+HSPLandroidx/compose/ui/ZIndexElement;-><init>()V
+HSPLandroidx/compose/ui/ZIndexElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/ui/ZIndexElement;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/ZIndexNode$measure$1;-><init>(Ljava/lang/Object;ILjava/lang/Object;)V
+HSPLandroidx/compose/ui/ZIndexNode$measure$1;->invoke(Landroidx/compose/ui/layout/Placeable$PlacementScope;)V
+HSPLandroidx/compose/ui/ZIndexNode$measure$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/ZIndexNode$measure$1;->invoke(Ljava/lang/Throwable;)V
+HSPLandroidx/compose/ui/ZIndexNode;-><init>(F)V
+HSPLandroidx/compose/ui/ZIndexNode;->measure-3p2s80s(Landroidx/compose/ui/layout/MeasureScope;Landroidx/compose/ui/layout/Measurable;J)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/ui/autofill/AndroidAutofill;-><init>(Landroid/view/View;Landroidx/compose/ui/autofill/AutofillTree;)V
+HSPLandroidx/compose/ui/autofill/AutofillCallback;-><clinit>()V
+HSPLandroidx/compose/ui/autofill/AutofillCallback;->register(Landroidx/compose/ui/autofill/AndroidAutofill;)V
+HSPLandroidx/compose/ui/autofill/AutofillTree;-><init>()V
+HSPLandroidx/compose/ui/draw/CacheDrawModifierNodeImpl;-><init>(Landroidx/compose/ui/draw/CacheDrawScope;Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/draw/CacheDrawModifierNodeImpl;->draw(Landroidx/compose/ui/graphics/drawscope/ContentDrawScope;)V
+HSPLandroidx/compose/ui/draw/CacheDrawModifierNodeImpl;->getDensity()Landroidx/compose/ui/unit/Density;
+HSPLandroidx/compose/ui/draw/CacheDrawModifierNodeImpl;->getLayoutDirection()Landroidx/compose/ui/unit/LayoutDirection;
+HSPLandroidx/compose/ui/draw/CacheDrawModifierNodeImpl;->getSize-NH-jbRc()J
+HSPLandroidx/compose/ui/draw/CacheDrawModifierNodeImpl;->invalidateDrawCache()V
+HSPLandroidx/compose/ui/draw/CacheDrawModifierNodeImpl;->onMeasureResultChanged()V
+HSPLandroidx/compose/ui/draw/CacheDrawScope$onDrawBehind$1;-><init>(ILkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/draw/CacheDrawScope$onDrawBehind$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/draw/CacheDrawScope;-><init>()V
+HSPLandroidx/compose/ui/draw/CacheDrawScope;->getDensity()F
+HSPLandroidx/compose/ui/draw/CacheDrawScope;->getSize-NH-jbRc()J
+HSPLandroidx/compose/ui/draw/CacheDrawScope;->onDrawWithContent(Lkotlin/jvm/functions/Function1;)Landroidx/compose/ui/draw/DrawResult;
+HSPLandroidx/compose/ui/draw/ClipKt;->clip(Landroidx/compose/ui/Modifier;Landroidx/compose/ui/graphics/Shape;)Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/ui/draw/ClipKt;->clipToBounds(Landroidx/compose/ui/Modifier;)Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/ui/draw/ClipKt;->drawWithCache(Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function1;)Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/ui/draw/ClipKt;->paint$default(Landroidx/compose/ui/Modifier;Landroidx/compose/ui/graphics/painter/Painter;Landroidx/compose/ui/Alignment;Landroidx/compose/ui/layout/ContentScale;FLandroidx/compose/ui/graphics/BlendModeColorFilter;)Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/ui/draw/DrawResult;-><init>(Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/draw/DrawWithCacheElement;-><init>(Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/draw/DrawWithCacheElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/ui/draw/DrawWithCacheElement;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/draw/EmptyBuildDrawCacheParams;-><clinit>()V
+HSPLandroidx/compose/ui/draw/PainterElement;-><init>(Landroidx/compose/ui/graphics/painter/Painter;ZLandroidx/compose/ui/Alignment;Landroidx/compose/ui/layout/ContentScale;FLandroidx/compose/ui/graphics/BlendModeColorFilter;)V
+HSPLandroidx/compose/ui/draw/PainterElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/ui/draw/PainterElement;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/draw/PainterElement;->update(Landroidx/compose/ui/Modifier$Node;)V
+HSPLandroidx/compose/ui/draw/PainterNode$measure$1;-><init>(Landroidx/compose/ui/layout/Placeable;I)V
+HSPLandroidx/compose/ui/draw/PainterNode$measure$1;->invoke(Landroidx/compose/ui/layout/Placeable$PlacementScope;)V
+HSPLandroidx/compose/ui/draw/PainterNode$measure$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/draw/PainterNode;-><init>(Landroidx/compose/ui/graphics/painter/Painter;ZLandroidx/compose/ui/Alignment;Landroidx/compose/ui/layout/ContentScale;FLandroidx/compose/ui/graphics/BlendModeColorFilter;)V
+HSPLandroidx/compose/ui/draw/PainterNode;->draw(Landroidx/compose/ui/graphics/drawscope/ContentDrawScope;)V
+HSPLandroidx/compose/ui/draw/PainterNode;->getUseIntrinsicSize()Z
+HSPLandroidx/compose/ui/draw/PainterNode;->hasSpecifiedAndFiniteHeight-uvyYCjk(J)Z
+HSPLandroidx/compose/ui/draw/PainterNode;->hasSpecifiedAndFiniteWidth-uvyYCjk(J)Z
+HSPLandroidx/compose/ui/draw/PainterNode;->measure-3p2s80s(Landroidx/compose/ui/layout/MeasureScope;Landroidx/compose/ui/layout/Measurable;J)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/ui/draw/PainterNode;->modifyConstraints-ZezNO4M(J)J
+HSPLandroidx/compose/ui/focus/FocusChangedElement;-><init>(Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/focus/FocusChangedElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/ui/focus/FocusChangedElement;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/focus/FocusChangedElement;->update(Landroidx/compose/ui/Modifier$Node;)V
+HSPLandroidx/compose/ui/focus/FocusChangedNode;-><init>(Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/focus/FocusChangedNode;->onFocusEvent(Landroidx/compose/ui/focus/FocusStateImpl;)V
+HSPLandroidx/compose/ui/focus/FocusDirection;-><init>(I)V
+HSPLandroidx/compose/ui/focus/FocusInvalidationManager;-><init>(Landroidx/compose/ui/platform/AndroidComposeView$focusOwner$1;)V
+HSPLandroidx/compose/ui/focus/FocusInvalidationManager;->scheduleInvalidation(Ljava/util/LinkedHashSet;Ljava/lang/Object;)V
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->beamBeats-I7lrPNg(Landroidx/compose/ui/geometry/Rect;Landroidx/compose/ui/geometry/Rect;Landroidx/compose/ui/geometry/Rect;I)Z
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->beamBeats_I7lrPNg$inSourceBeam(ILandroidx/compose/ui/geometry/Rect;Landroidx/compose/ui/geometry/Rect;)Z
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->clearFocus(Landroidx/compose/ui/focus/FocusTargetNode;ZZ)Z
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->collectAccessibleChildren(Landroidx/compose/ui/node/DelegatableNode;Landroidx/compose/runtime/collection/MutableVector;)V
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->findActiveFocusNode(Landroidx/compose/ui/focus/FocusTargetNode;)Landroidx/compose/ui/focus/FocusTargetNode;
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->findBestCandidate-4WY_MpI(Landroidx/compose/runtime/collection/MutableVector;Landroidx/compose/ui/geometry/Rect;I)Landroidx/compose/ui/focus/FocusTargetNode;
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->findChildCorrespondingToFocusEnter--OM-vw8(Landroidx/compose/ui/focus/FocusTargetNode;ILkotlin/jvm/functions/Function1;)Z
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->focusRect(Landroidx/compose/ui/focus/FocusTargetNode;)Landroidx/compose/ui/geometry/Rect;
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->generateAndSearchChildren-4C6V_qg$1(Landroidx/compose/ui/focus/FocusTargetNode;Landroidx/compose/ui/focus/FocusTargetNode;ILkotlin/jvm/functions/Function1;)Z
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->getActiveChild(Landroidx/compose/ui/focus/FocusTargetNode;)Landroidx/compose/ui/focus/FocusTargetNode;
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->getFocusState(Landroidx/compose/ui/focus/FocusEventModifierNode;)Landroidx/compose/ui/focus/FocusStateImpl;
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->grantFocus(Landroidx/compose/ui/focus/FocusTargetNode;)V
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->invalidateFocusEvent(Landroidx/compose/ui/focus/FocusEventModifierNode;)V
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->isBetterCandidate_I7lrPNg$isCandidate(ILandroidx/compose/ui/geometry/Rect;Landroidx/compose/ui/geometry/Rect;)Z
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->isBetterCandidate_I7lrPNg$weightedDistance(ILandroidx/compose/ui/geometry/Rect;Landroidx/compose/ui/geometry/Rect;)J
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->isEligibleForFocusSearch(Landroidx/compose/ui/focus/FocusTargetNode;)Z
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->onFocusChanged(Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function1;)Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->performCustomClearFocus-Mxy_nc0(Landroidx/compose/ui/focus/FocusTargetNode;I)I
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->performCustomEnter-Mxy_nc0(Landroidx/compose/ui/focus/FocusTargetNode;I)I
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->performCustomRequestFocus-Mxy_nc0(Landroidx/compose/ui/focus/FocusTargetNode;I)I
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->performRequestFocus(Landroidx/compose/ui/focus/FocusTargetNode;)Z
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->refreshFocusEventNodes(Landroidx/compose/ui/focus/FocusTargetNode;)V
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->requestFocusForChild(Landroidx/compose/ui/focus/FocusTargetNode;Landroidx/compose/ui/focus/FocusTargetNode;)Z
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->requireActiveChild(Landroidx/compose/ui/focus/FocusTargetNode;)Landroidx/compose/ui/focus/FocusTargetNode;
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->requireTransactionManager(Landroidx/compose/ui/focus/FocusTargetNode;)Lcom/google/gson/internal/ConstructorConstructor;
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->searchBeyondBounds--OM-vw8(Landroidx/compose/ui/focus/FocusTargetNode;ILandroidx/compose/ui/focus/FocusOwnerImpl$moveFocus$foundNextItem$1;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->searchChildren-4C6V_qg$1(Landroidx/compose/ui/focus/FocusTargetNode;Landroidx/compose/ui/focus/FocusTargetNode;ILkotlin/jvm/functions/Function1;)Z
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->twoDimensionalFocusSearch--OM-vw8(Landroidx/compose/ui/focus/FocusTargetNode;ILandroidx/compose/ui/focus/FocusOwnerImpl$moveFocus$foundNextItem$1;)Ljava/lang/Boolean;
+HSPLandroidx/compose/ui/focus/FocusOwnerImpl$modifier$1;-><init>(Landroidx/compose/ui/focus/FocusOwnerImpl;)V
+HSPLandroidx/compose/ui/focus/FocusOwnerImpl$modifier$1;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/ui/focus/FocusOwnerImpl$moveFocus$foundNextItem$1;-><init>(Landroidx/compose/ui/focus/FocusTargetNode;Ljava/lang/Object;ILjava/lang/Object;I)V
+HSPLandroidx/compose/ui/focus/FocusOwnerImpl$moveFocus$foundNextItem$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/focus/FocusOwnerImpl;-><init>(Landroidx/compose/ui/platform/AndroidComposeView$focusOwner$1;)V
+HSPLandroidx/compose/ui/focus/FocusOwnerImpl;->moveFocus-3ESFkO8(I)Z
+HSPLandroidx/compose/ui/focus/FocusProperties$exit$1;-><clinit>()V
+HSPLandroidx/compose/ui/focus/FocusProperties$exit$1;-><init>(I)V
+HSPLandroidx/compose/ui/focus/FocusProperties$exit$1;->invoke(Landroidx/compose/ui/node/AlignmentLinesOwner;)V
+HSPLandroidx/compose/ui/focus/FocusProperties$exit$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/focus/FocusProperties$exit$1;->invoke-3ESFkO8()Landroidx/compose/ui/focus/FocusRequester;
+HSPLandroidx/compose/ui/focus/FocusPropertiesImpl;-><init>()V
+HSPLandroidx/compose/ui/focus/FocusPropertiesImpl;->setCanFocus(Z)V
+HSPLandroidx/compose/ui/focus/FocusRequester;-><clinit>()V
+HSPLandroidx/compose/ui/focus/FocusRequester;-><init>()V
+HSPLandroidx/compose/ui/focus/FocusStateImpl;-><clinit>()V
+HSPLandroidx/compose/ui/focus/FocusStateImpl;-><init>(ILjava/lang/String;)V
+HSPLandroidx/compose/ui/focus/FocusStateImpl;->isFocused()Z
+HSPLandroidx/compose/ui/focus/FocusTargetNode$FocusTargetElement;-><clinit>()V
+HSPLandroidx/compose/ui/focus/FocusTargetNode$FocusTargetElement;-><init>()V
+HSPLandroidx/compose/ui/focus/FocusTargetNode$FocusTargetElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/ui/focus/FocusTargetNode$FocusTargetElement;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/focus/FocusTargetNode;-><init>()V
+HSPLandroidx/compose/ui/focus/FocusTargetNode;->fetchFocusProperties$ui_release()Landroidx/compose/ui/focus/FocusPropertiesImpl;
+HSPLandroidx/compose/ui/focus/FocusTargetNode;->getFocusState()Landroidx/compose/ui/focus/FocusStateImpl;
+HSPLandroidx/compose/ui/focus/FocusTargetNode;->invalidateFocus$ui_release()V
+HSPLandroidx/compose/ui/focus/FocusTargetNode;->onReset()V
+HSPLandroidx/compose/ui/focus/FocusTargetNode;->scheduleInvalidationForFocusEvents$ui_release()V
+HSPLandroidx/compose/ui/focus/FocusTargetNode;->setFocusState(Landroidx/compose/ui/focus/FocusStateImpl;)V
+HSPLandroidx/compose/ui/geometry/CornerRadius;-><clinit>()V
+HSPLandroidx/compose/ui/geometry/CornerRadius;->getX-impl(J)F
+HSPLandroidx/compose/ui/geometry/CornerRadius;->getY-impl(J)F
+HSPLandroidx/compose/ui/geometry/MutableRect;-><init>()V
+HSPLandroidx/compose/ui/geometry/MutableRect;->isEmpty()Z
+HSPLandroidx/compose/ui/geometry/Offset;-><clinit>()V
+HSPLandroidx/compose/ui/geometry/Offset;->getX-impl(J)F
+HSPLandroidx/compose/ui/geometry/Offset;->getY-impl(J)F
+HSPLandroidx/compose/ui/geometry/Rect;-><clinit>()V
+HSPLandroidx/compose/ui/geometry/Rect;-><init>(FFFF)V
+HSPLandroidx/compose/ui/geometry/Rect;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/geometry/Rect;->intersect(Landroidx/compose/ui/geometry/Rect;)Landroidx/compose/ui/geometry/Rect;
+HSPLandroidx/compose/ui/geometry/Rect;->translate(FF)Landroidx/compose/ui/geometry/Rect;
+HSPLandroidx/compose/ui/geometry/Rect;->translate-k-4lQ0M(J)Landroidx/compose/ui/geometry/Rect;
+HSPLandroidx/compose/ui/geometry/RoundRect;-><clinit>()V
+HSPLandroidx/compose/ui/geometry/RoundRect;-><init>(FFFFJJJJ)V
+HSPLandroidx/compose/ui/geometry/Size;-><clinit>()V
+HSPLandroidx/compose/ui/geometry/Size;-><init>(J)V
+HSPLandroidx/compose/ui/geometry/Size;->equals-impl0(JJ)Z
+HSPLandroidx/compose/ui/geometry/Size;->getHeight-impl(J)F
+HSPLandroidx/compose/ui/geometry/Size;->getMinDimension-impl(J)F
+HSPLandroidx/compose/ui/geometry/Size;->getWidth-impl(J)F
+HSPLandroidx/compose/ui/geometry/Size;->isEmpty-impl(J)Z
+HSPLandroidx/compose/ui/graphics/AndroidCanvas;-><init>()V
+HSPLandroidx/compose/ui/graphics/AndroidCanvas;->drawImageRect-HPBpro0(Landroidx/compose/ui/graphics/ImageBitmap;JJJJLandroidx/compose/ui/graphics/AndroidPaint;)V
+HSPLandroidx/compose/ui/graphics/AndroidCanvas;->drawRect(FFFFLandroidx/compose/ui/graphics/AndroidPaint;)V
+HSPLandroidx/compose/ui/graphics/AndroidCanvas;->drawRoundRect(FFFFFFLandroidx/compose/ui/graphics/AndroidPaint;)V
+HSPLandroidx/compose/ui/graphics/AndroidCanvas;->restore()V
+HSPLandroidx/compose/ui/graphics/AndroidCanvas;->save()V
+HSPLandroidx/compose/ui/graphics/AndroidCanvas;->scale(FF)V
+HSPLandroidx/compose/ui/graphics/AndroidCanvas;->translate(FF)V
+HSPLandroidx/compose/ui/graphics/AndroidCanvas_androidKt;-><clinit>()V
+HSPLandroidx/compose/ui/graphics/AndroidImageBitmap;-><init>(Landroid/graphics/Bitmap;)V
+HSPLandroidx/compose/ui/graphics/AndroidPaint;-><init>(Landroid/graphics/Paint;)V
+HSPLandroidx/compose/ui/graphics/AndroidPaint;->setAlpha(F)V
+HSPLandroidx/compose/ui/graphics/AndroidPaint;->setBlendMode-s9anfk8(I)V
+HSPLandroidx/compose/ui/graphics/AndroidPaint;->setColor-8_81llA(J)V
+HSPLandroidx/compose/ui/graphics/AndroidPaint;->setStyle-k9PVt8s(I)V
+HSPLandroidx/compose/ui/graphics/AndroidPaint_androidKt$WhenMappings;-><clinit>()V
+HSPLandroidx/compose/ui/graphics/BlockGraphicsLayerElement;-><init>(Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/graphics/BlockGraphicsLayerElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/ui/graphics/BlockGraphicsLayerElement;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/graphics/BlockGraphicsLayerModifier;-><init>(Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/graphics/BlockGraphicsLayerModifier;->measure-3p2s80s(Landroidx/compose/ui/layout/MeasureScope;Landroidx/compose/ui/layout/Measurable;J)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/ui/graphics/Brush;-><init>()V
+HSPLandroidx/compose/ui/graphics/BrushKt;-><clinit>()V
+HSPLandroidx/compose/ui/graphics/BrushKt;->Color$default(III)J
+HSPLandroidx/compose/ui/graphics/BrushKt;->Color(FFFFLandroidx/compose/ui/graphics/colorspace/ColorSpace;)J
+HSPLandroidx/compose/ui/graphics/BrushKt;->Color(I)J
+HSPLandroidx/compose/ui/graphics/BrushKt;->Color(J)J
+HSPLandroidx/compose/ui/graphics/BrushKt;->Paint()Landroidx/compose/ui/graphics/AndroidPaint;
+HSPLandroidx/compose/ui/graphics/BrushKt;->drawOutline-wDX37Ww$default(Landroidx/compose/ui/graphics/drawscope/DrawScope;Landroidx/compose/ui/graphics/BrushKt;J)V
+HSPLandroidx/compose/ui/graphics/BrushKt;->graphicsLayer(Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function1;)Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/ui/graphics/BrushKt;->graphicsLayer-Ap8cVGQ$default(Landroidx/compose/ui/Modifier;FFLandroidx/compose/ui/graphics/Shape;ZI)Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/ui/graphics/BrushKt;->setFrom-tU-YjHk(Landroid/graphics/Matrix;[F)V
+HSPLandroidx/compose/ui/graphics/BrushKt;->toArgb-8_81llA(J)I
+HSPLandroidx/compose/ui/graphics/CanvasZHelper$$ExternalSyntheticApiModelOutline0;->m$1(Landroid/graphics/RenderNode;)I
+HSPLandroidx/compose/ui/graphics/CanvasZHelper$$ExternalSyntheticApiModelOutline0;->m$1(Landroid/graphics/RenderNode;F)V
+HSPLandroidx/compose/ui/graphics/CanvasZHelper$$ExternalSyntheticApiModelOutline0;->m$1(Landroid/graphics/RenderNode;I)V
+HSPLandroidx/compose/ui/graphics/CanvasZHelper$$ExternalSyntheticApiModelOutline0;->m$2(Landroid/graphics/RenderNode;F)V
+HSPLandroidx/compose/ui/graphics/CanvasZHelper$$ExternalSyntheticApiModelOutline0;->m$2(Landroid/graphics/RenderNode;I)V
+HSPLandroidx/compose/ui/graphics/CanvasZHelper$$ExternalSyntheticApiModelOutline0;->m$3(Landroid/graphics/RenderNode;F)V
+HSPLandroidx/compose/ui/graphics/CanvasZHelper$$ExternalSyntheticApiModelOutline0;->m$4(Landroid/graphics/RenderNode;F)V
+HSPLandroidx/compose/ui/graphics/CanvasZHelper$$ExternalSyntheticApiModelOutline0;->m$5(Landroid/graphics/RenderNode;F)V
+HSPLandroidx/compose/ui/graphics/CanvasZHelper$$ExternalSyntheticApiModelOutline0;->m$6(Landroid/graphics/RenderNode;F)V
+HSPLandroidx/compose/ui/graphics/CanvasZHelper$$ExternalSyntheticApiModelOutline0;->m$7(Landroid/graphics/RenderNode;F)V
+HSPLandroidx/compose/ui/graphics/CanvasZHelper$$ExternalSyntheticApiModelOutline0;->m$8(Landroid/graphics/RenderNode;F)V
+HSPLandroidx/compose/ui/graphics/CanvasZHelper$$ExternalSyntheticApiModelOutline0;->m(Landroid/graphics/Canvas;Landroid/graphics/RenderNode;)V
+HSPLandroidx/compose/ui/graphics/CanvasZHelper$$ExternalSyntheticApiModelOutline0;->m(Landroid/graphics/RenderNode;)I
+HSPLandroidx/compose/ui/graphics/CanvasZHelper$$ExternalSyntheticApiModelOutline0;->m(Landroid/graphics/RenderNode;)V
+HSPLandroidx/compose/ui/graphics/CanvasZHelper$$ExternalSyntheticApiModelOutline0;->m(Landroid/graphics/RenderNode;)Z
+HSPLandroidx/compose/ui/graphics/CanvasZHelper$$ExternalSyntheticApiModelOutline0;->m(Landroid/graphics/RenderNode;F)V
+HSPLandroidx/compose/ui/graphics/CanvasZHelper$$ExternalSyntheticApiModelOutline0;->m(Landroid/graphics/RenderNode;I)V
+HSPLandroidx/compose/ui/graphics/CanvasZHelper$$ExternalSyntheticApiModelOutline0;->m(Landroid/graphics/RenderNode;Z)V
+HSPLandroidx/compose/ui/graphics/CanvasZHelper$$ExternalSyntheticApiModelOutline0;->m(Landroid/view/View;)V
+HSPLandroidx/compose/ui/graphics/Color;-><clinit>()V
+HSPLandroidx/compose/ui/graphics/Color;-><init>(J)V
+HSPLandroidx/compose/ui/graphics/Color;->convert-vNxB06k(JLandroidx/compose/ui/graphics/colorspace/ColorSpace;)J
+HSPLandroidx/compose/ui/graphics/Color;->copy-wmQWz5c$default(JF)J
+HSPLandroidx/compose/ui/graphics/Color;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/graphics/Color;->equals-impl0(JJ)Z
+HSPLandroidx/compose/ui/graphics/Color;->getAlpha-impl(J)F
+HSPLandroidx/compose/ui/graphics/Color;->getBlue-impl(J)F
+HSPLandroidx/compose/ui/graphics/Color;->getColorSpace-impl(J)Landroidx/compose/ui/graphics/colorspace/ColorSpace;
+HSPLandroidx/compose/ui/graphics/Color;->getGreen-impl(J)F
+HSPLandroidx/compose/ui/graphics/Color;->getRed-impl(J)F
+HSPLandroidx/compose/ui/graphics/ColorSpaceVerificationHelper$$ExternalSyntheticLambda1;-><init>(ILjava/lang/Object;)V
+HSPLandroidx/compose/ui/graphics/Float16;-><clinit>()V
+HSPLandroidx/compose/ui/graphics/Float16;->constructor-impl(F)S
+HSPLandroidx/compose/ui/graphics/Float16;->toFloat-impl(S)F
+HSPLandroidx/compose/ui/graphics/GraphicsLayerElement;-><init>(FFFFFFFFFFJLandroidx/compose/ui/graphics/Shape;ZJJI)V
+HSPLandroidx/compose/ui/graphics/GraphicsLayerElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/ui/graphics/GraphicsLayerElement;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/graphics/GraphicsLayerElement;->update(Landroidx/compose/ui/Modifier$Node;)V
+HSPLandroidx/compose/ui/graphics/GraphicsLayerScopeKt;-><clinit>()V
+HSPLandroidx/compose/ui/graphics/Matrix;->constructor-impl$default()[F
+HSPLandroidx/compose/ui/graphics/Matrix;->map-MK-Hz9U([FJ)J
+HSPLandroidx/compose/ui/graphics/Matrix;->map-impl([FLandroidx/compose/ui/geometry/MutableRect;)V
+HSPLandroidx/compose/ui/graphics/Outline$Rectangle;-><init>(Landroidx/compose/ui/geometry/Rect;)V
+HSPLandroidx/compose/ui/graphics/Outline$Rounded;-><init>(Landroidx/compose/ui/geometry/RoundRect;)V
+HSPLandroidx/compose/ui/graphics/RectangleShapeKt$RectangleShape$1;-><init>(I)V
+HSPLandroidx/compose/ui/graphics/RectangleShapeKt$RectangleShape$1;->createOutline-Pq9zytI(JLandroidx/compose/ui/unit/LayoutDirection;Landroidx/compose/ui/unit/Density;)Landroidx/compose/ui/graphics/BrushKt;
+HSPLandroidx/compose/ui/graphics/ReusableGraphicsLayerScope;-><init>()V
+HSPLandroidx/compose/ui/graphics/Shadow;-><clinit>()V
+HSPLandroidx/compose/ui/graphics/Shadow;-><init>(JJF)V
+HSPLandroidx/compose/ui/graphics/Shadow;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/graphics/SimpleGraphicsLayerModifier$layerBlock$1;-><init>(Landroidx/compose/ui/graphics/SimpleGraphicsLayerModifier;)V
+HSPLandroidx/compose/ui/graphics/SimpleGraphicsLayerModifier$layerBlock$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/graphics/SimpleGraphicsLayerModifier;-><init>(FFFFFFFFFFJLandroidx/compose/ui/graphics/Shape;ZJJI)V
+HSPLandroidx/compose/ui/graphics/SimpleGraphicsLayerModifier;->getShouldAutoInvalidate()Z
+HSPLandroidx/compose/ui/graphics/SimpleGraphicsLayerModifier;->measure-3p2s80s(Landroidx/compose/ui/layout/MeasureScope;Landroidx/compose/ui/layout/Measurable;J)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/ui/graphics/SolidColor;-><init>(J)V
+HSPLandroidx/compose/ui/graphics/SolidColor;->applyTo-Pq9zytI(FJLandroidx/compose/ui/graphics/AndroidPaint;)V
+HSPLandroidx/compose/ui/graphics/SolidColor;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/graphics/TransformOrigin;-><clinit>()V
+HSPLandroidx/compose/ui/graphics/colorspace/Adaptation;-><clinit>()V
+HSPLandroidx/compose/ui/graphics/colorspace/Adaptation;-><init>([F)V
+HSPLandroidx/compose/ui/graphics/colorspace/ColorModel;-><clinit>()V
+HSPLandroidx/compose/ui/graphics/colorspace/ColorModel;->equals-impl0(JJ)Z
+HSPLandroidx/compose/ui/graphics/colorspace/ColorSpace;-><init>(Ljava/lang/String;JI)V
+HSPLandroidx/compose/ui/graphics/colorspace/ColorSpace;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/graphics/colorspace/ColorSpace;->isSrgb()Z
+HSPLandroidx/compose/ui/graphics/colorspace/ColorSpaces;-><clinit>()V
+HSPLandroidx/compose/ui/graphics/colorspace/Connector$Companion$identity$1;-><init>(Landroidx/compose/ui/graphics/colorspace/ColorSpace;)V
+HSPLandroidx/compose/ui/graphics/colorspace/Connector;-><clinit>()V
+HSPLandroidx/compose/ui/graphics/colorspace/Connector;-><init>(Landroidx/compose/ui/graphics/colorspace/ColorSpace;Landroidx/compose/ui/graphics/colorspace/ColorSpace;I)V
+HSPLandroidx/compose/ui/graphics/colorspace/Connector;-><init>(Landroidx/compose/ui/graphics/colorspace/ColorSpace;Landroidx/compose/ui/graphics/colorspace/ColorSpace;Landroidx/compose/ui/graphics/colorspace/ColorSpace;[F)V
+HSPLandroidx/compose/ui/graphics/colorspace/Connector;->transformToColor-wmQWz5c$ui_graphics_release(FFFF)J
+HSPLandroidx/compose/ui/graphics/colorspace/Lab;-><init>()V
+HSPLandroidx/compose/ui/graphics/colorspace/Oklab;-><clinit>()V
+HSPLandroidx/compose/ui/graphics/colorspace/Oklab;-><init>()V
+HSPLandroidx/compose/ui/graphics/colorspace/Oklab;->getMaxValue(I)F
+HSPLandroidx/compose/ui/graphics/colorspace/Oklab;->getMinValue(I)F
+HSPLandroidx/compose/ui/graphics/colorspace/Oklab;->toXy$ui_graphics_release(FFF)J
+HSPLandroidx/compose/ui/graphics/colorspace/Oklab;->toZ$ui_graphics_release(FFF)F
+HSPLandroidx/compose/ui/graphics/colorspace/Oklab;->xyzaToColor-JlNiLsg$ui_graphics_release(FFFFLandroidx/compose/ui/graphics/colorspace/ColorSpace;)J
+HSPLandroidx/compose/ui/graphics/colorspace/Rgb$$ExternalSyntheticLambda0;-><init>(Landroidx/compose/ui/graphics/colorspace/Rgb;I)V
+HSPLandroidx/compose/ui/graphics/colorspace/Rgb$$ExternalSyntheticLambda0;->invoke(D)D
+HSPLandroidx/compose/ui/graphics/colorspace/Rgb$$ExternalSyntheticLambda1;-><init>(Landroidx/compose/ui/graphics/colorspace/TransferParameters;I)V
+HSPLandroidx/compose/ui/graphics/colorspace/Rgb$$ExternalSyntheticLambda1;->invoke(D)D
+HSPLandroidx/compose/ui/graphics/colorspace/Rgb$$ExternalSyntheticLambda2;-><init>(DI)V
+HSPLandroidx/compose/ui/graphics/colorspace/Rgb$eotf$1;-><init>(Landroidx/compose/ui/graphics/colorspace/Rgb;I)V
+HSPLandroidx/compose/ui/graphics/colorspace/Rgb;-><clinit>()V
+HSPLandroidx/compose/ui/graphics/colorspace/Rgb;-><init>(Ljava/lang/String;[FLandroidx/compose/ui/graphics/colorspace/WhitePoint;DFFI)V
+HSPLandroidx/compose/ui/graphics/colorspace/Rgb;-><init>(Ljava/lang/String;[FLandroidx/compose/ui/graphics/colorspace/WhitePoint;Landroidx/compose/ui/graphics/colorspace/TransferParameters;I)V
+HSPLandroidx/compose/ui/graphics/colorspace/Rgb;-><init>(Ljava/lang/String;[FLandroidx/compose/ui/graphics/colorspace/WhitePoint;[FLandroidx/compose/ui/graphics/colorspace/DoubleFunction;Landroidx/compose/ui/graphics/colorspace/DoubleFunction;FFLandroidx/compose/ui/graphics/colorspace/TransferParameters;I)V
+HSPLandroidx/compose/ui/graphics/colorspace/Rgb;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/graphics/colorspace/Rgb;->getMaxValue(I)F
+HSPLandroidx/compose/ui/graphics/colorspace/Rgb;->getMinValue(I)F
+HSPLandroidx/compose/ui/graphics/colorspace/Rgb;->isSrgb()Z
+HSPLandroidx/compose/ui/graphics/colorspace/Rgb;->toXy$ui_graphics_release(FFF)J
+HSPLandroidx/compose/ui/graphics/colorspace/Rgb;->toZ$ui_graphics_release(FFF)F
+HSPLandroidx/compose/ui/graphics/colorspace/Rgb;->xyzaToColor-JlNiLsg$ui_graphics_release(FFFFLandroidx/compose/ui/graphics/colorspace/ColorSpace;)J
+HSPLandroidx/compose/ui/graphics/colorspace/TransferParameters;-><init>(DDDDD)V
+HSPLandroidx/compose/ui/graphics/colorspace/TransferParameters;-><init>(DDDDDDD)V
+HSPLandroidx/compose/ui/graphics/colorspace/WhitePoint;-><init>(FF)V
+HSPLandroidx/compose/ui/graphics/colorspace/WhitePoint;->toXyz$ui_graphics_release()[F
+HSPLandroidx/compose/ui/graphics/colorspace/Xyz;-><init>()V
+HSPLandroidx/compose/ui/graphics/drawscope/CanvasDrawScope$DrawParams;-><init>()V
+HSPLandroidx/compose/ui/graphics/drawscope/CanvasDrawScope$drawContext$1;-><init>(Landroidx/compose/ui/graphics/drawscope/CanvasDrawScope;)V
+HSPLandroidx/compose/ui/graphics/drawscope/CanvasDrawScope$drawContext$1;->getCanvas()Landroidx/compose/ui/graphics/Canvas;
+HSPLandroidx/compose/ui/graphics/drawscope/CanvasDrawScope$drawContext$1;->getSize-NH-jbRc()J
+HSPLandroidx/compose/ui/graphics/drawscope/CanvasDrawScope$drawContext$1;->setSize-uvyYCjk(J)V
+HSPLandroidx/compose/ui/graphics/drawscope/CanvasDrawScope;-><init>()V
+HSPLandroidx/compose/ui/graphics/drawscope/CanvasDrawScope;->configurePaint-2qPWKa0$default(Landroidx/compose/ui/graphics/drawscope/CanvasDrawScope;JLkotlin/ResultKt;FLandroidx/compose/ui/graphics/BlendModeColorFilter;I)Landroidx/compose/ui/graphics/AndroidPaint;
+HSPLandroidx/compose/ui/graphics/drawscope/CanvasDrawScope;->configurePaint-swdJneE$default(Landroidx/compose/ui/graphics/drawscope/CanvasDrawScope;Landroidx/compose/ui/graphics/Brush;Lkotlin/ResultKt;FLandroidx/compose/ui/graphics/BlendModeColorFilter;I)Landroidx/compose/ui/graphics/AndroidPaint;
+HSPLandroidx/compose/ui/graphics/drawscope/CanvasDrawScope;->configurePaint-swdJneE(Landroidx/compose/ui/graphics/Brush;Lkotlin/ResultKt;FLandroidx/compose/ui/graphics/BlendModeColorFilter;II)Landroidx/compose/ui/graphics/AndroidPaint;
+HSPLandroidx/compose/ui/graphics/drawscope/CanvasDrawScope;->drawImage-AZ2fEMs(Landroidx/compose/ui/graphics/ImageBitmap;JJJJFLkotlin/ResultKt;Landroidx/compose/ui/graphics/BlendModeColorFilter;II)V
+HSPLandroidx/compose/ui/graphics/drawscope/CanvasDrawScope;->drawRect-AsUm42w(Landroidx/compose/ui/graphics/Brush;JJFLkotlin/ResultKt;Landroidx/compose/ui/graphics/BlendModeColorFilter;I)V
+HSPLandroidx/compose/ui/graphics/drawscope/CanvasDrawScope;->drawRect-n-J9OG0(JJJFLkotlin/ResultKt;Landroidx/compose/ui/graphics/BlendModeColorFilter;I)V
+HSPLandroidx/compose/ui/graphics/drawscope/CanvasDrawScope;->drawRoundRect-u-Aw5IA(JJJJLkotlin/ResultKt;FLandroidx/compose/ui/graphics/BlendModeColorFilter;I)V
+HSPLandroidx/compose/ui/graphics/drawscope/CanvasDrawScope;->getDensity()F
+HSPLandroidx/compose/ui/graphics/drawscope/CanvasDrawScope;->getDrawContext()Landroidx/compose/ui/graphics/drawscope/CanvasDrawScope$drawContext$1;
+HSPLandroidx/compose/ui/graphics/drawscope/CanvasDrawScope;->getFontScale()F
+HSPLandroidx/compose/ui/graphics/drawscope/CanvasDrawScope;->selectPaint(Lkotlin/ResultKt;)Landroidx/compose/ui/graphics/AndroidPaint;
+HSPLandroidx/compose/ui/graphics/drawscope/CanvasDrawScopeKt$asDrawTransform$1;-><init>(Landroidx/compose/ui/graphics/drawscope/CanvasDrawScope$drawContext$1;)V
+HSPLandroidx/compose/ui/graphics/drawscope/CanvasDrawScopeKt$asDrawTransform$1;->inset(FFFF)V
+HSPLandroidx/compose/ui/graphics/drawscope/CanvasDrawScopeKt$asDrawTransform$1;->scale-0AR0LA0(FFJ)V
+HSPLandroidx/compose/ui/graphics/drawscope/CanvasDrawScopeKt$asDrawTransform$1;->translate(FF)V
+HSPLandroidx/compose/ui/graphics/drawscope/DrawScope;->drawImage-AZ2fEMs$default(Landroidx/compose/ui/graphics/drawscope/DrawScope;Landroidx/compose/ui/graphics/ImageBitmap;JJJFLandroidx/compose/ui/graphics/BlendModeColorFilter;II)V
+HSPLandroidx/compose/ui/graphics/drawscope/DrawScope;->drawRect-AsUm42w$default(Landroidx/compose/ui/graphics/drawscope/DrawScope;Landroidx/compose/ui/graphics/Brush;JJFLkotlin/ResultKt;I)V
+HSPLandroidx/compose/ui/graphics/drawscope/DrawScope;->drawRect-n-J9OG0$default(Landroidx/compose/ui/graphics/drawscope/DrawScope;JJI)V
+HSPLandroidx/compose/ui/graphics/drawscope/DrawScope;->getCenter-F1C5BW0()J
+HSPLandroidx/compose/ui/graphics/drawscope/DrawScope;->getSize-NH-jbRc()J
+HSPLandroidx/compose/ui/graphics/drawscope/DrawScope;->offsetSize-PENXr5M(JJ)J
+HSPLandroidx/compose/ui/graphics/drawscope/Fill;-><clinit>()V
+HSPLandroidx/compose/ui/graphics/drawscope/Stroke;-><init>(FFIII)V
+HSPLandroidx/compose/ui/graphics/drawscope/Stroke;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/graphics/painter/BitmapPainter;-><init>(Landroidx/compose/ui/graphics/ImageBitmap;)V
+HSPLandroidx/compose/ui/graphics/painter/BitmapPainter;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/graphics/painter/BitmapPainter;->getIntrinsicSize-NH-jbRc()J
+HSPLandroidx/compose/ui/graphics/painter/BitmapPainter;->onDraw(Landroidx/compose/ui/graphics/drawscope/DrawScope;)V
+HSPLandroidx/compose/ui/graphics/painter/Painter;-><init>()V
+HSPLandroidx/compose/ui/input/InputMode;-><init>(I)V
+HSPLandroidx/compose/ui/input/InputMode;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/input/InputModeManagerImpl;-><init>(I)V
+HSPLandroidx/compose/ui/input/key/Key;-><clinit>()V
+HSPLandroidx/compose/ui/input/key/Key;->equals-impl0(JJ)Z
+HSPLandroidx/compose/ui/input/key/KeyEvent;-><init>(Landroid/view/KeyEvent;)V
+HSPLandroidx/compose/ui/input/key/KeyInputElement;-><init>(Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/input/key/KeyInputElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/ui/input/key/KeyInputElement;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/input/key/KeyInputElement;->update(Landroidx/compose/ui/Modifier$Node;)V
+HSPLandroidx/compose/ui/input/key/KeyInputNode;-><init>(Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/input/key/Key_androidKt;->Key(I)J
+HSPLandroidx/compose/ui/input/key/Key_androidKt;->onKeyEvent(Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function1;)Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/ui/input/nestedscroll/NestedScrollDispatcher;-><init>()V
+HSPLandroidx/compose/ui/input/nestedscroll/NestedScrollNode;-><init>(Landroidx/compose/foundation/gestures/ScrollableNestedScrollConnection;Landroidx/compose/ui/input/nestedscroll/NestedScrollDispatcher;)V
+HSPLandroidx/compose/ui/input/nestedscroll/NestedScrollNode;->getProvidedValues()Landroidx/tv/material3/TabKt;
+HSPLandroidx/compose/ui/input/nestedscroll/NestedScrollNode;->onAttach()V
+HSPLandroidx/compose/ui/input/nestedscroll/NestedScrollNodeKt;-><clinit>()V
+HSPLandroidx/compose/ui/input/pointer/AndroidPointerIconType;-><init>(I)V
+HSPLandroidx/compose/ui/input/pointer/MotionEventAdapter;-><init>()V
+HSPLandroidx/compose/ui/input/pointer/NodeParent;-><init>()V
+HSPLandroidx/compose/ui/input/pointer/PointerEvent;-><init>(Ljava/util/List;Lcom/google/gson/internal/ConstructorConstructor;)V
+HSPLandroidx/compose/ui/input/pointer/PointerIcon;-><clinit>()V
+HSPLandroidx/compose/ui/input/pointer/PointerKeyboardModifiers;-><init>(I)V
+HSPLandroidx/compose/ui/input/pointer/PointerKeyboardModifiers;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/input/pointer/SuspendPointerInputElement;-><init>(Ljava/lang/Object;Lokhttp3/MediaType;Lkotlin/jvm/functions/Function2;I)V
+HSPLandroidx/compose/ui/input/pointer/SuspendPointerInputElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/ui/input/pointer/SuspendingPointerInputFilterKt;-><clinit>()V
+HSPLandroidx/compose/ui/input/pointer/SuspendingPointerInputFilterKt;->pointerInput(Landroidx/compose/ui/Modifier;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/ui/input/pointer/SuspendingPointerInputModifierNodeImpl;-><init>(Lkotlin/jvm/functions/Function2;)V
+HSPLandroidx/compose/ui/input/pointer/util/PointerIdArray;-><init>(FF)V
+HSPLandroidx/compose/ui/input/pointer/util/PointerIdArray;-><init>(FFLandroidx/compose/animation/core/AnimationVector;)V
+HSPLandroidx/compose/ui/input/pointer/util/PointerIdArray;-><init>(I[Landroidx/core/provider/FontsContractCompat$FontInfo;)V
+HSPLandroidx/compose/ui/input/pointer/util/PointerIdArray;-><init>(Landroidx/compose/animation/core/FloatAnimationSpec;)V
+HSPLandroidx/compose/ui/input/pointer/util/PointerIdArray;-><init>(Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;I)V
+HSPLandroidx/compose/ui/input/pointer/util/PointerIdArray;->get(I)Landroidx/compose/animation/core/FloatAnimationSpec;
+HSPLandroidx/compose/ui/input/pointer/util/VelocityTracker1D;-><init>()V
+HSPLandroidx/compose/ui/input/pointer/util/VelocityTracker;-><init>()V
+HSPLandroidx/compose/ui/input/rotary/RotaryInputElement;-><init>()V
+HSPLandroidx/compose/ui/input/rotary/RotaryInputElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/ui/input/rotary/RotaryInputModifierKt;->onRotaryScrollEvent()Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/ui/input/rotary/RotaryInputNode;-><init>(Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/layout/AlignmentLine;-><init>(Lkotlin/jvm/functions/Function2;)V
+HSPLandroidx/compose/ui/layout/AlignmentLineKt$FirstBaseline$1;-><clinit>()V
+HSPLandroidx/compose/ui/layout/AlignmentLineKt$FirstBaseline$1;-><init>()V
+HSPLandroidx/compose/ui/layout/AlignmentLineKt$LastBaseline$1;-><clinit>()V
+HSPLandroidx/compose/ui/layout/AlignmentLineKt$LastBaseline$1;-><init>()V
+HSPLandroidx/compose/ui/layout/AlignmentLineKt;-><clinit>()V
+HSPLandroidx/compose/ui/layout/BeyondBoundsLayoutKt;-><clinit>()V
+HSPLandroidx/compose/ui/layout/ComposableSingletons$SubcomposeLayoutKt;-><clinit>()V
+HSPLandroidx/compose/ui/layout/DefaultIntrinsicMeasurable;-><init>(Landroidx/compose/ui/layout/Measurable;Ljava/lang/Enum;Ljava/lang/Enum;I)V
+HSPLandroidx/compose/ui/layout/DefaultIntrinsicMeasurable;->getParentData()Ljava/lang/Object;
+HSPLandroidx/compose/ui/layout/FixedSizeIntrinsicsPlaceable;-><init>(III)V
+HSPLandroidx/compose/ui/layout/IntrinsicMinMax;-><clinit>()V
+HSPLandroidx/compose/ui/layout/IntrinsicMinMax;-><init>(ILjava/lang/String;)V
+HSPLandroidx/compose/ui/layout/IntrinsicWidthHeight;-><clinit>()V
+HSPLandroidx/compose/ui/layout/IntrinsicWidthHeight;-><init>(ILjava/lang/String;)V
+HSPLandroidx/compose/ui/layout/IntrinsicsMeasureScope;-><init>(Landroidx/compose/ui/layout/IntrinsicMeasureScope;Landroidx/compose/ui/unit/LayoutDirection;)V
+HSPLandroidx/compose/ui/layout/IntrinsicsMeasureScope;->roundToPx-0680j_4(F)I
+HSPLandroidx/compose/ui/layout/LayoutElement;-><init>(Lkotlin/jvm/functions/Function3;)V
+HSPLandroidx/compose/ui/layout/LayoutElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/ui/layout/LayoutElement;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/layout/LayoutKt$materializerOf$1;-><init>(Landroidx/compose/ui/Modifier;I)V
+HSPLandroidx/compose/ui/layout/LayoutKt$materializerOf$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/layout/LayoutKt$materializerOf$1;->invoke-Deg8D_g(Landroidx/compose/runtime/Composer;Landroidx/compose/runtime/Composer;)V
+HSPLandroidx/compose/ui/layout/LayoutKt;->ScaleFactor(FF)J
+HSPLandroidx/compose/ui/layout/LayoutKt;->SubcomposeLayout(Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V
+HSPLandroidx/compose/ui/layout/LayoutKt;->SubcomposeLayout(Landroidx/compose/ui/layout/SubcomposeLayoutState;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V
+HSPLandroidx/compose/ui/layout/LayoutKt;->findRootCoordinates(Landroidx/compose/ui/node/NodeCoordinator;)Landroidx/compose/ui/layout/LayoutCoordinates;
+HSPLandroidx/compose/ui/layout/LayoutKt;->layout(Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function3;)Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/ui/layout/LayoutKt;->modifierMaterializerOf(Landroidx/compose/ui/Modifier;)Landroidx/compose/runtime/internal/ComposableLambdaImpl;
+HSPLandroidx/compose/ui/layout/LayoutKt;->times-UQTWf7w(JJ)J
+HSPLandroidx/compose/ui/layout/LayoutModifierImpl;-><init>(Lkotlin/jvm/functions/Function3;)V
+HSPLandroidx/compose/ui/layout/LayoutModifierImpl;->measure-3p2s80s(Landroidx/compose/ui/layout/MeasureScope;Landroidx/compose/ui/layout/Measurable;J)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState$NodeState;-><init>(Ljava/lang/Object;Landroidx/compose/runtime/internal/ComposableLambdaImpl;)V
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState$PostLookaheadMeasureScopeImpl;-><init>(Landroidx/compose/ui/layout/LayoutNodeSubcompositionsState;)V
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState$Scope;-><init>(Landroidx/compose/ui/layout/LayoutNodeSubcompositionsState;)V
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState$Scope;->getDensity()F
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState$Scope;->getLayoutDirection()Landroidx/compose/ui/unit/LayoutDirection;
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState$Scope;->isLookingAhead()Z
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState$Scope;->subcompose(Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/util/List;
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState$createMeasurePolicy$1$measure-3p2s80s$$inlined$createMeasureResult$1;-><init>(Landroidx/compose/ui/layout/MeasureResult;Landroidx/compose/ui/layout/LayoutNodeSubcompositionsState;ILandroidx/compose/ui/layout/MeasureResult;I)V
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState$createMeasurePolicy$1$measure-3p2s80s$$inlined$createMeasureResult$1;->getAlignmentLines()Ljava/util/Map;
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState$createMeasurePolicy$1$measure-3p2s80s$$inlined$createMeasureResult$1;->getHeight()I
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState$createMeasurePolicy$1$measure-3p2s80s$$inlined$createMeasureResult$1;->getWidth()I
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState$createMeasurePolicy$1$measure-3p2s80s$$inlined$createMeasureResult$1;->placeChildren()V
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState$createMeasurePolicy$1;-><init>(Landroidx/compose/ui/layout/LayoutNodeSubcompositionsState;Lkotlin/jvm/functions/Function2;Ljava/lang/String;)V
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState$createMeasurePolicy$1;->measure-3p2s80s(Landroidx/compose/ui/layout/MeasureScope;Ljava/util/List;J)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState$precompose$1;-><init>(Landroidx/compose/ui/layout/LayoutNodeSubcompositionsState;Ljava/lang/Object;)V
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState$precompose$1;->dispose()V
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState$precompose$1;->premeasure-0kLqBqw(JI)V
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState;-><init>(Landroidx/compose/ui/node/LayoutNode;Landroidx/compose/ui/layout/SubcomposeSlotReusePolicy;)V
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState;->disposeOrReuseStartingFromIndex(I)V
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState;->makeSureStateIsConsistent()V
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState;->precompose(Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Landroidx/compose/ui/layout/LayoutNodeSubcompositionsState$precompose$1;
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState;->subcompose(Landroidx/compose/ui/node/LayoutNode;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)V
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState;->takeNodeFromReusables(Ljava/lang/Object;)Landroidx/compose/ui/node/LayoutNode;
+HSPLandroidx/compose/ui/layout/MeasurePolicy;->maxIntrinsicHeight(Landroidx/compose/ui/node/NodeCoordinator;Ljava/util/List;I)I
+HSPLandroidx/compose/ui/layout/MeasureScope$layout$1;-><init>(IILjava/util/Map;Landroidx/compose/ui/layout/MeasureScope;Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/layout/MeasureScope$layout$1;->getAlignmentLines()Ljava/util/Map;
+HSPLandroidx/compose/ui/layout/MeasureScope$layout$1;->getHeight()I
+HSPLandroidx/compose/ui/layout/MeasureScope$layout$1;->getWidth()I
+HSPLandroidx/compose/ui/layout/MeasureScope$layout$1;->placeChildren()V
+HSPLandroidx/compose/ui/layout/MeasureScope;->layout$default(Landroidx/compose/ui/layout/MeasureScope;IILkotlin/jvm/functions/Function1;)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/ui/layout/MeasureScope;->layout(IILjava/util/Map;Lkotlin/jvm/functions/Function1;)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/ui/layout/OnSizeChangedModifier;-><init>(Landroidx/compose/foundation/AndroidEdgeEffectOverscrollEffect$onNewSize$1;)V
+HSPLandroidx/compose/ui/layout/PinnableContainerKt;-><clinit>()V
+HSPLandroidx/compose/ui/layout/Placeable$PlacementScope$Companion;->access$configureForPlacingForAlignment(Landroidx/compose/ui/node/LookaheadCapablePlaceable;)Z
+HSPLandroidx/compose/ui/layout/Placeable$PlacementScope;-><clinit>()V
+HSPLandroidx/compose/ui/layout/Placeable$PlacementScope;->place(Landroidx/compose/ui/layout/Placeable;IIF)V
+HSPLandroidx/compose/ui/layout/Placeable$PlacementScope;->place-70tqf50$default(Landroidx/compose/ui/layout/Placeable$PlacementScope;Landroidx/compose/ui/layout/Placeable;J)V
+HSPLandroidx/compose/ui/layout/Placeable$PlacementScope;->place-70tqf50(Landroidx/compose/ui/layout/Placeable;JF)V
+HSPLandroidx/compose/ui/layout/Placeable$PlacementScope;->placeRelative$default(Landroidx/compose/ui/layout/Placeable$PlacementScope;Landroidx/compose/ui/layout/Placeable;II)V
+HSPLandroidx/compose/ui/layout/Placeable$PlacementScope;->placeRelativeWithLayer$default(Landroidx/compose/ui/layout/Placeable$PlacementScope;Landroidx/compose/ui/layout/Placeable;II)V
+HSPLandroidx/compose/ui/layout/Placeable$PlacementScope;->placeRelativeWithLayer-aW-9-wM$default(Landroidx/compose/ui/layout/Placeable$PlacementScope;Landroidx/compose/ui/layout/Placeable;J)V
+HSPLandroidx/compose/ui/layout/Placeable$PlacementScope;->placeWithLayer$default(Landroidx/compose/ui/layout/Placeable$PlacementScope;Landroidx/compose/ui/layout/Placeable;IILkotlin/jvm/functions/Function1;I)V
+HSPLandroidx/compose/ui/layout/Placeable;-><init>()V
+HSPLandroidx/compose/ui/layout/Placeable;->getMeasuredHeight()I
+HSPLandroidx/compose/ui/layout/Placeable;->getMeasuredWidth()I
+HSPLandroidx/compose/ui/layout/Placeable;->onMeasuredSizeChanged()V
+HSPLandroidx/compose/ui/layout/Placeable;->setMeasuredSize-ozmzZPI(J)V
+HSPLandroidx/compose/ui/layout/Placeable;->setMeasurementConstraints-BRTryo0(J)V
+HSPLandroidx/compose/ui/layout/PlaceableKt;-><clinit>()V
+HSPLandroidx/compose/ui/layout/RootMeasurePolicy$measure$2;-><init>(ILjava/lang/Object;)V
+HSPLandroidx/compose/ui/layout/RootMeasurePolicy$measure$2;->invoke(Landroidx/compose/ui/layout/Placeable$PlacementScope;)V
+HSPLandroidx/compose/ui/layout/RootMeasurePolicy$measure$2;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/layout/RootMeasurePolicy;-><clinit>()V
+HSPLandroidx/compose/ui/layout/RootMeasurePolicy;-><init>()V
+HSPLandroidx/compose/ui/layout/RootMeasurePolicy;->measure-3p2s80s(Landroidx/compose/ui/layout/MeasureScope;Ljava/util/List;J)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/ui/layout/ScaleFactor;-><clinit>()V
+HSPLandroidx/compose/ui/layout/SubcomposeLayoutKt$SubcomposeLayout$2;-><init>(Ljava/lang/Object;ILjava/lang/Object;II)V
+HSPLandroidx/compose/ui/layout/SubcomposeLayoutKt$SubcomposeLayout$2;->invoke(Landroidx/compose/runtime/Composer;I)V
+HSPLandroidx/compose/ui/layout/SubcomposeLayoutKt$SubcomposeLayout$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/layout/SubcomposeLayoutKt$SubcomposeLayout$4;-><init>(ILjava/lang/Object;)V
+HSPLandroidx/compose/ui/layout/SubcomposeLayoutKt$SubcomposeLayout$4;->invoke()Ljava/lang/Object;
+HSPLandroidx/compose/ui/layout/SubcomposeLayoutKt$SubcomposeLayout$5$1$invoke$$inlined$onDispose$1;-><init>(ILjava/lang/Object;)V
+HSPLandroidx/compose/ui/layout/SubcomposeLayoutKt$SubcomposeLayout$5$1$invoke$$inlined$onDispose$1;->dispose()V
+HSPLandroidx/compose/ui/layout/SubcomposeLayoutState$setRoot$1;-><init>(Landroidx/compose/ui/layout/SubcomposeLayoutState;I)V
+HSPLandroidx/compose/ui/layout/SubcomposeLayoutState$setRoot$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/layout/SubcomposeLayoutState;-><init>(Landroidx/compose/ui/layout/SubcomposeSlotReusePolicy;)V
+HSPLandroidx/compose/ui/layout/SubcomposeLayoutState;->getState()Landroidx/compose/ui/layout/LayoutNodeSubcompositionsState;
+HSPLandroidx/compose/ui/layout/SubcomposeSlotReusePolicy$SlotIdsSet;-><init>()V
+HSPLandroidx/compose/ui/layout/SubcomposeSlotReusePolicy$SlotIdsSet;->clear()V
+HSPLandroidx/compose/ui/layout/SubcomposeSlotReusePolicy$SlotIdsSet;->contains(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/layout/SubcomposeSlotReusePolicy$SlotIdsSet;->iterator()Ljava/util/Iterator;
+HSPLandroidx/compose/ui/modifier/BackwardsCompatLocalMap;-><init>(Landroidx/compose/ui/modifier/ModifierLocalProvider;)V
+HSPLandroidx/compose/ui/modifier/BackwardsCompatLocalMap;->contains$ui_release(Landroidx/compose/ui/modifier/ModifierLocal;)Z
+HSPLandroidx/compose/ui/modifier/EmptyMap;-><clinit>()V
+HSPLandroidx/compose/ui/modifier/EmptyMap;->contains$ui_release(Landroidx/compose/ui/modifier/ModifierLocal;)Z
+HSPLandroidx/compose/ui/modifier/ModifierLocal;-><init>(Landroidx/compose/material3/ShapesKt$LocalShapes$1;)V
+HSPLandroidx/compose/ui/modifier/ModifierLocalManager;-><init>(Landroidx/compose/ui/node/Owner;)V
+HSPLandroidx/compose/ui/modifier/ModifierLocalModifierNode;->getCurrent(Landroidx/compose/ui/modifier/ProvidableModifierLocal;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/modifier/ModifierLocalModifierNode;->getProvidedValues()Landroidx/tv/material3/TabKt;
+HSPLandroidx/compose/ui/modifier/SingleLocalMap;-><init>(Landroidx/compose/ui/modifier/ModifierLocal;)V
+HSPLandroidx/compose/ui/modifier/SingleLocalMap;->contains$ui_release(Landroidx/compose/ui/modifier/ModifierLocal;)Z
+HSPLandroidx/compose/ui/modifier/SingleLocalMap;->get$ui_release(Landroidx/compose/ui/modifier/ProvidableModifierLocal;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/node/AlignmentLines;-><init>(Landroidx/compose/ui/node/AlignmentLinesOwner;)V
+HSPLandroidx/compose/ui/node/AlignmentLines;->getQueried$ui_release()Z
+HSPLandroidx/compose/ui/node/AlignmentLines;->getRequired$ui_release()Z
+HSPLandroidx/compose/ui/node/AlignmentLines;->onAlignmentsChanged()V
+HSPLandroidx/compose/ui/node/AlignmentLines;->recalculateQueryOwner()V
+HSPLandroidx/compose/ui/node/BackwardsCompatNode;-><init>(Landroidx/compose/ui/Modifier$Element;)V
+HSPLandroidx/compose/ui/node/BackwardsCompatNode;->draw(Landroidx/compose/ui/graphics/drawscope/ContentDrawScope;)V
+HSPLandroidx/compose/ui/node/BackwardsCompatNode;->getProvidedValues()Landroidx/tv/material3/TabKt;
+HSPLandroidx/compose/ui/node/BackwardsCompatNode;->initializeModifier(Z)V
+HSPLandroidx/compose/ui/node/BackwardsCompatNode;->onAttach()V
+HSPLandroidx/compose/ui/node/BackwardsCompatNode;->onGloballyPositioned(Landroidx/compose/ui/node/NodeCoordinator;)V
+HSPLandroidx/compose/ui/node/BackwardsCompatNode;->onMeasureResultChanged()V
+HSPLandroidx/compose/ui/node/BackwardsCompatNode;->onPlaced(Landroidx/compose/ui/node/NodeCoordinator;)V
+HSPLandroidx/compose/ui/node/BackwardsCompatNode;->onRemeasured-ozmzZPI(J)V
+HSPLandroidx/compose/ui/node/BackwardsCompatNode;->unInitializeModifier()V
+HSPLandroidx/compose/ui/node/CanFocusChecker;-><clinit>()V
+HSPLandroidx/compose/ui/node/CanFocusChecker;->setCanFocus(Z)V
+HSPLandroidx/compose/ui/node/ComposeUiNode$Companion;-><clinit>()V
+HSPLandroidx/compose/ui/node/ComposeUiNode;-><clinit>()V
+HSPLandroidx/compose/ui/node/DelegatingNode;-><init>()V
+HSPLandroidx/compose/ui/node/DelegatingNode;->delegate(Landroidx/compose/ui/Modifier$Node;)V
+HSPLandroidx/compose/ui/node/DelegatingNode;->markAsAttached$ui_release()V
+HSPLandroidx/compose/ui/node/DelegatingNode;->markAsDetached$ui_release()V
+HSPLandroidx/compose/ui/node/DelegatingNode;->reset$ui_release()V
+HSPLandroidx/compose/ui/node/DelegatingNode;->runAttachLifecycle$ui_release()V
+HSPLandroidx/compose/ui/node/DelegatingNode;->runDetachLifecycle$ui_release()V
+HSPLandroidx/compose/ui/node/DelegatingNode;->updateCoordinator$ui_release(Landroidx/compose/ui/node/NodeCoordinator;)V
+HSPLandroidx/compose/ui/node/DrawModifierNode;->onMeasureResultChanged()V
+HSPLandroidx/compose/ui/node/HitTestResult;-><init>()V
+HSPLandroidx/compose/ui/node/InnerNodeCoordinator;-><clinit>()V
+HSPLandroidx/compose/ui/node/InnerNodeCoordinator;-><init>(Landroidx/compose/ui/node/LayoutNode;)V
+HSPLandroidx/compose/ui/node/InnerNodeCoordinator;->getTail()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/ui/node/InnerNodeCoordinator;->maxIntrinsicHeight(I)I
+HSPLandroidx/compose/ui/node/InnerNodeCoordinator;->measure-BRTryo0(J)Landroidx/compose/ui/layout/Placeable;
+HSPLandroidx/compose/ui/node/InnerNodeCoordinator;->performDraw(Landroidx/compose/ui/graphics/Canvas;)V
+HSPLandroidx/compose/ui/node/InnerNodeCoordinator;->placeAt-f8xVGno(JFLkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/node/IntrinsicsPolicy;-><init>(Landroidx/compose/ui/node/LayoutNode;)V
+HSPLandroidx/compose/ui/node/IntrinsicsPolicy;->measurePolicyFromState()Landroidx/compose/ui/layout/MeasurePolicy;
+HSPLandroidx/compose/ui/node/LayerPositionalProperties;-><init>()V
+HSPLandroidx/compose/ui/node/LayoutAwareModifierNode;->onRemeasured-ozmzZPI(J)V
+HSPLandroidx/compose/ui/node/LayoutModifierNode;->maxIntrinsicHeight(Landroidx/compose/ui/layout/IntrinsicMeasureScope;Landroidx/compose/ui/layout/Measurable;I)I
+HSPLandroidx/compose/ui/node/LayoutModifierNode;->maxIntrinsicWidth(Landroidx/compose/ui/layout/IntrinsicMeasureScope;Landroidx/compose/ui/layout/Measurable;I)I
+HSPLandroidx/compose/ui/node/LayoutModifierNodeCoordinator;-><clinit>()V
+HSPLandroidx/compose/ui/node/LayoutModifierNodeCoordinator;-><init>(Landroidx/compose/ui/node/LayoutNode;Landroidx/compose/ui/node/LayoutModifierNode;)V
+HSPLandroidx/compose/ui/node/LayoutModifierNodeCoordinator;->getTail()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/ui/node/LayoutModifierNodeCoordinator;->maxIntrinsicHeight(I)I
+HSPLandroidx/compose/ui/node/LayoutModifierNodeCoordinator;->maxIntrinsicWidth(I)I
+HSPLandroidx/compose/ui/node/LayoutModifierNodeCoordinator;->measure-BRTryo0(J)Landroidx/compose/ui/layout/Placeable;
+HSPLandroidx/compose/ui/node/LayoutModifierNodeCoordinator;->performDraw(Landroidx/compose/ui/graphics/Canvas;)V
+HSPLandroidx/compose/ui/node/LayoutModifierNodeCoordinator;->placeAt-f8xVGno(JFLkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/node/LayoutNode$$ExternalSyntheticLambda0;-><init>(I)V
+HSPLandroidx/compose/ui/node/LayoutNode$$ExternalSyntheticLambda0;->compare(Ljava/lang/Object;Ljava/lang/Object;)I
+HSPLandroidx/compose/ui/node/LayoutNode$Companion$ErrorMeasurePolicy$1;-><init>()V
+HSPLandroidx/compose/ui/node/LayoutNode$NoIntrinsicsMeasurePolicy;-><init>(Ljava/lang/String;)V
+HSPLandroidx/compose/ui/node/LayoutNode$WhenMappings;-><clinit>()V
+HSPLandroidx/compose/ui/node/LayoutNode$_foldedChildren$1;-><init>(ILjava/lang/Object;)V
+HSPLandroidx/compose/ui/node/LayoutNode$_foldedChildren$1;->invoke()Ljava/lang/Object;
+HSPLandroidx/compose/ui/node/LayoutNode$_foldedChildren$1;->invoke()V
+HSPLandroidx/compose/ui/node/LayoutNode;-><clinit>()V
+HSPLandroidx/compose/ui/node/LayoutNode;-><init>(IZ)V
+HSPLandroidx/compose/ui/node/LayoutNode;-><init>(ZI)V
+HSPLandroidx/compose/ui/node/LayoutNode;->attach$ui_release(Landroidx/compose/ui/node/Owner;)V
+HSPLandroidx/compose/ui/node/LayoutNode;->clearSubtreeIntrinsicsUsage$ui_release()V
+HSPLandroidx/compose/ui/node/LayoutNode;->clearSubtreePlacementIntrinsicsUsage()V
+HSPLandroidx/compose/ui/node/LayoutNode;->draw$ui_release(Landroidx/compose/ui/graphics/Canvas;)V
+HSPLandroidx/compose/ui/node/LayoutNode;->forceRemeasure()V
+HSPLandroidx/compose/ui/node/LayoutNode;->getChildMeasurables$ui_release()Ljava/util/List;
+HSPLandroidx/compose/ui/node/LayoutNode;->getChildren$ui_release()Ljava/util/List;
+HSPLandroidx/compose/ui/node/LayoutNode;->getFoldedChildren$ui_release()Ljava/util/List;
+HSPLandroidx/compose/ui/node/LayoutNode;->getMeasuredByParent$ui_release$enumunboxing$()I
+HSPLandroidx/compose/ui/node/LayoutNode;->getParent$ui_release()Landroidx/compose/ui/node/LayoutNode;
+HSPLandroidx/compose/ui/node/LayoutNode;->getPlaceOrder$ui_release()I
+HSPLandroidx/compose/ui/node/LayoutNode;->getZSortedChildren()Landroidx/compose/runtime/collection/MutableVector;
+HSPLandroidx/compose/ui/node/LayoutNode;->get_children$ui_release()Landroidx/compose/runtime/collection/MutableVector;
+HSPLandroidx/compose/ui/node/LayoutNode;->insertAt$ui_release(ILandroidx/compose/ui/node/LayoutNode;)V
+HSPLandroidx/compose/ui/node/LayoutNode;->invalidateLayer$ui_release()V
+HSPLandroidx/compose/ui/node/LayoutNode;->invalidateLayers$ui_release()V
+HSPLandroidx/compose/ui/node/LayoutNode;->invalidateMeasurements$ui_release()V
+HSPLandroidx/compose/ui/node/LayoutNode;->invalidateSemantics$ui_release()V
+HSPLandroidx/compose/ui/node/LayoutNode;->invalidateUnfoldedVirtualChildren()V
+HSPLandroidx/compose/ui/node/LayoutNode;->isAttached()Z
+HSPLandroidx/compose/ui/node/LayoutNode;->isPlaced()Z
+HSPLandroidx/compose/ui/node/LayoutNode;->isValidOwnerScope()Z
+HSPLandroidx/compose/ui/node/LayoutNode;->move$ui_release(III)V
+HSPLandroidx/compose/ui/node/LayoutNode;->onZSortedChildrenInvalidated$ui_release()V
+HSPLandroidx/compose/ui/node/LayoutNode;->requestRelayout$ui_release(Z)V
+HSPLandroidx/compose/ui/node/LayoutNode;->requestRemeasure$ui_release$default(Landroidx/compose/ui/node/LayoutNode;ZI)V
+HSPLandroidx/compose/ui/node/LayoutNode;->rescheduleRemeasureOrRelayout$ui_release(Landroidx/compose/ui/node/LayoutNode;)V
+HSPLandroidx/compose/ui/node/LayoutNode;->resetSubtreeIntrinsicsUsage$ui_release()V
+HSPLandroidx/compose/ui/node/LayoutNode;->setCompositionLocalMap(Landroidx/compose/runtime/CompositionLocalMap;)V
+HSPLandroidx/compose/ui/node/LayoutNode;->setDensity(Landroidx/compose/ui/unit/Density;)V
+HSPLandroidx/compose/ui/node/LayoutNode;->setLookaheadRoot(Landroidx/compose/ui/node/LayoutNode;)V
+HSPLandroidx/compose/ui/node/LayoutNode;->setMeasurePolicy(Landroidx/compose/ui/layout/MeasurePolicy;)V
+HSPLandroidx/compose/ui/node/LayoutNode;->setModifier(Landroidx/compose/ui/Modifier;)V
+HSPLandroidx/compose/ui/node/LayoutNode;->setViewConfiguration(Landroidx/compose/ui/platform/ViewConfiguration;)V
+HSPLandroidx/compose/ui/node/LayoutNode;->updateChildrenIfDirty$ui_release()V
+HSPLandroidx/compose/ui/node/LayoutNodeDrawScope;-><init>()V
+HSPLandroidx/compose/ui/node/LayoutNodeDrawScope;->drawContent()V
+HSPLandroidx/compose/ui/node/LayoutNodeDrawScope;->drawDirect-x_KDEd0$ui_release(Landroidx/compose/ui/graphics/Canvas;JLandroidx/compose/ui/node/NodeCoordinator;Landroidx/compose/ui/node/DrawModifierNode;)V
+HSPLandroidx/compose/ui/node/LayoutNodeDrawScope;->drawImage-AZ2fEMs(Landroidx/compose/ui/graphics/ImageBitmap;JJJJFLkotlin/ResultKt;Landroidx/compose/ui/graphics/BlendModeColorFilter;II)V
+HSPLandroidx/compose/ui/node/LayoutNodeDrawScope;->drawRect-AsUm42w(Landroidx/compose/ui/graphics/Brush;JJFLkotlin/ResultKt;Landroidx/compose/ui/graphics/BlendModeColorFilter;I)V
+HSPLandroidx/compose/ui/node/LayoutNodeDrawScope;->drawRect-n-J9OG0(JJJFLkotlin/ResultKt;Landroidx/compose/ui/graphics/BlendModeColorFilter;I)V
+HSPLandroidx/compose/ui/node/LayoutNodeDrawScope;->drawRoundRect-u-Aw5IA(JJJJLkotlin/ResultKt;FLandroidx/compose/ui/graphics/BlendModeColorFilter;I)V
+HSPLandroidx/compose/ui/node/LayoutNodeDrawScope;->getCenter-F1C5BW0()J
+HSPLandroidx/compose/ui/node/LayoutNodeDrawScope;->getDensity()F
+HSPLandroidx/compose/ui/node/LayoutNodeDrawScope;->getFontScale()F
+HSPLandroidx/compose/ui/node/LayoutNodeDrawScope;->getLayoutDirection()Landroidx/compose/ui/unit/LayoutDirection;
+HSPLandroidx/compose/ui/node/LayoutNodeDrawScope;->getSize-NH-jbRc()J
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate$placeOuterCoordinator$1;-><init>(Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/node/LayoutNodeLayoutDelegate;JF)V
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate$placeOuterCoordinator$1;->invoke()Ljava/lang/Object;
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate;-><init>(Landroidx/compose/ui/node/LayoutNodeLayoutDelegate;)V
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate;->forEachChildAlignmentLinesOwner(Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate;->getAlignmentLines()Landroidx/compose/ui/node/LookaheadAlignmentLines;
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate;->getChildDelegates$ui_release()Ljava/util/List;
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate;->getInnerCoordinator()Landroidx/compose/ui/node/InnerNodeCoordinator;
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate;->getMeasuredWidth()I
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate;->getParentAlignmentLinesOwner()Landroidx/compose/ui/node/AlignmentLinesOwner;
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate;->getParentData()Ljava/lang/Object;
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate;->layoutChildren()V
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate;->markNodeAndSubtreeAsPlaced()V
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate;->markSubtreeAsNotPlaced()V
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate;->maxIntrinsicHeight(I)I
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate;->maxIntrinsicWidth(I)I
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate;->measure-BRTryo0(J)Landroidx/compose/ui/layout/Placeable;
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate;->notifyChildrenUsingCoordinatesWhilePlacing()V
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate;->onIntrinsicsQueried()V
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate;->onNodePlaced$ui_release()V
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate;->placeAt-f8xVGno(JFLkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate;->placeOuterCoordinator-f8xVGno(JFLkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate;->remeasure-BRTryo0(J)Z
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate;->replace()V
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$performMeasure$2;-><init>(Landroidx/compose/ui/node/LayoutNodeLayoutDelegate;JI)V
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$performMeasure$2;->invoke()Ljava/lang/Object;
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$performMeasure$2;->invoke()V
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate;-><init>(Landroidx/compose/ui/node/LayoutNode;)V
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate;->getOuterCoordinator()Landroidx/compose/ui/node/NodeCoordinator;
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate;->isOutMostLookaheadRoot(Landroidx/compose/ui/node/LayoutNode;)Z
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate;->setCoordinatesAccessedDuringModifierPlacement(Z)V
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate;->setCoordinatesAccessedDuringPlacement(Z)V
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate;->updateParentData()V
+HSPLandroidx/compose/ui/node/LookaheadAlignmentLines;-><init>(Landroidx/compose/ui/node/AlignmentLinesOwner;I)V
+HSPLandroidx/compose/ui/node/LookaheadCapablePlaceable;->invalidateAlignmentLinesFromPositionChange(Landroidx/compose/ui/node/NodeCoordinator;)V
+HSPLandroidx/compose/ui/node/LookaheadCapablePlaceable;->isLookingAhead()Z
+HSPLandroidx/compose/ui/node/MeasureAndLayoutDelegate;-><init>(Landroidx/compose/ui/node/LayoutNode;)V
+HSPLandroidx/compose/ui/node/MeasureAndLayoutDelegate;->dispatchOnPositionedCallbacks(Z)V
+HSPLandroidx/compose/ui/node/MeasureAndLayoutDelegate;->doLookaheadRemeasure-sdFAvZA(Landroidx/compose/ui/node/LayoutNode;Landroidx/compose/ui/unit/Constraints;)Z
+HSPLandroidx/compose/ui/node/MeasureAndLayoutDelegate;->doRemeasure-sdFAvZA(Landroidx/compose/ui/node/LayoutNode;Landroidx/compose/ui/unit/Constraints;)Z
+HSPLandroidx/compose/ui/node/MeasureAndLayoutDelegate;->forceMeasureTheSubtree(Landroidx/compose/ui/node/LayoutNode;Z)V
+HSPLandroidx/compose/ui/node/MeasureAndLayoutDelegate;->measureAndLayout(Lkotlin/jvm/functions/Function0;)Z
+HSPLandroidx/compose/ui/node/MeasureAndLayoutDelegate;->measureAndLayout-0kLqBqw(Landroidx/compose/ui/node/LayoutNode;J)V
+HSPLandroidx/compose/ui/node/MeasureAndLayoutDelegate;->measureOnly()V
+HSPLandroidx/compose/ui/node/MeasureAndLayoutDelegate;->recurseRemeasure(Landroidx/compose/ui/node/LayoutNode;)V
+HSPLandroidx/compose/ui/node/MeasureAndLayoutDelegate;->remeasureAndRelayoutIfNeeded(Landroidx/compose/ui/node/LayoutNode;Z)Z
+HSPLandroidx/compose/ui/node/MeasureAndLayoutDelegate;->remeasureOnly(Landroidx/compose/ui/node/LayoutNode;)V
+HSPLandroidx/compose/ui/node/MeasureAndLayoutDelegate;->requestRelayout(Landroidx/compose/ui/node/LayoutNode;Z)Z
+HSPLandroidx/compose/ui/node/MeasureAndLayoutDelegate;->updateRootConstraints-BRTryo0(J)V
+HSPLandroidx/compose/ui/node/NodeChain$Differ;-><init>(Landroidx/compose/ui/node/NodeChain;Landroidx/compose/ui/Modifier$Node;ILandroidx/compose/runtime/collection/MutableVector;Landroidx/compose/runtime/collection/MutableVector;Z)V
+HSPLandroidx/compose/ui/node/NodeChain$Differ;->areItemsTheSame(II)Z
+HSPLandroidx/compose/ui/node/NodeChain;-><init>(Landroidx/compose/ui/node/LayoutNode;)V
+HSPLandroidx/compose/ui/node/NodeChain;->access$propagateCoordinator(Landroidx/compose/ui/node/NodeChain;Landroidx/compose/ui/Modifier$Node;Landroidx/compose/ui/node/NodeCoordinator;)V
+HSPLandroidx/compose/ui/node/NodeChain;->createAndInsertNodeAsChild(Landroidx/compose/ui/Modifier$Element;Landroidx/compose/ui/Modifier$Node;)Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/ui/node/NodeChain;->detachAndRemoveNode(Landroidx/compose/ui/Modifier$Node;)Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/ui/node/NodeChain;->has-H91voCI$ui_release(I)Z
+HSPLandroidx/compose/ui/node/NodeChain;->runAttachLifecycle()V
+HSPLandroidx/compose/ui/node/NodeChain;->syncCoordinators()V
+HSPLandroidx/compose/ui/node/NodeChain;->updateNode(Landroidx/compose/ui/Modifier$Element;Landroidx/compose/ui/Modifier$Element;Landroidx/compose/ui/Modifier$Node;)V
+HSPLandroidx/compose/ui/node/NodeChainKt;-><clinit>()V
+HSPLandroidx/compose/ui/node/NodeChainKt;->actionForModifiers(Landroidx/compose/ui/Modifier$Element;Landroidx/compose/ui/Modifier$Element;)I
+HSPLandroidx/compose/ui/node/NodeCoordinator$invoke$1;-><init>(Ljava/lang/Object;ILjava/lang/Object;)V
+HSPLandroidx/compose/ui/node/NodeCoordinator$invoke$1;->invoke()Ljava/lang/Object;
+HSPLandroidx/compose/ui/node/NodeCoordinator$invoke$1;->invoke()V
+HSPLandroidx/compose/ui/node/NodeCoordinator;-><clinit>()V
+HSPLandroidx/compose/ui/node/NodeCoordinator;-><init>(Landroidx/compose/ui/node/LayoutNode;)V
+HSPLandroidx/compose/ui/node/NodeCoordinator;->ancestorToLocal(Landroidx/compose/ui/node/NodeCoordinator;Landroidx/compose/ui/geometry/MutableRect;Z)V
+HSPLandroidx/compose/ui/node/NodeCoordinator;->draw(Landroidx/compose/ui/graphics/Canvas;)V
+HSPLandroidx/compose/ui/node/NodeCoordinator;->drawContainedDrawModifiers(Landroidx/compose/ui/graphics/Canvas;)V
+HSPLandroidx/compose/ui/node/NodeCoordinator;->findCommonAncestor$ui_release(Landroidx/compose/ui/node/NodeCoordinator;)Landroidx/compose/ui/node/NodeCoordinator;
+HSPLandroidx/compose/ui/node/NodeCoordinator;->getAlignmentLinesOwner()Landroidx/compose/ui/node/AlignmentLinesOwner;
+HSPLandroidx/compose/ui/node/NodeCoordinator;->getCoordinates()Landroidx/compose/ui/layout/LayoutCoordinates;
+HSPLandroidx/compose/ui/node/NodeCoordinator;->getDensity()F
+HSPLandroidx/compose/ui/node/NodeCoordinator;->getFontScale()F
+HSPLandroidx/compose/ui/node/NodeCoordinator;->getLayoutDirection()Landroidx/compose/ui/unit/LayoutDirection;
+HSPLandroidx/compose/ui/node/NodeCoordinator;->getLayoutNode()Landroidx/compose/ui/node/LayoutNode;
+HSPLandroidx/compose/ui/node/NodeCoordinator;->getMeasureResult$ui_release()Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/ui/node/NodeCoordinator;->getParent()Landroidx/compose/ui/node/LookaheadCapablePlaceable;
+HSPLandroidx/compose/ui/node/NodeCoordinator;->getParentData()Ljava/lang/Object;
+HSPLandroidx/compose/ui/node/NodeCoordinator;->getParentLayoutCoordinates()Landroidx/compose/ui/layout/LayoutCoordinates;
+HSPLandroidx/compose/ui/node/NodeCoordinator;->getSize-YbymL2g()J
+HSPLandroidx/compose/ui/node/NodeCoordinator;->head-H91voCI(I)Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/ui/node/NodeCoordinator;->headNode(Z)Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/ui/node/NodeCoordinator;->invalidateLayer()V
+HSPLandroidx/compose/ui/node/NodeCoordinator;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/node/NodeCoordinator;->isAttached()Z
+HSPLandroidx/compose/ui/node/NodeCoordinator;->isValidOwnerScope()Z
+HSPLandroidx/compose/ui/node/NodeCoordinator;->localBoundingBoxOf(Landroidx/compose/ui/layout/LayoutCoordinates;Z)Landroidx/compose/ui/geometry/Rect;
+HSPLandroidx/compose/ui/node/NodeCoordinator;->localToRoot-MK-Hz9U(J)J
+HSPLandroidx/compose/ui/node/NodeCoordinator;->onCoordinatesUsed$ui_release()V
+HSPLandroidx/compose/ui/node/NodeCoordinator;->onMeasured()V
+HSPLandroidx/compose/ui/node/NodeCoordinator;->onPlaced()V
+HSPLandroidx/compose/ui/node/NodeCoordinator;->placeSelf-f8xVGno(JFLkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/node/NodeCoordinator;->rectInParent$ui_release(Landroidx/compose/ui/geometry/MutableRect;ZZ)V
+HSPLandroidx/compose/ui/node/NodeCoordinator;->setMeasureResult$ui_release(Landroidx/compose/ui/layout/MeasureResult;)V
+HSPLandroidx/compose/ui/node/NodeCoordinator;->toParentPosition-MK-Hz9U(J)J
+HSPLandroidx/compose/ui/node/NodeCoordinator;->updateLayerParameters(Z)V
+HSPLandroidx/compose/ui/node/NodeMeasuringIntrinsics$IntrinsicMinMax;-><clinit>()V
+HSPLandroidx/compose/ui/node/NodeMeasuringIntrinsics$IntrinsicMinMax;-><init>(ILjava/lang/String;)V
+HSPLandroidx/compose/ui/node/NodeMeasuringIntrinsics$IntrinsicWidthHeight;-><clinit>()V
+HSPLandroidx/compose/ui/node/NodeMeasuringIntrinsics$IntrinsicWidthHeight;-><init>(ILjava/lang/String;)V
+HSPLandroidx/compose/ui/node/ObserverNodeOwnerScope;-><init>(Landroidx/compose/ui/node/ObserverModifierNode;)V
+HSPLandroidx/compose/ui/node/ObserverNodeOwnerScope;->isValidOwnerScope()Z
+HSPLandroidx/compose/ui/node/OnPositionedDispatcher$Companion$DepthComparator;-><clinit>()V
+HSPLandroidx/compose/ui/node/OnPositionedDispatcher$Companion$DepthComparator;->compare(Ljava/lang/Object;Ljava/lang/Object;)I
+HSPLandroidx/compose/ui/node/OnPositionedDispatcher;-><init>()V
+HSPLandroidx/compose/ui/node/OnPositionedDispatcher;->dispatchHierarchy(Landroidx/compose/ui/node/LayoutNode;)V
+HSPLandroidx/compose/ui/node/Owner;->measureAndLayout$default(Landroidx/compose/ui/node/Owner;)V
+HSPLandroidx/compose/ui/node/OwnerSnapshotObserver;-><init>(Landroidx/compose/ui/platform/AndroidComposeView$focusOwner$1;)V
+HSPLandroidx/compose/ui/node/OwnerSnapshotObserver;->observeLayoutModifierSnapshotReads$ui_release(Landroidx/compose/ui/node/LayoutNode;ZLkotlin/jvm/functions/Function0;)V
+HSPLandroidx/compose/ui/node/OwnerSnapshotObserver;->observeReads$ui_release(Landroidx/compose/ui/node/OwnerScope;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;)V
+HSPLandroidx/compose/ui/node/TailModifierNode;-><init>()V
+HSPLandroidx/compose/ui/node/TailModifierNode;->onAttach()V
+HSPLandroidx/compose/ui/node/TailModifierNode;->onDetach()V
+HSPLandroidx/compose/ui/node/UiApplier;-><init>(Landroidx/compose/ui/node/LayoutNode;)V
+HSPLandroidx/compose/ui/node/UiApplier;->down(Ljava/lang/Object;)V
+HSPLandroidx/compose/ui/node/UiApplier;->getCurrent()Ljava/lang/Object;
+HSPLandroidx/compose/ui/node/UiApplier;->insertBottomUp(ILjava/lang/Object;)V
+HSPLandroidx/compose/ui/node/UiApplier;->insertTopDown(ILjava/lang/Object;)V
+HSPLandroidx/compose/ui/node/UiApplier;->onEndChanges()V
+HSPLandroidx/compose/ui/node/UiApplier;->up()V
+HSPLandroidx/compose/ui/platform/AbstractComposeView;-><init>(Landroid/content/Context;Landroid/util/AttributeSet;I)V
+HSPLandroidx/compose/ui/platform/AbstractComposeView;->addView(Landroid/view/View;ILandroid/view/ViewGroup$LayoutParams;)V
+HSPLandroidx/compose/ui/platform/AbstractComposeView;->addView(Landroid/view/View;Landroid/view/ViewGroup$LayoutParams;)V
+HSPLandroidx/compose/ui/platform/AbstractComposeView;->checkAddView()V
+HSPLandroidx/compose/ui/platform/AbstractComposeView;->ensureCompositionCreated()V
+HSPLandroidx/compose/ui/platform/AbstractComposeView;->isAlive(Landroidx/compose/runtime/CompositionContext;)Z
+HSPLandroidx/compose/ui/platform/AbstractComposeView;->onAttachedToWindow()V
+HSPLandroidx/compose/ui/platform/AbstractComposeView;->onLayout(ZIIII)V
+HSPLandroidx/compose/ui/platform/AbstractComposeView;->onMeasure(II)V
+HSPLandroidx/compose/ui/platform/AbstractComposeView;->onRtlPropertiesChanged(I)V
+HSPLandroidx/compose/ui/platform/AbstractComposeView;->resolveParentCompositionContext()Landroidx/compose/runtime/CompositionContext;
+HSPLandroidx/compose/ui/platform/AbstractComposeView;->setParentCompositionContext(Landroidx/compose/runtime/CompositionContext;)V
+HSPLandroidx/compose/ui/platform/AbstractComposeView;->setParentContext(Landroidx/compose/runtime/CompositionContext;)V
+HSPLandroidx/compose/ui/platform/AbstractComposeView;->setPreviousAttachedWindowToken(Landroid/os/IBinder;)V
+HSPLandroidx/compose/ui/platform/AndroidAccessibilityManager;-><init>(Landroid/content/Context;)V
+HSPLandroidx/compose/ui/platform/AndroidClipboardManager;-><init>(Landroid/content/Context;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView$$ExternalSyntheticApiModelOutline0;->m(Landroid/content/res/Configuration;)I
+HSPLandroidx/compose/ui/platform/AndroidComposeView$$ExternalSyntheticApiModelOutline0;->m(Landroid/graphics/RenderNode;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView$$ExternalSyntheticApiModelOutline0;->m(Landroid/view/View;Landroid/view/translation/ViewTranslationCallback;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView$$ExternalSyntheticLambda1;-><init>(Landroidx/compose/ui/platform/AndroidComposeView;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView$$ExternalSyntheticLambda1;->onGlobalLayout()V
+HSPLandroidx/compose/ui/platform/AndroidComposeView$$ExternalSyntheticLambda2;-><init>(Landroidx/compose/ui/platform/AndroidComposeView;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView$$ExternalSyntheticLambda3;-><init>(Landroidx/compose/ui/platform/AndroidComposeView;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView$$ExternalSyntheticLambda3;->onTouchModeChanged(Z)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView$ViewTreeOwners;-><init>(Landroidx/lifecycle/LifecycleOwner;Landroidx/savedstate/SavedStateRegistryOwner;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView$focusOwner$1;-><init>(Landroidx/compose/ui/platform/AndroidComposeView;I)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView$focusOwner$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/platform/AndroidComposeView$focusOwner$1;->invoke(Lkotlin/jvm/functions/Function0;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView$pointerIconService$1;-><init>(Landroidx/compose/ui/platform/AndroidComposeView;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView$viewTreeOwners$2;-><init>(Landroidx/compose/ui/platform/AndroidComposeView;I)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView$viewTreeOwners$2;->invoke()Ljava/lang/Object;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;-><clinit>()V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;-><init>(Landroid/content/Context;Lkotlin/coroutines/CoroutineContext;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->access$get_viewTreeOwners(Landroidx/compose/ui/platform/AndroidComposeView;)Landroidx/compose/ui/platform/AndroidComposeView$ViewTreeOwners;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->convertMeasureSpec-I7RO_PI(I)J
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->dispatchDraw(Landroid/graphics/Canvas;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->dispatchKeyEvent(Landroid/view/KeyEvent;)Z
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->dispatchKeyEventPreIme(Landroid/view/KeyEvent;)Z
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->forceMeasureTheSubtree(Landroidx/compose/ui/node/LayoutNode;Z)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getAccessibilityManager()Landroidx/compose/ui/platform/AccessibilityManager;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getAccessibilityManager()Landroidx/compose/ui/platform/AndroidAccessibilityManager;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getAutofill()Landroidx/compose/ui/autofill/Autofill;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getAutofillTree()Landroidx/compose/ui/autofill/AutofillTree;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getClipboardManager()Landroidx/compose/ui/platform/AndroidClipboardManager;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getClipboardManager()Landroidx/compose/ui/platform/ClipboardManager;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getCoroutineContext()Lkotlin/coroutines/CoroutineContext;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getDensity()Landroidx/compose/ui/unit/Density;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getFocusOwner()Landroidx/compose/ui/focus/FocusOwner;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getFontFamilyResolver()Landroidx/compose/ui/text/font/FontFamily$Resolver;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getFontLoader()Landroidx/compose/ui/text/font/Font$ResourceLoader;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getHapticFeedBack()Landroidx/compose/ui/hapticfeedback/HapticFeedback;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getInputModeManager()Landroidx/compose/ui/input/InputModeManager;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getLayoutDirection()Landroidx/compose/ui/unit/LayoutDirection;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getPointerIconService()Landroidx/compose/ui/input/pointer/PointerIconService;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getRoot()Landroidx/compose/ui/node/LayoutNode;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getSemanticsOwner()Landroidx/compose/ui/semantics/SemanticsOwner;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getSharedDrawScope()Landroidx/compose/ui/node/LayoutNodeDrawScope;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getShowLayoutBounds()Z
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getSnapshotObserver()Landroidx/compose/ui/node/OwnerSnapshotObserver;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getSoftwareKeyboardController()Landroidx/compose/ui/platform/SoftwareKeyboardController;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getTextInputService()Landroidx/compose/ui/text/input/TextInputService;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getTextToolbar()Landroidx/compose/ui/platform/TextToolbar;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getView()Landroid/view/View;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getViewConfiguration()Landroidx/compose/ui/platform/ViewConfiguration;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getViewTreeOwners()Landroidx/compose/ui/platform/AndroidComposeView$ViewTreeOwners;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getWindowInfo()Landroidx/compose/ui/platform/WindowInfo;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->get_viewTreeOwners()Landroidx/compose/ui/platform/AndroidComposeView$ViewTreeOwners;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->invalidateLayers(Landroidx/compose/ui/node/LayoutNode;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->invalidateLayoutNodeMeasurement(Landroidx/compose/ui/node/LayoutNode;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->measureAndLayout(Z)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->measureAndLayout-0kLqBqw(Landroidx/compose/ui/node/LayoutNode;J)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->notifyLayerIsDirty$ui_release(Landroidx/compose/ui/node/OwnedLayer;Z)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->onAttachedToWindow()V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->onCheckIsTextEditor()Z
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->onCreateInputConnection(Landroid/view/inputmethod/EditorInfo;)Landroid/view/inputmethod/InputConnection;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->onDraw(Landroid/graphics/Canvas;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->onEndApplyChanges()V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->onFocusChanged(ZILandroid/graphics/Rect;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->onLayout(ZIIII)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->onLayoutChange(Landroidx/compose/ui/node/LayoutNode;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->onMeasure(II)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->onRequestRelayout(Landroidx/compose/ui/node/LayoutNode;ZZ)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->onResume(Landroidx/lifecycle/LifecycleOwner;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->onRtlPropertiesChanged(I)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->onSemanticsChange()V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->onWindowFocusChanged(Z)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->pack-ZIaKswc(II)J
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->recycle$ui_release(Landroidx/compose/ui/node/OwnedLayer;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->scheduleMeasureAndLayout(Landroidx/compose/ui/node/LayoutNode;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->setConfigurationChangeObserver(Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->setLayoutDirection(Landroidx/compose/ui/unit/LayoutDirection;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->setOnViewTreeOwnersAvailable(Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->setShowLayoutBounds(Z)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->set_viewTreeOwners(Landroidx/compose/ui/platform/AndroidComposeView$ViewTreeOwners;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->updatePositionCacheAndDispatch()V
+HSPLandroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat$$ExternalSyntheticLambda1;-><init>(Landroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat$$ExternalSyntheticLambda2;-><init>(Landroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat$1;-><init>(ILjava/lang/Object;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat$1;->onViewAttachedToWindow(Landroid/view/View;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat$MyNodeProvider;-><init>(Landroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat$SemanticsNodeCopy;-><init>(Landroidx/compose/ui/semantics/SemanticsNode;Ljava/util/Map;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat$boundsUpdatesEventLoop$1;-><init>(Landroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat;-><clinit>()V
+HSPLandroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat;-><init>(Landroidx/compose/ui/platform/AndroidComposeView;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat;->boundsUpdatesEventLoop(Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat;->getAccessibilityNodeProvider(Landroid/view/View;)Landroidx/core/view/accessibility/AccessibilityNodeProviderCompat;
+HSPLandroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat;->isEnabledForAccessibility()Z
+HSPLandroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat;->onStart(Landroidx/lifecycle/LifecycleOwner;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeViewForceDarkModeQ;-><clinit>()V
+HSPLandroidx/compose/ui/platform/AndroidComposeViewForceDarkModeQ;->disallowForceDark(Landroid/view/View;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeViewTranslationCallbackS;-><clinit>()V
+HSPLandroidx/compose/ui/platform/AndroidComposeViewTranslationCallbackS;->setViewTranslationCallback(Landroid/view/View;Landroid/view/translation/ViewTranslationCallback;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeViewVerificationHelperMethodsO;-><clinit>()V
+HSPLandroidx/compose/ui/platform/AndroidComposeViewVerificationHelperMethodsO;->focusable(Landroid/view/View;IZ)V
+HSPLandroidx/compose/ui/platform/AndroidCompositionLocals_androidKt$obtainImageVectorCache$callbacks$1$1;-><init>(Landroid/content/res/Configuration;Landroidx/compose/ui/res/ImageVectorCache;)V
+HSPLandroidx/compose/ui/platform/AndroidCompositionLocals_androidKt;-><clinit>()V
+HSPLandroidx/compose/ui/platform/AndroidCompositionLocals_androidKt;->ProvideAndroidCompositionLocals(Landroidx/compose/ui/platform/AndroidComposeView;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;I)V
+HSPLandroidx/compose/ui/platform/AndroidUiDispatcher$dispatchCallback$1;-><init>(Landroidx/compose/ui/platform/AndroidUiDispatcher;)V
+HSPLandroidx/compose/ui/platform/AndroidUiDispatcher$dispatchCallback$1;->doFrame(J)V
+HSPLandroidx/compose/ui/platform/AndroidUiDispatcher$dispatchCallback$1;->run()V
+HSPLandroidx/compose/ui/platform/AndroidUiDispatcher;-><clinit>()V
+HSPLandroidx/compose/ui/platform/AndroidUiDispatcher;-><init>(Landroid/view/Choreographer;Landroid/os/Handler;)V
+HSPLandroidx/compose/ui/platform/AndroidUiDispatcher;->access$performTrampolineDispatch(Landroidx/compose/ui/platform/AndroidUiDispatcher;)V
+HSPLandroidx/compose/ui/platform/AndroidUiDispatcher;->dispatch(Lkotlin/coroutines/CoroutineContext;Ljava/lang/Runnable;)V
+HSPLandroidx/compose/ui/platform/AndroidUiFrameClock$withFrameNanos$2$callback$1;-><init>(Lkotlinx/coroutines/CancellableContinuationImpl;Landroidx/compose/ui/platform/AndroidUiFrameClock;Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/platform/AndroidUiFrameClock$withFrameNanos$2$callback$1;->doFrame(J)V
+HSPLandroidx/compose/ui/platform/AndroidUiFrameClock;-><init>(Landroid/view/Choreographer;Landroidx/compose/ui/platform/AndroidUiDispatcher;)V
+HSPLandroidx/compose/ui/platform/AndroidUiFrameClock;->fold(Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/platform/AndroidUiFrameClock;->get(Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element;
+HSPLandroidx/compose/ui/platform/AndroidUiFrameClock;->minusKey(Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext;
+HSPLandroidx/compose/ui/platform/AndroidUiFrameClock;->withFrameNanos(Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/platform/AndroidViewConfiguration;-><init>(Landroid/view/ViewConfiguration;)V
+HSPLandroidx/compose/ui/platform/CalculateMatrixToWindowApi29;-><init>()V
+HSPLandroidx/compose/ui/platform/ComposableSingletons$Wrapper_androidKt;-><clinit>()V
+HSPLandroidx/compose/ui/platform/ComposeView;-><init>(Landroid/content/Context;)V
+HSPLandroidx/compose/ui/platform/ComposeView;->Content(Landroidx/compose/runtime/Composer;I)V
+HSPLandroidx/compose/ui/platform/ComposeView;->getShouldCreateCompositionOnAttachedToWindow()Z
+HSPLandroidx/compose/ui/platform/ComposeView;->setContent(Lkotlin/jvm/functions/Function2;)V
+HSPLandroidx/compose/ui/platform/CompositionLocalsKt;-><clinit>()V
+HSPLandroidx/compose/ui/platform/CompositionLocalsKt;->ProvideCommonCompositionLocals(Landroidx/compose/ui/node/Owner;Landroidx/compose/ui/platform/UriHandler;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;I)V
+HSPLandroidx/compose/ui/platform/DisposableSaveableStateRegistry;-><init>(Landroidx/compose/runtime/saveable/SaveableStateRegistryImpl;Landroidx/compose/ui/platform/DisposableSaveableStateRegistry_androidKt$DisposableSaveableStateRegistry$1;)V
+HSPLandroidx/compose/ui/platform/DisposableSaveableStateRegistry;->canBeSaved(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/platform/DisposableSaveableStateRegistry;->consumeRestored(Ljava/lang/String;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/platform/DisposableSaveableStateRegistry;->registerProvider(Ljava/lang/String;Lkotlin/jvm/functions/Function0;)Landroidx/compose/runtime/saveable/SaveableStateRegistryImpl$registerProvider$3;
+HSPLandroidx/compose/ui/platform/DisposableSaveableStateRegistry_androidKt$DisposableSaveableStateRegistry$1;-><init>(ZLandroidx/savedstate/SavedStateRegistry;Ljava/lang/String;)V
+HSPLandroidx/compose/ui/platform/GlobalSnapshotManager$ensureStarted$1;-><init>(Lkotlinx/coroutines/channels/Channel;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/ui/platform/GlobalSnapshotManager$ensureStarted$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/ui/platform/GlobalSnapshotManager$ensureStarted$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/platform/GlobalSnapshotManager;-><clinit>()V
+HSPLandroidx/compose/ui/platform/InspectableModifier;-><init>()V
+HSPLandroidx/compose/ui/platform/LayerMatrixCache;-><init>(Landroidx/compose/ui/text/SaversKt$ColorSaver$1;)V
+HSPLandroidx/compose/ui/platform/LayerMatrixCache;->calculateMatrix-GrdbGEg(Ljava/lang/Object;)[F
+HSPLandroidx/compose/ui/platform/LayerMatrixCache;->invalidate()V
+HSPLandroidx/compose/ui/platform/MotionDurationScaleImpl;-><init>()V
+HSPLandroidx/compose/ui/platform/MotionDurationScaleImpl;->fold(Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/platform/MotionDurationScaleImpl;->get(Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element;
+HSPLandroidx/compose/ui/platform/MotionDurationScaleImpl;->getScaleFactor()F
+HSPLandroidx/compose/ui/platform/MotionDurationScaleImpl;->minusKey(Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext;
+HSPLandroidx/compose/ui/platform/OutlineResolver;-><init>(Landroidx/compose/ui/unit/Density;)V
+HSPLandroidx/compose/ui/platform/OutlineResolver;->getOutline()Landroid/graphics/Outline;
+HSPLandroidx/compose/ui/platform/OutlineResolver;->update(Landroidx/compose/ui/graphics/Shape;FZFLandroidx/compose/ui/unit/LayoutDirection;Landroidx/compose/ui/unit/Density;)Z
+HSPLandroidx/compose/ui/platform/OutlineResolver;->updateCache()V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;-><init>()V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->discardDisplayList()V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->drawInto(Landroid/graphics/Canvas;)V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->getAlpha()F
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->getClipToOutline()Z
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->getElevation()F
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->getHasDisplayList()Z
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->getHeight()I
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->getLeft()I
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->getMatrix(Landroid/graphics/Matrix;)V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->getTop()I
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->getWidth()I
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->offsetLeftAndRight(I)V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->offsetTopAndBottom(I)V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->setAlpha(F)V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->setAmbientShadowColor(I)V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->setCameraDistance(F)V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->setClipToBounds(Z)V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->setClipToOutline(Z)V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->setCompositingStrategy-aDBOjCE(I)V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->setElevation(F)V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->setHasOverlappingRendering()Z
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->setOutline(Landroid/graphics/Outline;)V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->setPivotX(F)V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->setPivotY(F)V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->setPosition(IIII)Z
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->setRenderEffect()V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->setRotationX(F)V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->setRotationY(F)V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->setRotationZ(F)V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->setScaleX(F)V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->setScaleY(F)V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->setSpotShadowColor(I)V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->setTranslationX(F)V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->setTranslationY(F)V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29VerificationHelper;-><clinit>()V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29VerificationHelper;->setRenderEffect(Landroid/graphics/RenderNode;Landroidx/compose/ui/graphics/RenderEffect;)V
+HSPLandroidx/compose/ui/platform/RenderNodeLayer;-><init>(Landroidx/compose/ui/platform/AndroidComposeView;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/node/LayoutNode$_foldedChildren$1;)V
+HSPLandroidx/compose/ui/platform/RenderNodeLayer;->destroy()V
+HSPLandroidx/compose/ui/platform/RenderNodeLayer;->drawLayer(Landroidx/compose/ui/graphics/Canvas;)V
+HSPLandroidx/compose/ui/platform/RenderNodeLayer;->invalidate()V
+HSPLandroidx/compose/ui/platform/RenderNodeLayer;->mapBounds(Landroidx/compose/ui/geometry/MutableRect;Z)V
+HSPLandroidx/compose/ui/platform/RenderNodeLayer;->mapOffset-8S9VItk(JZ)J
+HSPLandroidx/compose/ui/platform/RenderNodeLayer;->move--gyyYBs(J)V
+HSPLandroidx/compose/ui/platform/RenderNodeLayer;->resize-ozmzZPI(J)V
+HSPLandroidx/compose/ui/platform/RenderNodeLayer;->reuseLayer(Landroidx/compose/ui/node/LayoutNode$_foldedChildren$1;Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/platform/RenderNodeLayer;->setDirty(Z)V
+HSPLandroidx/compose/ui/platform/RenderNodeLayer;->updateDisplayList()V
+HSPLandroidx/compose/ui/platform/RenderNodeLayer;->updateLayerProperties-dDxr-wY(FFFFFFFFFFJLandroidx/compose/ui/graphics/Shape;ZJJILandroidx/compose/ui/unit/LayoutDirection;Landroidx/compose/ui/unit/Density;)V
+HSPLandroidx/compose/ui/platform/ViewLayer;-><clinit>()V
+HSPLandroidx/compose/ui/platform/WeakCache;-><init>()V
+HSPLandroidx/compose/ui/platform/WeakCache;-><init>(I)V
+HSPLandroidx/compose/ui/platform/WeakCache;-><init>(Landroidx/compose/ui/node/InnerNodeCoordinator;)V
+HSPLandroidx/compose/ui/platform/WeakCache;-><init>(Ljava/lang/Object;ILjava/lang/Object;)V
+HSPLandroidx/compose/ui/platform/WeakCache;->add(Landroidx/compose/ui/node/LayoutNode;Z)V
+HSPLandroidx/compose/ui/platform/WeakCache;->clearWeakReferences()V
+HSPLandroidx/compose/ui/platform/WeakCache;->get$1()Ljava/lang/Object;
+HSPLandroidx/compose/ui/platform/WeakCache;->set(Ljava/lang/Object;)V
+HSPLandroidx/compose/ui/platform/WindowInfoImpl;-><clinit>()V
+HSPLandroidx/compose/ui/platform/WindowInfoImpl;-><init>()V
+HSPLandroidx/compose/ui/platform/WindowRecomposerPolicy$createAndInstallWindowRecomposer$unsetJob$1;-><init>(Landroidx/compose/runtime/Recomposer;Landroid/view/View;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/ui/platform/WindowRecomposerPolicy$createAndInstallWindowRecomposer$unsetJob$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/ui/platform/WindowRecomposerPolicy$createAndInstallWindowRecomposer$unsetJob$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/platform/WindowRecomposerPolicy;-><clinit>()V
+HSPLandroidx/compose/ui/platform/WindowRecomposer_androidKt$createLifecycleAwareWindowRecomposer$1;-><init>(Landroid/view/View;Landroidx/compose/runtime/Recomposer;)V
+HSPLandroidx/compose/ui/platform/WindowRecomposer_androidKt$createLifecycleAwareWindowRecomposer$1;->onViewAttachedToWindow(Landroid/view/View;)V
+HSPLandroidx/compose/ui/platform/WindowRecomposer_androidKt$createLifecycleAwareWindowRecomposer$2$WhenMappings;-><clinit>()V
+HSPLandroidx/compose/ui/platform/WindowRecomposer_androidKt$createLifecycleAwareWindowRecomposer$2$onStateChanged$1$1$1;-><init>(Lkotlinx/coroutines/flow/StateFlow;Landroidx/compose/ui/platform/MotionDurationScaleImpl;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/ui/platform/WindowRecomposer_androidKt$createLifecycleAwareWindowRecomposer$2$onStateChanged$1$1$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/ui/platform/WindowRecomposer_androidKt$createLifecycleAwareWindowRecomposer$2$onStateChanged$1$1$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/platform/WindowRecomposer_androidKt$createLifecycleAwareWindowRecomposer$2$onStateChanged$1;-><init>(Lkotlin/jvm/internal/Ref$ObjectRef;Landroidx/compose/runtime/Recomposer;Landroidx/lifecycle/LifecycleOwner;Landroidx/compose/ui/platform/WindowRecomposer_androidKt$createLifecycleAwareWindowRecomposer$2;Landroid/view/View;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/ui/platform/WindowRecomposer_androidKt$createLifecycleAwareWindowRecomposer$2$onStateChanged$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/ui/platform/WindowRecomposer_androidKt$createLifecycleAwareWindowRecomposer$2$onStateChanged$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/platform/WindowRecomposer_androidKt$createLifecycleAwareWindowRecomposer$2$onStateChanged$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/platform/WindowRecomposer_androidKt$createLifecycleAwareWindowRecomposer$2;-><init>(Lkotlinx/coroutines/internal/ContextScope;Landroidx/compose/runtime/PausableMonotonicFrameClock;Landroidx/compose/runtime/Recomposer;Lkotlin/jvm/internal/Ref$ObjectRef;Landroid/view/View;)V
+HSPLandroidx/compose/ui/platform/WindowRecomposer_androidKt$createLifecycleAwareWindowRecomposer$2;->onStateChanged(Landroidx/lifecycle/LifecycleOwner;Landroidx/lifecycle/Lifecycle$Event;)V
+HSPLandroidx/compose/ui/platform/WindowRecomposer_androidKt$getAnimationScaleFlowFor$1$1$1;-><init>(Landroid/content/ContentResolver;Landroid/net/Uri;Landroidx/emoji2/text/FontRequestEmojiCompatConfig$FontRequestMetadataLoader$1;Lkotlinx/coroutines/channels/Channel;Landroid/content/Context;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/ui/platform/WindowRecomposer_androidKt$getAnimationScaleFlowFor$1$1$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/ui/platform/WindowRecomposer_androidKt$getAnimationScaleFlowFor$1$1$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/platform/WindowRecomposer_androidKt$getAnimationScaleFlowFor$1$1$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/platform/WindowRecomposer_androidKt;-><clinit>()V
+HSPLandroidx/compose/ui/platform/WindowRecomposer_androidKt;->access$getAnimationScaleFlowFor(Landroid/content/Context;)Lkotlinx/coroutines/flow/StateFlow;
+HSPLandroidx/compose/ui/platform/WindowRecomposer_androidKt;->getCompositionContext(Landroid/view/View;)Landroidx/compose/runtime/CompositionContext;
+HSPLandroidx/compose/ui/platform/WrappedComposition$setContent$1$1$1;-><init>(Landroidx/compose/ui/platform/WrappedComposition;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/ui/platform/WrappedComposition$setContent$1$1$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/ui/platform/WrappedComposition$setContent$1$1$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/platform/WrappedComposition$setContent$1$1;-><init>(Landroidx/compose/ui/platform/WrappedComposition;Lkotlin/jvm/functions/Function2;I)V
+HSPLandroidx/compose/ui/platform/WrappedComposition$setContent$1$1;->invoke(Landroidx/compose/runtime/Composer;I)V
+HSPLandroidx/compose/ui/platform/WrappedComposition$setContent$1$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/platform/WrappedComposition$setContent$1;-><init>(Ljava/lang/Object;ILjava/lang/Object;)V
+HSPLandroidx/compose/ui/platform/WrappedComposition$setContent$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/platform/WrappedComposition;-><init>(Landroidx/compose/ui/platform/AndroidComposeView;Landroidx/compose/runtime/CompositionImpl;)V
+HSPLandroidx/compose/ui/platform/WrappedComposition;->onStateChanged(Landroidx/lifecycle/LifecycleOwner;Landroidx/lifecycle/Lifecycle$Event;)V
+HSPLandroidx/compose/ui/platform/WrappedComposition;->setContent(Lkotlin/jvm/functions/Function2;)V
+HSPLandroidx/compose/ui/platform/WrapperRenderNodeLayerHelperMethods;-><clinit>()V
+HSPLandroidx/compose/ui/platform/WrapperRenderNodeLayerHelperMethods;->onDescendantInvalidated(Landroidx/compose/ui/platform/AndroidComposeView;)V
+HSPLandroidx/compose/ui/platform/WrapperVerificationHelperMethods;-><clinit>()V
+HSPLandroidx/compose/ui/platform/WrapperVerificationHelperMethods;->attributeSourceResourceMap(Landroid/view/View;)Ljava/util/Map;
+HSPLandroidx/compose/ui/platform/Wrapper_androidKt;-><clinit>()V
+HSPLandroidx/compose/ui/platform/Wrapper_androidKt;->setContent(Landroidx/compose/ui/platform/AbstractComposeView;Landroidx/compose/runtime/CompositionContext;Landroidx/compose/runtime/internal/ComposableLambdaImpl;)Landroidx/compose/runtime/Composition;
+HSPLandroidx/compose/ui/res/ImageVectorCache;-><init>()V
+HSPLandroidx/compose/ui/semantics/AppendedSemanticsElement;-><init>(Lkotlin/jvm/functions/Function1;Z)V
+HSPLandroidx/compose/ui/semantics/AppendedSemanticsElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/ui/semantics/AppendedSemanticsElement;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/semantics/AppendedSemanticsElement;->update(Landroidx/compose/ui/Modifier$Node;)V
+HSPLandroidx/compose/ui/semantics/CollectionInfo;-><init>(II)V
+HSPLandroidx/compose/ui/semantics/CoreSemanticsModifierNode;-><init>(ZLkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/semantics/EmptySemanticsElement;-><clinit>()V
+HSPLandroidx/compose/ui/semantics/EmptySemanticsElement;-><init>()V
+HSPLandroidx/compose/ui/semantics/EmptySemanticsElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/ui/semantics/ScrollAxisRange;-><init>(Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;Z)V
+HSPLandroidx/compose/ui/semantics/SemanticsConfiguration;-><init>()V
+HSPLandroidx/compose/ui/semantics/SemanticsConfiguration;->contains(Landroidx/compose/ui/semantics/SemanticsPropertyKey;)Z
+HSPLandroidx/compose/ui/semantics/SemanticsModifierKt;-><clinit>()V
+HSPLandroidx/compose/ui/semantics/SemanticsNode;-><init>(Landroidx/compose/ui/Modifier$Node;ZLandroidx/compose/ui/node/LayoutNode;Landroidx/compose/ui/semantics/SemanticsConfiguration;)V
+HSPLandroidx/compose/ui/semantics/SemanticsNode;->fillOneLayerOfSemanticsWrappers(Landroidx/compose/ui/node/LayoutNode;Ljava/util/ArrayList;)V
+HSPLandroidx/compose/ui/semantics/SemanticsNode;->getChildren(ZZ)Ljava/util/List;
+HSPLandroidx/compose/ui/semantics/SemanticsNode;->getReplacedChildren$ui_release()Ljava/util/List;
+HSPLandroidx/compose/ui/semantics/SemanticsNode;->isMergingSemanticsOfDescendants()Z
+HSPLandroidx/compose/ui/semantics/SemanticsNode;->unmergedChildren$ui_release(Z)Ljava/util/List;
+HSPLandroidx/compose/ui/semantics/SemanticsOwner;-><init>(Landroidx/compose/ui/node/LayoutNode;)V
+HSPLandroidx/compose/ui/semantics/SemanticsOwner;->getUnmergedRootSemanticsNode()Landroidx/compose/ui/semantics/SemanticsNode;
+HSPLandroidx/compose/ui/semantics/SemanticsProperties;-><clinit>()V
+HSPLandroidx/compose/ui/semantics/SemanticsPropertyKey;-><init>(Ljava/lang/String;)V
+HSPLandroidx/compose/ui/semantics/SemanticsPropertyKey;-><init>(Ljava/lang/String;Lkotlin/jvm/functions/Function2;)V
+HSPLandroidx/compose/ui/text/AndroidParagraph;-><init>(Landroidx/compose/ui/text/platform/AndroidParagraphIntrinsics;IZJ)V
+HSPLandroidx/compose/ui/text/AndroidParagraph;->constructTextLayout(IILandroid/text/TextUtils$TruncateAt;IIIII)Landroidx/compose/ui/text/android/TextLayout;
+HSPLandroidx/compose/ui/text/AndroidParagraph;->getHeight()F
+HSPLandroidx/compose/ui/text/AndroidParagraph;->getWidth()F
+HSPLandroidx/compose/ui/text/AndroidParagraph;->paint(Landroidx/compose/ui/graphics/Canvas;)V
+HSPLandroidx/compose/ui/text/AndroidParagraph;->paint-LG529CI(Landroidx/compose/ui/graphics/Canvas;JLandroidx/compose/ui/graphics/Shadow;Landroidx/compose/ui/text/style/TextDecoration;Lkotlin/ResultKt;I)V
+HSPLandroidx/compose/ui/text/AnnotatedString$Range;-><init>(IILjava/lang/Object;)V
+HSPLandroidx/compose/ui/text/AnnotatedString$Range;-><init>(Ljava/lang/Object;IILjava/lang/String;)V
+HSPLandroidx/compose/ui/text/AnnotatedString;-><init>(Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;)V
+HSPLandroidx/compose/ui/text/AnnotatedString;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/text/AnnotatedStringKt;-><clinit>()V
+HSPLandroidx/compose/ui/text/EmojiSupportMatch;-><init>(I)V
+HSPLandroidx/compose/ui/text/MultiParagraph;->paint-LG529CI$default(Landroidx/compose/ui/text/MultiParagraph;Landroidx/compose/ui/graphics/Canvas;JLandroidx/compose/ui/graphics/Shadow;Landroidx/compose/ui/text/style/TextDecoration;Lkotlin/ResultKt;)V
+HSPLandroidx/compose/ui/text/MultiParagraphIntrinsics$maxIntrinsicWidth$2;-><init>(Landroidx/compose/ui/text/MultiParagraphIntrinsics;I)V
+HSPLandroidx/compose/ui/text/MultiParagraphIntrinsics$maxIntrinsicWidth$2;->invoke$1()Ljava/lang/Float;
+HSPLandroidx/compose/ui/text/MultiParagraphIntrinsics$maxIntrinsicWidth$2;->invoke()Ljava/lang/Object;
+HSPLandroidx/compose/ui/text/MultiParagraphIntrinsics;-><init>(Landroidx/compose/ui/text/AnnotatedString;Landroidx/compose/ui/text/TextStyle;Ljava/util/List;Landroidx/compose/ui/unit/Density;Landroidx/compose/ui/text/font/FontFamily$Resolver;)V
+HSPLandroidx/compose/ui/text/MultiParagraphIntrinsics;->getHasStaleResolvedFonts()Z
+HSPLandroidx/compose/ui/text/MultiParagraphIntrinsics;->getMaxIntrinsicWidth()F
+HSPLandroidx/compose/ui/text/ParagraphInfo;-><init>(Landroidx/compose/ui/text/AndroidParagraph;IIIIFF)V
+HSPLandroidx/compose/ui/text/ParagraphIntrinsicInfo;-><init>(Landroidx/compose/ui/text/platform/AndroidParagraphIntrinsics;II)V
+HSPLandroidx/compose/ui/text/ParagraphStyle;-><init>(Landroidx/compose/ui/text/style/TextAlign;Landroidx/compose/ui/text/style/TextDirection;JLandroidx/compose/ui/text/style/TextIndent;Landroidx/compose/ui/text/PlatformParagraphStyle;Landroidx/compose/ui/text/style/LineHeightStyle;Landroidx/compose/ui/text/style/LineBreak;Landroidx/compose/ui/text/style/Hyphens;Landroidx/compose/ui/text/style/TextMotion;)V
+HSPLandroidx/compose/ui/text/ParagraphStyle;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/text/ParagraphStyle;->merge(Landroidx/compose/ui/text/ParagraphStyle;)Landroidx/compose/ui/text/ParagraphStyle;
+HSPLandroidx/compose/ui/text/ParagraphStyleKt;-><clinit>()V
+HSPLandroidx/compose/ui/text/ParagraphStyleKt;->fastMerge-HtYhynw(Landroidx/compose/ui/text/ParagraphStyle;Landroidx/compose/ui/text/style/TextAlign;Landroidx/compose/ui/text/style/TextDirection;JLandroidx/compose/ui/text/style/TextIndent;Landroidx/compose/ui/text/PlatformParagraphStyle;Landroidx/compose/ui/text/style/LineHeightStyle;Landroidx/compose/ui/text/style/LineBreak;Landroidx/compose/ui/text/style/Hyphens;Landroidx/compose/ui/text/style/TextMotion;)Landroidx/compose/ui/text/ParagraphStyle;
+HSPLandroidx/compose/ui/text/PlatformParagraphStyle;-><init>(I)V
+HSPLandroidx/compose/ui/text/PlatformParagraphStyle;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/text/PlatformTextStyle;-><init>(Landroidx/compose/ui/text/PlatformParagraphStyle;)V
+HSPLandroidx/compose/ui/text/SaversKt$ColorSaver$1;-><clinit>()V
+HSPLandroidx/compose/ui/text/SaversKt$ColorSaver$1;-><init>(I)V
+HSPLandroidx/compose/ui/text/SaversKt$ColorSaver$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/text/SaversKt$ColorSaver$2;-><clinit>()V
+HSPLandroidx/compose/ui/text/SaversKt$ColorSaver$2;-><init>(I)V
+HSPLandroidx/compose/ui/text/SaversKt$ColorSaver$2;->invoke(Landroidx/compose/ui/node/LayoutNode;)V
+HSPLandroidx/compose/ui/text/SaversKt$ColorSaver$2;->invoke(Ljava/lang/Object;)Ljava/lang/Boolean;
+HSPLandroidx/compose/ui/text/SaversKt$ColorSaver$2;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/text/SpanStyle;-><init>(JJLandroidx/compose/ui/text/font/FontWeight;Landroidx/compose/ui/text/font/FontStyle;Landroidx/compose/ui/text/font/FontSynthesis;Landroidx/compose/ui/text/font/FontFamily;Ljava/lang/String;JLandroidx/compose/ui/text/style/BaselineShift;Landroidx/compose/ui/text/style/TextGeometricTransform;Landroidx/compose/ui/text/intl/LocaleList;JLandroidx/compose/ui/text/style/TextDecoration;Landroidx/compose/ui/graphics/Shadow;I)V
+HSPLandroidx/compose/ui/text/SpanStyle;-><init>(JJLandroidx/compose/ui/text/font/FontWeight;Landroidx/compose/ui/text/font/FontStyle;Landroidx/compose/ui/text/font/FontSynthesis;Landroidx/compose/ui/text/font/FontFamily;Ljava/lang/String;JLandroidx/compose/ui/text/style/BaselineShift;Landroidx/compose/ui/text/style/TextGeometricTransform;Landroidx/compose/ui/text/intl/LocaleList;JLandroidx/compose/ui/text/style/TextDecoration;Landroidx/compose/ui/graphics/Shadow;Lkotlin/ResultKt;)V
+HSPLandroidx/compose/ui/text/SpanStyle;-><init>(Landroidx/compose/ui/text/style/TextForegroundStyle;JLandroidx/compose/ui/text/font/FontWeight;Landroidx/compose/ui/text/font/FontStyle;Landroidx/compose/ui/text/font/FontSynthesis;Landroidx/compose/ui/text/font/FontFamily;Ljava/lang/String;JLandroidx/compose/ui/text/style/BaselineShift;Landroidx/compose/ui/text/style/TextGeometricTransform;Landroidx/compose/ui/text/intl/LocaleList;JLandroidx/compose/ui/text/style/TextDecoration;Landroidx/compose/ui/graphics/Shadow;Lkotlin/ResultKt;)V
+HSPLandroidx/compose/ui/text/SpanStyle;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/text/SpanStyle;->getColor-0d7_KjU()J
+HSPLandroidx/compose/ui/text/SpanStyle;->hasSameLayoutAffectingAttributes$ui_text_release(Landroidx/compose/ui/text/SpanStyle;)Z
+HSPLandroidx/compose/ui/text/SpanStyle;->hasSameNonLayoutAttributes$ui_text_release(Landroidx/compose/ui/text/SpanStyle;)Z
+HSPLandroidx/compose/ui/text/SpanStyle;->merge(Landroidx/compose/ui/text/SpanStyle;)Landroidx/compose/ui/text/SpanStyle;
+HSPLandroidx/compose/ui/text/SpanStyleKt;-><clinit>()V
+HSPLandroidx/compose/ui/text/SpanStyleKt;->fastMerge-dSHsh3o(Landroidx/compose/ui/text/SpanStyle;JLandroidx/compose/ui/graphics/Brush;FJLandroidx/compose/ui/text/font/FontWeight;Landroidx/compose/ui/text/font/FontStyle;Landroidx/compose/ui/text/font/FontSynthesis;Landroidx/compose/ui/text/font/FontFamily;Ljava/lang/String;JLandroidx/compose/ui/text/style/BaselineShift;Landroidx/compose/ui/text/style/TextGeometricTransform;Landroidx/compose/ui/text/intl/LocaleList;JLandroidx/compose/ui/text/style/TextDecoration;Landroidx/compose/ui/graphics/Shadow;Lkotlin/ResultKt;)Landroidx/compose/ui/text/SpanStyle;
+HSPLandroidx/compose/ui/text/TextLayoutInput;-><init>(Landroidx/compose/ui/text/AnnotatedString;Landroidx/compose/ui/text/TextStyle;Ljava/util/List;IZILandroidx/compose/ui/unit/Density;Landroidx/compose/ui/unit/LayoutDirection;Landroidx/compose/ui/text/font/FontFamily$Resolver;J)V
+HSPLandroidx/compose/ui/text/TextLayoutResult;-><init>(Landroidx/compose/ui/text/TextLayoutInput;Landroidx/compose/ui/text/MultiParagraph;J)V
+HSPLandroidx/compose/ui/text/TextRange;-><clinit>()V
+HSPLandroidx/compose/ui/text/TextRange;->getEnd-impl(J)I
+HSPLandroidx/compose/ui/text/TextStyle;-><clinit>()V
+HSPLandroidx/compose/ui/text/TextStyle;-><init>(JJLandroidx/compose/ui/text/font/FontWeight;Landroidx/compose/ui/text/font/FontStyle;Landroidx/compose/ui/text/font/FontFamily;JLandroidx/compose/ui/text/style/TextDecoration;Landroidx/compose/ui/text/style/TextAlign;JI)V
+HSPLandroidx/compose/ui/text/TextStyle;-><init>(JLandroidx/compose/ui/text/font/FontWeight;Landroidx/compose/ui/text/font/DefaultFontFamily;I)V
+HSPLandroidx/compose/ui/text/TextStyle;-><init>(Landroidx/compose/ui/text/SpanStyle;Landroidx/compose/ui/text/ParagraphStyle;)V
+HSPLandroidx/compose/ui/text/TextStyle;-><init>(Landroidx/compose/ui/text/SpanStyle;Landroidx/compose/ui/text/ParagraphStyle;Landroidx/compose/ui/text/PlatformTextStyle;)V
+HSPLandroidx/compose/ui/text/TextStyle;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/text/TextStyle;->getColor-0d7_KjU()J
+HSPLandroidx/compose/ui/text/TextStyle;->merge(Landroidx/compose/ui/text/TextStyle;)Landroidx/compose/ui/text/TextStyle;
+HSPLandroidx/compose/ui/text/TextStyle;->merge-Z1GrekI$default(IJJJJLandroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/font/FontFamily;Landroidx/compose/ui/text/font/FontStyle;Landroidx/compose/ui/text/font/FontWeight;Landroidx/compose/ui/text/style/TextAlign;Landroidx/compose/ui/text/style/TextDecoration;)Landroidx/compose/ui/text/TextStyle;
+HSPLandroidx/compose/ui/text/android/BoringLayoutFactoryDefault;->create(Ljava/lang/CharSequence;Landroid/text/TextPaint;ILandroid/text/Layout$Alignment;FFLandroid/text/BoringLayout$Metrics;ZLandroid/text/TextUtils$TruncateAt;I)Landroid/text/BoringLayout;
+HSPLandroidx/compose/ui/text/android/BoringLayoutFactoryDefault;->isBoring(Ljava/lang/CharSequence;Landroid/text/TextPaint;Landroid/text/TextDirectionHeuristic;)Landroid/text/BoringLayout$Metrics;
+HSPLandroidx/compose/ui/text/android/LayoutIntrinsics;-><init>(Ljava/lang/CharSequence;Landroidx/compose/ui/text/platform/AndroidTextPaint;I)V
+HSPLandroidx/compose/ui/text/android/LayoutIntrinsics;->getBoringMetrics()Landroid/text/BoringLayout$Metrics;
+HSPLandroidx/compose/ui/text/android/LayoutIntrinsics;->getMaxIntrinsicWidth()F
+HSPLandroidx/compose/ui/text/android/Paint29$$ExternalSyntheticApiModelOutline0;->m$1(Landroid/graphics/RenderNode;)F
+HSPLandroidx/compose/ui/text/android/Paint29$$ExternalSyntheticApiModelOutline0;->m$1(Landroid/graphics/RenderNode;)Z
+HSPLandroidx/compose/ui/text/android/Paint29$$ExternalSyntheticApiModelOutline0;->m$1(Landroid/graphics/RenderNode;F)V
+HSPLandroidx/compose/ui/text/android/Paint29$$ExternalSyntheticApiModelOutline0;->m$2(Landroid/graphics/RenderNode;)V
+HSPLandroidx/compose/ui/text/android/Paint29$$ExternalSyntheticApiModelOutline0;->m$2(Landroid/graphics/RenderNode;F)V
+HSPLandroidx/compose/ui/text/android/Paint29$$ExternalSyntheticApiModelOutline0;->m$3(Landroid/graphics/RenderNode;)I
+HSPLandroidx/compose/ui/text/android/Paint29$$ExternalSyntheticApiModelOutline0;->m$3(Landroid/graphics/RenderNode;)V
+HSPLandroidx/compose/ui/text/android/Paint29$$ExternalSyntheticApiModelOutline0;->m()Landroid/graphics/RenderNode;
+HSPLandroidx/compose/ui/text/android/Paint29$$ExternalSyntheticApiModelOutline0;->m(Landroid/graphics/Paint;Ljava/lang/CharSequence;IILandroid/graphics/Rect;)V
+HSPLandroidx/compose/ui/text/android/Paint29$$ExternalSyntheticApiModelOutline0;->m(Landroid/graphics/RenderNode;)F
+HSPLandroidx/compose/ui/text/android/Paint29$$ExternalSyntheticApiModelOutline0;->m(Landroid/graphics/RenderNode;)I
+HSPLandroidx/compose/ui/text/android/Paint29$$ExternalSyntheticApiModelOutline0;->m(Landroid/graphics/RenderNode;)Landroid/graphics/RecordingCanvas;
+HSPLandroidx/compose/ui/text/android/Paint29$$ExternalSyntheticApiModelOutline0;->m(Landroid/graphics/RenderNode;)V
+HSPLandroidx/compose/ui/text/android/Paint29$$ExternalSyntheticApiModelOutline0;->m(Landroid/graphics/RenderNode;)Z
+HSPLandroidx/compose/ui/text/android/Paint29$$ExternalSyntheticApiModelOutline0;->m(Landroid/graphics/RenderNode;F)V
+HSPLandroidx/compose/ui/text/android/Paint29$$ExternalSyntheticApiModelOutline0;->m(Landroid/graphics/RenderNode;I)V
+HSPLandroidx/compose/ui/text/android/Paint29$$ExternalSyntheticApiModelOutline0;->m(Landroid/graphics/RenderNode;IIII)Z
+HSPLandroidx/compose/ui/text/android/Paint29$$ExternalSyntheticApiModelOutline0;->m(Landroid/graphics/RenderNode;Landroid/graphics/Matrix;)V
+HSPLandroidx/compose/ui/text/android/Paint29$$ExternalSyntheticApiModelOutline0;->m(Landroid/graphics/RenderNode;Landroid/graphics/Outline;)V
+HSPLandroidx/compose/ui/text/android/Paint29$$ExternalSyntheticApiModelOutline0;->m(Landroid/graphics/RenderNode;Z)V
+HSPLandroidx/compose/ui/text/android/Paint29$$ExternalSyntheticApiModelOutline0;->m(Landroid/view/View;)Ljava/util/Map;
+HSPLandroidx/compose/ui/text/android/Paint29;->getTextBounds(Landroid/graphics/Paint;Ljava/lang/CharSequence;IILandroid/graphics/Rect;)V
+HSPLandroidx/compose/ui/text/android/StaticLayoutFactory23;->create(Landroidx/compose/ui/text/android/StaticLayoutParams;)Landroid/text/StaticLayout;
+HSPLandroidx/compose/ui/text/android/StaticLayoutFactory26;->setJustificationMode(Landroid/text/StaticLayout$Builder;I)V
+HSPLandroidx/compose/ui/text/android/StaticLayoutFactory28;->setUseLineSpacingFromFallbacks(Landroid/text/StaticLayout$Builder;Z)V
+HSPLandroidx/compose/ui/text/android/StaticLayoutParams;-><init>(Ljava/lang/CharSequence;IILandroidx/compose/ui/text/platform/AndroidTextPaint;ILandroid/text/TextDirectionHeuristic;Landroid/text/Layout$Alignment;ILandroid/text/TextUtils$TruncateAt;IFFIZZIIII[I[I)V
+HSPLandroidx/compose/ui/text/android/TextAlignmentAdapter;-><clinit>()V
+HSPLandroidx/compose/ui/text/android/TextAndroidCanvas;->drawText(Ljava/lang/String;FFLandroid/graphics/Paint;)V
+HSPLandroidx/compose/ui/text/android/TextAndroidCanvas;->drawTextRun(Ljava/lang/CharSequence;IIIIFFZLandroid/graphics/Paint;)V
+HSPLandroidx/compose/ui/text/android/TextAndroidCanvas;->getClipBounds(Landroid/graphics/Rect;)Z
+HSPLandroidx/compose/ui/text/android/TextLayout;-><init>(Ljava/lang/CharSequence;FLandroidx/compose/ui/text/platform/AndroidTextPaint;ILandroid/text/TextUtils$TruncateAt;IZIIIIIILandroidx/compose/ui/text/android/LayoutIntrinsics;)V
+HSPLandroidx/compose/ui/text/android/TextLayout;->getHeight()I
+HSPLandroidx/compose/ui/text/android/TextLayout;->getLineBaseline(I)F
+HSPLandroidx/compose/ui/text/android/TextLayout;->getText()Ljava/lang/CharSequence;
+HSPLandroidx/compose/ui/text/android/TextLayoutKt;-><clinit>()V
+HSPLandroidx/compose/ui/text/android/TextLayoutKt;->getTextDirectionHeuristic(I)Landroid/text/TextDirectionHeuristic;
+HSPLandroidx/compose/ui/text/android/style/LineHeightStyleSpan;-><init>(FIZZF)V
+HSPLandroidx/compose/ui/text/android/style/LineHeightStyleSpan;->chooseHeight(Ljava/lang/CharSequence;IIIILandroid/graphics/Paint$FontMetricsInt;)V
+HSPLandroidx/compose/ui/text/caches/LruCache;-><init>()V
+HSPLandroidx/compose/ui/text/caches/LruCache;->get(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/text/caches/LruCache;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/text/caches/LruCache;->size()I
+HSPLandroidx/compose/ui/text/caches/SimpleArrayMap;-><init>()V
+HSPLandroidx/compose/ui/text/font/AndroidFontResolveInterceptor;-><init>(I)V
+HSPLandroidx/compose/ui/text/font/AndroidFontResolveInterceptor;->interceptFontWeight(Landroidx/compose/ui/text/font/FontWeight;)Landroidx/compose/ui/text/font/FontWeight;
+HSPLandroidx/compose/ui/text/font/AsyncTypefaceCache;-><init>()V
+HSPLandroidx/compose/ui/text/font/FontFamily;-><clinit>()V
+HSPLandroidx/compose/ui/text/font/FontFamilyResolverImpl;-><init>(Landroidx/compose/ui/unit/Dp$Companion;Landroidx/compose/ui/text/font/AndroidFontResolveInterceptor;)V
+HSPLandroidx/compose/ui/text/font/FontFamilyResolverImpl;->resolve(Landroidx/compose/ui/text/font/TypefaceRequest;)Landroidx/compose/ui/text/font/TypefaceResult;
+HSPLandroidx/compose/ui/text/font/FontFamilyResolverImpl;->resolve-DPcqOEQ(Landroidx/compose/ui/text/font/FontFamily;Landroidx/compose/ui/text/font/FontWeight;II)Landroidx/compose/ui/text/font/TypefaceResult;
+HSPLandroidx/compose/ui/text/font/FontFamilyResolverKt;-><clinit>()V
+HSPLandroidx/compose/ui/text/font/FontListFontFamilyTypefaceAdapter$special$$inlined$CoroutineExceptionHandler$1;-><init>()V
+HSPLandroidx/compose/ui/text/font/FontListFontFamilyTypefaceAdapter;-><clinit>()V
+HSPLandroidx/compose/ui/text/font/FontListFontFamilyTypefaceAdapter;-><init>(Landroidx/compose/ui/text/font/AsyncTypefaceCache;)V
+HSPLandroidx/compose/ui/text/font/FontStyle;-><init>(I)V
+HSPLandroidx/compose/ui/text/font/FontSynthesis;-><init>(I)V
+HSPLandroidx/compose/ui/text/font/FontWeight;-><clinit>()V
+HSPLandroidx/compose/ui/text/font/FontWeight;-><init>(I)V
+HSPLandroidx/compose/ui/text/font/FontWeight;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/text/font/PlatformResolveInterceptor$Companion;-><clinit>()V
+HSPLandroidx/compose/ui/text/font/PlatformResolveInterceptor;-><clinit>()V
+HSPLandroidx/compose/ui/text/font/TypefaceRequest;-><init>(Landroidx/compose/ui/text/font/FontFamily;Landroidx/compose/ui/text/font/FontWeight;IILjava/lang/Object;)V
+HSPLandroidx/compose/ui/text/font/TypefaceRequest;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/text/font/TypefaceRequest;->hashCode()I
+HSPLandroidx/compose/ui/text/font/TypefaceResult$Immutable;-><init>(Ljava/lang/Object;Z)V
+HSPLandroidx/compose/ui/text/input/InputMethodManagerImpl;-><init>(Landroid/view/View;)V
+HSPLandroidx/compose/ui/text/input/TextFieldValue;-><clinit>()V
+HSPLandroidx/compose/ui/text/input/TextFieldValue;-><init>(Landroidx/compose/ui/text/AnnotatedString;JLandroidx/compose/ui/text/TextRange;)V
+HSPLandroidx/compose/ui/text/input/TextInputService;-><init>()V
+HSPLandroidx/compose/ui/text/input/TextInputServiceAndroid;-><init>(Landroid/view/View;Landroidx/compose/ui/input/pointer/PositionCalculator;)V
+HSPLandroidx/compose/ui/text/input/TextInputServiceAndroid_androidKt$$ExternalSyntheticLambda0;-><init>(Ljava/lang/Runnable;I)V
+HSPLandroidx/compose/ui/text/input/TextInputServiceAndroid_androidKt$$ExternalSyntheticLambda0;->doFrame(J)V
+HSPLandroidx/compose/ui/text/intl/AndroidLocale;-><init>(Ljava/util/Locale;)V
+HSPLandroidx/compose/ui/text/intl/AndroidLocaleDelegateAPI24;-><init>()V
+HSPLandroidx/compose/ui/text/intl/AndroidLocaleDelegateAPI24;->getCurrent()Landroidx/compose/ui/text/intl/LocaleList;
+HSPLandroidx/compose/ui/text/intl/Locale;-><init>(Landroidx/compose/ui/text/intl/AndroidLocale;)V
+HSPLandroidx/compose/ui/text/intl/LocaleList;-><init>(Ljava/util/List;)V
+HSPLandroidx/compose/ui/text/intl/LocaleList;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/text/intl/PlatformLocaleKt;-><clinit>()V
+HSPLandroidx/compose/ui/text/platform/AndroidParagraphHelper_androidKt;-><clinit>()V
+HSPLandroidx/compose/ui/text/platform/AndroidParagraphIntrinsics$resolveTypeface$1;-><init>(Landroidx/compose/ui/text/platform/AndroidParagraphIntrinsics;)V
+HSPLandroidx/compose/ui/text/platform/AndroidParagraphIntrinsics;-><init>(Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/font/FontFamily$Resolver;Landroidx/compose/ui/unit/Density;Ljava/lang/String;Ljava/util/List;Ljava/util/List;)V
+HSPLandroidx/compose/ui/text/platform/AndroidParagraphIntrinsics;->getHasStaleResolvedFonts()Z
+HSPLandroidx/compose/ui/text/platform/AndroidParagraphIntrinsics;->getMaxIntrinsicWidth()F
+HSPLandroidx/compose/ui/text/platform/AndroidTextPaint;-><init>(F)V
+HSPLandroidx/compose/ui/text/platform/AndroidTextPaint;->setBrush-12SF9DM(Landroidx/compose/ui/graphics/Brush;JF)V
+HSPLandroidx/compose/ui/text/platform/AndroidTextPaint;->setDrawStyle(Lkotlin/ResultKt;)V
+HSPLandroidx/compose/ui/text/platform/AndroidTextPaint;->setShadow(Landroidx/compose/ui/graphics/Shadow;)V
+HSPLandroidx/compose/ui/text/platform/AndroidTextPaint;->setTextDecoration(Landroidx/compose/ui/text/style/TextDecoration;)V
+HSPLandroidx/compose/ui/text/platform/DefaultImpl$getFontLoadState$initCallback$1;-><init>(Landroidx/compose/runtime/ParcelableSnapshotMutableState;Landroidx/compose/ui/text/platform/DefaultImpl;)V
+HSPLandroidx/compose/ui/text/platform/DefaultImpl;-><init>()V
+HSPLandroidx/compose/ui/text/platform/DefaultImpl;->getFontLoadState()Landroidx/compose/runtime/State;
+HSPLandroidx/compose/ui/text/platform/EmojiCompatStatus;-><clinit>()V
+HSPLandroidx/compose/ui/text/platform/ImmutableBool;-><init>(Z)V
+HSPLandroidx/compose/ui/text/platform/ImmutableBool;->getValue()Ljava/lang/Object;
+HSPLandroidx/compose/ui/text/platform/URLSpanCache;-><init>()V
+HSPLandroidx/compose/ui/text/platform/extensions/LocaleListHelperMethods;-><clinit>()V
+HSPLandroidx/compose/ui/text/style/BaselineShift;-><init>(F)V
+HSPLandroidx/compose/ui/text/style/ColorStyle;-><init>(J)V
+HSPLandroidx/compose/ui/text/style/ColorStyle;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/text/style/ColorStyle;->getAlpha()F
+HSPLandroidx/compose/ui/text/style/ColorStyle;->getBrush()Landroidx/compose/ui/graphics/Brush;
+HSPLandroidx/compose/ui/text/style/ColorStyle;->getColor-0d7_KjU()J
+HSPLandroidx/compose/ui/text/style/Hyphens;-><init>(I)V
+HSPLandroidx/compose/ui/text/style/Hyphens;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/text/style/LineBreak$Strategy;-><init>(I)V
+HSPLandroidx/compose/ui/text/style/LineBreak$Strictness;-><init>(I)V
+HSPLandroidx/compose/ui/text/style/LineBreak$WordBreak;-><init>(I)V
+HSPLandroidx/compose/ui/text/style/LineBreak;-><init>(I)V
+HSPLandroidx/compose/ui/text/style/LineBreak;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/text/style/LineHeightStyle$Alignment;-><clinit>()V
+HSPLandroidx/compose/ui/text/style/LineHeightStyle$Alignment;->constructor-impl(F)V
+HSPLandroidx/compose/ui/text/style/LineHeightStyle;-><clinit>()V
+HSPLandroidx/compose/ui/text/style/LineHeightStyle;-><init>(F)V
+HSPLandroidx/compose/ui/text/style/TextAlign;-><init>(I)V
+HSPLandroidx/compose/ui/text/style/TextAlign;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/text/style/TextDecoration;-><clinit>()V
+HSPLandroidx/compose/ui/text/style/TextDecoration;-><init>(I)V
+HSPLandroidx/compose/ui/text/style/TextDecoration;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/text/style/TextDirection;-><init>(I)V
+HSPLandroidx/compose/ui/text/style/TextDirection;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/text/style/TextForegroundStyle$Unspecified;-><clinit>()V
+HSPLandroidx/compose/ui/text/style/TextForegroundStyle$Unspecified;->getAlpha()F
+HSPLandroidx/compose/ui/text/style/TextForegroundStyle$Unspecified;->getBrush()Landroidx/compose/ui/graphics/Brush;
+HSPLandroidx/compose/ui/text/style/TextForegroundStyle$Unspecified;->getColor-0d7_KjU()J
+HSPLandroidx/compose/ui/text/style/TextGeometricTransform;-><clinit>()V
+HSPLandroidx/compose/ui/text/style/TextGeometricTransform;-><init>(FF)V
+HSPLandroidx/compose/ui/text/style/TextGeometricTransform;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/text/style/TextIndent;-><clinit>()V
+HSPLandroidx/compose/ui/text/style/TextIndent;-><init>(JJ)V
+HSPLandroidx/compose/ui/text/style/TextIndent;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/text/style/TextMotion;-><clinit>()V
+HSPLandroidx/compose/ui/text/style/TextMotion;-><init>(IZ)V
+HSPLandroidx/compose/ui/text/style/TextMotion;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/unit/Constraints;-><clinit>()V
+HSPLandroidx/compose/ui/unit/Constraints;-><init>(J)V
+HSPLandroidx/compose/ui/unit/Constraints;->copy-Zbe2FdA$default(JIIIII)J
+HSPLandroidx/compose/ui/unit/Constraints;->equals-impl0(JJ)Z
+HSPLandroidx/compose/ui/unit/Constraints;->getHasBoundedHeight-impl(J)Z
+HSPLandroidx/compose/ui/unit/Constraints;->getHasBoundedWidth-impl(J)Z
+HSPLandroidx/compose/ui/unit/Constraints;->getHasFixedHeight-impl(J)Z
+HSPLandroidx/compose/ui/unit/Constraints;->getHasFixedWidth-impl(J)Z
+HSPLandroidx/compose/ui/unit/Constraints;->getMaxHeight-impl(J)I
+HSPLandroidx/compose/ui/unit/Constraints;->getMaxWidth-impl(J)I
+HSPLandroidx/compose/ui/unit/Constraints;->getMinHeight-impl(J)I
+HSPLandroidx/compose/ui/unit/Constraints;->getMinWidth-impl(J)I
+HSPLandroidx/compose/ui/unit/Density;->roundToPx-0680j_4(F)I
+HSPLandroidx/compose/ui/unit/Density;->toDp-u2uoSUM(I)F
+HSPLandroidx/compose/ui/unit/Density;->toPx--R2X_6o(J)F
+HSPLandroidx/compose/ui/unit/Density;->toPx-0680j_4(F)F
+HSPLandroidx/compose/ui/unit/DensityImpl;-><init>(FF)V
+HSPLandroidx/compose/ui/unit/DensityImpl;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/unit/DensityImpl;->getDensity()F
+HSPLandroidx/compose/ui/unit/DensityImpl;->getFontScale()F
+HSPLandroidx/compose/ui/unit/Dp$Companion;-><clinit>()V
+HSPLandroidx/compose/ui/unit/Dp$Companion;-><init>(Landroid/content/Context;)V
+HSPLandroidx/compose/ui/unit/Dp$Companion;->access$getIsShowingLayoutBounds()Z
+HSPLandroidx/compose/ui/unit/Dp$Companion;->area([F)F
+HSPLandroidx/compose/ui/unit/Dp$Companion;->bitsNeedForSize(I)I
+HSPLandroidx/compose/ui/unit/Dp$Companion;->createAndroidTypefaceApi28-RetOiIg(Ljava/lang/String;Landroidx/compose/ui/text/font/FontWeight;I)Landroid/graphics/Typeface;
+HSPLandroidx/compose/ui/unit/Dp$Companion;->createConstraints-Zbe2FdA$ui_unit_release(IIII)J
+HSPLandroidx/compose/ui/unit/Dp$Companion;->fixed-JhjzzOo(II)J
+HSPLandroidx/compose/ui/unit/Dp$Companion;->observe(Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/unit/Dp;-><init>(F)V
+HSPLandroidx/compose/ui/unit/Dp;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/unit/Dp;->equals-impl0(FF)Z
+HSPLandroidx/compose/ui/unit/DpOffset;-><clinit>()V
+HSPLandroidx/compose/ui/unit/DpRect;-><init>(FFFF)V
+HSPLandroidx/compose/ui/unit/DpRect;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/unit/IntOffset;-><clinit>()V
+HSPLandroidx/compose/ui/unit/IntOffset;-><init>(J)V
+HSPLandroidx/compose/ui/unit/IntOffset;->getY-impl(J)I
+HSPLandroidx/compose/ui/unit/IntSize;-><init>(J)V
+HSPLandroidx/compose/ui/unit/IntSize;->equals-impl0(JJ)Z
+HSPLandroidx/compose/ui/unit/IntSize;->getHeight-impl(J)I
+HSPLandroidx/compose/ui/unit/LayoutDirection;-><clinit>()V
+HSPLandroidx/compose/ui/unit/LayoutDirection;-><init>(ILjava/lang/String;)V
+HSPLandroidx/compose/ui/unit/TextUnit;-><clinit>()V
+HSPLandroidx/compose/ui/unit/TextUnit;->equals-impl0(JJ)Z
+HSPLandroidx/compose/ui/unit/TextUnit;->getType-UIouoOA(J)J
+HSPLandroidx/compose/ui/unit/TextUnit;->getValue-impl(J)F
+HSPLandroidx/compose/ui/unit/TextUnitType;-><init>(J)V
+HSPLandroidx/compose/ui/unit/TextUnitType;->equals-impl0(JJ)Z
+HSPLandroidx/core/app/ComponentActivity;-><init>()V
+HSPLandroidx/core/app/ComponentActivity;->dispatchKeyEvent(Landroid/view/KeyEvent;)Z
+HSPLandroidx/core/app/ComponentActivity;->onCreate(Landroid/os/Bundle;)V
+HSPLandroidx/core/app/ComponentActivity;->superDispatchKeyEvent(Landroid/view/KeyEvent;)Z
+HSPLandroidx/core/app/CoreComponentFactory;-><init>()V
+HSPLandroidx/core/app/CoreComponentFactory;->instantiateActivity(Ljava/lang/ClassLoader;Ljava/lang/String;Landroid/content/Intent;)Landroid/app/Activity;
+HSPLandroidx/core/app/CoreComponentFactory;->instantiateApplication(Ljava/lang/ClassLoader;Ljava/lang/String;)Landroid/app/Application;
+HSPLandroidx/core/app/CoreComponentFactory;->instantiateProvider(Ljava/lang/ClassLoader;Ljava/lang/String;)Landroid/content/ContentProvider;
+HSPLandroidx/core/content/res/ComplexColorCompat;-><init>()V
+HSPLandroidx/core/content/res/ComplexColorCompat;-><init>(I)V
+HSPLandroidx/core/content/res/ComplexColorCompat;-><init>(Ljava/lang/Object;)V
+HSPLandroidx/core/content/res/ComplexColorCompat;->find(Ljava/lang/Object;)I
+HSPLandroidx/core/content/res/ComplexColorCompat;->get(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/core/content/res/ComplexColorCompat;->set(Ljava/lang/Object;Ljava/lang/Object;)V
+HSPLandroidx/core/graphics/TypefaceCompat;-><clinit>()V
+HSPLandroidx/core/graphics/TypefaceCompatApi29Impl;-><init>()V
+HSPLandroidx/core/graphics/TypefaceCompatApi29Impl;->createFromFontInfo(Landroid/content/Context;[Landroidx/core/provider/FontsContractCompat$FontInfo;I)Landroid/graphics/Typeface;
+HSPLandroidx/core/graphics/TypefaceCompatApi29Impl;->findBaseFont(Landroid/graphics/fonts/FontFamily;I)Landroid/graphics/fonts/Font;
+HSPLandroidx/core/graphics/TypefaceCompatApi29Impl;->getMatchScore(Landroid/graphics/fonts/FontStyle;Landroid/graphics/fonts/FontStyle;)I
+HSPLandroidx/core/graphics/TypefaceCompatUtil$Api19Impl;->openFileDescriptor(Landroid/content/ContentResolver;Landroid/net/Uri;Ljava/lang/String;Landroid/os/CancellationSignal;)Landroid/os/ParcelFileDescriptor;
+HSPLandroidx/core/os/BuildCompat$Extensions30Impl$$ExternalSyntheticApiModelOutline0;->m$1()I
+HSPLandroidx/core/os/BuildCompat$Extensions30Impl$$ExternalSyntheticApiModelOutline0;->m$2()I
+HSPLandroidx/core/os/BuildCompat$Extensions30Impl$$ExternalSyntheticApiModelOutline0;->m$3()I
+HSPLandroidx/core/os/BuildCompat$Extensions30Impl$$ExternalSyntheticApiModelOutline0;->m()I
+HSPLandroidx/core/os/BuildCompat$Extensions30Impl;-><clinit>()V
+HSPLandroidx/core/os/BuildCompat;-><clinit>()V
+HSPLandroidx/core/os/BuildCompat;->isAtLeastT()Z
+HSPLandroidx/core/os/TraceCompat$Api18Impl;->beginSection(Ljava/lang/String;)V
+HSPLandroidx/core/os/TraceCompat$Api18Impl;->endSection()V
+HSPLandroidx/core/os/TraceCompat;-><clinit>()V
+HSPLandroidx/core/provider/CallbackWithHandler$2;-><init>(ILjava/util/ArrayList;)V
+HSPLandroidx/core/provider/CallbackWithHandler$2;-><init>(Ljava/util/List;ILjava/lang/Throwable;)V
+HSPLandroidx/core/provider/CallbackWithHandler$2;->run()V
+HSPLandroidx/core/provider/FontProvider$Api16Impl;->query(Landroid/content/ContentResolver;Landroid/net/Uri;[Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)Landroid/database/Cursor;
+HSPLandroidx/core/provider/FontRequest;-><init>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)V
+HSPLandroidx/core/provider/FontsContractCompat$FontInfo;-><init>(Landroid/net/Uri;IIZI)V
+HSPLandroidx/core/view/AccessibilityDelegateCompat$AccessibilityDelegateAdapter;-><init>(Landroidx/core/view/AccessibilityDelegateCompat;)V
+HSPLandroidx/core/view/AccessibilityDelegateCompat$AccessibilityDelegateAdapter;->dispatchPopulateAccessibilityEvent(Landroid/view/View;Landroid/view/accessibility/AccessibilityEvent;)Z
+HSPLandroidx/core/view/AccessibilityDelegateCompat$AccessibilityDelegateAdapter;->getAccessibilityNodeProvider(Landroid/view/View;)Landroid/view/accessibility/AccessibilityNodeProvider;
+HSPLandroidx/core/view/AccessibilityDelegateCompat$AccessibilityDelegateAdapter;->onInitializeAccessibilityEvent(Landroid/view/View;Landroid/view/accessibility/AccessibilityEvent;)V
+HSPLandroidx/core/view/AccessibilityDelegateCompat$AccessibilityDelegateAdapter;->onPopulateAccessibilityEvent(Landroid/view/View;Landroid/view/accessibility/AccessibilityEvent;)V
+HSPLandroidx/core/view/AccessibilityDelegateCompat$AccessibilityDelegateAdapter;->sendAccessibilityEvent(Landroid/view/View;I)V
+HSPLandroidx/core/view/AccessibilityDelegateCompat$AccessibilityDelegateAdapter;->sendAccessibilityEventUnchecked(Landroid/view/View;Landroid/view/accessibility/AccessibilityEvent;)V
+HSPLandroidx/core/view/AccessibilityDelegateCompat;-><clinit>()V
+HSPLandroidx/core/view/AccessibilityDelegateCompat;-><init>()V
+HSPLandroidx/core/view/MenuHostHelper;-><init>(Landroidx/activity/ComponentActivity$$ExternalSyntheticLambda0;)V
+HSPLandroidx/core/view/MenuHostHelper;-><init>(Ljava/lang/reflect/Method;Ljava/lang/reflect/Method;Ljava/lang/reflect/Method;)V
+HSPLandroidx/core/view/ViewCompat$$ExternalSyntheticLambda0;-><init>(I)V
+HSPLandroidx/core/view/ViewCompat$$ExternalSyntheticLambda0;->invoke(D)D
+HSPLandroidx/core/view/ViewCompat$Api29Impl;->getContentCaptureSession(Landroid/view/View;)Landroid/view/contentcapture/ContentCaptureSession;
+HSPLandroidx/core/view/ViewCompat$Api30Impl;->setImportantForContentCapture(Landroid/view/View;I)V
+HSPLandroidx/core/view/ViewCompat;-><clinit>()V
+HSPLandroidx/core/view/accessibility/AccessibilityNodeProviderCompat;-><init>(Ljava/lang/Object;)V
+HSPLandroidx/customview/poolingcontainer/PoolingContainerListenerHolder;-><init>()V
+HSPLandroidx/emoji2/text/ConcurrencyHelpers$$ExternalSyntheticLambda0;-><init>(Ljava/lang/String;)V
+HSPLandroidx/emoji2/text/ConcurrencyHelpers$$ExternalSyntheticLambda0;->newThread(Ljava/lang/Runnable;)Ljava/lang/Thread;
+HSPLandroidx/emoji2/text/ConcurrencyHelpers$Handler28Impl;->createAsync(Landroid/os/Looper;)Landroid/os/Handler;
+HSPLandroidx/emoji2/text/DefaultGlyphChecker;-><clinit>()V
+HSPLandroidx/emoji2/text/DefaultGlyphChecker;-><init>()V
+HSPLandroidx/emoji2/text/EmojiCompat$CompatInternal19$1;-><init>(Landroidx/emoji2/text/EmojiCompat$CompatInternal19;)V
+HSPLandroidx/emoji2/text/EmojiCompat$CompatInternal19$1;->onLoaded(Landroidx/emoji2/text/MetadataRepo;)V
+HSPLandroidx/emoji2/text/EmojiCompat$CompatInternal19;-><init>(Landroidx/emoji2/text/EmojiCompat;)V
+HSPLandroidx/emoji2/text/EmojiCompat$CompatInternal19;->process(Ljava/lang/CharSequence;IZ)Ljava/lang/CharSequence;
+HSPLandroidx/emoji2/text/EmojiCompat$Config;-><init>(Landroidx/emoji2/text/EmojiCompat$MetadataRepoLoader;)V
+HSPLandroidx/emoji2/text/EmojiCompat;-><clinit>()V
+HSPLandroidx/emoji2/text/EmojiCompat;-><init>(Landroidx/emoji2/text/FontRequestEmojiCompatConfig;)V
+HSPLandroidx/emoji2/text/EmojiCompat;->get()Landroidx/emoji2/text/EmojiCompat;
+HSPLandroidx/emoji2/text/EmojiCompat;->getLoadState()I
+HSPLandroidx/emoji2/text/EmojiCompat;->load()V
+HSPLandroidx/emoji2/text/EmojiCompat;->onMetadataLoadSuccess()V
+HSPLandroidx/emoji2/text/EmojiCompatInitializer$1;-><init>(Landroidx/emoji2/text/EmojiCompatInitializer;Landroidx/lifecycle/Lifecycle;)V
+HSPLandroidx/emoji2/text/EmojiCompatInitializer$1;->onResume(Landroidx/lifecycle/LifecycleOwner;)V
+HSPLandroidx/emoji2/text/EmojiCompatInitializer$BackgroundDefaultLoader$$ExternalSyntheticLambda0;-><init>(Landroidx/compose/runtime/Stack;Lokhttp3/MediaType;Ljava/util/concurrent/ThreadPoolExecutor;)V
+HSPLandroidx/emoji2/text/EmojiCompatInitializer$BackgroundDefaultLoader$$ExternalSyntheticLambda0;->run()V
+HSPLandroidx/emoji2/text/EmojiCompatInitializer$BackgroundDefaultLoader$1;-><init>(Lokhttp3/MediaType;Ljava/util/concurrent/ThreadPoolExecutor;)V
+HSPLandroidx/emoji2/text/EmojiCompatInitializer$BackgroundDefaultLoader$1;->onLoaded(Landroidx/emoji2/text/MetadataRepo;)V
+HSPLandroidx/emoji2/text/EmojiCompatInitializer$LoadEmojiCompatRunnable;->run()V
+HSPLandroidx/emoji2/text/EmojiCompatInitializer;-><init>()V
+HSPLandroidx/emoji2/text/EmojiCompatInitializer;->create(Landroid/content/Context;)Ljava/lang/Boolean;
+HSPLandroidx/emoji2/text/EmojiCompatInitializer;->create(Landroid/content/Context;)Ljava/lang/Object;
+HSPLandroidx/emoji2/text/EmojiCompatInitializer;->dependencies()Ljava/util/List;
+HSPLandroidx/emoji2/text/EmojiProcessor$EmojiProcessAddSpanCallback;-><init>(Landroidx/emoji2/text/UnprecomputeTextOnModificationSpannable;Lkotlin/ULong$Companion;)V
+HSPLandroidx/emoji2/text/EmojiProcessor$EmojiProcessAddSpanCallback;->getResult()Ljava/lang/Object;
+HSPLandroidx/emoji2/text/EmojiProcessor$ProcessorSm;-><init>(Landroidx/emoji2/text/MetadataRepo$Node;Z[I)V
+HSPLandroidx/emoji2/text/EmojiProcessor$ProcessorSm;->shouldUseEmojiPresentationStyleForSingleCodepoint()Z
+HSPLandroidx/emoji2/text/EmojiProcessor;-><init>(Landroidx/compose/ui/node/LayoutNode;)V
+HSPLandroidx/emoji2/text/EmojiProcessor;-><init>(Landroidx/emoji2/text/MetadataRepo;Lkotlin/ULong$Companion;Landroidx/emoji2/text/DefaultGlyphChecker;Ljava/util/Set;)V
+HSPLandroidx/emoji2/text/EmojiProcessor;->process(Ljava/lang/String;IIIZLandroidx/emoji2/text/EmojiProcessor$EmojiProcessCallback;)Ljava/lang/Object;
+HSPLandroidx/emoji2/text/FontRequestEmojiCompatConfig$FontRequestMetadataLoader$$ExternalSyntheticLambda0;-><init>(Landroidx/emoji2/text/FontRequestEmojiCompatConfig$FontRequestMetadataLoader;I)V
+HSPLandroidx/emoji2/text/FontRequestEmojiCompatConfig$FontRequestMetadataLoader$$ExternalSyntheticLambda0;->run()V
+HSPLandroidx/emoji2/text/FontRequestEmojiCompatConfig$FontRequestMetadataLoader$1;-><init>(Lkotlinx/coroutines/channels/BufferedChannel;Landroid/os/Handler;I)V
+HSPLandroidx/emoji2/text/FontRequestEmojiCompatConfig$FontRequestMetadataLoader;-><init>(Landroid/content/Context;Landroidx/core/provider/FontRequest;)V
+HSPLandroidx/emoji2/text/FontRequestEmojiCompatConfig$FontRequestMetadataLoader;->cleanUp()V
+HSPLandroidx/emoji2/text/FontRequestEmojiCompatConfig$FontRequestMetadataLoader;->load(Lokhttp3/MediaType;)V
+HSPLandroidx/emoji2/text/FontRequestEmojiCompatConfig$FontRequestMetadataLoader;->loadInternal()V
+HSPLandroidx/emoji2/text/FontRequestEmojiCompatConfig$FontRequestMetadataLoader;->retrieveFontInfo()Landroidx/core/provider/FontsContractCompat$FontInfo;
+HSPLandroidx/emoji2/text/FontRequestEmojiCompatConfig;-><clinit>()V
+HSPLandroidx/emoji2/text/FontRequestEmojiCompatConfig;-><init>(Landroid/content/Context;)V
+HSPLandroidx/emoji2/text/FontRequestEmojiCompatConfig;-><init>(Landroid/content/Context;Landroidx/core/provider/FontRequest;)V
+HSPLandroidx/emoji2/text/MetadataRepo$Node;-><init>(I)V
+HSPLandroidx/emoji2/text/MetadataRepo$Node;->put(Landroidx/emoji2/text/TypefaceEmojiRasterizer;II)V
+HSPLandroidx/emoji2/text/MetadataRepo;-><init>(Landroid/graphics/Typeface;Landroidx/emoji2/text/flatbuffer/MetadataList;)V
+HSPLandroidx/emoji2/text/TypefaceEmojiRasterizer;-><clinit>()V
+HSPLandroidx/emoji2/text/TypefaceEmojiRasterizer;-><init>(Landroidx/emoji2/text/MetadataRepo;I)V
+HSPLandroidx/emoji2/text/TypefaceEmojiRasterizer;->getCodepointAt(I)I
+HSPLandroidx/emoji2/text/TypefaceEmojiRasterizer;->getCodepointsLength()I
+HSPLandroidx/emoji2/text/TypefaceEmojiRasterizer;->getMetadataItem()Landroidx/emoji2/text/flatbuffer/MetadataItem;
+HSPLandroidx/emoji2/text/flatbuffer/Table;-><init>()V
+HSPLandroidx/emoji2/text/flatbuffer/Table;->__offset(I)I
+HSPLandroidx/lifecycle/DefaultLifecycleObserver;->onResume(Landroidx/lifecycle/LifecycleOwner;)V
+HSPLandroidx/lifecycle/DefaultLifecycleObserver;->onStart(Landroidx/lifecycle/LifecycleOwner;)V
+HSPLandroidx/lifecycle/DefaultLifecycleObserverAdapter$WhenMappings;-><clinit>()V
+HSPLandroidx/lifecycle/DefaultLifecycleObserverAdapter;-><init>(Landroidx/lifecycle/DefaultLifecycleObserver;Landroidx/lifecycle/LifecycleEventObserver;)V
+HSPLandroidx/lifecycle/DefaultLifecycleObserverAdapter;->onStateChanged(Landroidx/lifecycle/LifecycleOwner;Landroidx/lifecycle/Lifecycle$Event;)V
+HSPLandroidx/lifecycle/EmptyActivityLifecycleCallbacks;->onActivityCreated(Landroid/app/Activity;Landroid/os/Bundle;)V
+HSPLandroidx/lifecycle/EmptyActivityLifecycleCallbacks;->onActivityResumed(Landroid/app/Activity;)V
+HSPLandroidx/lifecycle/EmptyActivityLifecycleCallbacks;->onActivityStarted(Landroid/app/Activity;)V
+HSPLandroidx/lifecycle/Lifecycle$Event$Companion;->upFrom(Landroidx/lifecycle/Lifecycle$State;)Landroidx/lifecycle/Lifecycle$Event;
+HSPLandroidx/lifecycle/Lifecycle$Event$WhenMappings;-><clinit>()V
+HSPLandroidx/lifecycle/Lifecycle$Event;-><clinit>()V
+HSPLandroidx/lifecycle/Lifecycle$Event;-><init>(ILjava/lang/String;)V
+HSPLandroidx/lifecycle/Lifecycle$Event;->getTargetState()Landroidx/lifecycle/Lifecycle$State;
+HSPLandroidx/lifecycle/Lifecycle$Event;->values()[Landroidx/lifecycle/Lifecycle$Event;
+HSPLandroidx/lifecycle/Lifecycle$State;-><clinit>()V
+HSPLandroidx/lifecycle/Lifecycle$State;-><init>(ILjava/lang/String;)V
+HSPLandroidx/lifecycle/Lifecycle;-><init>()V
+HSPLandroidx/lifecycle/LifecycleDestroyedException;-><init>(I)V
+HSPLandroidx/lifecycle/LifecycleDestroyedException;-><init>(II)V
+HSPLandroidx/lifecycle/LifecycleDestroyedException;->fillInStackTrace()Ljava/lang/Throwable;
+HSPLandroidx/lifecycle/LifecycleDispatcher$DispatcherActivityCallback;-><init>()V
+HSPLandroidx/lifecycle/LifecycleDispatcher$DispatcherActivityCallback;->onActivityCreated(Landroid/app/Activity;Landroid/os/Bundle;)V
+HSPLandroidx/lifecycle/LifecycleDispatcher;-><clinit>()V
+HSPLandroidx/lifecycle/LifecycleRegistry$ObserverWithState;-><init>(Landroidx/lifecycle/LifecycleObserver;Landroidx/lifecycle/Lifecycle$State;)V
+HSPLandroidx/lifecycle/LifecycleRegistry$ObserverWithState;->dispatchEvent(Landroidx/lifecycle/LifecycleOwner;Landroidx/lifecycle/Lifecycle$Event;)V
+HSPLandroidx/lifecycle/LifecycleRegistry;-><init>(Landroidx/lifecycle/LifecycleOwner;)V
+HSPLandroidx/lifecycle/LifecycleRegistry;->addObserver(Landroidx/lifecycle/LifecycleObserver;)V
+HSPLandroidx/lifecycle/LifecycleRegistry;->calculateTargetState(Landroidx/lifecycle/LifecycleObserver;)Landroidx/lifecycle/Lifecycle$State;
+HSPLandroidx/lifecycle/LifecycleRegistry;->enforceMainThreadIfNeeded(Ljava/lang/String;)V
+HSPLandroidx/lifecycle/LifecycleRegistry;->handleLifecycleEvent(Landroidx/lifecycle/Lifecycle$Event;)V
+HSPLandroidx/lifecycle/LifecycleRegistry;->moveToState(Landroidx/lifecycle/Lifecycle$State;)V
+HSPLandroidx/lifecycle/LifecycleRegistry;->removeObserver(Landroidx/lifecycle/LifecycleObserver;)V
+HSPLandroidx/lifecycle/LifecycleRegistry;->sync()V
+HSPLandroidx/lifecycle/Lifecycling;-><clinit>()V
+HSPLandroidx/lifecycle/ProcessLifecycleInitializer;-><init>()V
+HSPLandroidx/lifecycle/ProcessLifecycleInitializer;->create(Landroid/content/Context;)Ljava/lang/Object;
+HSPLandroidx/lifecycle/ProcessLifecycleInitializer;->dependencies()Ljava/util/List;
+HSPLandroidx/lifecycle/ProcessLifecycleOwner$Api29Impl;->registerActivityLifecycleCallbacks(Landroid/app/Activity;Landroid/app/Application$ActivityLifecycleCallbacks;)V
+HSPLandroidx/lifecycle/ProcessLifecycleOwner$attach$1$onActivityPreCreated$1;-><init>(Landroidx/lifecycle/ProcessLifecycleOwner;)V
+HSPLandroidx/lifecycle/ProcessLifecycleOwner$attach$1$onActivityPreCreated$1;->onActivityPostResumed(Landroid/app/Activity;)V
+HSPLandroidx/lifecycle/ProcessLifecycleOwner$attach$1$onActivityPreCreated$1;->onActivityPostStarted(Landroid/app/Activity;)V
+HSPLandroidx/lifecycle/ProcessLifecycleOwner$attach$1;-><init>(Landroidx/lifecycle/ProcessLifecycleOwner;)V
+HSPLandroidx/lifecycle/ProcessLifecycleOwner$attach$1;->onActivityCreated(Landroid/app/Activity;Landroid/os/Bundle;)V
+HSPLandroidx/lifecycle/ProcessLifecycleOwner$attach$1;->onActivityPreCreated(Landroid/app/Activity;Landroid/os/Bundle;)V
+HSPLandroidx/lifecycle/ProcessLifecycleOwner$initializationListener$1;-><init>(Landroidx/lifecycle/ProcessLifecycleOwner;)V
+HSPLandroidx/lifecycle/ProcessLifecycleOwner;-><clinit>()V
+HSPLandroidx/lifecycle/ProcessLifecycleOwner;-><init>()V
+HSPLandroidx/lifecycle/ProcessLifecycleOwner;->activityResumed$lifecycle_process_release()V
+HSPLandroidx/lifecycle/ProcessLifecycleOwner;->getLifecycle()Landroidx/lifecycle/LifecycleRegistry;
+HSPLandroidx/lifecycle/ReportFragment$LifecycleCallbacks;-><clinit>()V
+HSPLandroidx/lifecycle/ReportFragment$LifecycleCallbacks;-><init>()V
+HSPLandroidx/lifecycle/ReportFragment$LifecycleCallbacks;->onActivityCreated(Landroid/app/Activity;Landroid/os/Bundle;)V
+HSPLandroidx/lifecycle/ReportFragment$LifecycleCallbacks;->onActivityPostCreated(Landroid/app/Activity;Landroid/os/Bundle;)V
+HSPLandroidx/lifecycle/ReportFragment$LifecycleCallbacks;->onActivityPostResumed(Landroid/app/Activity;)V
+HSPLandroidx/lifecycle/ReportFragment$LifecycleCallbacks;->onActivityPostStarted(Landroid/app/Activity;)V
+HSPLandroidx/lifecycle/ReportFragment$LifecycleCallbacks;->onActivityResumed(Landroid/app/Activity;)V
+HSPLandroidx/lifecycle/ReportFragment$LifecycleCallbacks;->onActivityStarted(Landroid/app/Activity;)V
+HSPLandroidx/lifecycle/ReportFragment;-><init>()V
+HSPLandroidx/lifecycle/ReportFragment;->dispatch(Landroidx/lifecycle/Lifecycle$Event;)V
+HSPLandroidx/lifecycle/ReportFragment;->onActivityCreated(Landroid/os/Bundle;)V
+HSPLandroidx/lifecycle/ReportFragment;->onResume()V
+HSPLandroidx/lifecycle/ReportFragment;->onStart()V
+HSPLandroidx/lifecycle/SavedStateHandleAttacher;-><init>(Landroidx/lifecycle/SavedStateHandlesProvider;)V
+HSPLandroidx/lifecycle/SavedStateHandleAttacher;->onStateChanged(Landroidx/lifecycle/LifecycleOwner;Landroidx/lifecycle/Lifecycle$Event;)V
+HSPLandroidx/lifecycle/SavedStateHandlesProvider;-><init>(Landroidx/savedstate/SavedStateRegistry;Landroidx/lifecycle/ViewModelStoreOwner;)V
+HSPLandroidx/lifecycle/SavedStateHandlesVM;-><init>()V
+HSPLandroidx/lifecycle/ViewModelStore;-><init>()V
+HSPLandroidx/lifecycle/viewmodel/CreationExtras$Empty;-><clinit>()V
+HSPLandroidx/lifecycle/viewmodel/CreationExtras;-><init>()V
+HSPLandroidx/lifecycle/viewmodel/MutableCreationExtras;-><init>(Landroidx/lifecycle/viewmodel/CreationExtras;)V
+HSPLandroidx/lifecycle/viewmodel/ViewModelInitializer;-><init>(Ljava/lang/Class;)V
+HSPLandroidx/metrics/performance/DelegatingFrameMetricsListener;-><init>(Ljava/util/ArrayList;)V
+HSPLandroidx/metrics/performance/DelegatingFrameMetricsListener;->onFrameMetricsAvailable(Landroid/view/Window;Landroid/view/FrameMetrics;I)V
+HSPLandroidx/metrics/performance/FrameData;-><init>(JJZLjava/util/ArrayList;)V
+HSPLandroidx/metrics/performance/FrameDataApi24;-><init>(JJJZLjava/util/ArrayList;)V
+HSPLandroidx/metrics/performance/FrameDataApi31;-><init>(JJJJZLjava/util/ArrayList;)V
+HSPLandroidx/metrics/performance/FrameDataApi31;->copy()Landroidx/metrics/performance/FrameData;
+HSPLandroidx/metrics/performance/JankStats;-><init>(Landroid/view/Window;Lcom/example/tvcomposebasedtests/JankStatsAggregator$listener$1;)V
+HSPLandroidx/metrics/performance/JankStats;->logFrameData$metrics_performance_release(Landroidx/metrics/performance/FrameData;)V
+HSPLandroidx/metrics/performance/JankStatsApi16Impl;-><init>(Landroidx/metrics/performance/JankStats;Landroid/view/View;)V
+HSPLandroidx/metrics/performance/JankStatsApi22Impl;-><init>(Landroidx/metrics/performance/JankStats;Landroid/view/View;)V
+HSPLandroidx/metrics/performance/JankStatsApi24Impl$$ExternalSyntheticLambda0;-><init>(Landroidx/metrics/performance/JankStatsApi24Impl;Landroidx/metrics/performance/JankStats;)V
+HSPLandroidx/metrics/performance/JankStatsApi24Impl$$ExternalSyntheticLambda0;->onFrameMetricsAvailable(Landroid/view/Window;Landroid/view/FrameMetrics;I)V
+HSPLandroidx/metrics/performance/JankStatsApi24Impl;-><init>(Landroidx/metrics/performance/JankStats;Landroid/view/View;Landroid/view/Window;)V
+HSPLandroidx/metrics/performance/JankStatsApi24Impl;->getOrCreateFrameMetricsListenerDelegator(Landroid/view/Window;)Landroidx/metrics/performance/DelegatingFrameMetricsListener;
+HSPLandroidx/metrics/performance/JankStatsApi24Impl;->setupFrameTimer(Z)V
+HSPLandroidx/metrics/performance/JankStatsApi26Impl;-><init>(Landroidx/metrics/performance/JankStats;Landroid/view/View;Landroid/view/Window;)V
+HSPLandroidx/metrics/performance/JankStatsApi26Impl;->getFrameStartTime$metrics_performance_release(Landroid/view/FrameMetrics;)J
+HSPLandroidx/metrics/performance/JankStatsApi31Impl;-><init>(Landroidx/metrics/performance/JankStats;Landroid/view/View;Landroid/view/Window;)V
+HSPLandroidx/metrics/performance/JankStatsApi31Impl;->getExpectedFrameDuration(Landroid/view/FrameMetrics;)J
+HSPLandroidx/metrics/performance/JankStatsApi31Impl;->getFrameData$metrics_performance_release(JJLandroid/view/FrameMetrics;)Landroidx/metrics/performance/FrameDataApi24;
+HSPLandroidx/metrics/performance/PerformanceMetricsState;-><init>()V
+HSPLandroidx/metrics/performance/PerformanceMetricsState;->addFrameState(JJLjava/util/ArrayList;Ljava/util/ArrayList;)V
+HSPLandroidx/metrics/performance/PerformanceMetricsState;->cleanupSingleFrameStates$metrics_performance_release()V
+HSPLandroidx/metrics/performance/PerformanceMetricsState;->getIntervalStates$metrics_performance_release(JJLjava/util/ArrayList;)V
+HSPLandroidx/profileinstaller/ProfileInstallerInitializer$$ExternalSyntheticLambda0;-><init>(Ljava/lang/Object;ILjava/lang/Object;)V
+HSPLandroidx/profileinstaller/ProfileInstallerInitializer$$ExternalSyntheticLambda0;->run()V
+HSPLandroidx/profileinstaller/ProfileInstallerInitializer$$ExternalSyntheticLambda1;-><init>(Landroid/content/Context;I)V
+HSPLandroidx/profileinstaller/ProfileInstallerInitializer$Choreographer16Impl;->postFrameCallback(Ljava/lang/Runnable;)V
+HSPLandroidx/profileinstaller/ProfileInstallerInitializer$Handler28Impl;->createAsync(Landroid/os/Looper;)Landroid/os/Handler;
+HSPLandroidx/profileinstaller/ProfileInstallerInitializer;-><init>()V
+HSPLandroidx/profileinstaller/ProfileInstallerInitializer;->create(Landroid/content/Context;)Ljava/lang/Object;
+HSPLandroidx/profileinstaller/ProfileInstallerInitializer;->dependencies()Ljava/util/List;
+HSPLandroidx/savedstate/Recreator;-><init>(Landroidx/savedstate/SavedStateRegistryOwner;)V
+HSPLandroidx/savedstate/Recreator;->onStateChanged(Landroidx/lifecycle/LifecycleOwner;Landroidx/lifecycle/Lifecycle$Event;)V
+HSPLandroidx/savedstate/SavedStateRegistry$$ExternalSyntheticLambda0;-><init>(Landroidx/savedstate/SavedStateRegistry;)V
+HSPLandroidx/savedstate/SavedStateRegistry$$ExternalSyntheticLambda0;->onStateChanged(Landroidx/lifecycle/LifecycleOwner;Landroidx/lifecycle/Lifecycle$Event;)V
+HSPLandroidx/savedstate/SavedStateRegistry;-><init>()V
+HSPLandroidx/savedstate/SavedStateRegistry;->consumeRestoredStateForKey(Ljava/lang/String;)Landroid/os/Bundle;
+HSPLandroidx/savedstate/SavedStateRegistry;->registerSavedStateProvider(Ljava/lang/String;Landroidx/savedstate/SavedStateRegistry$SavedStateProvider;)V
+HSPLandroidx/savedstate/SavedStateRegistryController;-><init>(Landroidx/savedstate/SavedStateRegistryOwner;)V
+HSPLandroidx/savedstate/SavedStateRegistryController;->performAttach()V
+HSPLandroidx/startup/AppInitializer;-><clinit>()V
+HSPLandroidx/startup/AppInitializer;-><init>(Landroid/content/Context;)V
+HSPLandroidx/startup/AppInitializer;->discoverAndInitialize(Landroid/os/Bundle;)V
+HSPLandroidx/startup/AppInitializer;->doInitialize(Ljava/lang/Class;Ljava/util/HashSet;)Ljava/lang/Object;
+HSPLandroidx/startup/AppInitializer;->getInstance(Landroid/content/Context;)Landroidx/startup/AppInitializer;
+HSPLandroidx/startup/InitializationProvider;-><init>()V
+HSPLandroidx/startup/InitializationProvider;->onCreate()Z
+HSPLandroidx/tracing/Trace$$ExternalSyntheticApiModelOutline0;->m()Z
+HSPLandroidx/tracing/Trace$$ExternalSyntheticApiModelOutline0;->m(Landroid/app/Activity;Landroidx/lifecycle/ReportFragment$LifecycleCallbacks;)V
+HSPLandroidx/tv/foundation/PivotOffsets;-><init>()V
+HSPLandroidx/tv/foundation/TvBringIntoViewSpec;-><init>(Landroidx/tv/foundation/PivotOffsets;Z)V
+HSPLandroidx/tv/foundation/TvBringIntoViewSpec;->calculateScrollDistance(FFF)F
+HSPLandroidx/tv/foundation/TvBringIntoViewSpec;->getScrollAnimationSpec()Landroidx/compose/animation/core/AnimationSpec;
+HSPLandroidx/tv/foundation/lazy/grid/LazyGridItemPlacementAnimator;-><init>(I)V
+HSPLandroidx/tv/foundation/lazy/grid/LazyGridItemPlacementAnimator;->reset()V
+HSPLandroidx/tv/foundation/lazy/grid/LazyGridKt$rememberLazyGridMeasurePolicy$1$1$3;-><init>(Landroidx/compose/foundation/lazy/layout/LazyLayoutMeasureScopeImpl;JIII)V
+HSPLandroidx/tv/foundation/lazy/grid/LazyGridKt$rememberLazyGridMeasurePolicy$1$1$3;->invoke(IILkotlin/jvm/functions/Function1;)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/tv/foundation/lazy/grid/LazyGridKt$rememberLazyGridMeasurePolicy$1$1$3;->invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/foundation/lazy/grid/LazyGridScrollPosition;-><init>(III)V
+HSPLandroidx/tv/foundation/lazy/grid/LazyGridScrollPosition;->getIndex()I
+HSPLandroidx/tv/foundation/lazy/grid/LazyGridScrollPosition;->setIndex(I)V
+HSPLandroidx/tv/foundation/lazy/grid/LazyGridScrollPosition;->update(II)V
+HSPLandroidx/tv/foundation/lazy/grid/TvLazyGridState$remeasurementModifier$1;-><init>(Landroidx/compose/foundation/gestures/ScrollableState;I)V
+HSPLandroidx/tv/foundation/lazy/layout/AwaitFirstLayoutModifier$waitForFirstLayout$1;-><init>(Landroidx/tv/foundation/lazy/layout/AwaitFirstLayoutModifier;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/tv/foundation/lazy/layout/AwaitFirstLayoutModifier;->waitForFirstLayout(Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLandroidx/tv/foundation/lazy/layout/LazyLayoutNearestRangeState;-><clinit>()V
+HSPLandroidx/tv/foundation/lazy/layout/LazyLayoutNearestRangeState;-><init>(III)V
+HSPLandroidx/tv/foundation/lazy/layout/LazyLayoutNearestRangeState;->getValue()Ljava/lang/Object;
+HSPLandroidx/tv/foundation/lazy/layout/LazyLayoutNearestRangeState;->update(I)V
+HSPLandroidx/tv/foundation/lazy/layout/LazyLayoutSemanticsKt$LazyLayoutSemanticState$1;-><init>(Landroidx/tv/foundation/lazy/list/TvLazyListState;Z)V
+HSPLandroidx/tv/foundation/lazy/layout/LazyLayoutSemanticsKt$LazyLayoutSemanticState$1;->collectionInfo()Landroidx/compose/ui/semantics/CollectionInfo;
+HSPLandroidx/tv/foundation/lazy/layout/LazyLayoutSemanticsKt$lazyLayoutSemantics$1$1;-><init>(Landroidx/tv/material3/TabKt$Tab$3$1;ZLandroidx/compose/ui/semantics/ScrollAxisRange;Landroidx/compose/foundation/ScrollKt$scroll$2$semantics$1$1;Landroidx/compose/foundation/layout/OffsetNode$measure$1;Landroidx/compose/ui/semantics/CollectionInfo;)V
+HSPLandroidx/tv/foundation/lazy/layout/NearestRangeKeyIndexMap$2$1;-><init>(IILjava/util/HashMap;Landroidx/tv/foundation/lazy/layout/NearestRangeKeyIndexMap;)V
+HSPLandroidx/tv/foundation/lazy/layout/NearestRangeKeyIndexMap$2$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/foundation/lazy/layout/NearestRangeKeyIndexMap;-><init>(Lkotlin/ranges/IntRange;Landroidx/tv/material3/TabKt;)V
+HSPLandroidx/tv/foundation/lazy/layout/NearestRangeKeyIndexMap;->getKey(I)Ljava/lang/Object;
+HSPLandroidx/tv/foundation/lazy/list/EmptyLazyListLayoutInfo;-><clinit>()V
+HSPLandroidx/tv/foundation/lazy/list/LazyLayoutBeyondBoundsModifierLocal;-><clinit>()V
+HSPLandroidx/tv/foundation/lazy/list/LazyLayoutBeyondBoundsModifierLocal;-><init>(Landroidx/tv/foundation/lazy/list/LazyLayoutBeyondBoundsState;Landroidx/compose/runtime/Stack;ZLandroidx/compose/ui/unit/LayoutDirection;Landroidx/compose/foundation/gestures/Orientation;)V
+HSPLandroidx/tv/foundation/lazy/list/LazyLayoutBeyondBoundsModifierLocal;->getKey()Landroidx/compose/ui/modifier/ProvidableModifierLocal;
+HSPLandroidx/tv/foundation/lazy/list/LazyListBeyondBoundsState;-><init>(Landroidx/tv/foundation/lazy/list/TvLazyListState;I)V
+HSPLandroidx/tv/foundation/lazy/list/LazyListItemProviderImpl;-><init>(Landroidx/tv/foundation/lazy/list/TvLazyListState;Landroidx/tv/foundation/lazy/list/TvLazyListIntervalContent;Landroidx/tv/foundation/lazy/list/TvLazyListItemScopeImpl;Landroidx/tv/foundation/lazy/layout/NearestRangeKeyIndexMap;)V
+HSPLandroidx/tv/foundation/lazy/list/LazyListItemProviderImpl;->Item(ILjava/lang/Object;Landroidx/compose/runtime/Composer;I)V
+HSPLandroidx/tv/foundation/lazy/list/LazyListItemProviderImpl;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/tv/foundation/lazy/list/LazyListItemProviderImpl;->getContentType(I)Ljava/lang/Object;
+HSPLandroidx/tv/foundation/lazy/list/LazyListItemProviderImpl;->getItemCount()I
+HSPLandroidx/tv/foundation/lazy/list/LazyListItemProviderImpl;->getKey(I)Ljava/lang/Object;
+HSPLandroidx/tv/foundation/lazy/list/LazyListKt$rememberLazyListMeasurePolicy$1$1$measuredItemProvider$1;-><init>(JZLandroidx/tv/foundation/lazy/list/LazyListItemProviderImpl;Landroidx/compose/foundation/lazy/layout/LazyLayoutMeasureScopeImpl;IILandroidx/compose/ui/Alignment$Horizontal;Landroidx/compose/ui/Alignment$Vertical;ZIIJ)V
+HSPLandroidx/tv/foundation/lazy/list/LazyListKt$rememberLazyListMeasurePolicy$1$1$measuredItemProvider$1;->getAndMeasure(I)Landroidx/tv/foundation/lazy/list/LazyListMeasuredItem;
+HSPLandroidx/tv/foundation/lazy/list/LazyListKt$rememberLazyListMeasurePolicy$1$1;-><init>(Landroidx/tv/foundation/lazy/list/TvLazyListState;ZLandroidx/compose/foundation/layout/PaddingValuesImpl;ZLkotlin/reflect/KProperty0;Landroidx/compose/foundation/layout/Arrangement$Vertical;Landroidx/compose/foundation/layout/Arrangement$Horizontal;ILandroidx/compose/ui/Alignment$Horizontal;Landroidx/compose/ui/Alignment$Vertical;)V
+HSPLandroidx/tv/foundation/lazy/list/LazyListKt$rememberLazyListMeasurePolicy$1$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/foundation/lazy/list/LazyListMeasureResult;-><init>(Landroidx/tv/foundation/lazy/list/LazyListMeasuredItem;IZFLandroidx/compose/ui/layout/MeasureResult;FLjava/util/List;II)V
+HSPLandroidx/tv/foundation/lazy/list/LazyListMeasureResult;->getAlignmentLines()Ljava/util/Map;
+HSPLandroidx/tv/foundation/lazy/list/LazyListMeasureResult;->getHeight()I
+HSPLandroidx/tv/foundation/lazy/list/LazyListMeasureResult;->getTotalItemsCount()I
+HSPLandroidx/tv/foundation/lazy/list/LazyListMeasureResult;->getVisibleItemsInfo()Ljava/util/List;
+HSPLandroidx/tv/foundation/lazy/list/LazyListMeasureResult;->getWidth()I
+HSPLandroidx/tv/foundation/lazy/list/LazyListMeasureResult;->placeChildren()V
+HSPLandroidx/tv/foundation/lazy/list/LazyListMeasuredItem;-><init>(ILjava/util/List;ZLandroidx/compose/ui/Alignment$Horizontal;Landroidx/compose/ui/Alignment$Vertical;Landroidx/compose/ui/unit/LayoutDirection;ZIIIJLjava/lang/Object;Ljava/lang/Object;)V
+HSPLandroidx/tv/foundation/lazy/list/LazyListMeasuredItem;->getParentData(I)V
+HSPLandroidx/tv/foundation/lazy/list/LazyListMeasuredItem;->place(Landroidx/compose/ui/layout/Placeable$PlacementScope;)V
+HSPLandroidx/tv/foundation/lazy/list/LazyListMeasuredItem;->position(III)V
+HSPLandroidx/tv/foundation/lazy/list/LazyListStateKt$rememberTvLazyListState$1$1;-><init>(III)V
+HSPLandroidx/tv/foundation/lazy/list/LazyListStateKt$rememberTvLazyListState$1$1;->invoke()Ljava/lang/Object;
+HSPLandroidx/tv/foundation/lazy/list/LazyListStateKt;-><clinit>()V
+HSPLandroidx/tv/foundation/lazy/list/LazyListStateKt;->rememberTvLazyListState(Landroidx/compose/runtime/Composer;)Landroidx/tv/foundation/lazy/list/TvLazyListState;
+HSPLandroidx/tv/foundation/lazy/list/TvLazyListInterval;-><init>(Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/internal/ComposableLambdaImpl;)V
+HSPLandroidx/tv/foundation/lazy/list/TvLazyListInterval;->getKey()Lkotlin/jvm/functions/Function1;
+HSPLandroidx/tv/foundation/lazy/list/TvLazyListInterval;->getType()Lkotlin/jvm/functions/Function1;
+HSPLandroidx/tv/foundation/lazy/list/TvLazyListIntervalContent;-><init>(Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/tv/foundation/lazy/list/TvLazyListIntervalContent;->getIntervals()Landroidx/compose/foundation/lazy/layout/MutableIntervalList;
+HSPLandroidx/tv/foundation/lazy/list/TvLazyListItemScopeImpl;-><init>()V
+HSPLandroidx/tv/foundation/lazy/list/TvLazyListScope;->items$default(Landroidx/tv/foundation/lazy/list/TvLazyListScope;ILandroidx/compose/runtime/internal/ComposableLambdaImpl;)V
+HSPLandroidx/tv/foundation/lazy/list/TvLazyListState$scroll$1;-><init>(Landroidx/tv/foundation/lazy/list/TvLazyListState;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/tv/foundation/lazy/list/TvLazyListState$scroll$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/foundation/lazy/list/TvLazyListState;-><clinit>()V
+HSPLandroidx/tv/foundation/lazy/list/TvLazyListState;-><init>(II)V
+HSPLandroidx/tv/foundation/lazy/list/TvLazyListState;->applyMeasureResult$tv_foundation_release(Landroidx/tv/foundation/lazy/list/LazyListMeasureResult;Z)V
+HSPLandroidx/tv/foundation/lazy/list/TvLazyListState;->getCanScrollBackward()Z
+HSPLandroidx/tv/foundation/lazy/list/TvLazyListState;->getCanScrollForward()Z
+HSPLandroidx/tv/foundation/lazy/list/TvLazyListState;->scroll(Landroidx/compose/foundation/MutatePriority;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/Border;-><clinit>()V
+HSPLandroidx/tv/material3/Border;-><init>(Landroidx/compose/foundation/BorderStroke;FLandroidx/compose/ui/graphics/Shape;)V
+HSPLandroidx/tv/material3/Border;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/tv/material3/ColorScheme;-><init>(JJJJJJJJJJJJJJJJJJJJJJJJJJJJJ)V
+HSPLandroidx/tv/material3/ColorScheme;->getInverseSurface-0d7_KjU()J
+HSPLandroidx/tv/material3/ColorScheme;->getOnSurface-0d7_KjU()J
+HSPLandroidx/tv/material3/ColorScheme;->getSurface-0d7_KjU()J
+HSPLandroidx/tv/material3/ColorSchemeKt$LocalColorScheme$1;-><clinit>()V
+HSPLandroidx/tv/material3/ColorSchemeKt$LocalColorScheme$1;-><init>(I)V
+HSPLandroidx/tv/material3/ColorSchemeKt$LocalColorScheme$1;->invoke()Ljava/lang/Object;
+HSPLandroidx/tv/material3/ColorSchemeKt;-><clinit>()V
+HSPLandroidx/tv/material3/ColorSchemeKt;->contentColorFor-ek8zF_U(JLandroidx/compose/runtime/Composer;)J
+HSPLandroidx/tv/material3/ContentColorKt;-><clinit>()V
+HSPLandroidx/tv/material3/Glow;-><clinit>()V
+HSPLandroidx/tv/material3/Glow;-><init>(JF)V
+HSPLandroidx/tv/material3/Glow;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/tv/material3/GlowIndication;-><init>(JLandroidx/compose/ui/graphics/Shape;FFF)V
+HSPLandroidx/tv/material3/GlowIndication;->rememberUpdatedInstance(Landroidx/compose/foundation/interaction/InteractionSource;Landroidx/compose/runtime/Composer;)Landroidx/compose/foundation/IndicationInstance;
+HSPLandroidx/tv/material3/GlowIndicationInstance;-><init>(JLandroidx/compose/ui/graphics/Shape;Landroidx/compose/ui/unit/Density;FFF)V
+HSPLandroidx/tv/material3/GlowIndicationInstance;->drawIndication(Landroidx/compose/ui/graphics/drawscope/ContentDrawScope;)V
+HSPLandroidx/tv/material3/ScaleIndication;-><init>(F)V
+HSPLandroidx/tv/material3/ScaleIndicationInstance;-><init>(F)V
+HSPLandroidx/tv/material3/ScaleIndicationInstance;->drawIndication(Landroidx/compose/ui/graphics/drawscope/ContentDrawScope;)V
+HSPLandroidx/tv/material3/ScaleIndicationTokens;-><clinit>()V
+HSPLandroidx/tv/material3/ShapesKt$LocalShapes$1;-><clinit>()V
+HSPLandroidx/tv/material3/ShapesKt$LocalShapes$1;-><init>(I)V
+HSPLandroidx/tv/material3/ShapesKt$LocalShapes$1;->invoke()Ljava/lang/Object;
+HSPLandroidx/tv/material3/SurfaceKt$Surface$4;-><init>(ZLkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;ZLkotlin/jvm/functions/Function0;FLandroidx/tv/material3/ToggleableSurfaceShape;Landroidx/tv/material3/ToggleableSurfaceColors;Landroidx/tv/material3/ToggleableSurfaceScale;Landroidx/tv/material3/ToggleableSurfaceBorder;Landroidx/tv/material3/ToggleableSurfaceGlow;Landroidx/compose/foundation/interaction/MutableInteractionSourceImpl;Lkotlin/jvm/functions/Function3;III)V
+HSPLandroidx/tv/material3/SurfaceKt$SurfaceImpl$2$2$1;-><init>(ILjava/lang/Object;)V
+HSPLandroidx/tv/material3/SurfaceKt$SurfaceImpl$2$2$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/SurfaceKt$SurfaceImpl$2$4$1;-><init>(Landroidx/compose/ui/graphics/Shape;JI)V
+HSPLandroidx/tv/material3/SurfaceKt$SurfaceImpl$2$4$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/SurfaceKt$SurfaceImpl$2$5$1;-><init>(FLandroidx/compose/ui/graphics/Shape;)V
+HSPLandroidx/tv/material3/SurfaceKt$SurfaceImpl$2$5$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/SurfaceKt$SurfaceImpl$2;-><init>(JILandroidx/compose/ui/Modifier;Landroidx/compose/foundation/interaction/MutableInteractionSourceImpl;FLandroidx/tv/material3/Glow;Landroidx/compose/ui/graphics/Shape;Landroidx/tv/material3/Border;FLandroidx/compose/runtime/MutableState;ZLkotlin/jvm/functions/Function3;I)V
+HSPLandroidx/tv/material3/SurfaceKt$SurfaceImpl$3;-><init>(Landroidx/compose/ui/Modifier;ZZLandroidx/compose/ui/graphics/Shape;JJFLandroidx/tv/material3/Border;Landroidx/tv/material3/Glow;FLandroidx/compose/foundation/interaction/MutableInteractionSourceImpl;Lkotlin/jvm/functions/Function3;III)V
+HSPLandroidx/tv/material3/SurfaceKt$SurfaceImpl$3;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/SurfaceKt$handleDPadEnter$2$1;-><init>(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;I)V
+HSPLandroidx/tv/material3/SurfaceKt$handleDPadEnter$2$1;->invoke(Landroidx/compose/animation/core/AnimationScope;)V
+HSPLandroidx/tv/material3/SurfaceKt$handleDPadEnter$2$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/SurfaceKt$handleDPadEnter$2$2;-><init>(Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;ZLandroidx/compose/foundation/interaction/MutableInteractionSourceImpl;Landroidx/compose/foundation/interaction/PressInteraction$Press;Landroidx/compose/runtime/MutableState;)V
+HSPLandroidx/tv/material3/SurfaceKt$handleDPadEnter$2$2;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/SurfaceKt$handleDPadEnter$2;-><init>(Landroidx/compose/foundation/interaction/MutableInteractionSourceImpl;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;ZZ)V
+HSPLandroidx/tv/material3/SurfaceKt$handleDPadEnter$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/SurfaceKt$tvToggleable$1$1;-><init>(Landroidx/compose/ui/platform/AndroidComposeView;Z)V
+HSPLandroidx/tv/material3/SurfaceKt$tvToggleable$1$2;-><init>(Lkotlin/jvm/functions/Function0;I)V
+HSPLandroidx/tv/material3/SurfaceKt$tvToggleable$1$2;->invoke()Ljava/lang/Object;
+HSPLandroidx/tv/material3/SurfaceKt$tvToggleable$1;-><init>(ZLkotlin/jvm/functions/Function1;ZLkotlin/jvm/functions/Function0;)V
+HSPLandroidx/tv/material3/SurfaceKt;-><clinit>()V
+HSPLandroidx/tv/material3/SurfaceKt;->Surface-xYaah8o(ZLkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;ZLkotlin/jvm/functions/Function0;FLandroidx/tv/material3/ToggleableSurfaceShape;Landroidx/tv/material3/ToggleableSurfaceColors;Landroidx/tv/material3/ToggleableSurfaceScale;Landroidx/tv/material3/ToggleableSurfaceBorder;Landroidx/tv/material3/ToggleableSurfaceGlow;Landroidx/compose/foundation/interaction/MutableInteractionSourceImpl;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;III)V
+HSPLandroidx/tv/material3/SurfaceKt;->Surface_xYaah8o$lambda$4(Landroidx/compose/runtime/MutableState;)Z
+HSPLandroidx/tv/material3/SurfaceKt;->Surface_xYaah8o$lambda$5(Landroidx/compose/runtime/MutableState;)Z
+HSPLandroidx/tv/material3/SurfaceKt;->access$surfaceColorAtElevation-CLU3JFs(JFLandroidx/compose/runtime/Composer;)J
+HSPLandroidx/tv/material3/TabColors;-><init>(JJJJJJJJ)V
+HSPLandroidx/tv/material3/TabKt$Tab$1;-><clinit>()V
+HSPLandroidx/tv/material3/TabKt$Tab$1;-><init>()V
+HSPLandroidx/tv/material3/TabKt$Tab$3$1;-><init>(Lkotlin/jvm/functions/Function0;I)V
+HSPLandroidx/tv/material3/TabKt$Tab$3$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/TabKt$Tab$4$1;-><init>(IZ)V
+HSPLandroidx/tv/material3/TabKt$Tab$4$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/TabKt$Tab$6;-><init>(Lkotlin/jvm/functions/Function3;I)V
+HSPLandroidx/tv/material3/TabKt$Tab$6;->invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/TabKt$Tab$7;-><init>(Landroidx/tv/material3/TabRowScopeImpl;ZLkotlin/jvm/functions/Function0;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function0;ZLandroidx/tv/material3/TabColors;Landroidx/compose/foundation/interaction/MutableInteractionSourceImpl;Lkotlin/jvm/functions/Function3;II)V
+HSPLandroidx/tv/material3/TabKt;-><clinit>()V
+HSPLandroidx/tv/material3/TabKt;->BasicText-BpD7jsM(Ljava/lang/String;Landroidx/compose/ui/Modifier;Landroidx/compose/ui/text/TextStyle;Lkotlin/jvm/functions/Function1;IZILandroidx/compose/runtime/Composer;II)V
+HSPLandroidx/tv/material3/TabKt;->BasicText-VhcvRP8(Ljava/lang/String;Landroidx/compose/ui/Modifier;Landroidx/compose/ui/text/TextStyle;Lkotlin/jvm/functions/Function1;IZIILandroidx/compose/runtime/Composer;II)V
+HSPLandroidx/tv/material3/TabKt;->DisposableEffect(Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;)V
+HSPLandroidx/tv/material3/TabKt;->LaunchedEffect(Ljava/lang/Object;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;)V
+HSPLandroidx/tv/material3/TabKt;->LaunchedEffect(Ljava/lang/Object;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;)V
+HSPLandroidx/tv/material3/TabKt;->LazyLayout(Lkotlin/jvm/functions/Function0;Landroidx/compose/ui/Modifier;Landroidx/compose/foundation/lazy/layout/LazyLayoutPrefetchState;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V
+HSPLandroidx/tv/material3/TabKt;->LazyLayoutPrefetcher(Landroidx/compose/foundation/lazy/layout/LazyLayoutPrefetchState;Landroidx/compose/foundation/lazy/layout/LazyLayoutItemContentFactory;Landroidx/compose/ui/layout/SubcomposeLayoutState;Landroidx/compose/runtime/Composer;I)V
+HSPLandroidx/tv/material3/TabKt;->SideEffect(Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;)V
+HSPLandroidx/tv/material3/TabKt;->Tab(Landroidx/tv/material3/TabRowScopeImpl;ZLkotlin/jvm/functions/Function0;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function0;ZLandroidx/tv/material3/TabColors;Landroidx/compose/foundation/interaction/MutableInteractionSourceImpl;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V
+HSPLandroidx/tv/material3/TabKt;->TabRow-pAZo6Ak(ILandroidx/compose/ui/Modifier;JJLkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V
+HSPLandroidx/tv/material3/TabKt;->access$binarySearch(ILandroidx/compose/runtime/collection/MutableVector;)I
+HSPLandroidx/tv/material3/TabKt;->animate(Landroidx/compose/animation/core/AnimationState;Landroidx/compose/animation/core/Animation;JLkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/TabKt;->callWithFrameNanos(Landroidx/compose/animation/core/Animation;Lkotlin/jvm/functions/Function1;Landroidx/compose/animation/core/SuspendAnimationKt$animate$4;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/TabKt;->collectIsFocusedAsState(Landroidx/compose/foundation/interaction/InteractionSource;Landroidx/compose/runtime/Composer;I)Landroidx/compose/runtime/MutableState;
+HSPLandroidx/tv/material3/TabKt;->complexSqrt(D)Landroidx/compose/animation/core/ComplexDouble;
+HSPLandroidx/tv/material3/TabKt;->copy(Landroidx/compose/animation/core/AnimationVector;)Landroidx/compose/animation/core/AnimationVector;
+HSPLandroidx/tv/material3/TabKt;->createCompositionCoroutineScope(Landroidx/compose/runtime/Composer;)Lkotlinx/coroutines/internal/ContextScope;
+HSPLandroidx/tv/material3/TabKt;->derivedStateObservers()Landroidx/compose/runtime/collection/MutableVector;
+HSPLandroidx/tv/material3/TabKt;->doAnimationFrameWithScale(Landroidx/compose/animation/core/AnimationScope;JFLandroidx/compose/animation/core/Animation;Landroidx/compose/animation/core/AnimationState;Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/tv/material3/TabKt;->finalConstraints-tfFHcEY(JZIF)J
+HSPLandroidx/tv/material3/TabKt;->getContentType(I)Ljava/lang/Object;
+HSPLandroidx/tv/material3/TabKt;->getDurationScale(Lkotlin/coroutines/CoroutineContext;)F
+HSPLandroidx/tv/material3/TabKt;->getPoolingContainerListenerHolder(Landroid/view/View;)Landroidx/customview/poolingcontainer/PoolingContainerListenerHolder;
+HSPLandroidx/tv/material3/TabKt;->mutableStateOf$default(Ljava/lang/Object;)Landroidx/compose/runtime/ParcelableSnapshotMutableState;
+HSPLandroidx/tv/material3/TabKt;->read(Landroidx/compose/runtime/PersistentCompositionLocalMap;Landroidx/compose/runtime/ProvidableCompositionLocal;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/TabKt;->rememberSaveable([Ljava/lang/Object;Landroidx/compose/runtime/saveable/SaverKt$Saver$1;Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/TabKt;->rememberUpdatedState(Ljava/lang/Object;Landroidx/compose/runtime/Composer;)Landroidx/compose/runtime/MutableState;
+HSPLandroidx/tv/material3/TabKt;->resumeCancellableWith(Lkotlin/coroutines/Continuation;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/tv/material3/TabKt;->spring$default(FLjava/lang/Comparable;I)Landroidx/compose/animation/core/SpringSpec;
+HSPLandroidx/tv/material3/TabKt;->tween$default(ILandroidx/compose/animation/core/Easing;)Landroidx/compose/animation/core/TweenSpec;
+HSPLandroidx/tv/material3/TabKt;->updateCompositionMap([Landroidx/compose/runtime/ProvidedValue;Landroidx/compose/runtime/PersistentCompositionLocalMap;Landroidx/compose/runtime/PersistentCompositionLocalMap;)Landroidx/compose/runtime/internal/PersistentCompositionLocalHashMap;
+HSPLandroidx/tv/material3/TabKt;->updateState(Landroidx/compose/animation/core/AnimationScope;Landroidx/compose/animation/core/AnimationState;)V
+HSPLandroidx/tv/material3/TabKt;->withFrameNanos(Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/TabRowDefaults$PillIndicator$1;-><init>(Landroidx/tv/material3/TabRowDefaults;Landroidx/compose/ui/unit/DpRect;ZLandroidx/compose/ui/Modifier;JJII)V
+HSPLandroidx/tv/material3/TabRowDefaults$PillIndicator$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/TabRowDefaults;-><clinit>()V
+HSPLandroidx/tv/material3/TabRowDefaults;->PillIndicator-jA1GFJw(Landroidx/compose/ui/unit/DpRect;ZLandroidx/compose/ui/Modifier;JJLandroidx/compose/runtime/Composer;II)V
+HSPLandroidx/tv/material3/TabRowKt$TabRow$1;-><init>(I)V
+HSPLandroidx/tv/material3/TabRowKt$TabRow$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/TabRowKt$TabRow$2$1$1;-><init>(Landroidx/compose/runtime/MutableState;I)V
+HSPLandroidx/tv/material3/TabRowKt$TabRow$2$1$1;->invoke(Landroidx/compose/ui/focus/FocusState;)V
+HSPLandroidx/tv/material3/TabRowKt$TabRow$2$1$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/TabRowKt$TabRow$2$2$1$1$2;-><init>(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;II)V
+HSPLandroidx/tv/material3/TabRowKt$TabRow$2$2$1$1$2;-><init>(Lkotlin/jvm/functions/Function4;Ljava/util/ArrayList;ILandroidx/compose/runtime/MutableState;)V
+HSPLandroidx/tv/material3/TabRowKt$TabRow$2$2$1$1$2;->invoke(Landroidx/compose/runtime/Composer;I)V
+HSPLandroidx/tv/material3/TabRowKt$TabRow$2$2$1$1$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/TabRowKt$TabRow$2$2$1$1;-><init>(Ljava/util/ArrayList;Landroidx/compose/ui/layout/SubcomposeMeasureScope;Ljava/util/ArrayList;ILkotlin/jvm/functions/Function4;ILandroidx/compose/runtime/MutableState;II)V
+HSPLandroidx/tv/material3/TabRowKt$TabRow$2$2$1$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/TabRowKt$TabRow$2$2$1$separators$1;-><init>(IILkotlin/jvm/functions/Function1;)V
+HSPLandroidx/tv/material3/TabRowKt$TabRow$2$2$1$separators$1;-><init>(ILkotlin/jvm/functions/Function2;I)V
+HSPLandroidx/tv/material3/TabRowKt$TabRow$2$2$1$separators$1;->invoke(Landroidx/compose/runtime/Composer;I)V
+HSPLandroidx/tv/material3/TabRowKt$TabRow$2$2$1$separators$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/TabRowKt$TabRow$2$2$1;-><init>(Landroidx/compose/runtime/MutableState;Lkotlin/jvm/functions/Function3;ILkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function4;)V
+HSPLandroidx/tv/material3/TabRowKt$TabRow$2$2$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/TabRowKt$TabRow$2;-><init>(Landroidx/compose/ui/Modifier;JLandroidx/compose/foundation/ScrollState;Landroidx/compose/runtime/MutableState;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function4;I)V
+HSPLandroidx/tv/material3/TabRowKt$TabRow$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/TabRowKt$TabRow$3;-><init>(ILandroidx/compose/ui/Modifier;JJLkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function3;II)V
+HSPLandroidx/tv/material3/TabRowScopeImpl;-><init>(Z)V
+HSPLandroidx/tv/material3/TabRowSlots;-><clinit>()V
+HSPLandroidx/tv/material3/TabRowSlots;-><init>(ILjava/lang/String;)V
+HSPLandroidx/tv/material3/TextKt$Text$1;-><clinit>()V
+HSPLandroidx/tv/material3/TextKt$Text$1;-><init>()V
+HSPLandroidx/tv/material3/TextKt$Text$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/TextKt$Text$2;-><init>(Ljava/lang/String;Landroidx/compose/ui/Modifier;JJLandroidx/compose/ui/text/font/FontStyle;Landroidx/compose/ui/text/font/FontWeight;Landroidx/compose/ui/text/font/FontFamily;JLandroidx/compose/ui/text/style/TextDecoration;Landroidx/compose/ui/text/style/TextAlign;JIZILkotlin/jvm/functions/Function1;Landroidx/compose/ui/text/TextStyle;IIII)V
+HSPLandroidx/tv/material3/TextKt;-><clinit>()V
+HSPLandroidx/tv/material3/TextKt;->Text-fLXpl1I(Ljava/lang/String;Landroidx/compose/ui/Modifier;JJLandroidx/compose/ui/text/font/FontStyle;Landroidx/compose/ui/text/font/FontWeight;Landroidx/compose/ui/text/font/FontFamily;JLandroidx/compose/ui/text/style/TextDecoration;Landroidx/compose/ui/text/style/TextAlign;JIZILkotlin/jvm/functions/Function1;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/runtime/Composer;III)V
+HSPLandroidx/tv/material3/ToggleableSurfaceBorder;-><init>(Landroidx/tv/material3/Border;Landroidx/tv/material3/Border;Landroidx/tv/material3/Border;Landroidx/tv/material3/Border;Landroidx/tv/material3/Border;Landroidx/tv/material3/Border;Landroidx/tv/material3/Border;Landroidx/tv/material3/Border;Landroidx/tv/material3/Border;Landroidx/tv/material3/Border;)V
+HSPLandroidx/tv/material3/ToggleableSurfaceColors;-><init>(JJJJJJJJJJJJJJ)V
+HSPLandroidx/tv/material3/ToggleableSurfaceColors;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/tv/material3/ToggleableSurfaceGlow;-><init>(Landroidx/tv/material3/Glow;Landroidx/tv/material3/Glow;Landroidx/tv/material3/Glow;Landroidx/tv/material3/Glow;Landroidx/tv/material3/Glow;Landroidx/tv/material3/Glow;)V
+HSPLandroidx/tv/material3/ToggleableSurfaceScale;-><clinit>()V
+HSPLandroidx/tv/material3/ToggleableSurfaceScale;-><init>(FFFFFFFFFF)V
+HSPLandroidx/tv/material3/ToggleableSurfaceShape;-><init>(Landroidx/compose/ui/graphics/Shape;Landroidx/compose/ui/graphics/Shape;Landroidx/compose/ui/graphics/Shape;Landroidx/compose/ui/graphics/Shape;Landroidx/compose/ui/graphics/Shape;Landroidx/compose/ui/graphics/Shape;Landroidx/compose/ui/graphics/Shape;Landroidx/compose/ui/graphics/Shape;Landroidx/compose/ui/graphics/Shape;Landroidx/compose/ui/graphics/Shape;)V
+HSPLandroidx/tv/material3/ToggleableSurfaceShape;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/tv/material3/tokens/ColorLightTokens;-><clinit>()V
+HSPLandroidx/tv/material3/tokens/Elevation;-><clinit>()V
+HSPLandroidx/tv/material3/tokens/PaletteTokens;-><clinit>()V
+HSPLandroidx/tv/material3/tokens/ShapeTokens$BorderDefaultShape$1;-><clinit>()V
+HSPLandroidx/tv/material3/tokens/ShapeTokens$BorderDefaultShape$1;-><init>(I)V
+HSPLandroidx/tv/material3/tokens/ShapeTokens$BorderDefaultShape$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/tokens/ShapeTokens$BorderDefaultShape$1;->invoke(Ljava/util/List;II)Ljava/lang/Integer;
+HSPLandroidx/tv/material3/tokens/ShapeTokens$BorderDefaultShape$1;->invoke-3p2s80s(Landroidx/compose/ui/layout/MeasureScope;Landroidx/compose/ui/layout/Measurable;J)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/tv/material3/tokens/TypographyTokensKt;-><clinit>()V
+HSPLcom/example/tvcomposebasedtests/ComposableSingletons$MainActivityKt;-><clinit>()V
+HSPLcom/example/tvcomposebasedtests/ComposableSingletons$UtilsKt$lambda-1$1;-><clinit>()V
+HSPLcom/example/tvcomposebasedtests/ComposableSingletons$UtilsKt$lambda-1$1;-><init>(I)V
+HSPLcom/example/tvcomposebasedtests/ComposableSingletons$UtilsKt$lambda-1$1;->invoke(Landroidx/tv/foundation/lazy/list/TvLazyListItemScopeImpl;ILandroidx/compose/runtime/Composer;I)V
+HSPLcom/example/tvcomposebasedtests/ComposableSingletons$UtilsKt$lambda-1$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLcom/example/tvcomposebasedtests/Config;-><init>(Landroid/content/Context;Landroidx/activity/ComponentActivity;II)V
+HSPLcom/example/tvcomposebasedtests/Config;->toString()Ljava/lang/String;
+HSPLcom/example/tvcomposebasedtests/JankStatsAggregator$listener$1;-><init>(Lcom/example/tvcomposebasedtests/JankStatsAggregator;)V
+HSPLcom/example/tvcomposebasedtests/JankStatsAggregator;-><init>(Landroid/view/Window;Lcom/example/tvcomposebasedtests/MainActivity$jankReportListener$1;)V
+HSPLcom/example/tvcomposebasedtests/MainActivity$jankReportListener$1;-><init>(Lcom/example/tvcomposebasedtests/MainActivity;)V
+HSPLcom/example/tvcomposebasedtests/MainActivity$startFrameMetrics$listener$1;-><init>(Lcom/example/tvcomposebasedtests/MainActivity;)V
+HSPLcom/example/tvcomposebasedtests/MainActivity$startFrameMetrics$listener$1;->onFrameMetricsAvailable(Landroid/view/Window;Landroid/view/FrameMetrics;I)V
+HSPLcom/example/tvcomposebasedtests/MainActivity;-><init>()V
+HSPLcom/example/tvcomposebasedtests/MainActivity;->onCreate(Landroid/os/Bundle;)V
+HSPLcom/example/tvcomposebasedtests/MainActivity;->onResume()V
+HSPLcom/example/tvcomposebasedtests/UtilsKt$AddJankMetrics$1$2;-><init>(ILjava/lang/Object;)V
+HSPLcom/example/tvcomposebasedtests/UtilsKt$AddJankMetrics$1$2;->emit(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLcom/example/tvcomposebasedtests/UtilsKt$ScrollingRow$1$1$1;-><init>(II)V
+HSPLcom/example/tvcomposebasedtests/UtilsKt$ScrollingRow$1$1$1;->invoke(Landroidx/tv/foundation/lazy/list/TvLazyListScope;)V
+HSPLcom/example/tvcomposebasedtests/UtilsKt$ScrollingRow$1$1$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLcom/example/tvcomposebasedtests/UtilsKt$ScrollingRow$2;-><init>(III)V
+HSPLcom/example/tvcomposebasedtests/UtilsKt$ScrollingRow$2;->invoke(Landroidx/compose/runtime/Composer;I)V
+HSPLcom/example/tvcomposebasedtests/UtilsKt$ScrollingRow$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLcom/example/tvcomposebasedtests/UtilsKt;-><clinit>()V
+HSPLcom/example/tvcomposebasedtests/tvComponents/AppKt$App$1;-><init>(ILjava/lang/Object;)V
+HSPLcom/example/tvcomposebasedtests/tvComponents/AppKt$App$1;->invoke(Landroidx/compose/runtime/Composer;I)V
+HSPLcom/example/tvcomposebasedtests/tvComponents/AppKt$App$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLcom/example/tvcomposebasedtests/tvComponents/AppKt$App$1;->invoke-5SAbXVA(JLandroidx/compose/ui/unit/LayoutDirection;)J
+HSPLcom/example/tvcomposebasedtests/tvComponents/ComposableSingletons$LazyContainersKt;-><clinit>()V
+HSPLcom/example/tvcomposebasedtests/tvComponents/ComposableSingletons$TopNavigationKt;-><clinit>()V
+HSPLcom/example/tvcomposebasedtests/tvComponents/Navigation;-><clinit>()V
+HSPLcom/example/tvcomposebasedtests/tvComponents/Navigation;-><init>(Ljava/lang/String;ILjava/lang/String;Landroidx/compose/runtime/internal/ComposableLambdaImpl;)V
+HSPLcom/example/tvcomposebasedtests/tvComponents/Navigation;->values()[Lcom/example/tvcomposebasedtests/tvComponents/Navigation;
+HSPLcom/example/tvcomposebasedtests/tvComponents/TopNavigationKt$PillIndicatorTabRow$1$1$1$1;-><init>(ILkotlin/jvm/functions/Function1;)V
+HSPLcom/example/tvcomposebasedtests/tvComponents/TopNavigationKt$PillIndicatorTabRow$1$1$1$1;->invoke()Ljava/lang/Object;
+HSPLcom/example/tvcomposebasedtests/tvComponents/TopNavigationKt$PillIndicatorTabRow$1;-><init>(IILjava/util/List;Lkotlin/jvm/functions/Function1;)V
+HSPLcom/example/tvcomposebasedtests/tvComponents/TopNavigationKt$PillIndicatorTabRow$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLcom/example/tvcomposebasedtests/tvComponents/TopNavigationKt$TopNavigation$3$1;-><init>(Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/MutableState;Lkotlin/coroutines/Continuation;)V
+HSPLcom/example/tvcomposebasedtests/tvComponents/TopNavigationKt$TopNavigation$3$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLcom/example/tvcomposebasedtests/tvComponents/TopNavigationKt$TopNavigation$3$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLcom/google/gson/internal/ConstructorConstructor;-><init>()V
+HSPLcom/google/gson/internal/ConstructorConstructor;->access$commitTransaction(Lcom/google/gson/internal/ConstructorConstructor;)V
+HSPLcom/google/gson/internal/LinkedTreeMap$1;-><init>(I)V
+HSPLcom/google/gson/internal/LinkedTreeMap$1;->compare(Ljava/lang/Object;Ljava/lang/Object;)I
+HSPLkotlin/Pair;-><init>(Ljava/lang/Object;Ljava/lang/Object;)V
+HSPLkotlin/Result$Failure;-><init>(Ljava/lang/Throwable;)V
+HSPLkotlin/Result;->exceptionOrNull-impl(Ljava/lang/Object;)Ljava/lang/Throwable;
+HSPLkotlin/ResultKt$$ExternalSyntheticCheckNotZero0;->m$1()Ljava/util/Iterator;
+HSPLkotlin/ResultKt$$ExternalSyntheticCheckNotZero0;->m(III)I
+HSPLkotlin/ResultKt$$ExternalSyntheticCheckNotZero0;->m(ILjava/lang/String;)V
+HSPLkotlin/ResultKt$$ExternalSyntheticCheckNotZero0;->m(Ljava/lang/Object;)V
+HSPLkotlin/ResultKt$$ExternalSyntheticCheckNotZero0;->stringValueOf$1(I)Ljava/lang/String;
+HSPLkotlin/ResultKt$$ExternalSyntheticCheckNotZero0;->stringValueOf$2(I)Ljava/lang/String;
+HSPLkotlin/ResultKt$$ExternalSyntheticCheckNotZero0;->valueOf$1(Ljava/lang/String;)I
+HSPLkotlin/ResultKt$$ExternalSyntheticCheckNotZero0;->valueOf(Ljava/lang/String;)I
+HSPLkotlin/ResultKt;-><clinit>()V
+HSPLkotlin/ResultKt;-><init>(Landroidx/metrics/performance/JankStats;)V
+HSPLkotlin/ResultKt;->App(Landroidx/compose/runtime/Composer;I)V
+HSPLkotlin/ResultKt;->Constraints$default(III)J
+HSPLkotlin/ResultKt;->Constraints(IIII)J
+HSPLkotlin/ResultKt;->Density(Landroid/content/Context;)Landroidx/compose/ui/unit/DensityImpl;
+HSPLkotlin/ResultKt;->DpOffset-YgX7TsA(FF)J
+HSPLkotlin/ResultKt;->IntOffset(II)J
+HSPLkotlin/ResultKt;->IntSize(II)J
+HSPLkotlin/ResultKt;->MaterialTheme(Landroidx/compose/material3/ColorScheme;Landroidx/compose/material3/Shapes;Landroidx/compose/material3/Typography;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V
+HSPLkotlin/ResultKt;->SampleCardItem(ILandroidx/compose/runtime/Composer;I)V
+HSPLkotlin/ResultKt;->SampleTvLazyRow(ILandroidx/compose/runtime/Composer;I)V
+HSPLkotlin/ResultKt;->TvLazyRow(Landroidx/compose/ui/Modifier;Landroidx/tv/foundation/lazy/list/TvLazyListState;Landroidx/compose/foundation/layout/PaddingValuesImpl;ZLandroidx/compose/foundation/layout/Arrangement$Horizontal;Landroidx/compose/ui/Alignment$Vertical;ZLandroidx/tv/foundation/PivotOffsets;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V
+HSPLkotlin/ResultKt;->access$getHasEmojiCompat(Landroidx/compose/ui/text/TextStyle;)Z
+HSPLkotlin/ResultKt;->areEqual(Ljava/lang/Object;Ljava/lang/Object;)Z
+HSPLkotlin/ResultKt;->canBeSavedToBundle(Ljava/lang/Object;)Z
+HSPLkotlin/ResultKt;->checkArgument(Ljava/lang/String;Z)V
+HSPLkotlin/ResultKt;->checkElementIndex$runtime_release(II)V
+HSPLkotlin/ResultKt;->checkNotNull$1(Ljava/lang/Object;Ljava/lang/String;)V
+HSPLkotlin/ResultKt;->checkNotNull(Ljava/lang/Object;)V
+HSPLkotlin/ResultKt;->checkNotNull(Ljava/lang/Object;Ljava/lang/String;)V
+HSPLkotlin/ResultKt;->checkNotNullExpressionValue(Ljava/lang/Object;Ljava/lang/String;)V
+HSPLkotlin/ResultKt;->checkNotNullParameter(Ljava/lang/Object;Ljava/lang/String;)V
+HSPLkotlin/ResultKt;->compare(II)I
+HSPLkotlin/ResultKt;->constrain-4WqzIAM(JJ)J
+HSPLkotlin/ResultKt;->constrainHeight-K40F9xA(JI)I
+HSPLkotlin/ResultKt;->constrainWidth-K40F9xA(JI)I
+HSPLkotlin/ResultKt;->create(Landroid/content/Context;)Landroidx/emoji2/text/FontRequestEmojiCompatConfig;
+HSPLkotlin/ResultKt;->createCoroutineUnintercepted(Ljava/lang/Object;Lkotlin/coroutines/Continuation;Lkotlin/jvm/functions/Function2;)Lkotlin/coroutines/Continuation;
+HSPLkotlin/ResultKt;->createFailure(Ljava/lang/Throwable;)Lkotlin/Result$Failure;
+HSPLkotlin/ResultKt;->distinctUntilChanged(Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/flow/Flow;
+HSPLkotlin/ResultKt;->emitAllImpl$FlowKt__ChannelsKt(Lkotlinx/coroutines/flow/FlowCollector;Lkotlinx/coroutines/channels/ProducerCoroutine;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLkotlin/ResultKt;->ensureActive(Lkotlin/coroutines/CoroutineContext;)V
+HSPLkotlin/ResultKt;->findIndexByKey$1(Landroidx/compose/foundation/lazy/layout/LazyLayoutItemProvider;Ljava/lang/Object;I)I
+HSPLkotlin/ResultKt;->first(Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLkotlin/ResultKt;->get(Landroid/view/View;)Landroidx/lifecycle/LifecycleOwner;
+HSPLkotlin/ResultKt;->getExclusions()Ljava/util/Set;
+HSPLkotlin/ResultKt;->getJob(Lkotlin/coroutines/CoroutineContext;)Lkotlinx/coroutines/Job;
+HSPLkotlin/ResultKt;->getOrCreateCancellableContinuation(Lkotlin/coroutines/Continuation;)Lkotlinx/coroutines/CancellableContinuationImpl;
+HSPLkotlin/ResultKt;->getProgressionLastElement(III)I
+HSPLkotlin/ResultKt;->getSp(D)J
+HSPLkotlin/ResultKt;->getSp(I)J
+HSPLkotlin/ResultKt;->hasFontAttributes(Landroidx/compose/ui/text/SpanStyle;)Z
+HSPLkotlin/ResultKt;->intercepted(Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLkotlin/ResultKt;->isEnabled()Z
+HSPLkotlin/ResultKt;->isUnspecified--R2X_6o(J)Z
+HSPLkotlin/ResultKt;->launch$default(Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/android/HandlerContext;ILkotlin/jvm/functions/Function2;I)Lkotlinx/coroutines/StandaloneCoroutine;
+HSPLkotlin/ResultKt;->launch(Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;ILkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/StandaloneCoroutine;
+HSPLkotlin/ResultKt;->lazyLayoutSemantics(Landroidx/compose/ui/Modifier;Lkotlin/reflect/KProperty0;Landroidx/tv/foundation/lazy/layout/LazyLayoutSemanticState;Landroidx/compose/foundation/gestures/Orientation;ZZLandroidx/compose/runtime/Composer;)Landroidx/compose/ui/Modifier;
+HSPLkotlin/ResultKt;->mapCapacity(I)I
+HSPLkotlin/ResultKt;->materializeModifier(Landroidx/compose/runtime/Composer;Landroidx/compose/ui/Modifier;)Landroidx/compose/ui/Modifier;
+HSPLkotlin/ResultKt;->mmap(Landroid/content/Context;Landroid/net/Uri;)Ljava/nio/MappedByteBuffer;
+HSPLkotlin/ResultKt;->offset-NN6Ew-U(IIJ)J
+HSPLkotlin/ResultKt;->overscrollEffect(Landroidx/compose/runtime/Composer;)Landroidx/compose/foundation/OverscrollEffect;
+HSPLkotlin/ResultKt;->pack(FJ)J
+HSPLkotlin/ResultKt;->plus(Lkotlin/coroutines/CoroutineContext;Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext;
+HSPLkotlin/ResultKt;->read(Ljava/nio/MappedByteBuffer;)Landroidx/emoji2/text/flatbuffer/MetadataList;
+HSPLkotlin/ResultKt;->recoverResult(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlin/ResultKt;->requireOwner(Landroidx/compose/ui/node/LayoutNode;)Landroidx/compose/ui/node/Owner;
+HSPLkotlin/ResultKt;->resolveLineHeightInPx-o2QH7mI(JFLandroidx/compose/ui/unit/Density;)F
+HSPLkotlin/ResultKt;->restoreThreadContext(Lkotlin/coroutines/CoroutineContext;Ljava/lang/Object;)V
+HSPLkotlin/ResultKt;->resume(Lkotlinx/coroutines/DispatchedTask;Lkotlin/coroutines/Continuation;Z)V
+HSPLkotlin/ResultKt;->roundToInt(F)I
+HSPLkotlin/ResultKt;->scrollableWithPivot(Landroidx/compose/ui/Modifier;Landroidx/compose/foundation/gestures/ScrollableState;Landroidx/compose/foundation/gestures/Orientation;Landroidx/tv/foundation/PivotOffsets;ZZ)Landroidx/compose/ui/Modifier;
+HSPLkotlin/ResultKt;->startUndispatchedOrReturn(Lkotlinx/coroutines/internal/ScopeCoroutine;Lkotlinx/coroutines/internal/ScopeCoroutine;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
+HSPLkotlin/ResultKt;->stateIn(Lkotlinx/coroutines/flow/SafeFlow;Lkotlinx/coroutines/internal/ContextScope;Lkotlinx/coroutines/flow/StartedWhileSubscribed;Ljava/lang/Float;)Lkotlinx/coroutines/flow/ReadonlyStateFlow;
+HSPLkotlin/ResultKt;->systemProp$default(Ljava/lang/String;IIII)I
+HSPLkotlin/ResultKt;->systemProp(Ljava/lang/String;JJJ)J
+HSPLkotlin/ResultKt;->threadContextElements(Lkotlin/coroutines/CoroutineContext;)Ljava/lang/Object;
+HSPLkotlin/ResultKt;->throwOnFailure(Ljava/lang/Object;)V
+HSPLkotlin/ResultKt;->toSize-ozmzZPI(J)J
+HSPLkotlin/ResultKt;->ulongToDouble(J)D
+HSPLkotlin/ResultKt;->updateThreadContext(Lkotlin/coroutines/CoroutineContext;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlin/ResultKt;->withContext(Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLkotlin/SynchronizedLazyImpl;-><init>(Lkotlin/jvm/functions/Function0;)V
+HSPLkotlin/SynchronizedLazyImpl;->getValue()Ljava/lang/Object;
+HSPLkotlin/TuplesKt;-><clinit>()V
+HSPLkotlin/TuplesKt;->CornerRadius(FF)J
+HSPLkotlin/TuplesKt;->LazyList(Landroidx/compose/ui/Modifier;Landroidx/tv/foundation/lazy/list/TvLazyListState;Landroidx/compose/foundation/layout/PaddingValuesImpl;ZZZILandroidx/tv/foundation/PivotOffsets;Landroidx/compose/ui/Alignment$Horizontal;Landroidx/compose/foundation/layout/Arrangement$Vertical;Landroidx/compose/ui/Alignment$Vertical;Landroidx/compose/foundation/layout/Arrangement$Horizontal;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;III)V
+HSPLkotlin/TuplesKt;->PillIndicatorTabRow(Ljava/util/List;ILkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)V
+HSPLkotlin/TuplesKt;->Rect-tz77jQw(JJ)Landroidx/compose/ui/geometry/Rect;
+HSPLkotlin/TuplesKt;->ScrollPositionUpdater(Lkotlin/jvm/functions/Function0;Landroidx/tv/foundation/lazy/list/TvLazyListState;Landroidx/compose/runtime/Composer;I)V
+HSPLkotlin/TuplesKt;->Size(FF)J
+HSPLkotlin/TuplesKt;->TopNavigation(Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V
+HSPLkotlin/TuplesKt;->access$addLayoutNodeChildren(Landroidx/compose/runtime/collection/MutableVector;Landroidx/compose/ui/Modifier$Node;)V
+HSPLkotlin/TuplesKt;->access$insertEntryAtIndex([Ljava/lang/Object;ILjava/lang/Object;Ljava/lang/Object;)[Ljava/lang/Object;
+HSPLkotlin/TuplesKt;->access$pop(Landroidx/compose/runtime/collection/MutableVector;)Landroidx/compose/ui/Modifier$Node;
+HSPLkotlin/TuplesKt;->access$removeRange(Ljava/util/ArrayList;II)V
+HSPLkotlin/TuplesKt;->asLayoutModifierNode(Landroidx/compose/ui/Modifier$Node;)Landroidx/compose/ui/node/LayoutModifierNode;
+HSPLkotlin/TuplesKt;->autoInvalidateInsertedNode(Landroidx/compose/ui/Modifier$Node;)V
+HSPLkotlin/TuplesKt;->autoInvalidateNodeIncludingDelegates(Landroidx/compose/ui/Modifier$Node;II)V
+HSPLkotlin/TuplesKt;->autoInvalidateNodeSelf(Landroidx/compose/ui/Modifier$Node;II)V
+HSPLkotlin/TuplesKt;->autoInvalidateUpdatedNode(Landroidx/compose/ui/Modifier$Node;)V
+HSPLkotlin/TuplesKt;->beforeCheckcastToFunctionOfArity(ILjava/lang/Object;)V
+HSPLkotlin/TuplesKt;->binarySearch([II)I
+HSPLkotlin/TuplesKt;->bitsForSlot(II)I
+HSPLkotlin/TuplesKt;->calculateLazyLayoutPinnedIndices(Landroidx/compose/foundation/lazy/layout/LazyLayoutItemProvider;Landroidx/compose/foundation/lazy/layout/LazyLayoutPinnedItemList;Landroidx/compose/runtime/Stack;)Ljava/util/List;
+HSPLkotlin/TuplesKt;->calculateNodeKindSetFrom(Landroidx/compose/ui/Modifier$Element;)I
+HSPLkotlin/TuplesKt;->calculateNodeKindSetFrom(Landroidx/compose/ui/Modifier$Node;)I
+HSPLkotlin/TuplesKt;->calculateNodeKindSetFromIncludingDelegates(Landroidx/compose/ui/Modifier$Node;)I
+HSPLkotlin/TuplesKt;->checkRadix(I)V
+HSPLkotlin/TuplesKt;->coerceIn(DDD)D
+HSPLkotlin/TuplesKt;->coerceIn(FFF)F
+HSPLkotlin/TuplesKt;->coerceIn(III)I
+HSPLkotlin/TuplesKt;->compareValues(Ljava/lang/Comparable;Ljava/lang/Comparable;)I
+HSPLkotlin/TuplesKt;->composableLambda(Landroidx/compose/runtime/Composer;ILkotlin/jvm/internal/Lambda;)Landroidx/compose/runtime/internal/ComposableLambdaImpl;
+HSPLkotlin/TuplesKt;->composableLambdaInstance(ILkotlin/jvm/internal/Lambda;Z)Landroidx/compose/runtime/internal/ComposableLambdaImpl;
+HSPLkotlin/TuplesKt;->currentValueOf(Landroidx/compose/ui/node/CompositionLocalConsumerModifierNode;Landroidx/compose/runtime/ProvidableCompositionLocal;)Ljava/lang/Object;
+HSPLkotlin/TuplesKt;->findLocation(ILjava/util/List;)I
+HSPLkotlin/TuplesKt;->findSegmentInternal(Lkotlinx/coroutines/internal/Segment;JLkotlin/jvm/functions/Function2;)Ljava/lang/Object;
+HSPLkotlin/TuplesKt;->get(Lkotlin/coroutines/CoroutineContext$Element;Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element;
+HSPLkotlin/TuplesKt;->getCenter-uvyYCjk(J)J
+HSPLkotlin/TuplesKt;->getEllipsizedLeftPadding(Landroid/text/Layout;ILandroid/graphics/Paint;)F
+HSPLkotlin/TuplesKt;->getEllipsizedRightPadding(Landroid/text/Layout;ILandroid/graphics/Paint;)F
+HSPLkotlin/TuplesKt;->getIncludeSelfInTraversal-H91voCI(I)Z
+HSPLkotlin/TuplesKt;->getLastIndex(Ljava/util/List;)I
+HSPLkotlin/TuplesKt;->invalidateDraw(Landroidx/compose/ui/node/DrawModifierNode;)V
+HSPLkotlin/TuplesKt;->invalidateMeasurement(Landroidx/compose/ui/node/LayoutModifierNode;)V
+HSPLkotlin/TuplesKt;->invalidateSemantics(Landroidx/compose/ui/node/SemanticsModifierNode;)V
+HSPLkotlin/TuplesKt;->isWhitespace(C)Z
+HSPLkotlin/TuplesKt;->lazy(Lkotlin/jvm/functions/Function0;)Lkotlin/Lazy;
+HSPLkotlin/TuplesKt;->listOf(Ljava/lang/Object;)Ljava/util/List;
+HSPLkotlin/TuplesKt;->listOf([Ljava/lang/Object;)Ljava/util/List;
+HSPLkotlin/TuplesKt;->minusKey(Lkotlin/coroutines/CoroutineContext$Element;Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext;
+HSPLkotlin/TuplesKt;->observeReads(Landroidx/compose/ui/Modifier$Node;Lkotlin/jvm/functions/Function0;)V
+HSPLkotlin/TuplesKt;->painterResource(ILandroidx/compose/runtime/Composer;)Landroidx/compose/ui/graphics/painter/Painter;
+HSPLkotlin/TuplesKt;->replacableWith(Landroidx/compose/runtime/RecomposeScope;Landroidx/compose/runtime/RecomposeScopeImpl;)Z
+HSPLkotlin/TuplesKt;->requireCoordinator-64DMado(Landroidx/compose/ui/node/DelegatableNode;I)Landroidx/compose/ui/node/NodeCoordinator;
+HSPLkotlin/TuplesKt;->requireLayoutNode(Landroidx/compose/ui/node/DelegatableNode;)Landroidx/compose/ui/node/LayoutNode;
+HSPLkotlin/TuplesKt;->requireOwner(Landroidx/compose/ui/node/DelegatableNode;)Landroidx/compose/ui/node/Owner;
+HSPLkotlin/TuplesKt;->resolveDefaults(Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/unit/LayoutDirection;)Landroidx/compose/ui/text/TextStyle;
+HSPLkotlin/TuplesKt;->runtimeCheck(Z)V
+HSPLkotlin/TuplesKt;->until(II)Lkotlin/ranges/IntRange;
+HSPLkotlin/ULong$Companion;-><init>()V
+HSPLkotlin/ULong$Companion;-><init>(I)V
+HSPLkotlin/ULong$Companion;-><init>(II)V
+HSPLkotlin/ULong$Companion;->checkElementIndex$kotlin_stdlib(II)V
+HSPLkotlin/ULong$Companion;->computeScaleFactor-H7hwNQA(JJ)J
+HSPLkotlin/ULong$Companion;->dispatch$lifecycle_runtime_release(Landroid/app/Activity;Landroidx/lifecycle/Lifecycle$Event;)V
+HSPLkotlin/ULong$Companion;->getHolderForHierarchy(Landroid/view/View;)Landroidx/metrics/performance/PerformanceMetricsState$Holder;
+HSPLkotlin/ULong$Companion;->injectIfNeededIn(Landroid/app/Activity;)V
+HSPLkotlin/UNINITIALIZED_VALUE;-><clinit>()V
+HSPLkotlin/Unit;-><clinit>()V
+HSPLkotlin/UnsafeLazyImpl;-><init>(Lkotlin/jvm/functions/Function0;)V
+HSPLkotlin/UnsafeLazyImpl;->getValue()Ljava/lang/Object;
+HSPLkotlin/collections/AbstractCollection;->isEmpty()Z
+HSPLkotlin/collections/AbstractCollection;->size()I
+HSPLkotlin/collections/AbstractList;->equals(Ljava/lang/Object;)Z
+HSPLkotlin/collections/AbstractMap$toString$1;-><init>(ILjava/lang/Object;)V
+HSPLkotlin/collections/AbstractMap$toString$1;->invoke()Landroidx/compose/runtime/DisposableEffectResult;
+HSPLkotlin/collections/AbstractMap$toString$1;->invoke(F)Ljava/lang/Float;
+HSPLkotlin/collections/AbstractMap$toString$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlin/collections/AbstractMap$toString$1;->invoke(Ljava/lang/Object;)V
+HSPLkotlin/collections/AbstractMap;->entrySet()Ljava/util/Set;
+HSPLkotlin/collections/AbstractMap;->equals(Ljava/lang/Object;)Z
+HSPLkotlin/collections/AbstractMap;->size()I
+HSPLkotlin/collections/AbstractMutableList;-><init>()V
+HSPLkotlin/collections/AbstractMutableList;->size()I
+HSPLkotlin/collections/AbstractSet;->equals(Ljava/lang/Object;)Z
+HSPLkotlin/collections/ArrayDeque;-><clinit>()V
+HSPLkotlin/collections/ArrayDeque;-><init>()V
+HSPLkotlin/collections/ArrayDeque;->addLast(Ljava/lang/Object;)V
+HSPLkotlin/collections/ArrayDeque;->ensureCapacity(I)V
+HSPLkotlin/collections/ArrayDeque;->first()Ljava/lang/Object;
+HSPLkotlin/collections/ArrayDeque;->get(I)Ljava/lang/Object;
+HSPLkotlin/collections/ArrayDeque;->getSize()I
+HSPLkotlin/collections/ArrayDeque;->incremented(I)I
+HSPLkotlin/collections/ArrayDeque;->isEmpty()Z
+HSPLkotlin/collections/ArrayDeque;->positiveMod(I)I
+HSPLkotlin/collections/ArrayDeque;->removeFirst()Ljava/lang/Object;
+HSPLkotlin/collections/ArraysKt___ArraysKt;->asList([Ljava/lang/Object;)Ljava/util/List;
+HSPLkotlin/collections/ArraysKt___ArraysKt;->collectionSizeOrDefault(Ljava/lang/Iterable;)I
+HSPLkotlin/collections/ArraysKt___ArraysKt;->copyInto$default([I[III)V
+HSPLkotlin/collections/ArraysKt___ArraysKt;->copyInto$default([Ljava/lang/Object;[Ljava/lang/Object;III)V
+HSPLkotlin/collections/ArraysKt___ArraysKt;->copyInto([I[IIII)V
+HSPLkotlin/collections/ArraysKt___ArraysKt;->copyInto([Ljava/lang/Object;[Ljava/lang/Object;III)V
+HSPLkotlin/collections/ArraysKt___ArraysKt;->fill$default([Ljava/lang/Object;)V
+HSPLkotlin/collections/ArraysKt___ArraysKt;->fill(II[Ljava/lang/Object;)V
+HSPLkotlin/collections/ArraysKt___ArraysKt;->indexOf([Ljava/lang/Object;Ljava/lang/Object;)I
+HSPLkotlin/collections/CollectionsKt__MutableCollectionsJVMKt;->sortWith(Ljava/util/List;Ljava/util/Comparator;)V
+HSPLkotlin/collections/CollectionsKt__ReversedViewsKt;->addAll(Ljava/lang/Iterable;Ljava/util/Collection;)V
+HSPLkotlin/collections/CollectionsKt___CollectionsKt;->firstOrNull(Ljava/util/List;)Ljava/lang/Object;
+HSPLkotlin/collections/CollectionsKt___CollectionsKt;->last(Ljava/util/List;)Ljava/lang/Object;
+HSPLkotlin/collections/CollectionsKt___CollectionsKt;->plus(Ljava/util/List;Ljava/io/Serializable;)Ljava/util/ArrayList;
+HSPLkotlin/collections/CollectionsKt___CollectionsKt;->toIntArray(Ljava/util/ArrayList;)[I
+HSPLkotlin/collections/EmptyList;-><clinit>()V
+HSPLkotlin/collections/EmptyList;->contains(Ljava/lang/Object;)Z
+HSPLkotlin/collections/EmptyList;->isEmpty()Z
+HSPLkotlin/collections/EmptyList;->size()I
+HSPLkotlin/collections/EmptyList;->toArray()[Ljava/lang/Object;
+HSPLkotlin/collections/EmptyMap;-><clinit>()V
+HSPLkotlin/collections/EmptyMap;->isEmpty()Z
+HSPLkotlin/collections/EmptyMap;->size()I
+HSPLkotlin/coroutines/AbstractCoroutineContextElement;-><init>(Lkotlin/coroutines/CoroutineContext$Key;)V
+HSPLkotlin/coroutines/AbstractCoroutineContextElement;->fold(Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
+HSPLkotlin/coroutines/AbstractCoroutineContextElement;->get(Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element;
+HSPLkotlin/coroutines/AbstractCoroutineContextElement;->getKey()Lkotlin/coroutines/CoroutineContext$Key;
+HSPLkotlin/coroutines/AbstractCoroutineContextElement;->minusKey(Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext;
+HSPLkotlin/coroutines/AbstractCoroutineContextElement;->plus(Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext;
+HSPLkotlin/coroutines/AbstractCoroutineContextKey;-><init>(Lkotlin/coroutines/CoroutineContext$Key;Lkotlin/jvm/functions/Function1;)V
+HSPLkotlin/coroutines/CombinedContext;-><init>(Lkotlin/coroutines/CoroutineContext$Element;Lkotlin/coroutines/CoroutineContext;)V
+HSPLkotlin/coroutines/CombinedContext;->fold(Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
+HSPLkotlin/coroutines/CombinedContext;->minusKey(Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext;
+HSPLkotlin/coroutines/CombinedContext;->plus(Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext;
+HSPLkotlin/coroutines/CoroutineContext$plus$1;-><clinit>()V
+HSPLkotlin/coroutines/CoroutineContext$plus$1;-><init>(I)V
+HSPLkotlin/coroutines/CoroutineContext$plus$1;->invoke(Landroidx/compose/runtime/Composer;I)V
+HSPLkotlin/coroutines/CoroutineContext$plus$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlin/coroutines/EmptyCoroutineContext;-><clinit>()V
+HSPLkotlin/coroutines/EmptyCoroutineContext;->fold(Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
+HSPLkotlin/coroutines/EmptyCoroutineContext;->plus(Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext;
+HSPLkotlin/coroutines/intrinsics/CoroutineSingletons;-><clinit>()V
+HSPLkotlin/coroutines/intrinsics/CoroutineSingletons;-><init>(ILjava/lang/String;)V
+HSPLkotlin/coroutines/jvm/internal/BaseContinuationImpl;-><init>(Lkotlin/coroutines/Continuation;)V
+HSPLkotlin/coroutines/jvm/internal/BaseContinuationImpl;->resumeWith(Ljava/lang/Object;)V
+HSPLkotlin/coroutines/jvm/internal/CompletedContinuation;-><clinit>()V
+HSPLkotlin/coroutines/jvm/internal/ContinuationImpl;-><init>(Lkotlin/coroutines/Continuation;)V
+HSPLkotlin/coroutines/jvm/internal/ContinuationImpl;-><init>(Lkotlin/coroutines/Continuation;Lkotlin/coroutines/CoroutineContext;)V
+HSPLkotlin/coroutines/jvm/internal/ContinuationImpl;->getContext()Lkotlin/coroutines/CoroutineContext;
+HSPLkotlin/coroutines/jvm/internal/ContinuationImpl;->releaseIntercepted()V
+HSPLkotlin/coroutines/jvm/internal/SuspendLambda;-><init>(ILkotlin/coroutines/Continuation;)V
+HSPLkotlin/coroutines/jvm/internal/SuspendLambda;->getArity()I
+HSPLkotlin/jvm/internal/ArrayIterator;-><init>(ILjava/lang/Object;)V
+HSPLkotlin/jvm/internal/ArrayIterator;->hasNext()Z
+HSPLkotlin/jvm/internal/ArrayIterator;->next()Ljava/lang/Object;
+HSPLkotlin/jvm/internal/CallableReference$NoReceiver;-><clinit>()V
+HSPLkotlin/jvm/internal/CallableReference;-><init>(Ljava/lang/Object;Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Z)V
+HSPLkotlin/jvm/internal/ClassReference;-><clinit>()V
+HSPLkotlin/jvm/internal/ClassReference;-><init>(Ljava/lang/Class;)V
+HSPLkotlin/jvm/internal/ClassReference;->getJClass()Ljava/lang/Class;
+HSPLkotlin/jvm/internal/FunctionReferenceImpl;-><init>(ILjava/lang/Class;Ljava/lang/String;Ljava/lang/String;I)V
+HSPLkotlin/jvm/internal/Lambda;-><init>(I)V
+HSPLkotlin/jvm/internal/Lambda;->getArity()I
+HSPLkotlin/jvm/internal/PropertyReference0Impl;->invoke()Ljava/lang/Object;
+HSPLkotlin/jvm/internal/PropertyReference;-><init>(Ljava/lang/Object;Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;I)V
+HSPLkotlin/jvm/internal/PropertyReference;->equals(Ljava/lang/Object;)Z
+HSPLkotlin/jvm/internal/Reflection;-><clinit>()V
+HSPLkotlin/jvm/internal/Reflection;->getOrCreateKotlinClass(Ljava/lang/Class;)Lkotlin/jvm/internal/ClassReference;
+HSPLkotlin/random/FallbackThreadLocalRandom$implStorage$1;-><init>(I)V
+HSPLkotlin/ranges/IntProgression;-><init>(III)V
+HSPLkotlin/ranges/IntProgression;->iterator()Ljava/util/Iterator;
+HSPLkotlin/ranges/IntProgressionIterator;-><init>(III)V
+HSPLkotlin/ranges/IntProgressionIterator;->hasNext()Z
+HSPLkotlin/ranges/IntProgressionIterator;->nextInt()I
+HSPLkotlin/ranges/IntRange;-><clinit>()V
+HSPLkotlin/ranges/IntRange;-><init>(II)V
+HSPLkotlin/ranges/IntRange;->equals(Ljava/lang/Object;)Z
+HSPLkotlin/ranges/IntRange;->isEmpty()Z
+HSPLkotlin/sequences/ConstrainedOnceSequence;-><init>(Lkotlin/sequences/SequencesKt__SequencesKt$asSequence$$inlined$Sequence$1;)V
+HSPLkotlin/sequences/ConstrainedOnceSequence;->iterator()Ljava/util/Iterator;
+HSPLkotlin/sequences/FilteringSequence$iterator$1;-><init>(Lkotlin/sequences/FilteringSequence;)V
+HSPLkotlin/sequences/FilteringSequence$iterator$1;->calcNext()V
+HSPLkotlin/sequences/FilteringSequence$iterator$1;->hasNext()Z
+HSPLkotlin/sequences/FilteringSequence$iterator$1;->next()Ljava/lang/Object;
+HSPLkotlin/sequences/FilteringSequence;-><init>(Lkotlin/sequences/GeneratorSequence;)V
+HSPLkotlin/sequences/GeneratorSequence$iterator$1;-><init>(Lkotlin/sequences/GeneratorSequence;)V
+HSPLkotlin/sequences/GeneratorSequence$iterator$1;->calcNext()V
+HSPLkotlin/sequences/GeneratorSequence$iterator$1;->hasNext()Z
+HSPLkotlin/sequences/GeneratorSequence$iterator$1;->next()Ljava/lang/Object;
+HSPLkotlin/sequences/GeneratorSequence;-><init>(Landroidx/compose/ui/node/LayoutNode$_foldedChildren$1;Lkotlin/jvm/functions/Function1;)V
+HSPLkotlin/sequences/GeneratorSequence;-><init>(Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function1;)V
+HSPLkotlin/sequences/GeneratorSequence;->iterator()Ljava/util/Iterator;
+HSPLkotlin/sequences/SequencesKt;->firstOrNull(Lkotlin/sequences/FilteringSequence;)Ljava/lang/Object;
+HSPLkotlin/sequences/SequencesKt;->mapNotNull(Lkotlin/sequences/Sequence;Lkotlinx/coroutines/CoroutineDispatcher$Key$1;)Lkotlin/sequences/FilteringSequence;
+HSPLkotlin/sequences/SequencesKt;->toList(Lkotlin/sequences/Sequence;)Ljava/util/List;
+HSPLkotlin/sequences/SequencesKt__SequencesKt$asSequence$$inlined$Sequence$1;-><init>(ILjava/lang/Object;)V
+HSPLkotlin/sequences/SequencesKt__SequencesKt$asSequence$$inlined$Sequence$1;->iterator()Ljava/util/Iterator;
+HSPLkotlin/sequences/TransformingSequence$iterator$1;-><init>(Lkotlin/sequences/GeneratorSequence;)V
+HSPLkotlin/sequences/TransformingSequence$iterator$1;->hasNext()Z
+HSPLkotlin/sequences/TransformingSequence$iterator$1;->next()Ljava/lang/Object;
+HSPLkotlin/text/StringsKt__IndentKt$getIndentFunction$2;-><init>(ILjava/lang/String;)V
+HSPLkotlin/text/StringsKt__RegexExtensionsKt;->generateSequence(Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Lkotlin/sequences/Sequence;
+HSPLkotlin/text/StringsKt__StringsKt;->endsWith$default(Ljava/lang/CharSequence;Ljava/lang/String;)Z
+HSPLkotlin/text/StringsKt__StringsKt;->getLastIndex(Ljava/lang/CharSequence;)I
+HSPLkotlin/text/StringsKt__StringsKt;->indexOf(Ljava/lang/CharSequence;Ljava/lang/String;IZ)I
+HSPLkotlin/text/StringsKt__StringsKt;->isBlank(Ljava/lang/CharSequence;)Z
+HSPLkotlin/text/StringsKt__StringsKt;->replace$default(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
+HSPLkotlin/text/StringsKt__StringsKt;->substringAfterLast$default(Ljava/lang/String;)Ljava/lang/String;
+HSPLkotlin/text/StringsKt___StringsKt;->last(Ljava/lang/CharSequence;)C
+HSPLkotlinx/coroutines/AbstractCoroutine;-><init>(Lkotlin/coroutines/CoroutineContext;Z)V
+HSPLkotlinx/coroutines/AbstractCoroutine;->getContext()Lkotlin/coroutines/CoroutineContext;
+HSPLkotlinx/coroutines/AbstractCoroutine;->getCoroutineContext()Lkotlin/coroutines/CoroutineContext;
+HSPLkotlinx/coroutines/AbstractCoroutine;->isActive()Z
+HSPLkotlinx/coroutines/AbstractCoroutine;->onCancelled(Ljava/lang/Throwable;Z)V
+HSPLkotlinx/coroutines/AbstractCoroutine;->onCompleted(Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/AbstractCoroutine;->onCompletionInternal(Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/AbstractCoroutine;->resumeWith(Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/AbstractCoroutine;->start$enumunboxing$(ILkotlinx/coroutines/AbstractCoroutine;Lkotlin/jvm/functions/Function2;)V
+HSPLkotlinx/coroutines/Active;-><clinit>()V
+HSPLkotlinx/coroutines/BlockingEventLoop;-><init>(Ljava/lang/Thread;)V
+HSPLkotlinx/coroutines/CancelHandler;-><init>()V
+HSPLkotlinx/coroutines/CancellableContinuationImpl;-><clinit>()V
+HSPLkotlinx/coroutines/CancellableContinuationImpl;-><init>(ILkotlin/coroutines/Continuation;)V
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->callCancelHandler(Lkotlinx/coroutines/CancelHandler;Ljava/lang/Throwable;)V
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->callSegmentOnCancellation(Lkotlinx/coroutines/internal/Segment;Ljava/lang/Throwable;)V
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->cancel(Ljava/lang/Throwable;)Z
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->completeResume(Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->detachChild$kotlinx_coroutines_core()V
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->dispatchResume(I)V
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->getContinuationCancellationCause(Lkotlinx/coroutines/JobSupport;)Ljava/lang/Throwable;
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->getDelegate$kotlinx_coroutines_core()Lkotlin/coroutines/Continuation;
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->getExceptionalResult$kotlinx_coroutines_core(Ljava/lang/Object;)Ljava/lang/Throwable;
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->getResult()Ljava/lang/Object;
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->getSuccessfulResult$kotlinx_coroutines_core(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->initCancellability()V
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->installParentHandle()Lkotlinx/coroutines/DisposableHandle;
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->invokeOnCancellation(Lkotlin/jvm/functions/Function1;)V
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->invokeOnCancellation(Lkotlinx/coroutines/internal/Segment;I)V
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->invokeOnCancellationImpl(Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->isReusable()Z
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->releaseClaimedReusableContinuation$kotlinx_coroutines_core()V
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->resumeImpl(Ljava/lang/Object;ILkotlin/jvm/functions/Function1;)V
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->resumeWith(Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->resumedState(Lkotlinx/coroutines/NotCompleted;Ljava/lang/Object;ILkotlin/jvm/functions/Function1;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->takeState$kotlinx_coroutines_core()Ljava/lang/Object;
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->tryResume(Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/internal/Symbol;
+HSPLkotlinx/coroutines/CancelledContinuation;-><clinit>()V
+HSPLkotlinx/coroutines/CancelledContinuation;-><init>(Lkotlin/coroutines/Continuation;Ljava/lang/Throwable;Z)V
+HSPLkotlinx/coroutines/ChildContinuation;-><init>(Lkotlinx/coroutines/CancellableContinuationImpl;)V
+HSPLkotlinx/coroutines/ChildContinuation;->invoke(Ljava/lang/Throwable;)V
+HSPLkotlinx/coroutines/ChildHandleNode;-><init>(Lkotlinx/coroutines/JobSupport;)V
+HSPLkotlinx/coroutines/ChildHandleNode;->childCancelled(Ljava/lang/Throwable;)Z
+HSPLkotlinx/coroutines/ChildHandleNode;->invoke(Ljava/lang/Throwable;)V
+HSPLkotlinx/coroutines/CompletedContinuation;-><init>(Ljava/lang/Object;Lkotlinx/coroutines/CancelHandler;Lkotlin/jvm/functions/Function1;Ljava/lang/Object;Ljava/lang/Throwable;)V
+HSPLkotlinx/coroutines/CompletedContinuation;-><init>(Ljava/lang/Object;Lkotlinx/coroutines/CancelHandler;Lkotlin/jvm/functions/Function1;Ljava/util/concurrent/CancellationException;I)V
+HSPLkotlinx/coroutines/CompletedExceptionally;-><clinit>()V
+HSPLkotlinx/coroutines/CompletedExceptionally;-><init>(Ljava/lang/Throwable;Z)V
+HSPLkotlinx/coroutines/CoroutineContextKt$foldCopies$1;-><clinit>()V
+HSPLkotlinx/coroutines/CoroutineContextKt$foldCopies$1;-><init>(I)V
+HSPLkotlinx/coroutines/CoroutineContextKt$foldCopies$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/CoroutineDispatcher$Key$1;-><clinit>()V
+HSPLkotlinx/coroutines/CoroutineDispatcher$Key$1;-><init>(I)V
+HSPLkotlinx/coroutines/CoroutineDispatcher$Key$1;->invoke(Landroid/view/View;)Landroid/view/View;
+HSPLkotlinx/coroutines/CoroutineDispatcher$Key$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/CoroutineDispatcher$Key;-><init>(I)V
+HSPLkotlinx/coroutines/CoroutineDispatcher;-><clinit>()V
+HSPLkotlinx/coroutines/CoroutineDispatcher;-><init>()V
+HSPLkotlinx/coroutines/CoroutineDispatcher;->get(Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element;
+HSPLkotlinx/coroutines/CoroutineDispatcher;->isDispatchNeeded()Z
+HSPLkotlinx/coroutines/CoroutineDispatcher;->minusKey(Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext;
+HSPLkotlinx/coroutines/DefaultExecutor;-><clinit>()V
+HSPLkotlinx/coroutines/DefaultExecutorKt;-><clinit>()V
+HSPLkotlinx/coroutines/DispatchedTask;-><init>(I)V
+HSPLkotlinx/coroutines/DispatchedTask;->getExceptionalResult$kotlinx_coroutines_core(Ljava/lang/Object;)Ljava/lang/Throwable;
+HSPLkotlinx/coroutines/DispatchedTask;->getSuccessfulResult$kotlinx_coroutines_core(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/DispatchedTask;->handleFatalException(Ljava/lang/Throwable;Ljava/lang/Throwable;)V
+HSPLkotlinx/coroutines/DispatchedTask;->run()V
+HSPLkotlinx/coroutines/Dispatchers;-><clinit>()V
+HSPLkotlinx/coroutines/Empty;-><init>(Z)V
+HSPLkotlinx/coroutines/Empty;->getList()Lkotlinx/coroutines/NodeList;
+HSPLkotlinx/coroutines/Empty;->isActive()Z
+HSPLkotlinx/coroutines/EventLoopImplBase;-><clinit>()V
+HSPLkotlinx/coroutines/EventLoopImplBase;-><init>()V
+HSPLkotlinx/coroutines/EventLoopImplPlatform;-><init>()V
+HSPLkotlinx/coroutines/EventLoopImplPlatform;->decrementUseCount(Z)V
+HSPLkotlinx/coroutines/EventLoopImplPlatform;->incrementUseCount(Z)V
+HSPLkotlinx/coroutines/EventLoopImplPlatform;->isUnconfinedLoopActive()Z
+HSPLkotlinx/coroutines/EventLoopImplPlatform;->processUnconfinedEvent()Z
+HSPLkotlinx/coroutines/ExecutorCoroutineDispatcher$Key$1;-><clinit>()V
+HSPLkotlinx/coroutines/ExecutorCoroutineDispatcher$Key$1;-><init>(I)V
+HSPLkotlinx/coroutines/ExecutorCoroutineDispatcher$Key$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/ExecutorCoroutineDispatcher;-><clinit>()V
+HSPLkotlinx/coroutines/GlobalScope;-><clinit>()V
+HSPLkotlinx/coroutines/GlobalScope;->getCoroutineContext()Lkotlin/coroutines/CoroutineContext;
+HSPLkotlinx/coroutines/InvokeOnCancel;-><init>(ILjava/lang/Object;)V
+HSPLkotlinx/coroutines/InvokeOnCancel;->invoke(Ljava/lang/Throwable;)V
+HSPLkotlinx/coroutines/InvokeOnCompletion;-><init>(ILjava/lang/Object;)V
+HSPLkotlinx/coroutines/JobImpl;-><init>(Lkotlinx/coroutines/Job;)V
+HSPLkotlinx/coroutines/JobImpl;->getHandlesException$kotlinx_coroutines_core()Z
+HSPLkotlinx/coroutines/JobImpl;->getOnCancelComplete$kotlinx_coroutines_core()Z
+HSPLkotlinx/coroutines/JobNode;-><init>()V
+HSPLkotlinx/coroutines/JobNode;->dispose()V
+HSPLkotlinx/coroutines/JobNode;->getJob()Lkotlinx/coroutines/JobSupport;
+HSPLkotlinx/coroutines/JobNode;->getList()Lkotlinx/coroutines/NodeList;
+HSPLkotlinx/coroutines/JobNode;->isActive()Z
+HSPLkotlinx/coroutines/JobSupport$ChildCompletion;-><init>(Lkotlinx/coroutines/JobSupport;Lkotlinx/coroutines/JobSupport$Finishing;Lkotlinx/coroutines/ChildHandleNode;Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/JobSupport$ChildCompletion;->invoke(Ljava/lang/Throwable;)V
+HSPLkotlinx/coroutines/JobSupport$Finishing;-><clinit>()V
+HSPLkotlinx/coroutines/JobSupport$Finishing;-><init>(Lkotlinx/coroutines/NodeList;Ljava/lang/Throwable;)V
+HSPLkotlinx/coroutines/JobSupport$Finishing;->addExceptionLocked(Ljava/lang/Throwable;)V
+HSPLkotlinx/coroutines/JobSupport$Finishing;->getList()Lkotlinx/coroutines/NodeList;
+HSPLkotlinx/coroutines/JobSupport$Finishing;->getRootCause()Ljava/lang/Throwable;
+HSPLkotlinx/coroutines/JobSupport$Finishing;->isCancelling()Z
+HSPLkotlinx/coroutines/JobSupport$Finishing;->isCompleting()Z
+HSPLkotlinx/coroutines/JobSupport$Finishing;->sealLocked(Ljava/lang/Throwable;)Ljava/util/ArrayList;
+HSPLkotlinx/coroutines/JobSupport$addLastAtomic$$inlined$addLastIf$1;-><init>(Lkotlinx/coroutines/internal/LockFreeLinkedListNode;Lkotlinx/coroutines/JobSupport;Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/JobSupport$addLastAtomic$$inlined$addLastIf$1;->complete(Ljava/lang/Object;Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/JobSupport$addLastAtomic$$inlined$addLastIf$1;->prepare(Ljava/lang/Object;)Lkotlinx/coroutines/internal/Symbol;
+HSPLkotlinx/coroutines/JobSupport;-><clinit>()V
+HSPLkotlinx/coroutines/JobSupport;-><init>(Z)V
+HSPLkotlinx/coroutines/JobSupport;->addLastAtomic(Ljava/lang/Object;Lkotlinx/coroutines/NodeList;Lkotlinx/coroutines/JobNode;)Z
+HSPLkotlinx/coroutines/JobSupport;->afterCompletion(Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/JobSupport;->afterResume(Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/JobSupport;->cancel(Ljava/util/concurrent/CancellationException;)V
+HSPLkotlinx/coroutines/JobSupport;->cancelImpl$kotlinx_coroutines_core(Ljava/lang/Object;)Z
+HSPLkotlinx/coroutines/JobSupport;->cancelInternal(Ljava/util/concurrent/CancellationException;)V
+HSPLkotlinx/coroutines/JobSupport;->cancelParent(Ljava/lang/Throwable;)Z
+HSPLkotlinx/coroutines/JobSupport;->childCancelled(Ljava/lang/Throwable;)Z
+HSPLkotlinx/coroutines/JobSupport;->completeStateFinalization(Lkotlinx/coroutines/Incomplete;Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/JobSupport;->createCauseException(Ljava/lang/Object;)Ljava/lang/Throwable;
+HSPLkotlinx/coroutines/JobSupport;->finalizeFinishingState(Lkotlinx/coroutines/JobSupport$Finishing;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/JobSupport;->fold(Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/JobSupport;->get(Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element;
+HSPLkotlinx/coroutines/JobSupport;->getCancellationException()Ljava/util/concurrent/CancellationException;
+HSPLkotlinx/coroutines/JobSupport;->getFinalRootCause(Lkotlinx/coroutines/JobSupport$Finishing;Ljava/util/ArrayList;)Ljava/lang/Throwable;
+HSPLkotlinx/coroutines/JobSupport;->getKey()Lkotlin/coroutines/CoroutineContext$Key;
+HSPLkotlinx/coroutines/JobSupport;->getOnCancelComplete$kotlinx_coroutines_core()Z
+HSPLkotlinx/coroutines/JobSupport;->getOrPromoteCancellingList(Lkotlinx/coroutines/Incomplete;)Lkotlinx/coroutines/NodeList;
+HSPLkotlinx/coroutines/JobSupport;->getState$kotlinx_coroutines_core()Ljava/lang/Object;
+HSPLkotlinx/coroutines/JobSupport;->initParentJob(Lkotlinx/coroutines/Job;)V
+HSPLkotlinx/coroutines/JobSupport;->invokeOnCompletion(ZZLkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/DisposableHandle;
+HSPLkotlinx/coroutines/JobSupport;->isActive()Z
+HSPLkotlinx/coroutines/JobSupport;->isScopedCoroutine()Z
+HSPLkotlinx/coroutines/JobSupport;->makeCompletingOnce$kotlinx_coroutines_core(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/JobSupport;->minusKey(Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext;
+HSPLkotlinx/coroutines/JobSupport;->nextChild(Lkotlinx/coroutines/internal/LockFreeLinkedListNode;)Lkotlinx/coroutines/ChildHandleNode;
+HSPLkotlinx/coroutines/JobSupport;->notifyCancelling(Lkotlinx/coroutines/NodeList;Ljava/lang/Throwable;)V
+HSPLkotlinx/coroutines/JobSupport;->onCompletionInternal(Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/JobSupport;->plus(Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext;
+HSPLkotlinx/coroutines/JobSupport;->promoteSingleToNodeList(Lkotlinx/coroutines/JobNode;)V
+HSPLkotlinx/coroutines/JobSupport;->startInternal(Ljava/lang/Object;)I
+HSPLkotlinx/coroutines/JobSupport;->tryMakeCompleting(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/JobSupport;->tryWaitForChild(Lkotlinx/coroutines/JobSupport$Finishing;Lkotlinx/coroutines/ChildHandleNode;Ljava/lang/Object;)Z
+HSPLkotlinx/coroutines/NodeList;-><init>()V
+HSPLkotlinx/coroutines/NodeList;->getList()Lkotlinx/coroutines/NodeList;
+HSPLkotlinx/coroutines/NodeList;->isActive()Z
+HSPLkotlinx/coroutines/NodeList;->isRemoved()Z
+HSPLkotlinx/coroutines/NonDisposableHandle;-><clinit>()V
+HSPLkotlinx/coroutines/NonDisposableHandle;->dispose()V
+HSPLkotlinx/coroutines/ThreadLocalEventLoop;-><clinit>()V
+HSPLkotlinx/coroutines/ThreadLocalEventLoop;->getEventLoop$kotlinx_coroutines_core()Lkotlinx/coroutines/EventLoopImplPlatform;
+HSPLkotlinx/coroutines/Unconfined;-><clinit>()V
+HSPLkotlinx/coroutines/UndispatchedCoroutine;-><init>(Lkotlin/coroutines/Continuation;Lkotlin/coroutines/CoroutineContext;)V
+HSPLkotlinx/coroutines/UndispatchedMarker;-><clinit>()V
+HSPLkotlinx/coroutines/UndispatchedMarker;->fold(Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/UndispatchedMarker;->get(Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element;
+HSPLkotlinx/coroutines/UndispatchedMarker;->getKey()Lkotlin/coroutines/CoroutineContext$Key;
+HSPLkotlinx/coroutines/android/AndroidDispatcherFactory;-><init>()V
+HSPLkotlinx/coroutines/android/AndroidDispatcherFactory;->createDispatcher(Ljava/util/List;)Lkotlinx/coroutines/MainCoroutineDispatcher;
+HSPLkotlinx/coroutines/android/HandlerContext;-><init>(Landroid/os/Handler;)V
+HSPLkotlinx/coroutines/android/HandlerContext;-><init>(Landroid/os/Handler;Ljava/lang/String;Z)V
+HSPLkotlinx/coroutines/android/HandlerContext;->dispatch(Lkotlin/coroutines/CoroutineContext;Ljava/lang/Runnable;)V
+HSPLkotlinx/coroutines/android/HandlerContext;->isDispatchNeeded()Z
+HSPLkotlinx/coroutines/android/HandlerDispatcherKt;-><clinit>()V
+HSPLkotlinx/coroutines/android/HandlerDispatcherKt;->asHandler(Landroid/os/Looper;)Landroid/os/Handler;
+HSPLkotlinx/coroutines/channels/BufferOverflow;-><clinit>()V
+HSPLkotlinx/coroutines/channels/BufferOverflow;-><init>(ILjava/lang/String;)V
+HSPLkotlinx/coroutines/channels/BufferedChannel$BufferedChannelIterator;-><init>(Lkotlinx/coroutines/channels/BufferedChannel;)V
+HSPLkotlinx/coroutines/channels/BufferedChannel$BufferedChannelIterator;->hasNext(Lkotlin/coroutines/jvm/internal/ContinuationImpl;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/channels/BufferedChannel$BufferedChannelIterator;->invokeOnCancellation(Lkotlinx/coroutines/internal/Segment;I)V
+HSPLkotlinx/coroutines/channels/BufferedChannel$BufferedChannelIterator;->next()Ljava/lang/Object;
+HSPLkotlinx/coroutines/channels/BufferedChannel;-><clinit>()V
+HSPLkotlinx/coroutines/channels/BufferedChannel;-><init>(ILkotlin/jvm/functions/Function1;)V
+HSPLkotlinx/coroutines/channels/BufferedChannel;->access$findSegmentSend(Lkotlinx/coroutines/channels/BufferedChannel;JLkotlinx/coroutines/channels/ChannelSegment;)Lkotlinx/coroutines/channels/ChannelSegment;
+HSPLkotlinx/coroutines/channels/BufferedChannel;->access$updateCellSend(Lkotlinx/coroutines/channels/BufferedChannel;Lkotlinx/coroutines/channels/ChannelSegment;ILjava/lang/Object;JLjava/lang/Object;Z)I
+HSPLkotlinx/coroutines/channels/BufferedChannel;->bufferOrRendezvousSend(J)Z
+HSPLkotlinx/coroutines/channels/BufferedChannel;->dropFirstElementUntilTheSpecifiedCellIsInTheBuffer(J)V
+HSPLkotlinx/coroutines/channels/BufferedChannel;->expandBuffer()V
+HSPLkotlinx/coroutines/channels/BufferedChannel;->findSegmentReceive(JLkotlinx/coroutines/channels/ChannelSegment;)Lkotlinx/coroutines/channels/ChannelSegment;
+HSPLkotlinx/coroutines/channels/BufferedChannel;->getBufferEndCounter()J
+HSPLkotlinx/coroutines/channels/BufferedChannel;->getReceiversCounter$kotlinx_coroutines_core()J
+HSPLkotlinx/coroutines/channels/BufferedChannel;->getSendersCounter$kotlinx_coroutines_core()J
+HSPLkotlinx/coroutines/channels/BufferedChannel;->incCompletedExpandBufferAttempts(J)V
+HSPLkotlinx/coroutines/channels/BufferedChannel;->isClosed(JZ)Z
+HSPLkotlinx/coroutines/channels/BufferedChannel;->isClosedForReceive()Z
+HSPLkotlinx/coroutines/channels/BufferedChannel;->isRendezvousOrUnlimited()Z
+HSPLkotlinx/coroutines/channels/BufferedChannel;->iterator()Lkotlinx/coroutines/channels/BufferedChannel$BufferedChannelIterator;
+HSPLkotlinx/coroutines/channels/BufferedChannel;->receive(Lkotlin/coroutines/jvm/internal/SuspendLambda;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/channels/BufferedChannel;->send(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/channels/BufferedChannel;->tryReceive-PtdJZtk()Ljava/lang/Object;
+HSPLkotlinx/coroutines/channels/BufferedChannel;->tryResumeReceiver(Ljava/lang/Object;Ljava/lang/Object;)Z
+HSPLkotlinx/coroutines/channels/BufferedChannel;->updateCellReceive(Lkotlinx/coroutines/channels/ChannelSegment;IJLjava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/channels/BufferedChannel;->waitExpandBufferCompletion$kotlinx_coroutines_core(J)V
+HSPLkotlinx/coroutines/channels/BufferedChannelKt$createSegmentFunction$1;-><clinit>()V
+HSPLkotlinx/coroutines/channels/BufferedChannelKt$createSegmentFunction$1;-><init>()V
+HSPLkotlinx/coroutines/channels/BufferedChannelKt$createSegmentFunction$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/channels/BufferedChannelKt;-><clinit>()V
+HSPLkotlinx/coroutines/channels/BufferedChannelKt;->tryResume0(Lkotlinx/coroutines/CancellableContinuation;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Z
+HSPLkotlinx/coroutines/channels/Channel$Factory;-><clinit>()V
+HSPLkotlinx/coroutines/channels/Channel;-><clinit>()V
+HSPLkotlinx/coroutines/channels/ChannelSegment;-><init>(JLkotlinx/coroutines/channels/ChannelSegment;Lkotlinx/coroutines/channels/BufferedChannel;I)V
+HSPLkotlinx/coroutines/channels/ChannelSegment;->casState$kotlinx_coroutines_core(Ljava/lang/Object;ILjava/lang/Object;)Z
+HSPLkotlinx/coroutines/channels/ChannelSegment;->getNumberOfSlots()I
+HSPLkotlinx/coroutines/channels/ChannelSegment;->getState$kotlinx_coroutines_core(I)Ljava/lang/Object;
+HSPLkotlinx/coroutines/channels/ChannelSegment;->onCancellation(ILkotlin/coroutines/CoroutineContext;)V
+HSPLkotlinx/coroutines/channels/ChannelSegment;->onCancelledRequest(IZ)V
+HSPLkotlinx/coroutines/channels/ChannelSegment;->retrieveElement$kotlinx_coroutines_core(I)Ljava/lang/Object;
+HSPLkotlinx/coroutines/channels/ChannelSegment;->setElementLazy(ILjava/lang/Object;)V
+HSPLkotlinx/coroutines/channels/ChannelSegment;->setState$kotlinx_coroutines_core(ILkotlinx/coroutines/internal/Symbol;)V
+HSPLkotlinx/coroutines/channels/ConflatedBufferedChannel;-><init>(ILkotlinx/coroutines/channels/BufferOverflow;Lkotlin/jvm/functions/Function1;)V
+HSPLkotlinx/coroutines/channels/ConflatedBufferedChannel;->trySend-JP2dKIU(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/channels/ConflatedBufferedChannel;->trySendImpl-Mj0NB7M(Ljava/lang/Object;Z)Ljava/lang/Object;
+HSPLkotlinx/coroutines/channels/ProducerCoroutine;-><init>(Lkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/channels/BufferedChannel;)V
+HSPLkotlinx/coroutines/channels/ProducerCoroutine;->iterator()Lkotlinx/coroutines/channels/BufferedChannel$BufferedChannelIterator;
+HSPLkotlinx/coroutines/channels/ProducerCoroutine;->send(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/AbstractFlow$collect$1;-><init>(Lkotlinx/coroutines/flow/SafeFlow;Lkotlin/coroutines/Continuation;)V
+HSPLkotlinx/coroutines/flow/DistinctFlowImpl$collect$2$emit$1;-><init>(Lkotlinx/coroutines/flow/DistinctFlowImpl$collect$2;Lkotlin/coroutines/Continuation;)V
+HSPLkotlinx/coroutines/flow/DistinctFlowImpl$collect$2;-><init>(Lkotlinx/coroutines/flow/DistinctFlowImpl;Lkotlin/jvm/internal/Ref$ObjectRef;Lkotlinx/coroutines/flow/FlowCollector;)V
+HSPLkotlinx/coroutines/flow/DistinctFlowImpl$collect$2;->emit(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/DistinctFlowImpl;-><init>(Lkotlinx/coroutines/flow/Flow;)V
+HSPLkotlinx/coroutines/flow/DistinctFlowImpl;->collect(Lkotlinx/coroutines/flow/FlowCollector;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/FlowKt__ChannelsKt$emitAllImpl$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/FlowKt__LimitKt$dropWhile$$inlined$unsafeFlow$1;-><init>(Lkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest;Lkotlinx/coroutines/flow/StartedWhileSubscribed$command$2;)V
+HSPLkotlinx/coroutines/flow/FlowKt__LimitKt$dropWhile$$inlined$unsafeFlow$1;->collect(Lkotlinx/coroutines/flow/FlowCollector;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/FlowKt__LimitKt$dropWhile$1$1$emit$1;-><init>(Lkotlinx/coroutines/flow/FlowKt__LimitKt$dropWhile$1$1;Lkotlin/coroutines/Continuation;)V
+HSPLkotlinx/coroutines/flow/FlowKt__LimitKt$dropWhile$1$1;-><init>(Lkotlin/jvm/internal/Ref$BooleanRef;Lkotlinx/coroutines/flow/FlowCollector;Lkotlin/jvm/functions/Function2;)V
+HSPLkotlinx/coroutines/flow/FlowKt__LimitKt$dropWhile$1$1;->emit(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/FlowKt__MergeKt$mapLatest$1;-><init>(Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)V
+HSPLkotlinx/coroutines/flow/FlowKt__MergeKt$mapLatest$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/FlowKt__MergeKt$mapLatest$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/FlowKt__MergeKt;-><clinit>()V
+HSPLkotlinx/coroutines/flow/FlowKt__ReduceKt$first$$inlined$collectWhile$2$1;-><init>(Lkotlinx/coroutines/flow/FlowKt__ReduceKt$first$$inlined$collectWhile$2;Lkotlin/coroutines/Continuation;)V
+HSPLkotlinx/coroutines/flow/FlowKt__ReduceKt$first$$inlined$collectWhile$2;-><init>(Lkotlin/jvm/functions/Function2;Lkotlin/jvm/internal/Ref$ObjectRef;)V
+HSPLkotlinx/coroutines/flow/FlowKt__ReduceKt$first$$inlined$collectWhile$2;->emit(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/FlowKt__ShareKt$launchSharing$1$2;-><init>(Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/MutableSharedFlow;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)V
+HSPLkotlinx/coroutines/flow/FlowKt__ShareKt$launchSharing$1$2;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLkotlinx/coroutines/flow/FlowKt__ShareKt$launchSharing$1$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/FlowKt__ShareKt$launchSharing$1$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/FlowKt__ShareKt$launchSharing$1;-><init>(Lkotlinx/coroutines/flow/SharingStarted;Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/MutableSharedFlow;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)V
+HSPLkotlinx/coroutines/flow/FlowKt__ShareKt$launchSharing$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLkotlinx/coroutines/flow/FlowKt__ShareKt$launchSharing$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/FlowKt__ShareKt$launchSharing$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/ReadonlyStateFlow;-><init>(Lkotlinx/coroutines/flow/StateFlowImpl;)V
+HSPLkotlinx/coroutines/flow/ReadonlyStateFlow;->collect(Lkotlinx/coroutines/flow/FlowCollector;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/ReadonlyStateFlow;->getValue()Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/SafeFlow;-><init>(Lkotlin/jvm/functions/Function2;)V
+HSPLkotlinx/coroutines/flow/SafeFlow;->collect(Lkotlinx/coroutines/flow/FlowCollector;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/SharedFlowImpl$collect$1;-><init>(Lkotlinx/coroutines/flow/SharedFlowImpl;Lkotlin/coroutines/Continuation;)V
+HSPLkotlinx/coroutines/flow/SharedFlowImpl$collect$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/SharedFlowImpl;-><init>(IILkotlinx/coroutines/channels/BufferOverflow;)V
+HSPLkotlinx/coroutines/flow/SharedFlowImpl;->awaitValue(Lkotlinx/coroutines/flow/SharedFlowSlot;Lkotlinx/coroutines/flow/SharedFlowImpl$collect$1;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/SharedFlowImpl;->cleanupTailLocked()V
+HSPLkotlinx/coroutines/flow/SharedFlowImpl;->collect$suspendImpl(Lkotlinx/coroutines/flow/SharedFlowImpl;Lkotlinx/coroutines/flow/FlowCollector;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/intrinsics/CoroutineSingletons;
+HSPLkotlinx/coroutines/flow/SharedFlowImpl;->collect(Lkotlinx/coroutines/flow/FlowCollector;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/SharedFlowImpl;->createSlot()Lkotlinx/coroutines/flow/internal/AbstractSharedFlowSlot;
+HSPLkotlinx/coroutines/flow/SharedFlowImpl;->createSlotArray()[Lkotlinx/coroutines/flow/internal/AbstractSharedFlowSlot;
+HSPLkotlinx/coroutines/flow/SharedFlowImpl;->dropOldestLocked()V
+HSPLkotlinx/coroutines/flow/SharedFlowImpl;->emit(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/SharedFlowImpl;->enqueueLocked(Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/flow/SharedFlowImpl;->findSlotsToResumeLocked([Lkotlin/coroutines/Continuation;)[Lkotlin/coroutines/Continuation;
+HSPLkotlinx/coroutines/flow/SharedFlowImpl;->getHead()J
+HSPLkotlinx/coroutines/flow/SharedFlowImpl;->growBuffer(II[Ljava/lang/Object;)[Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/SharedFlowImpl;->tryEmit(Ljava/lang/Object;)Z
+HSPLkotlinx/coroutines/flow/SharedFlowImpl;->tryEmitLocked(Ljava/lang/Object;)Z
+HSPLkotlinx/coroutines/flow/SharedFlowImpl;->tryPeekLocked(Lkotlinx/coroutines/flow/SharedFlowSlot;)J
+HSPLkotlinx/coroutines/flow/SharedFlowImpl;->tryTakeValue(Lkotlinx/coroutines/flow/SharedFlowSlot;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/SharedFlowImpl;->updateBufferLocked(JJJJ)V
+HSPLkotlinx/coroutines/flow/SharedFlowImpl;->updateCollectorIndexLocked$kotlinx_coroutines_core(J)[Lkotlin/coroutines/Continuation;
+HSPLkotlinx/coroutines/flow/SharedFlowSlot;-><init>()V
+HSPLkotlinx/coroutines/flow/SharedFlowSlot;->allocateLocked(Lkotlinx/coroutines/flow/internal/AbstractSharedFlow;)Z
+HSPLkotlinx/coroutines/flow/SharingCommand;-><clinit>()V
+HSPLkotlinx/coroutines/flow/SharingCommand;-><init>(ILjava/lang/String;)V
+HSPLkotlinx/coroutines/flow/SharingConfig;-><init>()V
+HSPLkotlinx/coroutines/flow/SharingConfig;-><init>(ILkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/channels/BufferOverflow;Lkotlinx/coroutines/flow/Flow;)V
+HSPLkotlinx/coroutines/flow/SharingConfig;->add(Ljava/lang/Object;Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/flow/SharingConfig;->contains(Ljava/lang/Object;)Z
+HSPLkotlinx/coroutines/flow/SharingConfig;->find(Ljava/lang/Object;)I
+HSPLkotlinx/coroutines/flow/SharingConfig;->remove(Ljava/lang/Object;Ljava/lang/Object;)Z
+HSPLkotlinx/coroutines/flow/SharingConfig;->removeScope(Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/flow/SharingConfig;->scopeSetAt(I)Landroidx/compose/runtime/collection/IdentityArraySet;
+HSPLkotlinx/coroutines/flow/StartedLazily;-><init>(I)V
+HSPLkotlinx/coroutines/flow/StartedWhileSubscribed$command$1;-><init>(Lkotlinx/coroutines/flow/StartedWhileSubscribed;Lkotlin/coroutines/Continuation;)V
+HSPLkotlinx/coroutines/flow/StartedWhileSubscribed$command$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/StartedWhileSubscribed$command$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/StartedWhileSubscribed$command$2;-><init>(Lkotlin/coroutines/Continuation;)V
+HSPLkotlinx/coroutines/flow/StartedWhileSubscribed$command$2;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLkotlinx/coroutines/flow/StartedWhileSubscribed$command$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/StartedWhileSubscribed$command$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/StartedWhileSubscribed;-><init>(JJ)V
+HSPLkotlinx/coroutines/flow/StartedWhileSubscribed;->command(Lkotlinx/coroutines/flow/internal/SubscriptionCountStateFlow;)Lkotlinx/coroutines/flow/Flow;
+HSPLkotlinx/coroutines/flow/StartedWhileSubscribed;->equals(Ljava/lang/Object;)Z
+HSPLkotlinx/coroutines/flow/StateFlowImpl$collect$1;-><init>(Lkotlinx/coroutines/flow/StateFlowImpl;Lkotlin/coroutines/Continuation;)V
+HSPLkotlinx/coroutines/flow/StateFlowImpl$collect$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/StateFlowImpl;-><clinit>()V
+HSPLkotlinx/coroutines/flow/StateFlowImpl;-><init>(Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/flow/StateFlowImpl;->collect(Lkotlinx/coroutines/flow/FlowCollector;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/StateFlowImpl;->createSlot()Lkotlinx/coroutines/flow/internal/AbstractSharedFlowSlot;
+HSPLkotlinx/coroutines/flow/StateFlowImpl;->createSlotArray()[Lkotlinx/coroutines/flow/internal/AbstractSharedFlowSlot;
+HSPLkotlinx/coroutines/flow/StateFlowImpl;->getValue()Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/StateFlowImpl;->setValue(Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/flow/StateFlowImpl;->updateState(Ljava/lang/Object;Ljava/lang/Object;)Z
+HSPLkotlinx/coroutines/flow/StateFlowSlot;-><clinit>()V
+HSPLkotlinx/coroutines/flow/StateFlowSlot;->allocateLocked(Lkotlinx/coroutines/flow/internal/AbstractSharedFlow;)Z
+HSPLkotlinx/coroutines/flow/internal/AbstractSharedFlow;->allocateSlot()Lkotlinx/coroutines/flow/internal/AbstractSharedFlowSlot;
+HSPLkotlinx/coroutines/flow/internal/ChannelFlow$collect$2;-><init>(Lkotlin/coroutines/Continuation;Lkotlinx/coroutines/flow/FlowCollector;Lkotlinx/coroutines/flow/internal/ChannelFlow;)V
+HSPLkotlinx/coroutines/flow/internal/ChannelFlow$collect$2;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLkotlinx/coroutines/flow/internal/ChannelFlow$collect$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/internal/ChannelFlow$collect$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/internal/ChannelFlow$collectToFun$1;-><init>(Lkotlinx/coroutines/flow/internal/ChannelFlow;Lkotlin/coroutines/Continuation;)V
+HSPLkotlinx/coroutines/flow/internal/ChannelFlow$collectToFun$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLkotlinx/coroutines/flow/internal/ChannelFlow$collectToFun$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/internal/ChannelFlow;-><init>(Lkotlin/coroutines/CoroutineContext;ILkotlinx/coroutines/channels/BufferOverflow;)V
+HSPLkotlinx/coroutines/flow/internal/ChannelFlow;->fuse(Lkotlin/coroutines/CoroutineContext;ILkotlinx/coroutines/channels/BufferOverflow;)Lkotlinx/coroutines/flow/Flow;
+HSPLkotlinx/coroutines/flow/internal/ChannelFlowOperator;-><init>(ILkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/channels/BufferOverflow;Lkotlinx/coroutines/flow/Flow;)V
+HSPLkotlinx/coroutines/flow/internal/ChannelFlowOperator;->collect(Lkotlinx/coroutines/flow/FlowCollector;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest$flowCollect$3$1$2;-><init>(Lkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest;Lkotlinx/coroutines/flow/FlowCollector;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)V
+HSPLkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest$flowCollect$3$1$2;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest$flowCollect$3$1$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest$flowCollect$3$1$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest$flowCollect$3$1$emit$1;-><init>(Lkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest$flowCollect$3$1;Lkotlin/coroutines/Continuation;)V
+HSPLkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest$flowCollect$3$1;-><init>(Lkotlin/jvm/internal/Ref$ObjectRef;Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest;Lkotlinx/coroutines/flow/FlowCollector;)V
+HSPLkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest$flowCollect$3$1;->emit(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest$flowCollect$3;-><init>(Lkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest;Lkotlinx/coroutines/flow/FlowCollector;Lkotlin/coroutines/Continuation;)V
+HSPLkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest$flowCollect$3;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest$flowCollect$3;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest$flowCollect$3;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest;-><init>(Lkotlin/jvm/functions/Function3;Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/CoroutineContext;ILkotlinx/coroutines/channels/BufferOverflow;)V
+HSPLkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest;->create(Lkotlin/coroutines/CoroutineContext;ILkotlinx/coroutines/channels/BufferOverflow;)Lkotlinx/coroutines/flow/internal/ChannelFlow;
+HSPLkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest;->flowCollect(Lkotlinx/coroutines/flow/FlowCollector;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/internal/NoOpContinuation;-><clinit>()V
+HSPLkotlinx/coroutines/flow/internal/NopCollector;-><clinit>()V
+HSPLkotlinx/coroutines/flow/internal/SafeCollector;-><init>(Lkotlinx/coroutines/flow/FlowCollector;Lkotlin/coroutines/CoroutineContext;)V
+HSPLkotlinx/coroutines/flow/internal/SendingCollector;-><init>(Lkotlinx/coroutines/channels/ProducerScope;)V
+HSPLkotlinx/coroutines/flow/internal/SendingCollector;->emit(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/internal/SubscriptionCountStateFlow;-><init>(I)V
+HSPLkotlinx/coroutines/internal/AtomicOp;-><clinit>()V
+HSPLkotlinx/coroutines/internal/AtomicOp;-><init>()V
+HSPLkotlinx/coroutines/internal/AtomicOp;->perform(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/internal/ConcurrentLinkedListNode;-><clinit>()V
+HSPLkotlinx/coroutines/internal/ConcurrentLinkedListNode;-><init>(Lkotlinx/coroutines/internal/ConcurrentLinkedListNode;)V
+HSPLkotlinx/coroutines/internal/ConcurrentLinkedListNode;->cleanPrev()V
+HSPLkotlinx/coroutines/internal/ConcurrentLinkedListNode;->getNext()Lkotlinx/coroutines/internal/ConcurrentLinkedListNode;
+HSPLkotlinx/coroutines/internal/ContextScope;-><init>(Lkotlin/coroutines/CoroutineContext;)V
+HSPLkotlinx/coroutines/internal/ContextScope;->getCoroutineContext()Lkotlin/coroutines/CoroutineContext;
+HSPLkotlinx/coroutines/internal/DispatchedContinuation;-><clinit>()V
+HSPLkotlinx/coroutines/internal/DispatchedContinuation;-><init>(Lkotlinx/coroutines/CoroutineDispatcher;Lkotlin/coroutines/jvm/internal/ContinuationImpl;)V
+HSPLkotlinx/coroutines/internal/DispatchedContinuation;->getContext()Lkotlin/coroutines/CoroutineContext;
+HSPLkotlinx/coroutines/internal/DispatchedContinuation;->getDelegate$kotlinx_coroutines_core()Lkotlin/coroutines/Continuation;
+HSPLkotlinx/coroutines/internal/DispatchedContinuation;->resumeWith(Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/internal/DispatchedContinuation;->takeState$kotlinx_coroutines_core()Ljava/lang/Object;
+HSPLkotlinx/coroutines/internal/LimitedDispatcher;-><clinit>()V
+HSPLkotlinx/coroutines/internal/LimitedDispatcher;-><init>(Lkotlinx/coroutines/scheduling/UnlimitedIoScheduler;I)V
+HSPLkotlinx/coroutines/internal/LockFreeLinkedListNode$toString$1;-><init>(ILjava/lang/Object;)V
+HSPLkotlinx/coroutines/internal/LockFreeLinkedListNode;-><clinit>()V
+HSPLkotlinx/coroutines/internal/LockFreeLinkedListNode;-><init>()V
+HSPLkotlinx/coroutines/internal/LockFreeLinkedListNode;->correctPrev()Lkotlinx/coroutines/internal/LockFreeLinkedListNode;
+HSPLkotlinx/coroutines/internal/LockFreeLinkedListNode;->finishAdd(Lkotlinx/coroutines/internal/LockFreeLinkedListNode;)V
+HSPLkotlinx/coroutines/internal/LockFreeLinkedListNode;->getNext()Ljava/lang/Object;
+HSPLkotlinx/coroutines/internal/LockFreeLinkedListNode;->getNextNode()Lkotlinx/coroutines/internal/LockFreeLinkedListNode;
+HSPLkotlinx/coroutines/internal/LockFreeLinkedListNode;->getPrevNode()Lkotlinx/coroutines/internal/LockFreeLinkedListNode;
+HSPLkotlinx/coroutines/internal/LockFreeLinkedListNode;->isRemoved()Z
+HSPLkotlinx/coroutines/internal/LockFreeTaskQueue;-><clinit>()V
+HSPLkotlinx/coroutines/internal/LockFreeTaskQueue;-><init>()V
+HSPLkotlinx/coroutines/internal/LockFreeTaskQueueCore;-><clinit>()V
+HSPLkotlinx/coroutines/internal/LockFreeTaskQueueCore;-><init>(IZ)V
+HSPLkotlinx/coroutines/internal/MainDispatcherLoader;-><clinit>()V
+HSPLkotlinx/coroutines/internal/Removed;-><init>(Lkotlinx/coroutines/internal/LockFreeLinkedListNode;)V
+HSPLkotlinx/coroutines/internal/ResizableAtomicArray;-><init>(I)V
+HSPLkotlinx/coroutines/internal/ScopeCoroutine;-><init>(Lkotlin/coroutines/Continuation;Lkotlin/coroutines/CoroutineContext;)V
+HSPLkotlinx/coroutines/internal/ScopeCoroutine;->afterCompletion(Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/internal/ScopeCoroutine;->afterResume(Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/internal/ScopeCoroutine;->isScopedCoroutine()Z
+HSPLkotlinx/coroutines/internal/Segment;-><clinit>()V
+HSPLkotlinx/coroutines/internal/Segment;-><init>(JLkotlinx/coroutines/internal/Segment;I)V
+HSPLkotlinx/coroutines/internal/Segment;->decPointers$kotlinx_coroutines_core()Z
+HSPLkotlinx/coroutines/internal/Segment;->isRemoved()Z
+HSPLkotlinx/coroutines/internal/Segment;->onSlotCleaned()V
+HSPLkotlinx/coroutines/internal/Segment;->tryIncPointers$kotlinx_coroutines_core()Z
+HSPLkotlinx/coroutines/internal/Symbol;-><init>(ILjava/lang/String;)V
+HSPLkotlinx/coroutines/internal/SystemPropsKt__SystemPropsKt;-><clinit>()V
+HSPLkotlinx/coroutines/scheduling/CoroutineScheduler;-><clinit>()V
+HSPLkotlinx/coroutines/scheduling/CoroutineScheduler;-><init>(IIJLjava/lang/String;)V
+HSPLkotlinx/coroutines/scheduling/DefaultIoScheduler;-><clinit>()V
+HSPLkotlinx/coroutines/scheduling/DefaultScheduler;-><clinit>()V
+HSPLkotlinx/coroutines/scheduling/DefaultScheduler;-><init>()V
+HSPLkotlinx/coroutines/scheduling/NanoTimeSource;-><clinit>()V
+HSPLkotlinx/coroutines/scheduling/SchedulerCoroutineDispatcher;-><init>(IIJLjava/lang/String;)V
+HSPLkotlinx/coroutines/scheduling/Task;-><init>(JLkotlin/ULong$Companion;)V
+HSPLkotlinx/coroutines/scheduling/TasksKt;-><clinit>()V
+HSPLkotlinx/coroutines/scheduling/UnlimitedIoScheduler;-><clinit>()V
+HSPLkotlinx/coroutines/sync/MutexImpl$CancellableContinuationWithOwner$resume$2;-><init>(Lkotlinx/coroutines/sync/MutexImpl;Lkotlinx/coroutines/sync/MutexImpl$CancellableContinuationWithOwner;I)V
+HSPLkotlinx/coroutines/sync/MutexImpl$CancellableContinuationWithOwner;-><init>(Lkotlinx/coroutines/sync/MutexImpl;Lkotlinx/coroutines/CancellableContinuationImpl;)V
+HSPLkotlinx/coroutines/sync/MutexImpl$CancellableContinuationWithOwner;->completeResume(Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/sync/MutexImpl$CancellableContinuationWithOwner;->invokeOnCancellation(Lkotlinx/coroutines/internal/Segment;I)V
+HSPLkotlinx/coroutines/sync/MutexImpl$CancellableContinuationWithOwner;->tryResume(Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/internal/Symbol;
+HSPLkotlinx/coroutines/sync/MutexImpl;-><clinit>()V
+HSPLkotlinx/coroutines/sync/MutexImpl;-><init>(Z)V
+HSPLkotlinx/coroutines/sync/MutexImpl;->isLocked()Z
+HSPLkotlinx/coroutines/sync/MutexImpl;->lock(Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/sync/MutexImpl;->unlock(Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/sync/SemaphoreImpl$addAcquireToQueue$createNewSegment$1;-><clinit>()V
+HSPLkotlinx/coroutines/sync/SemaphoreImpl$addAcquireToQueue$createNewSegment$1;-><init>()V
+HSPLkotlinx/coroutines/sync/SemaphoreImpl$tryResumeNextFromQueue$createNewSegment$1;-><clinit>()V
+HSPLkotlinx/coroutines/sync/SemaphoreImpl$tryResumeNextFromQueue$createNewSegment$1;-><init>()V
+HSPLkotlinx/coroutines/sync/SemaphoreImpl;-><clinit>()V
+HSPLkotlinx/coroutines/sync/SemaphoreImpl;-><init>(I)V
+HSPLkotlinx/coroutines/sync/SemaphoreImpl;->acquire(Lkotlinx/coroutines/sync/MutexImpl$CancellableContinuationWithOwner;)V
+HSPLkotlinx/coroutines/sync/SemaphoreImpl;->release()V
+HSPLkotlinx/coroutines/sync/SemaphoreKt;-><clinit>()V
+HSPLkotlinx/coroutines/sync/SemaphoreSegment;-><init>(JLkotlinx/coroutines/sync/SemaphoreSegment;I)V
+HSPLkotlinx/coroutines/sync/SemaphoreSegment;->getNumberOfSlots()I
+HSPLokhttp3/Headers$Builder;-><init>()V
+HSPLokhttp3/Headers$Builder;->add(I)V
+HSPLokhttp3/Headers$Builder;->takeMax()I
+HSPLokhttp3/MediaType;-><clinit>()V
+HSPLokhttp3/MediaType;->Channel$default(ILkotlinx/coroutines/channels/BufferOverflow;I)Lkotlinx/coroutines/channels/BufferedChannel;
+HSPLokhttp3/MediaType;->CompositionLocalProvider(Landroidx/compose/runtime/ProvidedValue;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;I)V
+HSPLokhttp3/MediaType;->CoroutineScope(Lkotlin/coroutines/CoroutineContext;)Lkotlinx/coroutines/internal/ContextScope;
+HSPLokhttp3/MediaType;->LazyLayoutPinnableItem(Ljava/lang/Object;ILandroidx/compose/foundation/lazy/layout/LazyLayoutPinnedItemList;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;I)V
+HSPLokhttp3/MediaType;->LazySaveableStateHolderProvider(Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;I)V
+HSPLokhttp3/MediaType;->Offset(FF)J
+HSPLokhttp3/MediaType;->ParagraphIntrinsics(Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/font/FontFamily$Resolver;Landroidx/compose/ui/unit/Density;Ljava/lang/String;Ljava/util/List;Ljava/util/List;)Landroidx/compose/ui/text/platform/AndroidParagraphIntrinsics;
+HSPLokhttp3/MediaType;->RoundRect-gG7oq9Y(FFFFJ)Landroidx/compose/ui/geometry/RoundRect;
+HSPLokhttp3/MediaType;->TextRange(II)J
+HSPLokhttp3/MediaType;->access$SkippableItem-JVlU9Rs(Landroidx/compose/foundation/lazy/layout/LazyLayoutItemProvider;Ljava/lang/Object;ILjava/lang/Object;Landroidx/compose/runtime/Composer;I)V
+HSPLokhttp3/MediaType;->access$checkIndex(ILjava/util/List;)V
+HSPLokhttp3/MediaType;->access$containsMark([II)Z
+HSPLokhttp3/MediaType;->access$groupSize([II)I
+HSPLokhttp3/MediaType;->access$hasAux([II)Z
+HSPLokhttp3/MediaType;->access$isChainUpdate(Landroidx/compose/ui/node/BackwardsCompatNode;)Z
+HSPLokhttp3/MediaType;->access$isNode([II)Z
+HSPLokhttp3/MediaType;->access$nodeCount([II)I
+HSPLokhttp3/MediaType;->access$slotAnchor([II)I
+HSPLokhttp3/MediaType;->access$updateGroupSize([III)V
+HSPLokhttp3/MediaType;->access$updateNodeCount([III)V
+HSPLokhttp3/MediaType;->adapt$default(Landroidx/compose/ui/graphics/colorspace/ColorSpace;)Landroidx/compose/ui/graphics/colorspace/ColorSpace;
+HSPLokhttp3/MediaType;->cancel(Lkotlinx/coroutines/CoroutineScope;Landroidx/lifecycle/LifecycleDestroyedException;)V
+HSPLokhttp3/MediaType;->ceilToIntPx(F)I
+HSPLokhttp3/MediaType;->checkParallelism(I)V
+HSPLokhttp3/MediaType;->chromaticAdaptation([F[F[F)[F
+HSPLokhttp3/MediaType;->coerceIn-8ffj60Q(IJ)J
+HSPLokhttp3/MediaType;->collectIsPressedAsState(Landroidx/compose/foundation/interaction/InteractionSource;Landroidx/compose/runtime/Composer;I)Landroidx/compose/runtime/MutableState;
+HSPLokhttp3/MediaType;->colors-u3YEpmA(JJJJJJJJJJJJLandroidx/compose/runtime/Composer;II)Landroidx/tv/material3/ToggleableSurfaceColors;
+HSPLokhttp3/MediaType;->compare(Landroidx/compose/ui/graphics/colorspace/WhitePoint;Landroidx/compose/ui/graphics/colorspace/WhitePoint;)Z
+HSPLokhttp3/MediaType;->coroutineScope(Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLokhttp3/MediaType;->countOneBits(I)I
+HSPLokhttp3/MediaType;->createFontFamilyResolver(Landroid/content/Context;)Landroidx/compose/ui/text/font/FontFamilyResolverImpl;
+HSPLokhttp3/MediaType;->foldCopies(Lkotlin/coroutines/CoroutineContext;Lkotlin/coroutines/CoroutineContext;Z)Lkotlin/coroutines/CoroutineContext;
+HSPLokhttp3/MediaType;->getCharSequenceBounds(Landroid/text/TextPaint;Ljava/lang/CharSequence;II)Landroid/graphics/Rect;
+HSPLokhttp3/MediaType;->getFontFamilyResult(Landroid/content/Context;Landroidx/core/provider/FontRequest;)Landroidx/compose/ui/input/pointer/util/PointerIdArray;
+HSPLokhttp3/MediaType;->getOrNull(Landroidx/compose/ui/semantics/SemanticsConfiguration;Landroidx/compose/ui/semantics/SemanticsPropertyKey;)Ljava/lang/Object;
+HSPLokhttp3/MediaType;->getSegment-impl(Ljava/lang/Object;)Lkotlinx/coroutines/internal/Segment;
+HSPLokhttp3/MediaType;->inverse3x3([F)[F
+HSPLokhttp3/MediaType;->invokeComposable(Landroidx/compose/runtime/Composer;Lkotlin/jvm/functions/Function2;)V
+HSPLokhttp3/MediaType;->invokeOnCompletion$default(Lkotlinx/coroutines/Job;ZLkotlinx/coroutines/JobNode;I)Lkotlinx/coroutines/DisposableHandle;
+HSPLokhttp3/MediaType;->isActive(Lkotlinx/coroutines/CoroutineScope;)Z
+HSPLokhttp3/MediaType;->isClosed-impl(Ljava/lang/Object;)Z
+HSPLokhttp3/MediaType;->mul3x3([F[F)[F
+HSPLokhttp3/MediaType;->mul3x3Diag([F[F)[F
+HSPLokhttp3/MediaType;->mul3x3Float3([F[F)V
+HSPLokhttp3/MediaType;->mul3x3Float3_0([FFFF)F
+HSPLokhttp3/MediaType;->mul3x3Float3_1([FFFF)F
+HSPLokhttp3/MediaType;->mul3x3Float3_2([FFFF)F
+HSPLokhttp3/MediaType;->search(Ljava/util/ArrayList;II)I
+HSPLokhttp3/MediaType;->set-impl(Landroidx/compose/runtime/Composer;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)V
+HSPLokhttp3/MediaType;->setInt-A6tL2VI(Landroidx/compose/runtime/changelist/Operations;II)V
+HSPLokhttp3/MediaType;->setObject-DKhxnng(Landroidx/compose/runtime/changelist/Operations;ILjava/lang/Object;)V
+HSPLokhttp3/MediaType;->shape(Landroidx/compose/ui/graphics/RectangleShapeKt$RectangleShape$1;Landroidx/compose/runtime/Composer;I)Landroidx/tv/material3/ToggleableSurfaceShape;
+HSPLokhttp3/MediaType;->toArray(Ljava/util/Collection;)[Ljava/lang/Object;
+HSPLokhttp3/MediaType;->updateChangedFlags(I)I
+L_COROUTINE/ArtificialStackFrames;
+Landroidx/activity/ComponentActivity$$ExternalSyntheticLambda0;
+Landroidx/activity/ComponentActivity$$ExternalSyntheticLambda1;
+Landroidx/activity/ComponentActivity$$ExternalSyntheticLambda2;
+Landroidx/activity/ComponentActivity$$ExternalSyntheticLambda3;
+Landroidx/activity/ComponentActivity$1;
+Landroidx/activity/ComponentActivity$2;
+Landroidx/activity/ComponentActivity$3;
+Landroidx/activity/ComponentActivity$4;
+Landroidx/activity/ComponentActivity$5;
+Landroidx/activity/ComponentActivity$NonConfigurationInstances;
+Landroidx/activity/ComponentActivity$ReportFullyDrawnExecutorApi16Impl;
+Landroidx/activity/ComponentActivity;
+Landroidx/activity/FullyDrawnReporter;
+Landroidx/activity/OnBackPressedDispatcher;
+Landroidx/activity/compose/ComponentActivityKt;
+Landroidx/activity/contextaware/ContextAwareHelper;
+Landroidx/activity/result/ActivityResult$1;
+Landroidx/arch/core/executor/ArchTaskExecutor;
+Landroidx/arch/core/executor/DefaultTaskExecutor$1;
+Landroidx/arch/core/executor/DefaultTaskExecutor;
+Landroidx/arch/core/internal/FastSafeIterableMap;
+Landroidx/arch/core/internal/SafeIterableMap$AscendingIterator;
+Landroidx/arch/core/internal/SafeIterableMap$Entry;
+Landroidx/arch/core/internal/SafeIterableMap$IteratorWithAdditions;
+Landroidx/arch/core/internal/SafeIterableMap$ListIterator;
+Landroidx/arch/core/internal/SafeIterableMap$SupportRemove;
+Landroidx/arch/core/internal/SafeIterableMap;
+Landroidx/collection/ArrayMap;
+Landroidx/collection/ArraySet;
+Landroidx/collection/LongSparseArray;
+Landroidx/collection/SimpleArrayMap;
+Landroidx/collection/SparseArrayCompat;
+Landroidx/compose/animation/FlingCalculator;
+Landroidx/compose/animation/FlingCalculatorKt;
+Landroidx/compose/animation/SingleValueAnimationKt;
+Landroidx/compose/animation/SplineBasedFloatDecayAnimationSpec;
+Landroidx/compose/animation/SplineBasedFloatDecayAnimationSpec_androidKt;
+Landroidx/compose/animation/core/Animatable$runAnimation$2;
+Landroidx/compose/animation/core/Animatable;
+Landroidx/compose/animation/core/AnimateAsStateKt$animateValueAsState$2;
+Landroidx/compose/animation/core/AnimateAsStateKt$animateValueAsState$3$1;
+Landroidx/compose/animation/core/AnimateAsStateKt$animateValueAsState$3;
+Landroidx/compose/animation/core/AnimateAsStateKt;
+Landroidx/compose/animation/core/Animation;
+Landroidx/compose/animation/core/AnimationEndReason$EnumUnboxingLocalUtility;
+Landroidx/compose/animation/core/AnimationResult;
+Landroidx/compose/animation/core/AnimationScope;
+Landroidx/compose/animation/core/AnimationSpec;
+Landroidx/compose/animation/core/AnimationState;
+Landroidx/compose/animation/core/AnimationVector1D;
+Landroidx/compose/animation/core/AnimationVector2D;
+Landroidx/compose/animation/core/AnimationVector3D;
+Landroidx/compose/animation/core/AnimationVector4D;
+Landroidx/compose/animation/core/AnimationVector;
+Landroidx/compose/animation/core/Animations;
+Landroidx/compose/animation/core/ComplexDouble;
+Landroidx/compose/animation/core/CubicBezierEasing;
+Landroidx/compose/animation/core/DecayAnimationSpecImpl;
+Landroidx/compose/animation/core/Easing;
+Landroidx/compose/animation/core/EasingKt$$ExternalSyntheticLambda0;
+Landroidx/compose/animation/core/EasingKt;
+Landroidx/compose/animation/core/FloatAnimationSpec;
+Landroidx/compose/animation/core/FloatDecayAnimationSpec;
+Landroidx/compose/animation/core/FloatSpringSpec;
+Landroidx/compose/animation/core/FloatTweenSpec;
+Landroidx/compose/animation/core/MutatorMutex$Mutator;
+Landroidx/compose/animation/core/MutatorMutex$mutate$2;
+Landroidx/compose/animation/core/MutatorMutex;
+Landroidx/compose/animation/core/SpringSimulation;
+Landroidx/compose/animation/core/SpringSpec;
+Landroidx/compose/animation/core/SuspendAnimationKt$animate$4;
+Landroidx/compose/animation/core/SuspendAnimationKt$animate$6;
+Landroidx/compose/animation/core/SuspendAnimationKt$animate$7;
+Landroidx/compose/animation/core/SuspendAnimationKt$animate$9;
+Landroidx/compose/animation/core/TargetBasedAnimation;
+Landroidx/compose/animation/core/TweenSpec;
+Landroidx/compose/animation/core/TwoWayConverterImpl;
+Landroidx/compose/animation/core/VectorConvertersKt;
+Landroidx/compose/animation/core/VectorizedDurationBasedAnimationSpec;
+Landroidx/compose/animation/core/VectorizedFiniteAnimationSpec;
+Landroidx/compose/animation/core/VectorizedFloatAnimationSpec;
+Landroidx/compose/animation/core/VectorizedSpringSpec;
+Landroidx/compose/animation/core/VectorizedTweenSpec;
+Landroidx/compose/animation/core/VisibilityThresholdsKt;
+Landroidx/compose/foundation/AndroidEdgeEffectOverscrollEffect$effectModifier$1;
+Landroidx/compose/foundation/AndroidEdgeEffectOverscrollEffect$onNewSize$1;
+Landroidx/compose/foundation/AndroidEdgeEffectOverscrollEffect;
+Landroidx/compose/foundation/AndroidOverscrollKt;
+Landroidx/compose/foundation/Api31Impl;
+Landroidx/compose/foundation/BackgroundElement;
+Landroidx/compose/foundation/BackgroundNode;
+Landroidx/compose/foundation/BorderCache;
+Landroidx/compose/foundation/BorderKt$drawRectBorder$1;
+Landroidx/compose/foundation/BorderModifierNode$drawGenericBorder$3;
+Landroidx/compose/foundation/BorderModifierNode$drawRoundRectBorder$1;
+Landroidx/compose/foundation/BorderModifierNode;
+Landroidx/compose/foundation/BorderModifierNodeElement;
+Landroidx/compose/foundation/BorderStroke;
+Landroidx/compose/foundation/ClipScrollableContainerKt;
+Landroidx/compose/foundation/DrawOverscrollModifier;
+Landroidx/compose/foundation/FocusableElement;
+Landroidx/compose/foundation/FocusableInteractionNode$emitWithFallback$1;
+Landroidx/compose/foundation/FocusableInteractionNode;
+Landroidx/compose/foundation/FocusableKt$FocusableInNonTouchModeElement$1;
+Landroidx/compose/foundation/FocusableKt;
+Landroidx/compose/foundation/FocusableNode$onFocusEvent$1;
+Landroidx/compose/foundation/FocusableNode;
+Landroidx/compose/foundation/FocusablePinnableContainerNode;
+Landroidx/compose/foundation/FocusableSemanticsNode;
+Landroidx/compose/foundation/FocusedBoundsKt;
+Landroidx/compose/foundation/FocusedBoundsNode;
+Landroidx/compose/foundation/FocusedBoundsObserverNode;
+Landroidx/compose/foundation/ImageKt$Image$1$1;
+Landroidx/compose/foundation/ImageKt$Image$1;
+Landroidx/compose/foundation/ImageKt$Image$2;
+Landroidx/compose/foundation/ImageKt;
+Landroidx/compose/foundation/Indication;
+Landroidx/compose/foundation/IndicationInstance;
+Landroidx/compose/foundation/IndicationKt$indication$2;
+Landroidx/compose/foundation/IndicationKt;
+Landroidx/compose/foundation/IndicationModifier;
+Landroidx/compose/foundation/MutatePriority;
+Landroidx/compose/foundation/MutatorMutex$Mutator;
+Landroidx/compose/foundation/MutatorMutex$mutateWith$2;
+Landroidx/compose/foundation/MutatorMutex;
+Landroidx/compose/foundation/OverscrollConfiguration;
+Landroidx/compose/foundation/OverscrollConfigurationKt;
+Landroidx/compose/foundation/OverscrollEffect;
+Landroidx/compose/foundation/ScrollKt$rememberScrollState$1$1;
+Landroidx/compose/foundation/ScrollKt$scroll$2$semantics$1$1;
+Landroidx/compose/foundation/ScrollKt$scroll$2$semantics$1;
+Landroidx/compose/foundation/ScrollKt$scroll$2;
+Landroidx/compose/foundation/ScrollState$canScrollForward$2;
+Landroidx/compose/foundation/ScrollState;
+Landroidx/compose/foundation/ScrollingLayoutElement;
+Landroidx/compose/foundation/ScrollingLayoutNode;
+Landroidx/compose/foundation/gestures/BringIntoViewRequestPriorityQueue;
+Landroidx/compose/foundation/gestures/BringIntoViewSpec$Companion$DefaultBringIntoViewSpec$1;
+Landroidx/compose/foundation/gestures/BringIntoViewSpec$Companion;
+Landroidx/compose/foundation/gestures/BringIntoViewSpec;
+Landroidx/compose/foundation/gestures/ContentInViewNode$Request;
+Landroidx/compose/foundation/gestures/ContentInViewNode$launchAnimation$2$1;
+Landroidx/compose/foundation/gestures/ContentInViewNode$launchAnimation$2;
+Landroidx/compose/foundation/gestures/ContentInViewNode;
+Landroidx/compose/foundation/gestures/DefaultFlingBehavior;
+Landroidx/compose/foundation/gestures/DefaultScrollableState$scroll$2$1;
+Landroidx/compose/foundation/gestures/DefaultScrollableState$scroll$2;
+Landroidx/compose/foundation/gestures/DefaultScrollableState$scrollScope$1;
+Landroidx/compose/foundation/gestures/DefaultScrollableState;
+Landroidx/compose/foundation/gestures/DraggableKt$awaitDrag$2;
+Landroidx/compose/foundation/gestures/DraggableNode$onAttach$1;
+Landroidx/compose/foundation/gestures/DraggableNode$pointerInputNode$1;
+Landroidx/compose/foundation/gestures/DraggableNode;
+Landroidx/compose/foundation/gestures/FlingBehavior;
+Landroidx/compose/foundation/gestures/ModifierLocalScrollableContainerProvider;
+Landroidx/compose/foundation/gestures/MouseWheelScrollNode$1;
+Landroidx/compose/foundation/gestures/MouseWheelScrollNode;
+Landroidx/compose/foundation/gestures/Orientation;
+Landroidx/compose/foundation/gestures/ScrollDraggableState;
+Landroidx/compose/foundation/gestures/ScrollScope;
+Landroidx/compose/foundation/gestures/ScrollableElement;
+Landroidx/compose/foundation/gestures/ScrollableGesturesNode$onDragStopped$1;
+Landroidx/compose/foundation/gestures/ScrollableGesturesNode;
+Landroidx/compose/foundation/gestures/ScrollableKt$DefaultScrollMotionDurationScale$1;
+Landroidx/compose/foundation/gestures/ScrollableKt$NoOpOnDragStarted$1;
+Landroidx/compose/foundation/gestures/ScrollableKt$NoOpScrollScope$1;
+Landroidx/compose/foundation/gestures/ScrollableKt$UnityDensity$1;
+Landroidx/compose/foundation/gestures/ScrollableKt;
+Landroidx/compose/foundation/gestures/ScrollableNestedScrollConnection;
+Landroidx/compose/foundation/gestures/ScrollableNode;
+Landroidx/compose/foundation/gestures/ScrollableState;
+Landroidx/compose/foundation/gestures/ScrollingLogic;
+Landroidx/compose/foundation/gestures/UpdatableAnimationState$animateToZero$1;
+Landroidx/compose/foundation/gestures/UpdatableAnimationState$animateToZero$4;
+Landroidx/compose/foundation/gestures/UpdatableAnimationState;
+Landroidx/compose/foundation/interaction/FocusInteraction$Focus;
+Landroidx/compose/foundation/interaction/FocusInteraction$Unfocus;
+Landroidx/compose/foundation/interaction/FocusInteractionKt$collectIsFocusedAsState$1$1$1;
+Landroidx/compose/foundation/interaction/FocusInteractionKt$collectIsFocusedAsState$1$1;
+Landroidx/compose/foundation/interaction/Interaction;
+Landroidx/compose/foundation/interaction/InteractionSource;
+Landroidx/compose/foundation/interaction/MutableInteractionSourceImpl;
+Landroidx/compose/foundation/interaction/PressInteraction$Press;
+Landroidx/compose/foundation/interaction/PressInteraction$Release;
+Landroidx/compose/foundation/interaction/PressInteractionKt$collectIsPressedAsState$1$1;
+Landroidx/compose/foundation/layout/Arrangement$Center$1;
+Landroidx/compose/foundation/layout/Arrangement$End$1;
+Landroidx/compose/foundation/layout/Arrangement$Horizontal;
+Landroidx/compose/foundation/layout/Arrangement$SpacedAligned;
+Landroidx/compose/foundation/layout/Arrangement$Top$1;
+Landroidx/compose/foundation/layout/Arrangement$Vertical;
+Landroidx/compose/foundation/layout/Arrangement;
+Landroidx/compose/foundation/layout/BoxKt$Box$2;
+Landroidx/compose/foundation/layout/BoxKt$EmptyBoxMeasurePolicy$1;
+Landroidx/compose/foundation/layout/BoxKt$boxMeasurePolicy$1$2;
+Landroidx/compose/foundation/layout/BoxKt$boxMeasurePolicy$1;
+Landroidx/compose/foundation/layout/BoxKt;
+Landroidx/compose/foundation/layout/BoxScope;
+Landroidx/compose/foundation/layout/BoxScopeInstance;
+Landroidx/compose/foundation/layout/ColumnKt;
+Landroidx/compose/foundation/layout/CrossAxisAlignment$VerticalCrossAxisAlignment;
+Landroidx/compose/foundation/layout/FillElement;
+Landroidx/compose/foundation/layout/FillNode;
+Landroidx/compose/foundation/layout/HorizontalAlignElement;
+Landroidx/compose/foundation/layout/HorizontalAlignNode;
+Landroidx/compose/foundation/layout/OffsetElement;
+Landroidx/compose/foundation/layout/OffsetKt;
+Landroidx/compose/foundation/layout/OffsetNode$measure$1;
+Landroidx/compose/foundation/layout/OffsetNode;
+Landroidx/compose/foundation/layout/PaddingElement;
+Landroidx/compose/foundation/layout/PaddingNode;
+Landroidx/compose/foundation/layout/PaddingValuesImpl;
+Landroidx/compose/foundation/layout/RowColumnImplKt$rowColumnMeasurePolicy$1;
+Landroidx/compose/foundation/layout/RowColumnMeasureHelperResult;
+Landroidx/compose/foundation/layout/RowColumnMeasurementHelper;
+Landroidx/compose/foundation/layout/RowColumnParentData;
+Landroidx/compose/foundation/layout/RowKt$DefaultRowMeasurePolicy$1;
+Landroidx/compose/foundation/layout/RowKt$rowMeasurePolicy$1$1;
+Landroidx/compose/foundation/layout/RowKt;
+Landroidx/compose/foundation/layout/RowScope;
+Landroidx/compose/foundation/layout/RowScopeInstance;
+Landroidx/compose/foundation/layout/SizeElement;
+Landroidx/compose/foundation/layout/SizeKt;
+Landroidx/compose/foundation/layout/SizeNode;
+Landroidx/compose/foundation/layout/SpacerMeasurePolicy;
+Landroidx/compose/foundation/layout/WrapContentElement;
+Landroidx/compose/foundation/layout/WrapContentNode$measure$1;
+Landroidx/compose/foundation/layout/WrapContentNode;
+Landroidx/compose/foundation/lazy/layout/DefaultLazyKey;
+Landroidx/compose/foundation/lazy/layout/IntervalList$Interval;
+Landroidx/compose/foundation/lazy/layout/LazyLayoutIntervalContent$Interval;
+Landroidx/compose/foundation/lazy/layout/LazyLayoutItemContentFactory$CachedItemContent;
+Landroidx/compose/foundation/lazy/layout/LazyLayoutItemContentFactory;
+Landroidx/compose/foundation/lazy/layout/LazyLayoutItemProvider;
+Landroidx/compose/foundation/lazy/layout/LazyLayoutItemReusePolicy;
+Landroidx/compose/foundation/lazy/layout/LazyLayoutKt$LazyLayout$3$2$1;
+Landroidx/compose/foundation/lazy/layout/LazyLayoutKt$LazyLayout$3$itemContentFactory$1$1;
+Landroidx/compose/foundation/lazy/layout/LazyLayoutKt$LazyLayout$3;
+Landroidx/compose/foundation/lazy/layout/LazyLayoutMeasureScopeImpl;
+Landroidx/compose/foundation/lazy/layout/LazyLayoutPinnableItem;
+Landroidx/compose/foundation/lazy/layout/LazyLayoutPinnedItemList;
+Landroidx/compose/foundation/lazy/layout/LazyLayoutPrefetchState$PrefetchHandle;
+Landroidx/compose/foundation/lazy/layout/LazyLayoutPrefetchState$Prefetcher;
+Landroidx/compose/foundation/lazy/layout/LazyLayoutPrefetchState;
+Landroidx/compose/foundation/lazy/layout/LazyLayoutPrefetcher$PrefetchRequest;
+Landroidx/compose/foundation/lazy/layout/LazyLayoutPrefetcher;
+Landroidx/compose/foundation/lazy/layout/LazySaveableStateHolder$1;
+Landroidx/compose/foundation/lazy/layout/LazySaveableStateHolder$SaveableStateProvider$2$invoke$$inlined$onDispose$1;
+Landroidx/compose/foundation/lazy/layout/LazySaveableStateHolder;
+Landroidx/compose/foundation/lazy/layout/MutableIntervalList;
+Landroidx/compose/foundation/relocation/BringIntoViewChildNode;
+Landroidx/compose/foundation/relocation/BringIntoViewKt;
+Landroidx/compose/foundation/relocation/BringIntoViewParent;
+Landroidx/compose/foundation/relocation/BringIntoViewRequesterImpl$bringIntoView$1;
+Landroidx/compose/foundation/relocation/BringIntoViewRequesterImpl;
+Landroidx/compose/foundation/relocation/BringIntoViewRequesterNode;
+Landroidx/compose/foundation/relocation/BringIntoViewResponder;
+Landroidx/compose/foundation/relocation/BringIntoViewResponderNode$bringChildIntoView$2$1$1;
+Landroidx/compose/foundation/relocation/BringIntoViewResponderNode$bringChildIntoView$2$1;
+Landroidx/compose/foundation/relocation/BringIntoViewResponderNode$bringChildIntoView$2$2;
+Landroidx/compose/foundation/relocation/BringIntoViewResponderNode$bringChildIntoView$2;
+Landroidx/compose/foundation/relocation/BringIntoViewResponderNode$bringChildIntoView$parentRect$1;
+Landroidx/compose/foundation/relocation/BringIntoViewResponderNode;
+Landroidx/compose/foundation/relocation/BringIntoViewResponder_androidKt$defaultBringIntoViewParent$1;
+Landroidx/compose/foundation/shape/CornerBasedShape;
+Landroidx/compose/foundation/shape/CornerSize;
+Landroidx/compose/foundation/shape/DpCornerSize;
+Landroidx/compose/foundation/shape/GenericShape;
+Landroidx/compose/foundation/shape/PercentCornerSize;
+Landroidx/compose/foundation/shape/RoundedCornerShape;
+Landroidx/compose/foundation/shape/RoundedCornerShapeKt;
+Landroidx/compose/foundation/text/EmptyMeasurePolicy;
+Landroidx/compose/foundation/text/modifiers/InlineDensity;
+Landroidx/compose/foundation/text/modifiers/MinLinesConstrainer;
+Landroidx/compose/foundation/text/modifiers/MultiParagraphLayoutCache;
+Landroidx/compose/foundation/text/modifiers/TextAnnotatedStringElement;
+Landroidx/compose/foundation/text/modifiers/TextAnnotatedStringNode$TextSubstitutionValue;
+Landroidx/compose/foundation/text/modifiers/TextAnnotatedStringNode;
+Landroidx/compose/foundation/text/selection/SelectionRegistrarKt;
+Landroidx/compose/foundation/text/selection/TextSelectionColors;
+Landroidx/compose/foundation/text/selection/TextSelectionColorsKt;
+Landroidx/compose/material/ripple/AndroidRippleIndicationInstance;
+Landroidx/compose/material/ripple/PlatformRipple;
+Landroidx/compose/material/ripple/RippleAlpha;
+Landroidx/compose/material/ripple/RippleIndicationInstance;
+Landroidx/compose/material/ripple/RippleKt;
+Landroidx/compose/material/ripple/RippleTheme;
+Landroidx/compose/material/ripple/RippleThemeKt;
+Landroidx/compose/material3/ColorScheme;
+Landroidx/compose/material3/ColorSchemeKt;
+Landroidx/compose/material3/MaterialRippleTheme;
+Landroidx/compose/material3/MaterialThemeKt$MaterialTheme$2;
+Landroidx/compose/material3/ShapeDefaults;
+Landroidx/compose/material3/Shapes;
+Landroidx/compose/material3/ShapesKt$LocalShapes$1;
+Landroidx/compose/material3/ShapesKt;
+Landroidx/compose/material3/TextKt$ProvideTextStyle$1;
+Landroidx/compose/material3/TextKt$Text$1;
+Landroidx/compose/material3/TextKt;
+Landroidx/compose/material3/Typography;
+Landroidx/compose/material3/TypographyKt;
+Landroidx/compose/material3/tokens/ColorDarkTokens;
+Landroidx/compose/material3/tokens/PaletteTokens;
+Landroidx/compose/material3/tokens/ShapeTokens;
+Landroidx/compose/material3/tokens/TypeScaleTokens;
+Landroidx/compose/material3/tokens/TypefaceTokens;
+Landroidx/compose/material3/tokens/TypographyTokens;
+Landroidx/compose/runtime/Anchor;
+Landroidx/compose/runtime/Applier;
+Landroidx/compose/runtime/BroadcastFrameClock$FrameAwaiter;
+Landroidx/compose/runtime/BroadcastFrameClock;
+Landroidx/compose/runtime/ComposableSingletons$CompositionKt;
+Landroidx/compose/runtime/ComposeNodeLifecycleCallback;
+Landroidx/compose/runtime/Composer;
+Landroidx/compose/runtime/ComposerImpl$CompositionContextHolder;
+Landroidx/compose/runtime/ComposerImpl$CompositionContextImpl;
+Landroidx/compose/runtime/ComposerImpl$derivedStateObserver$1;
+Landroidx/compose/runtime/ComposerImpl;
+Landroidx/compose/runtime/Composition;
+Landroidx/compose/runtime/CompositionContext;
+Landroidx/compose/runtime/CompositionContextKt;
+Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;
+Landroidx/compose/runtime/CompositionImpl;
+Landroidx/compose/runtime/CompositionKt;
+Landroidx/compose/runtime/CompositionLocal;
+Landroidx/compose/runtime/CompositionLocalMap$Companion;
+Landroidx/compose/runtime/CompositionLocalMap;
+Landroidx/compose/runtime/CompositionObserverHolder;
+Landroidx/compose/runtime/CompositionScopedCoroutineScopeCanceller;
+Landroidx/compose/runtime/DerivedSnapshotState$ResultRecord;
+Landroidx/compose/runtime/DerivedSnapshotState;
+Landroidx/compose/runtime/DerivedStateObserver;
+Landroidx/compose/runtime/DisposableEffectImpl;
+Landroidx/compose/runtime/DisposableEffectResult;
+Landroidx/compose/runtime/DisposableEffectScope;
+Landroidx/compose/runtime/DynamicProvidableCompositionLocal;
+Landroidx/compose/runtime/GroupInfo;
+Landroidx/compose/runtime/IntStack;
+Landroidx/compose/runtime/Invalidation;
+Landroidx/compose/runtime/JoinedKey;
+Landroidx/compose/runtime/KeyInfo;
+Landroidx/compose/runtime/Latch$await$2$2;
+Landroidx/compose/runtime/Latch;
+Landroidx/compose/runtime/LaunchedEffectImpl;
+Landroidx/compose/runtime/LazyValueHolder;
+Landroidx/compose/runtime/MonotonicFrameClock;
+Landroidx/compose/runtime/MovableContentStateReference;
+Landroidx/compose/runtime/MutableFloatState;
+Landroidx/compose/runtime/MutableIntState;
+Landroidx/compose/runtime/MutableState;
+Landroidx/compose/runtime/OpaqueKey;
+Landroidx/compose/runtime/ParcelableSnapshotMutableFloatState;
+Landroidx/compose/runtime/ParcelableSnapshotMutableIntState;
+Landroidx/compose/runtime/ParcelableSnapshotMutableState$Companion$CREATOR$1;
+Landroidx/compose/runtime/ParcelableSnapshotMutableState;
+Landroidx/compose/runtime/PausableMonotonicFrameClock$withFrameNanos$1;
+Landroidx/compose/runtime/PausableMonotonicFrameClock;
+Landroidx/compose/runtime/Pending$keyMap$2;
+Landroidx/compose/runtime/Pending;
+Landroidx/compose/runtime/PersistentCompositionLocalMap;
+Landroidx/compose/runtime/ProduceStateScopeImpl;
+Landroidx/compose/runtime/ProvidableCompositionLocal;
+Landroidx/compose/runtime/ProvidedValue;
+Landroidx/compose/runtime/RecomposeScope;
+Landroidx/compose/runtime/RecomposeScopeImpl$end$1$2;
+Landroidx/compose/runtime/RecomposeScopeImpl;
+Landroidx/compose/runtime/RecomposeScopeOwner;
+Landroidx/compose/runtime/Recomposer$State;
+Landroidx/compose/runtime/Recomposer$effectJob$1$1;
+Landroidx/compose/runtime/Recomposer$join$2;
+Landroidx/compose/runtime/Recomposer$performRecompose$1$1;
+Landroidx/compose/runtime/Recomposer$recompositionRunner$2$3;
+Landroidx/compose/runtime/Recomposer$recompositionRunner$2$unregisterApplyObserver$1;
+Landroidx/compose/runtime/Recomposer$recompositionRunner$2;
+Landroidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2$1;
+Landroidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2;
+Landroidx/compose/runtime/Recomposer;
+Landroidx/compose/runtime/ReferentialEqualityPolicy;
+Landroidx/compose/runtime/RememberObserver;
+Landroidx/compose/runtime/SkippableUpdater;
+Landroidx/compose/runtime/SlotReader;
+Landroidx/compose/runtime/SlotTable;
+Landroidx/compose/runtime/SlotWriter;
+Landroidx/compose/runtime/SnapshotMutableFloatStateImpl$FloatStateStateRecord;
+Landroidx/compose/runtime/SnapshotMutableFloatStateImpl;
+Landroidx/compose/runtime/SnapshotMutableIntStateImpl$IntStateStateRecord;
+Landroidx/compose/runtime/SnapshotMutableIntStateImpl;
+Landroidx/compose/runtime/SnapshotMutableStateImpl$StateStateRecord;
+Landroidx/compose/runtime/SnapshotMutableStateImpl;
+Landroidx/compose/runtime/SnapshotMutationPolicy;
+Landroidx/compose/runtime/SnapshotStateKt__DerivedStateKt;
+Landroidx/compose/runtime/SnapshotStateKt__ProduceStateKt$produceState$3;
+Landroidx/compose/runtime/SnapshotStateKt__SnapshotFlowKt$collectAsState$1$1;
+Landroidx/compose/runtime/SnapshotStateKt__SnapshotFlowKt$collectAsState$1;
+Landroidx/compose/runtime/Stack;
+Landroidx/compose/runtime/State;
+Landroidx/compose/runtime/StaticProvidableCompositionLocal;
+Landroidx/compose/runtime/StaticValueHolder;
+Landroidx/compose/runtime/StructuralEqualityPolicy;
+Landroidx/compose/runtime/WeakReference;
+Landroidx/compose/runtime/changelist/ChangeList;
+Landroidx/compose/runtime/changelist/ComposerChangeListWriter;
+Landroidx/compose/runtime/changelist/FixupList;
+Landroidx/compose/runtime/changelist/Operation$AdvanceSlotsBy;
+Landroidx/compose/runtime/changelist/Operation$DeactivateCurrentGroup;
+Landroidx/compose/runtime/changelist/Operation$Downs;
+Landroidx/compose/runtime/changelist/Operation$EndCompositionScope;
+Landroidx/compose/runtime/changelist/Operation$EndCurrentGroup;
+Landroidx/compose/runtime/changelist/Operation$EnsureGroupStarted;
+Landroidx/compose/runtime/changelist/Operation$EnsureRootGroupStarted;
+Landroidx/compose/runtime/changelist/Operation$InsertNodeFixup;
+Landroidx/compose/runtime/changelist/Operation$InsertSlots;
+Landroidx/compose/runtime/changelist/Operation$InsertSlotsWithFixups;
+Landroidx/compose/runtime/changelist/Operation$MoveCurrentGroup;
+Landroidx/compose/runtime/changelist/Operation$PostInsertNodeFixup;
+Landroidx/compose/runtime/changelist/Operation$Remember;
+Landroidx/compose/runtime/changelist/Operation$SideEffect;
+Landroidx/compose/runtime/changelist/Operation$UpdateAuxData;
+Landroidx/compose/runtime/changelist/Operation$UpdateNode;
+Landroidx/compose/runtime/changelist/Operation$UpdateValue;
+Landroidx/compose/runtime/changelist/Operation$Ups;
+Landroidx/compose/runtime/changelist/Operation$UseCurrentNode;
+Landroidx/compose/runtime/changelist/Operation;
+Landroidx/compose/runtime/changelist/Operations$OpIterator;
+Landroidx/compose/runtime/changelist/Operations;
+Landroidx/compose/runtime/collection/IdentityArrayIntMap;
+Landroidx/compose/runtime/collection/IdentityArrayMap$asMap$1$entries$1$iterator$1$1;
+Landroidx/compose/runtime/collection/IdentityArraySet;
+Landroidx/compose/runtime/collection/MutableVector$MutableVectorList;
+Landroidx/compose/runtime/collection/MutableVector$VectorListIterator;
+Landroidx/compose/runtime/collection/MutableVector;
+Landroidx/compose/runtime/external/kotlinx/collections/immutable/ImmutableList;
+Landroidx/compose/runtime/external/kotlinx/collections/immutable/ImmutableSet;
+Landroidx/compose/runtime/external/kotlinx/collections/immutable/PersistentList;
+Landroidx/compose/runtime/external/kotlinx/collections/immutable/PersistentMap$Builder;
+Landroidx/compose/runtime/external/kotlinx/collections/immutable/PersistentMap;
+Landroidx/compose/runtime/external/kotlinx/collections/immutable/PersistentSet;
+Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableList/AbstractPersistentList;
+Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableList/SmallPersistentVector;
+Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMap;
+Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMapBaseIterator;
+Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMapBuilder;
+Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMapKeys;
+Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMapKeysIterator;
+Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;
+Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNodeBaseIterator;
+Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNodeKeysIterator;
+Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/persistentOrderedSet/Links;
+Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/persistentOrderedSet/PersistentOrderedSet;
+Landroidx/compose/runtime/external/kotlinx/collections/immutable/internal/DeltaCounter;
+Landroidx/compose/runtime/internal/ComposableLambda;
+Landroidx/compose/runtime/internal/ComposableLambdaImpl;
+Landroidx/compose/runtime/internal/PersistentCompositionLocalHashMap$Builder;
+Landroidx/compose/runtime/internal/PersistentCompositionLocalHashMap;
+Landroidx/compose/runtime/internal/ThreadMap;
+Landroidx/compose/runtime/saveable/ListSaverKt$listSaver$1;
+Landroidx/compose/runtime/saveable/RememberSaveableKt$rememberSaveable$1;
+Landroidx/compose/runtime/saveable/SaveableHolder;
+Landroidx/compose/runtime/saveable/SaveableStateHolder;
+Landroidx/compose/runtime/saveable/SaveableStateHolderImpl$RegistryHolder$registry$1;
+Landroidx/compose/runtime/saveable/SaveableStateHolderImpl$RegistryHolder;
+Landroidx/compose/runtime/saveable/SaveableStateHolderImpl$SaveableStateProvider$1$1$invoke$$inlined$onDispose$1;
+Landroidx/compose/runtime/saveable/SaveableStateHolderImpl;
+Landroidx/compose/runtime/saveable/SaveableStateRegistry;
+Landroidx/compose/runtime/saveable/SaveableStateRegistryImpl$registerProvider$3;
+Landroidx/compose/runtime/saveable/SaveableStateRegistryImpl;
+Landroidx/compose/runtime/saveable/SaveableStateRegistryKt;
+Landroidx/compose/runtime/saveable/SaverKt$Saver$1;
+Landroidx/compose/runtime/saveable/SaverKt;
+Landroidx/compose/runtime/snapshots/GlobalSnapshot$1$1$1;
+Landroidx/compose/runtime/snapshots/GlobalSnapshot;
+Landroidx/compose/runtime/snapshots/MutableSnapshot;
+Landroidx/compose/runtime/snapshots/ObserverHandle;
+Landroidx/compose/runtime/snapshots/ReadonlySnapshot;
+Landroidx/compose/runtime/snapshots/Snapshot$Companion$$ExternalSyntheticLambda0;
+Landroidx/compose/runtime/snapshots/Snapshot;
+Landroidx/compose/runtime/snapshots/SnapshotApplyResult$Failure;
+Landroidx/compose/runtime/snapshots/SnapshotApplyResult$Success;
+Landroidx/compose/runtime/snapshots/SnapshotDoubleIndexHeap;
+Landroidx/compose/runtime/snapshots/SnapshotIdSet;
+Landroidx/compose/runtime/snapshots/SnapshotKt$mergedReadObserver$1;
+Landroidx/compose/runtime/snapshots/SnapshotKt;
+Landroidx/compose/runtime/snapshots/SnapshotMutableState;
+Landroidx/compose/runtime/snapshots/SnapshotStateList$StateListStateRecord;
+Landroidx/compose/runtime/snapshots/SnapshotStateList$addAll$1;
+Landroidx/compose/runtime/snapshots/SnapshotStateList;
+Landroidx/compose/runtime/snapshots/SnapshotStateListKt;
+Landroidx/compose/runtime/snapshots/SnapshotStateObserver$ObservedScopeMap;
+Landroidx/compose/runtime/snapshots/SnapshotStateObserver;
+Landroidx/compose/runtime/snapshots/StateObject;
+Landroidx/compose/runtime/snapshots/StateRecord;
+Landroidx/compose/runtime/snapshots/TransparentObserverMutableSnapshot;
+Landroidx/compose/runtime/tooling/InspectionTablesKt;
+Landroidx/compose/ui/Alignment$Horizontal;
+Landroidx/compose/ui/Alignment$Vertical;
+Landroidx/compose/ui/Alignment;
+Landroidx/compose/ui/BiasAlignment$Horizontal;
+Landroidx/compose/ui/BiasAlignment$Vertical;
+Landroidx/compose/ui/BiasAlignment;
+Landroidx/compose/ui/CombinedModifier$toString$1;
+Landroidx/compose/ui/CombinedModifier;
+Landroidx/compose/ui/ComposedModifier;
+Landroidx/compose/ui/CompositionLocalMapInjectionElement;
+Landroidx/compose/ui/Modifier$Companion;
+Landroidx/compose/ui/Modifier$Element;
+Landroidx/compose/ui/Modifier$Node;
+Landroidx/compose/ui/Modifier;
+Landroidx/compose/ui/MotionDurationScale;
+Landroidx/compose/ui/ZIndexElement;
+Landroidx/compose/ui/ZIndexNode$measure$1;
+Landroidx/compose/ui/ZIndexNode;
+Landroidx/compose/ui/autofill/AndroidAutofill;
+Landroidx/compose/ui/autofill/Autofill;
+Landroidx/compose/ui/autofill/AutofillCallback;
+Landroidx/compose/ui/autofill/AutofillTree;
+Landroidx/compose/ui/draw/BuildDrawCacheParams;
+Landroidx/compose/ui/draw/CacheDrawModifierNode;
+Landroidx/compose/ui/draw/CacheDrawModifierNodeImpl;
+Landroidx/compose/ui/draw/CacheDrawScope$onDrawBehind$1;
+Landroidx/compose/ui/draw/CacheDrawScope;
+Landroidx/compose/ui/draw/ClipKt;
+Landroidx/compose/ui/draw/DrawModifier;
+Landroidx/compose/ui/draw/DrawResult;
+Landroidx/compose/ui/draw/DrawWithCacheElement;
+Landroidx/compose/ui/draw/EmptyBuildDrawCacheParams;
+Landroidx/compose/ui/draw/PainterElement;
+Landroidx/compose/ui/draw/PainterNode$measure$1;
+Landroidx/compose/ui/draw/PainterNode;
+Landroidx/compose/ui/focus/FocusChangedElement;
+Landroidx/compose/ui/focus/FocusChangedNode;
+Landroidx/compose/ui/focus/FocusDirection;
+Landroidx/compose/ui/focus/FocusEventModifierNode;
+Landroidx/compose/ui/focus/FocusInvalidationManager;
+Landroidx/compose/ui/focus/FocusModifierKt;
+Landroidx/compose/ui/focus/FocusOwner;
+Landroidx/compose/ui/focus/FocusOwnerImpl$modifier$1;
+Landroidx/compose/ui/focus/FocusOwnerImpl$moveFocus$foundNextItem$1;
+Landroidx/compose/ui/focus/FocusOwnerImpl;
+Landroidx/compose/ui/focus/FocusProperties$exit$1;
+Landroidx/compose/ui/focus/FocusProperties;
+Landroidx/compose/ui/focus/FocusPropertiesImpl;
+Landroidx/compose/ui/focus/FocusPropertiesModifierNode;
+Landroidx/compose/ui/focus/FocusRequester;
+Landroidx/compose/ui/focus/FocusRequesterModifierNode;
+Landroidx/compose/ui/focus/FocusState;
+Landroidx/compose/ui/focus/FocusStateImpl;
+Landroidx/compose/ui/focus/FocusTargetNode$FocusTargetElement;
+Landroidx/compose/ui/focus/FocusTargetNode;
+Landroidx/compose/ui/geometry/CornerRadius;
+Landroidx/compose/ui/geometry/MutableRect;
+Landroidx/compose/ui/geometry/Offset;
+Landroidx/compose/ui/geometry/Rect;
+Landroidx/compose/ui/geometry/RoundRect;
+Landroidx/compose/ui/geometry/Size;
+Landroidx/compose/ui/graphics/AndroidCanvas;
+Landroidx/compose/ui/graphics/AndroidCanvas_androidKt;
+Landroidx/compose/ui/graphics/AndroidImageBitmap;
+Landroidx/compose/ui/graphics/AndroidPaint;
+Landroidx/compose/ui/graphics/AndroidPaint_androidKt$WhenMappings;
+Landroidx/compose/ui/graphics/AndroidPath;
+Landroidx/compose/ui/graphics/BlendModeColorFilter;
+Landroidx/compose/ui/graphics/BlendModeColorFilterHelper;
+Landroidx/compose/ui/graphics/BlockGraphicsLayerElement;
+Landroidx/compose/ui/graphics/BlockGraphicsLayerModifier;
+Landroidx/compose/ui/graphics/Brush;
+Landroidx/compose/ui/graphics/BrushKt$ShaderBrush$1;
+Landroidx/compose/ui/graphics/BrushKt;
+Landroidx/compose/ui/graphics/Canvas;
+Landroidx/compose/ui/graphics/CanvasZHelper$$ExternalSyntheticApiModelOutline0;
+Landroidx/compose/ui/graphics/Color;
+Landroidx/compose/ui/graphics/ColorSpaceVerificationHelper$$ExternalSyntheticLambda1;
+Landroidx/compose/ui/graphics/Float16;
+Landroidx/compose/ui/graphics/GraphicsLayerElement;
+Landroidx/compose/ui/graphics/GraphicsLayerScopeKt;
+Landroidx/compose/ui/graphics/ImageBitmap;
+Landroidx/compose/ui/graphics/ImageBitmapConfig;
+Landroidx/compose/ui/graphics/Matrix;
+Landroidx/compose/ui/graphics/Outline$Generic;
+Landroidx/compose/ui/graphics/Outline$Rectangle;
+Landroidx/compose/ui/graphics/Outline$Rounded;
+Landroidx/compose/ui/graphics/Path;
+Landroidx/compose/ui/graphics/RectangleShapeKt$RectangleShape$1;
+Landroidx/compose/ui/graphics/ReusableGraphicsLayerScope;
+Landroidx/compose/ui/graphics/Shadow;
+Landroidx/compose/ui/graphics/Shape;
+Landroidx/compose/ui/graphics/SimpleGraphicsLayerModifier$layerBlock$1;
+Landroidx/compose/ui/graphics/SimpleGraphicsLayerModifier;
+Landroidx/compose/ui/graphics/SolidColor;
+Landroidx/compose/ui/graphics/TransformOrigin;
+Landroidx/compose/ui/graphics/colorspace/Adaptation$Companion$Bradford$1;
+Landroidx/compose/ui/graphics/colorspace/Adaptation;
+Landroidx/compose/ui/graphics/colorspace/ColorModel;
+Landroidx/compose/ui/graphics/colorspace/ColorSpace;
+Landroidx/compose/ui/graphics/colorspace/ColorSpaces;
+Landroidx/compose/ui/graphics/colorspace/Connector$Companion$identity$1;
+Landroidx/compose/ui/graphics/colorspace/Connector;
+Landroidx/compose/ui/graphics/colorspace/DoubleFunction;
+Landroidx/compose/ui/graphics/colorspace/Lab;
+Landroidx/compose/ui/graphics/colorspace/Oklab;
+Landroidx/compose/ui/graphics/colorspace/Rgb$$ExternalSyntheticLambda0;
+Landroidx/compose/ui/graphics/colorspace/Rgb$$ExternalSyntheticLambda1;
+Landroidx/compose/ui/graphics/colorspace/Rgb$$ExternalSyntheticLambda2;
+Landroidx/compose/ui/graphics/colorspace/Rgb$eotf$1;
+Landroidx/compose/ui/graphics/colorspace/Rgb;
+Landroidx/compose/ui/graphics/colorspace/TransferParameters;
+Landroidx/compose/ui/graphics/colorspace/WhitePoint;
+Landroidx/compose/ui/graphics/colorspace/Xyz;
+Landroidx/compose/ui/graphics/drawscope/CanvasDrawScope$DrawParams;
+Landroidx/compose/ui/graphics/drawscope/CanvasDrawScope$drawContext$1;
+Landroidx/compose/ui/graphics/drawscope/CanvasDrawScope;
+Landroidx/compose/ui/graphics/drawscope/CanvasDrawScopeKt$asDrawTransform$1;
+Landroidx/compose/ui/graphics/drawscope/ContentDrawScope;
+Landroidx/compose/ui/graphics/drawscope/DrawScope;
+Landroidx/compose/ui/graphics/drawscope/EmptyCanvas;
+Landroidx/compose/ui/graphics/drawscope/Fill;
+Landroidx/compose/ui/graphics/drawscope/Stroke;
+Landroidx/compose/ui/graphics/painter/BitmapPainter;
+Landroidx/compose/ui/graphics/painter/Painter;
+Landroidx/compose/ui/graphics/vector/GroupComponent;
+Landroidx/compose/ui/graphics/vector/ImageVector$Builder$GroupParams;
+Landroidx/compose/ui/graphics/vector/ImageVector$Builder;
+Landroidx/compose/ui/graphics/vector/ImageVector;
+Landroidx/compose/ui/graphics/vector/VNode;
+Landroidx/compose/ui/graphics/vector/VectorComponent;
+Landroidx/compose/ui/graphics/vector/VectorGroup;
+Landroidx/compose/ui/graphics/vector/VectorKt;
+Landroidx/compose/ui/graphics/vector/VectorNode;
+Landroidx/compose/ui/graphics/vector/VectorPainter;
+Landroidx/compose/ui/graphics/vector/VectorPath;
+Landroidx/compose/ui/graphics/vector/compat/AndroidVectorParser;
+Landroidx/compose/ui/hapticfeedback/HapticFeedback;
+Landroidx/compose/ui/input/InputMode;
+Landroidx/compose/ui/input/InputModeManager;
+Landroidx/compose/ui/input/InputModeManagerImpl;
+Landroidx/compose/ui/input/key/Key;
+Landroidx/compose/ui/input/key/KeyEvent;
+Landroidx/compose/ui/input/key/KeyInputElement;
+Landroidx/compose/ui/input/key/KeyInputModifierNode;
+Landroidx/compose/ui/input/key/KeyInputNode;
+Landroidx/compose/ui/input/key/Key_androidKt;
+Landroidx/compose/ui/input/nestedscroll/NestedScrollConnection;
+Landroidx/compose/ui/input/nestedscroll/NestedScrollDispatcher;
+Landroidx/compose/ui/input/nestedscroll/NestedScrollNode;
+Landroidx/compose/ui/input/nestedscroll/NestedScrollNodeKt;
+Landroidx/compose/ui/input/pointer/AndroidPointerIconType;
+Landroidx/compose/ui/input/pointer/MotionEventAdapter;
+Landroidx/compose/ui/input/pointer/Node;
+Landroidx/compose/ui/input/pointer/NodeParent;
+Landroidx/compose/ui/input/pointer/PointerEvent;
+Landroidx/compose/ui/input/pointer/PointerIcon;
+Landroidx/compose/ui/input/pointer/PointerIconService;
+Landroidx/compose/ui/input/pointer/PointerInputChange;
+Landroidx/compose/ui/input/pointer/PointerInputScope;
+Landroidx/compose/ui/input/pointer/PointerKeyboardModifiers;
+Landroidx/compose/ui/input/pointer/PositionCalculator;
+Landroidx/compose/ui/input/pointer/SuspendPointerInputElement;
+Landroidx/compose/ui/input/pointer/SuspendingPointerInputFilterKt;
+Landroidx/compose/ui/input/pointer/SuspendingPointerInputModifierNode;
+Landroidx/compose/ui/input/pointer/SuspendingPointerInputModifierNodeImpl$PointerEventHandlerCoroutine;
+Landroidx/compose/ui/input/pointer/SuspendingPointerInputModifierNodeImpl;
+Landroidx/compose/ui/input/pointer/util/DataPointAtTime;
+Landroidx/compose/ui/input/pointer/util/PointerIdArray;
+Landroidx/compose/ui/input/pointer/util/VelocityTracker1D;
+Landroidx/compose/ui/input/pointer/util/VelocityTracker;
+Landroidx/compose/ui/input/rotary/RotaryInputElement;
+Landroidx/compose/ui/input/rotary/RotaryInputModifierKt;
+Landroidx/compose/ui/input/rotary/RotaryInputModifierNode;
+Landroidx/compose/ui/input/rotary/RotaryInputNode;
+Landroidx/compose/ui/layout/AlignmentLine;
+Landroidx/compose/ui/layout/AlignmentLineKt$FirstBaseline$1;
+Landroidx/compose/ui/layout/AlignmentLineKt$LastBaseline$1;
+Landroidx/compose/ui/layout/AlignmentLineKt;
+Landroidx/compose/ui/layout/BeyondBoundsLayout$BeyondBoundsScope;
+Landroidx/compose/ui/layout/BeyondBoundsLayout;
+Landroidx/compose/ui/layout/BeyondBoundsLayoutKt;
+Landroidx/compose/ui/layout/ComposableSingletons$SubcomposeLayoutKt;
+Landroidx/compose/ui/layout/ContentScale;
+Landroidx/compose/ui/layout/DefaultIntrinsicMeasurable;
+Landroidx/compose/ui/layout/FixedSizeIntrinsicsPlaceable;
+Landroidx/compose/ui/layout/HorizontalAlignmentLine;
+Landroidx/compose/ui/layout/IntrinsicMeasureScope;
+Landroidx/compose/ui/layout/IntrinsicMinMax;
+Landroidx/compose/ui/layout/IntrinsicWidthHeight;
+Landroidx/compose/ui/layout/IntrinsicsMeasureScope;
+Landroidx/compose/ui/layout/LayoutCoordinates;
+Landroidx/compose/ui/layout/LayoutElement;
+Landroidx/compose/ui/layout/LayoutKt$materializerOf$1;
+Landroidx/compose/ui/layout/LayoutKt;
+Landroidx/compose/ui/layout/LayoutModifierImpl;
+Landroidx/compose/ui/layout/LayoutNodeSubcompositionsState$NodeState;
+Landroidx/compose/ui/layout/LayoutNodeSubcompositionsState$PostLookaheadMeasureScopeImpl;
+Landroidx/compose/ui/layout/LayoutNodeSubcompositionsState$Scope;
+Landroidx/compose/ui/layout/LayoutNodeSubcompositionsState$createMeasurePolicy$1$measure-3p2s80s$$inlined$createMeasureResult$1;
+Landroidx/compose/ui/layout/LayoutNodeSubcompositionsState$createMeasurePolicy$1;
+Landroidx/compose/ui/layout/LayoutNodeSubcompositionsState$precompose$1;
+Landroidx/compose/ui/layout/LayoutNodeSubcompositionsState;
+Landroidx/compose/ui/layout/LookaheadLayoutCoordinates;
+Landroidx/compose/ui/layout/Measurable;
+Landroidx/compose/ui/layout/MeasurePolicy;
+Landroidx/compose/ui/layout/MeasureResult;
+Landroidx/compose/ui/layout/MeasureScope$layout$1;
+Landroidx/compose/ui/layout/MeasureScope;
+Landroidx/compose/ui/layout/Measured;
+Landroidx/compose/ui/layout/OnRemeasuredModifier;
+Landroidx/compose/ui/layout/OnSizeChangedModifier;
+Landroidx/compose/ui/layout/PinnableContainerKt;
+Landroidx/compose/ui/layout/Placeable$PlacementScope$Companion;
+Landroidx/compose/ui/layout/Placeable$PlacementScope;
+Landroidx/compose/ui/layout/Placeable;
+Landroidx/compose/ui/layout/PlaceableKt;
+Landroidx/compose/ui/layout/Remeasurement;
+Landroidx/compose/ui/layout/RemeasurementModifier;
+Landroidx/compose/ui/layout/RootMeasurePolicy$measure$2;
+Landroidx/compose/ui/layout/RootMeasurePolicy;
+Landroidx/compose/ui/layout/ScaleFactor;
+Landroidx/compose/ui/layout/SubcomposeLayoutKt$SubcomposeLayout$2;
+Landroidx/compose/ui/layout/SubcomposeLayoutKt$SubcomposeLayout$4;
+Landroidx/compose/ui/layout/SubcomposeLayoutKt$SubcomposeLayout$5$1$invoke$$inlined$onDispose$1;
+Landroidx/compose/ui/layout/SubcomposeLayoutState$setRoot$1;
+Landroidx/compose/ui/layout/SubcomposeLayoutState;
+Landroidx/compose/ui/layout/SubcomposeMeasureScope;
+Landroidx/compose/ui/layout/SubcomposeSlotReusePolicy$SlotIdsSet;
+Landroidx/compose/ui/layout/SubcomposeSlotReusePolicy;
+Landroidx/compose/ui/modifier/BackwardsCompatLocalMap;
+Landroidx/compose/ui/modifier/EmptyMap;
+Landroidx/compose/ui/modifier/ModifierLocal;
+Landroidx/compose/ui/modifier/ModifierLocalConsumer;
+Landroidx/compose/ui/modifier/ModifierLocalManager;
+Landroidx/compose/ui/modifier/ModifierLocalModifierNode;
+Landroidx/compose/ui/modifier/ModifierLocalProvider;
+Landroidx/compose/ui/modifier/ModifierLocalReadScope;
+Landroidx/compose/ui/modifier/ProvidableModifierLocal;
+Landroidx/compose/ui/modifier/SingleLocalMap;
+Landroidx/compose/ui/node/AlignmentLines;
+Landroidx/compose/ui/node/AlignmentLinesOwner;
+Landroidx/compose/ui/node/BackwardsCompatNode;
+Landroidx/compose/ui/node/CanFocusChecker;
+Landroidx/compose/ui/node/ComposeUiNode$Companion;
+Landroidx/compose/ui/node/ComposeUiNode;
+Landroidx/compose/ui/node/CompositionLocalConsumerModifierNode;
+Landroidx/compose/ui/node/DelegatableNode;
+Landroidx/compose/ui/node/DelegatingNode;
+Landroidx/compose/ui/node/DrawModifierNode;
+Landroidx/compose/ui/node/GlobalPositionAwareModifierNode;
+Landroidx/compose/ui/node/HitTestResult;
+Landroidx/compose/ui/node/InnerNodeCoordinator;
+Landroidx/compose/ui/node/IntrinsicsPolicy;
+Landroidx/compose/ui/node/LayerPositionalProperties;
+Landroidx/compose/ui/node/LayoutAwareModifierNode;
+Landroidx/compose/ui/node/LayoutModifierNode;
+Landroidx/compose/ui/node/LayoutModifierNodeCoordinator;
+Landroidx/compose/ui/node/LayoutNode$$ExternalSyntheticLambda0;
+Landroidx/compose/ui/node/LayoutNode$Companion$DummyViewConfiguration$1;
+Landroidx/compose/ui/node/LayoutNode$Companion$ErrorMeasurePolicy$1;
+Landroidx/compose/ui/node/LayoutNode$NoIntrinsicsMeasurePolicy;
+Landroidx/compose/ui/node/LayoutNode$WhenMappings;
+Landroidx/compose/ui/node/LayoutNode$_foldedChildren$1;
+Landroidx/compose/ui/node/LayoutNode;
+Landroidx/compose/ui/node/LayoutNodeDrawScope;
+Landroidx/compose/ui/node/LayoutNodeLayoutDelegate$LookaheadPassDelegate;
+Landroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate$placeOuterCoordinator$1;
+Landroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate;
+Landroidx/compose/ui/node/LayoutNodeLayoutDelegate$performMeasure$2;
+Landroidx/compose/ui/node/LayoutNodeLayoutDelegate;
+Landroidx/compose/ui/node/LookaheadAlignmentLines;
+Landroidx/compose/ui/node/LookaheadCapablePlaceable;
+Landroidx/compose/ui/node/LookaheadDelegate;
+Landroidx/compose/ui/node/MeasureAndLayoutDelegate$PostponedRequest;
+Landroidx/compose/ui/node/MeasureAndLayoutDelegate;
+Landroidx/compose/ui/node/ModifierNodeElement;
+Landroidx/compose/ui/node/NodeChain$Differ;
+Landroidx/compose/ui/node/NodeChain;
+Landroidx/compose/ui/node/NodeChainKt$SentinelHead$1;
+Landroidx/compose/ui/node/NodeChainKt;
+Landroidx/compose/ui/node/NodeCoordinator$HitTestSource;
+Landroidx/compose/ui/node/NodeCoordinator$invoke$1;
+Landroidx/compose/ui/node/NodeCoordinator;
+Landroidx/compose/ui/node/NodeMeasuringIntrinsics$IntrinsicMinMax;
+Landroidx/compose/ui/node/NodeMeasuringIntrinsics$IntrinsicWidthHeight;
+Landroidx/compose/ui/node/ObserverModifierNode;
+Landroidx/compose/ui/node/ObserverNodeOwnerScope;
+Landroidx/compose/ui/node/OnPositionedDispatcher$Companion$DepthComparator;
+Landroidx/compose/ui/node/OnPositionedDispatcher;
+Landroidx/compose/ui/node/OwnedLayer;
+Landroidx/compose/ui/node/Owner$OnLayoutCompletedListener;
+Landroidx/compose/ui/node/Owner;
+Landroidx/compose/ui/node/OwnerScope;
+Landroidx/compose/ui/node/OwnerSnapshotObserver;
+Landroidx/compose/ui/node/ParentDataModifierNode;
+Landroidx/compose/ui/node/PointerInputModifierNode;
+Landroidx/compose/ui/node/RootForTest;
+Landroidx/compose/ui/node/SemanticsModifierNode;
+Landroidx/compose/ui/node/TailModifierNode;
+Landroidx/compose/ui/node/TreeSet;
+Landroidx/compose/ui/node/UiApplier;
+Landroidx/compose/ui/platform/AbstractComposeView;
+Landroidx/compose/ui/platform/AccessibilityManager;
+Landroidx/compose/ui/platform/AndroidAccessibilityManager;
+Landroidx/compose/ui/platform/AndroidClipboardManager;
+Landroidx/compose/ui/platform/AndroidComposeView$$ExternalSyntheticApiModelOutline0;
+Landroidx/compose/ui/platform/AndroidComposeView$$ExternalSyntheticLambda1;
+Landroidx/compose/ui/platform/AndroidComposeView$$ExternalSyntheticLambda2;
+Landroidx/compose/ui/platform/AndroidComposeView$$ExternalSyntheticLambda3;
+Landroidx/compose/ui/platform/AndroidComposeView$AndroidComposeViewTranslationCallback;
+Landroidx/compose/ui/platform/AndroidComposeView$ViewTreeOwners;
+Landroidx/compose/ui/platform/AndroidComposeView$focusOwner$1;
+Landroidx/compose/ui/platform/AndroidComposeView$pointerIconService$1;
+Landroidx/compose/ui/platform/AndroidComposeView$viewTreeOwners$2;
+Landroidx/compose/ui/platform/AndroidComposeView;
+Landroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat$$ExternalSyntheticLambda1;
+Landroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat$$ExternalSyntheticLambda2;
+Landroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat$1;
+Landroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat$MyNodeProvider;
+Landroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat$SemanticsNodeCopy;
+Landroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat$boundsUpdatesEventLoop$1;
+Landroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat$sendScrollEventIfNeeded$1;
+Landroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat;
+Landroidx/compose/ui/platform/AndroidComposeViewForceDarkModeQ;
+Landroidx/compose/ui/platform/AndroidComposeViewTranslationCallbackS;
+Landroidx/compose/ui/platform/AndroidComposeViewVerificationHelperMethodsN;
+Landroidx/compose/ui/platform/AndroidComposeViewVerificationHelperMethodsO;
+Landroidx/compose/ui/platform/AndroidCompositionLocals_androidKt$obtainImageVectorCache$callbacks$1$1;
+Landroidx/compose/ui/platform/AndroidCompositionLocals_androidKt;
+Landroidx/compose/ui/platform/AndroidUiDispatcher$dispatchCallback$1;
+Landroidx/compose/ui/platform/AndroidUiDispatcher;
+Landroidx/compose/ui/platform/AndroidUiFrameClock$withFrameNanos$2$callback$1;
+Landroidx/compose/ui/platform/AndroidUiFrameClock;
+Landroidx/compose/ui/platform/AndroidUriHandler;
+Landroidx/compose/ui/platform/AndroidViewConfiguration;
+Landroidx/compose/ui/platform/CalculateMatrixToWindow;
+Landroidx/compose/ui/platform/CalculateMatrixToWindowApi29;
+Landroidx/compose/ui/platform/ClipboardManager;
+Landroidx/compose/ui/platform/ComposableSingletons$Wrapper_androidKt;
+Landroidx/compose/ui/platform/ComposeView;
+Landroidx/compose/ui/platform/CompositionLocalsKt;
+Landroidx/compose/ui/platform/DeviceRenderNode;
+Landroidx/compose/ui/platform/DisposableSaveableStateRegistry;
+Landroidx/compose/ui/platform/DisposableSaveableStateRegistry_androidKt$DisposableSaveableStateRegistry$1;
+Landroidx/compose/ui/platform/DrawChildContainer;
+Landroidx/compose/ui/platform/GlobalSnapshotManager$ensureStarted$1;
+Landroidx/compose/ui/platform/GlobalSnapshotManager;
+Landroidx/compose/ui/platform/InspectableModifier$End;
+Landroidx/compose/ui/platform/InspectableModifier;
+Landroidx/compose/ui/platform/LayerMatrixCache;
+Landroidx/compose/ui/platform/MotionDurationScaleImpl;
+Landroidx/compose/ui/platform/OutlineResolver;
+Landroidx/compose/ui/platform/RenderNodeApi29;
+Landroidx/compose/ui/platform/RenderNodeApi29VerificationHelper;
+Landroidx/compose/ui/platform/RenderNodeLayer;
+Landroidx/compose/ui/platform/ScrollObservationScope;
+Landroidx/compose/ui/platform/SoftwareKeyboardController;
+Landroidx/compose/ui/platform/TextToolbar;
+Landroidx/compose/ui/platform/UriHandler;
+Landroidx/compose/ui/platform/ViewCompositionStrategy;
+Landroidx/compose/ui/platform/ViewConfiguration;
+Landroidx/compose/ui/platform/ViewLayer$Companion$OutlineProvider$1;
+Landroidx/compose/ui/platform/ViewLayer;
+Landroidx/compose/ui/platform/ViewLayerContainer;
+Landroidx/compose/ui/platform/WeakCache;
+Landroidx/compose/ui/platform/WindowInfo;
+Landroidx/compose/ui/platform/WindowInfoImpl;
+Landroidx/compose/ui/platform/WindowRecomposerFactory$Companion$$ExternalSyntheticLambda0;
+Landroidx/compose/ui/platform/WindowRecomposerFactory;
+Landroidx/compose/ui/platform/WindowRecomposerPolicy$createAndInstallWindowRecomposer$unsetJob$1;
+Landroidx/compose/ui/platform/WindowRecomposerPolicy;
+Landroidx/compose/ui/platform/WindowRecomposer_androidKt$createLifecycleAwareWindowRecomposer$1;
+Landroidx/compose/ui/platform/WindowRecomposer_androidKt$createLifecycleAwareWindowRecomposer$2$WhenMappings;
+Landroidx/compose/ui/platform/WindowRecomposer_androidKt$createLifecycleAwareWindowRecomposer$2$onStateChanged$1$1$1;
+Landroidx/compose/ui/platform/WindowRecomposer_androidKt$createLifecycleAwareWindowRecomposer$2$onStateChanged$1;
+Landroidx/compose/ui/platform/WindowRecomposer_androidKt$createLifecycleAwareWindowRecomposer$2;
+Landroidx/compose/ui/platform/WindowRecomposer_androidKt$getAnimationScaleFlowFor$1$1$1;
+Landroidx/compose/ui/platform/WindowRecomposer_androidKt;
+Landroidx/compose/ui/platform/WrappedComposition$setContent$1$1$1;
+Landroidx/compose/ui/platform/WrappedComposition$setContent$1$1;
+Landroidx/compose/ui/platform/WrappedComposition$setContent$1;
+Landroidx/compose/ui/platform/WrappedComposition;
+Landroidx/compose/ui/platform/WrapperRenderNodeLayerHelperMethods;
+Landroidx/compose/ui/platform/WrapperVerificationHelperMethods;
+Landroidx/compose/ui/platform/Wrapper_androidKt;
+Landroidx/compose/ui/res/ImageVectorCache$ImageVectorEntry;
+Landroidx/compose/ui/res/ImageVectorCache$Key;
+Landroidx/compose/ui/res/ImageVectorCache;
+Landroidx/compose/ui/semantics/AppendedSemanticsElement;
+Landroidx/compose/ui/semantics/CollectionInfo;
+Landroidx/compose/ui/semantics/CoreSemanticsModifierNode;
+Landroidx/compose/ui/semantics/EmptySemanticsElement;
+Landroidx/compose/ui/semantics/EmptySemanticsModifier;
+Landroidx/compose/ui/semantics/Role;
+Landroidx/compose/ui/semantics/ScrollAxisRange;
+Landroidx/compose/ui/semantics/SemanticsConfiguration;
+Landroidx/compose/ui/semantics/SemanticsModifier;
+Landroidx/compose/ui/semantics/SemanticsModifierKt;
+Landroidx/compose/ui/semantics/SemanticsNode;
+Landroidx/compose/ui/semantics/SemanticsOwner;
+Landroidx/compose/ui/semantics/SemanticsProperties;
+Landroidx/compose/ui/semantics/SemanticsPropertiesKt;
+Landroidx/compose/ui/semantics/SemanticsPropertyKey;
+Landroidx/compose/ui/semantics/SemanticsPropertyReceiver;
+Landroidx/compose/ui/text/AndroidParagraph;
+Landroidx/compose/ui/text/AnnotatedString$Range;
+Landroidx/compose/ui/text/AnnotatedString;
+Landroidx/compose/ui/text/AnnotatedStringKt;
+Landroidx/compose/ui/text/EmojiSupportMatch;
+Landroidx/compose/ui/text/MultiParagraph;
+Landroidx/compose/ui/text/MultiParagraphIntrinsics$maxIntrinsicWidth$2;
+Landroidx/compose/ui/text/MultiParagraphIntrinsics;
+Landroidx/compose/ui/text/ParagraphInfo;
+Landroidx/compose/ui/text/ParagraphIntrinsicInfo;
+Landroidx/compose/ui/text/ParagraphIntrinsics;
+Landroidx/compose/ui/text/ParagraphStyle;
+Landroidx/compose/ui/text/ParagraphStyleKt;
+Landroidx/compose/ui/text/PlatformParagraphStyle;
+Landroidx/compose/ui/text/PlatformTextStyle;
+Landroidx/compose/ui/text/SaversKt$ColorSaver$1;
+Landroidx/compose/ui/text/SaversKt$ColorSaver$2;
+Landroidx/compose/ui/text/SaversKt;
+Landroidx/compose/ui/text/SpanStyle;
+Landroidx/compose/ui/text/SpanStyleKt;
+Landroidx/compose/ui/text/TextLayoutInput;
+Landroidx/compose/ui/text/TextLayoutResult;
+Landroidx/compose/ui/text/TextRange;
+Landroidx/compose/ui/text/TextStyle;
+Landroidx/compose/ui/text/TtsAnnotation;
+Landroidx/compose/ui/text/UrlAnnotation;
+Landroidx/compose/ui/text/VerbatimTtsAnnotation;
+Landroidx/compose/ui/text/android/BoringLayoutFactoryDefault;
+Landroidx/compose/ui/text/android/LayoutIntrinsics;
+Landroidx/compose/ui/text/android/Paint29$$ExternalSyntheticApiModelOutline0;
+Landroidx/compose/ui/text/android/Paint29;
+Landroidx/compose/ui/text/android/StaticLayoutFactory23;
+Landroidx/compose/ui/text/android/StaticLayoutFactory26;
+Landroidx/compose/ui/text/android/StaticLayoutFactory28;
+Landroidx/compose/ui/text/android/StaticLayoutFactoryImpl;
+Landroidx/compose/ui/text/android/StaticLayoutParams;
+Landroidx/compose/ui/text/android/TextAlignmentAdapter;
+Landroidx/compose/ui/text/android/TextAndroidCanvas;
+Landroidx/compose/ui/text/android/TextLayout;
+Landroidx/compose/ui/text/android/TextLayoutKt;
+Landroidx/compose/ui/text/android/style/LetterSpacingSpanEm;
+Landroidx/compose/ui/text/android/style/LetterSpacingSpanPx;
+Landroidx/compose/ui/text/android/style/LineHeightSpan;
+Landroidx/compose/ui/text/android/style/LineHeightStyleSpan;
+Landroidx/compose/ui/text/android/style/PlaceholderSpan;
+Landroidx/compose/ui/text/android/style/ShadowSpan;
+Landroidx/compose/ui/text/android/style/SkewXSpan;
+Landroidx/compose/ui/text/android/style/TextDecorationSpan;
+Landroidx/compose/ui/text/android/style/TypefaceSpan;
+Landroidx/compose/ui/text/caches/LruCache;
+Landroidx/compose/ui/text/caches/SimpleArrayMap;
+Landroidx/compose/ui/text/font/AndroidFontResolveInterceptor;
+Landroidx/compose/ui/text/font/AsyncTypefaceCache;
+Landroidx/compose/ui/text/font/DefaultFontFamily;
+Landroidx/compose/ui/text/font/Font$ResourceLoader;
+Landroidx/compose/ui/text/font/FontFamily$Resolver;
+Landroidx/compose/ui/text/font/FontFamily;
+Landroidx/compose/ui/text/font/FontFamilyResolverImpl;
+Landroidx/compose/ui/text/font/FontFamilyResolverKt;
+Landroidx/compose/ui/text/font/FontListFontFamilyTypefaceAdapter$special$$inlined$CoroutineExceptionHandler$1;
+Landroidx/compose/ui/text/font/FontListFontFamilyTypefaceAdapter;
+Landroidx/compose/ui/text/font/FontStyle;
+Landroidx/compose/ui/text/font/FontSynthesis;
+Landroidx/compose/ui/text/font/FontWeight;
+Landroidx/compose/ui/text/font/GenericFontFamily;
+Landroidx/compose/ui/text/font/PlatformResolveInterceptor$Companion$Default$1;
+Landroidx/compose/ui/text/font/PlatformResolveInterceptor$Companion;
+Landroidx/compose/ui/text/font/PlatformResolveInterceptor;
+Landroidx/compose/ui/text/font/PlatformTypefaces;
+Landroidx/compose/ui/text/font/SystemFontFamily;
+Landroidx/compose/ui/text/font/TypefaceRequest;
+Landroidx/compose/ui/text/font/TypefaceResult$Immutable;
+Landroidx/compose/ui/text/font/TypefaceResult;
+Landroidx/compose/ui/text/input/InputMethodManagerImpl;
+Landroidx/compose/ui/text/input/PlatformTextInputService;
+Landroidx/compose/ui/text/input/TextFieldValue;
+Landroidx/compose/ui/text/input/TextInputService;
+Landroidx/compose/ui/text/input/TextInputServiceAndroid;
+Landroidx/compose/ui/text/input/TextInputServiceAndroid_androidKt$$ExternalSyntheticLambda0;
+Landroidx/compose/ui/text/intl/AndroidLocale;
+Landroidx/compose/ui/text/intl/AndroidLocaleDelegateAPI24;
+Landroidx/compose/ui/text/intl/Locale;
+Landroidx/compose/ui/text/intl/LocaleList;
+Landroidx/compose/ui/text/intl/PlatformLocaleKt;
+Landroidx/compose/ui/text/platform/AndroidParagraphHelper_androidKt$NoopSpan$1;
+Landroidx/compose/ui/text/platform/AndroidParagraphHelper_androidKt;
+Landroidx/compose/ui/text/platform/AndroidParagraphIntrinsics$resolveTypeface$1;
+Landroidx/compose/ui/text/platform/AndroidParagraphIntrinsics;
+Landroidx/compose/ui/text/platform/AndroidTextPaint;
+Landroidx/compose/ui/text/platform/DefaultImpl$getFontLoadState$initCallback$1;
+Landroidx/compose/ui/text/platform/DefaultImpl;
+Landroidx/compose/ui/text/platform/EmojiCompatStatus;
+Landroidx/compose/ui/text/platform/ImmutableBool;
+Landroidx/compose/ui/text/platform/URLSpanCache;
+Landroidx/compose/ui/text/platform/extensions/LocaleListHelperMethods;
+Landroidx/compose/ui/text/platform/style/DrawStyleSpan;
+Landroidx/compose/ui/text/platform/style/ShaderBrushSpan;
+Landroidx/compose/ui/text/style/BaselineShift;
+Landroidx/compose/ui/text/style/BrushStyle;
+Landroidx/compose/ui/text/style/ColorStyle;
+Landroidx/compose/ui/text/style/Hyphens;
+Landroidx/compose/ui/text/style/LineBreak$Strategy;
+Landroidx/compose/ui/text/style/LineBreak$Strictness;
+Landroidx/compose/ui/text/style/LineBreak$WordBreak;
+Landroidx/compose/ui/text/style/LineBreak;
+Landroidx/compose/ui/text/style/LineHeightStyle$Alignment;
+Landroidx/compose/ui/text/style/LineHeightStyle;
+Landroidx/compose/ui/text/style/TextAlign;
+Landroidx/compose/ui/text/style/TextDecoration;
+Landroidx/compose/ui/text/style/TextDirection;
+Landroidx/compose/ui/text/style/TextForegroundStyle$Unspecified;
+Landroidx/compose/ui/text/style/TextForegroundStyle;
+Landroidx/compose/ui/text/style/TextGeometricTransform;
+Landroidx/compose/ui/text/style/TextIndent;
+Landroidx/compose/ui/text/style/TextMotion;
+Landroidx/compose/ui/unit/Constraints;
+Landroidx/compose/ui/unit/Density;
+Landroidx/compose/ui/unit/DensityImpl;
+Landroidx/compose/ui/unit/Dp$Companion;
+Landroidx/compose/ui/unit/Dp;
+Landroidx/compose/ui/unit/DpOffset;
+Landroidx/compose/ui/unit/DpRect;
+Landroidx/compose/ui/unit/IntOffset;
+Landroidx/compose/ui/unit/IntSize;
+Landroidx/compose/ui/unit/LayoutDirection;
+Landroidx/compose/ui/unit/TextUnit;
+Landroidx/compose/ui/unit/TextUnitType;
+Landroidx/core/app/ComponentActivity;
+Landroidx/core/app/CoreComponentFactory;
+Landroidx/core/content/res/ColorStateListInflaterCompat;
+Landroidx/core/content/res/ComplexColorCompat;
+Landroidx/core/graphics/TypefaceCompat;
+Landroidx/core/graphics/TypefaceCompatApi29Impl;
+Landroidx/core/graphics/TypefaceCompatUtil$Api19Impl;
+Landroidx/core/os/BuildCompat$Extensions30Impl$$ExternalSyntheticApiModelOutline0;
+Landroidx/core/os/BuildCompat$Extensions30Impl;
+Landroidx/core/os/BuildCompat;
+Landroidx/core/os/TraceCompat$Api18Impl;
+Landroidx/core/os/TraceCompat;
+Landroidx/core/provider/CallbackWithHandler$2;
+Landroidx/core/provider/FontProvider$Api16Impl;
+Landroidx/core/provider/FontRequest;
+Landroidx/core/provider/FontsContractCompat$FontInfo;
+Landroidx/core/text/TextUtilsCompat$Api17Impl;
+Landroidx/core/text/TextUtilsCompat;
+Landroidx/core/view/AccessibilityDelegateCompat$AccessibilityDelegateAdapter;
+Landroidx/core/view/AccessibilityDelegateCompat;
+Landroidx/core/view/MenuHostHelper;
+Landroidx/core/view/ViewCompat$$ExternalSyntheticLambda0;
+Landroidx/core/view/ViewCompat$Api29Impl;
+Landroidx/core/view/ViewCompat$Api30Impl;
+Landroidx/core/view/ViewCompat;
+Landroidx/core/view/accessibility/AccessibilityNodeProviderCompat;
+Landroidx/customview/poolingcontainer/PoolingContainerListenerHolder;
+Landroidx/emoji2/text/ConcurrencyHelpers$$ExternalSyntheticLambda0;
+Landroidx/emoji2/text/ConcurrencyHelpers$Handler28Impl;
+Landroidx/emoji2/text/DefaultGlyphChecker;
+Landroidx/emoji2/text/EmojiCompat$CompatInternal19$1;
+Landroidx/emoji2/text/EmojiCompat$CompatInternal19;
+Landroidx/emoji2/text/EmojiCompat$Config;
+Landroidx/emoji2/text/EmojiCompat$GlyphChecker;
+Landroidx/emoji2/text/EmojiCompat$MetadataRepoLoader;
+Landroidx/emoji2/text/EmojiCompat;
+Landroidx/emoji2/text/EmojiCompatInitializer$1;
+Landroidx/emoji2/text/EmojiCompatInitializer$BackgroundDefaultLoader$$ExternalSyntheticLambda0;
+Landroidx/emoji2/text/EmojiCompatInitializer$BackgroundDefaultLoader$1;
+Landroidx/emoji2/text/EmojiCompatInitializer$LoadEmojiCompatRunnable;
+Landroidx/emoji2/text/EmojiCompatInitializer;
+Landroidx/emoji2/text/EmojiProcessor$EmojiProcessAddSpanCallback;
+Landroidx/emoji2/text/EmojiProcessor$EmojiProcessCallback;
+Landroidx/emoji2/text/EmojiProcessor$ProcessorSm;
+Landroidx/emoji2/text/EmojiProcessor;
+Landroidx/emoji2/text/FontRequestEmojiCompatConfig$FontRequestMetadataLoader$$ExternalSyntheticLambda0;
+Landroidx/emoji2/text/FontRequestEmojiCompatConfig$FontRequestMetadataLoader$1;
+Landroidx/emoji2/text/FontRequestEmojiCompatConfig$FontRequestMetadataLoader;
+Landroidx/emoji2/text/FontRequestEmojiCompatConfig;
+Landroidx/emoji2/text/MetadataRepo$Node;
+Landroidx/emoji2/text/MetadataRepo;
+Landroidx/emoji2/text/TypefaceEmojiRasterizer;
+Landroidx/emoji2/text/TypefaceEmojiSpan;
+Landroidx/emoji2/text/UnprecomputeTextOnModificationSpannable;
+Landroidx/emoji2/text/flatbuffer/MetadataItem;
+Landroidx/emoji2/text/flatbuffer/MetadataList;
+Landroidx/emoji2/text/flatbuffer/Table;
+Landroidx/lifecycle/DefaultLifecycleObserver;
+Landroidx/lifecycle/DefaultLifecycleObserverAdapter$WhenMappings;
+Landroidx/lifecycle/DefaultLifecycleObserverAdapter;
+Landroidx/lifecycle/EmptyActivityLifecycleCallbacks;
+Landroidx/lifecycle/HasDefaultViewModelProviderFactory;
+Landroidx/lifecycle/Lifecycle$Event$Companion;
+Landroidx/lifecycle/Lifecycle$Event$WhenMappings;
+Landroidx/lifecycle/Lifecycle$Event;
+Landroidx/lifecycle/Lifecycle$State;
+Landroidx/lifecycle/Lifecycle;
+Landroidx/lifecycle/LifecycleDestroyedException;
+Landroidx/lifecycle/LifecycleDispatcher$DispatcherActivityCallback;
+Landroidx/lifecycle/LifecycleDispatcher;
+Landroidx/lifecycle/LifecycleEventObserver;
+Landroidx/lifecycle/LifecycleObserver;
+Landroidx/lifecycle/LifecycleOwner;
+Landroidx/lifecycle/LifecycleRegistry$ObserverWithState;
+Landroidx/lifecycle/LifecycleRegistry;
+Landroidx/lifecycle/Lifecycling;
+Landroidx/lifecycle/ProcessLifecycleInitializer;
+Landroidx/lifecycle/ProcessLifecycleOwner$Api29Impl;
+Landroidx/lifecycle/ProcessLifecycleOwner$attach$1$onActivityPreCreated$1;
+Landroidx/lifecycle/ProcessLifecycleOwner$attach$1;
+Landroidx/lifecycle/ProcessLifecycleOwner$initializationListener$1;
+Landroidx/lifecycle/ProcessLifecycleOwner;
+Landroidx/lifecycle/ReportFragment$LifecycleCallbacks$Companion;
+Landroidx/lifecycle/ReportFragment$LifecycleCallbacks;
+Landroidx/lifecycle/ReportFragment;
+Landroidx/lifecycle/SavedStateHandleAttacher;
+Landroidx/lifecycle/SavedStateHandlesProvider;
+Landroidx/lifecycle/SavedStateHandlesVM;
+Landroidx/lifecycle/ViewModelStore;
+Landroidx/lifecycle/ViewModelStoreOwner;
+Landroidx/lifecycle/viewmodel/CreationExtras$Empty;
+Landroidx/lifecycle/viewmodel/CreationExtras;
+Landroidx/lifecycle/viewmodel/MutableCreationExtras;
+Landroidx/lifecycle/viewmodel/ViewModelInitializer;
+Landroidx/metrics/performance/DelegatingFrameMetricsListener;
+Landroidx/metrics/performance/FrameData;
+Landroidx/metrics/performance/FrameDataApi24;
+Landroidx/metrics/performance/FrameDataApi31;
+Landroidx/metrics/performance/JankStats;
+Landroidx/metrics/performance/JankStatsApi16Impl;
+Landroidx/metrics/performance/JankStatsApi22Impl;
+Landroidx/metrics/performance/JankStatsApi24Impl$$ExternalSyntheticLambda0;
+Landroidx/metrics/performance/JankStatsApi24Impl;
+Landroidx/metrics/performance/JankStatsApi26Impl;
+Landroidx/metrics/performance/JankStatsApi31Impl;
+Landroidx/metrics/performance/PerformanceMetricsState$Holder;
+Landroidx/metrics/performance/PerformanceMetricsState;
+Landroidx/profileinstaller/ProfileInstaller$DiagnosticsCallback;
+Landroidx/profileinstaller/ProfileInstallerInitializer$$ExternalSyntheticLambda0;
+Landroidx/profileinstaller/ProfileInstallerInitializer$$ExternalSyntheticLambda1;
+Landroidx/profileinstaller/ProfileInstallerInitializer$Choreographer16Impl;
+Landroidx/profileinstaller/ProfileInstallerInitializer$Handler28Impl;
+Landroidx/profileinstaller/ProfileInstallerInitializer;
+Landroidx/savedstate/Recreator;
+Landroidx/savedstate/SavedStateRegistry$$ExternalSyntheticLambda0;
+Landroidx/savedstate/SavedStateRegistry$SavedStateProvider;
+Landroidx/savedstate/SavedStateRegistry;
+Landroidx/savedstate/SavedStateRegistryController;
+Landroidx/savedstate/SavedStateRegistryOwner;
+Landroidx/startup/AppInitializer;
+Landroidx/startup/InitializationProvider;
+Landroidx/startup/Initializer;
+Landroidx/tracing/Trace$$ExternalSyntheticApiModelOutline0;
+Landroidx/tv/foundation/PivotOffsets;
+Landroidx/tv/foundation/TvBringIntoViewSpec;
+Landroidx/tv/foundation/lazy/grid/LazyGridIntervalContent;
+Landroidx/tv/foundation/lazy/grid/LazyGridItemPlacementAnimator$onMeasured$$inlined$sortBy$1;
+Landroidx/tv/foundation/lazy/grid/LazyGridItemPlacementAnimator;
+Landroidx/tv/foundation/lazy/grid/LazyGridItemProviderImpl;
+Landroidx/tv/foundation/lazy/grid/LazyGridKt$rememberLazyGridMeasurePolicy$1$1$3;
+Landroidx/tv/foundation/lazy/grid/LazyGridScrollPosition;
+Landroidx/tv/foundation/lazy/grid/TvGridItemSpan;
+Landroidx/tv/foundation/lazy/grid/TvLazyGridItemSpanScope;
+Landroidx/tv/foundation/lazy/grid/TvLazyGridScope;
+Landroidx/tv/foundation/lazy/grid/TvLazyGridState$remeasurementModifier$1;
+Landroidx/tv/foundation/lazy/grid/TvLazyGridState;
+Landroidx/tv/foundation/lazy/layout/AwaitFirstLayoutModifier$waitForFirstLayout$1;
+Landroidx/tv/foundation/lazy/layout/AwaitFirstLayoutModifier;
+Landroidx/tv/foundation/lazy/layout/LazyLayoutBeyondBoundsInfo$Interval;
+Landroidx/tv/foundation/lazy/layout/LazyLayoutKeyIndexMap;
+Landroidx/tv/foundation/lazy/layout/LazyLayoutNearestRangeState;
+Landroidx/tv/foundation/lazy/layout/LazyLayoutSemanticState;
+Landroidx/tv/foundation/lazy/layout/LazyLayoutSemanticsKt$LazyLayoutSemanticState$1;
+Landroidx/tv/foundation/lazy/layout/LazyLayoutSemanticsKt$lazyLayoutSemantics$1$1;
+Landroidx/tv/foundation/lazy/layout/NearestRangeKeyIndexMap$2$1;
+Landroidx/tv/foundation/lazy/layout/NearestRangeKeyIndexMap;
+Landroidx/tv/foundation/lazy/list/EmptyLazyListLayoutInfo;
+Landroidx/tv/foundation/lazy/list/LazyLayoutBeyondBoundsModifierLocal$Companion$emptyBeyondBoundsScope$1;
+Landroidx/tv/foundation/lazy/list/LazyLayoutBeyondBoundsModifierLocal;
+Landroidx/tv/foundation/lazy/list/LazyLayoutBeyondBoundsState;
+Landroidx/tv/foundation/lazy/list/LazyListBeyondBoundsState;
+Landroidx/tv/foundation/lazy/list/LazyListItemPlacementAnimator$onMeasured$$inlined$sortBy$2;
+Landroidx/tv/foundation/lazy/list/LazyListItemProviderImpl;
+Landroidx/tv/foundation/lazy/list/LazyListKt$rememberLazyListMeasurePolicy$1$1$measuredItemProvider$1;
+Landroidx/tv/foundation/lazy/list/LazyListKt$rememberLazyListMeasurePolicy$1$1;
+Landroidx/tv/foundation/lazy/list/LazyListMeasureResult;
+Landroidx/tv/foundation/lazy/list/LazyListMeasuredItem;
+Landroidx/tv/foundation/lazy/list/LazyListStateKt$rememberTvLazyListState$1$1;
+Landroidx/tv/foundation/lazy/list/LazyListStateKt;
+Landroidx/tv/foundation/lazy/list/TvLazyListInterval;
+Landroidx/tv/foundation/lazy/list/TvLazyListIntervalContent;
+Landroidx/tv/foundation/lazy/list/TvLazyListItemScopeImpl;
+Landroidx/tv/foundation/lazy/list/TvLazyListLayoutInfo;
+Landroidx/tv/foundation/lazy/list/TvLazyListScope;
+Landroidx/tv/foundation/lazy/list/TvLazyListState$scroll$1;
+Landroidx/tv/foundation/lazy/list/TvLazyListState;
+Landroidx/tv/material3/Border;
+Landroidx/tv/material3/BorderIndication;
+Landroidx/tv/material3/ColorScheme;
+Landroidx/tv/material3/ColorSchemeKt$LocalColorScheme$1;
+Landroidx/tv/material3/ColorSchemeKt;
+Landroidx/tv/material3/ContentColorKt;
+Landroidx/tv/material3/Glow;
+Landroidx/tv/material3/GlowIndication;
+Landroidx/tv/material3/GlowIndicationInstance;
+Landroidx/tv/material3/ScaleIndication;
+Landroidx/tv/material3/ScaleIndicationInstance;
+Landroidx/tv/material3/ScaleIndicationTokens;
+Landroidx/tv/material3/ShapesKt$LocalShapes$1;
+Landroidx/tv/material3/SurfaceKt$Surface$4;
+Landroidx/tv/material3/SurfaceKt$SurfaceImpl$2$2$1;
+Landroidx/tv/material3/SurfaceKt$SurfaceImpl$2$4$1;
+Landroidx/tv/material3/SurfaceKt$SurfaceImpl$2$5$1;
+Landroidx/tv/material3/SurfaceKt$SurfaceImpl$2;
+Landroidx/tv/material3/SurfaceKt$SurfaceImpl$3;
+Landroidx/tv/material3/SurfaceKt$handleDPadEnter$2$1;
+Landroidx/tv/material3/SurfaceKt$handleDPadEnter$2$2;
+Landroidx/tv/material3/SurfaceKt$handleDPadEnter$2;
+Landroidx/tv/material3/SurfaceKt$tvToggleable$1$1;
+Landroidx/tv/material3/SurfaceKt$tvToggleable$1$2;
+Landroidx/tv/material3/SurfaceKt$tvToggleable$1;
+Landroidx/tv/material3/SurfaceKt;
+Landroidx/tv/material3/TabColors;
+Landroidx/tv/material3/TabKt$Tab$1;
+Landroidx/tv/material3/TabKt$Tab$3$1;
+Landroidx/tv/material3/TabKt$Tab$4$1;
+Landroidx/tv/material3/TabKt$Tab$6;
+Landroidx/tv/material3/TabKt$Tab$7;
+Landroidx/tv/material3/TabKt;
+Landroidx/tv/material3/TabRowDefaults$PillIndicator$1;
+Landroidx/tv/material3/TabRowDefaults;
+Landroidx/tv/material3/TabRowKt$TabRow$1;
+Landroidx/tv/material3/TabRowKt$TabRow$2$1$1;
+Landroidx/tv/material3/TabRowKt$TabRow$2$2$1$1$2;
+Landroidx/tv/material3/TabRowKt$TabRow$2$2$1$1;
+Landroidx/tv/material3/TabRowKt$TabRow$2$2$1$separators$1;
+Landroidx/tv/material3/TabRowKt$TabRow$2$2$1;
+Landroidx/tv/material3/TabRowKt$TabRow$2;
+Landroidx/tv/material3/TabRowKt$TabRow$3;
+Landroidx/tv/material3/TabRowScopeImpl;
+Landroidx/tv/material3/TabRowSlots;
+Landroidx/tv/material3/TextKt$Text$1;
+Landroidx/tv/material3/TextKt$Text$2;
+Landroidx/tv/material3/TextKt;
+Landroidx/tv/material3/ToggleableSurfaceBorder;
+Landroidx/tv/material3/ToggleableSurfaceColors;
+Landroidx/tv/material3/ToggleableSurfaceGlow;
+Landroidx/tv/material3/ToggleableSurfaceScale;
+Landroidx/tv/material3/ToggleableSurfaceShape;
+Landroidx/tv/material3/tokens/ColorLightTokens;
+Landroidx/tv/material3/tokens/Elevation;
+Landroidx/tv/material3/tokens/PaletteTokens;
+Landroidx/tv/material3/tokens/ShapeTokens$BorderDefaultShape$1;
+Landroidx/tv/material3/tokens/ShapeTokens;
+Landroidx/tv/material3/tokens/TypographyTokensKt;
+Lcom/example/tvcomposebasedtests/ComposableSingletons$MainActivityKt;
+Lcom/example/tvcomposebasedtests/ComposableSingletons$UtilsKt$lambda-1$1;
+Lcom/example/tvcomposebasedtests/Config;
+Lcom/example/tvcomposebasedtests/JankStatsAggregator$listener$1;
+Lcom/example/tvcomposebasedtests/JankStatsAggregator;
+Lcom/example/tvcomposebasedtests/MainActivity$jankReportListener$1;
+Lcom/example/tvcomposebasedtests/MainActivity$startFrameMetrics$listener$1;
+Lcom/example/tvcomposebasedtests/MainActivity;
+Lcom/example/tvcomposebasedtests/UtilsKt$AddJankMetrics$1$2;
+Lcom/example/tvcomposebasedtests/UtilsKt$ScrollingRow$1$1$1;
+Lcom/example/tvcomposebasedtests/UtilsKt$ScrollingRow$2;
+Lcom/example/tvcomposebasedtests/UtilsKt;
+Lcom/example/tvcomposebasedtests/tvComponents/AppKt$App$1;
+Lcom/example/tvcomposebasedtests/tvComponents/ComposableSingletons$LazyContainersKt;
+Lcom/example/tvcomposebasedtests/tvComponents/ComposableSingletons$TopNavigationKt;
+Lcom/example/tvcomposebasedtests/tvComponents/Navigation;
+Lcom/example/tvcomposebasedtests/tvComponents/TopNavigationKt$PillIndicatorTabRow$1$1$1$1;
+Lcom/example/tvcomposebasedtests/tvComponents/TopNavigationKt$PillIndicatorTabRow$1;
+Lcom/example/tvcomposebasedtests/tvComponents/TopNavigationKt$TopNavigation$3$1;
+Lcom/google/gson/JsonIOException;
+Lcom/google/gson/internal/ConstructorConstructor;
+Lcom/google/gson/internal/LinkedTreeMap$1;
+Lcom/google/gson/internal/ObjectConstructor;
+Lkotlin/Function;
+Lkotlin/Lazy;
+Lkotlin/Pair;
+Lkotlin/Result$Failure;
+Lkotlin/Result;
+Lkotlin/ResultKt$$ExternalSyntheticCheckNotZero0;
+Lkotlin/ResultKt;
+Lkotlin/SynchronizedLazyImpl;
+Lkotlin/TuplesKt;
+Lkotlin/ULong$Companion;
+Lkotlin/UNINITIALIZED_VALUE;
+Lkotlin/Unit;
+Lkotlin/UnsafeLazyImpl;
+Lkotlin/collections/AbstractCollection;
+Lkotlin/collections/AbstractList;
+Lkotlin/collections/AbstractMap$toString$1;
+Lkotlin/collections/AbstractMap;
+Lkotlin/collections/AbstractMutableList;
+Lkotlin/collections/AbstractSet;
+Lkotlin/collections/ArrayDeque;
+Lkotlin/collections/ArraysKt___ArraysKt;
+Lkotlin/collections/CollectionsKt__MutableCollectionsJVMKt;
+Lkotlin/collections/CollectionsKt__ReversedViewsKt;
+Lkotlin/collections/CollectionsKt___CollectionsKt;
+Lkotlin/collections/EmptyList;
+Lkotlin/collections/EmptyMap;
+Lkotlin/coroutines/AbstractCoroutineContextElement;
+Lkotlin/coroutines/AbstractCoroutineContextKey;
+Lkotlin/coroutines/CombinedContext;
+Lkotlin/coroutines/Continuation;
+Lkotlin/coroutines/ContinuationInterceptor;
+Lkotlin/coroutines/CoroutineContext$Element;
+Lkotlin/coroutines/CoroutineContext$Key;
+Lkotlin/coroutines/CoroutineContext$plus$1;
+Lkotlin/coroutines/CoroutineContext;
+Lkotlin/coroutines/EmptyCoroutineContext;
+Lkotlin/coroutines/intrinsics/CoroutineSingletons;
+Lkotlin/coroutines/jvm/internal/BaseContinuationImpl;
+Lkotlin/coroutines/jvm/internal/CompletedContinuation;
+Lkotlin/coroutines/jvm/internal/ContinuationImpl;
+Lkotlin/coroutines/jvm/internal/CoroutineStackFrame;
+Lkotlin/coroutines/jvm/internal/SuspendLambda;
+Lkotlin/jvm/functions/Function0;
+Lkotlin/jvm/functions/Function12;
+Lkotlin/jvm/functions/Function1;
+Lkotlin/jvm/functions/Function22;
+Lkotlin/jvm/functions/Function2;
+Lkotlin/jvm/functions/Function3;
+Lkotlin/jvm/functions/Function4;
+Lkotlin/jvm/functions/Function5;
+Lkotlin/jvm/internal/ArrayIterator;
+Lkotlin/jvm/internal/CallableReference$NoReceiver;
+Lkotlin/jvm/internal/CallableReference;
+Lkotlin/jvm/internal/ClassBasedDeclarationContainer;
+Lkotlin/jvm/internal/ClassReference;
+Lkotlin/jvm/internal/FunctionBase;
+Lkotlin/jvm/internal/FunctionReferenceImpl;
+Lkotlin/jvm/internal/Lambda;
+Lkotlin/jvm/internal/PropertyReference0Impl;
+Lkotlin/jvm/internal/PropertyReference;
+Lkotlin/jvm/internal/Ref$BooleanRef;
+Lkotlin/jvm/internal/Ref$ObjectRef;
+Lkotlin/jvm/internal/Reflection;
+Lkotlin/jvm/internal/ReflectionFactory;
+Lkotlin/jvm/internal/markers/KMappedMarker;
+Lkotlin/jvm/internal/markers/KMutableCollection;
+Lkotlin/jvm/internal/markers/KMutableMap;
+Lkotlin/math/MathKt;
+Lkotlin/random/FallbackThreadLocalRandom$implStorage$1;
+Lkotlin/ranges/IntProgression;
+Lkotlin/ranges/IntProgressionIterator;
+Lkotlin/ranges/IntRange;
+Lkotlin/reflect/KCallable;
+Lkotlin/reflect/KClass;
+Lkotlin/reflect/KFunction;
+Lkotlin/reflect/KProperty0;
+Lkotlin/reflect/KProperty;
+Lkotlin/sequences/ConstrainedOnceSequence;
+Lkotlin/sequences/FilteringSequence$iterator$1;
+Lkotlin/sequences/FilteringSequence;
+Lkotlin/sequences/GeneratorSequence$iterator$1;
+Lkotlin/sequences/GeneratorSequence;
+Lkotlin/sequences/Sequence;
+Lkotlin/sequences/SequencesKt;
+Lkotlin/sequences/SequencesKt__SequencesKt$asSequence$$inlined$Sequence$1;
+Lkotlin/sequences/TransformingSequence$iterator$1;
+Lkotlin/text/StringsKt__IndentKt$getIndentFunction$2;
+Lkotlin/text/StringsKt__RegexExtensionsKt;
+Lkotlin/text/StringsKt__StringBuilderKt;
+Lkotlin/text/StringsKt__StringNumberConversionsKt;
+Lkotlin/text/StringsKt__StringsKt;
+Lkotlin/text/StringsKt___StringsKt;
+Lkotlinx/coroutines/AbstractCoroutine;
+Lkotlinx/coroutines/Active;
+Lkotlinx/coroutines/BlockingCoroutine;
+Lkotlinx/coroutines/BlockingEventLoop;
+Lkotlinx/coroutines/CancelHandler;
+Lkotlinx/coroutines/CancellableContinuation;
+Lkotlinx/coroutines/CancellableContinuationImpl;
+Lkotlinx/coroutines/CancelledContinuation;
+Lkotlinx/coroutines/ChildContinuation;
+Lkotlinx/coroutines/ChildHandle;
+Lkotlinx/coroutines/ChildHandleNode;
+Lkotlinx/coroutines/ChildJob;
+Lkotlinx/coroutines/CompletableDeferredImpl;
+Lkotlinx/coroutines/CompletedContinuation;
+Lkotlinx/coroutines/CompletedExceptionally;
+Lkotlinx/coroutines/CompletedWithCancellation;
+Lkotlinx/coroutines/CoroutineContextKt$foldCopies$1;
+Lkotlinx/coroutines/CoroutineDispatcher$Key$1;
+Lkotlinx/coroutines/CoroutineDispatcher$Key;
+Lkotlinx/coroutines/CoroutineDispatcher;
+Lkotlinx/coroutines/CoroutineExceptionHandler;
+Lkotlinx/coroutines/CoroutineScope;
+Lkotlinx/coroutines/DefaultExecutor;
+Lkotlinx/coroutines/DefaultExecutorKt;
+Lkotlinx/coroutines/Delay;
+Lkotlinx/coroutines/DispatchedTask;
+Lkotlinx/coroutines/Dispatchers;
+Lkotlinx/coroutines/DisposableHandle;
+Lkotlinx/coroutines/Empty;
+Lkotlinx/coroutines/EventLoopImplBase;
+Lkotlinx/coroutines/EventLoopImplPlatform;
+Lkotlinx/coroutines/ExecutorCoroutineDispatcher$Key$1;
+Lkotlinx/coroutines/ExecutorCoroutineDispatcher;
+Lkotlinx/coroutines/GlobalScope;
+Lkotlinx/coroutines/InactiveNodeList;
+Lkotlinx/coroutines/Incomplete;
+Lkotlinx/coroutines/IncompleteStateBox;
+Lkotlinx/coroutines/InvokeOnCancel;
+Lkotlinx/coroutines/InvokeOnCancelling;
+Lkotlinx/coroutines/InvokeOnCompletion;
+Lkotlinx/coroutines/Job;
+Lkotlinx/coroutines/JobCancellingNode;
+Lkotlinx/coroutines/JobImpl;
+Lkotlinx/coroutines/JobNode;
+Lkotlinx/coroutines/JobSupport$ChildCompletion;
+Lkotlinx/coroutines/JobSupport$Finishing;
+Lkotlinx/coroutines/JobSupport$addLastAtomic$$inlined$addLastIf$1;
+Lkotlinx/coroutines/JobSupport;
+Lkotlinx/coroutines/MainCoroutineDispatcher;
+Lkotlinx/coroutines/NodeList;
+Lkotlinx/coroutines/NonDisposableHandle;
+Lkotlinx/coroutines/NotCompleted;
+Lkotlinx/coroutines/ParentJob;
+Lkotlinx/coroutines/StandaloneCoroutine;
+Lkotlinx/coroutines/SupervisorJobImpl;
+Lkotlinx/coroutines/ThreadLocalEventLoop;
+Lkotlinx/coroutines/TimeoutCancellationException;
+Lkotlinx/coroutines/Unconfined;
+Lkotlinx/coroutines/UndispatchedCoroutine;
+Lkotlinx/coroutines/UndispatchedMarker;
+Lkotlinx/coroutines/Waiter;
+Lkotlinx/coroutines/android/AndroidDispatcherFactory;
+Lkotlinx/coroutines/android/HandlerContext;
+Lkotlinx/coroutines/android/HandlerDispatcher;
+Lkotlinx/coroutines/android/HandlerDispatcherKt;
+Lkotlinx/coroutines/channels/BufferOverflow;
+Lkotlinx/coroutines/channels/BufferedChannel$BufferedChannelIterator;
+Lkotlinx/coroutines/channels/BufferedChannel;
+Lkotlinx/coroutines/channels/BufferedChannelKt$createSegmentFunction$1;
+Lkotlinx/coroutines/channels/BufferedChannelKt;
+Lkotlinx/coroutines/channels/Channel$Factory;
+Lkotlinx/coroutines/channels/Channel;
+Lkotlinx/coroutines/channels/ChannelResult$Closed;
+Lkotlinx/coroutines/channels/ChannelResult$Failed;
+Lkotlinx/coroutines/channels/ChannelSegment;
+Lkotlinx/coroutines/channels/ConflatedBufferedChannel;
+Lkotlinx/coroutines/channels/ProducerCoroutine;
+Lkotlinx/coroutines/channels/ProducerScope;
+Lkotlinx/coroutines/channels/ReceiveChannel;
+Lkotlinx/coroutines/channels/SendChannel;
+Lkotlinx/coroutines/channels/WaiterEB;
+Lkotlinx/coroutines/flow/AbstractFlow$collect$1;
+Lkotlinx/coroutines/flow/DistinctFlowImpl$collect$2$emit$1;
+Lkotlinx/coroutines/flow/DistinctFlowImpl$collect$2;
+Lkotlinx/coroutines/flow/DistinctFlowImpl;
+Lkotlinx/coroutines/flow/Flow;
+Lkotlinx/coroutines/flow/FlowCollector;
+Lkotlinx/coroutines/flow/FlowKt__ChannelsKt$emitAllImpl$1;
+Lkotlinx/coroutines/flow/FlowKt__LimitKt$dropWhile$$inlined$unsafeFlow$1;
+Lkotlinx/coroutines/flow/FlowKt__LimitKt$dropWhile$1$1$emit$1;
+Lkotlinx/coroutines/flow/FlowKt__LimitKt$dropWhile$1$1;
+Lkotlinx/coroutines/flow/FlowKt__MergeKt$mapLatest$1;
+Lkotlinx/coroutines/flow/FlowKt__MergeKt;
+Lkotlinx/coroutines/flow/FlowKt__ReduceKt$first$$inlined$collectWhile$2$1;
+Lkotlinx/coroutines/flow/FlowKt__ReduceKt$first$$inlined$collectWhile$2;
+Lkotlinx/coroutines/flow/FlowKt__ReduceKt$first$3;
+Lkotlinx/coroutines/flow/FlowKt__ShareKt$launchSharing$1$2;
+Lkotlinx/coroutines/flow/FlowKt__ShareKt$launchSharing$1;
+Lkotlinx/coroutines/flow/MutableSharedFlow;
+Lkotlinx/coroutines/flow/ReadonlyStateFlow;
+Lkotlinx/coroutines/flow/SafeFlow;
+Lkotlinx/coroutines/flow/SharedFlowImpl$Emitter;
+Lkotlinx/coroutines/flow/SharedFlowImpl$collect$1;
+Lkotlinx/coroutines/flow/SharedFlowImpl;
+Lkotlinx/coroutines/flow/SharedFlowSlot;
+Lkotlinx/coroutines/flow/SharingCommand;
+Lkotlinx/coroutines/flow/SharingConfig;
+Lkotlinx/coroutines/flow/SharingStarted;
+Lkotlinx/coroutines/flow/StartedLazily;
+Lkotlinx/coroutines/flow/StartedWhileSubscribed$command$1;
+Lkotlinx/coroutines/flow/StartedWhileSubscribed$command$2;
+Lkotlinx/coroutines/flow/StartedWhileSubscribed;
+Lkotlinx/coroutines/flow/StateFlow;
+Lkotlinx/coroutines/flow/StateFlowImpl$collect$1;
+Lkotlinx/coroutines/flow/StateFlowImpl;
+Lkotlinx/coroutines/flow/StateFlowSlot;
+Lkotlinx/coroutines/flow/internal/AbortFlowException;
+Lkotlinx/coroutines/flow/internal/AbstractSharedFlow;
+Lkotlinx/coroutines/flow/internal/AbstractSharedFlowSlot;
+Lkotlinx/coroutines/flow/internal/ChannelFlow$collect$2;
+Lkotlinx/coroutines/flow/internal/ChannelFlow$collectToFun$1;
+Lkotlinx/coroutines/flow/internal/ChannelFlow;
+Lkotlinx/coroutines/flow/internal/ChannelFlowOperator;
+Lkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest$flowCollect$3$1$2;
+Lkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest$flowCollect$3$1$emit$1;
+Lkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest$flowCollect$3$1;
+Lkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest$flowCollect$3;
+Lkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest;
+Lkotlinx/coroutines/flow/internal/FusibleFlow;
+Lkotlinx/coroutines/flow/internal/NoOpContinuation;
+Lkotlinx/coroutines/flow/internal/NopCollector;
+Lkotlinx/coroutines/flow/internal/SafeCollector;
+Lkotlinx/coroutines/flow/internal/SendingCollector;
+Lkotlinx/coroutines/flow/internal/SubscriptionCountStateFlow;
+Lkotlinx/coroutines/internal/AtomicOp;
+Lkotlinx/coroutines/internal/ConcurrentLinkedListNode;
+Lkotlinx/coroutines/internal/ContextScope;
+Lkotlinx/coroutines/internal/DispatchedContinuation;
+Lkotlinx/coroutines/internal/LimitedDispatcher;
+Lkotlinx/coroutines/internal/LockFreeLinkedListNode$toString$1;
+Lkotlinx/coroutines/internal/LockFreeLinkedListNode;
+Lkotlinx/coroutines/internal/LockFreeTaskQueue;
+Lkotlinx/coroutines/internal/LockFreeTaskQueueCore;
+Lkotlinx/coroutines/internal/MainDispatcherFactory;
+Lkotlinx/coroutines/internal/MainDispatcherLoader;
+Lkotlinx/coroutines/internal/OpDescriptor;
+Lkotlinx/coroutines/internal/Removed;
+Lkotlinx/coroutines/internal/ResizableAtomicArray;
+Lkotlinx/coroutines/internal/ScopeCoroutine;
+Lkotlinx/coroutines/internal/Segment;
+Lkotlinx/coroutines/internal/StackTraceRecoveryKt;
+Lkotlinx/coroutines/internal/Symbol;
+Lkotlinx/coroutines/internal/SystemPropsKt__SystemPropsKt;
+Lkotlinx/coroutines/internal/ThreadState;
+Lkotlinx/coroutines/scheduling/CoroutineScheduler;
+Lkotlinx/coroutines/scheduling/DefaultIoScheduler;
+Lkotlinx/coroutines/scheduling/DefaultScheduler;
+Lkotlinx/coroutines/scheduling/GlobalQueue;
+Lkotlinx/coroutines/scheduling/NanoTimeSource;
+Lkotlinx/coroutines/scheduling/SchedulerCoroutineDispatcher;
+Lkotlinx/coroutines/scheduling/Task;
+Lkotlinx/coroutines/scheduling/TasksKt;
+Lkotlinx/coroutines/scheduling/UnlimitedIoScheduler;
+Lkotlinx/coroutines/sync/Mutex;
+Lkotlinx/coroutines/sync/MutexImpl$CancellableContinuationWithOwner$resume$2;
+Lkotlinx/coroutines/sync/MutexImpl$CancellableContinuationWithOwner;
+Lkotlinx/coroutines/sync/MutexImpl;
+Lkotlinx/coroutines/sync/SemaphoreImpl$addAcquireToQueue$createNewSegment$1;
+Lkotlinx/coroutines/sync/SemaphoreImpl$tryResumeNextFromQueue$createNewSegment$1;
+Lkotlinx/coroutines/sync/SemaphoreImpl;
+Lkotlinx/coroutines/sync/SemaphoreKt;
+Lkotlinx/coroutines/sync/SemaphoreSegment;
+Lokhttp3/Headers$Builder;
+Lokhttp3/MediaType;
+PL_COROUTINE/ArtificialStackFrames;->access$removeRunning(Landroidx/compose/runtime/Stack;)V
+PL_COROUTINE/ArtificialStackFrames;->moveGroup(Landroidx/compose/runtime/SlotWriter;ILandroidx/compose/runtime/SlotWriter;ZZZ)Ljava/util/List;
+PLandroidx/activity/ComponentActivity$ReportFullyDrawnExecutorApi16Impl;->run()V
+PLandroidx/arch/core/internal/SafeIterableMap$ListIterator;->next()Ljava/lang/Object;
+PLandroidx/arch/core/internal/SafeIterableMap$ListIterator;->nextNode()Landroidx/arch/core/internal/SafeIterableMap$Entry;
+PLandroidx/arch/core/internal/SafeIterableMap$ListIterator;->supportRemove(Landroidx/arch/core/internal/SafeIterableMap$Entry;)V
+PLandroidx/collection/ArrayMap$EntrySet;->iterator()Ljava/util/Iterator;
+PLandroidx/compose/foundation/DrawOverscrollModifier;->equals(Ljava/lang/Object;)Z
+PLandroidx/compose/foundation/OverscrollConfiguration;->equals(Ljava/lang/Object;)Z
+PLandroidx/compose/foundation/ScrollingLayoutElement;->equals(Ljava/lang/Object;)Z
+PLandroidx/compose/foundation/gestures/BringIntoViewRequestPriorityQueue;->resumeAndRemoveAll()V
+PLandroidx/compose/foundation/gestures/DraggableNode;->disposeInteractionSource()V
+PLandroidx/compose/foundation/gestures/DraggableNode;->onDetach()V
+PLandroidx/compose/foundation/gestures/ScrollableElement;->equals(Ljava/lang/Object;)Z
+PLandroidx/compose/foundation/layout/OffsetElement;->update(Landroidx/compose/ui/Modifier$Node;)V
+PLandroidx/compose/foundation/layout/SizeElement;->update(Landroidx/compose/ui/Modifier$Node;)V
+PLandroidx/compose/foundation/lazy/layout/LazyLayoutPrefetcher;->onForgotten()V
+PLandroidx/compose/runtime/ComposerImpl$CompositionContextHolder;->onForgotten()V
+PLandroidx/compose/runtime/ComposerImpl$CompositionContextImpl;->dispose()V
+PLandroidx/compose/runtime/ComposerImpl;->recordDelete()V
+PLandroidx/compose/runtime/ComposerImpl;->reportFreeMovableContent$reportGroup(Landroidx/compose/runtime/ComposerImpl;IZI)I
+PLandroidx/compose/runtime/ComposerImpl;->reportFreeMovableContent(I)V
+PLandroidx/compose/runtime/CompositionContext;->unregisterComposer$runtime_release(Landroidx/compose/runtime/Composer;)V
+PLandroidx/compose/runtime/CompositionScopedCoroutineScopeCanceller;->onForgotten()V
+PLandroidx/compose/runtime/Pending;->nodePositionOf(Landroidx/compose/runtime/KeyInfo;)I
+PLandroidx/compose/runtime/Pending;->updateNodeCount(II)Z
+PLandroidx/compose/runtime/Recomposer$effectJob$1$1;->invoke(Ljava/lang/Throwable;)V
+PLandroidx/compose/runtime/Recomposer;->cancel()V
+PLandroidx/compose/runtime/Recomposer;->reportRemovedComposition$runtime_release(Landroidx/compose/runtime/CompositionImpl;)V
+PLandroidx/compose/runtime/SlotWriter;->ensureStarted(I)V
+PLandroidx/compose/runtime/SlotWriter;->startGroup()V
+PLandroidx/compose/runtime/changelist/ComposerChangeListWriter;->removeNode(II)V
+PLandroidx/compose/runtime/changelist/Operation$EndCompositionScope;-><clinit>()V
+PLandroidx/compose/runtime/changelist/Operation$EndCompositionScope;-><init>()V
+PLandroidx/compose/runtime/changelist/Operation$EndCompositionScope;->execute(Landroidx/compose/runtime/changelist/Operations$OpIterator;Landroidx/compose/runtime/Applier;Landroidx/compose/runtime/SlotWriter;Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;)V
+PLandroidx/compose/runtime/changelist/Operation$EndCurrentGroup;-><clinit>()V
+PLandroidx/compose/runtime/changelist/Operation$EndCurrentGroup;-><init>()V
+PLandroidx/compose/runtime/changelist/Operation$EndCurrentGroup;->execute(Landroidx/compose/runtime/changelist/Operations$OpIterator;Landroidx/compose/runtime/Applier;Landroidx/compose/runtime/SlotWriter;Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;)V
+PLandroidx/compose/runtime/changelist/Operation$EnsureGroupStarted;-><clinit>()V
+PLandroidx/compose/runtime/changelist/Operation$EnsureGroupStarted;-><init>()V
+PLandroidx/compose/runtime/changelist/Operation$EnsureGroupStarted;->execute(Landroidx/compose/runtime/changelist/Operations$OpIterator;Landroidx/compose/runtime/Applier;Landroidx/compose/runtime/SlotWriter;Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;)V
+PLandroidx/compose/runtime/changelist/Operation$EnsureRootGroupStarted;-><clinit>()V
+PLandroidx/compose/runtime/changelist/Operation$EnsureRootGroupStarted;-><init>()V
+PLandroidx/compose/runtime/changelist/Operation$EnsureRootGroupStarted;->execute(Landroidx/compose/runtime/changelist/Operations$OpIterator;Landroidx/compose/runtime/Applier;Landroidx/compose/runtime/SlotWriter;Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;)V
+PLandroidx/compose/runtime/changelist/Operation$RemoveCurrentGroup;-><clinit>()V
+PLandroidx/compose/runtime/changelist/Operation$RemoveCurrentGroup;-><init>()V
+PLandroidx/compose/runtime/changelist/Operation$RemoveCurrentGroup;->execute(Landroidx/compose/runtime/changelist/Operations$OpIterator;Landroidx/compose/runtime/Applier;Landroidx/compose/runtime/SlotWriter;Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;)V
+PLandroidx/compose/runtime/changelist/Operation$RemoveNode;-><clinit>()V
+PLandroidx/compose/runtime/changelist/Operation$RemoveNode;-><init>()V
+PLandroidx/compose/runtime/changelist/Operation$RemoveNode;->execute(Landroidx/compose/runtime/changelist/Operations$OpIterator;Landroidx/compose/runtime/Applier;Landroidx/compose/runtime/SlotWriter;Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;)V
+PLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;->remove(IILandroidx/compose/runtime/Stack;)Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;
+PLandroidx/compose/runtime/saveable/SaveableHolder;->onForgotten()V
+PLandroidx/compose/runtime/saveable/SaveableStateRegistryImpl$registerProvider$3;->unregister()V
+PLandroidx/compose/runtime/snapshots/Snapshot$Companion$$ExternalSyntheticLambda0;->dispose()V
+PLandroidx/compose/runtime/snapshots/SnapshotIdSet$iterator$1;-><init>(Landroidx/compose/runtime/snapshots/SnapshotIdSet;Lkotlin/coroutines/Continuation;)V
+PLandroidx/compose/runtime/snapshots/SnapshotIdSet$iterator$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+PLandroidx/compose/runtime/snapshots/SnapshotIdSet$iterator$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+PLandroidx/compose/runtime/snapshots/SnapshotIdSet;->iterator()Ljava/util/Iterator;
+PLandroidx/compose/ui/autofill/AutofillCallback;->unregister(Landroidx/compose/ui/autofill/AndroidAutofill;)V
+PLandroidx/compose/ui/focus/FocusOwnerImpl;->clearFocus(ZZ)V
+PLandroidx/compose/ui/input/nestedscroll/NestedScrollNode;->onDetach()V
+PLandroidx/compose/ui/input/pointer/SuspendPointerInputElement;->equals(Ljava/lang/Object;)Z
+PLandroidx/compose/ui/input/pointer/SuspendingPointerInputModifierNodeImpl;->onDetach()V
+PLandroidx/compose/ui/input/pointer/SuspendingPointerInputModifierNodeImpl;->resetPointerInputHandler()V
+PLandroidx/compose/ui/layout/OnSizeChangedModifier;->equals(Ljava/lang/Object;)Z
+PLandroidx/compose/ui/modifier/ModifierLocalManager;->invalidate()V
+PLandroidx/compose/ui/node/BackwardsCompatNode;->onDetach()V
+PLandroidx/compose/ui/node/MeasureAndLayoutDelegate$PostponedRequest;-><init>(Landroidx/compose/ui/node/LayoutNode;ZZ)V
+PLandroidx/compose/ui/node/UiApplier;->remove(II)V
+PLandroidx/compose/ui/platform/AndroidComposeView;->getModifierLocalManager()Landroidx/compose/ui/modifier/ModifierLocalManager;
+PLandroidx/compose/ui/platform/AndroidComposeView;->onDetachedFromWindow()V
+PLandroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat$1;->onViewDetachedFromWindow(Landroid/view/View;)V
+PLandroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat$boundsUpdatesEventLoop$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+PLandroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat;->onStop(Landroidx/lifecycle/LifecycleOwner;)V
+PLandroidx/compose/ui/platform/DisposableSaveableStateRegistry_androidKt$DisposableSaveableStateRegistry$1;->invoke()Ljava/lang/Object;
+PLandroidx/compose/ui/platform/WeakCache;-><init>(Lcom/google/gson/internal/ConstructorConstructor;Ljava/lang/Class;)V
+PLandroidx/compose/ui/platform/WindowRecomposer_androidKt$createLifecycleAwareWindowRecomposer$1;->onViewDetachedFromWindow(Landroid/view/View;)V
+PLandroidx/compose/ui/platform/WrappedComposition;->dispose()V
+PLandroidx/compose/ui/text/PlatformTextStyle;->equals(Ljava/lang/Object;)Z
+PLandroidx/concurrent/futures/AbstractResolvableFuture$Listener;-><clinit>()V
+PLandroidx/concurrent/futures/AbstractResolvableFuture$SafeAtomicHelper;-><init>(Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;)V
+PLandroidx/concurrent/futures/AbstractResolvableFuture$SafeAtomicHelper;->casListeners(Landroidx/concurrent/futures/AbstractResolvableFuture;Landroidx/concurrent/futures/AbstractResolvableFuture$Listener;)Z
+PLandroidx/concurrent/futures/AbstractResolvableFuture$SafeAtomicHelper;->casValue(Landroidx/concurrent/futures/AbstractResolvableFuture;Ljava/lang/Object;Ljava/lang/Object;)Z
+PLandroidx/concurrent/futures/AbstractResolvableFuture$SafeAtomicHelper;->casWaiters(Landroidx/concurrent/futures/AbstractResolvableFuture;Landroidx/concurrent/futures/AbstractResolvableFuture$Waiter;Landroidx/concurrent/futures/AbstractResolvableFuture$Waiter;)Z
+PLandroidx/concurrent/futures/AbstractResolvableFuture$Waiter;-><clinit>()V
+PLandroidx/concurrent/futures/AbstractResolvableFuture$Waiter;-><init>(I)V
+PLandroidx/concurrent/futures/AbstractResolvableFuture;-><clinit>()V
+PLandroidx/concurrent/futures/AbstractResolvableFuture;->complete(Landroidx/concurrent/futures/AbstractResolvableFuture;)V
+PLandroidx/core/view/ViewKt$ancestors$1;-><clinit>()V
+PLandroidx/core/view/ViewKt$ancestors$1;-><init>()V
+PLandroidx/core/view/ViewKt$ancestors$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+PLandroidx/lifecycle/DefaultLifecycleObserver;->onDestroy(Landroidx/lifecycle/LifecycleOwner;)V
+PLandroidx/lifecycle/DefaultLifecycleObserver;->onStop(Landroidx/lifecycle/LifecycleOwner;)V
+PLandroidx/lifecycle/EmptyActivityLifecycleCallbacks;->onActivityDestroyed(Landroid/app/Activity;)V
+PLandroidx/lifecycle/EmptyActivityLifecycleCallbacks;->onActivityPaused(Landroid/app/Activity;)V
+PLandroidx/lifecycle/EmptyActivityLifecycleCallbacks;->onActivityStopped(Landroid/app/Activity;)V
+PLandroidx/lifecycle/ProcessLifecycleOwner$attach$1;->onActivityPaused(Landroid/app/Activity;)V
+PLandroidx/lifecycle/ProcessLifecycleOwner$attach$1;->onActivityStopped(Landroid/app/Activity;)V
+PLandroidx/lifecycle/ReportFragment$LifecycleCallbacks;->onActivityDestroyed(Landroid/app/Activity;)V
+PLandroidx/lifecycle/ReportFragment$LifecycleCallbacks;->onActivityPaused(Landroid/app/Activity;)V
+PLandroidx/lifecycle/ReportFragment$LifecycleCallbacks;->onActivityPreDestroyed(Landroid/app/Activity;)V
+PLandroidx/lifecycle/ReportFragment$LifecycleCallbacks;->onActivityPrePaused(Landroid/app/Activity;)V
+PLandroidx/lifecycle/ReportFragment$LifecycleCallbacks;->onActivityPreStopped(Landroid/app/Activity;)V
+PLandroidx/lifecycle/ReportFragment$LifecycleCallbacks;->onActivityStopped(Landroid/app/Activity;)V
+PLandroidx/lifecycle/ReportFragment;->onDestroy()V
+PLandroidx/lifecycle/ReportFragment;->onPause()V
+PLandroidx/lifecycle/ReportFragment;->onStop()V
+PLandroidx/metrics/performance/JankStatsApi24Impl;->removeFrameMetricsListenerDelegate(Landroidx/metrics/performance/JankStatsApi24Impl$$ExternalSyntheticLambda0;Landroid/view/Window;)V
+PLandroidx/profileinstaller/ProfileInstaller$$ExternalSyntheticLambda1;-><init>(I)V
+PLandroidx/profileinstaller/ProfileInstallerInitializer$$ExternalSyntheticLambda1;->run()V
+PLandroidx/profileinstaller/ProfileVerifier$Cache;-><init>(IIJJ)V
+PLandroidx/profileinstaller/ProfileVerifier$Cache;->writeOnFile(Ljava/io/File;)V
+PLandroidx/profileinstaller/ProfileVerifier;-><clinit>()V
+PLandroidx/profileinstaller/ProfileVerifier;->setCompilationStatus(IZZ)Landroidx/compose/ui/unit/Dp$Companion;
+PLandroidx/profileinstaller/ProfileVerifier;->writeProfileVerification(Landroid/content/Context;Z)V
+PLandroidx/tv/foundation/lazy/layout/LazyLayoutBeyondBoundsInfo$Interval;->equals(Ljava/lang/Object;)Z
+PLandroidx/tv/foundation/lazy/list/LazyLayoutBeyondBoundsModifierLocal;->getValue()Ljava/lang/Object;
+PLcom/example/tvcomposebasedtests/MainActivity;->onPause()V
+PLcom/google/gson/FieldNamingPolicy$1;-><init>()V
+PLcom/google/gson/FieldNamingPolicy$1;->translateName(Ljava/lang/reflect/Field;)Ljava/lang/String;
+PLcom/google/gson/FieldNamingPolicy$2;-><init>()V
+PLcom/google/gson/FieldNamingPolicy$3;-><init>()V
+PLcom/google/gson/FieldNamingPolicy$4;-><init>()V
+PLcom/google/gson/FieldNamingPolicy$5;-><init>()V
+PLcom/google/gson/FieldNamingPolicy$6;-><init>()V
+PLcom/google/gson/FieldNamingPolicy$7;-><init>()V
+PLcom/google/gson/FieldNamingPolicy;-><clinit>()V
+PLcom/google/gson/FieldNamingPolicy;-><init>(Ljava/lang/String;I)V
+PLcom/google/gson/Gson$1;-><init>(I)V
+PLcom/google/gson/Gson$3;-><init>(I)V
+PLcom/google/gson/Gson$4;-><init>(Lcom/google/gson/TypeAdapter;I)V
+PLcom/google/gson/Gson$FutureTypeAdapter;-><init>()V
+PLcom/google/gson/Gson;-><init>()V
+PLcom/google/gson/Gson;->newJsonWriter(Ljava/io/Writer;)Lcom/google/gson/stream/JsonWriter;
+PLcom/google/gson/Gson;->toJson(Lcom/google/gson/JsonObject;Lcom/google/gson/stream/JsonWriter;)V
+PLcom/google/gson/JsonNull;-><clinit>()V
+PLcom/google/gson/JsonPrimitive;-><init>(Ljava/lang/String;)V
+PLcom/google/gson/ToNumberPolicy$1;-><init>()V
+PLcom/google/gson/ToNumberPolicy$2;-><init>()V
+PLcom/google/gson/ToNumberPolicy$3;-><init>()V
+PLcom/google/gson/ToNumberPolicy$4;-><init>()V
+PLcom/google/gson/ToNumberPolicy;-><clinit>()V
+PLcom/google/gson/ToNumberPolicy;-><init>(Ljava/lang/String;I)V
+PLcom/google/gson/TypeAdapter;->nullSafe()Lcom/google/gson/Gson$4;
+PLcom/google/gson/internal/$Gson$Types$ParameterizedTypeImpl;-><init>(Ljava/lang/reflect/Type;Ljava/lang/reflect/Type;[Ljava/lang/reflect/Type;)V
+PLcom/google/gson/internal/$Gson$Types$ParameterizedTypeImpl;->getActualTypeArguments()[Ljava/lang/reflect/Type;
+PLcom/google/gson/internal/ConstructorConstructor;-><init>(Ljava/util/Map;Ljava/util/List;)V
+PLcom/google/gson/internal/ConstructorConstructor;->checkInstantiable(Ljava/lang/Class;)Ljava/lang/String;
+PLcom/google/gson/internal/ConstructorConstructor;->get(Lcom/google/gson/reflect/TypeToken;)Lcom/google/gson/internal/ObjectConstructor;
+PLcom/google/gson/internal/Excluder;-><clinit>()V
+PLcom/google/gson/internal/Excluder;-><init>()V
+PLcom/google/gson/internal/Excluder;->create(Lcom/google/gson/Gson;Lcom/google/gson/reflect/TypeToken;)Lcom/google/gson/TypeAdapter;
+PLcom/google/gson/internal/Excluder;->excludeClassInStrategy(Z)V
+PLcom/google/gson/internal/Excluder;->isAnonymousOrNonStaticLocal(Ljava/lang/Class;)Z
+PLcom/google/gson/internal/LinkedTreeMap$KeySet$1;-><init>(Landroidx/collection/ArrayMap$EntrySet;)V
+PLcom/google/gson/internal/LinkedTreeMap$KeySet$1;->next()Ljava/lang/Object;
+PLcom/google/gson/internal/LinkedTreeMap$LinkedTreeMapIterator;->nextNode()Lcom/google/gson/internal/LinkedTreeMap$Node;
+PLcom/google/gson/internal/LinkedTreeMap$Node;->getKey()Ljava/lang/Object;
+PLcom/google/gson/internal/LinkedTreeMap;-><clinit>()V
+PLcom/google/gson/internal/bind/ArrayTypeAdapter;-><clinit>()V
+PLcom/google/gson/internal/bind/CollectionTypeAdapterFactory;-><init>(Lcom/google/gson/internal/ConstructorConstructor;I)V
+PLcom/google/gson/internal/bind/CollectionTypeAdapterFactory;->create(Lcom/google/gson/Gson;Lcom/google/gson/reflect/TypeToken;)Lcom/google/gson/TypeAdapter;
+PLcom/google/gson/internal/bind/DateTypeAdapter;-><clinit>()V
+PLcom/google/gson/internal/bind/JsonTreeWriter$1;-><init>()V
+PLcom/google/gson/internal/bind/JsonTreeWriter;-><clinit>()V
+PLcom/google/gson/internal/bind/JsonTreeWriter;->beginObject()V
+PLcom/google/gson/internal/bind/JsonTreeWriter;->value(Ljava/lang/Boolean;)V
+PLcom/google/gson/internal/bind/MapTypeAdapterFactory;-><init>(Lcom/google/gson/internal/ConstructorConstructor;)V
+PLcom/google/gson/internal/bind/MapTypeAdapterFactory;->create(Lcom/google/gson/Gson;Lcom/google/gson/reflect/TypeToken;)Lcom/google/gson/TypeAdapter;
+PLcom/google/gson/internal/bind/NumberTypeAdapter$1;-><init>(ILjava/lang/Object;)V
+PLcom/google/gson/internal/bind/NumberTypeAdapter$1;->create(Lcom/google/gson/Gson;Lcom/google/gson/reflect/TypeToken;)Lcom/google/gson/TypeAdapter;
+PLcom/google/gson/internal/bind/NumberTypeAdapter;-><clinit>()V
+PLcom/google/gson/internal/bind/ObjectTypeAdapter;-><clinit>()V
+PLcom/google/gson/internal/bind/ObjectTypeAdapter;-><init>(Lcom/google/gson/Gson;)V
+PLcom/google/gson/internal/bind/ReflectiveTypeAdapterFactory$1;-><init>(Ljava/lang/String;Ljava/lang/reflect/Field;ZZLjava/lang/reflect/Method;ZLcom/google/gson/TypeAdapter;Lcom/google/gson/Gson;Lcom/google/gson/reflect/TypeToken;)V
+PLcom/google/gson/internal/bind/ReflectiveTypeAdapterFactory$Adapter;-><init>(Ljava/util/LinkedHashMap;)V
+PLcom/google/gson/internal/bind/ReflectiveTypeAdapterFactory;-><init>(Lcom/google/gson/internal/ConstructorConstructor;Lcom/google/gson/internal/Excluder;Lcom/google/gson/internal/bind/CollectionTypeAdapterFactory;Ljava/util/List;)V
+PLcom/google/gson/internal/bind/ReflectiveTypeAdapterFactory;->create(Lcom/google/gson/Gson;Lcom/google/gson/reflect/TypeToken;)Lcom/google/gson/TypeAdapter;
+PLcom/google/gson/internal/bind/ReflectiveTypeAdapterFactory;->getBoundFields(Lcom/google/gson/Gson;Lcom/google/gson/reflect/TypeToken;Ljava/lang/Class;Z)Ljava/util/LinkedHashMap;
+PLcom/google/gson/internal/bind/ReflectiveTypeAdapterFactory;->includeField(Ljava/lang/reflect/Field;Z)Z
+PLcom/google/gson/internal/bind/TypeAdapters$29;-><init>(I)V
+PLcom/google/gson/internal/bind/TypeAdapters$29;->create(Lcom/google/gson/Gson;Lcom/google/gson/reflect/TypeToken;)Lcom/google/gson/TypeAdapter;
+PLcom/google/gson/internal/bind/TypeAdapters$31;-><init>(Ljava/lang/Class;Lcom/google/gson/TypeAdapter;I)V
+PLcom/google/gson/internal/bind/TypeAdapters$31;->create(Lcom/google/gson/Gson;Lcom/google/gson/reflect/TypeToken;)Lcom/google/gson/TypeAdapter;
+PLcom/google/gson/internal/bind/TypeAdapters$32;-><init>(Ljava/lang/Class;Ljava/lang/Class;Lcom/google/gson/TypeAdapter;I)V
+PLcom/google/gson/internal/bind/TypeAdapters$32;->create(Lcom/google/gson/Gson;Lcom/google/gson/reflect/TypeToken;)Lcom/google/gson/TypeAdapter;
+PLcom/google/gson/internal/bind/TypeAdapters$34$1;-><init>(Lcom/google/gson/Gson;Ljava/lang/reflect/Type;Lcom/google/gson/TypeAdapter;Lcom/google/gson/internal/ObjectConstructor;)V
+PLcom/google/gson/internal/bind/TypeAdapters;-><clinit>()V
+PLcom/google/gson/internal/bind/TypeAdapters;->newFactory(Ljava/lang/Class;Lcom/google/gson/TypeAdapter;)Lcom/google/gson/internal/bind/TypeAdapters$31;
+PLcom/google/gson/internal/bind/TypeAdapters;->newFactory(Ljava/lang/Class;Ljava/lang/Class;Lcom/google/gson/TypeAdapter;)Lcom/google/gson/internal/bind/TypeAdapters$32;
+PLcom/google/gson/internal/reflect/ReflectionHelper$RecordNotSupportedHelper;-><init>()V
+PLcom/google/gson/internal/reflect/ReflectionHelper$RecordNotSupportedHelper;->isRecord(Ljava/lang/Class;)Z
+PLcom/google/gson/internal/reflect/ReflectionHelper$RecordSupportedHelper;-><init>()V
+PLcom/google/gson/internal/reflect/ReflectionHelper;-><clinit>()V
+PLcom/google/gson/internal/reflect/ReflectionHelper;->makeAccessible(Ljava/lang/reflect/AccessibleObject;)V
+PLcom/google/gson/internal/sql/SqlDateTypeAdapter;-><clinit>()V
+PLcom/google/gson/internal/sql/SqlTimeTypeAdapter;-><clinit>()V
+PLcom/google/gson/internal/sql/SqlTimestampTypeAdapter;-><clinit>()V
+PLcom/google/gson/internal/sql/SqlTypesSupport;-><clinit>()V
+PLcom/google/gson/stream/JsonWriter;-><clinit>()V
+PLcom/google/gson/stream/JsonWriter;->endArray()V
+PLcom/google/gson/stream/JsonWriter;->name(Ljava/lang/String;)V
+PLcom/google/gson/stream/JsonWriter;->newline()V
+PLkotlin/ResultKt;->SampleTvLazyColumn(ILandroidx/compose/runtime/Composer;I)V
+PLkotlin/ResultKt;->TvLazyColumn(Landroidx/compose/ui/Modifier;Landroidx/tv/foundation/lazy/list/TvLazyListState;Landroidx/compose/foundation/layout/PaddingValuesImpl;ZLandroidx/compose/foundation/layout/Arrangement$Vertical;Landroidx/compose/ui/Alignment$Horizontal;ZLandroidx/tv/foundation/PivotOffsets;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V
+PLkotlin/ResultKt;->checkArgument(Z)V
+PLkotlin/ResultKt;->checkNotPrimitive(Ljava/lang/reflect/Type;)V
+PLkotlin/ResultKt;->equals(Ljava/lang/reflect/Type;Ljava/lang/reflect/Type;)Z
+PLkotlin/ResultKt;->getFilterResult(Ljava/util/List;)V
+PLkotlin/ResultKt;->getGenericSupertype(Ljava/lang/reflect/Type;Ljava/lang/Class;Ljava/lang/Class;)Ljava/lang/reflect/Type;
+PLkotlin/ResultKt;->getSupertype(Ljava/lang/reflect/Type;Ljava/lang/Class;Ljava/lang/Class;)Ljava/lang/reflect/Type;
+PLkotlin/ResultKt;->resolve(Ljava/lang/reflect/Type;Ljava/lang/Class;Ljava/lang/reflect/Type;Ljava/util/HashMap;)Ljava/lang/reflect/Type;
+PLkotlin/ResultKt;->write(Lcom/google/gson/JsonElement;Lcom/google/gson/stream/JsonWriter;)V
+PLkotlin/TuplesKt;-><init>(I)V
+PLkotlin/TuplesKt;-><init>(Ljava/lang/Object;)V
+PLkotlin/TuplesKt;->access$removeEntryAtIndex([Ljava/lang/Object;I)[Ljava/lang/Object;
+PLkotlin/TuplesKt;->asMutableCollection(Ljava/util/LinkedHashSet;)Ljava/util/Collection;
+PLkotlin/TuplesKt;->closeFinally(Ljava/io/Closeable;Ljava/lang/Throwable;)V
+PLkotlin/TuplesKt;->writeProfile(Landroid/content/Context;Landroidx/profileinstaller/ProfileInstaller$$ExternalSyntheticLambda1;Landroidx/profileinstaller/ProfileInstaller$DiagnosticsCallback;Z)V
+PLkotlin/ULong$Companion;->checkPositionIndex$kotlin_stdlib(II)V
+PLkotlin/ULong$Companion;->onResultReceived(ILjava/lang/Object;)V
+PLkotlin/collections/CollectionsKt___CollectionsKt;->minus(Ljava/util/List;Lkotlin/Function;)Ljava/util/ArrayList;
+PLkotlin/coroutines/jvm/internal/BaseContinuationImpl;->releaseIntercepted()V
+PLkotlin/coroutines/jvm/internal/RestrictedContinuationImpl;-><init>(Lkotlin/coroutines/Continuation;)V
+PLkotlin/coroutines/jvm/internal/RestrictedSuspendLambda;-><init>(Lkotlin/coroutines/Continuation;)V
+PLkotlin/sequences/SequenceBuilderIterator;-><init>()V
+PLkotlin/sequences/SequenceBuilderIterator;->getContext()Lkotlin/coroutines/CoroutineContext;
+PLkotlin/sequences/SequenceBuilderIterator;->hasNext()Z
+PLkotlin/sequences/SequenceBuilderIterator;->next()Ljava/lang/Object;
+PLkotlin/sequences/SequenceBuilderIterator;->resumeWith(Ljava/lang/Object;)V
+PLkotlin/sequences/SequenceBuilderIterator;->yield(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)V
+PLkotlin/text/Charsets;-><clinit>()V
+PLkotlinx/coroutines/AbstractCoroutine;->cancellationExceptionMessage()Ljava/lang/String;
+PLkotlinx/coroutines/InvokeOnCompletion;->invoke(Ljava/lang/Throwable;)V
+PLkotlinx/coroutines/JobCancellationException;-><init>(Ljava/lang/String;Ljava/lang/Throwable;Lkotlinx/coroutines/Job;)V
+PLkotlinx/coroutines/JobCancellationException;->equals(Ljava/lang/Object;)Z
+PLkotlinx/coroutines/JobCancellationException;->fillInStackTrace()Ljava/lang/Throwable;
+PLkotlinx/coroutines/JobSupport$Finishing;->isActive()Z
+PLkotlinx/coroutines/JobSupport;->cancellationExceptionMessage()Ljava/lang/String;
+PLkotlinx/coroutines/UndispatchedCoroutine;->afterResume(Ljava/lang/Object;)V
+PLkotlinx/coroutines/flow/FlowKt__ReduceKt$first$3;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+PLkotlinx/coroutines/flow/SharedFlowSlot;->freeLocked(Lkotlinx/coroutines/flow/internal/AbstractSharedFlow;)[Lkotlin/coroutines/Continuation;
+PLkotlinx/coroutines/flow/SharingConfig;->clear()V
+PLkotlinx/coroutines/flow/StateFlowSlot;->freeLocked(Lkotlinx/coroutines/flow/internal/AbstractSharedFlow;)[Lkotlin/coroutines/Continuation;
+PLkotlinx/coroutines/flow/internal/AbstractSharedFlow;->freeSlot(Lkotlinx/coroutines/flow/internal/AbstractSharedFlowSlot;)V
+PLkotlinx/coroutines/internal/DispatchedContinuation;->cancelCompletedResult$kotlinx_coroutines_core(Ljava/lang/Object;Ljava/util/concurrent/CancellationException;)V
+PLokhttp3/MediaType;->access$locationOf(Ljava/util/ArrayList;II)I
diff --git a/tv/onboarding.md b/tv/onboarding.md
index 32d68cf..70a6c9d 100644
--- a/tv/onboarding.md
+++ b/tv/onboarding.md
@@ -1,51 +1,64 @@
# Onboarding to Compose for TV libraries
-## Getting Started
+## Learn about Jetpack Compose
+1. The [Compose landing page][compose-landing-page] provides an overview of Compose and its features.
+2. The [Compose Quick Tutorial][compose-quick-tutorial] walks you through the basics of Compose using code examples.
+3. The [Compose Course][compose-course] is a more comprehensive guide to Compose, covering topics such as layout, animations, and state management.
-1. See what Google suggests are [good design patterns][good-design-patterns]
- and [offered components][tv-components]
-2. Consult the documentation for information on the packages and various components that are
- offered.
- * [tv-foundation][tv-foundation]
- * [tv-foundation-lazy-list][tv-foundation-lazy-list]
- * [tv-foundation-lazy-grid][tv-foundation-lazy-grid]
- * [tv-material][tv-material]
-3. Read documentation and examples on [developer.android.com][dac]
-4. Read up on the [codelabs][codelabs]
-5. Ensure that you are on the latest version of [Compose for TV libraries][compose-for-tv-libraries]
+
+## Learn about Compose for TV
+1. Explore the [available components][tv-components] and the [design patterns][good-design-patterns] that Google recommends.
+2. Consult the documentation for information on the packages and various components available.
+ * [tv-foundation][tv-foundation]
+ * [tv-foundation-lazy-list][tv-foundation-lazy-list]
+ * [tv-foundation-lazy-grid][tv-foundation-lazy-grid]
+ * [tv-material][tv-material]
+3. Refer to the documentation and examples on [developer.android.com][dac].
+4. Get up to speed with the [codelabs][codelabs].
+5. Find the sample app on [GitHub][github-sample-app].
+
+## If you run into issues
+1. Make sure that you are using the most recent version of [Compose for TV libraries][compose-for-tv-libraries]
and Jetpack compose.
-6. Read the FAQs below
-7. Check with the community
- * [stack overflow][stackoverflow]
- * slack (TBD)
- * discord (TBD)
-8. Check if there is a bug already reported on [issue-tracker][issue-tracker]
-9. [File a bug on issue-tracker][issue-tracker-file-a-bug]
-10. Contact a Developer Relations partner from Google who can involve someone from the engineering
- team as necessary
+2. Read the FAQs below.
+3. Check with the community over on [stack overflow][stackoverflow].
+4. Check if there is a bug already reported on [issue-tracker][issue-tracker].
+5. File a bug on [issue-tracker][issue-tracker-file-a-bug].
+6. Reach out to a Google Developer Relations partner who can, if necessary, bring in someone from the engineering team.
## FAQs
-1. How can I improve the performance of my app written using tv-compose?
- * Any [performance improvements][improve-performance] suggested for a Compose app would
- generally apply to apps built with Compose for TV libraries too.
- * Use [baseline profiles][baseline-profiles] as recommended
- in [Jetpack Compose Performance guide][jetpack-compose-performance].
- Watch [Making apps blazing fast with Baseline Profiles][making-apps-blazing-fast-with-baseline-profiles]
- * Checkout [Interpreting Compose Compiler Metrics][interpreting-compose-compiler-metrics].
-2. My app is crashing!
- * Ensure that you are on the latest alpha version
- of [Compose for TV libraries][compose-for-tv-libraries] and Jetpack Compose
- * Check if there is a bug already reported on [issue-tracker][issue-tracker]
- * [File a bug on issue-tracker][issue-tracker-file-a-bug]
-3. The Navigation drawer is pushing my content aside. I don’t like it.
+1. ### How can I improve the performance of my app written using tv-compose?
+ * [Performance improvements][improve-performance] suggested for a Compose app would typically apply to apps built with Compose for TV libraries as well.
+ * Use [baseline profiles][baseline-profiles] as recommended
+ in [Jetpack Compose Performance guide][jetpack-compose-performance].
+ Watch [Making apps blazing fast with Baseline Profiles][making-apps-blazing-fast-with-baseline-profiles].
+ * Check out [Interpreting Compose Compiler Metrics][interpreting-compose-compiler-metrics].
+2. ### My app is crashing!
+ * Ensure that you are on the latest version.
+ of [Compose for TV libraries][compose-for-tv-libraries] and Jetpack Compose
+ * Check if there is a bug already reported on [issue-tracker][issue-tracker].
+ * [File a bug on issue-tracker][issue-tracker-file-a-bug].
+3. ### The Navigation drawer is pushing my content aside. I don’t like it.
Consider using a [Modal Navigation Drawer][modal-navigation-drawer] provided
- in [Compose for TV library][compose-for-tv-modal-navigation-drawer]
+ in [Compose for TV library][compose-for-tv-modal-navigation-drawer].
+4. ### Sideloading baseline profiles to test performance, without releasing the app.
+ Refer to the steps for [applying baseline profiles][tv-samples-baseline-profiles] in the
+ Jetstream sample app.
+
+
+[compose-landing-page]: https://developer.android.com/jetpack/compose
+
+[compose-quick-tutorial]: https://developer.android.com/jetpack/compose/tutorial
+
+[compose-course]: https://developer.android.com/courses/jetpack-compose/course
[good-design-patterns]: https://developer.android.com/design/ui/tv
[dac]: https://developer.android.com/training/tv/playback/compose
+[github-sample-app]: https://github.com/android/tv-samples/tree/main/JetStreamCompose
+
[modal-navigation-drawer]: https://m3.material.io/components/navigation-drawer/overview#15a3aa10-1be4-4be4-8370-36a1779f65e5
[compose-for-tv-modal-navigation-drawer]: https://developer.android.com/reference/kotlin/androidx/tv/material3/package-summary#ModalNavigationDrawer(kotlin.Function1,androidx.compose.ui.Modifier,androidx.tv.material3.DrawerState,androidx.compose.ui.graphics.Color,kotlin.Function0)
@@ -79,3 +92,5 @@
[tv-foundation-lazy-grid]: https://developer.android.com/reference/kotlin/androidx/tv/foundation/lazy/grid/package-summary
[tv-material]: https://developer.android.com/reference/kotlin/androidx/tv/material3/package-summary
+
+[tv-samples-baseline-profiles]: https://github.com/android/tv-samples/blob/main/JetStreamCompose/baseline-profiles.md
\ No newline at end of file
diff --git a/tv/tv-material/api/api_lint.ignore b/tv/tv-material/api/api_lint.ignore
new file mode 100644
index 0000000..5954166
--- /dev/null
+++ b/tv/tv-material/api/api_lint.ignore
@@ -0,0 +1,4 @@
+// Baseline format: 1.0
+
+GetterSetterNames: field NavigationDrawerScope.doesNavigationDrawerHaveFocus:
+ Invalid name for boolean property `doesNavigationDrawerHaveFocus`. Should start with one of `has`, `can`, `should`, `is`.
\ No newline at end of file
diff --git a/tv/tv-material/api/current.txt b/tv/tv-material/api/current.txt
index 1ea43bf..dcdfda26 100644
--- a/tv/tv-material/api/current.txt
+++ b/tv/tv-material/api/current.txt
@@ -690,6 +690,10 @@
property public final androidx.tv.material3.Glow selectedGlow;
}
+ public final class NavigationDrawerItemKt {
+ method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void NavigationDrawerItem(androidx.tv.material3.NavigationDrawerScope, boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> leadingContent, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingContent, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingContent, optional float tonalElevation, optional androidx.tv.material3.NavigationDrawerItemShape shape, optional androidx.tv.material3.NavigationDrawerItemColors colors, optional androidx.tv.material3.NavigationDrawerItemScale scale, optional androidx.tv.material3.NavigationDrawerItemBorder border, optional androidx.tv.material3.NavigationDrawerItemGlow glow, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ }
+
@SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class NavigationDrawerItemScale {
ctor public NavigationDrawerItemScale(@FloatRange(from=0.0) float scale, @FloatRange(from=0.0) float focusedScale, @FloatRange(from=0.0) float pressedScale, @FloatRange(from=0.0) float selectedScale, @FloatRange(from=0.0) float disabledScale, @FloatRange(from=0.0) float focusedSelectedScale, @FloatRange(from=0.0) float focusedDisabledScale, @FloatRange(from=0.0) float pressedSelectedScale);
method public float getDisabledScale();
@@ -737,14 +741,14 @@
}
public final class NavigationDrawerKt {
- method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void ModalNavigationDrawer(kotlin.jvm.functions.Function1<? super androidx.tv.material3.DrawerValue,kotlin.Unit> drawerContent, optional androidx.compose.ui.Modifier modifier, optional androidx.tv.material3.DrawerState drawerState, optional androidx.compose.ui.graphics.Brush scrimBrush, kotlin.jvm.functions.Function0<kotlin.Unit> content);
- method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void NavigationDrawer(kotlin.jvm.functions.Function1<? super androidx.tv.material3.DrawerValue,kotlin.Unit> drawerContent, optional androidx.compose.ui.Modifier modifier, optional androidx.tv.material3.DrawerState drawerState, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void ModalNavigationDrawer(kotlin.jvm.functions.Function2<? super androidx.tv.material3.NavigationDrawerScope,? super androidx.tv.material3.DrawerValue,kotlin.Unit> drawerContent, optional androidx.compose.ui.Modifier modifier, optional androidx.tv.material3.DrawerState drawerState, optional androidx.compose.ui.graphics.Brush scrimBrush, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void NavigationDrawer(kotlin.jvm.functions.Function2<? super androidx.tv.material3.NavigationDrawerScope,? super androidx.tv.material3.DrawerValue,kotlin.Unit> drawerContent, optional androidx.compose.ui.Modifier modifier, optional androidx.tv.material3.DrawerState drawerState, kotlin.jvm.functions.Function0<kotlin.Unit> content);
method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static androidx.tv.material3.DrawerState rememberDrawerState(androidx.tv.material3.DrawerValue initialValue);
}
@SuppressCompatibility @androidx.tv.material3.ExperimentalTvMaterial3Api public interface NavigationDrawerScope {
- method public boolean isActivated();
- property public abstract boolean isActivated;
+ method public boolean getDoesNavigationDrawerHaveFocus();
+ property public abstract boolean doesNavigationDrawerHaveFocus;
}
@SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class NonInteractiveSurfaceColors {
diff --git a/tv/tv-material/api/restricted_current.txt b/tv/tv-material/api/restricted_current.txt
index 1ea43bf..dcdfda26 100644
--- a/tv/tv-material/api/restricted_current.txt
+++ b/tv/tv-material/api/restricted_current.txt
@@ -690,6 +690,10 @@
property public final androidx.tv.material3.Glow selectedGlow;
}
+ public final class NavigationDrawerItemKt {
+ method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void NavigationDrawerItem(androidx.tv.material3.NavigationDrawerScope, boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> leadingContent, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingContent, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingContent, optional float tonalElevation, optional androidx.tv.material3.NavigationDrawerItemShape shape, optional androidx.tv.material3.NavigationDrawerItemColors colors, optional androidx.tv.material3.NavigationDrawerItemScale scale, optional androidx.tv.material3.NavigationDrawerItemBorder border, optional androidx.tv.material3.NavigationDrawerItemGlow glow, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ }
+
@SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class NavigationDrawerItemScale {
ctor public NavigationDrawerItemScale(@FloatRange(from=0.0) float scale, @FloatRange(from=0.0) float focusedScale, @FloatRange(from=0.0) float pressedScale, @FloatRange(from=0.0) float selectedScale, @FloatRange(from=0.0) float disabledScale, @FloatRange(from=0.0) float focusedSelectedScale, @FloatRange(from=0.0) float focusedDisabledScale, @FloatRange(from=0.0) float pressedSelectedScale);
method public float getDisabledScale();
@@ -737,14 +741,14 @@
}
public final class NavigationDrawerKt {
- method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void ModalNavigationDrawer(kotlin.jvm.functions.Function1<? super androidx.tv.material3.DrawerValue,kotlin.Unit> drawerContent, optional androidx.compose.ui.Modifier modifier, optional androidx.tv.material3.DrawerState drawerState, optional androidx.compose.ui.graphics.Brush scrimBrush, kotlin.jvm.functions.Function0<kotlin.Unit> content);
- method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void NavigationDrawer(kotlin.jvm.functions.Function1<? super androidx.tv.material3.DrawerValue,kotlin.Unit> drawerContent, optional androidx.compose.ui.Modifier modifier, optional androidx.tv.material3.DrawerState drawerState, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void ModalNavigationDrawer(kotlin.jvm.functions.Function2<? super androidx.tv.material3.NavigationDrawerScope,? super androidx.tv.material3.DrawerValue,kotlin.Unit> drawerContent, optional androidx.compose.ui.Modifier modifier, optional androidx.tv.material3.DrawerState drawerState, optional androidx.compose.ui.graphics.Brush scrimBrush, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void NavigationDrawer(kotlin.jvm.functions.Function2<? super androidx.tv.material3.NavigationDrawerScope,? super androidx.tv.material3.DrawerValue,kotlin.Unit> drawerContent, optional androidx.compose.ui.Modifier modifier, optional androidx.tv.material3.DrawerState drawerState, kotlin.jvm.functions.Function0<kotlin.Unit> content);
method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static androidx.tv.material3.DrawerState rememberDrawerState(androidx.tv.material3.DrawerValue initialValue);
}
@SuppressCompatibility @androidx.tv.material3.ExperimentalTvMaterial3Api public interface NavigationDrawerScope {
- method public boolean isActivated();
- property public abstract boolean isActivated;
+ method public boolean getDoesNavigationDrawerHaveFocus();
+ property public abstract boolean doesNavigationDrawerHaveFocus;
}
@SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class NonInteractiveSurfaceColors {
diff --git a/tv/tv-material/src/androidTest/java/androidx/tv/material3/NavigationDrawerItemScreenshotTest.kt b/tv/tv-material/src/androidTest/java/androidx/tv/material3/NavigationDrawerItemScreenshotTest.kt
new file mode 100644
index 0000000..e98dd07
--- /dev/null
+++ b/tv/tv-material/src/androidTest/java/androidx/tv/material3/NavigationDrawerItemScreenshotTest.kt
@@ -0,0 +1,379 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.tv.material3
+
+import android.os.Build
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Favorite
+import androidx.compose.runtime.Composable
+import androidx.compose.testutils.assertAgainstGolden
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onChild
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.requestFocus
+import androidx.compose.ui.unit.dp
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.screenshot.AndroidXScreenshotTestRule
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@LargeTest
+@RunWith(Parameterized::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+@OptIn(ExperimentalTvMaterial3Api::class)
+class NavigationDrawerItemScreenshotTest(private val scheme: ColorSchemeWrapper) {
+ @get:Rule
+ val rule = createComposeRule()
+
+ @get:Rule
+ val screenshotRule = AndroidXScreenshotTestRule(TV_GOLDEN_MATERIAL3)
+
+ val wrapperModifier = Modifier
+ .testTag(NavigationDrawerItemWrapperTag)
+ .background(scheme.colorScheme.surface)
+ .padding(20.dp)
+
+ @Test
+ fun navigationDrawerItem_customColor() {
+ rule.setMaterialContent(scheme.colorScheme) {
+ DrawerScope {
+ NavigationDrawerItem(
+ selected = false,
+ onClick = {},
+ leadingContent = {
+ Icon(
+ imageVector = Icons.Filled.Favorite,
+ contentDescription = null,
+ modifier = Modifier.size(NavigationDrawerItemDefaults.IconSize)
+ )
+ },
+ colors = NavigationDrawerItemDefaults.colors(containerColor = Color.Red)
+ ) {
+ Text("Favourite")
+ }
+ }
+ }
+
+ assertAgainstGolden("navigationDrawerItem_${scheme.name}_customColor")
+ }
+
+ @Test
+ fun navigationDrawerItem_oneLine() {
+ rule.setMaterialContent(scheme.colorScheme) {
+ DrawerScope {
+ NavigationDrawerItem(
+ selected = false,
+ onClick = {},
+ leadingContent = {
+ Icon(
+ imageVector = Icons.Filled.Favorite,
+ contentDescription = null,
+ modifier = Modifier.size(NavigationDrawerItemDefaults.IconSize)
+ )
+ }
+ ) {
+ Text("Favourite")
+ }
+ }
+ }
+
+ assertAgainstGolden("navigationDrawerItem_${scheme.name}_oneLine")
+ }
+
+ @Test
+ fun navigationDrawerItem_twoLine() {
+ rule.setMaterialContent(scheme.colorScheme) {
+ DrawerScope {
+ NavigationDrawerItem(
+ selected = false,
+ onClick = {},
+ leadingContent = {
+ Icon(
+ imageVector = Icons.Filled.Favorite,
+ contentDescription = null,
+ modifier = Modifier.size(NavigationDrawerItemDefaults.IconSize)
+ )
+ },
+ supportingContent = { Text("You like this") }
+ ) {
+ Text("Favourite")
+ }
+ }
+ }
+
+ assertAgainstGolden("navigationDrawerItem_${scheme.name}_twoLine")
+ }
+
+ @Test
+ fun navigationDrawerItem_twoLine_focused() {
+ rule.setMaterialContent(scheme.colorScheme) {
+ DrawerScope {
+ NavigationDrawerItem(
+ selected = false,
+ onClick = {},
+ leadingContent = {
+ Icon(
+ imageVector = Icons.Filled.Favorite,
+ contentDescription = null,
+ modifier = Modifier.size(NavigationDrawerItemDefaults.IconSize)
+ )
+ },
+ supportingContent = { Text("You like this") }
+ ) {
+ Text("Favourite")
+ }
+ }
+ }
+
+ rule.onNodeWithTag(NavigationDrawerItemWrapperTag)
+ .onChild()
+ .requestFocus()
+ rule.waitForIdle()
+
+ assertAgainstGolden("navigationDrawerItem_${scheme.name}_twoLine_focused")
+ }
+
+ @Test
+ fun navigationDrawerItem_twoLine_disabled() {
+ rule.setMaterialContent(scheme.colorScheme) {
+ DrawerScope {
+ NavigationDrawerItem(
+ selected = false,
+ onClick = {},
+ enabled = false,
+ leadingContent = {
+ Icon(
+ imageVector = Icons.Filled.Favorite,
+ contentDescription = null,
+ modifier = Modifier.size(NavigationDrawerItemDefaults.IconSize)
+ )
+ },
+ supportingContent = { Text("You like this") }
+ ) {
+ Text("Favourite")
+ }
+ }
+ }
+
+ assertAgainstGolden("navigationDrawerItem_${scheme.name}_twoLine_disabled")
+ }
+
+ @Test
+ fun navigationDrawerItem_twoLine_focusedDisabled() {
+ rule.setMaterialContent(scheme.colorScheme) {
+ DrawerScope {
+ NavigationDrawerItem(
+ selected = false,
+ onClick = {},
+ enabled = false,
+ leadingContent = {
+ Icon(
+ imageVector = Icons.Filled.Favorite,
+ contentDescription = null,
+ modifier = Modifier.size(NavigationDrawerItemDefaults.IconSize)
+ )
+ },
+ supportingContent = { Text("You like this") }
+ ) {
+ Text("Favourite")
+ }
+ }
+ }
+
+ rule.onNodeWithTag(NavigationDrawerItemWrapperTag)
+ .onChild()
+ .requestFocus()
+ rule.waitForIdle()
+
+ assertAgainstGolden("navigationDrawerItem_${scheme.name}_twoLine_focusedDisabled")
+ }
+
+ @Test
+ fun navigationDrawerItem_twoLine_selected() {
+ rule.setMaterialContent(scheme.colorScheme) {
+ DrawerScope {
+ NavigationDrawerItem(
+ selected = true,
+ onClick = {},
+ leadingContent = {
+ Icon(
+ imageVector = Icons.Filled.Favorite,
+ contentDescription = null,
+ modifier = Modifier.size(NavigationDrawerItemDefaults.IconSize)
+ )
+ },
+ supportingContent = { Text("You like this") }
+ ) {
+ Text("Favourite")
+ }
+ }
+ }
+
+ assertAgainstGolden("navigationDrawerItem_${scheme.name}_twoLine_selected")
+ }
+
+ @Test
+ fun navigationDrawerItem_twoLine_focusedSelected() {
+ rule.setMaterialContent(scheme.colorScheme) {
+ DrawerScope {
+ NavigationDrawerItem(
+ selected = true,
+ onClick = {},
+ leadingContent = {
+ Icon(
+ imageVector = Icons.Filled.Favorite,
+ contentDescription = null,
+ modifier = Modifier.size(NavigationDrawerItemDefaults.IconSize)
+ )
+ },
+ supportingContent = { Text("You like this") }
+ ) {
+ Text("Favourite")
+ }
+ }
+ }
+
+ rule.onNodeWithTag(NavigationDrawerItemWrapperTag)
+ .onChild()
+ .requestFocus()
+ rule.waitForIdle()
+
+ assertAgainstGolden("navigationDrawerItem_${scheme.name}_twoLine_focusedSelected")
+ }
+
+ @Test
+ fun navigationDrawerItem_inactive() {
+ rule.setMaterialContent(scheme.colorScheme) {
+ DrawerScope(false) {
+ NavigationDrawerItem(
+ selected = false,
+ onClick = {},
+ leadingContent = {
+ Icon(
+ imageVector = Icons.Filled.Favorite,
+ contentDescription = null,
+ modifier = Modifier.size(NavigationDrawerItemDefaults.IconSize)
+ )
+ },
+ ) {
+ Text("Favourite")
+ }
+ }
+ }
+
+ assertAgainstGolden("navigationDrawerItem_${scheme.name}_inactive")
+ }
+
+ @Test
+ fun navigationDrawerItem_inactive_selected() {
+ rule.setMaterialContent(scheme.colorScheme) {
+ DrawerScope(false) {
+ NavigationDrawerItem(
+ selected = true,
+ onClick = {},
+ leadingContent = {
+ Icon(
+ imageVector = Icons.Filled.Favorite,
+ contentDescription = null,
+ modifier = Modifier.size(NavigationDrawerItemDefaults.IconSize)
+ )
+ },
+ ) {
+ Text("Favourite")
+ }
+ }
+ }
+
+ assertAgainstGolden("navigationDrawerItem_${scheme.name}_inactive_selected")
+ }
+
+ @Test
+ fun navigationDrawerItem_twoLine_withTrailingContent() {
+ rule.setMaterialContent(scheme.colorScheme) {
+ DrawerScope {
+ NavigationDrawerItem(
+ selected = false,
+ onClick = {},
+ leadingContent = {
+ Icon(
+ imageVector = Icons.Filled.Favorite,
+ contentDescription = null,
+ modifier = Modifier.size(NavigationDrawerItemDefaults.IconSize)
+ )
+ },
+ supportingContent = { Text("You like this") },
+ trailingContent = {
+ NavigationDrawerItemDefaults.TrailingBadge("NEW")
+ }
+ ) {
+ Text("Favourite")
+ }
+ }
+ }
+
+ assertAgainstGolden("navigationDrawerItem_${scheme.name}_twoLine_withTrailingContent")
+ }
+
+ private fun assertAgainstGolden(goldenName: String) {
+ rule.onNodeWithTag(NavigationDrawerItemWrapperTag)
+ .captureToImage()
+ .assertAgainstGolden(screenshotRule, goldenName)
+ }
+
+ // Provide the ColorScheme and their name parameter in a ColorSchemeWrapper.
+ // This makes sure that the default method name and the initial Scuba image generated
+ // name is as expected.
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun parameters() = arrayOf(
+ ColorSchemeWrapper("lightTheme", lightColorScheme()),
+ ColorSchemeWrapper("darkTheme", darkColorScheme()),
+ )
+ }
+
+ class ColorSchemeWrapper constructor(val name: String, val colorScheme: ColorScheme) {
+ override fun toString(): String {
+ return name
+ }
+ }
+
+ @Composable
+ private fun DrawerScope(
+ doesNavigationDrawerHaveFocus: Boolean = true,
+ content: @Composable NavigationDrawerScope.() -> Unit
+ ) {
+ Box(wrapperModifier) {
+ NavigationDrawerScopeImpl(doesNavigationDrawerHaveFocus).apply {
+ content()
+ }
+ }
+ }
+}
+
+private const val NavigationDrawerItemWrapperTag = "navigationDrawerItem_wrapper"
diff --git a/tv/tv-material/src/androidTest/java/androidx/tv/material3/NavigationDrawerItemTest.kt b/tv/tv-material/src/androidTest/java/androidx/tv/material3/NavigationDrawerItemTest.kt
new file mode 100644
index 0000000..74d9ee4
--- /dev/null
+++ b/tv/tv-material/src/androidTest/java/androidx/tv/material3/NavigationDrawerItemTest.kt
@@ -0,0 +1,443 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.tv.material3
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.key.Key
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.SemanticsActions
+import androidx.compose.ui.semantics.SemanticsProperties
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.SemanticsMatcher
+import androidx.compose.ui.test.assert
+import androidx.compose.ui.test.assertHasClickAction
+import androidx.compose.ui.test.assertHeightIsEqualTo
+import androidx.compose.ui.test.assertIsEnabled
+import androidx.compose.ui.test.assertIsEqualTo
+import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.assertWidthIsEqualTo
+import androidx.compose.ui.test.getUnclippedBoundsInRoot
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performKeyInput
+import androidx.compose.ui.test.pressKey
+import androidx.compose.ui.test.requestFocus
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.width
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import com.google.common.truth.Truth
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(
+ ExperimentalTestApi::class,
+ ExperimentalTvMaterial3Api::class
+)
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class NavigationDrawerItemTest {
+ @get:Rule
+ val rule = createComposeRule()
+
+ @Test
+ fun navigationDrawerItem_findByTagAndClick() {
+ var counter = 0
+ val onClick: () -> Unit = { ++counter }
+
+ rule.setContent {
+ DrawerScope {
+ NavigationDrawerItem(
+ selected = false,
+ onClick = onClick,
+ leadingContent = { },
+ modifier = Modifier.testTag(NavigationDrawerItemTag),
+ ) {
+ Text(text = "Test Text")
+ }
+ }
+ }
+
+ rule.onNodeWithTag(NavigationDrawerItemTag)
+ .requestFocus()
+ .performKeyInput { pressKey(Key.DirectionCenter) }
+ rule.runOnIdle {
+ Truth.assertThat(counter).isEqualTo(1)
+ }
+ }
+
+ @Test
+ fun navigationDrawerItem_clickIsIndependentBetweenItems() {
+ var openItemClickCounter = 0
+ val openItemOnClick: () -> Unit = { ++openItemClickCounter }
+ val openItemTag = "OpenItem"
+
+ var closeItemClickCounter = 0
+ val closeItemOnClick: () -> Unit = { ++closeItemClickCounter }
+ val closeItemTag = "CloseItem"
+
+ rule.setContent {
+ DrawerScope {
+ Column {
+ NavigationDrawerItem(
+ selected = false,
+ onClick = openItemOnClick,
+ leadingContent = { },
+ modifier = Modifier.testTag(openItemTag),
+ ) {
+ Text(text = "Test Text")
+ }
+ NavigationDrawerItem(
+ selected = false,
+ onClick = closeItemOnClick,
+ leadingContent = { },
+ modifier = Modifier.testTag(closeItemTag),
+ ) {
+ Text(text = "Test Text")
+ }
+ }
+ }
+ }
+
+ rule.onNodeWithTag(openItemTag)
+ .requestFocus()
+ .performKeyInput { pressKey(Key.DirectionCenter) }
+
+ rule.runOnIdle {
+ Truth.assertThat(openItemClickCounter).isEqualTo(1)
+ Truth.assertThat(closeItemClickCounter).isEqualTo(0)
+ }
+
+ rule.onNodeWithTag(closeItemTag)
+ .requestFocus()
+ .performKeyInput { pressKey(Key.DirectionCenter) }
+
+ rule.runOnIdle {
+ Truth.assertThat(openItemClickCounter).isEqualTo(1)
+ Truth.assertThat(closeItemClickCounter).isEqualTo(1)
+ }
+ }
+
+ @Test
+ fun navigationDrawerItem_longClickAction() {
+ var counter = 0
+ val onLongClick: () -> Unit = { ++counter }
+
+ rule.setContent {
+ DrawerScope {
+ NavigationDrawerItem(
+ selected = false,
+ onClick = { },
+ onLongClick = onLongClick,
+ leadingContent = { },
+ modifier = Modifier.testTag(NavigationDrawerItemTag),
+ ) {
+ Text(text = "Test Text")
+ }
+ }
+ }
+
+ rule.onNodeWithTag(NavigationDrawerItemTag)
+ .requestFocus()
+ .performLongKeyPress(rule, Key.DirectionCenter)
+ rule.runOnIdle {
+ Truth.assertThat(counter).isEqualTo(1)
+ }
+
+ rule.onNodeWithTag(NavigationDrawerItemTag)
+ .requestFocus()
+ .performLongKeyPress(rule, Key.DirectionCenter, count = 2)
+ rule.runOnIdle {
+ Truth.assertThat(counter).isEqualTo(3)
+ }
+ }
+
+ @Test
+ fun navigationDrawerItem_findByTagAndStateChangeCheck() {
+ var checkedState by mutableStateOf(true)
+ val onClick: () -> Unit = { checkedState = !checkedState }
+
+ rule.setContent {
+ DrawerScope {
+ NavigationDrawerItem(
+ selected = checkedState,
+ onClick = onClick,
+ leadingContent = { },
+ modifier = Modifier.testTag(NavigationDrawerItemTag),
+ ) {
+ Text(text = "Test Text")
+ }
+ }
+ }
+
+ rule.onNodeWithTag(NavigationDrawerItemTag)
+ .requestFocus()
+ .performKeyInput { pressKey(Key.DirectionCenter) }
+ rule.runOnIdle {
+ Truth.assertThat(!checkedState)
+ }
+ }
+
+ @Test
+ fun navigationDrawerItem_trailingContentPadding() {
+ val testTrailingContentTag = "TrailingIconTag"
+
+ rule.setContent {
+ DrawerScope {
+ NavigationDrawerItem(
+ selected = false,
+ onClick = {},
+ trailingContent = {
+ Box(
+ modifier = Modifier
+ .size(NavigationDrawerItemDefaults.IconSize)
+ .background(Color.Red)
+ .testTag(testTrailingContentTag)
+ )
+ },
+ leadingContent = { },
+ modifier = Modifier
+ .testTag(NavigationDrawerItemTag)
+ .border(1.dp, Color.Blue),
+ ) {
+ Text(
+ text = "Test Text",
+ modifier = Modifier
+ .testTag(NavigationDrawerItemTextTag)
+ .fillMaxWidth()
+ )
+ }
+ }
+ }
+
+ rule.waitForIdle()
+
+ val itemBounds = rule.onNodeWithTag(NavigationDrawerItemTag).getUnclippedBoundsInRoot()
+ val textBounds = rule.onNodeWithTag(
+ NavigationDrawerItemTextTag,
+ useUnmergedTree = true
+ ).getUnclippedBoundsInRoot()
+ val trailingContentBounds = rule
+ .onNodeWithTag(testTrailingContentTag, useUnmergedTree = true)
+ .getUnclippedBoundsInRoot()
+
+ (itemBounds.bottom - trailingContentBounds.bottom).assertIsEqualTo(
+ 16.dp,
+ "padding between the bottom of the trailing content and the bottom of the nav " +
+ "drawer item"
+ )
+
+ (itemBounds.right - trailingContentBounds.right).assertIsEqualTo(
+ 16.dp,
+ "padding between the end of the trailing content and the end of the nav drawer item"
+ )
+
+ (trailingContentBounds.left - textBounds.right).assertIsEqualTo(
+ 8.dp,
+ "padding between the start of the trailing content and the end of the text."
+ )
+ }
+
+ @Test
+ fun navigationDrawerItem_semantics() {
+ var selected by mutableStateOf(false)
+ rule.setContent {
+ DrawerScope {
+ NavigationDrawerItem(
+ selected = selected,
+ onClick = { selected = !selected },
+ leadingContent = { },
+ modifier = Modifier.testTag(NavigationDrawerItemTag),
+ ) {
+ Text(text = "Test Text")
+ }
+ }
+ }
+
+ rule.onNodeWithTag(NavigationDrawerItemTag)
+ .assertHasClickAction()
+ .assert(SemanticsMatcher.keyIsDefined(SemanticsProperties.Selected))
+ .assert(SemanticsMatcher.expectValue(SemanticsProperties.Selected, false))
+ .requestFocus()
+ .assertIsEnabled()
+ .performKeyInput { pressKey(Key.DirectionCenter) }
+ .assert(SemanticsMatcher.expectValue(SemanticsProperties.Selected, true))
+ Truth.assertThat(selected).isEqualTo(true)
+ }
+
+ @Test
+ fun navigationDrawerItem_longClickSemantics() {
+ var selected by mutableStateOf(false)
+ rule.setContent {
+ DrawerScope {
+ NavigationDrawerItem(
+ selected = selected,
+ onClick = {},
+ onLongClick = { selected = !selected },
+ leadingContent = { },
+ modifier = Modifier.testTag(NavigationDrawerItemTag),
+ ) {
+ Text(text = "Test Text")
+ }
+ }
+ }
+
+ rule.onNodeWithTag(NavigationDrawerItemTag)
+ .assertHasClickAction()
+ .assert(SemanticsMatcher.keyIsDefined(SemanticsActions.OnLongClick))
+ .assert(SemanticsMatcher.keyIsDefined(SemanticsProperties.Selected))
+ .assert(SemanticsMatcher.expectValue(SemanticsProperties.Selected, false))
+ .requestFocus()
+ .assertIsEnabled()
+ .performLongKeyPress(rule, Key.DirectionCenter)
+ .assert(SemanticsMatcher.expectValue(SemanticsProperties.Selected, true))
+ Truth.assertThat(selected).isEqualTo(true)
+ }
+
+ @Test
+ fun navigationDrawerItem_disabledSemantics() {
+ rule.setContent {
+ DrawerScope {
+ NavigationDrawerItem(
+ selected = false,
+ onClick = {},
+ enabled = false,
+ leadingContent = { },
+ modifier = Modifier.testTag(NavigationDrawerItemTag),
+ ) {
+ Text(text = "Test Text")
+ }
+ }
+ }
+
+ rule.onNodeWithTag(NavigationDrawerItemTag)
+ .assertIsNotEnabled()
+ }
+
+ @Test
+ fun navigationDrawerItem_canBeDisabled() {
+ rule.setContent {
+ var enabled by remember { mutableStateOf(true) }
+ DrawerScope {
+ NavigationDrawerItem(
+ selected = false,
+ onClick = { enabled = false },
+ leadingContent = { },
+ enabled = enabled,
+ modifier = Modifier.testTag(NavigationDrawerItemTag),
+ ) {
+ Text(text = "Test Text")
+ }
+ }
+ }
+ rule.onNodeWithTag(NavigationDrawerItemTag)
+ // Confirm the button starts off enabled, with a click action
+ .assertHasClickAction()
+ .assertIsEnabled()
+ .requestFocus()
+ .performKeyInput { pressKey(Key.DirectionCenter) }
+ // Then confirm it's disabled with click action after clicking it
+ .assertHasClickAction()
+ .assertIsNotEnabled()
+ }
+
+ @Test
+ fun navigationDrawerItem_oneLineHeight() {
+ val expectedHeightNoIcon = 56.dp
+
+ rule.setContent {
+ DrawerScope {
+ NavigationDrawerItem(
+ selected = false,
+ onClick = {},
+ leadingContent = { },
+ modifier = Modifier.testTag(NavigationDrawerItemTag),
+ ) {
+ Text(text = "Test Text")
+ }
+ }
+ }
+
+ rule.onNodeWithTag(NavigationDrawerItemTag).assertHeightIsEqualTo(expectedHeightNoIcon)
+ }
+
+ @Test
+ fun navigationDrawerItem_width() {
+ rule.setContent {
+ DrawerScope {
+ NavigationDrawerItem(
+ selected = false,
+ onClick = { },
+ leadingContent = { },
+ modifier = Modifier.testTag(NavigationDrawerItemTag),
+ ) {
+ Text(text = "Test Text")
+ }
+ }
+ }
+ rule.onNodeWithTag(NavigationDrawerItemTag)
+ .assertWidthIsEqualTo(rule.onRoot().getUnclippedBoundsInRoot().width)
+ }
+
+ @Test
+ fun navigationDrawerItem_widthInInactiveState() {
+ rule.setContent {
+ DrawerScope(false) {
+ NavigationDrawerItem(
+ selected = false,
+ onClick = { },
+ leadingContent = { },
+ modifier = Modifier.testTag(NavigationDrawerItemTag),
+ ) {
+ Text(text = "Test Text")
+ }
+ }
+ }
+ rule.onNodeWithTag(NavigationDrawerItemTag)
+ .assertWidthIsEqualTo(56.dp)
+ }
+}
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+@Composable
+private fun DrawerScope(
+ isActivated: Boolean = true,
+ content: @Composable NavigationDrawerScope.() -> Unit
+) {
+ Box {
+ NavigationDrawerScopeImpl(isActivated).apply {
+ content()
+ }
+ }
+}
+
+private const val NavigationDrawerItemTag = "NavigationDrawerItem"
+private const val NavigationDrawerItemTextTag = "NavigationDrawerItemText"
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawer.kt b/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawer.kt
index 049ddf1..320fe9e 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawer.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawer.kt
@@ -79,7 +79,7 @@
@ExperimentalTvMaterial3Api
@Composable
fun ModalNavigationDrawer(
- drawerContent: @Composable (DrawerValue) -> Unit,
+ drawerContent: @Composable NavigationDrawerScope.(DrawerValue) -> Unit,
modifier: Modifier = Modifier,
drawerState: DrawerState = rememberDrawerState(DrawerValue.Closed),
scrimBrush: Brush = SolidColor(LocalColorScheme.current.scrim.copy(alpha = 0.5f)),
@@ -155,7 +155,7 @@
@ExperimentalTvMaterial3Api
@Composable
fun NavigationDrawer(
- drawerContent: @Composable (DrawerValue) -> Unit,
+ drawerContent: @Composable NavigationDrawerScope.(DrawerValue) -> Unit,
modifier: Modifier = Modifier,
drawerState: DrawerState = rememberDrawerState(DrawerValue.Closed),
content: @Composable () -> Unit
@@ -237,7 +237,7 @@
modifier: Modifier = Modifier,
drawerState: DrawerState = remember { DrawerState() },
sizeAnimationFinishedListener: ((initialValue: IntSize, targetValue: IntSize) -> Unit)? = null,
- content: @Composable (DrawerValue) -> Unit
+ content: @Composable NavigationDrawerScope.(DrawerValue) -> Unit
) {
// indicates that the drawer has been set to its initial state and has grabbed focus if
// necessary. Controls whether focus is used to decide the state of the drawer going forward.
@@ -269,7 +269,11 @@
}
.focusGroup()
- Box(modifier = internalModifier) { content.invoke(drawerState.currentValue) }
+ Box(modifier = internalModifier) {
+ NavigationDrawerScopeImpl(drawerState.currentValue == DrawerValue.Open).apply {
+ content(drawerState.currentValue)
+ }
+ }
}
private const val ClosedDrawerWidth = 80
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawerItem.kt b/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawerItem.kt
new file mode 100644
index 0000000..967d1c2f
--- /dev/null
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawerItem.kt
@@ -0,0 +1,241 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.tv.material3
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.core.animateDpAsState
+import androidx.compose.foundation.interaction.Interaction
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.layout
+import androidx.compose.ui.unit.Dp
+
+/**
+ * TV Material Design navigation drawer item.
+ *
+ * A [NavigationDrawerItem] represents a destination within drawers, either [NavigationDrawer] or
+ * [ModalNavigationDrawer]
+ *
+ * @sample androidx.tv.samples.SampleNavigationDrawer
+ * @sample androidx.tv.samples.SampleModalNavigationDrawerWithSolidScrim
+ * @sample androidx.tv.samples.SampleModalNavigationDrawerWithGradientScrim
+ *
+ * @param selected defines whether this composable is selected or not
+ * @param onClick called when this composable is clicked
+ * @param leadingContent the leading content of the list item
+ * @param modifier to be applied to the list item
+ * @param enabled controls the enabled state of this composable. When `false`, this component will
+ * not respond to user input, and it will appear visually disabled and disabled to accessibility
+ * services
+ * @param onLongClick called when this composable is long clicked (long-pressed)
+ * @param supportingContent the content displayed below the headline content
+ * @param trailingContent the trailing meta badge or icon
+ * @param tonalElevation the tonal elevation of this composable
+ * @param shape defines the shape of Composable's container in different interaction states
+ * @param colors defines the background and content colors used in the composable
+ * for different interaction states
+ * @param scale defines the size of the composable relative to its original size in
+ * different interaction states
+ * @param border defines a border around the composable in different interaction states
+ * @param glow defines a shadow to be shown behind the composable for different interaction states
+ * @param interactionSource the [MutableInteractionSource] representing the stream of
+ * [Interaction]s for this component. You can create and pass in your own [remember]ed instance
+ * to observe [Interaction]s and customize the appearance / behavior of this composable in different
+ * states
+ * @param content main content of this composable
+ */
+@ExperimentalTvMaterial3Api // TODO (b/263353219): Remove this before launching beta
+@Composable
+fun NavigationDrawerScope.NavigationDrawerItem(
+ selected: Boolean,
+ onClick: () -> Unit,
+ leadingContent: @Composable () -> Unit,
+ modifier: Modifier = Modifier,
+ enabled: Boolean = true,
+ onLongClick: (() -> Unit)? = null,
+ supportingContent: (@Composable () -> Unit)? = null,
+ trailingContent: (@Composable () -> Unit)? = null,
+ tonalElevation: Dp = NavigationDrawerItemDefaults.NavigationDrawerItemElevation,
+ shape: NavigationDrawerItemShape = NavigationDrawerItemDefaults.shape(),
+ colors: NavigationDrawerItemColors = NavigationDrawerItemDefaults.colors(),
+ scale: NavigationDrawerItemScale = NavigationDrawerItemScale.None,
+ border: NavigationDrawerItemBorder = NavigationDrawerItemDefaults.border(),
+ glow: NavigationDrawerItemGlow = NavigationDrawerItemDefaults.glow(),
+ interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
+ content: @Composable () -> Unit,
+) {
+ val animatedWidth by animateDpAsState(
+ targetValue = if (doesNavigationDrawerHaveFocus) {
+ NavigationDrawerItemDefaults.ExpandedDrawerItemWidth
+ } else {
+ NavigationDrawerItemDefaults.CollapsedDrawerItemWidth
+ },
+ label = "NavigationDrawerItem width open/closed state of the drawer item"
+ )
+ val navDrawerItemHeight = if (supportingContent == null) {
+ NavigationDrawerItemDefaults.ContainerHeightOneLine
+ } else {
+ NavigationDrawerItemDefaults.ContainerHeightTwoLine
+ }
+ ListItem(
+ selected = selected,
+ onClick = onClick,
+ headlineContent = {
+ AnimatedVisibility(
+ visible = doesNavigationDrawerHaveFocus,
+ enter = NavigationDrawerItemDefaults.ContentAnimationEnter,
+ exit = NavigationDrawerItemDefaults.ContentAnimationExit,
+ ) {
+ content()
+ }
+ },
+ leadingContent = {
+ Box(Modifier.size(NavigationDrawerItemDefaults.IconSize)) {
+ leadingContent()
+ }
+ },
+ trailingContent = if (trailingContent == null) null else {
+ {
+ AnimatedVisibility(
+ visible = doesNavigationDrawerHaveFocus,
+ enter = NavigationDrawerItemDefaults.ContentAnimationEnter,
+ exit = NavigationDrawerItemDefaults.ContentAnimationExit,
+ ) {
+ trailingContent()
+ }
+ }
+ },
+ supportingContent = if (supportingContent == null) null else {
+ {
+ AnimatedVisibility(
+ visible = doesNavigationDrawerHaveFocus,
+ enter = NavigationDrawerItemDefaults.ContentAnimationEnter,
+ exit = NavigationDrawerItemDefaults.ContentAnimationExit,
+ ) {
+ supportingContent()
+ }
+ }
+ },
+ modifier = modifier
+ .layout { measurable, constraints ->
+ val width = animatedWidth.roundToPx()
+ val height = navDrawerItemHeight.roundToPx()
+ val placeable = measurable.measure(
+ constraints.copy(
+ minWidth = width,
+ maxWidth = width,
+ minHeight = height,
+ maxHeight = height,
+ )
+ )
+ layout(placeable.width, placeable.height) {
+ placeable.place(0, 0)
+ }
+ },
+ enabled = enabled,
+ onLongClick = onLongClick,
+ tonalElevation = tonalElevation,
+ shape = shape.toToggleableListItemShape(),
+ colors = colors.toToggleableListItemColors(doesNavigationDrawerHaveFocus),
+ scale = scale.toToggleableListItemScale(),
+ border = border.toToggleableListItemBorder(),
+ glow = glow.toToggleableListItemGlow(),
+ interactionSource = interactionSource,
+ )
+}
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+@Composable
+private fun NavigationDrawerItemShape.toToggleableListItemShape() =
+ ListItemDefaults.shape(
+ shape = shape,
+ focusedShape = focusedShape,
+ pressedShape = pressedShape,
+ selectedShape = selectedShape,
+ disabledShape = disabledShape,
+ focusedSelectedShape = focusedSelectedShape,
+ focusedDisabledShape = focusedDisabledShape,
+ pressedSelectedShape = pressedSelectedShape,
+ )
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+@Composable
+private fun NavigationDrawerItemColors.toToggleableListItemColors(
+ doesNavigationDrawerHaveFocus: Boolean
+) =
+ ListItemDefaults.colors(
+ containerColor = containerColor,
+ contentColor = if (doesNavigationDrawerHaveFocus) contentColor else inactiveContentColor,
+ focusedContainerColor = focusedContainerColor,
+ focusedContentColor = focusedContentColor,
+ pressedContainerColor = pressedContainerColor,
+ pressedContentColor = pressedContentColor,
+ selectedContainerColor = selectedContainerColor,
+ selectedContentColor = selectedContentColor,
+ disabledContainerColor = disabledContainerColor,
+ disabledContentColor =
+ if (doesNavigationDrawerHaveFocus) disabledContentColor else disabledInactiveContentColor,
+ focusedSelectedContainerColor = focusedSelectedContainerColor,
+ focusedSelectedContentColor = focusedSelectedContentColor,
+ pressedSelectedContainerColor = pressedSelectedContainerColor,
+ pressedSelectedContentColor = pressedSelectedContentColor,
+ )
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+@Composable
+private fun NavigationDrawerItemScale.toToggleableListItemScale() =
+ ListItemDefaults.scale(
+ scale = scale,
+ focusedScale = focusedScale,
+ pressedScale = pressedScale,
+ selectedScale = selectedScale,
+ disabledScale = disabledScale,
+ focusedSelectedScale = focusedSelectedScale,
+ focusedDisabledScale = focusedDisabledScale,
+ pressedSelectedScale = pressedSelectedScale,
+ )
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+@Composable
+private fun NavigationDrawerItemBorder.toToggleableListItemBorder() =
+ ListItemDefaults.border(
+ border = border,
+ focusedBorder = focusedBorder,
+ pressedBorder = pressedBorder,
+ selectedBorder = selectedBorder,
+ disabledBorder = disabledBorder,
+ focusedSelectedBorder = focusedSelectedBorder,
+ focusedDisabledBorder = focusedDisabledBorder,
+ pressedSelectedBorder = pressedSelectedBorder,
+ )
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+@Composable
+private fun NavigationDrawerItemGlow.toToggleableListItemGlow() =
+ ListItemDefaults.glow(
+ glow = glow,
+ focusedGlow = focusedGlow,
+ pressedGlow = pressedGlow,
+ selectedGlow = selectedGlow,
+ focusedSelectedGlow = focusedSelectedGlow,
+ pressedSelectedGlow = pressedSelectedGlow,
+ )
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawerScope.kt b/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawerScope.kt
index ccdcec7..27dccf0 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawerScope.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawerScope.kt
@@ -25,10 +25,10 @@
/**
* Whether any item within the [NavigationDrawer] or [ModalNavigationDrawer] is focused
*/
- val isActivated: Boolean
+ val doesNavigationDrawerHaveFocus: Boolean
}
@OptIn(ExperimentalTvMaterial3Api::class)
-internal class NavigationDrawerScopeImpl constructor(
- override val isActivated: Boolean
+internal class NavigationDrawerScopeImpl(
+ override val doesNavigationDrawerHaveFocus: Boolean
) : NavigationDrawerScope
diff --git a/wear/compose/compose-foundation/src/main/baseline-prof.txt b/wear/compose/compose-foundation/src/main/baseline-prof.txt
index 00329b5..edfde2d 100644
--- a/wear/compose/compose-foundation/src/main/baseline-prof.txt
+++ b/wear/compose/compose-foundation/src/main/baseline-prof.txt
@@ -30,21 +30,28 @@
SPLandroidx/wear/compose/foundation/ExpandableItemsDefaults;->**(**)**
HSPLandroidx/wear/compose/foundation/ExpandableKt**->**(**)**
HSPLandroidx/wear/compose/foundation/ExpandableState;->**(**)**
-PLandroidx/wear/compose/foundation/FocusNode;->**(**)**
-HPLandroidx/wear/compose/foundation/HierarchicalFocusCoordinatorKt**->**(**)**
-PLandroidx/wear/compose/foundation/InternalMutatorMutex;->**(**)**
+SPLandroidx/wear/compose/foundation/FocusNode;->**(**)**
+HSPLandroidx/wear/compose/foundation/HierarchicalFocusCoordinatorKt**->**(**)**
+SPLandroidx/wear/compose/foundation/InternalMutatorMutex;->**(**)**
+HSPLandroidx/wear/compose/foundation/Modifiers;->**(**)**
HSPLandroidx/wear/compose/foundation/PaddingWrapper;->**(**)**
HSPLandroidx/wear/compose/foundation/PartialLayoutInfo;->**(**)**
Landroidx/wear/compose/foundation/ReduceMotion;
+HSPLandroidx/wear/compose/foundation/ResourcesKt**->**(**)**
+PLandroidx/wear/compose/foundation/RevealActionType;->**(**)**
HPLandroidx/wear/compose/foundation/RevealScopeImpl;->**(**)**
HPLandroidx/wear/compose/foundation/RevealState;->**(**)**
HPLandroidx/wear/compose/foundation/RevealValue;->**(**)**
-HPLandroidx/wear/compose/foundation/SwipeAnchorsModifier;->**(**)**
+HSPLandroidx/wear/compose/foundation/SwipeAnchorsModifier;->**(**)**
+HSPLandroidx/wear/compose/foundation/SwipeToDismissBoxKt**->**(**)**
+HSPLandroidx/wear/compose/foundation/SwipeToDismissBoxState;->**(**)**
+SPLandroidx/wear/compose/foundation/SwipeToDismissKeys;->**(**)**
+SPLandroidx/wear/compose/foundation/SwipeToDismissValue;->**(**)**
PLandroidx/wear/compose/foundation/SwipeToRevealDefaults;->**(**)**
HPLandroidx/wear/compose/foundation/SwipeToRevealKt**->**(**)**
-PLandroidx/wear/compose/foundation/SwipeableV2Defaults;->**(**)**
-HPLandroidx/wear/compose/foundation/SwipeableV2Kt**->**(**)**
-HPLandroidx/wear/compose/foundation/SwipeableV2State;->**(**)**
+SPLandroidx/wear/compose/foundation/SwipeableV2Defaults;->**(**)**
+HSPLandroidx/wear/compose/foundation/SwipeableV2Kt**->**(**)**
+HSPLandroidx/wear/compose/foundation/SwipeableV2State;->**(**)**
SPLandroidx/wear/compose/foundation/lazy/AutoCenteringParams;->**(**)**
HSPLandroidx/wear/compose/foundation/lazy/CombinedPaddingValues;->**(**)**
HSPLandroidx/wear/compose/foundation/lazy/DefaultScalingLazyListItemInfo;->**(**)**
diff --git a/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/MaterialTest.kt b/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/MaterialTest.kt
index 3cd42b0..8e2e2bc 100644
--- a/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/MaterialTest.kt
+++ b/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/MaterialTest.kt
@@ -16,7 +16,9 @@
package androidx.wear.compose.material
import android.graphics.Bitmap
+import android.os.Build
import android.util.Log
+import androidx.annotation.RequiresApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
@@ -25,6 +27,8 @@
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Add
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.testutils.assertAgainstGolden
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Rect
@@ -33,10 +37,12 @@
import androidx.compose.ui.graphics.asAndroidBitmap
import androidx.compose.ui.graphics.toPixelMap
import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.SemanticsActions
import androidx.compose.ui.semantics.SemanticsNode
import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.captureToImage
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
@@ -45,11 +51,13 @@
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpRect
+import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.height
import androidx.compose.ui.unit.isUnspecified
import androidx.compose.ui.unit.toSize
import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.screenshot.AndroidXScreenshotTestRule
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
@@ -209,6 +217,25 @@
return this
}
+@RequiresApi(Build.VERSION_CODES.O)
+internal fun ComposeContentTestRule.verifyScreenshot(
+ screenshotRule: AndroidXScreenshotTestRule,
+ methodName: String,
+ testTag: String = TEST_TAG,
+ layoutDirection: LayoutDirection = LayoutDirection.Ltr,
+ content: @Composable () -> Unit
+) {
+ setContentWithTheme {
+ CompositionLocalProvider(LocalLayoutDirection provides layoutDirection) {
+ content()
+ }
+ }
+
+ onNodeWithTag(testTag)
+ .captureToImage()
+ .assertAgainstGolden(screenshotRule, methodName)
+}
+
/**
* Asserts that the layout of this node has height equal to [expectedHeight].
*
diff --git a/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/SwipeToRevealScreenshotTest.kt b/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/SwipeToRevealScreenshotTest.kt
new file mode 100644
index 0000000..9622c31
--- /dev/null
+++ b/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/SwipeToRevealScreenshotTest.kt
@@ -0,0 +1,234 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material
+
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.screenshot.AndroidXScreenshotTestRule
+import androidx.wear.compose.foundation.ExperimentalWearFoundationApi
+import androidx.wear.compose.foundation.RevealActionType
+import androidx.wear.compose.foundation.RevealState
+import androidx.wear.compose.foundation.RevealValue
+import androidx.wear.compose.foundation.rememberRevealState
+import kotlinx.coroutines.launch
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TestName
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+@RequiresApi(Build.VERSION_CODES.O)
+@OptIn(ExperimentalWearMaterialApi::class, ExperimentalWearFoundationApi::class)
+class SwipeToRevealScreenshotTest {
+ @get:Rule
+ val rule = createComposeRule()
+
+ @get:Rule
+ val screenshotRule = AndroidXScreenshotTestRule(SCREENSHOT_GOLDEN_PATH)
+
+ @get:Rule
+ val testName = TestName()
+
+ @Test
+ fun swipeToRevealCard_singleAction() {
+ rule.verifyScreenshot(
+ screenshotRule = screenshotRule,
+ methodName = testName.methodName
+ ) {
+ swipeToRevealCard(
+ revealState = rememberRevealState(initialValue = RevealValue.Revealing),
+ secondaryAction = null
+ )
+ }
+ }
+
+ @Test
+ fun swipeToRevealChip_singleAction() {
+ rule.verifyScreenshot(
+ screenshotRule = screenshotRule,
+ methodName = testName.methodName
+ ) {
+ swipeToRevealChip(
+ revealState = rememberRevealState(initialValue = RevealValue.Revealing),
+ secondaryAction = null
+ )
+ }
+ }
+
+ @Test
+ fun swipeToRevealCard_twoActions() {
+ rule.verifyScreenshot(
+ screenshotRule = screenshotRule,
+ methodName = testName.methodName
+ ) {
+ swipeToRevealCard(
+ revealState = rememberRevealState(initialValue = RevealValue.Revealing)
+ )
+ }
+ }
+
+ @Test
+ fun swipeToRevealChip_twoActions() {
+ rule.verifyScreenshot(
+ screenshotRule = screenshotRule,
+ methodName = testName.methodName
+ ) {
+ swipeToRevealChip(
+ revealState = rememberRevealState(initialValue = RevealValue.Revealing)
+ )
+ }
+ }
+
+ @Test
+ fun swipeToRevealChip_undoPrimaryAction() {
+ rule.verifyScreenshot(
+ screenshotRule = screenshotRule,
+ methodName = testName.methodName
+ ) {
+ swipeToRevealChip(
+ revealState = rememberRevealState(initialValue = RevealValue.Revealed)
+ )
+ }
+ }
+
+ @Test
+ fun swipeToRevealCard_undoPrimaryAction() {
+ rule.verifyScreenshot(
+ screenshotRule = screenshotRule,
+ methodName = testName.methodName
+ ) {
+ swipeToRevealCard(
+ revealState = rememberRevealState(initialValue = RevealValue.Revealed)
+ )
+ }
+ }
+
+ @Test
+ fun swipeToRevealChip_undoSecondaryAction() {
+ rule.verifyScreenshot(
+ screenshotRule = screenshotRule,
+ methodName = testName.methodName
+ ) {
+ val revealState = rememberRevealState()
+ val coroutineScope = rememberCoroutineScope()
+ coroutineScope.launch { revealState.animateTo(RevealValue.Revealed) }
+ revealState.lastActionType = RevealActionType.SecondaryAction
+ swipeToRevealChip(
+ revealState = revealState
+ )
+ }
+ }
+
+ @Test
+ fun swipeToRevealCard_undoSecondaryAction() {
+ rule.verifyScreenshot(
+ screenshotRule = screenshotRule,
+ methodName = testName.methodName
+ ) {
+ val revealState = rememberRevealState()
+ val coroutineScope = rememberCoroutineScope()
+ coroutineScope.launch { revealState.animateTo(RevealValue.Revealed) }
+ revealState.lastActionType = RevealActionType.SecondaryAction
+ swipeToRevealCard(
+ revealState = revealState
+ )
+ }
+ }
+
+ @Composable
+ private fun swipeToRevealCard(
+ revealState: RevealState = rememberRevealState(),
+ secondaryAction: SwipeToRevealAction? = SwipeToRevealDefaults.secondaryAction(
+ icon = { Icon(SwipeToRevealDefaults.MoreOptions, "More Options") }
+ ),
+ undoPrimaryAction: SwipeToRevealAction? = SwipeToRevealDefaults.undoAction(
+ label = { Text("Undo") }
+ ),
+ undoSecondaryAction: SwipeToRevealAction? = SwipeToRevealDefaults.undoAction(
+ label = { Text("Undo") }
+ )
+ ) {
+ Box(modifier = Modifier.fillMaxSize().background(MaterialTheme.colors.background)) {
+ SwipeToRevealCard(
+ modifier = Modifier.testTag(TEST_TAG),
+ primaryAction = SwipeToRevealDefaults.primaryAction(
+ icon = { Icon(SwipeToRevealDefaults.Delete, "Delete") },
+ label = { Text("Delete") }
+ ),
+ secondaryAction = secondaryAction,
+ undoPrimaryAction = undoPrimaryAction,
+ undoSecondaryAction = undoSecondaryAction,
+ revealState = revealState
+ ) {
+ TitleCard(
+ onClick = { /*TODO*/ },
+ title = { Text("Title of card") },
+ time = { Text("now") },
+ ) {
+ Text("Swipe To Reveal - Card")
+ }
+ }
+ }
+ }
+
+ @Composable
+ private fun swipeToRevealChip(
+ revealState: RevealState = rememberRevealState(),
+ secondaryAction: SwipeToRevealAction? = SwipeToRevealDefaults.secondaryAction(
+ icon = { Icon(SwipeToRevealDefaults.MoreOptions, "More Options") }
+ ),
+ undoPrimaryAction: SwipeToRevealAction? = SwipeToRevealDefaults.undoAction(
+ label = { Text("Undo") }
+ ),
+ undoSecondaryAction: SwipeToRevealAction? = SwipeToRevealDefaults.undoAction(
+ label = { Text("Undo") }
+ )
+ ) {
+ Box(modifier = Modifier.fillMaxSize().background(MaterialTheme.colors.background)) {
+ SwipeToRevealChip(
+ modifier = Modifier.testTag(TEST_TAG),
+ primaryAction = SwipeToRevealDefaults.primaryAction(
+ icon = { Icon(SwipeToRevealDefaults.Delete, "Delete") },
+ label = { Text("Delete") }
+ ),
+ secondaryAction = secondaryAction,
+ undoPrimaryAction = undoPrimaryAction,
+ undoSecondaryAction = undoSecondaryAction,
+ revealState = revealState
+ ) {
+ Chip(
+ onClick = { /* onClick handler for chip */ },
+ colors = ChipDefaults.primaryChipColors(),
+ border = ChipDefaults.outlinedChipBorder()
+ ) {
+ Text("Swipe To Reveal - Chip")
+ }
+ }
+ }
+ }
+}
diff --git a/wear/compose/compose-material/src/main/baseline-prof.txt b/wear/compose/compose-material/src/main/baseline-prof.txt
index de41148..8c7b94c 100644
--- a/wear/compose/compose-material/src/main/baseline-prof.txt
+++ b/wear/compose/compose-material/src/main/baseline-prof.txt
@@ -67,7 +67,7 @@
HPLandroidx/wear/compose/material/PlaceholderKt**->**(**)**
PLandroidx/wear/compose/material/PlaceholderModifier;->**(**)**
HPLandroidx/wear/compose/material/PlaceholderShimmerModifier;->**(**)**
-PLandroidx/wear/compose/material/PlaceholderStage;->**(**)**
+HPLandroidx/wear/compose/material/PlaceholderStage;->**(**)**
HPLandroidx/wear/compose/material/PlaceholderState;->**(**)**
HSPLandroidx/wear/compose/material/PositionIndicatorAlignment;->**(**)**
HSPLandroidx/wear/compose/material/PositionIndicatorKt**->**(**)**
@@ -146,7 +146,12 @@
HSPLandroidx/wear/compose/materialcore/IconKt**->**(**)**
HPLandroidx/wear/compose/materialcore/RangeDefaults;->**(**)**
PLandroidx/wear/compose/materialcore/RangeIcons;->**(**)**
+HPLandroidx/wear/compose/materialcore/RepeatableClickableKt**->**(**)**
+HSPLandroidx/wear/compose/materialcore/ResourcesKt**->**(**)**
+HPLandroidx/wear/compose/materialcore/SelectionControlsKt**->**(**)**
+PLandroidx/wear/compose/materialcore/SelectionStage;->**(**)**
HPLandroidx/wear/compose/materialcore/SliderKt**->**(**)**
PLandroidx/wear/compose/materialcore/StepperDefaults;->**(**)**
HPLandroidx/wear/compose/materialcore/StepperKt**->**(**)**
HSPLandroidx/wear/compose/materialcore/TextKt**->**(**)**
+HSPLandroidx/wear/compose/materialcore/ToggleButtonKt**->**(**)**
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconToggleButtonTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconToggleButtonTest.kt
index 0c068d7..e8d1930 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconToggleButtonTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconToggleButtonTest.kt
@@ -425,8 +425,8 @@
status = Status.Disabled,
checked = false,
colors = { IconButtonDefaults.iconToggleButtonColors() },
- containerColor = { MaterialTheme.colorScheme.surface },
- contentColor = { MaterialTheme.colorScheme.onSurfaceVariant }
+ containerColor = { MaterialTheme.colorScheme.surface.toDisabledColor() },
+ contentColor = { MaterialTheme.colorScheme.onSurfaceVariant.toDisabledColor() }
)
@RequiresApi(Build.VERSION_CODES.O)
@@ -436,8 +436,8 @@
status = Status.Disabled,
checked = true,
colors = { IconButtonDefaults.iconToggleButtonColors() },
- containerColor = { MaterialTheme.colorScheme.primary },
- contentColor = { MaterialTheme.colorScheme.onPrimary },
+ containerColor = { MaterialTheme.colorScheme.primary.toDisabledColor() },
+ contentColor = { MaterialTheme.colorScheme.onPrimary.toDisabledColor() },
)
@RequiresApi(Build.VERSION_CODES.O)
@@ -523,11 +523,11 @@
colors = {
IconButtonDefaults.iconToggleButtonColors(
// Apply the content color override for the content alpha to be applied
- checkedContainerColor = overrideColor
+ disabledCheckedContainerColor = overrideColor
)
},
containerColor = { overrideColor },
- contentColor = { MaterialTheme.colorScheme.onPrimary }
+ contentColor = { MaterialTheme.colorScheme.onPrimary.toDisabledColor() }
)
}
@@ -542,10 +542,10 @@
colors = {
IconButtonDefaults.iconToggleButtonColors(
// Apply the content color override for the content alpha to be applied
- checkedContentColor = overrideColor
+ disabledCheckedContentColor = overrideColor
)
},
- containerColor = { MaterialTheme.colorScheme.primary },
+ containerColor = { MaterialTheme.colorScheme.primary.toDisabledColor() },
contentColor = { overrideColor }
)
}
@@ -561,11 +561,11 @@
colors = {
IconButtonDefaults.iconToggleButtonColors(
// Apply the content color override for the content alpha to be applied
- uncheckedContainerColor = overrideColor
+ disabledUncheckedContainerColor = overrideColor
)
},
containerColor = { overrideColor },
- contentColor = { MaterialTheme.colorScheme.onSurfaceVariant }
+ contentColor = { MaterialTheme.colorScheme.onSurfaceVariant.toDisabledColor() }
)
}
@@ -580,11 +580,11 @@
colors = {
IconButtonDefaults.iconToggleButtonColors(
// Apply the content color override for the content alpha to be applied
- uncheckedContentColor = overrideColor
+ disabledUncheckedContentColor = overrideColor
)
},
contentColor = { overrideColor },
- containerColor = { MaterialTheme.colorScheme.surface }
+ containerColor = { MaterialTheme.colorScheme.surface.toDisabledColor() }
)
}
@@ -618,7 +618,9 @@
onCheckedChange = {},
enabled = false,
content = { TestImage() },
- modifier = Modifier.testTag(TEST_TAG).semantics { role = overrideRole }
+ modifier = Modifier
+ .testTag(TEST_TAG)
+ .semantics { role = overrideRole }
)
}
@@ -639,7 +641,6 @@
contentColor: @Composable () -> Color,
) {
verifyColors(
- status = status,
expectedContainerColor = containerColor,
expectedContentColor = contentColor,
content = {
@@ -705,10 +706,8 @@
@RequiresApi(Build.VERSION_CODES.O)
private fun ComposeContentTestRule.verifyColors(
- status: Status,
expectedContainerColor: @Composable () -> Color,
expectedContentColor: @Composable () -> Color,
- applyAlphaForDisabled: Boolean = true,
content: @Composable () -> Color
) {
val testBackgroundColor = Color.White
@@ -717,17 +716,8 @@
var actualContentColor = Color.Transparent
setContentWithTheme {
finalExpectedContainerColor =
- if (status.enabled() || !applyAlphaForDisabled) {
- expectedContainerColor()
- } else {
- expectedContainerColor().copy(ContentAlpha.disabled)
- }.compositeOver(testBackgroundColor)
- finalExpectedContent =
- if (status.enabled() || !applyAlphaForDisabled) {
- expectedContentColor()
- } else {
- expectedContentColor().copy(ContentAlpha.disabled)
- }
+ expectedContainerColor().compositeOver(testBackgroundColor)
+ finalExpectedContent = expectedContentColor()
Box(
Modifier
.fillMaxSize()
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/IconButton.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/IconButton.kt
index 71aee71..8755ede 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/IconButton.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/IconButton.kt
@@ -33,6 +33,7 @@
import androidx.wear.compose.material3.tokens.FilledIconButtonTokens
import androidx.wear.compose.material3.tokens.FilledTonalIconButtonTokens
import androidx.wear.compose.material3.tokens.IconButtonTokens
+import androidx.wear.compose.material3.tokens.IconToggleButtonTokens
import androidx.wear.compose.material3.tokens.OutlinedIconButtonTokens
/**
@@ -317,7 +318,6 @@
shape = shape,
content = provideScopeContent(
colors.contentColor(enabled = enabled, checked = checked),
- MaterialTheme.typography.labelMedium,
content
)
)
@@ -475,14 +475,19 @@
*/
@Composable
fun iconToggleButtonColors(
- checkedContainerColor: Color = MaterialTheme.colorScheme.primary,
- checkedContentColor: Color = MaterialTheme.colorScheme.onPrimary,
- uncheckedContainerColor: Color = MaterialTheme.colorScheme.surface,
- uncheckedContentColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
- disabledCheckedContainerColor: Color = checkedContainerColor.toDisabledColor(),
- disabledCheckedContentColor: Color = checkedContentColor.toDisabledColor(),
- disabledUncheckedContainerColor: Color = uncheckedContainerColor.toDisabledColor(),
- disabledUncheckedContentColor: Color = uncheckedContentColor.toDisabledColor(),
+ checkedContainerColor: Color = IconToggleButtonTokens.CheckedContainerColor.value,
+ checkedContentColor: Color = IconToggleButtonTokens.CheckedContentColor.value,
+ uncheckedContainerColor: Color = IconToggleButtonTokens.UncheckedContainerColor.value,
+ uncheckedContentColor: Color = IconToggleButtonTokens.UncheckedContentColor.value,
+ disabledCheckedContainerColor: Color = IconToggleButtonTokens.DisabledCheckedContainerColor
+ .value.toDisabledColor(IconToggleButtonTokens.DisabledCheckedContainerOpacity),
+ disabledCheckedContentColor: Color = IconToggleButtonTokens.DisabledCheckedContentColor
+ .value.toDisabledColor(IconToggleButtonTokens.DisabledCheckedContentOpacity),
+ disabledUncheckedContainerColor: Color = IconToggleButtonTokens
+ .DisabledUncheckedContainerColor.value
+ .toDisabledColor(IconToggleButtonTokens.DisabledUncheckedContainerOpacity),
+ disabledUncheckedContentColor: Color = IconToggleButtonTokens.DisabledUncheckedContentColor
+ .value.toDisabledColor(IconToggleButtonTokens.DisabledUncheckedContentOpacity),
): ToggleButtonColors {
return ToggleButtonColors(
checkedContainerColor = checkedContainerColor,
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/IconToggleButtonTokens.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/IconToggleButtonTokens.kt
new file mode 100644
index 0000000..b6ae374
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/IconToggleButtonTokens.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// VERSION: v0_16
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+package androidx.wear.compose.material3.tokens
+
+internal object IconToggleButtonTokens {
+ val CheckedContainerColor = ColorSchemeKeyTokens.Primary
+ val CheckedContentColor = ColorSchemeKeyTokens.OnPrimary
+ val DisabledCheckedContainerColor = ColorSchemeKeyTokens.Primary
+ val DisabledCheckedContainerOpacity = 0.38f
+ val DisabledCheckedContentColor = ColorSchemeKeyTokens.OnPrimary
+ val DisabledCheckedContentOpacity = 0.38f
+ val DisabledUncheckedContainerColor = ColorSchemeKeyTokens.Surface
+ val DisabledUncheckedContainerOpacity = 0.38f
+ val DisabledUncheckedContentColor = ColorSchemeKeyTokens.OnSurfaceVariant
+ val DisabledUncheckedContentOpacity = 0.38f
+ val UncheckedContainerColor = ColorSchemeKeyTokens.Surface
+ val UncheckedContentColor = ColorSchemeKeyTokens.OnSurfaceVariant
+}
diff --git a/wear/compose/integration-tests/macrobenchmark-target/build.gradle b/wear/compose/integration-tests/macrobenchmark-target/build.gradle
index af518ce..5ed6e31 100644
--- a/wear/compose/integration-tests/macrobenchmark-target/build.gradle
+++ b/wear/compose/integration-tests/macrobenchmark-target/build.gradle
@@ -44,7 +44,7 @@
implementation(project(":compose:runtime:runtime"))
implementation(project(":compose:ui:ui-tooling"))
implementation(project(":compose:material:material-icons-core"))
- implementation("androidx.activity:activity-compose:1.3.1")
+ implementation(project(":activity:activity-compose"))
implementation(project(":profileinstaller:profileinstaller"))
implementation project(path: ':wear:compose:compose-foundation')
implementation project(path: ':wear:compose:compose-material')
diff --git a/wear/compose/integration-tests/macrobenchmark/build.gradle b/wear/compose/integration-tests/macrobenchmark/build.gradle
index 61090b4..1b3a97c 100644
--- a/wear/compose/integration-tests/macrobenchmark/build.gradle
+++ b/wear/compose/integration-tests/macrobenchmark/build.gradle
@@ -23,7 +23,6 @@
android {
defaultConfig {
minSdkVersion 29
- testInstrumentationRunnerArguments["androidx.benchmark.fullTracing.enable"] = "true"
}
namespace "androidx.wear.compose.integration.macrobenchmark"
targetProjectPath = ":wear:compose:integration-tests:macrobenchmark-target"
diff --git a/wear/compose/integration-tests/macrobenchmark/src/main/java/androidx/wear/compose/integration/macrobenchmark/SwipeBenchmark.kt b/wear/compose/integration-tests/macrobenchmark/src/main/java/androidx/wear/compose/integration/macrobenchmark/SwipeBenchmark.kt
index c9871fe..25d3741 100644
--- a/wear/compose/integration-tests/macrobenchmark/src/main/java/androidx/wear/compose/integration/macrobenchmark/SwipeBenchmark.kt
+++ b/wear/compose/integration-tests/macrobenchmark/src/main/java/androidx/wear/compose/integration/macrobenchmark/SwipeBenchmark.kt
@@ -40,8 +40,6 @@
@get:Rule
val benchmarkRule = MacrobenchmarkRule()
- private lateinit var device: UiDevice
-
@Before
fun setUp() {
val instrumentation = InstrumentationRegistry.getInstrumentation()
@@ -64,8 +62,8 @@
val swipeToDismissBox = device.findObject(By.desc(CONTENT_DESCRIPTION))
// Setting a gesture margin is important otherwise gesture nav is triggered.
swipeToDismissBox.setGestureMargin(device.displayWidth / 5)
- repeat(10) {
- swipeToDismissBox.swipe(Direction.RIGHT, 0.75f)
+ repeat(3) {
+ swipeToDismissBox.swipe(Direction.RIGHT, 0.75f, SWIPE_SPEED)
device.waitForIdle()
}
}
@@ -80,4 +78,7 @@
@JvmStatic
fun parameters() = createCompilationParams()
}
+
+ private lateinit var device: UiDevice
+ private val SWIPE_SPEED = 500
}
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/ComplicationSlot.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/ComplicationSlot.kt
index c36b80e..e8f57d7 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/ComplicationSlot.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/ComplicationSlot.kt
@@ -28,6 +28,7 @@
import androidx.annotation.Px
import androidx.annotation.RestrictTo
import androidx.annotation.UiThread
+import androidx.annotation.VisibleForTesting
import androidx.annotation.WorkerThread
import androidx.wear.watchface.RenderParameters.HighlightedElement
import androidx.wear.watchface.complications.ComplicationSlotBounds
@@ -243,8 +244,8 @@
public const val ROUND_RECT: Int = 0
/**
- * For a full screen image complication slot drawn behind the watch face. Note you can only
- * have a single background complication slot.
+ * For a full screen image complication slot drawn behind the watch face. Note you can only have
+ * a single background complication slot.
*/
public const val BACKGROUND: Int = 1
@@ -342,21 +343,6 @@
* expanded by [ComplicationSlotBounds.perComplicationTypeMargins]. Expanded bounds can overlap so
* the [ComplicationSlot] with the lowest id that intersects the coordinates, if any, is selected.
*
- * @param accessibilityTraversalIndex Used to sort Complications when generating accessibility
- * content description labels.
- * @param bounds The complication slot's [ComplicationSlotBounds].
- * @param supportedTypes The list of [ComplicationType]s accepted by this complication slot, must be
- * non-empty. During complication data source selection, each item in this list is compared in
- * turn with entries from a data source's data source's supported types. The first matching entry
- * from `supportedTypes` is chosen. If there are no matches then that data source is not eligible
- * to be selected in this slot.
- * @param defaultPolicy The [DefaultComplicationDataSourcePolicy] which controls the initial
- * complication data source when the watch face is first installed.
- * @param defaultDataSourceType The default [ComplicationType] for the default complication data
- * source.
- * @param configExtras Extras to be merged into the Intent sent when invoking the complication data
- * source chooser activity. This features is intended for OEM watch faces where they have elements
- * that behave like a complication but are in fact entirely watch face specific.
* @property id The Watch Face's ID for the complication slot.
* @property boundsType The [ComplicationSlotBoundsTypeIntDef] of the complication slot.
* @property canvasComplicationFactory The [CanvasComplicationFactory] used to generate a
@@ -373,6 +359,25 @@
* complication slot.
*/
public class ComplicationSlot
+/**
+ * Constructs a [ComplicationSlot].
+ *
+ * @param accessibilityTraversalIndex Used to sort Complications when generating accessibility
+ * content description labels.
+ * @param bounds The complication slot's [ComplicationSlotBounds].
+ * @param supportedTypes The list of [ComplicationType]s accepted by this complication slot, must be
+ * non-empty. During complication data source selection, each item in this list is compared in
+ * turn with entries from a data source's data source's supported types. The first matching entry
+ * from `supportedTypes` is chosen. If there are no matches then that data source is not eligible
+ * to be selected in this slot.
+ * @param defaultPolicy The [DefaultComplicationDataSourcePolicy] which controls the initial
+ * complication data source when the watch face is first installed.
+ * @param defaultDataSourceType The default [ComplicationType] for the default complication data
+ * source.
+ * @param configExtras Extras to be merged into the Intent sent when invoking the complication data
+ * source chooser activity. This features is intended for OEM watch faces where they have elements
+ * that behave like a complication but are in fact entirely watch face specific.
+ */
@ComplicationExperimental
internal constructor(
public val id: Int,
@@ -391,8 +396,7 @@
screenReaderNameResourceId: Int?,
// TODO(b/230364881): This should really be public but some metalava bug is preventing
// @ComplicationExperimental from working on the getter so it's currently hidden.
- @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- public val boundingArc: BoundingArc?
+ @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public val boundingArc: BoundingArc?
) {
/**
* The [ComplicationSlotsManager] this is attached to. Only set after the
@@ -430,7 +434,8 @@
private var lastComplicationUpdate = Instant.EPOCH
- private class ComplicationDataHistoryEntry(
+ @VisibleForTesting
+ internal class ComplicationDataHistoryEntry(
val complicationData: ComplicationData,
val time: Instant
)
@@ -439,7 +444,8 @@
* There doesn't seem to be a convenient ring buffer in the standard library so implement our
* own one.
*/
- private class RingBuffer(val size: Int) : Iterable<ComplicationDataHistoryEntry> {
+ @VisibleForTesting
+ internal class RingBuffer(val size: Int) : Iterable<ComplicationDataHistoryEntry> {
private val entries = arrayOfNulls<ComplicationDataHistoryEntry>(size)
private var readIndex = 0
private var writeIndex = 0
@@ -467,9 +473,11 @@
/**
* In userdebug builds maintain a history of the last [MAX_COMPLICATION_HISTORY_ENTRIES]-1
- * complications, which is logged in dumpsys to help debug complication issues.
+ * complications sent by the system, which is logged in dumpsys to help debug complication
+ * issues.
*/
- private val complicationHistory =
+ @VisibleForTesting
+ internal val complicationHistory =
if (Build.TYPE.equals("userdebug")) {
RingBuffer(MAX_COMPLICATION_HISTORY_ENTRIES)
} else {
@@ -666,8 +674,11 @@
)
}
+ /** Builder for constructing [ComplicationSlot]s. */
+ @OptIn(ComplicationExperimental::class)
+ public class Builder
/**
- * Builder for constructing [ComplicationSlot]s.
+ * Constructs a [Builder].
*
* @param id The watch face's ID for this complication. Can be any integer but should be unique
* within the watch face.
@@ -683,8 +694,6 @@
* @param complicationTapFilter The [ComplicationTapFilter] used to perform hit testing for this
* complication.
*/
- @OptIn(ComplicationExperimental::class)
- public class Builder
internal constructor(
private val id: Int,
private val canvasComplicationFactory: CanvasComplicationFactory,
@@ -1010,18 +1019,38 @@
/**
* Sets the current [ComplicationData] and if it's a timeline, the correct override for
- * [instant] is chosen.
+ * [instant] is chosen. Any images associated with the complication are loaded asynchronously
+ * and the complication history is updated.
*/
- internal fun setComplicationData(
- complicationData: ComplicationData,
- loadDrawablesAsynchronous: Boolean,
- instant: Instant
- ) {
+ internal fun setComplicationData(complicationData: ComplicationData, instant: Instant) {
lastComplicationUpdate = instant
complicationHistory?.push(ComplicationDataHistoryEntry(complicationData, instant))
timelineComplicationData = complicationData
timelineEntries = complicationData.asWireComplicationData().timelineEntries?.toList()
- selectComplicationDataForInstant(instant, loadDrawablesAsynchronous, true)
+ selectComplicationDataForInstant(
+ instant,
+ loadDrawablesAsynchronous = true,
+ forceUpdate = true
+ )
+ }
+
+ /**
+ * Sets the current [ComplicationData] and if it's a timeline, the correct override for
+ * [instant] is chosen. Any images are loaded synchronously. The complication history is not
+ * updated.
+ */
+ internal fun setComplicationDataForScreenshot(
+ complicationData: ComplicationData,
+ instant: Instant
+ ) {
+ lastComplicationUpdate = instant
+ timelineComplicationData = complicationData
+ timelineEntries = complicationData.asWireComplicationData().timelineEntries?.toList()
+ selectComplicationDataForInstant(
+ instant,
+ loadDrawablesAsynchronous = false,
+ forceUpdate = true
+ )
}
/**
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/ComplicationSlotsManager.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/ComplicationSlotsManager.kt
index d9cdbd9..22d34a0 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/ComplicationSlotsManager.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/ComplicationSlotsManager.kt
@@ -327,14 +327,14 @@
return
}
complication.dataDirty = complication.dataDirty || (complication.renderer.getData() != data)
- complication.setComplicationData(data, true, instant)
+ complication.setComplicationData(data, instant)
}
/**
* For use by screen shot code which will reset the data afterwards, hence dirty bit not set.
*/
@UiThread
- internal fun setComplicationDataUpdateSync(
+ internal fun setComplicationDataUpdateForScreenshot(
complicationSlotId: Int,
data: ComplicationData,
instant: Instant
@@ -348,7 +348,7 @@
)
return
}
- complication.setComplicationData(data, false, instant)
+ complication.setComplicationDataForScreenshot(data, instant)
}
/**
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
index b2e238e..568894e 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
@@ -252,8 +252,9 @@
}
.apply { setContext(context) }
- val engine = watchFaceService.createHeadlessEngine(componentName)
- as WatchFaceService.EngineWrapper
+ val engine =
+ watchFaceService.createHeadlessEngine(componentName)
+ as WatchFaceService.EngineWrapper
val headlessWatchFaceImpl = engine.createHeadlessInstance(params)
return engine.deferredWatchFaceImpl.await().WFEditorDelegate(headlessWatchFaceImpl)
}
@@ -472,10 +473,7 @@
}
}
- /**
- * The [OverlayStyle]. This feature is unimplemented on any platform, and will be
- * removed.
- */
+ /** The [OverlayStyle]. This feature is unimplemented on any platform, and will be removed. */
@Deprecated("OverlayStyle will be removed in a future release.")
@Suppress("Deprecation")
public var overlayStyle: OverlayStyle = OverlayStyle()
@@ -655,8 +653,7 @@
private val tapListener = watchface.tapListener
internal var complicationDeniedDialogIntent = watchface.complicationDeniedDialogIntent
internal var complicationRationaleDialogIntent = watchface.complicationRationaleDialogIntent
- @Suppress("Deprecation")
- internal var overlayStyle = watchface.overlayStyle
+ @Suppress("Deprecation") internal var overlayStyle = watchface.overlayStyle
private var mockTime = MockTime(1.0, 0, Long.MAX_VALUE)
@@ -671,27 +668,25 @@
init {
val context = watchFaceHostApi.getContext()
- val displayManager =
- context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
+ val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
displayManager.registerDisplayListener(
- object : DisplayManager.DisplayListener {
- override fun onDisplayAdded(displayId: Int) {}
+ object : DisplayManager.DisplayListener {
+ override fun onDisplayAdded(displayId: Int) {}
- override fun onDisplayChanged(displayId: Int) {
- val display = displayManager.getDisplay(Display.DEFAULT_DISPLAY)!!
- if (display.state == Display.STATE_OFF &&
- watchState.isVisible.value == false) {
- // We want to avoid a glimpse of a stale time when transitioning from
- // hidden to visible, so we render two black frames to clear the buffers
- // when the display has been turned off and the watch is not visible.
- renderer.renderBlackFrame()
- renderer.renderBlackFrame()
- }
+ override fun onDisplayChanged(displayId: Int) {
+ val display = displayManager.getDisplay(Display.DEFAULT_DISPLAY)!!
+ if (display.state == Display.STATE_OFF && watchState.isVisible.value == false) {
+ // We want to avoid a glimpse of a stale time when transitioning from
+ // hidden to visible, so we render two black frames to clear the buffers
+ // when the display has been turned off and the watch is not visible.
+ renderer.renderBlackFrame()
+ renderer.renderBlackFrame()
}
+ }
- override fun onDisplayRemoved(displayId: Int) {}
- },
- watchFaceHostApi.getUiThreadHandler()
+ override fun onDisplayRemoved(displayId: Int) {}
+ },
+ watchFaceHostApi.getUiThreadHandler()
)
}
@@ -893,8 +888,11 @@
get() = watchFaceHostApi.getComplicationRationaleIntent()
override var editorObscuresWatchFace: Boolean
- get() = InteractiveInstanceManager
- .getCurrentInteractiveInstance()?.engine?.editorObscuresWatchFace ?: false
+ get() =
+ InteractiveInstanceManager.getCurrentInteractiveInstance()
+ ?.engine
+ ?.editorObscuresWatchFace
+ ?: false
set(value) {
InteractiveInstanceManager.getCurrentInteractiveInstance()?.engine?.let {
it.editorObscuresWatchFace = value
@@ -915,7 +913,7 @@
slotIdToComplicationData?.let {
for ((id, complicationData) in it) {
- complicationSlotsManager.setComplicationDataUpdateSync(
+ complicationSlotsManager.setComplicationDataUpdateForScreenshot(
id,
complicationData,
instant
@@ -930,7 +928,7 @@
slotIdToComplicationData?.let {
val now = getNow()
for ((id, complicationData) in oldComplicationData) {
- complicationSlotsManager.setComplicationDataUpdateSync(
+ complicationSlotsManager.setComplicationDataUpdateForScreenshot(
id,
complicationData,
now
@@ -994,8 +992,11 @@
// Separate calls are issued to deliver the state of isAmbient and isVisible, so during init
// we might not yet know the state of both (which is required by the shouldAnimate logic).
// If the editor is obscuring the watch face, there's no need to schedule a frame.
- if (!watchState.isAmbient.hasValue() || !watchState.isVisible.hasValue() ||
- editorObscuresWatchFace) {
+ if (
+ !watchState.isAmbient.hasValue() ||
+ !watchState.isVisible.hasValue() ||
+ editorObscuresWatchFace
+ ) {
return
}
@@ -1194,7 +1195,7 @@
params.idAndComplicationDatumWireFormats?.let {
for (idAndData in it) {
- complicationSlotsManager.setComplicationDataUpdateSync(
+ complicationSlotsManager.setComplicationDataUpdateForScreenshot(
idAndData.id,
idAndData.complicationData.toApiComplicationData(),
instant
@@ -1218,7 +1219,7 @@
if (params.idAndComplicationDatumWireFormats != null) {
val now = getNow()
for ((id, complicationData) in oldComplicationData) {
- complicationSlotsManager.setComplicationDataUpdateSync(
+ complicationSlotsManager.setComplicationDataUpdateForScreenshot(
id,
complicationData,
now
@@ -1272,20 +1273,21 @@
// Compute the bounds of the complication based on the display rather than
// the headless renderer (which may be smaller).
- val bounds = it.computeBounds(
- Rect(
- 0,
- 0,
- Resources.getSystem().displayMetrics.widthPixels,
- Resources.getSystem().displayMetrics.heightPixels
+ val bounds =
+ it.computeBounds(
+ Rect(
+ 0,
+ 0,
+ Resources.getSystem().displayMetrics.widthPixels,
+ Resources.getSystem().displayMetrics.heightPixels
+ )
)
- )
var prevData: ComplicationData? = null
val screenshotComplicationData = params.complicationData
if (screenshotComplicationData != null) {
prevData = it.renderer.getData()
- complicationSlotsManager.setComplicationDataUpdateSync(
+ complicationSlotsManager.setComplicationDataUpdateForScreenshot(
params.complicationSlotId,
screenshotComplicationData.toApiComplicationData(),
instant
@@ -1303,12 +1305,13 @@
params.complicationSlotId
)
picture.endRecording()
- complicationBitmap = Api28CreateBitmapHelper.createBitmap(
- picture,
- bounds.width(),
- bounds.height(),
- Bitmap.Config.ARGB_8888
- )
+ complicationBitmap =
+ Api28CreateBitmapHelper.createBitmap(
+ picture,
+ bounds.width(),
+ bounds.height(),
+ Bitmap.Config.ARGB_8888
+ )
} else {
complicationBitmap =
Bitmap.createBitmap(
@@ -1330,7 +1333,7 @@
// Restore previous ComplicationData & style if required.
if (prevData != null) {
val now = getNow()
- complicationSlotsManager.setComplicationDataUpdateSync(
+ complicationSlotsManager.setComplicationDataUpdateForScreenshot(
params.complicationSlotId,
prevData,
now
@@ -1421,7 +1424,7 @@
params.idAndComplicationDatumWireFormats?.let {
for (idAndData in it) {
- watchFaceImpl.complicationSlotsManager.setComplicationDataUpdateSync(
+ watchFaceImpl.complicationSlotsManager.setComplicationDataUpdateForScreenshot(
idAndData.id,
idAndData.complicationData.toApiComplicationData(),
instant
@@ -1443,7 +1446,7 @@
if (params.idAndComplicationDatumWireFormats != null) {
val now = watchFaceImpl.getNow()
for ((id, complicationData) in oldComplicationData) {
- watchFaceImpl.complicationSlotsManager.setComplicationDataUpdateSync(
+ watchFaceImpl.complicationSlotsManager.setComplicationDataUpdateForScreenshot(
id,
complicationData,
now
diff --git a/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt b/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
index 3a6c4ec..f131d12 100644
--- a/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
+++ b/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
@@ -70,6 +70,7 @@
import androidx.wear.watchface.complications.data.ShortTextComplicationData
import androidx.wear.watchface.complications.data.TimeDifferenceComplicationText
import androidx.wear.watchface.complications.data.TimeDifferenceStyle
+import androidx.wear.watchface.complications.data.toApiComplicationData
import androidx.wear.watchface.complications.rendering.CanvasComplicationDrawable
import androidx.wear.watchface.complications.rendering.ComplicationDrawable
import androidx.wear.watchface.control.HeadlessWatchFaceImpl
@@ -142,6 +143,7 @@
import org.mockito.kotlin.verifyNoMoreInteractions
import org.robolectric.Shadows.shadowOf
import org.robolectric.annotation.Config
+import org.robolectric.shadows.ShadowBuild
private const val INTERACTIVE_UPDATE_RATE_MS = 16L
private const val LEFT_COMPLICATION_ID = 1000
@@ -728,6 +730,7 @@
@Before
public fun setUp() {
+ ShadowBuild.setType("userdebug")
Assume.assumeTrue("This test suite assumes API 26", Build.VERSION.SDK_INT >= 26)
`when`(handler.getLooper()).thenReturn(Looper.myLooper())
@@ -758,9 +761,11 @@
(TODO: b/264994539) - Explicitly releasing the mSurfaceControl field,
accessed via reflection. Remove when a proper fix is found
*/
- val mSurfaceControlObject: Field = WatchFaceService.EngineWrapper::class
- .java.superclass // android.service.wallpaper.WallpaperService$Engine
- .getDeclaredField("mSurfaceControl")
+ val mSurfaceControlObject: Field =
+ WatchFaceService.EngineWrapper::class
+ .java
+ .superclass // android.service.wallpaper.WallpaperService$Engine
+ .getDeclaredField("mSurfaceControl")
mSurfaceControlObject.isAccessible = true
(mSurfaceControlObject.get(engineWrapper) as SurfaceControl).release()
}
@@ -1317,11 +1322,7 @@
null
)
verify(tapListener)
- .onTapEvent(
- TapType.UP,
- TapEvent(10, 200, Instant.ofEpochMilli(looperTimeMillis)),
- null
- )
+ .onTapEvent(TapType.UP, TapEvent(10, 200, Instant.ofEpochMilli(looperTimeMillis)), null)
}
@Test
@@ -2858,6 +2859,48 @@
@Test
@Config(sdk = [Build.VERSION_CODES.O_MR1])
+ public fun updateComplicationData_appendsToHistory() {
+ initWallpaperInteractiveWatchFaceInstance(
+ complicationSlots = listOf(leftComplication)
+ )
+ // Validate that the history is initially empty.
+ assertThat(leftComplication.complicationHistory!!.iterator().asSequence().toList())
+ .isEmpty()
+ val longTextComplication =
+ WireComplicationData.Builder(WireComplicationData.TYPE_LONG_TEXT)
+ .setLongText(WireComplicationText.plainText("TYPE_LONG_TEXT"))
+ .build()
+
+ interactiveWatchFaceInstance.updateComplicationData(
+ listOf(IdAndComplicationDataWireFormat(LEFT_COMPLICATION_ID, longTextComplication))
+ )
+
+ assertThat(leftComplication.complicationHistory.toList().map { it.complicationData })
+ .containsExactly(longTextComplication.toApiComplicationData())
+ }
+
+ @Test
+ @Config(sdk = [Build.VERSION_CODES.O_MR1])
+ public fun setComplicationDataUpdateForScreenshot_doesNotAppendToHistory() {
+ initWallpaperInteractiveWatchFaceInstance(
+ complicationSlots = listOf(leftComplication)
+ )
+
+ complicationSlotsManager.setComplicationDataUpdateForScreenshot(
+ LEFT_COMPLICATION_ID,
+ WireComplicationData.Builder(WireComplicationData.TYPE_LONG_TEXT)
+ .setLongText(WireComplicationText.plainText("TYPE_LONG_TEXT"))
+ .build()
+ .toApiComplicationData(),
+ Instant.now()
+ )
+
+ assertThat(leftComplication.complicationHistory!!.iterator().asSequence().toList())
+ .isEmpty()
+ }
+
+ @Test
+ @Config(sdk = [Build.VERSION_CODES.O_MR1])
public fun complicationCache() {
val complicationCache = HashMap<String, ByteArray>()
val instanceParams =
diff --git a/window/extensions/extensions/src/main/java/androidx/window/extensions/WindowExtensions.java b/window/extensions/extensions/src/main/java/androidx/window/extensions/WindowExtensions.java
index 676d286..74163e6 100644
--- a/window/extensions/extensions/src/main/java/androidx/window/extensions/WindowExtensions.java
+++ b/window/extensions/extensions/src/main/java/androidx/window/extensions/WindowExtensions.java
@@ -18,12 +18,15 @@
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+import android.app.Activity;
import android.app.ActivityOptions;
+import android.content.Context;
import android.os.IBinder;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.window.extensions.area.WindowAreaComponent;
+import androidx.window.extensions.core.util.function.Consumer;
import androidx.window.extensions.embedding.ActivityEmbeddingComponent;
import androidx.window.extensions.embedding.ActivityStack;
import androidx.window.extensions.embedding.SplitAttributes;
@@ -75,10 +78,13 @@
* block.
* The added APIs for Vendor API level 2 are:
* <ul>
+ * <li>{@link WindowAreaComponent#addRearDisplayStatusListener(Consumer)}</li>
+ * <li>{@link WindowAreaComponent#startRearDisplaySession(Activity, Consumer)}</li>
* <li>{@link androidx.window.extensions.embedding.SplitPlaceholderRule.Builder#setFinishPrimaryWithPlaceholder(int)}</li>
* <li>{@link androidx.window.extensions.embedding.SplitAttributes}</li>
* <li>{@link ActivityEmbeddingComponent#setSplitAttributesCalculator(
* androidx.window.extensions.core.util.function.Function)}</li>
+ * <li>{@link WindowLayoutComponent#addWindowLayoutInfoListener(Context, Consumer)}</li>
* </ul>
*/
@RestrictTo(LIBRARY_GROUP)
@@ -99,7 +105,11 @@
* <li>{@link ActivityEmbeddingComponent#updateSplitAttributes(IBinder, SplitAttributes)}
* </li>
* <li>{@link ActivityEmbeddingComponent#finishActivityStacks(Set)}</li>
- * <li>{@link androidx.window.extensions.area.WindowAreaComponent} APIs</li>
+ * <li>{@link WindowAreaComponent#addRearDisplayPresentationStatusListener(Consumer)}</li>
+ * <li>{@link WindowAreaComponent#startRearDisplayPresentationSession(Activity, Consumer)}
+ * </li>
+ * <li>{@link WindowAreaComponent#getRearDisplayMetrics()}</li>
+ * <li>{@link WindowAreaComponent#getRearDisplayPresentation()}</li>
* </ul>
* </p>
*/
diff --git a/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/ExampleWindowInitializer.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/ExampleWindowInitializer.kt
index f9d8303..55425fa 100644
--- a/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/ExampleWindowInitializer.kt
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/ExampleWindowInitializer.kt
@@ -18,6 +18,7 @@
import android.content.Context
import androidx.startup.Initializer
+import androidx.window.WindowSdkExtensions
import androidx.window.demo.R
import androidx.window.demo.embedding.SplitAttributesToggleMainActivity.Companion.PREFIX_FULLSCREEN_TOGGLE
import androidx.window.demo.embedding.SplitAttributesToggleMainActivity.Companion.PREFIX_PLACEHOLDER
@@ -55,7 +56,7 @@
override fun create(context: Context): RuleController {
SplitController.getInstance(context).apply {
- if (isSplitAttributesCalculatorSupported()) {
+ if (WindowSdkExtensions.getInstance().extensionVersion >= 2) {
setSplitAttributesCalculator(::sampleSplitAttributesCalculator)
}
}
diff --git a/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitActivityBase.java b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitActivityBase.java
index faf792e..aaa751e 100644
--- a/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitActivityBase.java
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitActivityBase.java
@@ -39,6 +39,7 @@
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.util.Consumer;
+import androidx.window.WindowSdkExtensions;
import androidx.window.demo.R;
import androidx.window.demo.databinding.ActivitySplitActivityLayoutBinding;
import androidx.window.embedding.ActivityEmbeddingController;
@@ -111,8 +112,7 @@
}
startActivity(new Intent(this, SplitActivityE.class), bundle);
});
- if (!ActivityEmbeddingOptions.isSetLaunchingActivityStackSupported(
- ActivityOptions.makeBasic())) {
+ if (WindowSdkExtensions.getInstance().getExtensionVersion() < 3) {
mViewBinding.setLaunchingEInActivityStack.setEnabled(false);
}
mViewBinding.launchF.setOnClickListener((View v) ->
diff --git a/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitAttributesToggleMainActivity.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitAttributesToggleMainActivity.kt
index 1021af3..d74e64d 100644
--- a/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitAttributesToggleMainActivity.kt
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitAttributesToggleMainActivity.kt
@@ -28,6 +28,7 @@
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
+import androidx.window.WindowSdkExtensions
import androidx.window.core.ExperimentalWindowApi
import androidx.window.demo.R
import androidx.window.demo.databinding.ActivitySplitAttributesTogglePrimaryActivityBinding
@@ -70,7 +71,7 @@
activityEmbeddingController = ActivityEmbeddingController.getInstance(this)
- if (!splitController.isSplitAttributesCalculatorSupported()) {
+ if (WindowSdkExtensions.getInstance().extensionVersion < 2) {
placeholderFoldingAwareAttrsRadioButton.isEnabled = false
viewBinding.placeholderUseCustomizedSplitAttributes.isEnabled = false
splitRuleFoldingAwareAttrsRadioButton.isEnabled = false
@@ -212,11 +213,13 @@
private suspend fun updateWarningMessages() {
val warningMessages = StringBuilder().apply {
- if (!splitController.isSplitAttributesCalculatorSupported()) {
+ val apiLevel = WindowSdkExtensions.getInstance().extensionVersion
+
+ if (apiLevel < 2) {
append(resources.getString(R.string.split_attributes_calculator_not_supported))
append("\n")
}
- if (!activityEmbeddingController.isFinishingActivityStacksSupported()) {
+ if (apiLevel < 3) {
append("Finishing secondary activities is not supported on this device!\n")
}
if (viewBinding.finishSecondaryActivitiesButton.isEnabled &&
diff --git a/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitAttributesTogglePrimaryActivity.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitAttributesTogglePrimaryActivity.kt
index 097da84..e7c62cd 100644
--- a/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitAttributesTogglePrimaryActivity.kt
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitAttributesTogglePrimaryActivity.kt
@@ -24,6 +24,7 @@
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
+import androidx.window.WindowSdkExtensions
import androidx.window.core.ExperimentalWindowApi
import androidx.window.demo.R
import androidx.window.embedding.ActivityStack
@@ -43,8 +44,7 @@
viewBinding.rootSplitActivityLayout.setBackgroundColor(Color.parseColor("#e8f5e9"))
- val isRuntimeApiSupported = activityEmbeddingController
- .isFinishingActivityStacksSupported()
+ val isRuntimeApiSupported = WindowSdkExtensions.getInstance().extensionVersion >= 3
secondaryActivityIntent = Intent(
this,
diff --git a/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitAttributesToggleSecondaryActivity.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitAttributesToggleSecondaryActivity.kt
index eac978a..01104cb 100644
--- a/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitAttributesToggleSecondaryActivity.kt
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitAttributesToggleSecondaryActivity.kt
@@ -24,6 +24,7 @@
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
+import androidx.window.WindowSdkExtensions
import androidx.window.core.ExperimentalWindowApi
import androidx.window.demo.R
import androidx.window.demo.databinding.ActivitySplitAttributesToggleSecondaryActivityBinding
@@ -63,10 +64,8 @@
activityEmbeddingController = ActivityEmbeddingController.getInstance(this)
val splitAttributesCustomizationEnabled = demoActivityEmbeddingController
.splitAttributesCustomizationEnabled.get()
- splitAttributesUpdatesSupported =
- splitController.isInvalidatingTopVisibleSplitAttributesSupported() &&
- splitController.isUpdatingSplitAttributesSupported() &&
- !splitAttributesCustomizationEnabled
+ splitAttributesUpdatesSupported = WindowSdkExtensions.getInstance().extensionVersion >= 3 &&
+ !splitAttributesCustomizationEnabled
fullscreenToggleButton.apply {
// Disable the toggle fullscreen feature if the device doesn't support runtime
diff --git a/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitDeviceStateActivityBase.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitDeviceStateActivityBase.kt
index 16aad4f..fcb0ddb 100644
--- a/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitDeviceStateActivityBase.kt
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitDeviceStateActivityBase.kt
@@ -28,6 +28,7 @@
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
+import androidx.window.WindowSdkExtensions
import androidx.window.demo.R
import androidx.window.demo.databinding.ActivitySplitDeviceStateLayoutBinding
import androidx.window.embedding.EmbeddingRule
@@ -63,6 +64,8 @@
/** The last selected split rule id. */
private var lastCheckedRuleId = 0
+ private val isCallbackSupported = WindowSdkExtensions.getInstance().extensionVersion >= 2
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewBinding = ActivitySplitDeviceStateLayoutBinding.inflate(layoutInflater)
@@ -103,7 +106,6 @@
viewBinding.swapPrimarySecondaryPositionCheckBox.setOnCheckedChangeListener(this)
viewBinding.launchActivityToSide.setOnClickListener(this)
- val isCallbackSupported = splitController.isSplitAttributesCalculatorSupported()
if (!isCallbackSupported) {
// Disable the radioButtons that use SplitAttributesCalculator
viewBinding.showFullscreenInPortraitRadioButton.isEnabled = false
@@ -295,7 +297,7 @@
.setSplitType(SPLIT_TYPE_EXPAND)
.build()
var suggestToFinishItself = false
- val isCallbackSupported = splitController.isSplitAttributesCalculatorSupported()
+
// Traverse SplitInfos from the end because last SplitInfo has the highest z-order.
for (info in newSplitInfos.reversed()) {
if (info.contains(this@SplitDeviceStateActivityBase)) {
diff --git a/window/window-testing/src/main/java/androidx/window/testing/embedding/StubEmbeddingBackend.kt b/window/window-testing/src/main/java/androidx/window/testing/embedding/StubEmbeddingBackend.kt
index 78aca27..758b6d0 100644
--- a/window/window-testing/src/main/java/androidx/window/testing/embedding/StubEmbeddingBackend.kt
+++ b/window/window-testing/src/main/java/androidx/window/testing/embedding/StubEmbeddingBackend.kt
@@ -159,10 +159,6 @@
TODO("Not yet implemented")
}
- override fun isSplitAttributesCalculatorSupported(): Boolean {
- TODO("Not yet implemented")
- }
-
override fun getActivityStack(activity: Activity): ActivityStack? {
TODO("Not yet implemented")
}
@@ -178,10 +174,6 @@
TODO("Not yet implemented")
}
- override fun isFinishActivityStacksSupported(): Boolean {
- TODO("Not yet implemented")
- }
-
override fun invalidateTopVisibleSplitAttributes() {
TODO("Not yet implemented")
}
@@ -190,10 +182,6 @@
TODO("Not yet implemented")
}
- override fun areSplitAttributesUpdatesSupported(): Boolean {
- TODO("Not yet implemented")
- }
-
private fun validateRules(rules: Set<EmbeddingRule>) {
val tags = HashSet<String>()
rules.forEach { rule ->
diff --git a/window/window/api/1.2.0-beta03.txt b/window/window/api/1.2.0-beta03.txt
index 1930a35..4241d96 100644
--- a/window/window/api/1.2.0-beta03.txt
+++ b/window/window/api/1.2.0-beta03.txt
@@ -1,6 +1,11 @@
// Signature format: 4.0
package androidx.window {
+ @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.CONSTRUCTOR, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.PROPERTY}) public @interface RequiresWindowSdkExtension {
+ method public abstract int version();
+ property public abstract int version;
+ }
+
public final class WindowProperties {
field public static final androidx.window.WindowProperties INSTANCE;
field public static final String PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE = "android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE";
@@ -10,6 +15,17 @@
field public static final String PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES = "android.window.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES";
}
+ public abstract class WindowSdkExtensions {
+ method @IntRange(from=0L) public int getExtensionVersion();
+ method public static final androidx.window.WindowSdkExtensions getInstance();
+ property @IntRange(from=0L) public int extensionVersion;
+ field public static final androidx.window.WindowSdkExtensions.Companion Companion;
+ }
+
+ public static final class WindowSdkExtensions.Companion {
+ method public androidx.window.WindowSdkExtensions getInstance();
+ }
+
}
package androidx.window.area {
@@ -107,11 +123,10 @@
package androidx.window.embedding {
public final class ActivityEmbeddingController {
- method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public void finishActivityStacks(java.util.Set<androidx.window.embedding.ActivityStack> activityStacks);
+ method @SuppressCompatibility @androidx.window.RequiresWindowSdkExtension(version=3) @androidx.window.core.ExperimentalWindowApi public void finishActivityStacks(java.util.Set<androidx.window.embedding.ActivityStack> activityStacks);
method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public androidx.window.embedding.ActivityStack? getActivityStack(android.app.Activity activity);
method public static androidx.window.embedding.ActivityEmbeddingController getInstance(android.content.Context context);
method public boolean isActivityEmbedded(android.app.Activity activity);
- method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public boolean isFinishingActivityStacksSupported();
field public static final androidx.window.embedding.ActivityEmbeddingController.Companion Companion;
}
@@ -120,9 +135,8 @@
}
public final class ActivityEmbeddingOptions {
- method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public static boolean isSetLaunchingActivityStackSupported(android.app.ActivityOptions);
- method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public static android.app.ActivityOptions setLaunchingActivityStack(android.app.ActivityOptions, android.app.Activity activity);
- method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public static android.app.ActivityOptions setLaunchingActivityStack(android.app.ActivityOptions, android.content.Context context, androidx.window.embedding.ActivityStack activityStack);
+ method @SuppressCompatibility @androidx.window.RequiresWindowSdkExtension(version=3) @androidx.window.core.ExperimentalWindowApi public static android.app.ActivityOptions setLaunchingActivityStack(android.app.ActivityOptions, android.app.Activity activity);
+ method @SuppressCompatibility @androidx.window.RequiresWindowSdkExtension(version=3) @androidx.window.core.ExperimentalWindowApi public static android.app.ActivityOptions setLaunchingActivityStack(android.app.ActivityOptions, android.content.Context context, androidx.window.embedding.ActivityStack activityStack);
}
public final class ActivityFilter {
@@ -245,16 +259,13 @@
}
public final class SplitController {
- method public void clearSplitAttributesCalculator();
+ method @androidx.window.RequiresWindowSdkExtension(version=2) public void clearSplitAttributesCalculator();
method public static androidx.window.embedding.SplitController getInstance(android.content.Context context);
method public androidx.window.embedding.SplitController.SplitSupportStatus getSplitSupportStatus();
- method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public void invalidateTopVisibleSplitAttributes();
- method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public boolean isInvalidatingTopVisibleSplitAttributesSupported();
- method public boolean isSplitAttributesCalculatorSupported();
- method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public boolean isUpdatingSplitAttributesSupported();
- method public void setSplitAttributesCalculator(kotlin.jvm.functions.Function1<? super androidx.window.embedding.SplitAttributesCalculatorParams,androidx.window.embedding.SplitAttributes> calculator);
+ method @SuppressCompatibility @androidx.window.RequiresWindowSdkExtension(version=3) @androidx.window.core.ExperimentalWindowApi public void invalidateTopVisibleSplitAttributes();
+ method @androidx.window.RequiresWindowSdkExtension(version=2) public void setSplitAttributesCalculator(kotlin.jvm.functions.Function1<? super androidx.window.embedding.SplitAttributesCalculatorParams,androidx.window.embedding.SplitAttributes> calculator);
method public kotlinx.coroutines.flow.Flow<java.util.List<androidx.window.embedding.SplitInfo>> splitInfoList(android.app.Activity activity);
- method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public void updateSplitAttributes(androidx.window.embedding.SplitInfo splitInfo, androidx.window.embedding.SplitAttributes splitAttributes);
+ method @SuppressCompatibility @androidx.window.RequiresWindowSdkExtension(version=3) @androidx.window.core.ExperimentalWindowApi public void updateSplitAttributes(androidx.window.embedding.SplitInfo splitInfo, androidx.window.embedding.SplitAttributes splitAttributes);
property public final androidx.window.embedding.SplitController.SplitSupportStatus splitSupportStatus;
field public static final androidx.window.embedding.SplitController.Companion Companion;
}
diff --git a/window/window/api/current.ignore b/window/window/api/current.ignore
deleted file mode 100644
index 2b9500cb..0000000
--- a/window/window/api/current.ignore
+++ /dev/null
@@ -1,7 +0,0 @@
-// Baseline format: 1.0
-AddedMethod: androidx.window.embedding.SplitAttributesCalculatorParams#areDefaultConstraintsSatisfied():
- Added method androidx.window.embedding.SplitAttributesCalculatorParams.areDefaultConstraintsSatisfied()
-
-
-RemovedMethod: androidx.window.embedding.SplitAttributesCalculatorParams#getAreDefaultConstraintsSatisfied():
- Removed method androidx.window.embedding.SplitAttributesCalculatorParams.getAreDefaultConstraintsSatisfied()
diff --git a/window/window/api/current.txt b/window/window/api/current.txt
index 1930a35..4241d96 100644
--- a/window/window/api/current.txt
+++ b/window/window/api/current.txt
@@ -1,6 +1,11 @@
// Signature format: 4.0
package androidx.window {
+ @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.CONSTRUCTOR, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.PROPERTY}) public @interface RequiresWindowSdkExtension {
+ method public abstract int version();
+ property public abstract int version;
+ }
+
public final class WindowProperties {
field public static final androidx.window.WindowProperties INSTANCE;
field public static final String PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE = "android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE";
@@ -10,6 +15,17 @@
field public static final String PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES = "android.window.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES";
}
+ public abstract class WindowSdkExtensions {
+ method @IntRange(from=0L) public int getExtensionVersion();
+ method public static final androidx.window.WindowSdkExtensions getInstance();
+ property @IntRange(from=0L) public int extensionVersion;
+ field public static final androidx.window.WindowSdkExtensions.Companion Companion;
+ }
+
+ public static final class WindowSdkExtensions.Companion {
+ method public androidx.window.WindowSdkExtensions getInstance();
+ }
+
}
package androidx.window.area {
@@ -107,11 +123,10 @@
package androidx.window.embedding {
public final class ActivityEmbeddingController {
- method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public void finishActivityStacks(java.util.Set<androidx.window.embedding.ActivityStack> activityStacks);
+ method @SuppressCompatibility @androidx.window.RequiresWindowSdkExtension(version=3) @androidx.window.core.ExperimentalWindowApi public void finishActivityStacks(java.util.Set<androidx.window.embedding.ActivityStack> activityStacks);
method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public androidx.window.embedding.ActivityStack? getActivityStack(android.app.Activity activity);
method public static androidx.window.embedding.ActivityEmbeddingController getInstance(android.content.Context context);
method public boolean isActivityEmbedded(android.app.Activity activity);
- method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public boolean isFinishingActivityStacksSupported();
field public static final androidx.window.embedding.ActivityEmbeddingController.Companion Companion;
}
@@ -120,9 +135,8 @@
}
public final class ActivityEmbeddingOptions {
- method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public static boolean isSetLaunchingActivityStackSupported(android.app.ActivityOptions);
- method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public static android.app.ActivityOptions setLaunchingActivityStack(android.app.ActivityOptions, android.app.Activity activity);
- method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public static android.app.ActivityOptions setLaunchingActivityStack(android.app.ActivityOptions, android.content.Context context, androidx.window.embedding.ActivityStack activityStack);
+ method @SuppressCompatibility @androidx.window.RequiresWindowSdkExtension(version=3) @androidx.window.core.ExperimentalWindowApi public static android.app.ActivityOptions setLaunchingActivityStack(android.app.ActivityOptions, android.app.Activity activity);
+ method @SuppressCompatibility @androidx.window.RequiresWindowSdkExtension(version=3) @androidx.window.core.ExperimentalWindowApi public static android.app.ActivityOptions setLaunchingActivityStack(android.app.ActivityOptions, android.content.Context context, androidx.window.embedding.ActivityStack activityStack);
}
public final class ActivityFilter {
@@ -245,16 +259,13 @@
}
public final class SplitController {
- method public void clearSplitAttributesCalculator();
+ method @androidx.window.RequiresWindowSdkExtension(version=2) public void clearSplitAttributesCalculator();
method public static androidx.window.embedding.SplitController getInstance(android.content.Context context);
method public androidx.window.embedding.SplitController.SplitSupportStatus getSplitSupportStatus();
- method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public void invalidateTopVisibleSplitAttributes();
- method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public boolean isInvalidatingTopVisibleSplitAttributesSupported();
- method public boolean isSplitAttributesCalculatorSupported();
- method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public boolean isUpdatingSplitAttributesSupported();
- method public void setSplitAttributesCalculator(kotlin.jvm.functions.Function1<? super androidx.window.embedding.SplitAttributesCalculatorParams,androidx.window.embedding.SplitAttributes> calculator);
+ method @SuppressCompatibility @androidx.window.RequiresWindowSdkExtension(version=3) @androidx.window.core.ExperimentalWindowApi public void invalidateTopVisibleSplitAttributes();
+ method @androidx.window.RequiresWindowSdkExtension(version=2) public void setSplitAttributesCalculator(kotlin.jvm.functions.Function1<? super androidx.window.embedding.SplitAttributesCalculatorParams,androidx.window.embedding.SplitAttributes> calculator);
method public kotlinx.coroutines.flow.Flow<java.util.List<androidx.window.embedding.SplitInfo>> splitInfoList(android.app.Activity activity);
- method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public void updateSplitAttributes(androidx.window.embedding.SplitInfo splitInfo, androidx.window.embedding.SplitAttributes splitAttributes);
+ method @SuppressCompatibility @androidx.window.RequiresWindowSdkExtension(version=3) @androidx.window.core.ExperimentalWindowApi public void updateSplitAttributes(androidx.window.embedding.SplitInfo splitInfo, androidx.window.embedding.SplitAttributes splitAttributes);
property public final androidx.window.embedding.SplitController.SplitSupportStatus splitSupportStatus;
field public static final androidx.window.embedding.SplitController.Companion Companion;
}
diff --git a/window/window/api/restricted_1.2.0-beta03.txt b/window/window/api/restricted_1.2.0-beta03.txt
index 1930a35..4241d96 100644
--- a/window/window/api/restricted_1.2.0-beta03.txt
+++ b/window/window/api/restricted_1.2.0-beta03.txt
@@ -1,6 +1,11 @@
// Signature format: 4.0
package androidx.window {
+ @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.CONSTRUCTOR, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.PROPERTY}) public @interface RequiresWindowSdkExtension {
+ method public abstract int version();
+ property public abstract int version;
+ }
+
public final class WindowProperties {
field public static final androidx.window.WindowProperties INSTANCE;
field public static final String PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE = "android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE";
@@ -10,6 +15,17 @@
field public static final String PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES = "android.window.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES";
}
+ public abstract class WindowSdkExtensions {
+ method @IntRange(from=0L) public int getExtensionVersion();
+ method public static final androidx.window.WindowSdkExtensions getInstance();
+ property @IntRange(from=0L) public int extensionVersion;
+ field public static final androidx.window.WindowSdkExtensions.Companion Companion;
+ }
+
+ public static final class WindowSdkExtensions.Companion {
+ method public androidx.window.WindowSdkExtensions getInstance();
+ }
+
}
package androidx.window.area {
@@ -107,11 +123,10 @@
package androidx.window.embedding {
public final class ActivityEmbeddingController {
- method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public void finishActivityStacks(java.util.Set<androidx.window.embedding.ActivityStack> activityStacks);
+ method @SuppressCompatibility @androidx.window.RequiresWindowSdkExtension(version=3) @androidx.window.core.ExperimentalWindowApi public void finishActivityStacks(java.util.Set<androidx.window.embedding.ActivityStack> activityStacks);
method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public androidx.window.embedding.ActivityStack? getActivityStack(android.app.Activity activity);
method public static androidx.window.embedding.ActivityEmbeddingController getInstance(android.content.Context context);
method public boolean isActivityEmbedded(android.app.Activity activity);
- method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public boolean isFinishingActivityStacksSupported();
field public static final androidx.window.embedding.ActivityEmbeddingController.Companion Companion;
}
@@ -120,9 +135,8 @@
}
public final class ActivityEmbeddingOptions {
- method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public static boolean isSetLaunchingActivityStackSupported(android.app.ActivityOptions);
- method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public static android.app.ActivityOptions setLaunchingActivityStack(android.app.ActivityOptions, android.app.Activity activity);
- method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public static android.app.ActivityOptions setLaunchingActivityStack(android.app.ActivityOptions, android.content.Context context, androidx.window.embedding.ActivityStack activityStack);
+ method @SuppressCompatibility @androidx.window.RequiresWindowSdkExtension(version=3) @androidx.window.core.ExperimentalWindowApi public static android.app.ActivityOptions setLaunchingActivityStack(android.app.ActivityOptions, android.app.Activity activity);
+ method @SuppressCompatibility @androidx.window.RequiresWindowSdkExtension(version=3) @androidx.window.core.ExperimentalWindowApi public static android.app.ActivityOptions setLaunchingActivityStack(android.app.ActivityOptions, android.content.Context context, androidx.window.embedding.ActivityStack activityStack);
}
public final class ActivityFilter {
@@ -245,16 +259,13 @@
}
public final class SplitController {
- method public void clearSplitAttributesCalculator();
+ method @androidx.window.RequiresWindowSdkExtension(version=2) public void clearSplitAttributesCalculator();
method public static androidx.window.embedding.SplitController getInstance(android.content.Context context);
method public androidx.window.embedding.SplitController.SplitSupportStatus getSplitSupportStatus();
- method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public void invalidateTopVisibleSplitAttributes();
- method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public boolean isInvalidatingTopVisibleSplitAttributesSupported();
- method public boolean isSplitAttributesCalculatorSupported();
- method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public boolean isUpdatingSplitAttributesSupported();
- method public void setSplitAttributesCalculator(kotlin.jvm.functions.Function1<? super androidx.window.embedding.SplitAttributesCalculatorParams,androidx.window.embedding.SplitAttributes> calculator);
+ method @SuppressCompatibility @androidx.window.RequiresWindowSdkExtension(version=3) @androidx.window.core.ExperimentalWindowApi public void invalidateTopVisibleSplitAttributes();
+ method @androidx.window.RequiresWindowSdkExtension(version=2) public void setSplitAttributesCalculator(kotlin.jvm.functions.Function1<? super androidx.window.embedding.SplitAttributesCalculatorParams,androidx.window.embedding.SplitAttributes> calculator);
method public kotlinx.coroutines.flow.Flow<java.util.List<androidx.window.embedding.SplitInfo>> splitInfoList(android.app.Activity activity);
- method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public void updateSplitAttributes(androidx.window.embedding.SplitInfo splitInfo, androidx.window.embedding.SplitAttributes splitAttributes);
+ method @SuppressCompatibility @androidx.window.RequiresWindowSdkExtension(version=3) @androidx.window.core.ExperimentalWindowApi public void updateSplitAttributes(androidx.window.embedding.SplitInfo splitInfo, androidx.window.embedding.SplitAttributes splitAttributes);
property public final androidx.window.embedding.SplitController.SplitSupportStatus splitSupportStatus;
field public static final androidx.window.embedding.SplitController.Companion Companion;
}
diff --git a/window/window/api/restricted_current.ignore b/window/window/api/restricted_current.ignore
deleted file mode 100644
index 2b9500cb..0000000
--- a/window/window/api/restricted_current.ignore
+++ /dev/null
@@ -1,7 +0,0 @@
-// Baseline format: 1.0
-AddedMethod: androidx.window.embedding.SplitAttributesCalculatorParams#areDefaultConstraintsSatisfied():
- Added method androidx.window.embedding.SplitAttributesCalculatorParams.areDefaultConstraintsSatisfied()
-
-
-RemovedMethod: androidx.window.embedding.SplitAttributesCalculatorParams#getAreDefaultConstraintsSatisfied():
- Removed method androidx.window.embedding.SplitAttributesCalculatorParams.getAreDefaultConstraintsSatisfied()
diff --git a/window/window/api/restricted_current.txt b/window/window/api/restricted_current.txt
index 1930a35..4241d96 100644
--- a/window/window/api/restricted_current.txt
+++ b/window/window/api/restricted_current.txt
@@ -1,6 +1,11 @@
// Signature format: 4.0
package androidx.window {
+ @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.CONSTRUCTOR, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.PROPERTY}) public @interface RequiresWindowSdkExtension {
+ method public abstract int version();
+ property public abstract int version;
+ }
+
public final class WindowProperties {
field public static final androidx.window.WindowProperties INSTANCE;
field public static final String PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE = "android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE";
@@ -10,6 +15,17 @@
field public static final String PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES = "android.window.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES";
}
+ public abstract class WindowSdkExtensions {
+ method @IntRange(from=0L) public int getExtensionVersion();
+ method public static final androidx.window.WindowSdkExtensions getInstance();
+ property @IntRange(from=0L) public int extensionVersion;
+ field public static final androidx.window.WindowSdkExtensions.Companion Companion;
+ }
+
+ public static final class WindowSdkExtensions.Companion {
+ method public androidx.window.WindowSdkExtensions getInstance();
+ }
+
}
package androidx.window.area {
@@ -107,11 +123,10 @@
package androidx.window.embedding {
public final class ActivityEmbeddingController {
- method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public void finishActivityStacks(java.util.Set<androidx.window.embedding.ActivityStack> activityStacks);
+ method @SuppressCompatibility @androidx.window.RequiresWindowSdkExtension(version=3) @androidx.window.core.ExperimentalWindowApi public void finishActivityStacks(java.util.Set<androidx.window.embedding.ActivityStack> activityStacks);
method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public androidx.window.embedding.ActivityStack? getActivityStack(android.app.Activity activity);
method public static androidx.window.embedding.ActivityEmbeddingController getInstance(android.content.Context context);
method public boolean isActivityEmbedded(android.app.Activity activity);
- method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public boolean isFinishingActivityStacksSupported();
field public static final androidx.window.embedding.ActivityEmbeddingController.Companion Companion;
}
@@ -120,9 +135,8 @@
}
public final class ActivityEmbeddingOptions {
- method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public static boolean isSetLaunchingActivityStackSupported(android.app.ActivityOptions);
- method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public static android.app.ActivityOptions setLaunchingActivityStack(android.app.ActivityOptions, android.app.Activity activity);
- method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public static android.app.ActivityOptions setLaunchingActivityStack(android.app.ActivityOptions, android.content.Context context, androidx.window.embedding.ActivityStack activityStack);
+ method @SuppressCompatibility @androidx.window.RequiresWindowSdkExtension(version=3) @androidx.window.core.ExperimentalWindowApi public static android.app.ActivityOptions setLaunchingActivityStack(android.app.ActivityOptions, android.app.Activity activity);
+ method @SuppressCompatibility @androidx.window.RequiresWindowSdkExtension(version=3) @androidx.window.core.ExperimentalWindowApi public static android.app.ActivityOptions setLaunchingActivityStack(android.app.ActivityOptions, android.content.Context context, androidx.window.embedding.ActivityStack activityStack);
}
public final class ActivityFilter {
@@ -245,16 +259,13 @@
}
public final class SplitController {
- method public void clearSplitAttributesCalculator();
+ method @androidx.window.RequiresWindowSdkExtension(version=2) public void clearSplitAttributesCalculator();
method public static androidx.window.embedding.SplitController getInstance(android.content.Context context);
method public androidx.window.embedding.SplitController.SplitSupportStatus getSplitSupportStatus();
- method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public void invalidateTopVisibleSplitAttributes();
- method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public boolean isInvalidatingTopVisibleSplitAttributesSupported();
- method public boolean isSplitAttributesCalculatorSupported();
- method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public boolean isUpdatingSplitAttributesSupported();
- method public void setSplitAttributesCalculator(kotlin.jvm.functions.Function1<? super androidx.window.embedding.SplitAttributesCalculatorParams,androidx.window.embedding.SplitAttributes> calculator);
+ method @SuppressCompatibility @androidx.window.RequiresWindowSdkExtension(version=3) @androidx.window.core.ExperimentalWindowApi public void invalidateTopVisibleSplitAttributes();
+ method @androidx.window.RequiresWindowSdkExtension(version=2) public void setSplitAttributesCalculator(kotlin.jvm.functions.Function1<? super androidx.window.embedding.SplitAttributesCalculatorParams,androidx.window.embedding.SplitAttributes> calculator);
method public kotlinx.coroutines.flow.Flow<java.util.List<androidx.window.embedding.SplitInfo>> splitInfoList(android.app.Activity activity);
- method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public void updateSplitAttributes(androidx.window.embedding.SplitInfo splitInfo, androidx.window.embedding.SplitAttributes splitAttributes);
+ method @SuppressCompatibility @androidx.window.RequiresWindowSdkExtension(version=3) @androidx.window.core.ExperimentalWindowApi public void updateSplitAttributes(androidx.window.embedding.SplitInfo splitInfo, androidx.window.embedding.SplitAttributes splitAttributes);
property public final androidx.window.embedding.SplitController.SplitSupportStatus splitSupportStatus;
field public static final androidx.window.embedding.SplitController.Companion Companion;
}
diff --git a/window/window/samples/src/main/java/androidx.window.samples/WindowSdkExtensionsSamples.kt b/window/window/samples/src/main/java/androidx.window.samples/WindowSdkExtensionsSamples.kt
new file mode 100644
index 0000000..f4049d1
--- /dev/null
+++ b/window/window/samples/src/main/java/androidx.window.samples/WindowSdkExtensionsSamples.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.samples
+
+import androidx.annotation.Sampled
+import androidx.window.RequiresWindowSdkExtension
+import androidx.window.WindowSdkExtensions
+import androidx.window.embedding.SplitAttributes
+import androidx.window.embedding.SplitAttributesCalculatorParams
+import androidx.window.embedding.SplitController
+import androidx.window.samples.embedding.context
+
+@Sampled
+fun checkWindowSdkExtensionsVersion() {
+ // For example, SplitController#setSplitAttributesCalculator requires extension version 2.
+ // Callers must check the current extension version before invoking the API, or exception will
+ // be thrown.
+ if (WindowSdkExtensions.getInstance().extensionVersion >= 2) {
+ SplitController.getInstance(context).setSplitAttributesCalculator(splitAttributesCalculator)
+ }
+}
+
+val splitAttributesCalculator = { _: SplitAttributesCalculatorParams ->
+ SplitAttributes.Builder().build()
+}
+
+@Sampled
+fun annotateRequiresWindowSdkExtension() {
+ // Given that there's an API required Window SDK Extension version 3
+ @RequiresWindowSdkExtension(3)
+ fun coolFeature() {}
+
+ // Developers can use @RequiresWindowSdkExtension to annotate their own functions to document
+ // the required minimum API level.
+ @RequiresWindowSdkExtension(3)
+ fun useCoolFeatureNoCheck() {
+ coolFeature()
+ }
+
+ // Then users know they should wrap the function with version check
+ if (WindowSdkExtensions.getInstance().extensionVersion >= 3) {
+ useCoolFeatureNoCheck()
+ }
+}
diff --git a/window/window/src/main/java/androidx/window/RequiresWindowSdkExtension.kt b/window/window/src/main/java/androidx/window/RequiresWindowSdkExtension.kt
new file mode 100644
index 0000000..d112c94
--- /dev/null
+++ b/window/window/src/main/java/androidx/window/RequiresWindowSdkExtension.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window
+
+import androidx.annotation.IntRange
+
+// TODO(b/292738295): Provide lint checks for RequiresWindowSdkExtension
+/**
+ * Denotes that the annotated element must only be used if
+ * [WindowSdkExtensions.extensionVersion] is greater than or equal to the given [version].
+ * Please see code sample linked below for usages.
+ *
+ * Calling the API that requires a higher level than the device's current level may lead to
+ * exceptions or unexpected results.
+ *
+ * @param version the minimum required [WindowSdkExtensions] version of the denoted target
+ *
+ * @sample androidx.window.samples.annotateRequiresWindowSdkExtension
+ */
+@MustBeDocumented
+@Retention(value = AnnotationRetention.BINARY)
+@Target(
+ AnnotationTarget.CLASS,
+ AnnotationTarget.FUNCTION,
+ AnnotationTarget.PROPERTY_GETTER,
+ AnnotationTarget.PROPERTY_SETTER,
+ AnnotationTarget.CONSTRUCTOR,
+ AnnotationTarget.FIELD,
+ AnnotationTarget.PROPERTY,
+)
+annotation class RequiresWindowSdkExtension(
+ /** The minimum required [WindowSdkExtensions] version of the denoted target */
+ @IntRange(from = 1)
+ val version: Int
+)
diff --git a/window/window/src/main/java/androidx/window/WindowSdkExtensions.kt b/window/window/src/main/java/androidx/window/WindowSdkExtensions.kt
new file mode 100644
index 0000000..26f0a6d
--- /dev/null
+++ b/window/window/src/main/java/androidx/window/WindowSdkExtensions.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window
+
+import androidx.annotation.IntRange
+import androidx.window.core.ExtensionsUtil
+
+/**
+ * This class provides information about the extension SDK versions for window features present on
+ * this device. Use [extensionVersion] to get the version of the extension. The extension version
+ * advances as the platform evolves and new APIs are added, so is suitable to use for determining
+ * API availability at runtime.
+ *
+ * Window Manager Jetpack APIs that require window SDK extensions support are denoted with
+ * [RequiresWindowSdkExtension]. The caller must check whether the device's extension version is
+ * greater than or equal to the minimum level reported in [RequiresWindowSdkExtension].
+ *
+ * @sample androidx.window.samples.checkWindowSdkExtensionsVersion
+ */
+abstract class WindowSdkExtensions internal constructor() {
+
+ /**
+ * Reports the device's extension version
+ *
+ * When Window SDK Extensions is not present on the device, the extension version will be 0.
+ */
+ @get: IntRange(from = 0)
+ open val extensionVersion: Int = ExtensionsUtil.safeVendorApiLevel
+
+ /**
+ * Checks the [extensionVersion] and throws [UnsupportedOperationException] if the minimum
+ * [version] is not satisfied.
+ *
+ * @param version The minimum required extension version of the targeting API.
+ * @throws UnsupportedOperationException if the minimum [version] is not satisfied.
+ */
+ internal fun requireExtensionVersion(@IntRange(from = 1) version: Int) {
+ if (extensionVersion < version) {
+ throw UnsupportedOperationException("This API requires extension version " +
+ "$version, but the device is on $extensionVersion")
+ }
+ }
+
+ companion object {
+ /** Returns a [WindowSdkExtensions] instance. */
+ @JvmStatic
+ fun getInstance(): WindowSdkExtensions {
+ return decorator.decorate(object : WindowSdkExtensions() {})
+ }
+
+ private var decorator: WindowSdkExtensionsDecorator = EmptyDecoratorWindowSdk
+
+ internal fun overrideDecorator(overridingDecorator: WindowSdkExtensionsDecorator) {
+ decorator = overridingDecorator
+ }
+
+ internal fun reset() {
+ decorator = EmptyDecoratorWindowSdk
+ }
+ }
+}
+
+internal interface WindowSdkExtensionsDecorator {
+ /** Returns a [WindowSdkExtensions] instance. */
+ fun decorate(windowSdkExtensions: WindowSdkExtensions): WindowSdkExtensions
+}
+
+private object EmptyDecoratorWindowSdk : WindowSdkExtensionsDecorator {
+ override fun decorate(windowSdkExtensions: WindowSdkExtensions): WindowSdkExtensions =
+ windowSdkExtensions
+}
diff --git a/window/window/src/main/java/androidx/window/area/WindowAreaController.kt b/window/window/src/main/java/androidx/window/area/WindowAreaController.kt
index ae2500c..d671726 100644
--- a/window/window/src/main/java/androidx/window/area/WindowAreaController.kt
+++ b/window/window/src/main/java/androidx/window/area/WindowAreaController.kt
@@ -21,6 +21,7 @@
import android.os.Build
import android.util.Log
import androidx.annotation.RestrictTo
+import androidx.window.WindowSdkExtensions
import androidx.window.area.WindowAreaInfo.Type.Companion.TYPE_REAR_FACING
import androidx.window.area.utils.DeviceUtils
import androidx.window.core.BuildConfig
@@ -41,6 +42,9 @@
/**
* [Flow] of the list of current [WindowAreaInfo]s that are currently available to be interacted
* with.
+ *
+ * If [WindowSdkExtensions.extensionVersion] is less than 2, the flow will return
+ * empty [WindowAreaInfo] list flow.
*/
val windowAreaInfos: Flow<List<WindowAreaInfo>>
diff --git a/window/window/src/main/java/androidx/window/core/ExtensionsUtil.kt b/window/window/src/main/java/androidx/window/core/ExtensionsUtil.kt
index d12062c..ec92f1e 100644
--- a/window/window/src/main/java/androidx/window/core/ExtensionsUtil.kt
+++ b/window/window/src/main/java/androidx/window/core/ExtensionsUtil.kt
@@ -17,20 +17,19 @@
package androidx.window.core
import android.util.Log
-import androidx.annotation.VisibleForTesting
+import androidx.annotation.IntRange
import androidx.window.core.VerificationMode.LOG
import androidx.window.extensions.WindowExtensionsProvider
internal object ExtensionsUtil {
private val TAG = ExtensionsUtil::class.simpleName
- private var overrideVendorApiLevel: Int? = null
+ @get:IntRange(from = 0)
val safeVendorApiLevel: Int
get() {
return try {
- overrideVendorApiLevel
- ?: WindowExtensionsProvider.getWindowExtensions().vendorApiLevel
+ WindowExtensionsProvider.getWindowExtensions().vendorApiLevel
} catch (e: NoClassDefFoundError) {
if (BuildConfig.verificationMode == LOG) {
Log.d(TAG, "Embedding extension version not found")
@@ -43,14 +42,4 @@
0
}
}
-
- @VisibleForTesting
- internal fun setOverrideVendorApiLevel(apiLevel: Int) {
- overrideVendorApiLevel = apiLevel
- }
-
- @VisibleForTesting
- internal fun resetOverrideVendorApiLevel() {
- overrideVendorApiLevel = null
- }
}
diff --git a/window/window/src/main/java/androidx/window/embedding/ActivityEmbeddingController.kt b/window/window/src/main/java/androidx/window/embedding/ActivityEmbeddingController.kt
index 8adffae..bbbd045 100644
--- a/window/window/src/main/java/androidx/window/embedding/ActivityEmbeddingController.kt
+++ b/window/window/src/main/java/androidx/window/embedding/ActivityEmbeddingController.kt
@@ -20,9 +20,13 @@
import android.app.ActivityOptions
import android.content.Context
import android.os.IBinder
+import androidx.window.RequiresWindowSdkExtension
+import androidx.window.WindowSdkExtensions
import androidx.window.core.ExperimentalWindowApi
-/** The controller that allows checking the current [Activity] embedding status. */
+/**
+ * The controller that allows checking the current [Activity] embedding status.
+ */
class ActivityEmbeddingController internal constructor(private val backend: EmbeddingBackend) {
/**
* Checks if the [activity] is embedded and its presentation may be customized by the host
@@ -41,7 +45,7 @@
*
* @param activity The [Activity] to check.
* @return the [ActivityStack] that this [activity] is part of, or `null` if there is no such
- * [ActivityStack].
+ * [ActivityStack].
*/
@ExperimentalWindowApi
fun getActivityStack(activity: Activity): ActivityStack? =
@@ -53,6 +57,7 @@
* @param options The [android.app.ActivityOptions] to be updated.
* @param token The token of the [ActivityStack] to be set.
*/
+ @RequiresWindowSdkExtension(3)
internal fun setLaunchingActivityStack(
options: ActivityOptions,
token: IBinder
@@ -73,28 +78,20 @@
* will be expanded to fill the parent task container. This is useful to expand the primary
* container as the sample linked below shows.
*
- * **Note** that it's caller's responsibility to check whether this API is supported by calling
- * [isFinishingActivityStacksSupported]. If not, an alternative approach to finishing all
- * containers above a particular activity can be to launch it again with flag
- * [android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP].
+ * **Note** that it's caller's responsibility to check whether this API is supported by checking
+ * [WindowSdkExtensions.extensionVersion] is greater than or equal to 3. If not, an alternative
+ * approach to finishing all containers above a particular activity can be to launch it again
+ * with flag [android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP].
*
* @param activityStacks The set of [ActivityStack] to be finished.
- * @throws UnsupportedOperationException if this device doesn't support this API and
- * [isFinishingActivityStacksSupported] returns `false`.
+ * @throws UnsupportedOperationException if extension version is less than 3.
* @sample androidx.window.samples.embedding.expandPrimaryContainer
*/
@ExperimentalWindowApi
- fun finishActivityStacks(activityStacks: Set<ActivityStack>) =
+ @RequiresWindowSdkExtension(3)
+ fun finishActivityStacks(activityStacks: Set<ActivityStack>) {
backend.finishActivityStacks(activityStacks)
-
- /**
- * Checks whether [finishActivityStacks] is supported.
- *
- * @return `true` if [finishActivityStacks] is supported on the device, `false` otherwise.
- */
- @ExperimentalWindowApi
- fun isFinishingActivityStacksSupported(): Boolean =
- backend.isFinishActivityStacksSupported()
+ }
companion object {
/**
diff --git a/window/window/src/main/java/androidx/window/embedding/ActivityEmbeddingOptions.kt b/window/window/src/main/java/androidx/window/embedding/ActivityEmbeddingOptions.kt
index 190ffa3..8f5b66b 100644
--- a/window/window/src/main/java/androidx/window/embedding/ActivityEmbeddingOptions.kt
+++ b/window/window/src/main/java/androidx/window/embedding/ActivityEmbeddingOptions.kt
@@ -20,34 +20,29 @@
import android.app.Activity
import android.app.ActivityOptions
import android.content.Context
+import androidx.window.RequiresWindowSdkExtension
+import androidx.window.WindowSdkExtensions
import androidx.window.core.ExperimentalWindowApi
-import androidx.window.core.ExtensionsUtil
-import androidx.window.extensions.WindowExtensions
/**
* Sets the launching [ActivityStack] to the given [android.app.ActivityOptions].
*
* If the device doesn't support setting launching, [UnsupportedOperationException] will be thrown.
- * @see isSetLaunchingActivityStackSupported
*
* @param context The [android.content.Context] that is going to be used for launching
* activity with this [android.app.ActivityOptions], which is usually be the [android.app.Activity]
* of the app that hosts the task.
* @param activityStack The target [ActivityStack] for launching.
- * @throws UnsupportedOperationException if this device doesn't support this API.
+ * @throws UnsupportedOperationException if [WindowSdkExtensions.extensionVersion] is less than 3.
*/
@ExperimentalWindowApi
+@RequiresWindowSdkExtension(3)
fun ActivityOptions.setLaunchingActivityStack(
context: Context,
activityStack: ActivityStack
): ActivityOptions = let {
- if (!isSetLaunchingActivityStackSupported()) {
- throw UnsupportedOperationException("#setLaunchingActivityStack is not " +
- "supported on the device.")
- } else {
- ActivityEmbeddingController.getInstance(context)
- .setLaunchingActivityStack(this, activityStack.token)
- }
+ ActivityEmbeddingController.getInstance(context)
+ .setLaunchingActivityStack(this, activityStack.token)
}
/**
@@ -57,13 +52,12 @@
*
* If the device doesn't support setting launching or no available [ActivityStack]
* can be found from the given [activity], [UnsupportedOperationException] will be thrown.
- * @see isSetLaunchingActivityStackSupported
*
* @param activity The existing [android.app.Activity] on the target [ActivityStack].
- * @throws UnsupportedOperationException if this device doesn't support this API or no
- * available [ActivityStack] can be found.
+ * @throws UnsupportedOperationException if [WindowSdkExtensions.extensionVersion] is less than 3.
*/
@ExperimentalWindowApi
+@RequiresWindowSdkExtension(3)
fun ActivityOptions.setLaunchingActivityStack(activity: Activity): ActivityOptions {
val activityStack =
ActivityEmbeddingController.getInstance(activity).getActivityStack(activity)
@@ -74,12 +68,3 @@
"The given activity may not be embedded.")
}
}
-
-/**
- * Return `true` if the [setLaunchingActivityStack] APIs is supported and can be used
- * to set the launching [ActivityStack]. Otherwise, return `false`.
- */
-@ExperimentalWindowApi
-fun ActivityOptions.isSetLaunchingActivityStackSupported(): Boolean {
- return ExtensionsUtil.safeVendorApiLevel >= WindowExtensions.VENDOR_API_LEVEL_3
-}
diff --git a/window/window/src/main/java/androidx/window/embedding/EmbeddingAdapter.kt b/window/window/src/main/java/androidx/window/embedding/EmbeddingAdapter.kt
index ce56396..2462f83 100644
--- a/window/window/src/main/java/androidx/window/embedding/EmbeddingAdapter.kt
+++ b/window/window/src/main/java/androidx/window/embedding/EmbeddingAdapter.kt
@@ -24,7 +24,7 @@
import android.util.LayoutDirection
import android.util.Pair as AndroidPair
import android.view.WindowMetrics
-import androidx.window.core.ExtensionsUtil
+import androidx.window.WindowSdkExtensions
import androidx.window.core.PredicateAdapter
import androidx.window.embedding.SplitAttributes.LayoutDirection.Companion.BOTTOM_TO_TOP
import androidx.window.embedding.SplitAttributes.LayoutDirection.Companion.LEFT_TO_RIGHT
@@ -36,7 +36,6 @@
import androidx.window.embedding.SplitAttributes.SplitType.Companion.SPLIT_TYPE_EXPAND
import androidx.window.embedding.SplitAttributes.SplitType.Companion.SPLIT_TYPE_HINGE
import androidx.window.embedding.SplitAttributes.SplitType.Companion.ratio
-import androidx.window.extensions.WindowExtensions
import androidx.window.extensions.core.util.function.Function
import androidx.window.extensions.core.util.function.Predicate
import androidx.window.extensions.embedding.ActivityRule as OEMActivityRule
@@ -63,7 +62,8 @@
internal class EmbeddingAdapter(
private val predicateAdapter: PredicateAdapter
) {
- private val vendorApiLevel = ExtensionsUtil.safeVendorApiLevel
+ private val vendorApiLevel
+ get() = WindowSdkExtensions.getInstance().extensionVersion
private val api1Impl = VendorApiLevel1Impl(predicateAdapter)
private val api2Impl = VendorApiLevel2Impl()
@@ -73,8 +73,8 @@
private fun translate(splitInfo: OEMSplitInfo): SplitInfo {
return when (vendorApiLevel) {
- WindowExtensions.VENDOR_API_LEVEL_1 -> api1Impl.translateCompat(splitInfo)
- WindowExtensions.VENDOR_API_LEVEL_2 -> api2Impl.translateCompat(splitInfo)
+ 1 -> api1Impl.translateCompat(splitInfo)
+ 2 -> api2Impl.translateCompat(splitInfo)
else -> {
val primaryActivityStack = splitInfo.primaryActivityStack
val secondaryActivityStack = splitInfo.secondaryActivityStack
@@ -152,7 +152,7 @@
rule: SplitPairRule,
predicateClass: Class<*>
): OEMSplitPairRule {
- if (vendorApiLevel < WindowExtensions.VENDOR_API_LEVEL_2) {
+ if (vendorApiLevel < 2) {
return api1Impl.translateSplitPairRuleCompat(context, rule, predicateClass)
} else {
val activitiesPairPredicate =
@@ -194,7 +194,7 @@
}
fun translateSplitAttributes(splitAttributes: SplitAttributes): OEMSplitAttributes {
- require(vendorApiLevel >= WindowExtensions.VENDOR_API_LEVEL_2)
+ require(vendorApiLevel >= 2)
// To workaround the "unused" error in ktlint. It is necessary to translate SplitAttributes
// from WM Jetpack version to WM extension version.
return androidx.window.extensions.embedding.SplitAttributes.Builder()
@@ -215,7 +215,7 @@
}
private fun translateSplitType(splitType: SplitType): OEMSplitType {
- require(vendorApiLevel >= WindowExtensions.VENDOR_API_LEVEL_2)
+ require(vendorApiLevel >= 2)
return when (splitType) {
SPLIT_TYPE_HINGE -> OEMSplitType.HingeSplitType(
translateSplitType(SPLIT_TYPE_EQUAL)
@@ -238,7 +238,7 @@
rule: SplitPlaceholderRule,
predicateClass: Class<*>
): OEMSplitPlaceholderRule {
- if (vendorApiLevel < WindowExtensions.VENDOR_API_LEVEL_2) {
+ if (vendorApiLevel < 2) {
return api1Impl.translateSplitPlaceholderRuleCompat(
context,
rule,
@@ -285,7 +285,7 @@
rule: ActivityRule,
predicateClass: Class<*>
): OEMActivityRule {
- if (vendorApiLevel < WindowExtensions.VENDOR_API_LEVEL_2) {
+ if (vendorApiLevel < 2) {
return api1Impl.translateActivityRuleCompat(rule, predicateClass)
} else {
val activityPredicate = Predicate<Activity> { activity ->
@@ -317,6 +317,7 @@
}.toSet()
}
+ /** Provides backward compatibility for Window extensions with API level 2 */
private inner class VendorApiLevel2Impl {
fun translateCompat(splitInfo: OEMSplitInfo): SplitInfo {
val primaryActivityStack = splitInfo.primaryActivityStack
@@ -342,15 +343,13 @@
}
/**
- * Provides backward compatibility for Window extensions with
- * [WindowExtensions.VENDOR_API_LEVEL_1]
- * @see WindowExtensions.getVendorApiLevel
+ * Provides backward compatibility for [WindowSdkExtensions] version 1
*/
// Suppress deprecation because this object is to provide backward compatibility.
@Suppress("DEPRECATION")
private inner class VendorApiLevel1Impl(val predicateAdapter: PredicateAdapter) {
/**
- * Obtains [SplitAttributes] from [OEMSplitInfo] with [WindowExtensions.VENDOR_API_LEVEL_1]
+ * Obtains [SplitAttributes] from [OEMSplitInfo] with [WindowSdkExtensions] version 1
*/
fun getSplitAttributesCompat(splitInfo: OEMSplitInfo): SplitAttributes =
SplitAttributes.Builder()
@@ -472,9 +471,8 @@
}
/**
- * Returns `true` if `attrs` is compatible with [WindowExtensions.VENDOR_API_LEVEL_1] and
- * doesn't use the new features introduced in [WindowExtensions.VENDOR_API_LEVEL_2] or
- * higher.
+ * Returns `true` if `attrs` is compatible with vendor API level 1 and
+ * doesn't use the new features introduced in vendor API level 2 or higher.
*/
private fun isSplitAttributesSupported(attrs: SplitAttributes) =
attrs.splitType.value in 0.0..1.0 && attrs.splitType.value != 1.0f &&
@@ -519,12 +517,12 @@
internal companion object {
/**
* The default token of [SplitInfo], which provides compatibility for device prior to
- * [WindowExtensions.VENDOR_API_LEVEL_3]
+ * vendor API level 3
*/
val INVALID_SPLIT_INFO_TOKEN = Binder()
/**
* The default token of [ActivityStack], which provides compatibility for device prior to
- * [WindowExtensions.VENDOR_API_LEVEL_3]
+ * vendor API level 3
*/
val INVALID_ACTIVITY_STACK_TOKEN = Binder()
}
diff --git a/window/window/src/main/java/androidx/window/embedding/EmbeddingBackend.kt b/window/window/src/main/java/androidx/window/embedding/EmbeddingBackend.kt
index 22b6333..c1b09d0 100644
--- a/window/window/src/main/java/androidx/window/embedding/EmbeddingBackend.kt
+++ b/window/window/src/main/java/androidx/window/embedding/EmbeddingBackend.kt
@@ -22,6 +22,7 @@
import android.os.IBinder
import androidx.annotation.RestrictTo
import androidx.core.util.Consumer
+import androidx.window.RequiresWindowSdkExtension
import java.util.concurrent.Executor
/**
@@ -50,28 +51,28 @@
fun isActivityEmbedded(activity: Activity): Boolean
+ @RequiresWindowSdkExtension(2)
fun setSplitAttributesCalculator(
calculator: (SplitAttributesCalculatorParams) -> SplitAttributes
)
+ @RequiresWindowSdkExtension(2)
fun clearSplitAttributesCalculator()
- fun isSplitAttributesCalculatorSupported(): Boolean
-
fun getActivityStack(activity: Activity): ActivityStack?
+ @RequiresWindowSdkExtension(3)
fun setLaunchingActivityStack(options: ActivityOptions, token: IBinder): ActivityOptions
+ @RequiresWindowSdkExtension(3)
fun finishActivityStacks(activityStacks: Set<ActivityStack>)
- fun isFinishActivityStacksSupported(): Boolean
-
+ @RequiresWindowSdkExtension(3)
fun invalidateTopVisibleSplitAttributes()
+ @RequiresWindowSdkExtension(3)
fun updateSplitAttributes(splitInfo: SplitInfo, splitAttributes: SplitAttributes)
- fun areSplitAttributesUpdatesSupported(): Boolean
-
companion object {
private var decorator: (EmbeddingBackend) -> EmbeddingBackend =
diff --git a/window/window/src/main/java/androidx/window/embedding/EmbeddingCompat.kt b/window/window/src/main/java/androidx/window/embedding/EmbeddingCompat.kt
index 2c543f3..a4b7166 100644
--- a/window/window/src/main/java/androidx/window/embedding/EmbeddingCompat.kt
+++ b/window/window/src/main/java/androidx/window/embedding/EmbeddingCompat.kt
@@ -21,6 +21,8 @@
import android.content.Context
import android.os.IBinder
import android.util.Log
+import androidx.window.RequiresWindowSdkExtension
+import androidx.window.WindowSdkExtensions
import androidx.window.core.BuildConfig
import androidx.window.core.ConsumerAdapter
import androidx.window.core.ExtensionsUtil
@@ -28,7 +30,6 @@
import androidx.window.embedding.EmbeddingInterfaceCompat.EmbeddingCallbackInterface
import androidx.window.embedding.SplitController.SplitSupportStatus.Companion.SPLIT_AVAILABLE
import androidx.window.extensions.WindowExtensions.VENDOR_API_LEVEL_2
-import androidx.window.extensions.WindowExtensions.VENDOR_API_LEVEL_3
import androidx.window.extensions.WindowExtensionsProvider
import androidx.window.extensions.core.util.function.Consumer
import androidx.window.extensions.embedding.ActivityEmbeddingComponent
@@ -39,7 +40,7 @@
* Adapter implementation for different historical versions of activity embedding OEM interface in
* [ActivityEmbeddingComponent]. Only supports the single current version in this implementation.
*/
-internal class EmbeddingCompat constructor(
+internal class EmbeddingCompat(
private val embeddingExtension: ActivityEmbeddingComponent,
private val adapter: EmbeddingAdapter,
private val consumerAdapter: ConsumerAdapter,
@@ -92,70 +93,59 @@
return embeddingExtension.isActivityEmbedded(activity)
}
+ @RequiresWindowSdkExtension(2)
override fun setSplitAttributesCalculator(
calculator: (SplitAttributesCalculatorParams) -> SplitAttributes
) {
- if (!isSplitAttributesCalculatorSupported()) {
- throw UnsupportedOperationException("#setSplitAttributesCalculator is not supported " +
- "on the device.")
- }
+ WindowSdkExtensions.getInstance().requireExtensionVersion(2)
+
embeddingExtension.setSplitAttributesCalculator(
adapter.translateSplitAttributesCalculator(calculator)
)
}
+ @RequiresWindowSdkExtension(2)
override fun clearSplitAttributesCalculator() {
- if (!isSplitAttributesCalculatorSupported()) {
- throw UnsupportedOperationException("#clearSplitAttributesCalculator is not " +
- "supported on the device.")
- }
+ WindowSdkExtensions.getInstance().requireExtensionVersion(2)
+
embeddingExtension.clearSplitAttributesCalculator()
}
- override fun isSplitAttributesCalculatorSupported(): Boolean =
- ExtensionsUtil.safeVendorApiLevel >= VENDOR_API_LEVEL_2
-
+ @RequiresWindowSdkExtension(3)
override fun finishActivityStacks(activityStacks: Set<ActivityStack>) {
- if (!isFinishActivityStacksSupported()) {
- throw UnsupportedOperationException("#finishActivityStacks is not " +
- "supported on the device.")
- }
+ WindowSdkExtensions.getInstance().requireExtensionVersion(3)
+
val stackTokens = activityStacks.mapTo(mutableSetOf()) { it.token }
embeddingExtension.finishActivityStacks(stackTokens)
}
- override fun isFinishActivityStacksSupported(): Boolean =
- ExtensionsUtil.safeVendorApiLevel >= VENDOR_API_LEVEL_3
-
+ @RequiresWindowSdkExtension(3)
override fun invalidateTopVisibleSplitAttributes() {
- if (!areSplitAttributesUpdatesSupported()) {
- throw UnsupportedOperationException("#invalidateTopVisibleSplitAttributes is not " +
- "supported on the device.")
- }
+ WindowSdkExtensions.getInstance().requireExtensionVersion(3)
+
embeddingExtension.invalidateTopVisibleSplitAttributes()
}
+ @RequiresWindowSdkExtension(3)
override fun updateSplitAttributes(
splitInfo: SplitInfo,
splitAttributes: SplitAttributes
) {
- if (!areSplitAttributesUpdatesSupported()) {
- throw UnsupportedOperationException("#updateSplitAttributes is not supported on the " +
- "device.")
- }
+ WindowSdkExtensions.getInstance().requireExtensionVersion(3)
+
embeddingExtension.updateSplitAttributes(
splitInfo.token,
adapter.translateSplitAttributes(splitAttributes)
)
}
- override fun areSplitAttributesUpdatesSupported(): Boolean =
- ExtensionsUtil.safeVendorApiLevel >= VENDOR_API_LEVEL_3
-
+ @RequiresWindowSdkExtension(3)
override fun setLaunchingActivityStack(
options: ActivityOptions,
token: IBinder
): ActivityOptions {
+ WindowSdkExtensions.getInstance().requireExtensionVersion(3)
+
return embeddingExtension.setLaunchingActivityStack(options, token)
}
diff --git a/window/window/src/main/java/androidx/window/embedding/EmbeddingInterfaceCompat.kt b/window/window/src/main/java/androidx/window/embedding/EmbeddingInterfaceCompat.kt
index ccfba1d..e176a02 100644
--- a/window/window/src/main/java/androidx/window/embedding/EmbeddingInterfaceCompat.kt
+++ b/window/window/src/main/java/androidx/window/embedding/EmbeddingInterfaceCompat.kt
@@ -19,6 +19,7 @@
import android.app.Activity
import android.app.ActivityOptions
import android.os.IBinder
+import androidx.window.RequiresWindowSdkExtension
import androidx.window.extensions.embedding.ActivityEmbeddingComponent
/**
@@ -37,23 +38,23 @@
fun isActivityEmbedded(activity: Activity): Boolean
+ @RequiresWindowSdkExtension(2)
fun setSplitAttributesCalculator(
calculator: (SplitAttributesCalculatorParams) -> SplitAttributes
)
+ @RequiresWindowSdkExtension(2)
fun clearSplitAttributesCalculator()
- fun isSplitAttributesCalculatorSupported(): Boolean
-
+ @RequiresWindowSdkExtension(3)
fun setLaunchingActivityStack(options: ActivityOptions, token: IBinder): ActivityOptions
+ @RequiresWindowSdkExtension(3)
fun finishActivityStacks(activityStacks: Set<ActivityStack>)
- fun isFinishActivityStacksSupported(): Boolean
-
+ @RequiresWindowSdkExtension(3)
fun invalidateTopVisibleSplitAttributes()
+ @RequiresWindowSdkExtension(3)
fun updateSplitAttributes(splitInfo: SplitInfo, splitAttributes: SplitAttributes)
-
- fun areSplitAttributesUpdatesSupported(): Boolean
}
diff --git a/window/window/src/main/java/androidx/window/embedding/ExtensionEmbeddingBackend.kt b/window/window/src/main/java/androidx/window/embedding/ExtensionEmbeddingBackend.kt
index c7f4c3b..a53a98c 100644
--- a/window/window/src/main/java/androidx/window/embedding/ExtensionEmbeddingBackend.kt
+++ b/window/window/src/main/java/androidx/window/embedding/ExtensionEmbeddingBackend.kt
@@ -29,6 +29,7 @@
import androidx.annotation.VisibleForTesting
import androidx.collection.ArraySet
import androidx.core.util.Consumer
+import androidx.window.RequiresWindowSdkExtension
import androidx.window.WindowProperties
import androidx.window.core.BuildConfig
import androidx.window.core.ConsumerAdapter
@@ -336,6 +337,7 @@
return embeddingExtension?.isActivityEmbedded(activity) ?: false
}
+ @RequiresWindowSdkExtension(2)
override fun setSplitAttributesCalculator(
calculator: (SplitAttributesCalculatorParams) -> SplitAttributes
) {
@@ -344,15 +346,13 @@
}
}
+ @RequiresWindowSdkExtension(2)
override fun clearSplitAttributesCalculator() {
globalLock.withLock {
embeddingExtension?.clearSplitAttributesCalculator()
}
}
- override fun isSplitAttributesCalculatorSupported(): Boolean =
- embeddingExtension?.isSplitAttributesCalculatorSupported() ?: false
-
override fun getActivityStack(activity: Activity): ActivityStack? {
globalLock.withLock {
val lastInfo: List<SplitInfo> = splitInfoEmbeddingCallback.lastInfo ?: return null
@@ -371,22 +371,23 @@
}
}
+ @RequiresWindowSdkExtension(3)
override fun setLaunchingActivityStack(
options: ActivityOptions,
token: IBinder
): ActivityOptions = embeddingExtension?.setLaunchingActivityStack(options, token) ?: options
+ @RequiresWindowSdkExtension(3)
override fun finishActivityStacks(activityStacks: Set<ActivityStack>) {
embeddingExtension?.finishActivityStacks(activityStacks)
}
- override fun isFinishActivityStacksSupported(): Boolean =
- embeddingExtension?.isFinishActivityStacksSupported() ?: false
-
+ @RequiresWindowSdkExtension(3)
override fun invalidateTopVisibleSplitAttributes() {
embeddingExtension?.invalidateTopVisibleSplitAttributes()
}
+ @RequiresWindowSdkExtension(3)
override fun updateSplitAttributes(
splitInfo: SplitInfo,
splitAttributes: SplitAttributes
@@ -394,8 +395,6 @@
embeddingExtension?.updateSplitAttributes(splitInfo, splitAttributes)
}
- override fun areSplitAttributesUpdatesSupported(): Boolean =
- embeddingExtension?.areSplitAttributesUpdatesSupported() ?: false
@RequiresApi(31)
private object Api31Impl {
@DoNotInline
diff --git a/window/window/src/main/java/androidx/window/embedding/RuleController.kt b/window/window/src/main/java/androidx/window/embedding/RuleController.kt
index 192f3ec..8943726 100644
--- a/window/window/src/main/java/androidx/window/embedding/RuleController.kt
+++ b/window/window/src/main/java/androidx/window/embedding/RuleController.kt
@@ -27,6 +27,7 @@
* - [setRules]
* - [parseRules]
* - [clearRules]
+ * - [getRules]
*
* **Note** that this class is recommended to be configured in [androidx.startup.Initializer] or
* [android.app.Application.onCreate], so that the rules are applied early in the application
diff --git a/window/window/src/main/java/androidx/window/embedding/SplitAttributes.kt b/window/window/src/main/java/androidx/window/embedding/SplitAttributes.kt
index 3e22b1a..ac627fc 100644
--- a/window/window/src/main/java/androidx/window/embedding/SplitAttributes.kt
+++ b/window/window/src/main/java/androidx/window/embedding/SplitAttributes.kt
@@ -21,6 +21,7 @@
import androidx.annotation.IntRange
import androidx.annotation.RestrictTo
import androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP
+import androidx.window.WindowSdkExtensions
import androidx.window.core.SpecificationComputer.Companion.startSpecification
import androidx.window.core.VerificationMode
import androidx.window.embedding.SplitAttributes.LayoutDirection.Companion.LOCALE
@@ -307,7 +308,8 @@
* <img width="70%" height="70%" src="/images/guide/topics/large-screens/activity-embedding/reference-docs/a_to_a_b_ttb.png" alt="Activity A starts activity B to the bottom."/>
*
* If the horizontal layout direction is not supported on the
- * device, layout direction falls back to `LOCALE`.
+ * device that [WindowSdkExtensions.extensionVersion] is less than 2, layout direction
+ * falls back to `LOCALE`.
*
* See also [layoutDirection].
*/
@@ -323,7 +325,8 @@
* <img width="70%" height="70%" src="/images/guide/topics/large-screens/activity-embedding/reference-docs/a_to_a_b_btt.png" alt="Activity A starts activity B to the top."/>
*
* If the horizontal layout direction is not supported on the
- * device, layout direction falls back to `LOCALE`.
+ * device that [WindowSdkExtensions.extensionVersion] is less than 2, layout direction
+ * falls back to `LOCALE`.
*
* See also [layoutDirection].
*/
diff --git a/window/window/src/main/java/androidx/window/embedding/SplitController.kt b/window/window/src/main/java/androidx/window/embedding/SplitController.kt
index e478ff7..063bc0c 100644
--- a/window/window/src/main/java/androidx/window/embedding/SplitController.kt
+++ b/window/window/src/main/java/androidx/window/embedding/SplitController.kt
@@ -19,7 +19,9 @@
import android.app.Activity
import android.content.Context
import androidx.core.util.Consumer
+import androidx.window.RequiresWindowSdkExtension
import androidx.window.WindowProperties
+import androidx.window.WindowSdkExtensions
import androidx.window.core.ExperimentalWindowApi
import androidx.window.layout.WindowMetrics
import kotlinx.coroutines.channels.awaitClose
@@ -27,17 +29,17 @@
import kotlinx.coroutines.flow.callbackFlow
/**
-* The controller class that gets information about the currently active activity
-* splits and provides interaction points to customize the splits and form new
-* splits.
-*
-* A split is a pair of containers that host activities in the same or different
-* processes, combined under the same parent window of the hosting task.
-*
-* A pair of activities can be put into a split by providing a static or runtime
-* split rule and then launching the activities in the same task using
-* [Activity.startActivity()][android.app.Activity.startActivity].
-*/
+ * The controller class that gets information about the currently active activity
+ * splits and provides interaction points to customize the splits and form new
+ * splits.
+ *
+ * A split is a pair of containers that host activities in the same or different
+ * processes, combined under the same parent window of the hosting task.
+ *
+ * A pair of activities can be put into a split by providing a static or runtime
+ * split rule and then launching the activities in the same task using
+ * [Activity.startActivity()][android.app.Activity.startActivity].
+ */
class SplitController internal constructor(private val embeddingBackend: EmbeddingBackend) {
/**
@@ -86,8 +88,8 @@
/**
* Sets or replaces the previously registered [SplitAttributes] calculator.
*
- * **Note** that it's callers' responsibility to check if this API is supported by calling
- * [isSplitAttributesCalculatorSupported] before using the this API. It is suggested to always
+ * **Note** that it's callers' responsibility to check if this API is supported by checking
+ * [WindowSdkExtensions.extensionVersion] before using the this API. It is suggested to always
* set meaningful [SplitRule.defaultSplitAttributes] in case this API is not supported on some
* devices.
*
@@ -124,9 +126,10 @@
* @sample androidx.window.samples.embedding.splitAttributesCalculatorSample
* @param calculator the function to calculate [SplitAttributes] based on the
* [SplitAttributesCalculatorParams]. It will replace the previously set if it exists.
- * @throws UnsupportedOperationException if [isSplitAttributesCalculatorSupported] reports
- * `false`
+ * @throws UnsupportedOperationException if [WindowSdkExtensions.extensionVersion]
+ * is less than 2.
*/
+ @RequiresWindowSdkExtension(2)
fun setSplitAttributesCalculator(
calculator: (SplitAttributesCalculatorParams) -> SplitAttributes
) {
@@ -135,19 +138,17 @@
/**
* Clears the callback previously set by [setSplitAttributesCalculator].
- * The caller **must** make sure [isSplitAttributesCalculatorSupported] before invoking.
+ * The caller **must** make sure if [WindowSdkExtensions.extensionVersion] is greater than
+ * or equal to 2.
*
- * @throws UnsupportedOperationException if [isSplitAttributesCalculatorSupported] reports
- * `false`
+ * @throws UnsupportedOperationException if [WindowSdkExtensions.extensionVersion]
+ * is less than 2.
*/
+ @RequiresWindowSdkExtension(2)
fun clearSplitAttributesCalculator() {
embeddingBackend.clearSplitAttributesCalculator()
}
- /** Returns whether [setSplitAttributesCalculator] is supported or not. */
- fun isSplitAttributesCalculatorSupported(): Boolean =
- embeddingBackend.isSplitAttributesCalculatorSupported()
-
/**
* Triggers a [SplitAttributes] update callback for the current topmost and visible split layout
* if there is one. This method can be used when a change to the split presentation originates
@@ -160,23 +161,14 @@
*
* The call will be ignored if there is no visible split.
*
- * @throws UnsupportedOperationException if the device doesn't support this API.
+ * @throws UnsupportedOperationException if [WindowSdkExtensions.extensionVersion]
+ * is less than 3.
*/
@ExperimentalWindowApi
- fun invalidateTopVisibleSplitAttributes() =
+ @RequiresWindowSdkExtension(3)
+ fun invalidateTopVisibleSplitAttributes() {
embeddingBackend.invalidateTopVisibleSplitAttributes()
-
- /**
- * Checks whether [invalidateTopVisibleSplitAttributes] is supported on the device.
- *
- * Invoking these APIs if the feature is not supported would trigger an
- * [UnsupportedOperationException].
- * @return `true` if the runtime APIs to update [SplitAttributes] are supported and can be
- * called safely, `false` otherwise.
- */
- @ExperimentalWindowApi
- fun isInvalidatingTopVisibleSplitAttributesSupported(): Boolean =
- embeddingBackend.areSplitAttributesUpdatesSupported()
+ }
/**
* Updates the [SplitAttributes] of a split pair. This is an alternative to using
@@ -198,23 +190,14 @@
*
* @param splitInfo the split pair to update
* @param splitAttributes the [SplitAttributes] to be applied
- * @throws UnsupportedOperationException if this device doesn't support this API
+ * @throws UnsupportedOperationException if [WindowSdkExtensions.extensionVersion]
+ * is less than 3.
*/
@ExperimentalWindowApi
- fun updateSplitAttributes(splitInfo: SplitInfo, splitAttributes: SplitAttributes) =
+ @RequiresWindowSdkExtension(3)
+ fun updateSplitAttributes(splitInfo: SplitInfo, splitAttributes: SplitAttributes) {
embeddingBackend.updateSplitAttributes(splitInfo, splitAttributes)
-
- /**
- * Checks whether [updateSplitAttributes] is supported on the device.
- *
- * Invoking these APIs if the feature is not supported would trigger an
- * [UnsupportedOperationException].
- * @return `true` if the runtime APIs to update [SplitAttributes] are supported and can be
- * called safely, `false` otherwise.
- */
- @ExperimentalWindowApi
- fun isUpdatingSplitAttributesSupported(): Boolean =
- embeddingBackend.areSplitAttributesUpdatesSupported()
+ }
/**
* A class to determine if activity splits with Activity Embedding are currently available.
diff --git a/window/window/src/main/java/androidx/window/layout/WindowInfoTracker.kt b/window/window/src/main/java/androidx/window/layout/WindowInfoTracker.kt
index 60eb71e..06ca64f 100644
--- a/window/window/src/main/java/androidx/window/layout/WindowInfoTracker.kt
+++ b/window/window/src/main/java/androidx/window/layout/WindowInfoTracker.kt
@@ -23,6 +23,7 @@
import androidx.annotation.RestrictTo
import androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP
import androidx.annotation.UiContext
+import androidx.window.WindowSdkExtensions
import androidx.window.core.ConsumerAdapter
import androidx.window.layout.adapter.WindowBackend
import androidx.window.layout.adapter.extensions.ExtensionWindowBackend
@@ -53,6 +54,9 @@
* Obtaining a [WindowInfoTracker] through [WindowInfoTracker.getOrCreate] guarantees having a
* default implementation for this method.
*
+ * If the passed [context] is not an [Activity] and [WindowSdkExtensions.extensionVersion]
+ * is less than 2, the flow will return empty [WindowLayoutInfo] list flow.
+ *
* @param context a [UiContext] such as an [Activity], an [InputMethodService], or an instance
* created via [Context.createWindowContext] that listens to configuration changes.
* @see WindowLayoutInfo
diff --git a/window/window/src/test/java/androidx/window/StubWindowSdkExtensions.kt b/window/window/src/test/java/androidx/window/StubWindowSdkExtensions.kt
new file mode 100644
index 0000000..3ffe46f
--- /dev/null
+++ b/window/window/src/test/java/androidx/window/StubWindowSdkExtensions.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window
+
+import androidx.annotation.IntRange
+
+internal class StubWindowSdkExtensions : WindowSdkExtensions() {
+ override val extensionVersion: Int
+ get() = overrideVersion
+
+ private var overrideVersion: Int = 0
+
+ internal fun overrideExtensionVersion(@IntRange(from = 0) version: Int) {
+ overrideVersion = version
+ }
+}
diff --git a/window/window/src/test/java/androidx/window/WindowSdkExtensionsRule.kt b/window/window/src/test/java/androidx/window/WindowSdkExtensionsRule.kt
new file mode 100644
index 0000000..a2826e2
--- /dev/null
+++ b/window/window/src/test/java/androidx/window/WindowSdkExtensionsRule.kt
@@ -0,0 +1,67 @@
+/**
+*
+* Copyright 2023 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package androidx.window
+
+import androidx.annotation.IntRange
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * Test rule for overriding [WindowSdkExtensions] properties.
+ *
+ * This should mainly be used for validating the behavior with a simplified version of
+ * [WindowSdkExtensions] in unit tests.
+ * For on-device Android tests, it's highly suggested to respect
+ * the device's [WindowSdkExtensions.extensionVersion]. Overriding the real device's is error-prone,
+ * and may lead to unexpected behavior.
+ */
+class WindowSdkExtensionsRule : TestRule {
+
+ private val mStubWindowSdkExtensions = StubWindowSdkExtensions()
+
+ override fun apply(
+ @Suppress("InvalidNullabilityOverride") // JUnit missing annotations
+ base: Statement,
+ @Suppress("InvalidNullabilityOverride") // JUnit missing annotations
+ description: Description
+ ): Statement {
+ return object : Statement() {
+ override fun evaluate() {
+ WindowSdkExtensions.overrideDecorator(object : WindowSdkExtensionsDecorator {
+ override fun decorate(windowSdkExtensions: WindowSdkExtensions):
+ WindowSdkExtensions = mStubWindowSdkExtensions
+ })
+ try {
+ base.evaluate()
+ } finally {
+ WindowSdkExtensions.reset()
+ }
+ }
+ }
+ }
+
+ /**
+ * Overrides [WindowSdkExtensions.extensionVersion] for testing.
+ *
+ * @param version The extension version to override
+ */
+ fun overrideExtensionVersion(@IntRange(from = 0) version: Int) {
+ mStubWindowSdkExtensions.overrideExtensionVersion(version)
+ }
+}
diff --git a/window/window/src/test/java/androidx/window/embedding/ActivityEmbeddingControllerTest.kt b/window/window/src/test/java/androidx/window/embedding/ActivityEmbeddingControllerTest.kt
index c4cf0b5..6d950f6 100644
--- a/window/window/src/test/java/androidx/window/embedding/ActivityEmbeddingControllerTest.kt
+++ b/window/window/src/test/java/androidx/window/embedding/ActivityEmbeddingControllerTest.kt
@@ -19,6 +19,7 @@
import android.app.Activity
import android.content.Context
import android.os.Binder
+import androidx.window.core.ExperimentalWindowApi
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
@@ -61,7 +62,7 @@
}
@Test
- @OptIn(androidx.window.core.ExperimentalWindowApi::class)
+ @OptIn(ExperimentalWindowApi::class)
fun testGetActivityStack() {
val activityStack = ActivityStack(listOf(), true, Binder())
whenever(mockEmbeddingBackend.getActivityStack(mockActivity)).thenReturn(activityStack)
@@ -70,19 +71,7 @@
}
@Test
- @OptIn(androidx.window.core.ExperimentalWindowApi::class)
- fun testIsFinishingActivityStacksSupported() {
- whenever(mockEmbeddingBackend.isFinishActivityStacksSupported()).thenReturn(true)
-
- assertTrue(activityEmbeddingController.isFinishingActivityStacksSupported())
-
- whenever(mockEmbeddingBackend.isFinishActivityStacksSupported()).thenReturn(false)
-
- assertFalse(activityEmbeddingController.isFinishingActivityStacksSupported())
- }
-
- @Test
- @OptIn(androidx.window.core.ExperimentalWindowApi::class)
+ @OptIn(ExperimentalWindowApi::class)
fun testFinishActivityStacks() {
val activityStacks: Set<ActivityStack> = mock()
activityEmbeddingController.finishActivityStacks(activityStacks)
@@ -91,7 +80,7 @@
}
@Test
- @OptIn(androidx.window.core.ExperimentalWindowApi::class)
+ @OptIn(ExperimentalWindowApi::class)
fun testGetInstance() {
EmbeddingBackend.overrideDecorator(object : EmbeddingBackendDecorator {
override fun decorate(embeddingBackend: EmbeddingBackend): EmbeddingBackend =
diff --git a/window/window/src/test/java/androidx/window/embedding/ActivityEmbeddingOptionsTest.kt b/window/window/src/test/java/androidx/window/embedding/ActivityEmbeddingOptionsTest.kt
index d9c2127..aea6821 100644
--- a/window/window/src/test/java/androidx/window/embedding/ActivityEmbeddingOptionsTest.kt
+++ b/window/window/src/test/java/androidx/window/embedding/ActivityEmbeddingOptionsTest.kt
@@ -19,18 +19,12 @@
import android.app.Activity
import android.app.ActivityOptions
import android.content.Context
-import androidx.window.core.ExtensionsUtil
-import androidx.window.extensions.WindowExtensions
+import androidx.window.core.ExperimentalWindowApi
import org.junit.After
-import org.junit.Assert.assertFalse
-import org.junit.Assert.assertThrows
-import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
-import org.mockito.kotlin.any
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
-import org.mockito.kotlin.never
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@@ -38,9 +32,8 @@
* The unit tests for activity embedding extension functions to [ActivityOptions]
*
* @see [ActivityOptions.setLaunchingActivityStack]
- * @see [ActivityOptions.isSetLaunchingActivityStackSupported]
*/
-@OptIn(androidx.window.core.ExperimentalWindowApi::class)
+@OptIn(ExperimentalWindowApi::class)
class ActivityEmbeddingOptionsTest {
private lateinit var mockEmbeddingBackend: EmbeddingBackend
@@ -64,15 +57,11 @@
override fun decorate(embeddingBackend: EmbeddingBackend): EmbeddingBackend =
mockEmbeddingBackend
})
-
- // ActivityEmbeddingOptions is only supported since level 3
- ExtensionsUtil.setOverrideVendorApiLevel(WindowExtensions.VENDOR_API_LEVEL_3)
}
@After
fun tearDown() {
EmbeddingBackend.reset()
- ExtensionsUtil.resetOverrideVendorApiLevel()
}
@Test
@@ -90,25 +79,4 @@
verify(mockEmbeddingBackend).setLaunchingActivityStack(
mockActivityOptions, mockActivityStack.token)
}
-
- @Test
- fun testSetLaunchingActivityStack_unsupportedApiLevel() {
- ExtensionsUtil.setOverrideVendorApiLevel(WindowExtensions.VENDOR_API_LEVEL_2)
-
- assertThrows(UnsupportedOperationException::class.java) {
- mockActivityOptions.setLaunchingActivityStack(mockActivity, mockActivityStack)
- }
- verify(mockEmbeddingBackend, never()).setLaunchingActivityStack(any(), any())
- }
-
- @Test
- fun testIsSetLaunchingActivityStackSupported() {
- ExtensionsUtil.setOverrideVendorApiLevel(WindowExtensions.VENDOR_API_LEVEL_2)
-
- assertFalse(mockActivityOptions.isSetLaunchingActivityStackSupported())
-
- ExtensionsUtil.setOverrideVendorApiLevel(WindowExtensions.VENDOR_API_LEVEL_3)
-
- assertTrue(mockActivityOptions.isSetLaunchingActivityStackSupported())
- }
}
diff --git a/window/window/src/test/java/androidx/window/embedding/RequiresWindowSdkExtensionTests.kt b/window/window/src/test/java/androidx/window/embedding/RequiresWindowSdkExtensionTests.kt
new file mode 100644
index 0000000..7b0b830
--- /dev/null
+++ b/window/window/src/test/java/androidx/window/embedding/RequiresWindowSdkExtensionTests.kt
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.embedding
+
+import android.app.ActivityOptions
+import android.content.Context
+import android.os.Binder
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.window.RequiresWindowSdkExtension
+import androidx.window.WindowSdkExtensions
+import androidx.window.WindowSdkExtensionsRule
+import androidx.window.core.ConsumerAdapter
+import androidx.window.core.PredicateAdapter
+import androidx.window.embedding.EmbeddingAdapter.Companion.INVALID_ACTIVITY_STACK_TOKEN
+import androidx.window.embedding.EmbeddingAdapter.Companion.INVALID_SPLIT_INFO_TOKEN
+import androidx.window.extensions.core.util.function.Function
+import androidx.window.extensions.embedding.ActivityEmbeddingComponent
+import androidx.window.extensions.embedding.SplitAttributes as OemSplitAttributes
+import androidx.window.extensions.embedding.SplitAttributesCalculatorParams as OemSplitAttributesCalculatorParams
+import org.junit.After
+import org.junit.Assert.assertThrows
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+/**
+ * Verifies the behavior of [RequiresWindowSdkExtension]
+ * - If the [WindowSdkExtensions.extensionVersion] is greater than or equal to the minimum required
+ * version denoted in [RequiresWindowSdkExtension.version], the denoted API must be called
+ * successfully
+ * - Otherwise, [UnsupportedOperationException] must be thrown.
+ */
+@RequiresApi(Build.VERSION_CODES.M) // To call ActivityOptions.makeBasic()
+class RequiresWindowSdkExtensionTests {
+
+ @get:Rule
+ val testRule = WindowSdkExtensionsRule()
+
+ @Mock
+ private lateinit var embeddingExtension: ActivityEmbeddingComponent
+ @Mock
+ private lateinit var classLoader: ClassLoader
+ @Mock
+ private lateinit var applicationContext: Context
+ @Mock
+ private lateinit var activityOptions: ActivityOptions
+
+ private lateinit var mockAnnotations: AutoCloseable
+ private lateinit var embeddingCompat: EmbeddingCompat
+
+ @Before
+ fun setUp() {
+ mockAnnotations = MockitoAnnotations.openMocks(this)
+ embeddingCompat = EmbeddingCompat(
+ embeddingExtension,
+ EmbeddingAdapter(PredicateAdapter(classLoader)),
+ ConsumerAdapter(classLoader),
+ applicationContext
+ )
+
+ doReturn(activityOptions).whenever(embeddingExtension).setLaunchingActivityStack(
+ activityOptions,
+ INVALID_ACTIVITY_STACK_TOKEN
+ )
+ }
+
+ @After
+ fun tearDown() {
+ mockAnnotations.close()
+ }
+
+ @Test
+ fun testVendorApiLevel1() {
+ testRule.overrideExtensionVersion(1)
+
+ assertThrows(UnsupportedOperationException::class.java) {
+ embeddingCompat.setSplitAttributesCalculator { _ -> TEST_SPLIT_ATTRIBUTES }
+ }
+ verify(embeddingExtension, never()).setSplitAttributesCalculator(any())
+
+ assertThrows(UnsupportedOperationException::class.java) {
+ embeddingCompat.clearSplitAttributesCalculator()
+ }
+ verify(embeddingExtension, never()).clearSplitAttributesCalculator()
+
+ assertThrows(UnsupportedOperationException::class.java) {
+ embeddingCompat.setLaunchingActivityStack(activityOptions, Binder())
+ }
+ verify(embeddingExtension, never()).setLaunchingActivityStack(any(), any())
+
+ assertThrows(UnsupportedOperationException::class.java) {
+ embeddingCompat.finishActivityStacks(emptySet())
+ }
+ verify(embeddingExtension, never()).finishActivityStacks(any())
+
+ assertThrows(UnsupportedOperationException::class.java) {
+ embeddingCompat.updateSplitAttributes(TEST_SPLIT_INFO, TEST_SPLIT_ATTRIBUTES)
+ }
+ verify(embeddingExtension, never()).updateSplitAttributes(any(), any())
+
+ assertThrows(UnsupportedOperationException::class.java) {
+ embeddingCompat.invalidateTopVisibleSplitAttributes()
+ }
+ verify(embeddingExtension, never()).invalidateTopVisibleSplitAttributes()
+ }
+
+ @Test
+ fun testVendorApiLevel2() {
+ testRule.overrideExtensionVersion(2)
+
+ embeddingCompat.setSplitAttributesCalculator { _ -> TEST_SPLIT_ATTRIBUTES }
+ verify(embeddingExtension).setSplitAttributesCalculator(
+ any<Function<OemSplitAttributesCalculatorParams, OemSplitAttributes>>()
+ )
+
+ embeddingCompat.clearSplitAttributesCalculator()
+ verify(embeddingExtension).clearSplitAttributesCalculator()
+
+ assertThrows(UnsupportedOperationException::class.java) {
+ embeddingCompat.setLaunchingActivityStack(activityOptions, INVALID_ACTIVITY_STACK_TOKEN)
+ }
+ verify(embeddingExtension, never()).setLaunchingActivityStack(any(), any())
+
+ assertThrows(UnsupportedOperationException::class.java) {
+ embeddingCompat.finishActivityStacks(emptySet())
+ }
+ verify(embeddingExtension, never()).finishActivityStacks(any())
+
+ assertThrows(UnsupportedOperationException::class.java) {
+ embeddingCompat.updateSplitAttributes(TEST_SPLIT_INFO, TEST_SPLIT_ATTRIBUTES)
+ }
+ verify(embeddingExtension, never()).updateSplitAttributes(any(), any())
+
+ assertThrows(UnsupportedOperationException::class.java) {
+ embeddingCompat.invalidateTopVisibleSplitAttributes()
+ }
+ verify(embeddingExtension, never()).invalidateTopVisibleSplitAttributes()
+ }
+
+ @Test
+ fun testVendorApiLevel3() {
+ testRule.overrideExtensionVersion(3)
+
+ embeddingCompat.setSplitAttributesCalculator { _ -> TEST_SPLIT_ATTRIBUTES }
+ verify(embeddingExtension).setSplitAttributesCalculator(
+ any<Function<OemSplitAttributesCalculatorParams, OemSplitAttributes>>()
+ )
+
+ embeddingCompat.clearSplitAttributesCalculator()
+ verify(embeddingExtension).clearSplitAttributesCalculator()
+
+ embeddingCompat.setLaunchingActivityStack(activityOptions, INVALID_ACTIVITY_STACK_TOKEN)
+
+ verify(embeddingExtension).setLaunchingActivityStack(
+ activityOptions,
+ INVALID_ACTIVITY_STACK_TOKEN
+ )
+
+ embeddingCompat.finishActivityStacks(emptySet())
+ verify(embeddingExtension).finishActivityStacks(emptySet())
+
+ embeddingCompat.updateSplitAttributes(TEST_SPLIT_INFO, TEST_SPLIT_ATTRIBUTES)
+ verify(embeddingExtension).updateSplitAttributes(
+ INVALID_SPLIT_INFO_TOKEN,
+ OemSplitAttributes.Builder().build()
+ )
+
+ embeddingCompat.invalidateTopVisibleSplitAttributes()
+ verify(embeddingExtension).invalidateTopVisibleSplitAttributes()
+ }
+
+ companion object {
+ private val TEST_SPLIT_INFO = SplitInfo(
+ ActivityStack(emptyList(), isEmpty = true, INVALID_ACTIVITY_STACK_TOKEN),
+ ActivityStack(emptyList(), isEmpty = true, INVALID_ACTIVITY_STACK_TOKEN),
+ SplitAttributes.Builder().build(),
+ INVALID_SPLIT_INFO_TOKEN,
+ )
+
+ private val TEST_SPLIT_ATTRIBUTES = SplitAttributes.Builder().build()
+ }
+}
diff --git a/window/window/src/test/java/androidx/window/embedding/SplitControllerTest.kt b/window/window/src/test/java/androidx/window/embedding/SplitControllerTest.kt
index 2188e0e..390c681 100644
--- a/window/window/src/test/java/androidx/window/embedding/SplitControllerTest.kt
+++ b/window/window/src/test/java/androidx/window/embedding/SplitControllerTest.kt
@@ -26,7 +26,6 @@
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
-import org.junit.Assert.assertTrue
import org.junit.Test
import org.mockito.kotlin.any
import org.mockito.kotlin.doAnswer
@@ -78,9 +77,6 @@
fun test_splitAttributesCalculator_delegates() {
val mockCalculator = mock<(SplitAttributesCalculatorParams) -> SplitAttributes>()
- whenever(mockBackend.isSplitAttributesCalculatorSupported()).thenReturn(true)
- assertTrue(splitController.isSplitAttributesCalculatorSupported())
-
splitController.setSplitAttributesCalculator(mockCalculator)
verify(mockBackend).setSplitAttributesCalculator(mockCalculator)
@@ -90,9 +86,6 @@
@Test
fun test_updateSplitAttribute_delegates() {
- whenever(mockBackend.areSplitAttributesUpdatesSupported()).thenReturn(true)
- assertTrue(splitController.isUpdatingSplitAttributesSupported())
-
val mockSplitAttributes = SplitAttributes()
val mockSplitInfo = SplitInfo(
ActivityStack(emptyList(), true, mock()),
@@ -106,9 +99,6 @@
@Test
fun test_invalidateTopVisibleSplitAttributes_delegates() {
- whenever(mockBackend.areSplitAttributesUpdatesSupported()).thenReturn(true)
- assertTrue(splitController.isInvalidatingTopVisibleSplitAttributesSupported())
-
splitController.invalidateTopVisibleSplitAttributes()
verify(mockBackend).invalidateTopVisibleSplitAttributes()
}