Merge "Update Material3 demos" into androidx-main
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/ast/operators/ComparatorNodeCtsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/ast/operators/ComparatorNodeCtsTest.java
index 16fd0d6..4098926 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/ast/operators/ComparatorNodeCtsTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/ast/operators/ComparatorNodeCtsTest.java
@@ -138,4 +138,79 @@
() -> new ComparatorNode(ComparatorNode.LESS_EQUALS, propertyPath, value)
.setPropertyPath(null));
}
+
+ @Test
+ public void testToString_equals_returnsCorrectString() {
+ List<PropertyPath.PathSegment> pathSegmentList = List.of(
+ PropertyPath.PathSegment.create("example"),
+ PropertyPath.PathSegment.create("property"),
+ PropertyPath.PathSegment.create("path"));
+ PropertyPath propertyPath = new PropertyPath(pathSegmentList);
+ int value = 0;
+
+ ComparatorNode comparatorNode = new ComparatorNode(ComparatorNode.EQUALS, propertyPath,
+ value);
+
+ assertThat(comparatorNode.toString()).isEqualTo("(example.property.path == 0)");
+ }
+
+ @Test
+ public void testToString_lessThan_returnsCorrectString() {
+ List<PropertyPath.PathSegment> pathSegmentList = List.of(
+ PropertyPath.PathSegment.create("example"),
+ PropertyPath.PathSegment.create("property"),
+ PropertyPath.PathSegment.create("path"));
+ PropertyPath propertyPath = new PropertyPath(pathSegmentList);
+ int value = 0;
+
+ ComparatorNode comparatorNode = new ComparatorNode(ComparatorNode.LESS_THAN, propertyPath,
+ value);
+
+ assertThat(comparatorNode.toString()).isEqualTo("(example.property.path < 0)");
+ }
+
+ @Test
+ public void testToString_lessEquals_returnsCorrectString() {
+ List<PropertyPath.PathSegment> pathSegmentList = List.of(
+ PropertyPath.PathSegment.create("example"),
+ PropertyPath.PathSegment.create("property"),
+ PropertyPath.PathSegment.create("path"));
+ PropertyPath propertyPath = new PropertyPath(pathSegmentList);
+ int value = 0;
+
+ ComparatorNode comparatorNode = new ComparatorNode(ComparatorNode.LESS_EQUALS, propertyPath,
+ value);
+
+ assertThat(comparatorNode.toString()).isEqualTo("(example.property.path <= 0)");
+ }
+
+ @Test
+ public void testToString_greaterThan_returnsCorrectString() {
+ List<PropertyPath.PathSegment> pathSegmentList = List.of(
+ PropertyPath.PathSegment.create("example"),
+ PropertyPath.PathSegment.create("property"),
+ PropertyPath.PathSegment.create("path"));
+ PropertyPath propertyPath = new PropertyPath(pathSegmentList);
+ int value = 0;
+
+ ComparatorNode comparatorNode = new ComparatorNode(ComparatorNode.GREATER_THAN,
+ propertyPath, value);
+
+ assertThat(comparatorNode.toString()).isEqualTo("(example.property.path > 0)");
+ }
+
+ @Test
+ public void testToString_greaterEquals_returnsCorrectString() {
+ List<PropertyPath.PathSegment> pathSegmentList = List.of(
+ PropertyPath.PathSegment.create("example"),
+ PropertyPath.PathSegment.create("property"),
+ PropertyPath.PathSegment.create("path"));
+ PropertyPath propertyPath = new PropertyPath(pathSegmentList);
+ int value = 0;
+
+ ComparatorNode comparatorNode = new ComparatorNode(ComparatorNode.GREATER_EQUALS,
+ propertyPath, value);
+
+ assertThat(comparatorNode.toString()).isEqualTo("(example.property.path >= 0)");
+ }
}
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/ast/operators/PropertyRestrictNodeTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/ast/operators/PropertyRestrictNodeTest.java
index 204dca1..f78abb9 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/ast/operators/PropertyRestrictNodeTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/ast/operators/PropertyRestrictNodeTest.java
@@ -134,4 +134,21 @@
assertThrows(NullPointerException.class, () -> propertyRestrictNode.setChild(null));
}
+
+ @Test
+ public void testToString_returnsCorrectString() {
+ List<PropertyPath.PathSegment> pathSegmentList =
+ List.of(PropertyPath.PathSegment.create("example"),
+ PropertyPath.PathSegment.create("property"),
+ PropertyPath.PathSegment.create("segment"));
+ PropertyPath propertyPath = new PropertyPath(pathSegmentList);
+
+ TextNode textNode = new TextNode("foo");
+
+ PropertyRestrictNode propertyRestrictNode = new PropertyRestrictNode(propertyPath,
+ textNode);
+
+ assertThat(propertyRestrictNode.toString())
+ .isEqualTo("(example.property.segment:(foo))");
+ }
}
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/ast/searchtest/AbstractSyntaxTreeSearchCtsTestBase.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/ast/searchtest/AbstractSyntaxTreeSearchCtsTestBase.java
index d2d110d..540841f7 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/ast/searchtest/AbstractSyntaxTreeSearchCtsTestBase.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/ast/searchtest/AbstractSyntaxTreeSearchCtsTestBase.java
@@ -23,10 +23,12 @@
import androidx.annotation.NonNull;
import androidx.appsearch.app.AppSearchSchema;
+import androidx.appsearch.app.AppSearchSchema.LongPropertyConfig;
import androidx.appsearch.app.AppSearchSchema.PropertyConfig;
import androidx.appsearch.app.AppSearchSchema.StringPropertyConfig;
import androidx.appsearch.app.AppSearchSession;
import androidx.appsearch.app.GenericDocument;
+import androidx.appsearch.app.PropertyPath;
import androidx.appsearch.app.PutDocumentsRequest;
import androidx.appsearch.app.SearchResults;
import androidx.appsearch.app.SearchSpec;
@@ -34,7 +36,9 @@
import androidx.appsearch.ast.NegationNode;
import androidx.appsearch.ast.TextNode;
import androidx.appsearch.ast.operators.AndNode;
+import androidx.appsearch.ast.operators.ComparatorNode;
import androidx.appsearch.ast.operators.OrNode;
+import androidx.appsearch.ast.operators.PropertyRestrictNode;
import androidx.appsearch.flags.CheckFlagsRule;
import androidx.appsearch.flags.DeviceFlagsValueProvider;
import androidx.appsearch.flags.Flags;
@@ -453,4 +457,91 @@
List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
assertThat(documents).containsExactly(fooBarEmail, fooBazEmail, bazEmail);
}
+
+ @Test
+ public void testComparatorNode_toString_doesNumericSearch() throws Exception {
+ // Schema registration
+ AppSearchSchema transactionSchema =
+ new AppSearchSchema.Builder("transaction")
+ .addProperty(
+ new LongPropertyConfig.Builder("price")
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setIndexingType(LongPropertyConfig.INDEXING_TYPE_RANGE)
+ .build())
+ .addProperty(
+ new LongPropertyConfig.Builder("cost")
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setIndexingType(LongPropertyConfig.INDEXING_TYPE_RANGE)
+ .build())
+ .build();
+ mDb1.setSchemaAsync(new SetSchemaRequest.Builder()
+ .addSchemas(transactionSchema)
+ .build()
+ ).get();
+
+ // Index some documents
+ GenericDocument doc1 =
+ new GenericDocument.Builder<>("namespace", "id1", "transaction")
+ .setPropertyLong("price", 10)
+ .build();
+ GenericDocument doc2 =
+ new GenericDocument.Builder<>("namespace", "id2", "transaction")
+ .setPropertyLong("price", 25)
+ .build();
+ GenericDocument doc3 =
+ new GenericDocument.Builder<>("namespace", "id3", "transaction")
+ .setPropertyLong("cost", 2)
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.putAsync(
+ new PutDocumentsRequest.Builder()
+ .addGenericDocuments(doc1, doc2, doc3)
+ .build()));
+
+ // Query for the document.
+ PropertyPath pricePath = new PropertyPath("price");
+ ComparatorNode comparatorNode = new ComparatorNode(ComparatorNode.LESS_THAN, pricePath, 20);
+
+ SearchResults searchResults = mDb1.search(comparatorNode.toString(),
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .setNumericSearchEnabled(true)
+ .build());
+ List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+ assertThat(documents).containsExactly(doc1);
+ }
+
+ @Test
+ public void testPropertyRestrict_toString_restrictsByProperty() throws Exception {
+ mDb1.setSchemaAsync(
+ new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build()).get();
+
+ AppSearchEmail fooFromEmail = new AppSearchEmail.Builder("namespace", "id1")
+ .setFrom("foo")
+ .setBody("bar")
+ .build();
+ AppSearchEmail fooBodyEmail = new AppSearchEmail.Builder("namespace", "id2")
+ .setBody("foo")
+ .build();
+
+ checkIsBatchResultSuccess(mDb1.putAsync(
+ new PutDocumentsRequest.Builder()
+ .addGenericDocuments(fooFromEmail, fooBodyEmail)
+ .build()
+ )
+ );
+
+ // Query for the document.
+ TextNode foo = new TextNode("foo");
+ PropertyRestrictNode propertyRestrictNode = new PropertyRestrictNode(
+ new PropertyPath("body"), foo);
+
+ SearchResults searchResults = mDb1.search(propertyRestrictNode.toString(),
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .setListFilterQueryLanguageEnabled(true)
+ .build());
+ List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+ assertThat(documents).containsExactly(fooBodyEmail);
+ }
}
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/ast/operators/ComparatorNode.java b/appsearch/appsearch/src/main/java/androidx/appsearch/ast/operators/ComparatorNode.java
index 6d6b6e0..1ac28c5 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/ast/operators/ComparatorNode.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/ast/operators/ComparatorNode.java
@@ -138,4 +138,36 @@
public void setValue(long value) {
mValue = value;
}
+
+ /**
+ * Get the query string representation of {@link ComparatorNode}.
+ *
+ * <p>The string representation is the string representation of the property path joined from
+ * the left to the value being compared with the string representation of the
+ * {@link Comparator}.
+ */
+ @NonNull
+ @Override
+ public String toString() {
+ String comparatorString = "";
+ switch (mComparator) {
+ case ComparatorNode.EQUALS:
+ comparatorString = "==";
+ break;
+ case ComparatorNode.LESS_THAN:
+ comparatorString = "<";
+ break;
+ case ComparatorNode.LESS_EQUALS:
+ comparatorString = "<=";
+ break;
+ case ComparatorNode.GREATER_THAN:
+ comparatorString = ">";
+ break;
+ case ComparatorNode.GREATER_EQUALS:
+ comparatorString = ">=";
+ }
+ // Equivalent in behavior but more efficient than
+ // String.format("(%s %s %s)", mPropertyPath, comparatorString, mValue);
+ return "(" + mPropertyPath + " " + comparatorString + " " + mValue + ")";
+ }
}
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/ast/operators/PropertyRestrictNode.java b/appsearch/appsearch/src/main/java/androidx/appsearch/ast/operators/PropertyRestrictNode.java
index ba87da6..42c7902 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/ast/operators/PropertyRestrictNode.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/ast/operators/PropertyRestrictNode.java
@@ -102,4 +102,17 @@
public void setChild(@NonNull Node childNode) {
mChildren.set(0, Preconditions.checkNotNull(childNode));
}
+
+ /**
+ * Get the query string representation of {@link PropertyRestrictNode}.
+ *
+ * <p>The string representation is the string representation of the property path joined from
+ * the left to the query sub expression surrounded in parentheses with the property restrict
+ * symbol (":").
+ */
+ @NonNull
+ @Override
+ public String toString() {
+ return "(" + mProperty + ":" + getChild() + ")";
+ }
}
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/VirtualFile.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/VirtualFile.kt
index f57ca90..2d5e28c 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/VirtualFile.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/VirtualFile.kt
@@ -164,7 +164,7 @@
override fun executeCommand(block: (String) -> String): String {
val cmd = block(absolutePath)
- return trace("UserFile#executeCommand $cmd") {
+ return trace("UserFile#executeCommand $cmd".take(127)) {
DataInputStream(Runtime.getRuntime().exec(cmd).inputStream)
.bufferedReader()
.use { it.readText() }
@@ -222,7 +222,7 @@
var counterOs: CounterOutputStream? = null
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
- trace("ShellFile#useOutputStream $cmd") {
+ trace("ShellFile#useOutputStream $cmd".take(127)) {
val (_, inDescriptor, errDescriptor) = uiAutomation.executeShellCommandRwe(cmd)
ParcelFileDescriptor.AutoCloseOutputStream(inDescriptor).use {
counterOs = CounterOutputStream(it)
@@ -231,7 +231,7 @@
checkErr(errDescriptor)
}
} else {
- trace("ShellFile#useOutputStream $cmd") {
+ trace("ShellFile#useOutputStream $cmd".take(127)) {
val (_, inDescriptor) = uiAutomation.executeShellCommandRw(cmd)
ParcelFileDescriptor.AutoCloseOutputStream(inDescriptor).use {
counterOs = CounterOutputStream(it)
@@ -268,7 +268,7 @@
override fun executeCommand(block: (String) -> String): String {
val cmd = rootState.maybeRootify(block(absolutePath))
val output =
- trace("ShellFile#executeCommand $cmd") {
+ trace("ShellFile#executeCommand $cmd".take(127)) {
uiAutomation.executeShellCommand(cmd).fullyReadInputStream()
}
return output.trim()
diff --git a/benchmark/benchmark-macro-junit4/src/main/java/androidx/benchmark/macro/junit4/MacrobenchmarkRule.kt b/benchmark/benchmark-macro-junit4/src/main/java/androidx/benchmark/macro/junit4/MacrobenchmarkRule.kt
index 7825418..da3cf49 100644
--- a/benchmark/benchmark-macro-junit4/src/main/java/androidx/benchmark/macro/junit4/MacrobenchmarkRule.kt
+++ b/benchmark/benchmark-macro-junit4/src/main/java/androidx/benchmark/macro/junit4/MacrobenchmarkRule.kt
@@ -16,7 +16,6 @@
package androidx.benchmark.macro.junit4
-import android.Manifest
import androidx.annotation.IntRange
import androidx.benchmark.Arguments
import androidx.benchmark.ExperimentalBenchmarkConfigApi
@@ -27,9 +26,7 @@
import androidx.benchmark.macro.StartupMode
import androidx.benchmark.macro.macrobenchmarkWithStartupMode
import androidx.benchmark.perfetto.PerfettoConfig
-import androidx.test.rule.GrantPermissionRule
import org.junit.Assume.assumeTrue
-import org.junit.rules.RuleChain
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
@@ -226,16 +223,7 @@
measureBlock
)
- override fun apply(base: Statement, description: Description): Statement {
- // Grant external storage, as it may be needed for test output directory.
- return RuleChain.outerRule(
- GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE)
- )
- .around(::applyInternal)
- .apply(base, description)
- }
-
- private fun applyInternal(base: Statement, description: Description) =
+ override fun apply(base: Statement, description: Description): Statement =
object : Statement() {
override fun evaluate() {
assumeTrue(Arguments.RuleType.Macrobenchmark in Arguments.enabledRules)
diff --git a/biometric/biometric/src/main/res/values-fa/strings.xml b/biometric/biometric/src/main/res/values-fa/strings.xml
index 6e9482f..563ced0 100644
--- a/biometric/biometric/src/main/res/values-fa/strings.xml
+++ b/biometric/biometric/src/main/res/values-fa/strings.xml
@@ -23,7 +23,7 @@
<string name="fingerprint_error_no_fingerprints" msgid="7520712796891883488">"اثر انگشتی ثبت نشده است."</string>
<string name="fingerprint_error_hw_not_present" msgid="6306988885793029438">"این دستگاه حسگر اثر انگشت ندارد"</string>
<string name="fingerprint_error_user_canceled" msgid="7627716295344353987">"کاربر عملیات اثر انگشت را لغو کرد"</string>
- <string name="fingerprint_error_lockout" msgid="7291787166416782245">"تعداد تلاشها بیش از حد مجاز است. لطفاً بعداً دوباره امتحان کنید."</string>
+ <string name="fingerprint_error_lockout" msgid="7291787166416782245">"تلاشهای بسیار زیاد ناموفق. لطفاً بعداً دوباره امتحان کنید."</string>
<string name="default_error_msg" msgid="4776854077120974966">"خطای ناشناس"</string>
<string name="generic_error_user_canceled" msgid="7309881387583143581">"کاربر اصالتسنجی را لغو کرد."</string>
<string name="confirm_device_credential_password" msgid="5912733858573823945">"استفاده از گذرواژه"</string>
diff --git a/buildSrc/lint.xml b/buildSrc/lint.xml
index 78d7c83..98fbf34 100644
--- a/buildSrc/lint.xml
+++ b/buildSrc/lint.xml
@@ -32,6 +32,7 @@
<ignore path="**/src/androidUnitTest/**" />
<ignore path="**/src/jvmTest/**" />
<ignore path="**/src/commonTest/**" />
+ <ignore path="**/src/nonEmulatorJvmTest/**" />
<!-- Required for AppSearch icing tests. -->
<ignore path="**/java/tests/**" />
</issue>
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/clang/KonanCinteropExt.kt b/buildSrc/private/src/main/kotlin/androidx/build/clang/KonanCinteropExt.kt
index 6d1f6f7..74652f7 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/clang/KonanCinteropExt.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/clang/KonanCinteropExt.kt
@@ -55,7 +55,6 @@
cinteropName = cinteropName
)
registerCInterop(
- project,
kotlinNativeCompilation,
cinteropName,
createDefFileTask,
@@ -89,7 +88,7 @@
project.layout.file(archiveConfiguration.elements.map { it.single().asFile }),
cinteropName = archiveConfiguration.name
)
- registerCInterop(project, kotlinNativeCompilation, archiveConfiguration.name, createDefFileTask)
+ registerCInterop(kotlinNativeCompilation, archiveConfiguration.name, createDefFileTask)
}
private fun registerCreateDefFileTask(
@@ -116,7 +115,6 @@
}
private fun registerCInterop(
- project: Project,
kotlinNativeCompilation: KotlinNativeCompilation,
cinteropName: String,
createDefFileTask: TaskProvider<CreateDefFileWithLibraryPathTask>,
@@ -131,9 +129,5 @@
.map { it.clangParameters.includes }
)
}
- // TODO KT-62795 We shouldn't need this dependency once that issue is fixed.
- project.tasks.named(cInteropSettings.interopProcessingTaskName).configure {
- it.dependsOn(createDefFileTask)
- }
}
}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/sources/ValidateMultiplatformSourceSetNaming.kt b/buildSrc/private/src/main/kotlin/androidx/build/sources/ValidateMultiplatformSourceSetNaming.kt
index 74da1e8..7e117bc 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/sources/ValidateMultiplatformSourceSetNaming.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/sources/ValidateMultiplatformSourceSetNaming.kt
@@ -91,8 +91,8 @@
project.files(
target.compilations
.filterNot { compilation ->
- // Don't enforce suffixes for test source sets.
- compilation.name == "test" || compilation.name.endsWith("Test")
+ // Don't enforce suffixes for test source sets. Names can be e.g. testOnJvm
+ compilation.name.startsWith("test") || compilation.name.endsWith("Test")
}
.flatMap { compilation -> compilation.kotlinSourceSets }
.map { kotlinSourceSet -> kotlinSourceSet.kotlin.sourceDirectories }
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
index d65cef9..619a198 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
@@ -1140,6 +1140,7 @@
closeCameraDeviceOnClose = shouldCloseCameraDeviceOnClose,
finalizeSessionOnCloseBehavior = shouldFinalizeSessionOnCloseBehavior,
disableGraphLevelSurfaceTracking = shouldDisableGraphLevelSurfaceTracking,
+ enableRestartDelays = true,
)
}
}
diff --git a/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/CameraControllerSimulator.kt b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/CameraControllerSimulator.kt
index 35386c3..a1d8f5f 100644
--- a/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/CameraControllerSimulator.kt
+++ b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/CameraControllerSimulator.kt
@@ -22,7 +22,6 @@
import androidx.camera.camera2.pipe.CameraGraph
import androidx.camera.camera2.pipe.CameraGraphId
import androidx.camera.camera2.pipe.CameraId
-import androidx.camera.camera2.pipe.CameraStatusMonitor
import androidx.camera.camera2.pipe.GraphState.GraphStateError
import androidx.camera.camera2.pipe.StreamGraph
import androidx.camera.camera2.pipe.StreamId
@@ -172,14 +171,6 @@
}
}
- override fun onCameraStatusChanged(cameraStatus: CameraStatusMonitor.CameraStatus) {
- synchronized(lock) {
- check(!closed) { "Attempted to invoke restart after close." }
- stop()
- start()
- }
- }
-
override fun close() {
synchronized(lock) {
closed = true
diff --git a/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeCameraBackend.kt b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeCameraBackend.kt
index 56f4beb..f624b2b5a 100644
--- a/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeCameraBackend.kt
+++ b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeCameraBackend.kt
@@ -24,13 +24,10 @@
import androidx.camera.camera2.pipe.CameraGraphId
import androidx.camera.camera2.pipe.CameraId
import androidx.camera.camera2.pipe.CameraMetadata
-import androidx.camera.camera2.pipe.CameraStatusMonitor
import androidx.camera.camera2.pipe.StreamGraph
import androidx.camera.camera2.pipe.graph.GraphListener
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Deferred
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
/** The FakeCameraBackend implements [CameraBackend] and creates [CameraControllerSimulator]s. */
public class FakeCameraBackend(private val fakeCameras: Map<CameraId, CameraMetadata>) :
@@ -45,9 +42,6 @@
override val id: CameraBackendId
get() = FAKE_CAMERA_BACKEND_ID
- override val cameraStatus: Flow<CameraStatusMonitor.CameraStatus>
- get() = MutableSharedFlow()
-
override fun awaitCameraIds(): List<CameraId> = fakeCameraIds
override fun awaitConcurrentCameraIds(): Set<Set<CameraId>> = emptySet()
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraBackend.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraBackend.kt
index 2d6dbf6..19859af 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraBackend.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraBackend.kt
@@ -18,7 +18,6 @@
import androidx.annotation.RestrictTo
import androidx.camera.camera2.pipe.graph.GraphListener
import kotlinx.coroutines.Deferred
-import kotlinx.coroutines.flow.Flow
/** This is used to uniquely identify a specific backend implementation. */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@@ -26,30 +25,6 @@
public value class CameraBackendId(public val value: String)
/**
- * A CameraStatusMonitors monitors the status of the cameras, and emits updates when the status of
- * cameras changes, for instance when the camera access priorities have changed or when a particular
- * camera has become available.
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public interface CameraStatusMonitor {
- public val cameraStatus: Flow<CameraStatus>
-
- public abstract class CameraStatus internal constructor() {
- public object CameraPrioritiesChanged : CameraStatus() {
- override fun toString(): String = "CameraPrioritiesChanged"
- }
-
- public class CameraAvailable(public val cameraId: CameraId) : CameraStatus() {
- override fun toString(): String = "CameraAvailable(camera=$cameraId)"
- }
-
- public class CameraUnavailable(public val cameraId: CameraId) : CameraStatus() {
- override fun toString(): String = "CameraUnavailable(camera=$cameraId)"
- }
- }
-}
-
-/**
* A CameraBackend is used by [CameraPipe] to abstract out the lifecycle, state, and interactions
* with a set of camera devices in a standard way.
*
@@ -67,12 +42,6 @@
public val id: CameraBackendId
/**
- * A flow of camera statuses that provide camera status updates such as when the camera access
- * priorities have changed, or a certain camera has become available.
- */
- public val cameraStatus: Flow<CameraStatusMonitor.CameraStatus>
-
- /**
* Read out a list of _openable_ [CameraId]s for this backend. The backend may be able to report
* Metadata for non-openable cameras. However, these cameras should not appear the list of
* cameras returned by [getCameraIds].
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraController.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraController.kt
index 4f39a94..7422678 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraController.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraController.kt
@@ -64,13 +64,6 @@
public fun stop()
/**
- * Restart the current session. This should basically perform stop() then start(). However, the
- * implementation should handle its internal states correctly, and only restart under the right
- * [CameraStatusMonitor.CameraStatus] and [ControllerState].
- */
- public fun onCameraStatusChanged(cameraStatus: CameraStatusMonitor.CameraStatus)
-
- /**
* Close this instance. [start] and [stop] should not be invoked, and any additional calls will
* be ignored once this method returns. Depending on implementation the underlying camera
* connection may not be terminated immediately, depending on the [CameraBackend]
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraGraph.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraGraph.kt
index fd51277..4d05f58 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraGraph.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraGraph.kt
@@ -399,7 +399,17 @@
* - Device(s): LEGACY camera hardware level
* - API levels: 23 or LEGACY hardware level.
*/
- val disableGraphLevelSurfaceTracking: Boolean = false
+ val disableGraphLevelSurfaceTracking: Boolean = false,
+
+ /**
+ * Flag to enable CameraGraph to restart its internal camera controller(s) with a delay. The
+ * delay might be needed during Activity switching, to allow time for the preceding Activity
+ * to close its CameraGraphs to allow for the succeeding Activity to acquire the same
+ * camera.
+ * - Bug(s): b/344752133, b/153714651
+ * - Device(s): CameraX users
+ */
+ val enableRestartDelays: Boolean = false
) {
@JvmInline
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2Backend.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2Backend.kt
index 67a2ed2..b7eb8bb 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2Backend.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2Backend.kt
@@ -24,35 +24,27 @@
import androidx.camera.camera2.pipe.CameraGraphId
import androidx.camera.camera2.pipe.CameraId
import androidx.camera.camera2.pipe.CameraMetadata
-import androidx.camera.camera2.pipe.CameraStatusMonitor.CameraStatus
import androidx.camera.camera2.pipe.StreamGraph
import androidx.camera.camera2.pipe.config.Camera2ControllerComponent
import androidx.camera.camera2.pipe.config.Camera2ControllerConfig
-import androidx.camera.camera2.pipe.core.Threads
import androidx.camera.camera2.pipe.graph.GraphListener
import androidx.camera.camera2.pipe.graph.StreamGraphImpl
import javax.inject.Inject
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Deferred
-import kotlinx.coroutines.flow.Flow
/** This is the default [CameraBackend] implementation for CameraPipe based on Camera2. */
internal class Camera2Backend
@Inject
constructor(
- private val threads: Threads,
private val camera2DeviceCache: Camera2DeviceCache,
private val camera2MetadataCache: Camera2MetadataCache,
private val virtualCameraManager: VirtualCameraManager,
private val camera2CameraControllerComponent: Camera2ControllerComponent.Builder,
- private val camera2CameraStatusMonitor: Camera2CameraStatusMonitor,
) : CameraBackend {
override val id: CameraBackendId
get() = CameraBackendId("CXCP-Camera2")
- override val cameraStatus: Flow<CameraStatus>
- get() = camera2CameraStatusMonitor.cameraStatus
-
override suspend fun getCameraIds(): List<CameraId> = camera2DeviceCache.getCameraIds()
override fun awaitCameraIds(): List<CameraId>? = camera2DeviceCache.awaitCameraIds()
@@ -111,7 +103,7 @@
graphId,
graphConfig,
graphListener,
- streamGraph as StreamGraphImpl
+ streamGraph as StreamGraphImpl,
)
)
.build()
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraController.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraController.kt
index 2c7d2ec..fd75f74 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraController.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraController.kt
@@ -25,7 +25,6 @@
import androidx.camera.camera2.pipe.CameraGraph
import androidx.camera.camera2.pipe.CameraGraphId
import androidx.camera.camera2.pipe.CameraId
-import androidx.camera.camera2.pipe.CameraStatusMonitor.CameraStatus
import androidx.camera.camera2.pipe.CameraSurfaceManager
import androidx.camera.camera2.pipe.GraphState
import androidx.camera.camera2.pipe.StreamGraph
@@ -36,10 +35,13 @@
import androidx.camera.camera2.pipe.core.Threads
import androidx.camera.camera2.pipe.core.TimeSource
import androidx.camera.camera2.pipe.graph.GraphListener
+import androidx.camera.camera2.pipe.internal.CameraStatusMonitor
+import androidx.camera.camera2.pipe.internal.CameraStatusMonitor.CameraStatus
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
+import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
/**
@@ -59,6 +61,7 @@
private val threads: Threads,
private val graphConfig: CameraGraph.Config,
private val graphListener: GraphListener,
+ private val cameraStatusMonitor: CameraStatusMonitor,
private val captureSessionFactory: CaptureSessionFactory,
private val captureSequenceProcessorFactory: Camera2CaptureSequenceProcessorFactory,
private val virtualCameraManager: VirtualCameraManager,
@@ -84,126 +87,185 @@
@GuardedBy("lock") private var lastCameraError: CameraError? = null
+ @GuardedBy("lock") private var restartJob: Job? = null
+
private var currentCamera: VirtualCamera? = null
private var currentSession: CaptureSessionState? = null
private var currentSurfaceMap: Map<StreamId, Surface>? = null
private var currentCameraStateJob: Job? = null
+ private var cameraAvailabilityJob: Job? = null
+ private var cameraPrioritiesJob: Job? = null
- override fun start(): Unit =
- synchronized(lock) {
- if (controllerState == ControllerState.CLOSED) {
- Log.info { "Ignoring start(): Camera2CameraController is already closed" }
- return
- } else if (controllerState == ControllerState.STARTED) {
- Log.warn { "Ignoring start(): Camera2CameraController is already started" }
- return
- }
- lastCameraError = null
- val camera =
- virtualCameraManager.open(
- graphConfig.camera,
- graphConfig.sharedCameraIds,
- graphListener,
- ) { _ ->
- isForeground
- }
- if (camera == null) {
- Log.error {
- "Failed to start Camera2CameraController: Open request submission failed"
- }
- return
- }
-
- check(currentCamera == null)
- check(currentSession == null)
-
- currentCamera = camera
- val session =
- CaptureSessionState(
- graphListener,
- captureSessionFactory,
- captureSequenceProcessorFactory,
- cameraSurfaceManager,
- timeSource,
- graphConfig.flags,
- scope
- )
- currentSession = session
-
- val surfaces: Map<StreamId, Surface>? = currentSurfaceMap
- if (surfaces != null) {
- session.configureSurfaceMap(surfaces)
- }
-
- controllerState = ControllerState.STARTED
- Log.debug { "Started Camera2CameraController" }
- currentCameraStateJob?.cancel()
- currentCameraStateJob = scope.launch { bindSessionToCamera() }
- }
-
- override fun stop(): Unit =
- synchronized(lock) {
- if (controllerState == ControllerState.CLOSED) {
- Log.warn { "Ignoring stop(): Camera2CameraController is already closed" }
- return
- } else if (
- controllerState == ControllerState.STOPPING ||
- controllerState == ControllerState.STOPPED
- ) {
- Log.warn { "Ignoring stop(): CameraController already stopping or stopped" }
- return
- }
-
- val camera = currentCamera
- val session = currentSession
-
- currentCamera = null
- currentSession = null
-
- controllerState = ControllerState.STOPPING
- Log.debug { "Stopping Camera2CameraController" }
- disconnectSessionAndCamera(session, camera)
- }
-
- override fun onCameraStatusChanged(cameraStatus: CameraStatus): Unit =
- synchronized(lock) {
- Log.debug { "$this ($cameraId) camera status changed to $cameraStatus" }
- if (
- cameraStatus is CameraStatus.CameraAvailable ||
- cameraStatus is CameraStatus.CameraUnavailable
- ) {
- this@Camera2CameraController.cameraStatus = cameraStatus
- }
-
- var shouldRestart = false
- when (controllerState) {
- ControllerState.DISCONNECTED ->
- if (
- cameraStatus is CameraStatus.CameraAvailable ||
- cameraStatus is CameraStatus.CameraPrioritiesChanged
- ) {
- shouldRestart = true
+ init {
+ cameraAvailabilityJob =
+ scope.launch {
+ cameraStatusMonitor.cameraAvailability.collect { cameraStatus ->
+ when (cameraStatus) {
+ is CameraStatus.CameraAvailable -> {
+ check(cameraStatus.cameraId == cameraId)
+ onCameraStatusChanged(cameraStatus)
+ }
+ is CameraStatus.CameraUnavailable -> {
+ check(cameraStatus.cameraId == cameraId)
+ onCameraStatusChanged(cameraStatus)
+ }
}
- ControllerState.ERROR ->
- if (
- cameraStatus is CameraStatus.CameraAvailable &&
- lastCameraError != CameraError.ERROR_GRAPH_CONFIG
- ) {
- shouldRestart = true
- }
- }
- if (!shouldRestart) {
- Log.debug {
- "Camera status changed but not restarting: " +
- "Controller state = $controllerState, camera status = $cameraStatus."
}
- return
}
- Log.debug { "Restarting Camera2CameraController" }
- stop()
- start()
+
+ cameraPrioritiesJob =
+ scope.launch {
+ cameraStatusMonitor.cameraPriorities.collect {
+ onCameraStatusChanged(CameraStatus.CameraPrioritiesChanged)
+ }
+ }
+ }
+
+ override fun start() {
+ synchronized(lock) { startLocked() }
+ }
+
+ override fun stop() {
+ synchronized(lock) { stopLocked() }
+ }
+
+ private fun restart(delayMs: Long) {
+ synchronized(lock) {
+ restartJob?.cancel()
+ restartJob =
+ scope.launch {
+ delay(delayMs)
+ synchronized(lock) {
+ if (
+ controllerState != ControllerState.CLOSED &&
+ controllerState != ControllerState.STOPPING &&
+ controllerState != ControllerState.STOPPED
+ ) {
+ controllerState
+ stopLocked()
+ startLocked()
+ }
+ }
+ }
}
+ }
+
+ @GuardedBy("lock")
+ private fun startLocked() {
+ if (controllerState == ControllerState.CLOSED) {
+ Log.info { "Ignoring start(): Camera2CameraController is already closed" }
+ return
+ } else if (controllerState == ControllerState.STARTED) {
+ Log.warn { "Ignoring start(): Camera2CameraController is already started" }
+ return
+ }
+ lastCameraError = null
+ val camera =
+ virtualCameraManager.open(
+ graphConfig.camera,
+ graphConfig.sharedCameraIds,
+ graphListener,
+ ) { _ ->
+ isForeground
+ }
+ if (camera == null) {
+ Log.error { "Failed to start Camera2CameraController: Open request submission failed" }
+ return
+ }
+
+ check(currentCamera == null)
+ check(currentSession == null)
+
+ currentCamera = camera
+ val session =
+ CaptureSessionState(
+ graphListener,
+ captureSessionFactory,
+ captureSequenceProcessorFactory,
+ cameraSurfaceManager,
+ timeSource,
+ graphConfig.flags,
+ scope
+ )
+ currentSession = session
+
+ val surfaces: Map<StreamId, Surface>? = currentSurfaceMap
+ if (surfaces != null) {
+ session.configureSurfaceMap(surfaces)
+ }
+
+ controllerState = ControllerState.STARTED
+ Log.debug { "Started Camera2CameraController" }
+ currentCameraStateJob?.cancel()
+ currentCameraStateJob = scope.launch { bindSessionToCamera() }
+ }
+
+ @GuardedBy("lock")
+ private fun stopLocked() {
+ if (controllerState == ControllerState.CLOSED) {
+ Log.warn { "Ignoring stop(): Camera2CameraController is already closed" }
+ return
+ } else if (
+ controllerState == ControllerState.STOPPING ||
+ controllerState == ControllerState.STOPPED
+ ) {
+ Log.warn { "Ignoring stop(): CameraController already stopping or stopped" }
+ return
+ }
+
+ val camera = currentCamera
+ val session = currentSession
+
+ currentCamera = null
+ currentSession = null
+
+ controllerState = ControllerState.STOPPING
+ Log.debug { "Stopping Camera2CameraController" }
+ disconnectSessionAndCamera(session, camera)
+ }
+
+ private fun onCameraStatusChanged(cameraStatus: CameraStatus) {
+ val shouldRestart =
+ synchronized(lock) {
+ Log.debug { "$this ($cameraId) camera status changed to $cameraStatus" }
+ if (
+ cameraStatus is CameraStatus.CameraAvailable ||
+ cameraStatus is CameraStatus.CameraUnavailable
+ ) {
+ this@Camera2CameraController.cameraStatus = cameraStatus
+ }
+
+ var shouldRestart = false
+ when (controllerState) {
+ ControllerState.DISCONNECTED ->
+ if (
+ cameraStatus is CameraStatus.CameraAvailable ||
+ cameraStatus is CameraStatus.CameraPrioritiesChanged
+ ) {
+ shouldRestart = true
+ }
+ ControllerState.ERROR ->
+ if (
+ cameraStatus is CameraStatus.CameraAvailable &&
+ lastCameraError != CameraError.ERROR_GRAPH_CONFIG
+ ) {
+ shouldRestart = true
+ }
+ }
+ shouldRestart
+ }
+ if (!shouldRestart) {
+ Log.debug {
+ "Camera status changed but not restarting: " +
+ "Controller state = $controllerState, camera status = $cameraStatus."
+ }
+ return
+ }
+ Log.debug { "Restarting Camera2CameraController" }
+ val delayMs = if (graphConfig.flags.enableRestartDelays) 700L else 0L
+ restart(delayMs)
+ }
override fun close(): Unit =
synchronized(lock) {
@@ -221,6 +283,11 @@
currentCameraStateJob?.cancel()
currentCameraStateJob = null
+ cameraAvailabilityJob?.cancel()
+ cameraAvailabilityJob = null
+ cameraPrioritiesJob?.cancel()
+ cameraPrioritiesJob = null
+ cameraStatusMonitor.close()
disconnectSessionAndCamera(session, camera)
if (graphConfig.flags.closeCameraDeviceOnClose) {
@@ -347,6 +414,8 @@
}
?: run {
Log.warn { "Timeout when disconnecting session and camera for $session" }
+ Log.info { "Force finalizing current capture session" }
+ session?.finalizeSession(delayMs = 0)
graphListener.onGraphError(
GraphState.GraphStateError(
CameraError.ERROR_CAMERA_DEVICE,
@@ -358,7 +427,7 @@
}
companion object {
- private const val DISCONNECT_TIMEOUT_MS = 3_000L // 3s
+ private const val DISCONNECT_TIMEOUT_MS = 5000L // 5s
private const val MS_TO_NS = 1_000_000
}
}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraStatusMonitor.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraStatusMonitor.kt
index d49ccd1..2f4c9a3 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraStatusMonitor.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraStatusMonitor.kt
@@ -19,24 +19,58 @@
import android.hardware.camera2.CameraManager
import android.os.Build
import androidx.camera.camera2.pipe.CameraId
-import androidx.camera.camera2.pipe.CameraStatusMonitor
-import androidx.camera.camera2.pipe.CameraStatusMonitor.CameraStatus
import androidx.camera.camera2.pipe.core.Log
import androidx.camera.camera2.pipe.core.Threads
-import javax.inject.Inject
+import androidx.camera.camera2.pipe.internal.CameraStatusMonitor
+import androidx.camera.camera2.pipe.internal.CameraStatusMonitor.CameraStatus
import javax.inject.Provider
-import javax.inject.Singleton
+import kotlinx.atomicfu.atomic
+import kotlinx.coroutines.CoroutineName
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.onFailure
import kotlinx.coroutines.channels.trySendBlocking
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asSharedFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.launch
-@Singleton
-internal class Camera2CameraStatusMonitor
-@Inject
-constructor(cameraManager: Provider<CameraManager>, threads: Threads) : CameraStatusMonitor {
- override val cameraStatus = callbackFlow {
- val manager = cameraManager.get()
+internal class Camera2CameraStatusMonitor(
+ cameraManager: Provider<CameraManager>,
+ private val threads: Threads,
+ private val cameraId: CameraId,
+) : CameraStatusMonitor {
+ private val manager = cameraManager.get()
+ private val scope =
+ CoroutineScope(
+ threads.lightweightDispatcher.plus(CoroutineName("CXCP-CameraStatusMonitor"))
+ )
+
+ private val closed = atomic(false)
+
+ private val _cameraAvailability = MutableStateFlow<CameraStatus>(CameraStatus.Unknown)
+ override val cameraAvailability: StateFlow<CameraStatus> = _cameraAvailability.asStateFlow()
+
+ private val _cameraPriorities = MutableSharedFlow<Unit>()
+ override val cameraPriorities: SharedFlow<Unit> = _cameraPriorities.asSharedFlow()
+
+ private val cameraStatus = cameraStatusFlow()
+ private val cameraStatusJob =
+ scope.launch {
+ cameraStatus.collect { cameraStatus ->
+ when (cameraStatus) {
+ is CameraStatus.CameraAvailable -> _cameraAvailability.emit(cameraStatus)
+ is CameraStatus.CameraUnavailable -> _cameraAvailability.emit(cameraStatus)
+ is CameraStatus.CameraPrioritiesChanged -> _cameraPriorities.emit(Unit)
+ }
+ }
+ }
+
+ private fun cameraStatusFlow() = callbackFlow {
val availabilityCallback =
object : CameraManager.AvailabilityCallback() {
override fun onCameraAccessPrioritiesChanged() {
@@ -47,12 +81,14 @@
}
override fun onCameraAvailable(cameraId: String) {
+ if (cameraId != this@Camera2CameraStatusMonitor.cameraId.value) return
Log.debug { "Camera $cameraId has become available" }
trySendBlocking(CameraStatus.CameraAvailable(CameraId.fromCamera2Id(cameraId)))
.onFailure { Log.warn { "Failed to emit CameraAvailable($cameraId)" } }
}
override fun onCameraUnavailable(cameraId: String) {
+ if (cameraId != this@Camera2CameraStatusMonitor.cameraId.value) return
Log.debug { "Camera $cameraId has become unavailable" }
trySendBlocking(
CameraStatus.CameraUnavailable(CameraId.fromCamera2Id(cameraId))
@@ -72,4 +108,10 @@
awaitClose { manager.unregisterAvailabilityCallback(availabilityCallback) }
}
+
+ override fun close() {
+ if (closed.compareAndSet(expect = false, update = true)) {
+ cameraStatusJob.cancel()
+ }
+ }
}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CaptureSessionState.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CaptureSessionState.kt
index 8a69c5d..99a6f15 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CaptureSessionState.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CaptureSessionState.kt
@@ -367,7 +367,7 @@
}
}
- private fun finalizeSession(delayMs: Long = 0L) {
+ internal fun finalizeSession(delayMs: Long = 0L) {
if (delayMs != 0L) {
scope.launch {
Log.debug { "Finalizing $this in $delayMs ms" }
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/ExternalRequestProcessor.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/ExternalRequestProcessor.kt
index 818ace31..a329318 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/ExternalRequestProcessor.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/ExternalRequestProcessor.kt
@@ -24,7 +24,6 @@
import androidx.camera.camera2.pipe.CameraGraph
import androidx.camera.camera2.pipe.CameraGraphId
import androidx.camera.camera2.pipe.CameraId
-import androidx.camera.camera2.pipe.CameraStatusMonitor
import androidx.camera.camera2.pipe.CaptureSequence
import androidx.camera.camera2.pipe.CaptureSequenceProcessor
import androidx.camera.camera2.pipe.Metadata
@@ -73,11 +72,6 @@
}
}
- override fun onCameraStatusChanged(cameraStatus: CameraStatusMonitor.CameraStatus) {
- // This is intentionally made a no-op for now as CameraPipe external doesn't support
- // camera status monitoring and camera controller restart.
- }
-
override fun close() {
// TODO: ExternalRequestProcessor will be deprecated. This is a temporary patch to allow
// graphProcessor to have a suspending shutdown function.
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/Camera2Component.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/Camera2Component.kt
index 589e929..7f403c5 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/Camera2Component.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/Camera2Component.kt
@@ -16,11 +16,11 @@
package androidx.camera.camera2.pipe.config
+import android.hardware.camera2.CameraManager
import androidx.camera.camera2.pipe.CameraBackend
import androidx.camera.camera2.pipe.CameraController
import androidx.camera.camera2.pipe.CameraGraph
import androidx.camera.camera2.pipe.CameraGraphId
-import androidx.camera.camera2.pipe.CameraStatusMonitor
import androidx.camera.camera2.pipe.StreamGraph
import androidx.camera.camera2.pipe.compat.AudioRestrictionController
import androidx.camera.camera2.pipe.compat.AudioRestrictionControllerImpl
@@ -43,10 +43,12 @@
import androidx.camera.camera2.pipe.graph.GraphListener
import androidx.camera.camera2.pipe.graph.StreamGraphImpl
import androidx.camera.camera2.pipe.internal.CameraErrorListener
+import androidx.camera.camera2.pipe.internal.CameraStatusMonitor
import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.Subcomponent
+import javax.inject.Provider
import javax.inject.Scope
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
@@ -75,11 +77,6 @@
): CameraAvailabilityMonitor
@Binds
- abstract fun bindCameraStatusMonitor(
- camera2CameraStatusMonitor: Camera2CameraStatusMonitor
- ): CameraStatusMonitor
-
- @Binds
abstract fun bindCamera2DeviceCloser(
camera2CameraDeviceCloser: Camera2DeviceCloserImpl
): Camera2DeviceCloser
@@ -151,5 +148,15 @@
threads.lightweightDispatcher.plus(CoroutineName("CXCP-Camera2Controller"))
)
}
+
+ @Camera2ControllerScope
+ @Provides
+ fun provideCameraStatusMonitor(
+ cameraManager: Provider<CameraManager>,
+ threads: Threads,
+ graphConfig: CameraGraph.Config
+ ): CameraStatusMonitor {
+ return Camera2CameraStatusMonitor(cameraManager, threads, graphConfig.camera)
+ }
}
}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/CameraPipeComponent.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/CameraPipeComponent.kt
index e337f8e..e0d3b49 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/CameraPipeComponent.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/CameraPipeComponent.kt
@@ -54,8 +54,6 @@
/** Qualifier for requesting the CameraPipe scoped Context object */
@Qualifier internal annotation class CameraPipeContext
-@Qualifier internal annotation class ForGraphLifecycleManager
-
@Singleton
@Component(
modules =
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/ExternalCameraGraphComponent.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/ExternalCameraGraphComponent.kt
index 4035977..7089a3b 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/ExternalCameraGraphComponent.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/ExternalCameraGraphComponent.kt
@@ -26,7 +26,6 @@
import androidx.camera.camera2.pipe.CameraGraphId
import androidx.camera.camera2.pipe.CameraId
import androidx.camera.camera2.pipe.CameraMetadata
-import androidx.camera.camera2.pipe.CameraStatusMonitor
import androidx.camera.camera2.pipe.RequestProcessor
import androidx.camera.camera2.pipe.StreamGraph
import androidx.camera.camera2.pipe.compat.ExternalCameraController
@@ -35,8 +34,6 @@
import dagger.Provides
import dagger.Subcomponent
import kotlinx.coroutines.Deferred
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
@CameraGraphScope
@Subcomponent(modules = [SharedCameraGraphModules::class, ExternalCameraGraphConfigModule::class])
@@ -62,9 +59,6 @@
override val id: CameraBackendId
get() = CameraBackendId("External")
- override val cameraStatus: Flow<CameraStatusMonitor.CameraStatus>
- get() = MutableSharedFlow()
-
override suspend fun getCameraIds(): List<CameraId>? {
throwUnsupportedOperationException()
}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/ThreadConfigModule.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/ThreadConfigModule.kt
index 1e3d6cf..ec923aa 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/ThreadConfigModule.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/ThreadConfigModule.kt
@@ -43,7 +43,7 @@
// Lightweight executors are for CPU bound work that should take less than ~10ms to operate and
// do not block the calling thread.
private val lightweightThreadCount: Int =
- maxOf(2, Runtime.getRuntime().availableProcessors() - 2)
+ maxOf(4, Runtime.getRuntime().availableProcessors() - 2)
// Background thread count is for operations that are not latency sensitive and may take more
// than a few milliseconds to run.
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/CameraGraphImpl.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/CameraGraphImpl.kt
index ad0de5a..e7ee3477 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/CameraGraphImpl.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/CameraGraphImpl.kt
@@ -19,7 +19,6 @@
import android.os.Build
import android.view.Surface
import androidx.camera.camera2.pipe.AudioRestrictionMode
-import androidx.camera.camera2.pipe.CameraBackend
import androidx.camera.camera2.pipe.CameraController
import androidx.camera.camera2.pipe.CameraGraph
import androidx.camera.camera2.pipe.CameraGraphId
@@ -37,7 +36,6 @@
import androidx.camera.camera2.pipe.core.tryAcquireToken
import androidx.camera.camera2.pipe.internal.FrameCaptureQueue
import androidx.camera.camera2.pipe.internal.FrameDistributor
-import androidx.camera.camera2.pipe.internal.GraphLifecycleManager
import javax.inject.Inject
import kotlinx.atomicfu.atomic
import kotlinx.coroutines.CoroutineScope
@@ -56,12 +54,10 @@
constructor(
graphConfig: CameraGraph.Config,
metadata: CameraMetadata,
- private val graphLifecycleManager: GraphLifecycleManager,
private val graphProcessor: GraphProcessor,
private val graphListener: GraphListener,
private val streamGraph: StreamGraphImpl,
private val surfaceGraph: SurfaceGraph,
- private val cameraBackend: CameraBackend,
private val cameraController: CameraController,
private val graphState3A: GraphState3A,
private val listener3A: Listener3A,
@@ -135,7 +131,7 @@
Debug.traceStart { "$this#start" }
Log.info { "Starting $this" }
graphListener.onGraphStarting()
- graphLifecycleManager.monitorAndStart(cameraBackend, cameraController)
+ cameraController.start()
Debug.traceStop()
}
@@ -145,7 +141,7 @@
Debug.traceStart { "$this#stop" }
Log.info { "Stopping $this" }
graphListener.onGraphStopping()
- graphLifecycleManager.monitorAndStop(cameraBackend, cameraController)
+ cameraController.stop()
Debug.traceStop()
}
@@ -211,7 +207,7 @@
Debug.traceStart { "$this#close" }
Log.info { "Closing $this" }
graphProcessor.close()
- graphLifecycleManager.monitorAndClose(cameraBackend, cameraController)
+ cameraController.close()
frameDistributor.close()
frameCaptureQueue.close()
surfaceGraph.close()
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/CameraStatusMonitor.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/CameraStatusMonitor.kt
new file mode 100644
index 0000000..869f506e
--- /dev/null
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/CameraStatusMonitor.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.internal
+
+import androidx.annotation.RestrictTo
+import androidx.camera.camera2.pipe.CameraId
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.StateFlow
+
+/**
+ * A CameraStatusMonitor monitors the status of the cameras, and emits updates when the status of
+ * cameras changes, for instance when the camera access priorities have changed or when a particular
+ * camera has become available.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+internal interface CameraStatusMonitor : AutoCloseable {
+
+ /** Gets the state flow of the availability of the current camera. */
+ val cameraAvailability: StateFlow<CameraStatus>
+
+ /** A shared flow that emits when camera access priorities have changed. */
+ val cameraPriorities: SharedFlow<Unit>
+
+ abstract class CameraStatus internal constructor() {
+ object Unknown : CameraStatus() {
+ override fun toString(): String = "UnknownCameraStatus"
+ }
+
+ object CameraPrioritiesChanged : CameraStatus() {
+ override fun toString(): String = "CameraPrioritiesChanged"
+ }
+
+ class CameraAvailable(val cameraId: CameraId) : CameraStatus() {
+ override fun toString(): String = "CameraAvailable(camera=$cameraId)"
+ }
+
+ class CameraUnavailable(val cameraId: CameraId) : CameraStatus() {
+ override fun toString(): String = "CameraUnavailable(camera=$cameraId)"
+ }
+ }
+}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/GraphLifecycleManager.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/GraphLifecycleManager.kt
deleted file mode 100644
index f56d3aa..0000000
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/GraphLifecycleManager.kt
+++ /dev/null
@@ -1,153 +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.camera.camera2.pipe.internal
-
-import androidx.annotation.GuardedBy
-import androidx.camera.camera2.pipe.CameraBackend
-import androidx.camera.camera2.pipe.CameraBackendId
-import androidx.camera.camera2.pipe.CameraController
-import androidx.camera.camera2.pipe.CameraId
-import androidx.camera.camera2.pipe.CameraStatusMonitor
-import androidx.camera.camera2.pipe.CameraStatusMonitor.CameraStatus
-import androidx.camera.camera2.pipe.core.Threads
-import javax.inject.Inject
-import javax.inject.Singleton
-import kotlinx.coroutines.CoroutineName
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.launch
-
-/**
- * GraphLifecycleManager is a CameraPipe-level lifecycle manager that does the following:
- * - Oversees and executes the operations of [CameraController]`s. This means it will make sure the
- * operations are atomic, and executed based on permissible state transitions.
- * - Subscribe to [CameraStatusMonitor]s for camera status changes, basically “can attempt to
- * restart signals”, from the respective camera backends, and then only restart
- * [CameraController]s when the conditions are right.
- * - Once we've determined that we can restart [CameraController]s, select the “suitable”
- * [CameraController] to restart.
- */
-@Singleton
-internal class GraphLifecycleManager @Inject constructor(val threads: Threads) {
- private val lock = Any()
-
- private val scope =
- CoroutineScope(
- threads.lightweightDispatcher.plus(CoroutineName("CXCP-GraphLifecycleManager"))
- )
-
- @GuardedBy("lock")
- private val backendControllerMap =
- mutableMapOf<CameraBackendId, LinkedHashSet<CameraController>>()
-
- @GuardedBy("lock")
- private val backendCameraStatusMap =
- mutableMapOf<CameraBackendId, MutableMap<CameraId, CameraStatus>>()
-
- @GuardedBy("lock") private val backendStatusCollectJobMap = mutableMapOf<CameraBackendId, Job>()
-
- internal fun monitorAndStart(cameraBackend: CameraBackend, cameraController: CameraController) =
- synchronized(lock) {
- startMonitoring(cameraBackend, cameraController)
- cameraController.start()
- }
-
- internal fun monitorAndStop(cameraBackend: CameraBackend, cameraController: CameraController) =
- synchronized(lock) {
- cameraController.stop()
- stopMonitoring(cameraBackend, cameraController)
- }
-
- internal fun monitorAndClose(cameraBackend: CameraBackend, cameraController: CameraController) =
- synchronized(lock) {
- cameraController.close()
- stopMonitoring(cameraBackend, cameraController)
- }
-
- @GuardedBy("lock")
- private fun startMonitoring(cameraBackend: CameraBackend, cameraController: CameraController) {
- // Update this camera controller with the latest camera status, if exist.
- backendCameraStatusMap[cameraBackend.id]?.get(cameraController.cameraId)?.let { status ->
- cameraController.onCameraStatusChanged(status)
- }
-
- if (backendControllerMap.containsKey(cameraBackend.id)) {
- backendControllerMap[cameraBackend.id]?.add(cameraController)
- return
- }
- backendControllerMap[cameraBackend.id] = linkedSetOf(cameraController)
- backendStatusCollectJobMap[cameraBackend.id] =
- scope.launch {
- cameraBackend.cameraStatus.collect { cameraStatus ->
- when (cameraStatus) {
- is CameraStatus.CameraPrioritiesChanged ->
- onCameraStatusChanged(cameraBackend, cameraStatus)
- is CameraStatus.CameraAvailable ->
- onCameraStatusChanged(
- cameraBackend,
- cameraStatus,
- cameraStatus.cameraId,
- )
- is CameraStatus.CameraUnavailable ->
- onCameraStatusChanged(
- cameraBackend,
- cameraStatus,
- cameraStatus.cameraId,
- )
- }
- }
- }
- }
-
- @GuardedBy("lock")
- private fun stopMonitoring(cameraBackend: CameraBackend, cameraController: CameraController) {
- if (backendControllerMap.containsKey(cameraBackend.id)) {
- val controllerSet = backendControllerMap[cameraBackend.id]
- controllerSet?.remove(cameraController)
- if (controllerSet?.size == 0) {
- backendControllerMap.remove(cameraBackend.id)
- backendStatusCollectJobMap[cameraBackend.id]?.cancel()
- backendStatusCollectJobMap.remove(cameraBackend.id)
- }
- }
- }
-
- private fun onCameraStatusChanged(
- cameraBackend: CameraBackend,
- cameraStatus: CameraStatus,
- cameraId: CameraId? = null,
- ) =
- synchronized(lock) {
- if (cameraId != null) {
- val cameraStatusMap =
- backendCameraStatusMap.getOrPut(cameraBackend.id) { mutableMapOf() }
- cameraStatusMap[cameraId] = cameraStatus
- }
- // Restart the last CameraController being tracked in each backend. The last
- // CameraController would be the latest one being tracked, and should thus take priority
- // over previous CameraControllers.
- backendControllerMap[cameraBackend.id]
- ?.findLast {
- if (cameraId != null) {
- it.cameraId == cameraId
- } else {
- true
- }
- }
- ?.onCameraStatusChanged(cameraStatus)
- }
-}
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/CameraGraphImplTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/CameraGraphImplTest.kt
index fb91e7d..8fd32cc 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/CameraGraphImplTest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/CameraGraphImplTest.kt
@@ -34,7 +34,6 @@
import androidx.camera.camera2.pipe.internal.CameraGraphParametersImpl
import androidx.camera.camera2.pipe.internal.FrameCaptureQueue
import androidx.camera.camera2.pipe.internal.FrameDistributor
-import androidx.camera.camera2.pipe.internal.GraphLifecycleManager
import androidx.camera.camera2.pipe.internal.ImageSourceMap
import androidx.camera.camera2.pipe.media.ImageReaderImageSources
import androidx.camera.camera2.pipe.testing.CameraControllerSimulator
@@ -109,7 +108,6 @@
threads
)
private val cameraContext = CameraBackendsImpl.CameraBackendContext(context, threads, backends)
- private val graphLifecycleManager = GraphLifecycleManager(threads)
private val imageSources = ImageReaderImageSources(threads)
private val frameCaptureQueue = FrameCaptureQueue()
private val cameraController =
@@ -134,12 +132,10 @@
CameraGraphImpl(
graphConfig,
metadata,
- graphLifecycleManager,
fakeGraphProcessor,
fakeGraphProcessor,
streamGraph,
surfaceGraph,
- backend,
cameraController,
GraphState3A(),
Listener3A(),
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCameraController.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCameraController.kt
index 1319fc0..f3a3e4a 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCameraController.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCameraController.kt
@@ -20,7 +20,6 @@
import androidx.camera.camera2.pipe.CameraController
import androidx.camera.camera2.pipe.CameraGraphId
import androidx.camera.camera2.pipe.CameraId
-import androidx.camera.camera2.pipe.CameraStatusMonitor
import androidx.camera.camera2.pipe.StreamGraph
import androidx.camera.camera2.pipe.StreamId
@@ -41,11 +40,6 @@
started = false
}
- override fun onCameraStatusChanged(cameraStatus: CameraStatusMonitor.CameraStatus) {
- stop()
- start()
- }
-
override fun close() {
closed = true
started = false
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CaptureSession.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CaptureSession.java
index 70d6935..5838344 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CaptureSession.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CaptureSession.java
@@ -57,6 +57,7 @@
import androidx.camera.core.impl.utils.futures.Futures;
import androidx.concurrent.futures.CallbackToFutureAdapter;
import androidx.core.util.Preconditions;
+import androidx.tracing.Trace;
import com.google.common.util.concurrent.ListenableFuture;
@@ -106,6 +107,9 @@
/** The list of DeferrableSurface used to notify surface detach events */
@GuardedBy("mSessionLock")
List<DeferrableSurface> mConfiguredDeferrableSurfaces = Collections.emptyList();
+ /** Maximum state this session achieved (for debugging) */
+ @GuardedBy("mSessionLock")
+ State mHighestState = State.UNINITIALIZED;
/** Tracks the current state of the session. */
@GuardedBy("mSessionLock")
State mState = State.UNINITIALIZED;
@@ -136,7 +140,7 @@
*/
CaptureSession(@NonNull DynamicRangesCompat dynamicRangesCompat,
@NonNull Quirks cameraQuirks) {
- mState = State.INITIALIZED;
+ setState(State.INITIALIZED);
mDynamicRangesCompat = dynamicRangesCompat;
mCaptureSessionStateCallback = new StateCallback();
mRequestMonitor = new RequestMonitor(cameraQuirks.contains(CaptureNoResponseQuirk.class));
@@ -211,7 +215,7 @@
synchronized (mSessionLock) {
switch (mState) {
case INITIALIZED:
- mState = State.GET_SURFACE;
+ setState(State.GET_SURFACE);
mConfiguredDeferrableSurfaces = new ArrayList<>(sessionConfig.getSurfaces());
mSessionOpener = opener;
ListenableFuture<Void> openFuture = FutureChain.from(
@@ -283,7 +287,7 @@
configuredSurfaces.get(i));
}
- mState = State.OPENING;
+ setState(State.OPENING);
Logger.d(TAG, "Opening capture session.");
SynchronizedCaptureSession.StateCallback callbacks =
SynchronizedCaptureSessionStateCallbacks.createComboCallback(
@@ -451,7 +455,7 @@
mSessionOpener.stop();
// Fall through
case INITIALIZED:
- mState = State.RELEASED;
+ setState(State.RELEASED);
break;
case OPENED:
// Not break close flow. Fall through
@@ -459,7 +463,7 @@
Preconditions.checkNotNull(mSessionOpener,
"The Opener shouldn't null in state:" + mState);
mSessionOpener.stop();
- mState = State.CLOSED;
+ setState(State.CLOSED);
mRequestMonitor.stop();
mSessionConfig = null;
@@ -500,7 +504,7 @@
}
// Fall through
case OPENING:
- mState = State.RELEASING;
+ setState(State.RELEASING);
mRequestMonitor.stop();
Preconditions.checkNotNull(mSessionOpener,
"The Opener shouldn't null in state:" + mState);
@@ -531,7 +535,7 @@
mSessionOpener.stop();
// Fall through
case INITIALIZED:
- mState = State.RELEASED;
+ setState(State.RELEASED);
// Fall through
case RELEASED:
break;
@@ -604,7 +608,7 @@
return;
}
- mState = State.RELEASED;
+ setState(State.RELEASED);
mSynchronizedCaptureSession = null;
if (mReleaseCompleter != null) {
@@ -887,6 +891,22 @@
}
@GuardedBy("mSessionLock")
+ private void setState(@NonNull State state) {
+ if (state.ordinal() > mHighestState.ordinal()) {
+ mHighestState = state;
+ }
+ mState = state;
+ // Some sessions are created and immediately destroyed, so only trace those sessions
+ // that are actually used, which we distinguish by capture sessions that have gone to
+ // at least a GET_SURFACE state.
+ if (Trace.isEnabled() && mHighestState.ordinal() >= State.GET_SURFACE.ordinal()) {
+ String counterName = "CX:C2State[" + String.format("CaptureSession@%x", hashCode())
+ + "]";
+ Trace.setCounter(counterName, state.ordinal());
+ }
+ }
+
+ @GuardedBy("mSessionLock")
private CameraCaptureSession.CaptureCallback createCamera2CaptureCallback(
List<CameraCaptureCallback> cameraCaptureCallbacks,
CameraCaptureSession.CaptureCallback... additionalCallbacks) {
@@ -899,10 +919,18 @@
return Camera2CaptureCallbacks.createComboCallback(camera2Callbacks);
}
+ // Debugging note: these states are kept in ordinal order. Any additions or changes should try
+ // to maintain the same order such that the highest ordinal is the state of largest resource
+ // utilization.
enum State {
/** The default state of the session before construction. */
UNINITIALIZED,
/**
+ * Terminal state where the session has been cleaned up. At this point the session should
+ * not be used as nothing will happen in this state.
+ */
+ RELEASED,
+ /**
* Stable state once the session has been constructed, but prior to the {@link
* CameraCaptureSession} being opened.
*/
@@ -912,6 +940,15 @@
* surfaces is ready, we can create the {@link CameraCaptureSession}.
*/
GET_SURFACE,
+ /** Transitional state where the resources are being cleaned up. */
+ RELEASING,
+ /**
+ * Stable state where the session has been closed. However the {@link CameraCaptureSession}
+ * is still valid. It will remain valid until a new instance is opened at which point {@link
+ * CameraCaptureSession.StateCallback#onClosed(CameraCaptureSession)} will be called to do
+ * final cleanup.
+ */
+ CLOSED,
/**
* Transitional state when the {@link CameraCaptureSession} is in the process of being
* opened.
@@ -922,21 +959,7 @@
* this state if a valid {@link SessionConfig} has been set then the {@link
* CaptureRequest} will be issued.
*/
- OPENED,
- /**
- * Stable state where the session has been closed. However the {@link CameraCaptureSession}
- * is still valid. It will remain valid until a new instance is opened at which point {@link
- * CameraCaptureSession.StateCallback#onClosed(CameraCaptureSession)} will be called to do
- * final cleanup.
- */
- CLOSED,
- /** Transitional state where the resources are being cleaned up. */
- RELEASING,
- /**
- * Terminal state where the session has been cleaned up. At this point the session should
- * not be used as nothing will happen in this state.
- */
- RELEASED
+ OPENED
}
/**
@@ -964,7 +987,7 @@
throw new IllegalStateException(
"onConfigured() should not be possible in state: " + mState);
case OPENING:
- mState = State.OPENED;
+ setState(State.OPENED);
mSynchronizedCaptureSession = session;
Logger.d(TAG, "Attempting to send capture request onConfigured");
issueRepeatingCaptureRequests(mSessionConfig);
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/VirtualCameraControl.java b/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/VirtualCameraControl.java
index 947f25b..19bb708 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/VirtualCameraControl.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/VirtualCameraControl.java
@@ -15,6 +15,7 @@
*/
package androidx.camera.core.streamsharing;
+import static androidx.camera.core.ImageCapture.FLASH_TYPE_USE_TORCH_AS_FLASH;
import static androidx.core.util.Preconditions.checkArgument;
import static java.util.Collections.singletonList;
@@ -58,8 +59,14 @@
@ImageCapture.FlashType int flashType) {
checkArgument(captureConfigs.size() == 1, "Only support one capture config.");
+ // FLASH_TYPE_USE_TORCH_AS_FLASH is used to ensure the flash is always on when capturing.
+ // Since we are using JPEG snapshot here, there is no way for the framework to know exactly
+ // when capture is invoked and thus torch as flash workaround is required. Note that this
+ // becomes an issue only when TEMPLATE_PREVIEW is used, usually due to quirk like
+ // PreviewUnderExposureQuirk right now, since TEMPLATE_RECORD would use torch as flash
+ // capture workaround anyway.
ListenableFuture<CameraCapturePipeline> capturePipeline = getCameraCapturePipelineAsync(
- captureMode, flashType);
+ captureMode, FLASH_TYPE_USE_TORCH_AS_FLASH);
ListenableFuture<Void> captureFuture = FutureChain.from(
capturePipeline
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingTest.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingTest.kt
index ed807fd..7acbea7 100644
--- a/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingTest.kt
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingTest.kt
@@ -793,7 +793,8 @@
@Test
fun updateVideoUsage_whenRecordingStartedPausedResumedStopped(): Unit = runBlocking {
implName.ignoreTestForCameraPipe(
- "TODO: b/339615736 - Enable when implemented at camera-pipe"
+ "TODO: b/339615736 - Enable when implemented at camera-pipe",
+ evenInLab = true,
)
checkAndBindUseCases(videoCapture, preview)
@@ -832,7 +833,8 @@
assumeStopCodecAfterSurfaceRemovalCrashMediaServerQuirk()
implName.ignoreTestForCameraPipe(
- "TODO: b/339615736 - Enable when implemented at camera-pipe"
+ "TODO: b/339615736 - Enable when implemented at camera-pipe",
+ evenInLab = true,
)
checkAndBindUseCases(preview, videoCapture)
@@ -864,7 +866,8 @@
assumeStopCodecAfterSurfaceRemovalCrashMediaServerQuirk()
implName.ignoreTestForCameraPipe(
- "TODO: b/339615736 - Enable when implemented at camera-pipe"
+ "TODO: b/339615736 - Enable when implemented at camera-pipe",
+ evenInLab = true,
)
checkAndBindUseCases(preview, videoCapture)
@@ -888,7 +891,8 @@
)
implName.ignoreTestForCameraPipe(
- "TODO: b/339615736 - Enable when implemented at camera-pipe"
+ "TODO: b/339615736 - Enable when implemented at camera-pipe",
+ evenInLab = true,
)
checkAndBindUseCases(preview, videoCapture)
@@ -922,7 +926,8 @@
)
implName.ignoreTestForCameraPipe(
- "TODO: b/339615736 - Enable when implemented at camera-pipe"
+ "TODO: b/339615736 - Enable when implemented at camera-pipe",
+ evenInLab = true,
)
checkAndBindUseCases(preview, videoCapture)
@@ -956,7 +961,8 @@
)
implName.ignoreTestForCameraPipe(
- "TODO: b/339615736 - Enable when implemented at camera-pipe"
+ "TODO: b/339615736 - Enable when implemented at camera-pipe",
+ evenInLab = true,
)
checkAndBindUseCases(preview, videoCapture)
diff --git a/camera/camera-view/api/current.txt b/camera/camera-view/api/current.txt
index 3b66417..d2fd422d 100644
--- a/camera/camera-view/api/current.txt
+++ b/camera/camera-view/api/current.txt
@@ -85,9 +85,6 @@
field @Deprecated public static final int UNASSIGNED_ASPECT_RATIO = -1; // 0xffffffff
}
- @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalPreviewViewScreenFlash {
- }
-
public final class LifecycleCameraController extends androidx.camera.view.CameraController {
ctor public LifecycleCameraController(android.content.Context);
method @MainThread public void bindToLifecycle(androidx.lifecycle.LifecycleOwner);
@@ -106,7 +103,7 @@
method @SuppressCompatibility public androidx.camera.view.transform.OutputTransform? getOutputTransform();
method public androidx.lifecycle.LiveData<androidx.camera.view.PreviewView.StreamState!> getPreviewStreamState();
method @UiThread public androidx.camera.view.PreviewView.ScaleType getScaleType();
- method @SuppressCompatibility @UiThread @androidx.camera.view.ExperimentalPreviewViewScreenFlash public androidx.camera.core.ImageCapture.ScreenFlash? getScreenFlash();
+ method @UiThread public androidx.camera.core.ImageCapture.ScreenFlash? getScreenFlash();
method @UiThread public android.graphics.Matrix? getSensorToViewTransform();
method @UiThread public androidx.camera.core.Preview.SurfaceProvider getSurfaceProvider();
method @UiThread public androidx.camera.core.ViewPort? getViewPort();
@@ -114,7 +111,7 @@
method @UiThread public void setController(androidx.camera.view.CameraController?);
method @UiThread public void setImplementationMode(androidx.camera.view.PreviewView.ImplementationMode);
method @UiThread public void setScaleType(androidx.camera.view.PreviewView.ScaleType);
- method @SuppressCompatibility @androidx.camera.view.ExperimentalPreviewViewScreenFlash public void setScreenFlashOverlayColor(@ColorInt int);
+ method public void setScreenFlashOverlayColor(@ColorInt int);
method @UiThread public void setScreenFlashWindow(android.view.Window?);
}
diff --git a/camera/camera-view/api/restricted_current.txt b/camera/camera-view/api/restricted_current.txt
index 3b66417..d2fd422d 100644
--- a/camera/camera-view/api/restricted_current.txt
+++ b/camera/camera-view/api/restricted_current.txt
@@ -85,9 +85,6 @@
field @Deprecated public static final int UNASSIGNED_ASPECT_RATIO = -1; // 0xffffffff
}
- @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalPreviewViewScreenFlash {
- }
-
public final class LifecycleCameraController extends androidx.camera.view.CameraController {
ctor public LifecycleCameraController(android.content.Context);
method @MainThread public void bindToLifecycle(androidx.lifecycle.LifecycleOwner);
@@ -106,7 +103,7 @@
method @SuppressCompatibility public androidx.camera.view.transform.OutputTransform? getOutputTransform();
method public androidx.lifecycle.LiveData<androidx.camera.view.PreviewView.StreamState!> getPreviewStreamState();
method @UiThread public androidx.camera.view.PreviewView.ScaleType getScaleType();
- method @SuppressCompatibility @UiThread @androidx.camera.view.ExperimentalPreviewViewScreenFlash public androidx.camera.core.ImageCapture.ScreenFlash? getScreenFlash();
+ method @UiThread public androidx.camera.core.ImageCapture.ScreenFlash? getScreenFlash();
method @UiThread public android.graphics.Matrix? getSensorToViewTransform();
method @UiThread public androidx.camera.core.Preview.SurfaceProvider getSurfaceProvider();
method @UiThread public androidx.camera.core.ViewPort? getViewPort();
@@ -114,7 +111,7 @@
method @UiThread public void setController(androidx.camera.view.CameraController?);
method @UiThread public void setImplementationMode(androidx.camera.view.PreviewView.ImplementationMode);
method @UiThread public void setScaleType(androidx.camera.view.PreviewView.ScaleType);
- method @SuppressCompatibility @androidx.camera.view.ExperimentalPreviewViewScreenFlash public void setScreenFlashOverlayColor(@ColorInt int);
+ method public void setScreenFlashOverlayColor(@ColorInt int);
method @UiThread public void setScreenFlashWindow(android.view.Window?);
}
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/ExperimentalPreviewViewScreenFlash.java b/camera/camera-view/src/main/java/androidx/camera/view/ExperimentalPreviewViewScreenFlash.java
deleted file mode 100644
index df9117e..0000000
--- a/camera/camera-view/src/main/java/androidx/camera/view/ExperimentalPreviewViewScreenFlash.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.camera.view;
-
-import static java.lang.annotation.RetentionPolicy.CLASS;
-
-import androidx.annotation.RequiresOptIn;
-
-import java.lang.annotation.Retention;
-
-/**
- * Denotes that the annotated API is designed to be experimental for the screen flash feature and
- * may change in a future release.
- */
-@Retention(CLASS)
-@RequiresOptIn
-public @interface ExperimentalPreviewViewScreenFlash {
-}
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/PreviewView.java b/camera/camera-view/src/main/java/androidx/camera/view/PreviewView.java
index d128e54..5af22bf 100644
--- a/camera/camera-view/src/main/java/androidx/camera/view/PreviewView.java
+++ b/camera/camera-view/src/main/java/androidx/camera/view/PreviewView.java
@@ -1179,7 +1179,6 @@
* @see ScreenFlashView#getScreenFlash()
* @see ImageCapture#FLASH_MODE_SCREEN
*/
- @ExperimentalPreviewViewScreenFlash
@UiThread
@Nullable
public ImageCapture.ScreenFlash getScreenFlash() {
@@ -1194,7 +1193,6 @@
* @see #getScreenFlash()
* @see ImageCapture#FLASH_MODE_SCREEN
*/
- @ExperimentalPreviewViewScreenFlash
public void setScreenFlashOverlayColor(@ColorInt int color) {
mScreenFlashView.setBackgroundColor(color);
}
diff --git a/camera/integration-tests/camerapipetestapp/src/main/java/androidx/camera/integration/camera2/pipe/CameraPipeActivity.kt b/camera/integration-tests/camerapipetestapp/src/main/java/androidx/camera/integration/camera2/pipe/CameraPipeActivity.kt
index ece3c3d..75727d7a 100644
--- a/camera/integration-tests/camerapipetestapp/src/main/java/androidx/camera/integration/camera2/pipe/CameraPipeActivity.kt
+++ b/camera/integration-tests/camerapipetestapp/src/main/java/androidx/camera/integration/camera2/pipe/CameraPipeActivity.kt
@@ -137,7 +137,7 @@
var cameras = currentCameras
cameras?.let {
for (camera in it) {
- camera.stop()
+ camera.close()
}
}
Trace.endSection()
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/FlashTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/FlashTest.kt
index 527aeae..2e8fc9a 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/FlashTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/FlashTest.kt
@@ -209,13 +209,16 @@
fun flashEnabledInRequest_whenCapturedWithFlashOnAndSharedEffect() {
verifyRequestAeOrFlashModeForFlashModeCapture(
ImageCapture.FLASH_MODE_ON,
- addSharedEffect = true
+ addSharedEffect = true,
+ // In this test, torch as flash workaround should always be used
+ expectedAeMode = CONTROL_AE_MODE_ON,
)
}
private fun verifyRequestAeOrFlashModeForFlashModeCapture(
@ImageCapture.FlashMode flashMode: Int,
addSharedEffect: Boolean = false,
+ expectedAeMode: Int? = null,
) {
Assume.assumeFalse(
"Cuttlefish API 29 has AE mode availability issue for flash enabled modes." +
@@ -234,11 +237,12 @@
@Volatile var isAeModeExpected = true
private val expectedAeMode =
- when (flashMode) {
- ImageCapture.FLASH_MODE_ON -> CONTROL_AE_MODE_ON_ALWAYS_FLASH
- ImageCapture.FLASH_MODE_AUTO -> CONTROL_AE_MODE_ON_AUTO_FLASH
- else -> CONTROL_AE_MODE_ON
- }
+ expectedAeMode
+ ?: when (flashMode) {
+ ImageCapture.FLASH_MODE_ON -> CONTROL_AE_MODE_ON_ALWAYS_FLASH
+ ImageCapture.FLASH_MODE_AUTO -> CONTROL_AE_MODE_ON_AUTO_FLASH
+ else -> CONTROL_AE_MODE_ON
+ }
override fun onCaptureCompleted(
session: CameraCaptureSession,
@@ -251,7 +255,7 @@
isFlashModeSet = true
}
- if (request[CONTROL_AE_MODE] != expectedAeMode) {
+ if (request[CONTROL_AE_MODE] != this.expectedAeMode) {
isAeModeExpected = false
}
}
diff --git a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/camera2extensions/Camera2ExtensionsActivityTest.kt b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/camera2extensions/Camera2ExtensionsActivityTest.kt
index c933712..7ef19fa 100644
--- a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/camera2extensions/Camera2ExtensionsActivityTest.kt
+++ b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/camera2extensions/Camera2ExtensionsActivityTest.kt
@@ -42,6 +42,7 @@
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.GrantPermissionRule
import androidx.test.uiautomator.UiDevice
+import androidx.testutils.withActivity
import org.junit.After
import org.junit.Assume
import org.junit.Assume.assumeTrue
@@ -200,4 +201,21 @@
return activityScenario
}
+
+ @Test
+ fun checkPreviewUpdated_afterSwitchCamera() {
+ val activityScenario =
+ launchCamera2ExtensionsActivityAndWaitForCaptureSessionConfigured(config)
+ with(activityScenario) { // Launches activity
+ use { // Ensures that ActivityScenario is cleaned up properly
+ // Waits for preview to receive enough frames for its IdlingResource to idle.
+ waitForPreviewIdle()
+
+ withActivity { switchCamera() }
+
+ // Waits for preview to receive enough frames again after switching camera
+ waitForPreviewIdle()
+ }
+ }
+ }
}
diff --git a/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/Camera2ExtensionsActivity.kt b/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/Camera2ExtensionsActivity.kt
index 5ca6d34..c9c78a7 100644
--- a/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/Camera2ExtensionsActivity.kt
+++ b/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/Camera2ExtensionsActivity.kt
@@ -620,24 +620,7 @@
}
val cameraSwitchButton = findViewById<Button>(R.id.Switch)
- cameraSwitchButton.setOnClickListener {
- val newCameraId = if (currentCameraId == backCameraId) frontCameraId else backCameraId
-
- if (!isCameraSupportExtensions(newCameraId)) {
- Toast.makeText(
- this,
- "Camera of the other lens facing doesn't support Camera2 extensions.",
- Toast.LENGTH_SHORT
- )
- .show()
- return@setOnClickListener
- }
-
- enableUiControl(false)
- currentCameraId = newCameraId
- restartCamera = true
- closeCaptureSessionAndCameraAsync()
- }
+ cameraSwitchButton.setOnClickListener { switchCamera() }
val captureButton = findViewById<Button>(R.id.Picture)
captureButton.setOnClickListener {
@@ -647,6 +630,26 @@
}
}
+ @VisibleForTesting
+ fun switchCamera() {
+ val newCameraId = if (currentCameraId == backCameraId) frontCameraId else backCameraId
+
+ if (!isCameraSupportExtensions(newCameraId)) {
+ Toast.makeText(
+ this,
+ "Camera of the other lens facing doesn't support Camera2 extensions.",
+ Toast.LENGTH_SHORT
+ )
+ .show()
+ return
+ }
+
+ enableUiControl(false)
+ currentCameraId = newCameraId
+ restartCamera = true
+ closeCaptureSessionAndCameraAsync()
+ }
+
override fun onStart() {
super.onStart()
Log.d(TAG, "onStart()")
@@ -769,7 +772,7 @@
if (!oldCaptureSessionClosedDeferred.isCompleted) {
oldCaptureSessionClosedDeferred.complete(Unit)
}
- if (!keepCamera && synchronized(lock) { activityStopped }) {
+ if (!keepCamera || synchronized(lock) { activityStopped }) {
Log.d(TAG, "Close camera++")
cameraDevice?.close()
cameraDevice = null
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-ca/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-ca/strings.xml
index 400f759..d70e320 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-ca/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-ca/strings.xml
@@ -81,8 +81,8 @@
<string name="dial" msgid="3145707439707628311">"Marca"</string>
<string name="address" msgid="9010635942573581302">"Adreça"</string>
<string name="phone" msgid="2504766809811627577">"Telèfon"</string>
- <string name="fail_start_nav" msgid="6921321606009212189">"S\'ha produït un error en iniciar la navegació"</string>
- <string name="fail_start_dialer" msgid="1471602619507306261">"S\'ha produït un error en iniciar el telèfon"</string>
+ <string name="fail_start_nav" msgid="6921321606009212189">"Hi ha hagut un error en iniciar la navegació"</string>
+ <string name="fail_start_dialer" msgid="1471602619507306261">"Hi ha hagut un error en iniciar el telèfon"</string>
<string name="car_hardware_demo_title" msgid="3679106197233262689">"Demostració del maquinari del cotxe"</string>
<string name="car_hardware_info" msgid="1244783247616395012">"Informació sobre el maquinari del cotxe"</string>
<string name="model_info" msgid="494224423025683030">"Informació del model"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-fa/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-fa/strings.xml
index e50341f..0d07cf6 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-fa/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-fa/strings.xml
@@ -94,7 +94,7 @@
<string name="no_energy_profile_permission" msgid="4662285713731308888">"اجازه نمایه سوخت اعطا نشده است."</string>
<string name="fuel_types" msgid="6811375173343218212">"انواع سوخت"</string>
<string name="unavailable" msgid="3636401138255192934">"دردسترس نیست"</string>
- <string name="ev_connector_types" msgid="735458637011996125">"انواع رابطهای خودروی برقی"</string>
+ <string name="ev_connector_types" msgid="735458637011996125">"انواع رابطهای خودرو برقی"</string>
<string name="example_title" msgid="530257630320010494">"نمونه %d"</string>
<string name="example_1_text" msgid="8631503055894800688">"رنگ این نوشتار "<annotation color="red">"قرمز"</annotation>" است"</string>
<string name="example_2_text" msgid="1359373957397219102">"رنگ این نوشتار "<annotation color="green">"سبز"</annotation>" است"</string>
@@ -181,7 +181,7 @@
<string name="no_energy_level_permission" msgid="1684773185095107825">"اجازه میزان سوخت اعطا نشده است."</string>
<string name="no_speed_permission" msgid="5812532480922675390">"اجازه سرعت اعطا نشده است."</string>
<string name="no_mileage_permission" msgid="4074779840599589847">"اجازه مسافت طیشده اعطا نشده است."</string>
- <string name="no_ev_status_permission" msgid="933075402821938973">"اجازه وضعیت خودروی برقی اعطا نشده است."</string>
+ <string name="no_ev_status_permission" msgid="933075402821938973">"اجازه وضعیت خودرو برقی اعطا نشده است."</string>
<string name="no_accelerometer_permission" msgid="896914448469117234">"اجازه شتابسنج اعطا نشده است."</string>
<string name="no_gyroscope_permission" msgid="665293140266771569">"اجازه ژیروسکوپ اعطا نشده است."</string>
<string name="no_compass_permission" msgid="5162304489577567125">"اجازه قطبنما اعطا نشده است."</string>
@@ -190,7 +190,7 @@
<string name="fetch_energy_level" msgid="1773415471137542832">"درحال واکشی میزان سوخت."</string>
<string name="fetch_speed" msgid="7333830984597189627">"درحال واکشی سرعت."</string>
<string name="fetch_mileage" msgid="7490131687788025123">"درحال واکشی مسافت طیشده."</string>
- <string name="fetch_ev_status" msgid="2798910410830567052">"درحال واکشی وضعیت خودروی برقی."</string>
+ <string name="fetch_ev_status" msgid="2798910410830567052">"درحال واکشی وضعیت خودرو برقی."</string>
<string name="fetch_accelerometer" msgid="697750041126810911">"درحال واکشی شتابسنج."</string>
<string name="fetch_gyroscope" msgid="7153155318827188539">"درحال واکشی ژیروسکوپ."</string>
<string name="fetch_compass" msgid="7316188117590056717">"درحال واکشی قطبنما."</string>
@@ -204,8 +204,8 @@
<string name="raw_speed" msgid="7295910214088983967">"سرعت اولیه"</string>
<string name="unit" msgid="7697521583928135171">"واحد"</string>
<string name="odometer" msgid="3925174645651546591">"مسافتشمار"</string>
- <string name="ev_connected" msgid="2277845607662494696">"درگاه شارژ خودروی برقی متصل شد"</string>
- <string name="ev_open" msgid="4916704450914519643">"درگاه شارژ خودروی برقی باز است"</string>
+ <string name="ev_connected" msgid="2277845607662494696">"درگاه شارژ خودرو برقی متصل شد"</string>
+ <string name="ev_open" msgid="4916704450914519643">"درگاه شارژ خودرو برقی باز است"</string>
<string name="accelerometer" msgid="2084026313768299185">"شتابسنج"</string>
<string name="gyroscope" msgid="3428075828014504651">"ژیروسکوپ"</string>
<string name="compass" msgid="7037367764762441245">"قطبنما"</string>
diff --git a/compose/animation/animation-core/build.gradle b/compose/animation/animation-core/build.gradle
index fac6444..4a6cdf1 100644
--- a/compose/animation/animation-core/build.gradle
+++ b/compose/animation/animation-core/build.gradle
@@ -29,12 +29,30 @@
plugins {
id("AndroidXPlugin")
- id("com.android.library")
id("AndroidXComposePlugin")
}
androidXMultiplatform {
- android()
+ androidLibrary {
+ namespace = "androidx.compose.animation.core"
+ withAndroidTestOnDeviceBuilder {
+ it.compilationName = "instrumentedTest"
+ it.defaultSourceSetName = "androidInstrumentedTest"
+ it.sourceSetTreeName = "test"
+ }
+ withAndroidTestOnJvmBuilder {
+ it.defaultSourceSetName = "androidUnitTest"
+ }
+ optimization {
+ it.consumerKeepRules.publish = true
+ it.consumerKeepRules.files.add(
+ new File(project.projectDir, "proguard-rules.pro")
+ )
+ }
+
+ compileSdk = 35
+ aarMetadata.minCompileSdk = 35
+ }
jvmStubs()
linuxX64Stubs()
@@ -132,10 +150,3 @@
kotlinTarget = KotlinTarget.KOTLIN_1_9
}
-android {
- compileSdk 35
- namespace "androidx.compose.animation.core"
- buildTypes.configureEach {
- consumerProguardFiles("proguard-rules.pro")
- }
-}
diff --git a/compose/animation/animation/build.gradle b/compose/animation/animation/build.gradle
index fdc6664..56fd517 100644
--- a/compose/animation/animation/build.gradle
+++ b/compose/animation/animation/build.gradle
@@ -29,12 +29,24 @@
plugins {
id("AndroidXPlugin")
- id("com.android.library")
id("AndroidXComposePlugin")
}
androidXMultiplatform {
- android()
+ androidLibrary {
+ namespace = "androidx.compose.animation"
+ withAndroidTestOnDeviceBuilder {
+ it.compilationName = "instrumentedTest"
+ it.defaultSourceSetName = "androidInstrumentedTest"
+ it.sourceSetTreeName = "test"
+ }
+ withAndroidTestOnJvmBuilder {
+ it.defaultSourceSetName = "androidUnitTest"
+ }
+
+ compileSdk = 35
+ aarMetadata.minCompileSdk = 35
+ }
jvmStubs()
linuxX64Stubs()
@@ -130,7 +142,3 @@
kotlinTarget = KotlinTarget.KOTLIN_1_9
}
-android {
- compileSdk 35
- namespace "androidx.compose.animation"
-}
diff --git a/compose/foundation/foundation-layout/build.gradle b/compose/foundation/foundation-layout/build.gradle
index a90bb95..54dd48b 100644
--- a/compose/foundation/foundation-layout/build.gradle
+++ b/compose/foundation/foundation-layout/build.gradle
@@ -28,12 +28,31 @@
plugins {
id("AndroidXPlugin")
- id("com.android.library")
id("AndroidXComposePlugin")
}
androidXMultiplatform {
- android()
+ androidLibrary {
+ namespace = "androidx.compose.foundation.layout"
+ withAndroidTestOnDeviceBuilder {
+ it.compilationName = "instrumentedTest"
+ it.defaultSourceSetName = "androidInstrumentedTest"
+ it.sourceSetTreeName = "test"
+ }
+ withAndroidTestOnJvmBuilder {
+ it.defaultSourceSetName = "androidUnitTest"
+ }
+
+ compileSdk = 35
+ aarMetadata.minCompileSdk = 35
+
+ optimization {
+ it.consumerKeepRules.publish = true
+ it.consumerKeepRules.files.add(
+ new File(project.projectDir, "proguard-rules.pro")
+ )
+ }
+ }
jvmStubs()
linuxX64Stubs()
@@ -122,10 +141,3 @@
kotlinTarget = KotlinTarget.KOTLIN_1_9
}
-android {
- compileSdk 35
- namespace "androidx.compose.foundation.layout"
- buildTypes.configureEach {
- consumerProguardFiles("proguard-rules.pro")
- }
-}
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextFieldDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextFieldDemos.kt
index fc672cd..d037b31 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextFieldDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextFieldDemos.kt
@@ -28,6 +28,7 @@
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.TextFieldLineLimits
import androidx.compose.foundation.text.input.TextFieldState
+import androidx.compose.foundation.text.input.delete
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Button
import androidx.compose.material.Checkbox
@@ -81,6 +82,9 @@
TagLine(tag = "BasicTextField Edit Controls")
BasicTextFieldEditControls()
+
+ TagLine(tag = "BasicTextField Programmatic Edit")
+ BasicTextFieldProgrammaticEdit()
}
}
@@ -183,3 +187,35 @@
)
}
}
+
+@Composable
+fun BasicTextFieldProgrammaticEdit() {
+ val state = remember { TextFieldState() }
+ Column {
+ Row {
+ Button(onClick = { state.edit { replace(selection.start, selection.end, "A") } }) {
+ Text("A")
+ }
+ Button(onClick = { state.edit { replace(selection.start, selection.end, "B") } }) {
+ Text("B")
+ }
+ Button(
+ onClick = {
+ state.edit {
+ if (selection.collapsed) {
+ delete((selection.min - 1).coerceAtLeast(0), selection.min)
+ } else {
+ delete(selection.start, selection.end)
+ }
+ }
+ }
+ ) {
+ Text("Backspace")
+ }
+ }
+ BasicTextField(
+ state = state,
+ modifier = demoTextFieldModifiers,
+ )
+ }
+}
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/AndroidTextInputSession.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/AndroidTextInputSession.android.kt
index 27bfefa3..4535283 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/AndroidTextInputSession.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/AndroidTextInputSession.android.kt
@@ -83,22 +83,15 @@
): Nothing {
coroutineScope {
launch(start = CoroutineStart.UNDISPATCHED) {
- state.collectImeNotifications { oldValue, newValue, restartImeIfContentChanges ->
+ state.collectImeNotifications { oldValue, newValue, restartIme ->
val oldSelection = oldValue.selection
- val newSelection = newValue.selection
val oldComposition = oldValue.composition
+ val newSelection = newValue.selection
val newComposition = newValue.composition
- // No need to restart the IME if there wasn't a composing region. This is useful
- // to not unnecessarily restart filtered digit only, or password fields.
- if (
- restartImeIfContentChanges &&
- oldValue.composition != null &&
- !oldValue.contentEquals(newValue)
- ) {
+ if (restartIme) {
composeImm.restartInput()
} else if (oldSelection != newSelection || oldComposition != newComposition) {
- // Don't call updateSelection if input is going to be restarted anyway
composeImm.updateSelection(
selectionStart = newSelection.min,
selectionEnd = newSelection.max,
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/TextFieldStateTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/TextFieldStateTest.kt
index 52f27c4..9aab5b0 100644
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/TextFieldStateTest.kt
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/TextFieldStateTest.kt
@@ -17,6 +17,8 @@
package androidx.compose.foundation.text.input
import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.text.input.internal.DefaultImeEditCommandScope
+import androidx.compose.foundation.text.input.internal.TransformedTextFieldState
import androidx.compose.foundation.text.input.internal.setComposingText
import androidx.compose.foundation.text.input.internal.withImeScope
import androidx.compose.runtime.snapshotFlow
@@ -672,6 +674,145 @@
assertThat(state.composition).isNull()
}
+ @Test
+ fun notifyImeListener_firesAfterProgrammaticEdit() {
+ val state = TextFieldState("Hello")
+ var oldValueCalled: TextFieldCharSequence? = null
+ var newValueCalled: TextFieldCharSequence? = null
+ var restartImeCalled: Boolean? = null
+ val listener =
+ TextFieldState.NotifyImeListener { oldValue, newValue, restartIme ->
+ oldValueCalled = oldValue
+ newValueCalled = newValue
+ restartImeCalled = restartIme
+ }
+ state.addNotifyImeListener(listener)
+
+ state.edit { append(" World") }
+
+ assertThat(oldValueCalled.toString()).isEqualTo("Hello")
+ assertThat(newValueCalled.toString()).isEqualTo("Hello World")
+ assertThat(restartImeCalled).isFalse()
+ }
+
+ @Test
+ fun notifyImeListener_firesAfterProgrammaticEdit_restartsImeIfComposing() {
+ val state = TextFieldState("Hello")
+ // We need a composing region to fire restartIme
+ state.editAsUser(null) { setComposition(0, 5) }
+ var oldValueCalled: TextFieldCharSequence? = null
+ var newValueCalled: TextFieldCharSequence? = null
+ var restartImeCalled: Boolean? = null
+ val listener =
+ TextFieldState.NotifyImeListener { oldValue, newValue, restartIme ->
+ oldValueCalled = oldValue
+ newValueCalled = newValue
+ restartImeCalled = restartIme
+ }
+ state.addNotifyImeListener(listener)
+
+ state.edit { append(" World") }
+
+ assertThat(oldValueCalled.toString()).isEqualTo("Hello")
+ assertThat(newValueCalled.toString()).isEqualTo("Hello World")
+ assertThat(restartImeCalled).isTrue()
+ }
+
+ @Test
+ fun notifyImeListener_firesAfterProgrammaticEdit_doesNotRestartIfContentIsSame() {
+ val state = TextFieldState("Hello")
+ // We need a composing region to fire restartIme
+ state.editAsUser(null) { setComposition(0, 5) }
+ var oldValueCalled: TextFieldCharSequence? = null
+ var newValueCalled: TextFieldCharSequence? = null
+ var restartImeCalled: Boolean? = null
+ val listener =
+ TextFieldState.NotifyImeListener { oldValue, newValue, restartIme ->
+ oldValueCalled = oldValue
+ newValueCalled = newValue
+ restartImeCalled = restartIme
+ }
+ state.addNotifyImeListener(listener)
+
+ state.edit {
+ // this ends up being no-op
+ append(" World")
+ delete(5, length)
+ }
+
+ assertThat(oldValueCalled.toString()).isEqualTo("Hello")
+ assertThat(newValueCalled.toString()).isEqualTo("Hello")
+ assertThat(restartImeCalled).isFalse()
+ }
+
+ @Test
+ fun notifyImeListener_firesAfterUserEdit() {
+ val state = TextFieldState("Hello")
+ // We need a composing region for restartIme to be true. It's not going to be but let's
+ // cover all corners
+ state.editAsUser(null) { setComposition(0, 5) }
+ var oldValueCalled: TextFieldCharSequence? = null
+ var newValueCalled: TextFieldCharSequence? = null
+ var restartImeCalled: Boolean? = null
+ val listener =
+ TextFieldState.NotifyImeListener { oldValue, newValue, restartIme ->
+ oldValueCalled = oldValue
+ newValueCalled = newValue
+ restartImeCalled = restartIme
+ }
+ state.addNotifyImeListener(listener)
+
+ DefaultImeEditCommandScope(TransformedTextFieldState(state)).setComposingText("World", 1)
+
+ assertThat(oldValueCalled.toString()).isEqualTo("Hello")
+ assertThat(newValueCalled.toString()).isEqualTo("World")
+ // Even though content changes and there was a composing region, IME is not restarted
+ assertThat(restartImeCalled).isFalse()
+ }
+
+ @Test
+ fun notifyImeListener_firesAfterUndoRedo() {
+ val state = TextFieldState("Hello")
+ state.editAsUser(null) { append(" World") }
+ var oldValueCalled: TextFieldCharSequence? = null
+ var newValueCalled: TextFieldCharSequence? = null
+ var restartImeCalled: Boolean? = null
+ val listener =
+ TextFieldState.NotifyImeListener { oldValue, newValue, restartIme ->
+ oldValueCalled = oldValue
+ newValueCalled = newValue
+ restartImeCalled = restartIme
+ }
+ state.addNotifyImeListener(listener)
+
+ state.undoState.undo() // should remove " World"
+
+ assertThat(oldValueCalled.toString()).isEqualTo("Hello World")
+ assertThat(newValueCalled.toString()).isEqualTo("Hello")
+ assertThat(restartImeCalled).isFalse()
+ }
+
+ @Test
+ fun notifyImeListener_restartImeIsFalse_ifOnlySelectionIsChanged() {
+ val state = TextFieldState("Hello", TextRange(3))
+ var oldValueCalled: TextFieldCharSequence? = null
+ var newValueCalled: TextFieldCharSequence? = null
+ var restartImeCalled: Boolean? = null
+ val listener =
+ TextFieldState.NotifyImeListener { oldValue, newValue, restartIme ->
+ oldValueCalled = oldValue
+ newValueCalled = newValue
+ restartImeCalled = restartIme
+ }
+ state.addNotifyImeListener(listener)
+
+ state.editAsUser(null, restartImeIfContentChanges = true) { selection = TextRange.Zero }
+
+ assertThat(oldValueCalled?.selection).isEqualTo(TextRange(3))
+ assertThat(newValueCalled?.selection).isEqualTo(TextRange(0))
+ assertThat(restartImeCalled).isFalse()
+ }
+
private fun runTestWithSnapshotsThenCancelChildren(testBody: suspend TestScope.() -> Unit) {
val globalWriteObserverHandle =
Snapshot.registerGlobalWriteObserver {
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/TextFieldStateInternalBufferTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/TextFieldStateInternalBufferTest.kt
index 54f6830..d3f54dc 100644
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/TextFieldStateInternalBufferTest.kt
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/TextFieldStateInternalBufferTest.kt
@@ -89,9 +89,6 @@
var resetCalled = 0
var selectionCalled = 0
- state.addImeContentListener { _, _, restart ->
- if (restart) resetCalled++ else selectionCalled++
- }
val initialBuffer = state.mainBuffer
state.syncMainBufferToTemporaryBuffer(
@@ -100,13 +97,17 @@
assertThat(state.mainBuffer).isNotSameInstanceAs(initialBuffer)
val updatedBuffer = state.mainBuffer
+
+ state.addImeContentListener { _, _, restart ->
+ if (restart) resetCalled++ else selectionCalled++
+ }
state.syncMainBufferToTemporaryBuffer(
TextFieldCharSequence("qwerty", TextRange.Zero, TextRange.Zero)
)
assertThat(state.mainBuffer).isSameInstanceAs(updatedBuffer)
- assertThat(resetCalled).isEqualTo(2)
- assertThat(selectionCalled).isEqualTo(0)
+ assertThat(resetCalled).isEqualTo(0)
+ assertThat(selectionCalled).isEqualTo(1)
}
@Test
@@ -127,8 +128,9 @@
state.syncMainBufferToTemporaryBuffer(newTextFieldValue)
assertThat(state.mainBuffer).isNotSameInstanceAs(initialBuffer)
- assertThat(resetCalled).isEqualTo(2)
- assertThat(selectionCalled).isEqualTo(0)
+ // no composing region, reset shouldn't be called
+ assertThat(resetCalled).isEqualTo(0)
+ assertThat(selectionCalled).isEqualTo(2)
}
@Test
@@ -137,22 +139,22 @@
var resetCalled = 0
var selectionCalled = 0
- state.addImeContentListener { _, _, restart ->
- if (restart) resetCalled++ else selectionCalled++
- }
val textFieldValue = TextFieldCharSequence("qwerty", TextRange.Zero, TextRange.Zero)
state.syncMainBufferToTemporaryBuffer(textFieldValue)
val initialBuffer = state.mainBuffer
+ state.addImeContentListener { _, _, restart ->
+ if (restart) resetCalled++ else selectionCalled++
+ }
val newTextFieldValue = TextFieldCharSequence(textFieldValue, selection = TextRange(1))
state.syncMainBufferToTemporaryBuffer(newTextFieldValue)
assertThat(state.mainBuffer).isSameInstanceAs(initialBuffer)
assertThat(newTextFieldValue.selection.start).isEqualTo(state.mainBuffer.selection.start)
assertThat(newTextFieldValue.selection.end).isEqualTo(state.mainBuffer.selection.end)
- assertThat(resetCalled).isEqualTo(2)
- assertThat(selectionCalled).isEqualTo(0)
+ assertThat(resetCalled).isEqualTo(0)
+ assertThat(selectionCalled).isEqualTo(1)
}
@Test
@@ -161,25 +163,24 @@
var resetCalled = 0
var selectionCalled = 0
- state.addImeContentListener { _, _, restart ->
- if (restart) resetCalled++ else selectionCalled++
- }
val textFieldValue = TextFieldCharSequence("qwerty", TextRange.Zero, TextRange(1))
state.syncMainBufferToTemporaryBuffer(textFieldValue)
val initialBuffer = state.mainBuffer
+ state.addImeContentListener { _, _, restart ->
+ if (restart) resetCalled++ else selectionCalled++
+ }
// composition can not be set from app, IME owns it.
assertThat(initialBuffer.composition).isNull()
- val newTextFieldValue =
- TextFieldCharSequence(textFieldValue, textFieldValue.selection, composition = null)
+ val newTextFieldValue = TextFieldCharSequence("qwerty", TextRange.Zero, composition = null)
state.syncMainBufferToTemporaryBuffer(newTextFieldValue)
assertThat(state.mainBuffer).isSameInstanceAs(initialBuffer)
assertThat(state.mainBuffer.composition).isNull()
- assertThat(resetCalled).isEqualTo(2)
- assertThat(selectionCalled).isEqualTo(0)
+ assertThat(resetCalled).isEqualTo(0)
+ assertThat(selectionCalled).isEqualTo(1)
}
@Test
@@ -207,8 +208,9 @@
assertThat(state.mainBuffer).isSameInstanceAs(initialBuffer)
assertThat(updatedSelection.start).isEqualTo(initialBuffer.selection.start)
assertThat(updatedSelection.end).isEqualTo(initialBuffer.selection.end)
- assertThat(resetCalled).isEqualTo(1)
- assertThat(selectionCalled).isEqualTo(0)
+ // content does not change so restart input is unexpected
+ assertThat(resetCalled).isEqualTo(0)
+ assertThat(selectionCalled).isEqualTo(1)
}
@Test
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/TransformedTextFieldStateTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/TransformedTextFieldStateTest.kt
index a8fd56a..01cb362 100644
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/TransformedTextFieldStateTest.kt
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/TransformedTextFieldStateTest.kt
@@ -18,6 +18,7 @@
import androidx.compose.foundation.text.input.OutputTransformation
import androidx.compose.foundation.text.input.PlacedAnnotation
+import androidx.compose.foundation.text.input.TextFieldCharSequence
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.foundation.text.input.delete
import androidx.compose.foundation.text.input.insert
@@ -26,6 +27,8 @@
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextRange
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@@ -234,4 +237,96 @@
assertThat(transformedState.outputText.selection).isEqualTo(TextRange(0, 4))
// Rest of indices and wedge affinity are covered by mapToTransformed tests.
}
+
+ @Test
+ fun collectImeNotifications_usesVisualText() = runTest {
+ val state = TextFieldState("hello")
+ val outputTransformation = OutputTransformation {
+ insert(0, "a")
+ insert(length, "a")
+ }
+ val transformedState =
+ TransformedTextFieldState(
+ textFieldState = state,
+ outputTransformation = outputTransformation
+ )
+
+ val collectedOldValues = mutableListOf<TextFieldCharSequence>()
+ val collectedNewValues = mutableListOf<TextFieldCharSequence>()
+ val collectedRestartImes = mutableListOf<Boolean>()
+ val job = launch {
+ transformedState.collectImeNotifications { oldValue, newValue, restartIme ->
+ collectedOldValues += oldValue
+ collectedNewValues += newValue
+ collectedRestartImes += restartIme
+ }
+ }
+
+ testScheduler.advanceUntilIdle()
+
+ transformedState.editUntransformedTextAsUser(restartImeIfContentChanges = false) {
+ append(" world")
+ }
+
+ testScheduler.advanceUntilIdle()
+
+ assertThat(collectedOldValues)
+ .containsExactly(TextFieldCharSequence("ahelloa", selection = TextRange(6)))
+ assertThat(collectedNewValues)
+ .containsExactly(TextFieldCharSequence("ahello worlda", selection = TextRange(12)))
+ assertThat(collectedRestartImes).containsExactly(false)
+ job.cancel()
+ }
+
+ @Test
+ fun collectImeNotifications_carriesRestartImeUnchanged() = runTest {
+ val state = TextFieldState("hello").apply { editAsUser(null) { setComposition(0, 5) } }
+ val outputTransformation = OutputTransformation {
+ insert(0, "a")
+ insert(length, "a")
+ }
+ val transformedState =
+ TransformedTextFieldState(
+ textFieldState = state,
+ outputTransformation = outputTransformation
+ )
+
+ val collectedOldValues = mutableListOf<TextFieldCharSequence>()
+ val collectedNewValues = mutableListOf<TextFieldCharSequence>()
+ val collectedRestartImes = mutableListOf<Boolean>()
+ val job = launch {
+ transformedState.collectImeNotifications { oldValue, newValue, restartIme ->
+ collectedOldValues += oldValue
+ collectedNewValues += newValue
+ collectedRestartImes += restartIme
+ }
+ }
+
+ testScheduler.advanceUntilIdle()
+
+ transformedState.editUntransformedTextAsUser(restartImeIfContentChanges = true) {
+ append(" world")
+ }
+
+ testScheduler.advanceUntilIdle()
+
+ assertThat(collectedOldValues)
+ .containsExactly(
+ TextFieldCharSequence(
+ "ahelloa",
+ selection = TextRange(6),
+ composition = TextRange(0, 7)
+ )
+ )
+ assertThat(collectedNewValues)
+ .containsExactly(
+ TextFieldCharSequence(
+ "ahello worlda",
+ selection = TextRange(12),
+ composition = TextRange(0, 6)
+ )
+ )
+ assertThat(collectedRestartImes).containsExactly(true)
+ job.cancel()
+ }
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer.kt
index 6022683..4a68df3 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer.kt
@@ -448,7 +448,8 @@
internal fun toTextFieldCharSequence(
selection: TextRange = this.selection,
composition: TextRange? = this.composition,
- composingAnnotations: List<PlacedAnnotation>? = this.composingAnnotations?.asMutableList(),
+ composingAnnotations: List<PlacedAnnotation>? =
+ this.composingAnnotations?.asMutableList()?.takeIf { it.isNotEmpty() },
): TextFieldCharSequence =
TextFieldCharSequence(
text = buffer.toString(),
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldState.kt
index 2864629..771c234 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldState.kt
@@ -335,8 +335,17 @@
return
}
- // There's a meaningful change to the buffer, let's run the full logic.
- // first take a _snapshot_ of current state of the mainBuffer after changes are applied.
+ // Eventually we may need to run a string equality check between old value and the new value
+ // This is an O(n) operation, meaning that it gets as expensive as the text length.
+ // Therefore we create this flag to remind ourselves whether the original changes may have
+ // caused a content difference. This value being false is a strong indicator that the
+ // content definitely hasn't changed. However this being true only introduces a possibility.
+ // The content change may have been an exact string replacement like "ab" => "ab".
+ val contentMayHaveChanged = mainBuffer.changeTracker.changeCount != 0
+
+ // There's a meaningful change to the buffer, either content or selection. We need to run
+ // the full logic including InputTransformation. But F=first take a _snapshot_ of current
+ // state of the mainBuffer after changes are applied.
val afterEditValue =
TextFieldCharSequence(
text = mainBuffer.toString(),
@@ -355,7 +364,11 @@
updateValueAndNotifyListeners(
oldValue = beforeEditValue,
newValue = afterEditValue,
- restartImeIfContentChanges = restartImeIfContentChanges
+ // updateValueAndNotifyListeners use restartImeIfContentChanges flag to possibly
+ // skip doing string equality check. Here we add our own flag to indicate the
+ // possibility of content changing. Since false value is a string indicator,
+ // this added logic works.
+ restartImeIfContentChanges = contentMayHaveChanged && restartImeIfContentChanges
)
recordEditForUndo(
previousValue = beforeEditValue,
@@ -431,7 +444,19 @@
value = newValue
finishEditing()
- notifyImeListeners.forEach { it.onChange(oldValue, newValue, restartImeIfContentChanges) }
+ notifyImeListeners.forEach {
+ it.onChange(
+ oldValue = oldValue,
+ newValue = newValue,
+ restartIme =
+ restartImeIfContentChanges &&
+ !oldValue.contentEquals(newValue)
+ // No need to restart the IME if there wasn't a composing region. This is
+ // useful to not unnecessarily restart digit only, or password fields.
+ &&
+ oldValue.composition != null
+ )
+ }
}
/**
@@ -481,23 +506,31 @@
*
* State in [TextFieldState] can change through various means but categorically there are two
* sources; Developer([TextFieldState.edit]) and User([TextFieldState.editAsUser]). Only
- * non-InputTransformed IME sourced changes can skip updating the IME. Otherwise, all changes
+ * non-InputTransformed, IME sourced changes can skip updating the IME. Otherwise, all changes
* must be sent to the IME to let it synchronize its state with the [TextFieldState]. Such a
- * communication channel is established by the IME registering a [NotifyImeListener] on a
- * [TextFieldState].
+ * communication channel is established by the text input session registering a
+ * [NotifyImeListener] on a [TextFieldState].
*/
internal fun interface NotifyImeListener {
/**
- * Called when the value in [TextFieldState] changes via any source. The
- * [restartImeIfContentChanges] flag determines whether a text change between [oldValue] and
- * [newValue] should restart the ongoing input connection. Selection changes never require a
- * restart.
+ * Called when the value in [TextFieldState] changes via any source. The [restartIme] flag
+ * determines whether the ongoing input connection should be restarted. Selection or
+ * Composition range changes never require a restart.
+ *
+ * @param oldValue The previous value of the [TextFieldState] before the latest changes are
+ * applied with one exception. If an [InputTransformation] is applied on the changes
+ * coming from the IME, we use the value after user changes are applied but before
+ * [InputTransformation]. This is essentially the last known state to the IME.
+ * @param newValue Current state of the [TextFieldState]. This is always equal to the
+ * [TextFieldState.value] at the time of calling this function.
+ * @param restartIme Whether to ignore other parameters and basically restart the input
+ * session with new configuration.
*/
fun onChange(
oldValue: TextFieldCharSequence,
newValue: TextFieldCharSequence,
- restartImeIfContentChanges: Boolean
+ restartIme: Boolean
)
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TransformedTextFieldState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TransformedTextFieldState.kt
index 789d2ab4..77bdfe3 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TransformedTextFieldState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TransformedTextFieldState.kt
@@ -402,16 +402,44 @@
// TODO(b/296583846) Get rid of this.
/**
- * Adds [notifyImeListener] to the underlying [TextFieldState] and then suspends until
- * cancelled, removing the listener before continuing.
+ * Adds a [TextFieldState.NotifyImeListener] to the underlying [TextFieldState] and then
+ * suspends until cancelled, removing the listener before continuing.
+ *
+ * This listener is responsible for updating the IME about the latest changes to the underlying
+ * [TextFieldState]. Please note that the IME should be aware of the [outputText], rather than
+ * [untransformedText] since users mainly interact with the output representation.
+ *
+ * The real challenge comes from the fact that IME doesn't need updates if its commands are not
+ * interfered with. That's why [TextFieldState.NotifyImeListener] actually sends the latest
+ * synced value from IME, rather than the previous value inside the [TextFieldState] before the
+ * changes are applied. In the existence of [OutputTransformation], we have to transform these
+ * values once more before updating the IME.
*/
suspend fun collectImeNotifications(
notifyImeListener: TextFieldState.NotifyImeListener
): Nothing {
+ val transformedNotifyImeListener =
+ if (outputTransformation != null) {
+ TextFieldState.NotifyImeListener { oldValue, _, restartIme ->
+ notifyImeListener.onChange(
+ oldValue =
+ calculateTransformedText(
+ untransformedValue = oldValue,
+ outputTransformation = outputTransformation,
+ wedgeAffinity = selectionWedgeAffinity
+ )
+ ?.text ?: oldValue,
+ newValue = visualText,
+ restartIme = restartIme
+ )
+ }
+ } else {
+ notifyImeListener
+ }
suspendCancellableCoroutine<Nothing> { continuation ->
- textFieldState.addNotifyImeListener(notifyImeListener)
+ textFieldState.addNotifyImeListener(transformedNotifyImeListener)
continuation.invokeOnCancellation {
- textFieldState.removeNotifyImeListener(notifyImeListener)
+ textFieldState.removeNotifyImeListener(transformedNotifyImeListener)
}
}
}
diff --git a/compose/material/material-ripple/build.gradle b/compose/material/material-ripple/build.gradle
index b6d5e76..9385d8d 100644
--- a/compose/material/material-ripple/build.gradle
+++ b/compose/material/material-ripple/build.gradle
@@ -28,12 +28,24 @@
plugins {
id("AndroidXPlugin")
- id("com.android.library")
id("AndroidXComposePlugin")
}
androidXMultiplatform {
- android()
+ androidLibrary {
+ namespace = "androidx.compose.material.ripple"
+ withAndroidTestOnDeviceBuilder {
+ it.compilationName = "instrumentedTest"
+ it.defaultSourceSetName = "androidInstrumentedTest"
+ it.sourceSetTreeName = "test"
+ }
+ withAndroidTestOnJvmBuilder {
+ it.defaultSourceSetName = "androidUnitTest"
+ }
+
+ compileSdk = 35
+ aarMetadata.minCompileSdk = 35
+ }
jvmStubs()
linuxX64Stubs()
@@ -116,7 +128,3 @@
kotlinTarget = KotlinTarget.KOTLIN_1_9
}
-android {
- compileSdk 35
- namespace "androidx.compose.material.ripple"
-}
diff --git a/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/TooltipBenchmark.kt b/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/TooltipBenchmark.kt
index f33e079..31c5ed8 100644
--- a/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/TooltipBenchmark.kt
+++ b/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/TooltipBenchmark.kt
@@ -89,11 +89,11 @@
when (tooltipType) {
TooltipType.Plain -> {
tooltip = { PlainTooltipTest() }
- positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider()
+ positionProvider = TooltipDefaults.rememberTooltipPositionProvider()
}
TooltipType.Rich -> {
tooltip = { RichTooltipTest() }
- positionProvider = TooltipDefaults.rememberRichTooltipPositionProvider()
+ positionProvider = TooltipDefaults.rememberTooltipPositionProvider()
}
}
diff --git a/compose/material3/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/material3/integration/macrobenchmark/target/TooltipActivity.kt b/compose/material3/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/material3/integration/macrobenchmark/target/TooltipActivity.kt
index bf222cbc..a2f6eb1 100644
--- a/compose/material3/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/material3/integration/macrobenchmark/target/TooltipActivity.kt
+++ b/compose/material3/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/material3/integration/macrobenchmark/target/TooltipActivity.kt
@@ -37,7 +37,7 @@
super.onCreate(savedInstanceState)
setContent {
TooltipBox(
- positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
+ positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
tooltip = {
PlainTooltip(caretSize = TooltipDefaults.caretSize) {
Text("Tooltip Description")
diff --git a/compose/material3/material3-adaptive-navigation-suite/build.gradle b/compose/material3/material3-adaptive-navigation-suite/build.gradle
index 8871853..65c3958 100644
--- a/compose/material3/material3-adaptive-navigation-suite/build.gradle
+++ b/compose/material3/material3-adaptive-navigation-suite/build.gradle
@@ -29,12 +29,24 @@
plugins {
id("AndroidXPlugin")
- id("com.android.library")
id("AndroidXComposePlugin")
}
androidXMultiplatform {
- android()
+ androidLibrary {
+ namespace = "androidx.compose.material3.adaptive.navigationsuite"
+ withAndroidTestOnDeviceBuilder {
+ it.compilationName = "instrumentedTest"
+ it.defaultSourceSetName = "androidInstrumentedTest"
+ it.sourceSetTreeName = "test"
+ }
+ withAndroidTestOnJvmBuilder {
+ it.defaultSourceSetName = "androidUnitTest"
+ }
+
+ compileSdk = 35
+ aarMetadata.minCompileSdk = 35
+ }
jvmStubs()
defaultPlatform(PlatformIdentifier.ANDROID)
@@ -97,10 +109,6 @@
}
}
-android {
- compileSdk 35
- namespace "androidx.compose.material3.adaptive.navigationsuite"
-}
androidx {
name = "Material Adaptive Navigation Suite"
diff --git a/compose/material3/material3-common/build.gradle b/compose/material3/material3-common/build.gradle
index 0572958..3eac2d7 100644
--- a/compose/material3/material3-common/build.gradle
+++ b/compose/material3/material3-common/build.gradle
@@ -31,11 +31,23 @@
plugins {
id("AndroidXPlugin")
id("AndroidXComposePlugin")
- id("com.android.library")
}
androidXMultiplatform {
- android()
+ androidLibrary {
+ namespace = "androidx.compose.material3.common"
+ withAndroidTestOnDeviceBuilder {
+ it.compilationName = "instrumentedTest"
+ it.defaultSourceSetName = "androidInstrumentedTest"
+ it.sourceSetTreeName = "test"
+ }
+ withAndroidTestOnJvmBuilder {
+ it.defaultSourceSetName = "androidUnitTest"
+ }
+
+ compileSdk = 35
+ aarMetadata.minCompileSdk = 35
+ }
jvmStubs()
linuxX64Stubs()
@@ -99,10 +111,6 @@
}
}
-android {
- compileSdk 35
- namespace "androidx.compose.material3.common"
-}
androidx {
name = "Compose Material 3 Common"
diff --git a/compose/material3/material3-window-size-class/build.gradle b/compose/material3/material3-window-size-class/build.gradle
index a943601..46fb47e 100644
--- a/compose/material3/material3-window-size-class/build.gradle
+++ b/compose/material3/material3-window-size-class/build.gradle
@@ -28,12 +28,24 @@
plugins {
id("AndroidXPlugin")
- id("com.android.library")
id("AndroidXComposePlugin")
}
androidXMultiplatform {
- android()
+ androidLibrary {
+ namespace = "androidx.compose.material3.windowsizeclass"
+ withAndroidTestOnDeviceBuilder {
+ it.compilationName = "instrumentedTest"
+ it.defaultSourceSetName = "androidInstrumentedTest"
+ it.sourceSetTreeName = "test"
+ }
+ withAndroidTestOnJvmBuilder {
+ it.defaultSourceSetName = "androidUnitTest"
+ }
+
+ compileSdk = 35
+ aarMetadata.minCompileSdk = 35
+ }
jvmStubs()
linuxX64Stubs()
@@ -114,7 +126,3 @@
kotlinTarget = KotlinTarget.KOTLIN_1_9
}
-android {
- compileSdk 35
- namespace "androidx.compose.material3.windowsizeclass"
-}
diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt
index a43c6e0..28407dd 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -1929,7 +1929,8 @@
}
@SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class SheetState {
- ctor public SheetState(boolean skipPartiallyExpanded, androidx.compose.ui.unit.Density density, optional androidx.compose.material3.SheetValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange, optional boolean skipHiddenState);
+ ctor @Deprecated public SheetState(boolean skipPartiallyExpanded, androidx.compose.ui.unit.Density density, optional androidx.compose.material3.SheetValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange, optional boolean skipHiddenState);
+ ctor public SheetState(boolean skipPartiallyExpanded, kotlin.jvm.functions.Function0<java.lang.Float> positionalThreshold, kotlin.jvm.functions.Function0<java.lang.Float> velocityThreshold, optional androidx.compose.material3.SheetValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange, optional boolean skipHiddenState);
method public suspend Object? expand(kotlin.coroutines.Continuation<? super kotlin.Unit>);
method public androidx.compose.material3.SheetValue getCurrentValue();
method public boolean getHasExpandedState();
@@ -1951,7 +1952,8 @@
}
public static final class SheetState.Companion {
- method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.SheetState,androidx.compose.material3.SheetValue> Saver(boolean skipPartiallyExpanded, kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange, androidx.compose.ui.unit.Density density, boolean skipHiddenState);
+ method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.SheetState,androidx.compose.material3.SheetValue> Saver(boolean skipPartiallyExpanded, kotlin.jvm.functions.Function0<java.lang.Float> positionalThreshold, kotlin.jvm.functions.Function0<java.lang.Float> velocityThreshold, kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange, boolean skipHiddenState);
+ method @Deprecated public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.SheetState,androidx.compose.material3.SheetValue> Saver(boolean skipPartiallyExpanded, kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange, androidx.compose.ui.unit.Density density, boolean skipHiddenState);
}
@SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public enum SheetValue {
@@ -2691,8 +2693,9 @@
method public float getPlainTooltipMaxWidth();
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getRichTooltipContainerShape();
method public float getRichTooltipMaxWidth();
- method @androidx.compose.runtime.Composable public androidx.compose.ui.window.PopupPositionProvider rememberPlainTooltipPositionProvider(optional float spacingBetweenTooltipAndAnchor);
- method @androidx.compose.runtime.Composable public androidx.compose.ui.window.PopupPositionProvider rememberRichTooltipPositionProvider(optional float spacingBetweenTooltipAndAnchor);
+ method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.ui.window.PopupPositionProvider rememberPlainTooltipPositionProvider(optional float spacingBetweenTooltipAndAnchor);
+ method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.ui.window.PopupPositionProvider rememberRichTooltipPositionProvider(optional float spacingBetweenTooltipAndAnchor);
+ method @androidx.compose.runtime.Composable public androidx.compose.ui.window.PopupPositionProvider rememberTooltipPositionProvider(optional float spacingBetweenTooltipAndAnchor);
method @androidx.compose.runtime.Composable public androidx.compose.material3.RichTooltipColors richTooltipColors();
method @androidx.compose.runtime.Composable public androidx.compose.material3.RichTooltipColors richTooltipColors(optional long containerColor, optional long contentColor, optional long titleContentColor, optional long actionContentColor);
property public final long caretSize;
diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt
index a43c6e0..28407dd 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -1929,7 +1929,8 @@
}
@SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class SheetState {
- ctor public SheetState(boolean skipPartiallyExpanded, androidx.compose.ui.unit.Density density, optional androidx.compose.material3.SheetValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange, optional boolean skipHiddenState);
+ ctor @Deprecated public SheetState(boolean skipPartiallyExpanded, androidx.compose.ui.unit.Density density, optional androidx.compose.material3.SheetValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange, optional boolean skipHiddenState);
+ ctor public SheetState(boolean skipPartiallyExpanded, kotlin.jvm.functions.Function0<java.lang.Float> positionalThreshold, kotlin.jvm.functions.Function0<java.lang.Float> velocityThreshold, optional androidx.compose.material3.SheetValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange, optional boolean skipHiddenState);
method public suspend Object? expand(kotlin.coroutines.Continuation<? super kotlin.Unit>);
method public androidx.compose.material3.SheetValue getCurrentValue();
method public boolean getHasExpandedState();
@@ -1951,7 +1952,8 @@
}
public static final class SheetState.Companion {
- method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.SheetState,androidx.compose.material3.SheetValue> Saver(boolean skipPartiallyExpanded, kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange, androidx.compose.ui.unit.Density density, boolean skipHiddenState);
+ method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.SheetState,androidx.compose.material3.SheetValue> Saver(boolean skipPartiallyExpanded, kotlin.jvm.functions.Function0<java.lang.Float> positionalThreshold, kotlin.jvm.functions.Function0<java.lang.Float> velocityThreshold, kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange, boolean skipHiddenState);
+ method @Deprecated public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.SheetState,androidx.compose.material3.SheetValue> Saver(boolean skipPartiallyExpanded, kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange, androidx.compose.ui.unit.Density density, boolean skipHiddenState);
}
@SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public enum SheetValue {
@@ -2691,8 +2693,9 @@
method public float getPlainTooltipMaxWidth();
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getRichTooltipContainerShape();
method public float getRichTooltipMaxWidth();
- method @androidx.compose.runtime.Composable public androidx.compose.ui.window.PopupPositionProvider rememberPlainTooltipPositionProvider(optional float spacingBetweenTooltipAndAnchor);
- method @androidx.compose.runtime.Composable public androidx.compose.ui.window.PopupPositionProvider rememberRichTooltipPositionProvider(optional float spacingBetweenTooltipAndAnchor);
+ method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.ui.window.PopupPositionProvider rememberPlainTooltipPositionProvider(optional float spacingBetweenTooltipAndAnchor);
+ method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.ui.window.PopupPositionProvider rememberRichTooltipPositionProvider(optional float spacingBetweenTooltipAndAnchor);
+ method @androidx.compose.runtime.Composable public androidx.compose.ui.window.PopupPositionProvider rememberTooltipPositionProvider(optional float spacingBetweenTooltipAndAnchor);
method @androidx.compose.runtime.Composable public androidx.compose.material3.RichTooltipColors richTooltipColors();
method @androidx.compose.runtime.Composable public androidx.compose.material3.RichTooltipColors richTooltipColors(optional long containerColor, optional long contentColor, optional long titleContentColor, optional long actionContentColor);
property public final long caretSize;
diff --git a/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/TooltipDemo.kt b/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/TooltipDemo.kt
index ec5eede..9e4c440 100644
--- a/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/TooltipDemo.kt
+++ b/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/TooltipDemo.kt
@@ -65,7 +65,7 @@
val textFieldTooltipState = rememberTooltipState()
val scope = rememberCoroutineScope()
TooltipBox(
- positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
+ positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
tooltip = { PlainTooltip { Text(textFieldTooltipText) } },
state = textFieldTooltipState
) {
@@ -96,7 +96,7 @@
LazyColumn(verticalArrangement = Arrangement.spacedBy(4.dp)) {
items(listData) { item ->
TooltipBox(
- positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
+ positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
tooltip = { PlainTooltip { Text("${item.itemName} added to list") } },
state = item.addedTooltipState
) {
@@ -115,7 +115,7 @@
headlineContent = { Text(itemName) },
trailingContent = {
TooltipBox(
- positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
+ positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
tooltip = { PlainTooltip { Text("Delete $itemName") } },
state = rememberTooltipState(),
enableUserInput = true
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/TooltipSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/TooltipSamples.kt
index bd37b75..1d8809e 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/TooltipSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/TooltipSamples.kt
@@ -50,7 +50,7 @@
@Composable
fun PlainTooltipSample() {
TooltipBox(
- positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
+ positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
tooltip = { PlainTooltip { Text("Add to favorites") } },
state = rememberTooltipState()
) {
@@ -69,7 +69,7 @@
val scope = rememberCoroutineScope()
Column(horizontalAlignment = Alignment.CenterHorizontally) {
TooltipBox(
- positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
+ positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
tooltip = { PlainTooltip { Text("Add to list") } },
state = tooltipState
) {
@@ -87,7 +87,7 @@
@Composable
fun PlainTooltipWithCaret() {
TooltipBox(
- positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
+ positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
tooltip = {
PlainTooltip(caretSize = TooltipDefaults.caretSize) { Text("Add to favorites") }
},
@@ -104,7 +104,7 @@
@Composable
fun PlainTooltipWithCustomCaret() {
TooltipBox(
- positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
+ positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
tooltip = { PlainTooltip(caretSize = DpSize(24.dp, 12.dp)) { Text("Add to favorites") } },
state = rememberTooltipState()
) {
@@ -121,7 +121,7 @@
val tooltipState = rememberTooltipState(isPersistent = true)
val scope = rememberCoroutineScope()
TooltipBox(
- positionProvider = TooltipDefaults.rememberRichTooltipPositionProvider(),
+ positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
tooltip = {
RichTooltip(
title = { Text(richTooltipSubheadText) },
@@ -150,7 +150,7 @@
val scope = rememberCoroutineScope()
Column(horizontalAlignment = Alignment.CenterHorizontally) {
TooltipBox(
- positionProvider = TooltipDefaults.rememberRichTooltipPositionProvider(),
+ positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
tooltip = {
RichTooltip(
title = { Text(richTooltipSubheadText) },
@@ -181,7 +181,7 @@
val tooltipState = rememberTooltipState(isPersistent = true)
val scope = rememberCoroutineScope()
TooltipBox(
- positionProvider = TooltipDefaults.rememberRichTooltipPositionProvider(),
+ positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
tooltip = {
RichTooltip(
title = { Text(richTooltipSubheadText) },
@@ -210,7 +210,7 @@
val tooltipState = rememberTooltipState(isPersistent = true)
val scope = rememberCoroutineScope()
TooltipBox(
- positionProvider = TooltipDefaults.rememberRichTooltipPositionProvider(),
+ positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
tooltip = {
RichTooltip(
title = { Text(richTooltipSubheadText) },
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/BottomSheetScaffoldTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/BottomSheetScaffoldTest.kt
index 40418e9..16a6375 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/BottomSheetScaffoldTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/BottomSheetScaffoldTest.kt
@@ -252,7 +252,12 @@
skipPartiallyExpanded = false,
skipHiddenState = true,
initialValue = SheetValue.PartiallyExpanded,
- density = rule.density
+ positionalThreshold = {
+ with(rule.density) { BottomSheetDefaults.PositionalThreshold.toPx() }
+ },
+ velocityThreshold = {
+ with(rule.density) { BottomSheetDefaults.VelocityThreshold.toPx() }
+ },
)
rule.setContent {
scope = rememberCoroutineScope()
@@ -923,7 +928,16 @@
)
var sheetCoords: LayoutCoordinates? = null
var rootCoords: LayoutCoordinates? = null
- val state = SheetState(false, density = Density(1f))
+ val state =
+ SheetState(
+ skipPartiallyExpanded = false,
+ positionalThreshold = {
+ with(rule.density) { BottomSheetDefaults.PositionalThreshold.toPx() }
+ },
+ velocityThreshold = {
+ with(rule.density) { BottomSheetDefaults.VelocityThreshold.toPx() }
+ },
+ )
var sheetValue by mutableStateOf(SheetValue.Hidden)
rule.setContent {
Box(Modifier.onGloballyPositioned { rootCoords = it }.offset { offset }) {
@@ -983,7 +997,16 @@
IntOffset(100, 10),
)
var sheetCoords: LayoutCoordinates? = null
- val state = SheetState(false, density = Density(1f))
+ val state =
+ SheetState(
+ skipPartiallyExpanded = false,
+ positionalThreshold = {
+ with(rule.density) { BottomSheetDefaults.PositionalThreshold.toPx() }
+ },
+ velocityThreshold = {
+ with(rule.density) { BottomSheetDefaults.VelocityThreshold.toPx() }
+ },
+ )
var sheetValue by mutableStateOf(SheetValue.Hidden)
rule.setContent {
LaunchedEffect(sheetValue) {
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ExposedDropdownMenuTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ExposedDropdownMenuTest.kt
index 6453fe4..a6dc9a7 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ExposedDropdownMenuTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ExposedDropdownMenuTest.kt
@@ -84,6 +84,7 @@
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.junit.Assume.assumeNotNull
+import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -187,7 +188,11 @@
rule.onNodeWithTag(EDMTag).assertIsDisplayed()
rule.onNodeWithTag(MenuItemTag).assertIsDisplayed()
- UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()).pressBack()
+ val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+ // First back closes keyboard
+ device.pressBack()
+ // Second back closes menu
+ device.pressBack()
rule.onNodeWithTag(TFTag).assertIsDisplayed()
rule.onNodeWithTag(MenuItemTag).assertDoesNotExist()
@@ -261,6 +266,7 @@
rule.onNodeWithTag(MenuItemTag).assertDoesNotExist()
}
+ @Ignore("b/374850853")
@Test
fun edm_editable_collapsesOnEscapePress() {
rule.setMaterialContent(lightColorScheme()) {
@@ -531,6 +537,46 @@
}
@Test
+ fun edm_anchorTypeIsUpdated_evenIfTextFieldIsNotClicked() {
+ var expanded by mutableStateOf(false)
+ var type: ExposedDropdownMenuAnchorType? = null
+ rule.setMaterialContent(lightColorScheme()) {
+ ExposedDropdownMenuBox(
+ expanded = expanded,
+ onExpandedChange = { expanded = it },
+ ) {
+ TextField(
+ modifier = Modifier.menuAnchor(ExposedDropdownMenuAnchorType.PrimaryEditable),
+ state = rememberTextFieldState(),
+ lineLimits = TextFieldLineLimits.SingleLine,
+ label = { Text("Label") },
+ trailingIcon = {
+ ExposedDropdownMenuDefaults.TrailingIcon(
+ expanded = expanded,
+ modifier =
+ Modifier.menuAnchor(
+ ExposedDropdownMenuAnchorType.SecondaryEditable
+ ),
+ )
+ }
+ )
+ ExposedDropdownMenu(
+ expanded = expanded,
+ onDismissRequest = { expanded = false },
+ ) {
+ DropdownMenuItem(
+ text = { Text(OptionName) },
+ onClick = {},
+ )
+ }
+ SideEffect { type = anchorType }
+ }
+ }
+ rule.runOnIdle { expanded = true }
+ assertThat(type).isEqualTo(ExposedDropdownMenuAnchorType.PrimaryEditable)
+ }
+
+ @Test
fun edm_widthMatchesTextFieldWidth() {
var textFieldBounds by mutableStateOf(Rect.Zero)
var menuBounds by mutableStateOf(Rect.Zero)
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt
index 8fc3131..1293b79 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt
@@ -125,9 +125,20 @@
@Test
fun modalBottomSheet_isDismissedOnTapOutside() {
var showBottomSheet by mutableStateOf(true)
- val sheetState = SheetState(skipPartiallyExpanded = false, density = rule.density)
+ lateinit var sheetState: SheetState
rule.setContent {
+ val density = LocalDensity.current
+ sheetState =
+ SheetState(
+ skipPartiallyExpanded = false,
+ positionalThreshold = {
+ with(density) { BottomSheetDefaults.PositionalThreshold.toPx() }
+ },
+ velocityThreshold = {
+ with(density) { BottomSheetDefaults.VelocityThreshold.toPx() }
+ },
+ )
if (showBottomSheet) {
ModalBottomSheet(
sheetState = sheetState,
@@ -160,8 +171,16 @@
@Test
fun modalBottomSheet_isDismissedOnSwipeDown() {
var showBottomSheet by mutableStateOf(true)
- val sheetState = SheetState(skipPartiallyExpanded = false, density = rule.density)
-
+ val sheetState =
+ SheetState(
+ skipPartiallyExpanded = false,
+ positionalThreshold = {
+ with(rule.density) { BottomSheetDefaults.PositionalThreshold.toPx() }
+ },
+ velocityThreshold = {
+ with(rule.density) { BottomSheetDefaults.VelocityThreshold.toPx() }
+ },
+ )
rule.setContent {
if (showBottomSheet) {
ModalBottomSheet(
@@ -362,9 +381,20 @@
@Test
fun modalBottomSheet_shortSheet_isDismissedOnBackPress() {
var showBottomSheet by mutableStateOf(true)
- val sheetState = SheetState(skipPartiallyExpanded = true, density = rule.density)
+ lateinit var sheetState: SheetState
rule.setContent {
+ val density = LocalDensity.current
+ sheetState =
+ SheetState(
+ skipPartiallyExpanded = true,
+ positionalThreshold = {
+ with(density) { BottomSheetDefaults.PositionalThreshold.toPx() }
+ },
+ velocityThreshold = {
+ with(density) { BottomSheetDefaults.VelocityThreshold.toPx() }
+ },
+ )
val dispatcher = LocalOnBackPressedDispatcherOwner.current!!.onBackPressedDispatcher
if (showBottomSheet) {
ModalBottomSheet(
@@ -395,9 +425,20 @@
@Test
fun modalBottomSheet_tallSheet_isDismissedOnBackPress() {
var showBottomSheet by mutableStateOf(true)
- val sheetState = SheetState(skipPartiallyExpanded = false, density = rule.density)
+ lateinit var sheetState: SheetState
rule.setContent {
+ val density = LocalDensity.current
+ sheetState =
+ SheetState(
+ skipPartiallyExpanded = false,
+ positionalThreshold = {
+ with(density) { BottomSheetDefaults.PositionalThreshold.toPx() }
+ },
+ velocityThreshold = {
+ with(density) { BottomSheetDefaults.VelocityThreshold.toPx() }
+ },
+ )
val dispatcher = LocalOnBackPressedDispatcherOwner.current!!.onBackPressedDispatcher
if (showBottomSheet) {
ModalBottomSheet(
@@ -611,10 +652,21 @@
fun modalBottomSheet_missingAnchors_findsClosest() {
val topTag = "ModalBottomSheetLayout"
var showShortContent by mutableStateOf(false)
- val sheetState = SheetState(skipPartiallyExpanded = false, density = rule.density)
+ lateinit var sheetState: SheetState
lateinit var scope: CoroutineScope
rule.setContent {
+ val density = LocalDensity.current
+ sheetState =
+ SheetState(
+ skipPartiallyExpanded = false,
+ positionalThreshold = {
+ with(density) { BottomSheetDefaults.PositionalThreshold.toPx() }
+ },
+ velocityThreshold = {
+ with(density) { BottomSheetDefaults.VelocityThreshold.toPx() }
+ },
+ )
scope = rememberCoroutineScope()
ModalBottomSheet(
onDismissRequest = {},
@@ -790,8 +842,20 @@
@Test
fun modalBottomSheet_testParialExpandReturnsIllegalStateException_whenSkipPartialExpanded() {
lateinit var scope: CoroutineScope
- val bottomSheetState = SheetState(skipPartiallyExpanded = true, density = rule.density)
+ lateinit var bottomSheetState: SheetState
+
rule.setContent {
+ val density = LocalDensity.current
+ bottomSheetState =
+ SheetState(
+ skipPartiallyExpanded = true,
+ positionalThreshold = {
+ with(density) { BottomSheetDefaults.PositionalThreshold.toPx() }
+ },
+ velocityThreshold = {
+ with(density) { BottomSheetDefaults.VelocityThreshold.toPx() }
+ },
+ )
scope = rememberCoroutineScope()
ModalBottomSheet(
onDismissRequest = {},
@@ -931,10 +995,22 @@
@Test
fun modalBottomSheet_shortSheet_anchorChangeHandler_previousTargetNotInAnchors_reconciles() {
- val sheetState = SheetState(skipPartiallyExpanded = false, density = rule.density)
var hasSheetContent by mutableStateOf(false) // Start out with empty sheet content
lateinit var scope: CoroutineScope
+ lateinit var sheetState: SheetState
+
rule.setContent {
+ val density = LocalDensity.current
+ sheetState =
+ SheetState(
+ skipPartiallyExpanded = false,
+ positionalThreshold = {
+ with(density) { BottomSheetDefaults.PositionalThreshold.toPx() }
+ },
+ velocityThreshold = {
+ with(density) { BottomSheetDefaults.VelocityThreshold.toPx() }
+ },
+ )
scope = rememberCoroutineScope()
ModalBottomSheet(
@@ -968,10 +1044,22 @@
@Test
fun modalBottomSheet_tallSheet_anchorChangeHandler_previousTargetNotInAnchors_reconciles() {
- val sheetState = SheetState(skipPartiallyExpanded = false, density = rule.density)
var hasSheetContent by mutableStateOf(false) // Start out with empty sheet content
lateinit var scope: CoroutineScope
+ lateinit var sheetState: SheetState
+
rule.setContent {
+ val density = LocalDensity.current
+ sheetState =
+ SheetState(
+ skipPartiallyExpanded = false,
+ positionalThreshold = {
+ with(density) { BottomSheetDefaults.PositionalThreshold.toPx() }
+ },
+ velocityThreshold = {
+ with(density) { BottomSheetDefaults.VelocityThreshold.toPx() }
+ },
+ )
scope = rememberCoroutineScope()
ModalBottomSheet(
onDismissRequest = {},
@@ -1006,8 +1094,6 @@
fun modalBottomSheet_callsOnDismissRequest_onNestedScrollFling() {
var callCount by mutableStateOf(0)
val expectedCallCount = 1
- val sheetState = SheetState(skipPartiallyExpanded = true, density = rule.density)
-
val nestedScrollDispatcher = NestedScrollDispatcher()
val nestedScrollConnection =
object : NestedScrollConnection {
@@ -1015,7 +1101,20 @@
}
lateinit var scope: CoroutineScope
+ lateinit var sheetState: SheetState
+
rule.setContent {
+ val density = LocalDensity.current
+ sheetState =
+ SheetState(
+ skipPartiallyExpanded = true,
+ positionalThreshold = {
+ with(density) { BottomSheetDefaults.PositionalThreshold.toPx() }
+ },
+ velocityThreshold = {
+ with(density) { BottomSheetDefaults.VelocityThreshold.toPx() }
+ },
+ )
scope = rememberCoroutineScope()
ModalBottomSheet(
onDismissRequest = { callCount += 1 },
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/TooltipScreenshotTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/TooltipScreenshotTest.kt
index feb118a..f2b416d 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/TooltipScreenshotTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/TooltipScreenshotTest.kt
@@ -120,7 +120,7 @@
private fun PlainTooltipTest() {
val tooltipState = rememberTooltipState()
TooltipBox(
- positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
+ positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
tooltip = {
PlainTooltip(modifier = Modifier.testTag(TooltipTestTag)) {
Text("Tooltip Description")
@@ -137,7 +137,7 @@
private fun RichTooltipTest() {
val tooltipState = rememberTooltipState(isPersistent = true)
TooltipBox(
- positionProvider = TooltipDefaults.rememberRichTooltipPositionProvider(),
+ positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
tooltip = {
RichTooltip(
title = { Text("Title") },
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/TooltipTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/TooltipTest.kt
index 0cfc573..fccc9c2 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/TooltipTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/TooltipTest.kt
@@ -465,9 +465,7 @@
// Plain tooltip positioning
lateinit var positionProvider: PopupPositionProvider
- rule.setContent {
- positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider()
- }
+ rule.setContent { positionProvider = TooltipDefaults.rememberTooltipPositionProvider() }
val tooltipPosition =
positionProvider.calculatePosition(
@@ -500,7 +498,7 @@
// Rich tooltip positioning
lateinit var positionProvider: PopupPositionProvider
- rule.setContent { positionProvider = TooltipDefaults.rememberRichTooltipPositionProvider() }
+ rule.setContent { positionProvider = TooltipDefaults.rememberTooltipPositionProvider() }
val tooltipPosition =
positionProvider.calculatePosition(
@@ -521,7 +519,7 @@
var anchorBounds = Rect.Zero
rule.setContent {
TooltipBox(
- positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
+ positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
state = rememberTooltipState(initialIsVisible = true, isPersistent = true),
tooltip = {
PlainTooltip(
@@ -565,7 +563,7 @@
var anchorBounds = Rect.Zero
rule.setContent {
TooltipBox(
- positionProvider = TooltipDefaults.rememberRichTooltipPositionProvider(),
+ positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
state = rememberTooltipState(initialIsVisible = true, isPersistent = true),
tooltip = {
RichTooltip(
@@ -615,7 +613,7 @@
topState = rememberTooltipState(isPersistent = true)
bottomState = rememberTooltipState(isPersistent = true)
TooltipBox(
- positionProvider = TooltipDefaults.rememberRichTooltipPositionProvider(),
+ positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
tooltip = {
RichTooltip(
title = {
@@ -636,7 +634,7 @@
scope.launch { topState.show() }
TooltipBox(
- positionProvider = TooltipDefaults.rememberRichTooltipPositionProvider(),
+ positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
tooltip = {
RichTooltip(
title = {
@@ -679,7 +677,7 @@
val scope = rememberCoroutineScope()
topState = rememberTooltipState(isPersistent = true, mutatorMutex = MutatorMutex())
TooltipBox(
- positionProvider = TooltipDefaults.rememberRichTooltipPositionProvider(),
+ positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
tooltip = {
RichTooltip(
title = {
@@ -701,7 +699,7 @@
bottomState = rememberTooltipState(isPersistent = true, mutatorMutex = MutatorMutex())
TooltipBox(
- positionProvider = TooltipDefaults.rememberRichTooltipPositionProvider(),
+ positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
tooltip = {
RichTooltip(
title = {
@@ -741,7 +739,7 @@
tooltipState: TooltipState = rememberTooltipState(),
) {
TooltipBox(
- positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
+ positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
tooltip = {
PlainTooltip(
modifier = modifier.testTag(ContainerTestTag),
@@ -763,7 +761,7 @@
tooltipState: TooltipState = rememberTooltipState(action != null),
) {
TooltipBox(
- positionProvider = TooltipDefaults.rememberRichTooltipPositionProvider(),
+ positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
tooltip = {
RichTooltip(
title = title,
@@ -782,7 +780,7 @@
fun plainTooltip_withClickable_hasCorrectSemantics() {
rule.setMaterialContent(lightColorScheme()) {
TooltipBox(
- positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
+ positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
tooltip = {
PlainTooltip(
modifier = Modifier.testTag(ContainerTestTag),
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/WavyProgressIndicatorTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/WavyProgressIndicatorTest.kt
index b4fa43b..edfc828 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/WavyProgressIndicatorTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/WavyProgressIndicatorTest.kt
@@ -161,21 +161,29 @@
fun determinateLinearWavyProgressIndicator_sizeModifier() {
val expectedWidth = 100.dp
val expectedHeight = 10.dp
- val expectedSize =
- with(rule.density) { IntSize(expectedWidth.roundToPx(), expectedHeight.roundToPx()) }
val tag = "linear"
- var trackColor = Color.Unspecified
- var progressColor = Color.Unspecified
- rule.setContent {
- trackColor = ProgressIndicatorDefaults.linearTrackColor
- progressColor = ProgressIndicatorDefaults.linearColor
-
- Box(Modifier.testTag(tag)) {
+ val contentToTest =
+ rule.setMaterialContentForSizeAssertions {
LinearWavyProgressIndicator(
- modifier = Modifier.size(expectedWidth, expectedHeight),
+ modifier = Modifier.size(expectedWidth, expectedHeight).testTag(tag),
progress = { 0.5f }
)
}
+
+ contentToTest.assertWidthIsEqualTo(expectedWidth).assertHeightIsEqualTo(expectedHeight)
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ @Test
+ fun determinateLinearWavyProgressIndicator_colors() {
+ val tag = "linear"
+ var trackColor = Color.Unspecified
+ var progressColor = Color.Unspecified
+ rule.setMaterialContentForSizeAssertions {
+ trackColor = ProgressIndicatorDefaults.linearTrackColor
+ progressColor = ProgressIndicatorDefaults.linearColor
+
+ Box(Modifier.testTag(tag)) { LinearWavyProgressIndicator(progress = { 0.5f }) }
}
rule
@@ -183,11 +191,6 @@
.captureToImage()
.assertContainsColor(trackColor)
.assertContainsColor(progressColor)
- .toPixelMap()
- .let {
- assertEquals(expectedSize.width, it.width)
- assertEquals(expectedSize.height, it.height)
- }
}
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
@@ -195,24 +198,29 @@
fun indeterminateLinearWavyProgressIndicator_sizeModifier() {
val expectedWidth = 100.dp
val expectedHeight = 10.dp
- val expectedSize =
- with(rule.density) { IntSize(expectedWidth.roundToPx(), expectedHeight.roundToPx()) }
- rule.mainClock.autoAdvance = false
val tag = "linear"
- rule.setContent {
- Box(Modifier.testTag(tag)) {
+ val contentToTest =
+ rule.setMaterialContentForSizeAssertions {
LinearWavyProgressIndicator(
- modifier = Modifier.size(expectedWidth, expectedHeight),
- color = Color.Blue
+ modifier = Modifier.size(expectedWidth, expectedHeight).testTag(tag),
)
}
+
+ contentToTest.assertWidthIsEqualTo(expectedWidth).assertHeightIsEqualTo(expectedHeight)
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ @Test
+ fun indeterminateLinearWavyProgressIndicator_colors() {
+ rule.mainClock.autoAdvance = false
+ val tag = "linear"
+ rule.setMaterialContentForSizeAssertions {
+ Box(Modifier.testTag(tag)) { LinearWavyProgressIndicator(color = Color.Blue) }
}
rule.mainClock.advanceTimeBy(300)
rule.onNodeWithTag(tag).captureToImage().toPixelMap().let {
- assertEquals(expectedSize.width, it.width)
- assertEquals(expectedSize.height, it.height)
// Assert that a center pixel relatively at the start of the path has the right
// progress color.
it.assertPixelColor(Color.Blue, 5, it.height / 2)
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ExposedDropdownMenu.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ExposedDropdownMenu.android.kt
index 9c9a97b..d67fed8 100644
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ExposedDropdownMenu.android.kt
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ExposedDropdownMenu.android.kt
@@ -68,6 +68,8 @@
import androidx.compose.ui.layout.layout
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.positionInWindow
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.platform.InspectorInfo
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
@@ -168,6 +170,13 @@
): Modifier =
this.focusRequester(focusRequester)
.then(
+ ExposedDropdownMenuAnchorElement {
+ if (type.hasGreaterOrEqualPriorityThan(anchorTypeState.value)) {
+ anchorTypeState.value = type
+ }
+ }
+ )
+ .then(
if (!enabled) Modifier
else
Modifier.expandable(
@@ -516,6 +525,17 @@
override fun toString(): String = name
}
+private fun ExposedDropdownMenuAnchorType.hasGreaterOrEqualPriorityThan(
+ that: ExposedDropdownMenuAnchorType
+): Boolean =
+ when (this) {
+ ExposedDropdownMenuAnchorType.PrimaryNotEditable,
+ ExposedDropdownMenuAnchorType.PrimaryEditable -> true
+ ExposedDropdownMenuAnchorType.SecondaryEditable ->
+ that == ExposedDropdownMenuAnchorType.SecondaryEditable
+ else -> false
+ }
+
@Deprecated(
message = "Renamed to ExposedDropdownMenuAnchorType",
replaceWith = ReplaceWith("ExposedDropdownMenuAnchorType"),
@@ -1434,6 +1454,29 @@
}
}
+private data class ExposedDropdownMenuAnchorElement(
+ val updateStateOnAttach: () -> Unit,
+) : ModifierNodeElement<ExposedDropdownMenuAnchorNode>() {
+ override fun create() = ExposedDropdownMenuAnchorNode(updateStateOnAttach)
+
+ override fun update(node: ExposedDropdownMenuAnchorNode) {
+ node.updateStateOnAttach = updateStateOnAttach
+ }
+
+ override fun InspectorInfo.inspectableProperties() {
+ name = "exposedDropdownMenuAnchorType"
+ properties["updateStateOnAttach"] = updateStateOnAttach
+ }
+}
+
+private class ExposedDropdownMenuAnchorNode(
+ var updateStateOnAttach: () -> Unit,
+) : Modifier.Node() {
+ override fun onAttach() {
+ updateStateOnAttach()
+ }
+}
+
private fun Modifier.expandable(
expanded: Boolean,
onExpandedChange: () -> Unit,
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/Tooltip.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/Tooltip.android.kt
index c2c9412..ffa265c 100644
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/Tooltip.android.kt
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/Tooltip.android.kt
@@ -125,7 +125,6 @@
val configuration = LocalConfiguration.current
Modifier.drawCaret { anchorLayoutCoordinates ->
drawCaretWithPath(
- CaretType.Plain,
density,
configuration,
containerColor,
@@ -253,7 +252,6 @@
val configuration = LocalConfiguration.current
Modifier.drawCaret { anchorLayoutCoordinates ->
drawCaretWithPath(
- CaretType.Rich,
density,
configuration,
elevatedColor,
@@ -315,7 +313,6 @@
@ExperimentalMaterial3Api
private fun CacheDrawScope.drawCaretWithPath(
- caretType: CaretType,
density: Density,
configuration: Configuration,
containerColor: Color,
@@ -351,42 +348,27 @@
tooltipHeight
}
- val position: Offset
- if (caretType == CaretType.Plain) {
- position =
- if (anchorMid + tooltipWidth / 2 > screenWidthPx) {
- // Caret needs to be near the right
- val anchorMidFromRightScreenEdge = screenWidthPx - anchorMid
- val caretX = tooltipWidth - anchorMidFromRightScreenEdge
- Offset(caretX, caretY)
- } else {
- // Caret needs to be near the left
- val tooltipLeft = anchorLeft - (this.size.width / 2 - anchorWidth / 2)
- val caretX = anchorMid - maxOf(tooltipLeft, 0f)
- Offset(caretX, caretY)
- }
- } else {
- // Default the caret to the left
- var preferredPosition = Offset(anchorMid - anchorLeft, caretY)
- if (anchorLeft + tooltipWidth > screenWidthPx) {
- // Need to move the caret to the right
- preferredPosition = Offset(anchorMid - (anchorRight - tooltipWidth), caretY)
- if (anchorRight - tooltipWidth < 0) {
- // Need to center the caret
- // Caret might need to be offset depending on where
- // the tooltip is placed relative to the anchor
- if (anchorLeft - tooltipWidth / 2 + anchorWidth / 2 <= 0) {
- preferredPosition = Offset(anchorMid, caretY)
- } else if (anchorRight + tooltipWidth / 2 - anchorWidth / 2 >= screenWidthPx) {
- val anchorMidFromRightScreenEdge = screenWidthPx - anchorMid
- val caretX = tooltipWidth - anchorMidFromRightScreenEdge
- preferredPosition = Offset(caretX, caretY)
- } else {
- preferredPosition = Offset(tooltipWidth / 2, caretY)
- }
- }
+ // Default the caret to be in the middle
+ // caret might need to be offset depending on where
+ // the tooltip is placed relative to the anchor
+ var position: Offset =
+ if (anchorLeft - tooltipWidth / 2 + anchorWidth / 2 <= 0) {
+ Offset(anchorMid, caretY)
+ } else if (anchorRight + tooltipWidth / 2 - anchorWidth / 2 >= screenWidthPx) {
+ val anchorMidFromRightScreenEdge = screenWidthPx - anchorMid
+ val caretX = tooltipWidth - anchorMidFromRightScreenEdge
+ Offset(caretX, caretY)
+ } else {
+ Offset(tooltipWidth / 2, caretY)
}
- position = preferredPosition
+ if (anchorMid - tooltipWidth / 2 < 0) {
+ // The tooltip needs to be start aligned if it would collide with the left side of
+ // screen.
+ position = Offset(anchorMid - anchorLeft, caretY)
+ } else if (anchorMid + tooltipWidth / 2 > screenWidthPx) {
+ // The tooltip needs to be end aligned if it would collide with the right side of the
+ // screen.
+ position = Offset(anchorMid - (anchorRight - tooltipWidth), caretY)
}
if (isCaretTop) {
@@ -415,9 +397,3 @@
}
}
}
-
-@ExperimentalMaterial3Api
-private enum class CaretType {
- Plain,
- Rich
-}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DragHandle.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DragHandle.kt
new file mode 100644
index 0000000..da5127e
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DragHandle.kt
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material3
+
+import androidx.compose.foundation.gestures.PressGestureScope
+import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.hoverable
+import androidx.compose.foundation.indication
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.interaction.collectIsDraggedAsState
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.DragHandleDefaults.dragHandleColors
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Immutable
+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.draw.drawBehind
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.graphics.isSpecified
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.layout.layout
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.DpSize
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.fastRoundToInt
+
+@Composable
+internal fun VerticalDragHandle(
+ modifier: Modifier = Modifier,
+ sizes: DragHandleSizes = DragHandleDefaults.DefaultDragHandleSizes,
+ colors: DragHandleColors = MaterialTheme.colorScheme.dragHandleColors(),
+ shapes: DragHandleShapes = DragHandleDefaults.DefaultDragHandleShapes,
+ interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
+) {
+ val isDragged by interactionSource.collectIsDraggedAsState()
+ var isPressed by remember { mutableStateOf(false) }
+ Box(
+ modifier =
+ modifier
+ .minimumInteractiveComponentSize()
+ .hoverable(interactionSource)
+ .pressable(interactionSource) { _ ->
+ isPressed = true
+ tryAwaitRelease()
+ isPressed = false
+ }
+ .graphicsLayer {
+ shape = if (isDragged || isPressed) shapes.pressedShape else shapes.defaultShape
+ clip = true
+ }
+ .layout { measurable, _ ->
+ val dragHandleSize =
+ if (isDragged || isPressed) {
+ sizes.pressedSize
+ } else {
+ sizes.defaultSize
+ }
+ .toSize()
+ // set constraints here to be the size needed
+ val placeable =
+ measurable.measure(
+ Constraints.fixed(
+ dragHandleSize.width.fastRoundToInt(),
+ dragHandleSize.height.fastRoundToInt()
+ )
+ )
+ layout(placeable.width, placeable.height) { placeable.placeRelative(0, 0) }
+ }
+ .drawBehind {
+ drawRect(
+ if (isDragged || isPressed) colors.pressedColor else colors.defaultColor
+ )
+ }
+ .indication(interactionSource, ripple())
+ )
+}
+
+@Immutable
+internal class DragHandleColors(val defaultColor: Color, val pressedColor: Color) {
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other == null || other !is DragHandleColors) return false
+ if (defaultColor != other.defaultColor) return false
+ if (pressedColor != other.pressedColor) return false
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = defaultColor.hashCode()
+ result = 31 * result + pressedColor.hashCode()
+ return result
+ }
+}
+
+@Immutable
+internal class DragHandleShapes(val defaultShape: Shape, val pressedShape: Shape) {
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other == null || other !is DragHandleShapes) return false
+ if (defaultShape != other.defaultShape) return false
+ if (pressedShape != other.pressedShape) return false
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = defaultShape.hashCode()
+ result = 31 * result + pressedShape.hashCode()
+ return result
+ }
+}
+
+@Immutable
+internal class DragHandleSizes(val defaultSize: DpSize, val pressedSize: DpSize) {
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other == null || other !is DragHandleSizes) return false
+ if (defaultSize != other.defaultSize) return false
+ if (pressedSize != other.pressedSize) return false
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = defaultSize.hashCode()
+ result = 31 * result + pressedSize.hashCode()
+ return result
+ }
+}
+
+// TODO(b/343194663): Introduce tokens and theming
+internal object DragHandleDefaults {
+ @Composable
+ fun dragHandleColors(
+ defaultColor: Color = Color.Unspecified,
+ pressedColor: Color = Color.Unspecified
+ ): DragHandleColors = MaterialTheme.colorScheme.dragHandleColors(defaultColor, pressedColor)
+
+ fun dragHandleShapes(
+ defaultShape: Shape = CircleShape,
+ pressedShape: Shape = RoundedCornerShape(12.dp)
+ ): DragHandleShapes = DragHandleShapes(defaultShape, pressedShape)
+
+ fun dragHandleSizes(
+ defaultSize: DpSize = DpSize(4.dp, 48.dp),
+ pressedSize: DpSize = DpSize(12.dp, 52.dp)
+ ): DragHandleSizes = DragHandleSizes(defaultSize, pressedSize)
+
+ internal fun ColorScheme.dragHandleColors(
+ defaultColor: Color = Color.Unspecified,
+ pressedColor: Color = Color.Unspecified
+ ): DragHandleColors =
+ DragHandleColors(
+ if (defaultColor.isSpecified) defaultColor else outline,
+ if (pressedColor.isSpecified) pressedColor else onSurface
+ )
+
+ internal val DefaultDragHandleShapes = dragHandleShapes()
+
+ internal val DefaultDragHandleSizes = dragHandleSizes()
+}
+
+private fun Modifier.pressable(
+ interactionSource: MutableInteractionSource,
+ onPress: suspend PressGestureScope.(Offset) -> Unit,
+): Modifier = pointerInput(interactionSource) { detectTapGestures(onPress = onPress) }
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
index 27ac94b..b3a8bee 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Label.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Label.kt
@@ -41,7 +41,7 @@
/**
* Label component that will append a [label] to [content]. The positioning logic uses
- * [TooltipDefaults.rememberPlainTooltipPositionProvider].
+ * [TooltipDefaults.rememberTooltipPositionProvider].
*
* Label appended to thumbs of Slider:
*
@@ -71,7 +71,7 @@
@Suppress("NAME_SHADOWING")
val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
// Has the same positioning logic as PlainTooltips
- val positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider()
+ val positionProvider = TooltipDefaults.rememberTooltipPositionProvider()
val state =
if (isPersistent) remember { LabelStateImpl() }
else rememberBasicTooltipState(mutatorMutex = MutatorMutex())
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SheetDefaults.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SheetDefaults.kt
index 12620b9..66d3d8d 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SheetDefaults.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SheetDefaults.kt
@@ -69,8 +69,15 @@
* should be skipped. If true, the sheet will always expand to the [Expanded] state and move to
* the [Hidden] state if available when hiding the sheet, either programmatically or by user
* interaction.
+ * @param positionalThreshold The positional threshold, in px, to be used when calculating the
+ * target state while a drag is in progress and when settling after the drag ends. This is the
+ * distance from the start of a transition. It will be, depending on the direction of the
+ * interaction, added or subtracted from/to the origin offset. It should always be a positive
+ * value.
+ * @param velocityThreshold The velocity threshold (in px per second) that the end velocity has to
+ * exceed in order to animate to the next state, even if the [positionalThreshold] has not been
+ * reached.
* @param initialValue The initial value of the state.
- * @param density The density that this state can use to convert values to and from dp.
* @param confirmValueChange Optional callback invoked to confirm or veto a pending state change.
* @param skipHiddenState Whether the hidden state should be skipped. If true, the sheet will always
* expand to the [Expanded] state and move to the [PartiallyExpanded] if available, either
@@ -80,11 +87,13 @@
@ExperimentalMaterial3Api
class SheetState(
internal val skipPartiallyExpanded: Boolean,
- density: Density,
+ positionalThreshold: () -> Float,
+ velocityThreshold: () -> Float,
initialValue: SheetValue = Hidden,
confirmValueChange: (SheetValue) -> Boolean = { true },
internal val skipHiddenState: Boolean = false,
) {
+
init {
if (skipPartiallyExpanded) {
require(initialValue != PartiallyExpanded) {
@@ -270,8 +279,8 @@
initialValue = initialValue,
animationSpec = { anchoredDraggableMotionSpec },
confirmValueChange = confirmValueChange,
- positionalThreshold = { with(density) { 56.dp.toPx() } },
- velocityThreshold = { with(density) { 125.dp.toPx() } },
+ positionalThreshold = { positionalThreshold() },
+ velocityThreshold = velocityThreshold,
)
internal val offset: Float
@@ -285,8 +294,9 @@
/** The default [Saver] implementation for [SheetState]. */
fun Saver(
skipPartiallyExpanded: Boolean,
+ positionalThreshold: () -> Float,
+ velocityThreshold: () -> Float,
confirmValueChange: (SheetValue) -> Boolean,
- density: Density,
skipHiddenState: Boolean,
) =
Saver<SheetState, SheetValue>(
@@ -294,14 +304,53 @@
restore = { savedValue ->
SheetState(
skipPartiallyExpanded,
- density,
+ positionalThreshold,
+ velocityThreshold,
savedValue,
confirmValueChange,
skipHiddenState,
)
}
)
+
+ @Deprecated(
+ level = DeprecationLevel.HIDDEN,
+ message = "Maintained for binary compatibility."
+ )
+ fun Saver(
+ skipPartiallyExpanded: Boolean,
+ confirmValueChange: (SheetValue) -> Boolean,
+ density: Density,
+ skipHiddenState: Boolean,
+ ) =
+ Saver(
+ skipPartiallyExpanded = skipPartiallyExpanded,
+ confirmValueChange = confirmValueChange,
+ skipHiddenState = skipHiddenState,
+ positionalThreshold = {
+ with(density) { BottomSheetDefaults.PositionalThreshold.toPx() }
+ },
+ velocityThreshold = {
+ with(density) { BottomSheetDefaults.VelocityThreshold.toPx() }
+ }
+ )
}
+
+ @Deprecated(level = DeprecationLevel.HIDDEN, message = "Maintained for binary compatibility.")
+ constructor(
+ skipPartiallyExpanded: Boolean,
+ density: Density,
+ initialValue: SheetValue = Hidden,
+ confirmValueChange: (SheetValue) -> Boolean = { true },
+ skipHiddenState: Boolean = false,
+ ) : this(
+ skipPartiallyExpanded = skipPartiallyExpanded,
+ positionalThreshold = { with(density) { BottomSheetDefaults.PositionalThreshold.toPx() } },
+ velocityThreshold = { with(density) { BottomSheetDefaults.VelocityThreshold.toPx() } },
+ initialValue = initialValue,
+ confirmValueChange = confirmValueChange,
+ skipHiddenState = skipHiddenState,
+ )
}
/** Possible values of [SheetState]. */
@@ -350,6 +399,10 @@
val windowInsets: WindowInsets
@Composable get() = WindowInsets.safeDrawing.only(WindowInsetsSides.Bottom)
+ internal val PositionalThreshold = 56.dp
+
+ internal val VelocityThreshold = 125.dp
+
/** The optional visual marker placed on top of a bottom sheet to indicate it may be dragged. */
@Composable
fun DragHandle(
@@ -439,8 +492,12 @@
confirmValueChange: (SheetValue) -> Boolean = { true },
initialValue: SheetValue = Hidden,
skipHiddenState: Boolean = false,
+ positionalThreshold: Dp = BottomSheetDefaults.PositionalThreshold,
+ velocityThreshold: Dp = BottomSheetDefaults.VelocityThreshold,
): SheetState {
val density = LocalDensity.current
+ val positionalThresholdToPx = { with(density) { positionalThreshold.toPx() } }
+ val velocityThresholdToPx = { with(density) { velocityThreshold.toPx() } }
return rememberSaveable(
skipPartiallyExpanded,
confirmValueChange,
@@ -448,14 +505,16 @@
saver =
SheetState.Saver(
skipPartiallyExpanded = skipPartiallyExpanded,
+ positionalThreshold = positionalThresholdToPx,
+ velocityThreshold = velocityThresholdToPx,
confirmValueChange = confirmValueChange,
- density = density,
skipHiddenState = skipHiddenState,
)
) {
SheetState(
skipPartiallyExpanded,
- density,
+ positionalThresholdToPx,
+ velocityThresholdToPx,
initialValue,
confirmValueChange,
skipHiddenState,
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 c345413..0784a16 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
@@ -374,6 +374,12 @@
*
* @param spacingBetweenTooltipAndAnchor the spacing between the tooltip and the anchor content.
*/
+ @Deprecated(
+ "Deprecated in favor of rememberTooltipPositionProvider API.",
+ replaceWith =
+ ReplaceWith("rememberTooltipPositionProvider(spacingBetweenTooltipAndAnchor)"),
+ level = DeprecationLevel.HIDDEN
+ )
@Composable
fun rememberPlainTooltipPositionProvider(
spacingBetweenTooltipAndAnchor: Dp = SpacingBetweenTooltipAndAnchor
@@ -407,6 +413,12 @@
*
* @param spacingBetweenTooltipAndAnchor the spacing between the tooltip and the anchor content.
*/
+ @Deprecated(
+ "Deprecated in favor of rememberTooltipPositionProvider API.",
+ replaceWith =
+ ReplaceWith("rememberTooltipPositionProvider(spacingBetweenTooltipAndAnchor)"),
+ level = DeprecationLevel.HIDDEN
+ )
@Composable
fun rememberRichTooltipPositionProvider(
spacingBetweenTooltipAndAnchor: Dp = SpacingBetweenTooltipAndAnchor
@@ -443,6 +455,53 @@
}
}
}
+
+ /**
+ * [PopupPositionProvider] that should be used with either [RichTooltip] or [PlainTooltip]. It
+ * correctly positions the tooltip in respect to the anchor content.
+ *
+ * @param spacingBetweenTooltipAndAnchor the spacing between the tooltip and the anchor content.
+ */
+ @Composable
+ fun rememberTooltipPositionProvider(
+ spacingBetweenTooltipAndAnchor: Dp = SpacingBetweenTooltipAndAnchor
+ ): PopupPositionProvider {
+ val tooltipAnchorSpacing =
+ with(LocalDensity.current) { spacingBetweenTooltipAndAnchor.roundToPx() }
+ return remember(tooltipAnchorSpacing) {
+ object : PopupPositionProvider {
+ override fun calculatePosition(
+ anchorBounds: IntRect,
+ windowSize: IntSize,
+ layoutDirection: LayoutDirection,
+ popupContentSize: IntSize
+ ): IntOffset {
+ // Horizontal alignment preference: middle -> start -> end
+ // Vertical preference: above -> below
+
+ // Tooltip prefers to be center aligned horizontally.
+ var x = anchorBounds.left + (anchorBounds.width - popupContentSize.width) / 2
+
+ if (x < 0) {
+ // Make tooltip start aligned if colliding with the
+ // left side of the screen
+ x = anchorBounds.left
+ } else if (x + popupContentSize.width > windowSize.width) {
+ // Make tooltip end aligned if colliding with the
+ // right side of the screen
+ x = anchorBounds.right - popupContentSize.width
+ }
+
+ // Tooltip prefers to be above the anchor,
+ // but if this causes the tooltip to overlap with the anchor
+ // then we place it below the anchor
+ var y = anchorBounds.top - popupContentSize.height - tooltipAnchorSpacing
+ if (y < 0) y = anchorBounds.bottom + tooltipAnchorSpacing
+ return IntOffset(x, y)
+ }
+ }
+ }
+ }
}
@Stable
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/WavyProgressIndicator.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/WavyProgressIndicator.kt
index 70fea23..3debdfc 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/WavyProgressIndicator.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/WavyProgressIndicator.kt
@@ -30,7 +30,6 @@
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.requiredSizeIn
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.progressSemantics
import androidx.compose.material3.internal.IncreaseVerticalSemanticsBounds
@@ -185,7 +184,6 @@
.semantics(mergeDescendants = true) {
progressBarRangeInfo = ProgressBarRangeInfo(coercedProgress(), 0f..1f)
}
- .requiredSizeIn(minWidth = LinearContainerMinWidth)
.size(
width = WavyProgressIndicatorDefaults.LinearContainerWidth,
height = WavyProgressIndicatorDefaults.LinearContainerHeight
@@ -373,7 +371,6 @@
modifier
.then(IncreaseVerticalSemanticsBounds)
.progressSemantics()
- .requiredSizeIn(minWidth = LinearContainerMinWidth)
.size(
WavyProgressIndicatorDefaults.LinearContainerWidth,
WavyProgressIndicatorDefaults.LinearContainerHeight
diff --git a/compose/runtime/runtime-test-utils/build.gradle b/compose/runtime/runtime-test-utils/build.gradle
index f5ee280..f9f82e1 100644
--- a/compose/runtime/runtime-test-utils/build.gradle
+++ b/compose/runtime/runtime-test-utils/build.gradle
@@ -19,12 +19,21 @@
plugins {
id("AndroidXPlugin")
- id("com.android.library")
id("AndroidXComposePlugin")
}
androidXMultiplatform {
- android()
+ androidLibrary {
+ namespace = "androidx.compose.runtime.testutils"
+ withAndroidTestOnDeviceBuilder {
+ it.compilationName = "instrumentedTest"
+ it.defaultSourceSetName = "androidInstrumentedTest"
+ it.sourceSetTreeName = "test"
+ }
+ withAndroidTestOnJvmBuilder {
+ it.defaultSourceSetName = "androidUnitTest"
+ }
+ }
jvmStubs()
linuxX64Stubs()
@@ -73,6 +82,3 @@
description = "Compose runtime test utils shared between runtime and compiler tests."
}
-android {
- namespace "androidx.compose.runtime.testutils"
-}
diff --git a/compose/runtime/runtime/integration-tests/build.gradle b/compose/runtime/runtime/integration-tests/build.gradle
index 233a8eb..791773c 100644
--- a/compose/runtime/runtime/integration-tests/build.gradle
+++ b/compose/runtime/runtime/integration-tests/build.gradle
@@ -25,12 +25,24 @@
plugins {
id("AndroidXPlugin")
- id("com.android.library")
id("AndroidXComposePlugin")
}
androidXMultiplatform {
- android()
+ androidLibrary {
+ namespace = "androidx.compose.runtime.integrationtests"
+ withAndroidTestOnDeviceBuilder {
+ it.compilationName = "instrumentedTest"
+ it.defaultSourceSetName = "androidInstrumentedTest"
+ it.sourceSetTreeName = "test"
+ }
+ withAndroidTestOnJvmBuilder {
+ it.defaultSourceSetName = "androidUnitTest"
+ }
+
+ compileSdk = 35
+ aarMetadata.minCompileSdk = 35
+ }
sourceSets {
commonMain {
@@ -92,10 +104,6 @@
}
}
-android {
- compileSdk 35
- namespace "androidx.compose.runtime.integrationtests"
-}
tasks.withType(KotlinCompile).configureEach {
kotlinOptions {
@@ -109,7 +117,7 @@
}
public File findFile() {
- project.file("src/androidAndroidTest/kotlin/androidx/compose/runtime/GroupSizeTests.kt")
+ project.file("src/androidInstrumentedTest/kotlin/androidx/compose/runtime/GroupSizeTests.kt")
}
class UpdateExpectedGroupSizes extends DefaultTask {
diff --git a/compose/ui/ui-geometry/build.gradle b/compose/ui/ui-geometry/build.gradle
index 552d9fb..b9b102b 100644
--- a/compose/ui/ui-geometry/build.gradle
+++ b/compose/ui/ui-geometry/build.gradle
@@ -28,13 +28,21 @@
plugins {
id("AndroidXPlugin")
- id("com.android.library")
id("AndroidXComposePlugin")
}
-
androidXMultiplatform {
- android()
+ androidLibrary {
+ namespace = "androidx.compose.ui.geometry"
+ withAndroidTestOnDeviceBuilder {
+ it.compilationName = "instrumentedTest"
+ it.defaultSourceSetName = "androidInstrumentedTest"
+ it.sourceSetTreeName = "test"
+ }
+ withAndroidTestOnJvmBuilder {
+ it.defaultSourceSetName = "androidUnitTest"
+ }
+ }
jvmStubs()
linuxX64Stubs()
@@ -103,6 +111,3 @@
kotlinTarget = KotlinTarget.KOTLIN_1_9
}
-android {
- namespace "androidx.compose.ui.geometry"
-}
diff --git a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/AlternateViewHelper.kt b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/AlternateViewHelper.kt
new file mode 100644
index 0000000..5952212
--- /dev/null
+++ b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/AlternateViewHelper.kt
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.inspection
+
+import android.annotation.SuppressLint
+import android.app.Activity
+import android.os.Build
+import android.view.SurfaceControlViewHost
+import android.view.View
+import androidx.annotation.RequiresApi
+import androidx.inspection.InspectorEnvironment
+import java.lang.reflect.Field
+import java.lang.reflect.Method
+
+private const val PANEL_ENTITY_CLASS = "com.google.vr.androidx.xr.core.PanelEntity"
+private const val PANEL_ENTITY_IMPL_CLASS =
+ "com.google.vr.realitycore.runtime.androidxr.PanelEntityImpl"
+private const val JXR_CORE_SESSION_CLASS = "com.google.vr.androidx.xr.core.Session"
+
+private const val GET_ENTITIES_OF_TYPE_METHOD = "getEntitiesOfType"
+private const val IS_HIDDEN_METHOD = "isHidden"
+
+private const val SURFACE_CONTROL_VIEW_HOST_FIELD = "surfaceControlViewHost"
+private const val RT_PANEL_ENTITY_FIELD = "rtPanelEntity"
+
+class AlternateViewHelper(private val environment: InspectorEnvironment) {
+ private val activity = environment.artTooling().findInstances(Activity::class.java).first()
+
+ fun getAlternateViews(): List<View> {
+ return try {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) getExtraViewsImpl() else emptyList()
+ } catch (ex: Exception) {
+ emptyList()
+ }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.R)
+ private fun getExtraViewsImpl(): List<View> {
+ val sessionClass = loadClass(JXR_CORE_SESSION_CLASS)
+ val xrSessions = environment.artTooling().findInstances(sessionClass)
+ return xrSessions.flatMap { getSessionViews(it) }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.R)
+ @SuppressLint("BanUncheckedReflection")
+ private fun getSessionViews(session: Any): List<View> {
+ val entitiesFun =
+ loadMethod(session.javaClass, GET_ENTITIES_OF_TYPE_METHOD, Class::class.java)
+ val entityClass = loadClass(PANEL_ENTITY_CLASS)
+ val entities = entitiesFun.invoke(session, entityClass) as List<*>
+ return entities.mapNotNull { entity -> entity?.let { getView(it) } }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.R)
+ private fun getView(entity: Any): View? {
+ if (isHidden(entity)) {
+ return null
+ }
+ return entity
+ .mapAllFields { field ->
+ if (field.name == RT_PANEL_ENTITY_FIELD) {
+ getRuntimeEntityView(field.get(entity)!!)
+ } else {
+ null
+ }
+ }
+ .filterNotNull()
+ .firstOrNull()
+ }
+
+ @SuppressLint("BanUncheckedReflection")
+ private fun isHidden(instance: Any): Boolean {
+ var isHidden = false
+
+ runCatching {
+ instance.mapAllMethods { method ->
+ if (method.name == IS_HIDDEN_METHOD) {
+ isHidden = method.invoke(instance, true) as Boolean
+ return@mapAllMethods
+ }
+ }
+ }
+
+ return isHidden
+ }
+
+ @RequiresApi(Build.VERSION_CODES.R)
+ private fun getRuntimeEntityView(instance: Any): View? {
+ val clazz = instance.javaClass
+ if (clazz.name != PANEL_ENTITY_IMPL_CLASS) {
+ return null
+ }
+ val surfaceControlViewHostField = loadField(clazz, SURFACE_CONTROL_VIEW_HOST_FIELD)
+ if (surfaceControlViewHostField != null) {
+ surfaceControlViewHostField.isAccessible = true
+ val surfaceControlViewHost =
+ surfaceControlViewHostField.get(instance) as SurfaceControlViewHost
+ return surfaceControlViewHost.view
+ } else {
+ return null
+ }
+ }
+
+ @Suppress("UnnecessaryLambdaCreation")
+ private fun <T> Any.mapAllFields(block: (filed: Field) -> T): List<T> {
+ var clazz: Class<*>? = javaClass
+ val results = mutableListOf<T>()
+
+ while (clazz != Any::class.java && clazz != null) {
+ results.addAll(loadFields(clazz).map { block(it) })
+
+ // Move to the superclass
+ clazz = clazz.superclass
+ }
+
+ return results
+ }
+
+ @Suppress("UnnecessaryLambdaCreation")
+ private fun <T> Any.mapAllMethods(block: (method: Method) -> T): List<T> {
+ var clazz: Class<*>? = javaClass
+ val results = mutableListOf<T>()
+
+ while (clazz != Any::class.java && clazz != null) {
+ results.addAll(loadMethods(clazz).map { block(it) })
+
+ // Move to the superclass
+ clazz = clazz.superclass
+ }
+
+ return results
+ }
+
+ private fun loadClass(name: String): Class<*> = activity.classLoader.loadClass(name)
+
+ private fun loadMethod(cls: Class<*>, name: String, vararg args: Class<*>): Method =
+ cls.getDeclaredMethod(name, *args).apply { isAccessible = true }
+
+ private fun loadMethods(cls: Class<*>): List<Method> =
+ cls.declaredMethods.map { it.apply { isAccessible = true } }
+
+ private fun loadField(cls: Class<*>, name: String): Field? =
+ runCatching { cls.getDeclaredField(name).apply { isAccessible = true } }.getOrNull()
+
+ private fun loadFields(cls: Class<*>): List<Field> =
+ cls.declaredFields.map { it.apply { it.isAccessible = true } }
+}
diff --git a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt
index e5a9a65..9364f6e 100644
--- a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt
+++ b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt
@@ -18,7 +18,6 @@
import android.util.Log
import android.view.View
-import android.view.inspector.WindowInspector
import androidx.collection.LongList
import androidx.collection.LongObjectMap
import androidx.collection.MutableLongObjectMap
@@ -116,6 +115,7 @@
val viewsToSkip: LongList
)
+ private val rootsDetector = RootsDetector(environment)
private val layoutInspectorTree = LayoutInspectorTree()
private val recompositionHandler = RecompositionHandler(environment.artTooling())
private var delayParameterExtractions = false
@@ -472,7 +472,7 @@
ThreadUtils.assertOnMainThread()
val roots =
- WindowInspector.getGlobalWindowViews().asSequence().filter { root ->
+ rootsDetector.getRoots().asSequence().filter { root ->
root.visibility == View.VISIBLE &&
root.isAttachedToWindow &&
(generation > 0 || root.uniqueDrawingId == rootViewId)
@@ -516,7 +516,7 @@
/** Add a slot table to all AndroidComposeViews that doesn't already have one. */
private fun addSlotTableToComposeViews() =
ThreadUtils.runOnMainThread {
- val roots = WindowInspector.getGlobalWindowViews()
+ val roots = rootsDetector.getRoots()
val composeViews =
roots.flatMap { it.flatten() }.filter { it.isAndroidComposeView() }
@@ -524,8 +524,7 @@
val slotTablesAdded = composeViews.sumOf { it.addSlotTable() }
if (slotTablesAdded > 0) {
// The slot tables added to existing views will be empty until the
- // composables
- // are reloaded. Do that now:
+ // composables are reloaded. Do that now:
hotReload()
}
}
diff --git a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/RootsDetector.kt b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/RootsDetector.kt
new file mode 100644
index 0000000..5619d3c
--- /dev/null
+++ b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/RootsDetector.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.inspection
+
+import android.view.View
+import android.view.inspector.WindowInspector
+import androidx.compose.ui.inspection.util.ThreadUtils
+import androidx.inspection.InspectorEnvironment
+
+class RootsDetector(environment: InspectorEnvironment) {
+ private val alternateViewHelper = AlternateViewHelper(environment)
+
+ fun getRoots(): List<View> {
+ val alternateViews = alternateViewHelper.getAlternateViews()
+ return alternateViews.ifEmpty { getAndroidViews() }
+ }
+
+ private fun getAndroidViews(): List<View> {
+ ThreadUtils.assertOnMainThread()
+ val views = WindowInspector.getGlobalWindowViews()
+ return views
+ }
+}
diff --git a/compose/ui/ui-tooling-data/build.gradle b/compose/ui/ui-tooling-data/build.gradle
index f3ac046..472e819 100644
--- a/compose/ui/ui-tooling-data/build.gradle
+++ b/compose/ui/ui-tooling-data/build.gradle
@@ -28,12 +28,24 @@
plugins {
id("AndroidXPlugin")
- id("com.android.library")
id("AndroidXComposePlugin")
}
androidXMultiplatform {
- android()
+ androidLibrary {
+ namespace = "androidx.compose.ui.tooling.data"
+ withAndroidTestOnDeviceBuilder {
+ it.compilationName = "instrumentedTest"
+ it.defaultSourceSetName = "androidInstrumentedTest"
+ it.sourceSetTreeName = "test"
+ }
+ withAndroidTestOnJvmBuilder {
+ it.defaultSourceSetName = "androidUnitTest"
+ }
+
+ compileSdk = 35
+ aarMetadata.minCompileSdk = 35
+ }
jvmStubs()
defaultPlatform(PlatformIdentifier.ANDROID)
@@ -111,7 +123,3 @@
kotlinTarget = KotlinTarget.KOTLIN_1_9
}
-android {
- compileSdk 35
- namespace "androidx.compose.ui.tooling.data"
-}
diff --git a/compose/ui/ui-tooling-preview/build.gradle b/compose/ui/ui-tooling-preview/build.gradle
index 8293248..2ea5618 100644
--- a/compose/ui/ui-tooling-preview/build.gradle
+++ b/compose/ui/ui-tooling-preview/build.gradle
@@ -27,11 +27,20 @@
plugins {
id("AndroidXPlugin")
id("AndroidXComposePlugin")
- id("com.android.library")
}
androidXMultiplatform {
- android()
+ androidLibrary {
+ namespace = "androidx.compose.ui.tooling.preview"
+ withAndroidTestOnDeviceBuilder {
+ it.compilationName = "instrumentedTest"
+ it.defaultSourceSetName = "androidInstrumentedTest"
+ it.sourceSetTreeName = "test"
+ }
+ withAndroidTestOnJvmBuilder {
+ it.defaultSourceSetName = "androidUnitTest"
+ }
+ }
jvmStubs()
defaultPlatform(PlatformIdentifier.ANDROID)
@@ -93,6 +102,3 @@
metalavaK2UastEnabled = false
}
-android {
- namespace "androidx.compose.ui.tooling.preview"
-}
diff --git a/compose/ui/ui-unit/build.gradle b/compose/ui/ui-unit/build.gradle
index 9cd6331..ce761f72 100644
--- a/compose/ui/ui-unit/build.gradle
+++ b/compose/ui/ui-unit/build.gradle
@@ -28,12 +28,27 @@
plugins {
id("AndroidXPlugin")
- id("com.android.library")
id("AndroidXComposePlugin")
}
androidXMultiplatform {
- android()
+ androidLibrary {
+ namespace = "androidx.compose.ui.unit"
+ withAndroidTestOnDeviceBuilder {
+ it.compilationName = "instrumentedTest"
+ it.defaultSourceSetName = "androidInstrumentedTest"
+ it.sourceSetTreeName = "test"
+ }
+ withAndroidTestOnJvmBuilder {
+ it.defaultSourceSetName = "androidUnitTest"
+ }
+ optimization {
+ it.consumerKeepRules.publish = true
+ it.consumerKeepRules.files.add(
+ new File(project.projectDir, "proguard-rules.pro")
+ )
+ }
+ }
jvmStubs()
linuxX64Stubs()
@@ -116,9 +131,3 @@
kotlinTarget = KotlinTarget.KOTLIN_1_9
}
-android {
- namespace "androidx.compose.ui.unit"
- buildTypes.configureEach {
- consumerProguardFiles("proguard-rules.pro")
- }
-}
diff --git a/compose/ui/ui-util/build.gradle b/compose/ui/ui-util/build.gradle
index b1788829..f6ee5c2 100644
--- a/compose/ui/ui-util/build.gradle
+++ b/compose/ui/ui-util/build.gradle
@@ -27,12 +27,27 @@
plugins {
id("AndroidXPlugin")
- id("com.android.library")
id("AndroidXComposePlugin")
}
androidXMultiplatform {
- android()
+ androidLibrary {
+ namespace = "androidx.compose.ui.util"
+ withAndroidTestOnDeviceBuilder {
+ it.compilationName = "instrumentedTest"
+ it.defaultSourceSetName = "androidInstrumentedTest"
+ it.sourceSetTreeName = "test"
+ }
+ withAndroidTestOnJvmBuilder {
+ it.defaultSourceSetName = "androidUnitTest"
+ }
+ optimization {
+ it.consumerKeepRules.publish = true
+ it.consumerKeepRules.files.add(
+ new File(project.projectDir, "proguard-rules.pro")
+ )
+ }
+ }
jvmStubs()
linuxX64Stubs()
@@ -105,9 +120,3 @@
composeCompilerPluginEnabled = false
}
-android {
- namespace "androidx.compose.ui.util"
- buildTypes.configureEach {
- consumerProguardFiles("proguard-rules.pro")
- }
-}
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ComposeView.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ComposeView.android.kt
index d2ab361..0b66d5d 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ComposeView.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ComposeView.android.kt
@@ -60,6 +60,7 @@
init {
clipChildren = false
clipToPadding = false
+ importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_YES
}
/**
diff --git a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/CallSessionLegacyTest.kt b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/CallSessionLegacyTest.kt
index 45eb092..5925c55 100644
--- a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/CallSessionLegacyTest.kt
+++ b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/CallSessionLegacyTest.kt
@@ -38,6 +38,7 @@
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotNull
import org.junit.Test
import org.junit.runner.RunWith
@@ -91,6 +92,40 @@
}
}
+ /**
+ * Ensure that if the platform returns a null active bluetooth device, the jetpack layer does
+ * not crash the client application or destroy the call session
+ */
+ @SmallTest
+ @Test
+ fun testOnCallAudioStateChangedWithNullActiveDevice() {
+ setUpBackwardsCompatTest()
+ runBlocking {
+ val callSession =
+ initCallSessionLegacy(
+ coroutineContext,
+ null,
+ )
+
+ val supportedRouteMask =
+ CallAudioState.ROUTE_BLUETOOTH or
+ CallAudioState.ROUTE_WIRED_HEADSET or
+ CallAudioState.ROUTE_SPEAKER
+
+ val cas = CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH, supportedRouteMask)
+
+ callSession.onCallAudioStateChanged(cas)
+
+ val currentCallEndpoint = callSession.getCurrentCallEndpointForSession()
+ assertNotNull(currentCallEndpoint)
+ assertEquals(CallEndpointCompat.TYPE_BLUETOOTH, currentCallEndpoint!!.type)
+ assertEquals(
+ EndpointUtils.endpointTypeToString(CallEndpointCompat.TYPE_BLUETOOTH),
+ currentCallEndpoint.name
+ )
+ }
+ }
+
private fun initCallSessionLegacy(
coroutineContext: CoroutineContext,
preferredStartingEndpoint: CallEndpointCompat?,
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/internal/CallSessionLegacy.kt b/core/core-telecom/src/main/java/androidx/core/telecom/internal/CallSessionLegacy.kt
index 42a10f8..dcbc391 100644
--- a/core/core-telecom/src/main/java/androidx/core/telecom/internal/CallSessionLegacy.kt
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/internal/CallSessionLegacy.kt
@@ -83,6 +83,10 @@
}
}
+ fun getCurrentCallEndpointForSession(): CallEndpointCompat? {
+ return mCurrentCallEndpoint
+ }
+
companion object {
private const val WAIT_FOR_BT_TO_CONNECT_TIMEOUT: Long = 1000L
// TODO:: b/369153472 , remove delay and instead wait until onCallEndpointChanged
@@ -158,26 +162,34 @@
if (Build.VERSION.SDK_INT >= VERSION_CODES.P) {
Api28PlusImpl.refreshBluetoothDeviceCache(mCachedBluetoothDevices, state)
}
- setCurrentCallEndpoint(state)
- setAvailableCallEndpoints(state)
- callChannels.isMutedChannel.trySend(state.isMuted).getOrThrow()
- CoroutineScope(coroutineContext).launch {
- if (state.isMuted) {
- onStateChangedCallback.emit(CallStateEvent.GLOBAL_MUTED)
- } else {
- onStateChangedCallback.emit(CallStateEvent.GLOBAL_UNMUTE)
+ try {
+ setCurrentCallEndpoint(state)
+ setAvailableCallEndpoints(state)
+ callChannels.isMutedChannel.trySend(state.isMuted).getOrThrow()
+ CoroutineScope(coroutineContext).launch {
+ if (state.isMuted) {
+ onStateChangedCallback.emit(CallStateEvent.GLOBAL_MUTED)
+ } else {
+ onStateChangedCallback.emit(CallStateEvent.GLOBAL_UNMUTE)
+ }
}
+ // On the first call audio state change, determine if the platform started on the
+ // correct audio route. Otherwise, request an endpoint switch.
+ if (mAvailableCallEndpoints != null) {
+ switchStartingCallEndpointOnCallStart(mAvailableCallEndpoints!!)
+ }
+ // In the event the users headset disconnects, they will likely want to continue the
+ // call via the speakerphone
+ if (mCurrentCallEndpoint != null && mAvailableCallEndpoints != null) {
+ maybeSwitchToSpeakerOnHeadsetDisconnect(
+ mCurrentCallEndpoint!!,
+ mPreviousCallEndpoint,
+ mAvailableCallEndpoints!!,
+ )
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "onCallAudioStateChanged: caught=[${e.stackTraceToString()}", e)
}
- // On the first call audio state change, determine if the platform started on the correct
- // audio route. Otherwise, request an endpoint switch.
- switchStartingCallEndpointOnCallStart(mAvailableCallEndpoints!!)
- // In the event the users headset disconnects, they will likely want to continue the call
- // via the speakerphone
- maybeSwitchToSpeakerOnHeadsetDisconnect(
- mCurrentCallEndpoint!!,
- mPreviousCallEndpoint,
- mAvailableCallEndpoints!!,
- )
// clear out the last user requested CallEndpoint. It's only used to determine if the
// change in current endpoints was intentional.
if (mLastClientRequestedEndpoint?.type == mCurrentCallEndpoint?.type) {
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/internal/utils/EndpointUtils.kt b/core/core-telecom/src/main/java/androidx/core/telecom/internal/utils/EndpointUtils.kt
index 1588a2e..f868af5 100644
--- a/core/core-telecom/src/main/java/androidx/core/telecom/internal/utils/EndpointUtils.kt
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/internal/utils/EndpointUtils.kt
@@ -166,13 +166,28 @@
fun toCallEndpointCompat(state: CallAudioState, sessionId: Int): CallEndpointCompat {
val type: Int = mapRouteToType(state.route)
- return if (type == CallEndpointCompat.TYPE_BLUETOOTH && SDK_INT >= P) {
+ return if (
+ isBluetoothType(type) && buildIsAtLeastP() && hasActiveBluetoothDevice(state)
+ ) {
BluetoothApi28PlusImpl.getCallEndpointFromAudioState(state, sessionId)
} else {
CallEndpointCompat(endpointTypeToString(type), type, getUuid(sessionId, type))
}
}
+ private fun isBluetoothType(type: Int): Boolean {
+ return type == CallEndpointCompat.TYPE_BLUETOOTH
+ }
+
+ private fun buildIsAtLeastP(): Boolean {
+ return SDK_INT >= P
+ }
+
+ @RequiresApi(P)
+ private fun hasActiveBluetoothDevice(state: CallAudioState): Boolean {
+ return state.activeBluetoothDevice != null
+ }
+
fun toCallEndpointsCompat(state: CallAudioState, sessionId: Int): List<CallEndpointCompat> {
val endpoints: ArrayList<CallEndpointCompat> = ArrayList()
val bitMask = state.supportedRouteMask
@@ -278,7 +293,7 @@
fun endpointTypeToString(endpointType: Int): String {
return when (endpointType) {
CallEndpointCompat.TYPE_EARPIECE -> "EARPIECE"
- CallEndpointCompat.TYPE_BLUETOOTH -> "BLUETOOTH"
+ CallEndpointCompat.TYPE_BLUETOOTH -> BLUETOOTH_DEVICE_DEFAULT_NAME
CallEndpointCompat.TYPE_WIRED_HEADSET -> "WIRED_HEADSET"
CallEndpointCompat.TYPE_SPEAKER -> "SPEAKER"
CallEndpointCompat.TYPE_STREAMING -> "EXTERNAL"
diff --git a/datastore/datastore-preferences/build.gradle b/datastore/datastore-preferences/build.gradle
index 9fb7310..47d0108 100644
--- a/datastore/datastore-preferences/build.gradle
+++ b/datastore/datastore-preferences/build.gradle
@@ -28,12 +28,8 @@
plugins {
id("AndroidXPlugin")
- id("com.android.library")
}
-android{
- namespace "androidx.datastore.preferences"
-}
androidXMultiplatform {
jvm()
mac()
@@ -41,7 +37,17 @@
ios()
watchos()
tvos()
- android()
+ androidLibrary {
+ namespace = "androidx.datastore.preferences"
+ withAndroidTestOnDeviceBuilder {
+ it.compilationName = "instrumentedTest"
+ it.defaultSourceSetName = "androidInstrumentedTest"
+ it.sourceSetTreeName = "test"
+ }
+ withAndroidTestOnJvmBuilder {
+ it.defaultSourceSetName = "androidUnitTest"
+ }
+ }
defaultPlatform(PlatformIdentifier.ANDROID)
@@ -93,7 +99,6 @@
}
}
-
androidx {
name = "Preferences DataStore"
type = LibraryType.PUBLISHED_LIBRARY
diff --git a/datastore/datastore/build.gradle b/datastore/datastore/build.gradle
index 0933373..80d4bbd6 100644
--- a/datastore/datastore/build.gradle
+++ b/datastore/datastore/build.gradle
@@ -27,12 +27,8 @@
plugins {
id("AndroidXPlugin")
- id("com.android.library")
}
-android {
- namespace "androidx.datastore.datastore"
-}
androidXMultiplatform {
jvm()
@@ -41,7 +37,17 @@
watchos()
tvos()
linux()
- android()
+ androidLibrary {
+ namespace = "androidx.datastore.datastore"
+ withAndroidTestOnDeviceBuilder {
+ it.compilationName = "instrumentedTest"
+ it.defaultSourceSetName = "androidInstrumentedTest"
+ it.sourceSetTreeName = "test"
+ }
+ withAndroidTestOnJvmBuilder {
+ it.defaultSourceSetName = "androidUnitTest"
+ }
+ }
defaultPlatform(PlatformIdentifier.ANDROID)
diff --git a/development/project-creator/compose-template/groupId/artifactId/build.gradle b/development/project-creator/compose-template/groupId/artifactId/build.gradle
index 9cde1b1..83fd2fb 100644
--- a/development/project-creator/compose-template/groupId/artifactId/build.gradle
+++ b/development/project-creator/compose-template/groupId/artifactId/build.gradle
@@ -28,11 +28,20 @@
plugins {
id("AndroidXPlugin")
id("AndroidXComposePlugin")
- id("com.android.library")
}
androidXMultiplatform {
- android()
+ androidLibrary {
+ namespace = "<PACKAGE>"
+ withAndroidTestOnDeviceBuilder {
+ it.compilationName = "instrumentedTest"
+ it.defaultSourceSetName = "androidInstrumentedTest"
+ it.sourceSetTreeName = "test"
+ }
+ withAndroidTestOnJvmBuilder {
+ it.defaultSourceSetName = "androidUnitTest"
+ }
+ }
jvmStubs()
linuxX64Stubs()
@@ -97,9 +106,6 @@
}
}
-android {
- namespace "<PACKAGE>"
-}
androidx {
name = "<NAME>"
diff --git a/docs/api_guidelines/dependencies.md b/docs/api_guidelines/dependencies.md
index 74b88cd..c6f1576 100644
--- a/docs/api_guidelines/dependencies.md
+++ b/docs/api_guidelines/dependencies.md
@@ -236,15 +236,19 @@
}
```
-### System health {#dependencies-health}
+### Dependency considerations {#dependencies-health}
Generally, Jetpack libraries should avoid dependencies that negatively impact
developers without providing substantial benefit. Libraries should consider the
-system health implications of their dependencies, including:
+implications of their dependencies, including:
- Large dependencies where only a small portion is needed (e.g. APK bloat)
- Dependencies that slow down build times through annotation processing or
compiler overhead
+- Dependencies which do not maintain binary compatibility and conflict with
+ semantic versioning guarantees
+- Dependencies that are intended for server environments and don't interact
+ well with the Android build toolchain (e.g. R8) or runtime (e.g. ART)
#### Kotlin {#dependencies-kotlin}
@@ -281,6 +285,12 @@
}
```
+#### GSON {#dependencies-gson}
+
+GSON relies heavily on reflection and interacts poorly with app optimization
+tools like R8. Instead, consider using `org.json` which is included in the
+Android platform SDK.
+
#### Guava {#dependencies-guava}
The full Guava library is very large and should only be used in cases where
diff --git a/graphics/graphics-shapes/build.gradle b/graphics/graphics-shapes/build.gradle
index 7212f00..000cc74 100644
--- a/graphics/graphics-shapes/build.gradle
+++ b/graphics/graphics-shapes/build.gradle
@@ -26,12 +26,20 @@
plugins {
id("AndroidXPlugin")
- id("com.android.library")
}
-
androidXMultiplatform {
- android()
+ androidLibrary {
+ namespace = "androidx.graphics.shapes"
+ withAndroidTestOnDeviceBuilder {
+ it.compilationName = "instrumentedTest"
+ it.defaultSourceSetName = "androidInstrumentedTest"
+ it.sourceSetTreeName = "test"
+ }
+ withAndroidTestOnJvmBuilder {
+ it.defaultSourceSetName = "androidUnitTest"
+ }
+ }
desktop()
linux()
ios()
@@ -99,9 +107,6 @@
}
}
-android {
- namespace "androidx.graphics.shapes"
-}
androidx {
name = "Graphics Shapes"
diff --git a/libraryversions.toml b/libraryversions.toml
index 225bd0f..5bb21b8 100644
--- a/libraryversions.toml
+++ b/libraryversions.toml
@@ -21,8 +21,8 @@
CAR_APP = "1.7.0-beta02"
COLLECTION = "1.5.0-alpha05"
COMPOSE = "1.8.0-alpha05"
-COMPOSE_MATERIAL3 = "1.4.0-alpha02"
-COMPOSE_MATERIAL3_ADAPTIVE = "1.1.0-alpha05"
+COMPOSE_MATERIAL3 = "1.4.0-alpha03"
+COMPOSE_MATERIAL3_ADAPTIVE = "1.1.0-alpha06"
COMPOSE_MATERIAL3_COMMON = "1.0.0-alpha01"
COMPOSE_RUNTIME = "1.8.0-alpha05"
CONSTRAINTLAYOUT = "2.2.0-beta01"
diff --git a/lifecycle/lifecycle-runtime-compose/build.gradle b/lifecycle/lifecycle-runtime-compose/build.gradle
index a802e2f..7a93d89 100644
--- a/lifecycle/lifecycle-runtime-compose/build.gradle
+++ b/lifecycle/lifecycle-runtime-compose/build.gradle
@@ -27,12 +27,31 @@
plugins {
id("AndroidXPlugin")
- id("com.android.library")
id("AndroidXComposePlugin")
}
androidXMultiplatform {
- android()
+ androidLibrary {
+ namespace = "androidx.lifecycle.runtime.compose"
+ withAndroidTestOnDeviceBuilder {
+ it.compilationName = "instrumentedTest"
+ it.defaultSourceSetName = "androidInstrumentedTest"
+ it.sourceSetTreeName = "test"
+ }
+ withAndroidTestOnJvmBuilder {
+ it.defaultSourceSetName = "androidUnitTest"
+ }
+
+ compileSdk = 35
+ aarMetadata.minCompileSdk = 35
+
+ optimization {
+ it.consumerKeepRules.publish = true
+ it.consumerKeepRules.files.add(
+ new File(project.projectDir, "proguard-rules.pro")
+ )
+ }
+ }
jvmStubs()
linuxX64Stubs()
@@ -94,12 +113,3 @@
samples(project(":lifecycle:lifecycle-runtime-compose:lifecycle-runtime-compose-samples"))
}
-android {
- compileSdk 35
-
- buildTypes.configureEach {
- consumerProguardFiles "proguard-rules.pro"
- }
-
- namespace "androidx.lifecycle.runtime.compose"
-}
diff --git a/lifecycle/lifecycle-runtime-ktx/build.gradle b/lifecycle/lifecycle-runtime-ktx/build.gradle
index e660299..049067d 100644
--- a/lifecycle/lifecycle-runtime-ktx/build.gradle
+++ b/lifecycle/lifecycle-runtime-ktx/build.gradle
@@ -27,11 +27,20 @@
plugins {
id("AndroidXPlugin")
- id("com.android.library")
}
androidXMultiplatform {
- android()
+ androidLibrary {
+ namespace = "androidx.lifecycle.ktx"
+ withAndroidTestOnDeviceBuilder {
+ it.compilationName = "instrumentedTest"
+ it.defaultSourceSetName = "androidInstrumentedTest"
+ it.sourceSetTreeName = "test"
+ }
+ withAndroidTestOnJvmBuilder {
+ it.defaultSourceSetName = "androidUnitTest"
+ }
+ }
defaultPlatform(PlatformIdentifier.ANDROID)
@@ -60,6 +69,3 @@
description = "Kotlin extensions for 'lifecycle' artifact"
}
-android {
- namespace "androidx.lifecycle.ktx"
-}
diff --git a/lifecycle/lifecycle-runtime-testing/build.gradle b/lifecycle/lifecycle-runtime-testing/build.gradle
index 430b902..89a61a8 100644
--- a/lifecycle/lifecycle-runtime-testing/build.gradle
+++ b/lifecycle/lifecycle-runtime-testing/build.gradle
@@ -27,11 +27,20 @@
plugins {
id("AndroidXPlugin")
- id("com.android.library")
}
androidXMultiplatform {
- android()
+ androidLibrary {
+ namespace = "androidx.lifecycle.testing"
+ withAndroidTestOnDeviceBuilder {
+ it.compilationName = "instrumentedTest"
+ it.defaultSourceSetName = "androidInstrumentedTest"
+ it.sourceSetTreeName = "test"
+ }
+ withAndroidTestOnJvmBuilder {
+ it.defaultSourceSetName = "androidUnitTest"
+ }
+ }
desktop()
mac()
linux()
@@ -73,13 +82,10 @@
androidx {
name = "Lifecycle Runtime Testing"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_TEST_LIBRARY
inceptionYear = "2019"
description = "Testing utilities for 'lifecycle' artifact"
legacyDisableKotlinStrictApiMode = true
metalavaK2UastEnabled = false
}
-android {
- namespace "androidx.lifecycle.testing"
-}
diff --git a/lifecycle/lifecycle-viewmodel-testing/build.gradle b/lifecycle/lifecycle-viewmodel-testing/build.gradle
index bd1b11a..919a73e 100644
--- a/lifecycle/lifecycle-viewmodel-testing/build.gradle
+++ b/lifecycle/lifecycle-viewmodel-testing/build.gradle
@@ -29,11 +29,20 @@
plugins {
id("AndroidXPlugin")
- id("com.android.library")
}
androidXMultiplatform {
- android()
+ androidLibrary {
+ namespace = "androidx.lifecycle.viewmodel.testing"
+ withAndroidTestOnDeviceBuilder {
+ it.compilationName = "instrumentedTest"
+ it.defaultSourceSetName = "androidInstrumentedTest"
+ it.sourceSetTreeName = "test"
+ }
+ withAndroidTestOnJvmBuilder {
+ it.defaultSourceSetName = "androidUnitTest"
+ }
+ }
desktop()
mac()
linux()
@@ -108,6 +117,3 @@
metalavaK2UastEnabled = false
}
-android {
- namespace "androidx.lifecycle.viewmodel.testing"
-}
diff --git a/navigation/navigation-runtime/api/current.txt b/navigation/navigation-runtime/api/current.txt
index b801964..a4cc6458 100644
--- a/navigation/navigation-runtime/api/current.txt
+++ b/navigation/navigation-runtime/api/current.txt
@@ -113,6 +113,7 @@
method public androidx.lifecycle.ViewModelStoreOwner getViewModelStoreOwner(@IdRes int navGraphId);
method public final kotlinx.coroutines.flow.StateFlow<java.util.List<androidx.navigation.NavBackStackEntry>> getVisibleEntries();
method @MainThread public boolean handleDeepLink(android.content.Intent? intent);
+ method @MainThread public final boolean handleDeepLink(androidx.navigation.NavDeepLinkRequest request);
method @MainThread public void navigate(android.net.Uri deepLink);
method @MainThread public void navigate(android.net.Uri deepLink, androidx.navigation.NavOptions? navOptions);
method @MainThread public void navigate(android.net.Uri deepLink, androidx.navigation.NavOptions? navOptions, androidx.navigation.Navigator.Extras? navigatorExtras);
diff --git a/navigation/navigation-runtime/api/restricted_current.txt b/navigation/navigation-runtime/api/restricted_current.txt
index b801964..a4cc6458 100644
--- a/navigation/navigation-runtime/api/restricted_current.txt
+++ b/navigation/navigation-runtime/api/restricted_current.txt
@@ -113,6 +113,7 @@
method public androidx.lifecycle.ViewModelStoreOwner getViewModelStoreOwner(@IdRes int navGraphId);
method public final kotlinx.coroutines.flow.StateFlow<java.util.List<androidx.navigation.NavBackStackEntry>> getVisibleEntries();
method @MainThread public boolean handleDeepLink(android.content.Intent? intent);
+ method @MainThread public final boolean handleDeepLink(androidx.navigation.NavDeepLinkRequest request);
method @MainThread public void navigate(android.net.Uri deepLink);
method @MainThread public void navigate(android.net.Uri deepLink, androidx.navigation.NavOptions? navOptions);
method @MainThread public void navigate(android.net.Uri deepLink, androidx.navigation.NavOptions? navOptions, androidx.navigation.Navigator.Extras? navigatorExtras);
diff --git a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt
index 1fbe388d..95a9f20 100644
--- a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt
+++ b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt
@@ -3331,6 +3331,66 @@
@UiThreadTest
@Test
+ fun testHandleDeepLinkAsUri() {
+ val navController = createNavController()
+ val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+ navController.setGraph(R.navigation.nav_simple)
+
+ val deepLink = Uri.parse("test-app://test/333")
+ val request = NavDeepLinkRequest.Builder.fromUri(deepLink).build()
+ assertWithMessage("NavController should handle deep links to its own graph")
+ .that(navController.handleDeepLink(request))
+ .isTrue()
+ // Verify that we navigated down to the deep link
+ assertThat(navigator.backStack.map { it.destination.id })
+ .containsExactly(R.id.start_test, R.id.nonNullableArg_test)
+ .inOrder()
+
+ val destination = navController.currentDestination
+ assertThat(destination?.id ?: 0).isEqualTo(R.id.nonNullableArg_test)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testHandleDeepLink_InvalidUri() {
+ val navController = createNavController()
+ navController.setGraph(R.navigation.nav_simple)
+
+ val deepLink = Uri.parse("test-app://invalid/uri")
+ val request = NavDeepLinkRequest.Builder.fromUri(deepLink).build()
+ assertWithMessage("NavController should not match with any deeplink due to invalid uri")
+ .that(navController.handleDeepLink(request))
+ .isFalse()
+ }
+
+ @UiThreadTest
+ @Test
+ fun testHandleDeepLink_MimeType() {
+ val navController = createNavController()
+ navController.setGraph(R.navigation.nav_simple)
+ val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+ val deepLink = NavDeepLinkRequest(Uri.parse("invalidDeepLink.com"), null, "type/test")
+
+ navController.handleDeepLink(deepLink)
+ assertThat(navController.currentDestination?.id ?: 0).isEqualTo(R.id.second_test)
+ assertThat(navigator.backStack.size).isEqualTo(2)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testHandleDeepLink_Action() {
+ val navController = createNavController()
+ navController.setGraph(R.navigation.nav_simple)
+ val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+ val deepLink = NavDeepLinkRequest(null, "test.action", null)
+
+ navController.handleDeepLink(deepLink)
+ assertThat(navController.currentDestination?.id ?: 0).isEqualTo(R.id.second_test)
+ assertThat(navigator.backStack.size).isEqualTo(2)
+ }
+
+ @UiThreadTest
+ @Test
fun testHandleDeepLinkActionMissingURI_nonNullableArg() {
val navController = createNavController()
navController.setGraph(R.navigation.nav_simple)
diff --git a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt
index ab95516..8efc3e2 100644
--- a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt
+++ b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt
@@ -1539,7 +1539,55 @@
}
return true
}
- if (flags and Intent.FLAG_ACTIVITY_NEW_TASK != 0) {
+ return handleDeepLink(deepLink, args, flags and Intent.FLAG_ACTIVITY_NEW_TASK != 0)
+ }
+
+ /**
+ * Checks the given NavDeepLinkRequest for a Navigation deep link and navigates to the
+ * destination if present.
+ *
+ * The [navigation graph][graph] should be set before calling this method.
+ *
+ * @param request The request that contains a valid deep link, an action or a mimeType.
+ * @return True if the navigation controller found a valid deep link and navigated to it.
+ * @throws IllegalStateException if deep link cannot be accessed from the current destination
+ * @see NavDestination.addDeepLink
+ */
+ @MainThread
+ public fun handleDeepLink(request: NavDeepLinkRequest): Boolean {
+ val currGraph = backQueue.getTopGraph()
+ val matchingDeepLink =
+ currGraph.matchDeepLinkComprehensive(
+ navDeepLinkRequest = request,
+ searchChildren = true,
+ searchParent = true,
+ lastVisited = currGraph
+ )
+ if (matchingDeepLink != null) {
+ val destination = matchingDeepLink.destination
+ val deepLink = destination.buildDeepLinkIds()
+ val globalArgs = Bundle()
+ val destinationArgs = destination.addInDefaultArgs(matchingDeepLink.matchingArgs)
+ if (destinationArgs != null) {
+ globalArgs.putAll(destinationArgs)
+ }
+ val args = arrayOfNulls<Bundle>(deepLink.size)
+ for (index in args.indices) {
+ val arguments = Bundle()
+ arguments.putAll(globalArgs)
+ args[index] = arguments
+ }
+ return handleDeepLink(deepLink, args, true)
+ }
+ return false
+ }
+
+ private fun handleDeepLink(
+ deepLink: IntArray,
+ args: Array<Bundle?>,
+ newTask: Boolean
+ ): Boolean {
+ if (newTask) {
// Start with a cleared task starting at our root when we're on our own task
if (!backQueue.isEmpty()) {
popBackStackInternal(_graph!!.id, true)
diff --git a/pdf/pdf-viewer/src/main/res/values-af/strings.xml b/pdf/pdf-viewer/src/main/res/values-af/strings.xml
index f62de4d..344991a 100644
--- a/pdf/pdf-viewer/src/main/res/values-af/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-af/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"Volgende"</string>
<string name="close_button_description" msgid="7379823906921067675">"Maak toe"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"Geen passende resultate nie"</string>
<string name="action_edit" msgid="5882082700509010966">"Wysig lêer"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Voer wagwoord in om te ontsluit"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-am/strings.xml b/pdf/pdf-viewer/src/main/res/values-am/strings.xml
index 9251d8b..7db9b5a 100644
--- a/pdf/pdf-viewer/src/main/res/values-am/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-am/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"ቀጣይ"</string>
<string name="close_button_description" msgid="7379823906921067675">"ዝጋ"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"ምንም የሚመሳሰሉ ውጤቶች የሉም"</string>
<string name="action_edit" msgid="5882082700509010966">"ፋይል አርትዕ"</string>
<string name="password_not_entered" msgid="8875370870743585303">"ለመክፈት የይለፍ ቃል ያስገቡ"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-ar/strings.xml b/pdf/pdf-viewer/src/main/res/values-ar/strings.xml
index 7477408..f88ddb3 100644
--- a/pdf/pdf-viewer/src/main/res/values-ar/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ar/strings.xml
@@ -47,6 +47,7 @@
<string name="next_button_description" msgid="4702699322249103693">"التالي"</string>
<string name="close_button_description" msgid="7379823906921067675">"إغلاق"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> من أصل <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="match_status_description" msgid="4996847358326345288">"<xliff:g id="POSITION">%1$d</xliff:g> من إجمالي <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="message_no_match_status" msgid="5929387004361286433">"ما مِن نتائج مطابقة"</string>
<string name="action_edit" msgid="5882082700509010966">"تعديل الملف"</string>
<string name="password_not_entered" msgid="8875370870743585303">"يجب إدخال كلمة المرور لفتح القفل"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-as/strings.xml b/pdf/pdf-viewer/src/main/res/values-as/strings.xml
index 575762e..c5fbb0d 100644
--- a/pdf/pdf-viewer/src/main/res/values-as/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-as/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"পৰৱৰ্তী"</string>
<string name="close_button_description" msgid="7379823906921067675">"বন্ধ কৰক"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"কোনো মিল থকা ফলাফল নাই"</string>
<string name="action_edit" msgid="5882082700509010966">"ফাইল সম্পাদনা কৰক"</string>
<string name="password_not_entered" msgid="8875370870743585303">"আনলক কৰিবলৈ পাছৱৰ্ড দিয়ক"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-az/strings.xml b/pdf/pdf-viewer/src/main/res/values-az/strings.xml
index 56f789c3c..f058531 100644
--- a/pdf/pdf-viewer/src/main/res/values-az/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-az/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"Növbəti"</string>
<string name="close_button_description" msgid="7379823906921067675">"Bağlayın"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"Uyğun gələn nəticə yoxdur"</string>
<string name="action_edit" msgid="5882082700509010966">"Faylı redaktə edin"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Kiliddən çıxarmaq üçün parol daxil edin"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-b+sr+Latn/strings.xml b/pdf/pdf-viewer/src/main/res/values-b+sr+Latn/strings.xml
index 98c7f5b..e43a0da 100644
--- a/pdf/pdf-viewer/src/main/res/values-b+sr+Latn/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-b+sr+Latn/strings.xml
@@ -47,6 +47,7 @@
<string name="next_button_description" msgid="4702699322249103693">"Dalje"</string>
<string name="close_button_description" msgid="7379823906921067675">"Zatvori"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="match_status_description" msgid="4996847358326345288">"<xliff:g id="POSITION">%1$d</xliff:g> od <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="message_no_match_status" msgid="5929387004361286433">"Nema podudarnih rezultata"</string>
<string name="action_edit" msgid="5882082700509010966">"Izmeni fajl"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Unesite lozinku za otključavanje"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-be/strings.xml b/pdf/pdf-viewer/src/main/res/values-be/strings.xml
index e5246b7..0d565d7 100644
--- a/pdf/pdf-viewer/src/main/res/values-be/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-be/strings.xml
@@ -47,6 +47,7 @@
<string name="next_button_description" msgid="4702699322249103693">"Далей"</string>
<string name="close_button_description" msgid="7379823906921067675">"Закрыць"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> з <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="match_status_description" msgid="4996847358326345288">"<xliff:g id="POSITION">%1$d</xliff:g> з <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="message_no_match_status" msgid="5929387004361286433">"Супадзенняў няма"</string>
<string name="action_edit" msgid="5882082700509010966">"Рэдагаваць файл"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Увядзіце пароль для разблакіроўкі"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-bg/strings.xml b/pdf/pdf-viewer/src/main/res/values-bg/strings.xml
index 1963288..3af4c32 100644
--- a/pdf/pdf-viewer/src/main/res/values-bg/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-bg/strings.xml
@@ -47,6 +47,7 @@
<string name="next_button_description" msgid="4702699322249103693">"Напред"</string>
<string name="close_button_description" msgid="7379823906921067675">"Затваряне"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="match_status_description" msgid="4996847358326345288">"<xliff:g id="POSITION">%1$d</xliff:g> от <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="message_no_match_status" msgid="5929387004361286433">"Няма съответстващи резултати"</string>
<string name="action_edit" msgid="5882082700509010966">"Редактиране на файла"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Въведете паролата, за да отключите"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-bn/strings.xml b/pdf/pdf-viewer/src/main/res/values-bn/strings.xml
index 7e9b6a8..523615d 100644
--- a/pdf/pdf-viewer/src/main/res/values-bn/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-bn/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"পরবর্তী"</string>
<string name="close_button_description" msgid="7379823906921067675">"বন্ধ করুন"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"কোনও ফলাফল মিলছে না"</string>
<string name="action_edit" msgid="5882082700509010966">"ফাইল এডিট করুন"</string>
<string name="password_not_entered" msgid="8875370870743585303">"আনলক করতে পাসওয়ার্ড লিখুন"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-bs/strings.xml b/pdf/pdf-viewer/src/main/res/values-bs/strings.xml
index 6e2be84..58cb55b 100644
--- a/pdf/pdf-viewer/src/main/res/values-bs/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-bs/strings.xml
@@ -47,6 +47,7 @@
<string name="next_button_description" msgid="4702699322249103693">"Naprijed"</string>
<string name="close_button_description" msgid="7379823906921067675">"Zatvaranje"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="match_status_description" msgid="4996847358326345288">"<xliff:g id="POSITION">%1$d</xliff:g> od <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="message_no_match_status" msgid="5929387004361286433">"Nema podudarnih rezultata"</string>
<string name="action_edit" msgid="5882082700509010966">"Uredite fajl"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Unesite lozinku da otključate fajl"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-ca/strings.xml b/pdf/pdf-viewer/src/main/res/values-ca/strings.xml
index e5237f2..e006cd2 100644
--- a/pdf/pdf-viewer/src/main/res/values-ca/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ca/strings.xml
@@ -47,6 +47,7 @@
<string name="next_button_description" msgid="4702699322249103693">"Següent"</string>
<string name="close_button_description" msgid="7379823906921067675">"Tanca"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="match_status_description" msgid="4996847358326345288">"<xliff:g id="POSITION">%1$d</xliff:g> de <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="message_no_match_status" msgid="5929387004361286433">"No hi ha cap resultat coincident"</string>
<string name="action_edit" msgid="5882082700509010966">"Edita el fitxer"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Introdueix la contrasenya per desbloquejar-lo"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-cs/strings.xml b/pdf/pdf-viewer/src/main/res/values-cs/strings.xml
index bb042a7..93a108c 100644
--- a/pdf/pdf-viewer/src/main/res/values-cs/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-cs/strings.xml
@@ -47,6 +47,7 @@
<string name="next_button_description" msgid="4702699322249103693">"Další"</string>
<string name="close_button_description" msgid="7379823906921067675">"Zavřít"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="match_status_description" msgid="4996847358326345288">"<xliff:g id="POSITION">%1$d</xliff:g> z <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="message_no_match_status" msgid="5929387004361286433">"Žádné výsledky neodpovídají"</string>
<string name="action_edit" msgid="5882082700509010966">"Upravit soubor"</string>
<string name="password_not_entered" msgid="8875370870743585303">"K odemknutí zadejte heslo"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-da/strings.xml b/pdf/pdf-viewer/src/main/res/values-da/strings.xml
index 120231b..5a5ac17 100644
--- a/pdf/pdf-viewer/src/main/res/values-da/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-da/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"Næste"</string>
<string name="close_button_description" msgid="7379823906921067675">"Luk"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"Ingen matchende resultater"</string>
<string name="action_edit" msgid="5882082700509010966">"Rediger fil"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Angiv adgangskode for at låse op"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-de/strings.xml b/pdf/pdf-viewer/src/main/res/values-de/strings.xml
index 8af7f80..15c0a75 100644
--- a/pdf/pdf-viewer/src/main/res/values-de/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-de/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"Weiter"</string>
<string name="close_button_description" msgid="7379823906921067675">"Schließen"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"Keine passenden Ergebnisse"</string>
<string name="action_edit" msgid="5882082700509010966">"Datei bearbeiten"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Gib zum Entsperren ein Passwort ein"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-el/strings.xml b/pdf/pdf-viewer/src/main/res/values-el/strings.xml
index 7f833eb..83efcf5 100644
--- a/pdf/pdf-viewer/src/main/res/values-el/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-el/strings.xml
@@ -47,6 +47,7 @@
<string name="next_button_description" msgid="4702699322249103693">"Επόμενο"</string>
<string name="close_button_description" msgid="7379823906921067675">"Κλείσιμο"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="match_status_description" msgid="4996847358326345288">"<xliff:g id="POSITION">%1$d</xliff:g> από <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="message_no_match_status" msgid="5929387004361286433">"Δεν υπάρχουν αντίστοιχα αποτελέσματα"</string>
<string name="action_edit" msgid="5882082700509010966">"Επεξεργασία αρχείου"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Εισαγάγετε τον κωδικό πρόσβασης για ξεκλείδωμα"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-en-rAU/strings.xml b/pdf/pdf-viewer/src/main/res/values-en-rAU/strings.xml
index b4f73b8..e3344f7 100644
--- a/pdf/pdf-viewer/src/main/res/values-en-rAU/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-en-rAU/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"Next"</string>
<string name="close_button_description" msgid="7379823906921067675">"Close"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"No matching results"</string>
<string name="action_edit" msgid="5882082700509010966">"Edit file"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Enter password to unlock"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-en-rCA/strings.xml b/pdf/pdf-viewer/src/main/res/values-en-rCA/strings.xml
index 74126a3..f9bb660 100644
--- a/pdf/pdf-viewer/src/main/res/values-en-rCA/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-en-rCA/strings.xml
@@ -47,6 +47,7 @@
<string name="next_button_description" msgid="4702699322249103693">"Next"</string>
<string name="close_button_description" msgid="7379823906921067675">"Close"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="match_status_description" msgid="4996847358326345288">"<xliff:g id="POSITION">%1$d</xliff:g> of <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="message_no_match_status" msgid="5929387004361286433">"No matching results"</string>
<string name="action_edit" msgid="5882082700509010966">"Edit file"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Enter password to unlock"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-en-rGB/strings.xml b/pdf/pdf-viewer/src/main/res/values-en-rGB/strings.xml
index b4f73b8..e3344f7 100644
--- a/pdf/pdf-viewer/src/main/res/values-en-rGB/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-en-rGB/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"Next"</string>
<string name="close_button_description" msgid="7379823906921067675">"Close"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"No matching results"</string>
<string name="action_edit" msgid="5882082700509010966">"Edit file"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Enter password to unlock"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-en-rIN/strings.xml b/pdf/pdf-viewer/src/main/res/values-en-rIN/strings.xml
index b4f73b8..e3344f7 100644
--- a/pdf/pdf-viewer/src/main/res/values-en-rIN/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-en-rIN/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"Next"</string>
<string name="close_button_description" msgid="7379823906921067675">"Close"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"No matching results"</string>
<string name="action_edit" msgid="5882082700509010966">"Edit file"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Enter password to unlock"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-es-rUS/strings.xml b/pdf/pdf-viewer/src/main/res/values-es-rUS/strings.xml
index ecf2f42..ac55757a 100644
--- a/pdf/pdf-viewer/src/main/res/values-es-rUS/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-es-rUS/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"Siguiente"</string>
<string name="close_button_description" msgid="7379823906921067675">"Cerrar"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"No hay resultados que coincidan"</string>
<string name="action_edit" msgid="5882082700509010966">"Editar el archivo"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Ingresa la contraseña para desbloquear"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-es/strings.xml b/pdf/pdf-viewer/src/main/res/values-es/strings.xml
index 48a146f..a8395fb 100644
--- a/pdf/pdf-viewer/src/main/res/values-es/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-es/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"Siguiente"</string>
<string name="close_button_description" msgid="7379823906921067675">"Cerrar"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"No hay coincidencias"</string>
<string name="action_edit" msgid="5882082700509010966">"Editar archivo"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Introduce la contraseña para desbloquear"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-et/strings.xml b/pdf/pdf-viewer/src/main/res/values-et/strings.xml
index e23221de..a57f96c 100644
--- a/pdf/pdf-viewer/src/main/res/values-et/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-et/strings.xml
@@ -47,6 +47,7 @@
<string name="next_button_description" msgid="4702699322249103693">"Järgmine"</string>
<string name="close_button_description" msgid="7379823906921067675">"Sule"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="match_status_description" msgid="4996847358326345288">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="message_no_match_status" msgid="5929387004361286433">"Päringule vastavaid tulemusi pole"</string>
<string name="action_edit" msgid="5882082700509010966">"Faili muutmine"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Avamiseks sisestage parool"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-eu/strings.xml b/pdf/pdf-viewer/src/main/res/values-eu/strings.xml
index c408c2b..e46c3a2 100644
--- a/pdf/pdf-viewer/src/main/res/values-eu/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-eu/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"Hurrengoa"</string>
<string name="close_button_description" msgid="7379823906921067675">"Itxi"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"Ez dago bat datorren emaitzarik"</string>
<string name="action_edit" msgid="5882082700509010966">"Editatu fitxategia"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Idatzi pasahitza desblokeatzeko"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-fa/strings.xml b/pdf/pdf-viewer/src/main/res/values-fa/strings.xml
index e017f32..9fe5557 100644
--- a/pdf/pdf-viewer/src/main/res/values-fa/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-fa/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"بعدی"</string>
<string name="close_button_description" msgid="7379823906921067675">"بستن"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"نتیجه منطبقی پیدا نشد"</string>
<string name="action_edit" msgid="5882082700509010966">"ویرایش فایل"</string>
<string name="password_not_entered" msgid="8875370870743585303">"گذرواژه را برای بازگشایی قفل وارد کنید"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-fi/strings.xml b/pdf/pdf-viewer/src/main/res/values-fi/strings.xml
index 9b9eb0a..e9e243ac 100644
--- a/pdf/pdf-viewer/src/main/res/values-fi/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-fi/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"Seuraava"</string>
<string name="close_button_description" msgid="7379823906921067675">"Sulje"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"Ei tuloksia"</string>
<string name="action_edit" msgid="5882082700509010966">"Muokkaa tiedostoa"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Poista lukitus lisäämällä salasana"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-fr-rCA/strings.xml b/pdf/pdf-viewer/src/main/res/values-fr-rCA/strings.xml
index 564ac9f..462595c 100644
--- a/pdf/pdf-viewer/src/main/res/values-fr-rCA/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-fr-rCA/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"Suivant"</string>
<string name="close_button_description" msgid="7379823906921067675">"Fermer"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"Aucun résultat correspondant"</string>
<string name="action_edit" msgid="5882082700509010966">"Modifier le fichier"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Entrez le mot de passe pour déverrouiller le fichier"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-fr/strings.xml b/pdf/pdf-viewer/src/main/res/values-fr/strings.xml
index f8067f1..7b5333c 100644
--- a/pdf/pdf-viewer/src/main/res/values-fr/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-fr/strings.xml
@@ -47,6 +47,7 @@
<string name="next_button_description" msgid="4702699322249103693">"Suivant"</string>
<string name="close_button_description" msgid="7379823906921067675">"Fermer"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="match_status_description" msgid="4996847358326345288">"<xliff:g id="POSITION">%1$d</xliff:g> sur <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="message_no_match_status" msgid="5929387004361286433">"Aucun résultat"</string>
<string name="action_edit" msgid="5882082700509010966">"Modifier le fichier"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Saisissez le mot de passe pour procéder au déverrouillage"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-gl/strings.xml b/pdf/pdf-viewer/src/main/res/values-gl/strings.xml
index e9996c3..f44eedd 100644
--- a/pdf/pdf-viewer/src/main/res/values-gl/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-gl/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"Seguinte"</string>
<string name="close_button_description" msgid="7379823906921067675">"Pechar"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"Non hai ningún resultado que coincida"</string>
<string name="action_edit" msgid="5882082700509010966">"Editar o ficheiro"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Introduce o contrasinal para desbloquear o ficheiro"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-gu/strings.xml b/pdf/pdf-viewer/src/main/res/values-gu/strings.xml
index a976158..3a6a1fe 100644
--- a/pdf/pdf-viewer/src/main/res/values-gu/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-gu/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"આગળ"</string>
<string name="close_button_description" msgid="7379823906921067675">"બંધ કરો"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"કોઈ મેળ ખાતું પરિણામ નથી"</string>
<string name="action_edit" msgid="5882082700509010966">"ફાઇલમાં ફેરફાર કરો"</string>
<string name="password_not_entered" msgid="8875370870743585303">"અનલૉક કરવા માટે પાસવર્ડ દાખલ કરો"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-hi/strings.xml b/pdf/pdf-viewer/src/main/res/values-hi/strings.xml
index 1904222..d7febf7 100644
--- a/pdf/pdf-viewer/src/main/res/values-hi/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-hi/strings.xml
@@ -47,6 +47,7 @@
<string name="next_button_description" msgid="4702699322249103693">"आगे बढ़ें"</string>
<string name="close_button_description" msgid="7379823906921067675">"बंद करें"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="match_status_description" msgid="4996847358326345288">"<xliff:g id="TOTAL">%2$d</xliff:g> में से <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="message_no_match_status" msgid="5929387004361286433">"कोई मिलता-जुलता नतीजा नहीं मिला"</string>
<string name="action_edit" msgid="5882082700509010966">"फ़ाइल में बदलाव करें"</string>
<string name="password_not_entered" msgid="8875370870743585303">"अनलॉक करने के लिए पासवर्ड डालें"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-hr/strings.xml b/pdf/pdf-viewer/src/main/res/values-hr/strings.xml
index d2be613..69663f2 100644
--- a/pdf/pdf-viewer/src/main/res/values-hr/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-hr/strings.xml
@@ -47,6 +47,7 @@
<string name="next_button_description" msgid="4702699322249103693">"Sljedeće"</string>
<string name="close_button_description" msgid="7379823906921067675">"Zatvori"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="match_status_description" msgid="4996847358326345288">"<xliff:g id="POSITION">%1$d</xliff:g> od <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="message_no_match_status" msgid="5929387004361286433">"Nema podudarnih rezultata"</string>
<string name="action_edit" msgid="5882082700509010966">"Uređivanje datoteke"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Unesite zaporku za otključavanje"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-hu/strings.xml b/pdf/pdf-viewer/src/main/res/values-hu/strings.xml
index 13cefe9..746c393 100644
--- a/pdf/pdf-viewer/src/main/res/values-hu/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-hu/strings.xml
@@ -47,6 +47,7 @@
<string name="next_button_description" msgid="4702699322249103693">"Következő"</string>
<string name="close_button_description" msgid="7379823906921067675">"Bezárás"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="TOTAL">%2$d</xliff:g>/<xliff:g id="POSITION">%1$d</xliff:g>."</string>
+ <string name="match_status_description" msgid="4996847358326345288">"<xliff:g id="TOTAL">%2$d</xliff:g>/<xliff:g id="POSITION">%1$d</xliff:g>."</string>
<string name="message_no_match_status" msgid="5929387004361286433">"Nincs találat"</string>
<string name="action_edit" msgid="5882082700509010966">"Fájl szerkesztése"</string>
<string name="password_not_entered" msgid="8875370870743585303">"A feloldáshoz írja be a jelszót"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-hy/strings.xml b/pdf/pdf-viewer/src/main/res/values-hy/strings.xml
index 53b5c63..0930a77 100644
--- a/pdf/pdf-viewer/src/main/res/values-hy/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-hy/strings.xml
@@ -47,6 +47,7 @@
<string name="next_button_description" msgid="4702699322249103693">"Հաջորդը"</string>
<string name="close_button_description" msgid="7379823906921067675">"Փակել"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="match_status_description" msgid="4996847358326345288">"<xliff:g id="POSITION">%1$d</xliff:g>՝ <xliff:g id="TOTAL">%2$d</xliff:g>-ից"</string>
<string name="message_no_match_status" msgid="5929387004361286433">"Համապատասխանող արդյունքներ չկան"</string>
<string name="action_edit" msgid="5882082700509010966">"Փոփոխել ֆայլը"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Մուտքագրեք գաղտնաբառը՝ ապակողպելու համար"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-in/strings.xml b/pdf/pdf-viewer/src/main/res/values-in/strings.xml
index d1c6170b..3b84829 100644
--- a/pdf/pdf-viewer/src/main/res/values-in/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-in/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"Berikutnya"</string>
<string name="close_button_description" msgid="7379823906921067675">"Tutup"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"Tidak ada hasil yang cocok"</string>
<string name="action_edit" msgid="5882082700509010966">"Edit file"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Masukkan sandi untuk membuka kunci"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-is/strings.xml b/pdf/pdf-viewer/src/main/res/values-is/strings.xml
index 845c448..84ae8bb 100644
--- a/pdf/pdf-viewer/src/main/res/values-is/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-is/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"Næsta"</string>
<string name="close_button_description" msgid="7379823906921067675">"Loka"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"Engar samsvarandi niðurstöður fundust"</string>
<string name="action_edit" msgid="5882082700509010966">"Breyta skrá"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Sláðu inn aðgangsorð til að opna"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-it/strings.xml b/pdf/pdf-viewer/src/main/res/values-it/strings.xml
index bea8957..b566885 100644
--- a/pdf/pdf-viewer/src/main/res/values-it/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-it/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"Avanti"</string>
<string name="close_button_description" msgid="7379823906921067675">"Chiudi"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"Nessun risultato corrispondente"</string>
<string name="action_edit" msgid="5882082700509010966">"Modifica file"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Inserisci la password per sbloccare il file"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-iw/strings.xml b/pdf/pdf-viewer/src/main/res/values-iw/strings.xml
index e7adcaa3..08eb4b5 100644
--- a/pdf/pdf-viewer/src/main/res/values-iw/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-iw/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"הבא"</string>
<string name="close_button_description" msgid="7379823906921067675">"סגירה"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> מתוך <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"לא נמצאו תוצאות תואמות"</string>
<string name="action_edit" msgid="5882082700509010966">"עריכת הקובץ"</string>
<string name="password_not_entered" msgid="8875370870743585303">"צריך להזין סיסמה לביטול הנעילה"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-ja/strings.xml b/pdf/pdf-viewer/src/main/res/values-ja/strings.xml
index e2161ea..09692f7 100644
--- a/pdf/pdf-viewer/src/main/res/values-ja/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ja/strings.xml
@@ -47,6 +47,7 @@
<string name="next_button_description" msgid="4702699322249103693">"次へ"</string>
<string name="close_button_description" msgid="7379823906921067675">"閉じる"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="match_status_description" msgid="4996847358326345288">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="message_no_match_status" msgid="5929387004361286433">"一致する結果がありません"</string>
<string name="action_edit" msgid="5882082700509010966">"ファイルを編集"</string>
<string name="password_not_entered" msgid="8875370870743585303">"ロックを解除するには、パスワードを入力してください"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-ka/strings.xml b/pdf/pdf-viewer/src/main/res/values-ka/strings.xml
index 1d76eda..e131512 100644
--- a/pdf/pdf-viewer/src/main/res/values-ka/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ka/strings.xml
@@ -47,6 +47,7 @@
<string name="next_button_description" msgid="4702699322249103693">"შემდეგი"</string>
<string name="close_button_description" msgid="7379823906921067675">"დახურვა"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="match_status_description" msgid="4996847358326345288">"<xliff:g id="TOTAL">%2$d</xliff:g>-დან <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="message_no_match_status" msgid="5929387004361286433">"შედეგებში არ არის დამთხვევა"</string>
<string name="action_edit" msgid="5882082700509010966">"ფაილის რედაქტირება"</string>
<string name="password_not_entered" msgid="8875370870743585303">"პაროლის შეყვანა განბლოკვისთვის"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-kk/strings.xml b/pdf/pdf-viewer/src/main/res/values-kk/strings.xml
index 6640153..31e081d 100644
--- a/pdf/pdf-viewer/src/main/res/values-kk/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-kk/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"Келесі"</string>
<string name="close_button_description" msgid="7379823906921067675">"Жабу"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"Сәйкес нәтижелер табылмады."</string>
<string name="action_edit" msgid="5882082700509010966">"Файлды өңдеу"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Құлыпты ашу үшін құпия сөзді енгізіңіз."</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-km/strings.xml b/pdf/pdf-viewer/src/main/res/values-km/strings.xml
index 11cd884..5ce58c9 100644
--- a/pdf/pdf-viewer/src/main/res/values-km/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-km/strings.xml
@@ -47,6 +47,7 @@
<string name="next_button_description" msgid="4702699322249103693">"បន្ទាប់"</string>
<string name="close_button_description" msgid="7379823906921067675">"បិទ"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="match_status_description" msgid="4996847358326345288">"<xliff:g id="POSITION">%1$d</xliff:g> នៃ <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="message_no_match_status" msgid="5929387004361286433">"គ្មានលទ្ធផលត្រូវគ្នាទេ"</string>
<string name="action_edit" msgid="5882082700509010966">"កែឯកសារ"</string>
<string name="password_not_entered" msgid="8875370870743585303">"បញ្ចូលពាក្យសម្ងាត់ ដើម្បីដោះសោ"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-kn/strings.xml b/pdf/pdf-viewer/src/main/res/values-kn/strings.xml
index 65e39a0..a94de26 100644
--- a/pdf/pdf-viewer/src/main/res/values-kn/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-kn/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"ಮುಂದಿನದು"</string>
<string name="close_button_description" msgid="7379823906921067675">"ಮುಚ್ಚಿರಿ"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"ಯಾವುದೇ ಹೊಂದಾಣಿಕೆಯ ಫಲಿತಾಂಶಗಳಿಲ್ಲ"</string>
<string name="action_edit" msgid="5882082700509010966">"ಫೈಲ್ ಎಡಿಟ್ ಮಾಡಿ"</string>
<string name="password_not_entered" msgid="8875370870743585303">"ಅನ್ಲಾಕ್ ಮಾಡಲು ಪಾಸವರ್ಡ್ ಅನ್ನು ನಮೂದಿಸಿ"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-ko/strings.xml b/pdf/pdf-viewer/src/main/res/values-ko/strings.xml
index 0726b77..2dc42f3 100644
--- a/pdf/pdf-viewer/src/main/res/values-ko/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ko/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"다음"</string>
<string name="close_button_description" msgid="7379823906921067675">"닫기"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"일치하는 결과 없음"</string>
<string name="action_edit" msgid="5882082700509010966">"파일 수정"</string>
<string name="password_not_entered" msgid="8875370870743585303">"잠금 해제하려면 비밀번호 입력"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-ky/strings.xml b/pdf/pdf-viewer/src/main/res/values-ky/strings.xml
index 1a2783b..d88a930 100644
--- a/pdf/pdf-viewer/src/main/res/values-ky/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ky/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"Кийинки"</string>
<string name="close_button_description" msgid="7379823906921067675">"Жабуу"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"Эч нерсе табылган жок"</string>
<string name="action_edit" msgid="5882082700509010966">"Файлды түзөтүү"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Кулпусун ачуу үчүн сырсөздү териңиз"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-lo/strings.xml b/pdf/pdf-viewer/src/main/res/values-lo/strings.xml
index 6eee324..4f4a7379 100644
--- a/pdf/pdf-viewer/src/main/res/values-lo/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-lo/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"ຕໍ່ໄປ"</string>
<string name="close_button_description" msgid="7379823906921067675">"ປິດ"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"ບໍ່ມີຜົນໄດ້ຮັບທີ່ກົງກັນ"</string>
<string name="action_edit" msgid="5882082700509010966">"ແກ້ໄຂໄຟລ໌"</string>
<string name="password_not_entered" msgid="8875370870743585303">"ໃສ່ລະຫັດເພື່ອປົດລັອກ"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-lt/strings.xml b/pdf/pdf-viewer/src/main/res/values-lt/strings.xml
index 44ef381..aea04bf 100644
--- a/pdf/pdf-viewer/src/main/res/values-lt/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-lt/strings.xml
@@ -47,6 +47,7 @@
<string name="next_button_description" msgid="4702699322249103693">"Kitas"</string>
<string name="close_button_description" msgid="7379823906921067675">"Uždaryti"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="match_status_description" msgid="4996847358326345288">"<xliff:g id="POSITION">%1$d</xliff:g> iš <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="message_no_match_status" msgid="5929387004361286433">"Atitikusių rezultatų nerasta"</string>
<string name="action_edit" msgid="5882082700509010966">"Redaguoti failą"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Įveskite slaptažodį, kad atrakintumėte"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-lv/strings.xml b/pdf/pdf-viewer/src/main/res/values-lv/strings.xml
index 62b43d2..8e489b8 100644
--- a/pdf/pdf-viewer/src/main/res/values-lv/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-lv/strings.xml
@@ -47,6 +47,7 @@
<string name="next_button_description" msgid="4702699322249103693">"Tālāk"</string>
<string name="close_button_description" msgid="7379823906921067675">"Aizvērt"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="match_status_description" msgid="4996847358326345288">"numur <xliff:g id="POSITION">%1$d</xliff:g>, kopējais skaits ir <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="message_no_match_status" msgid="5929387004361286433">"Nav atbilstošu rezultātu."</string>
<string name="action_edit" msgid="5882082700509010966">"Rediģēt failu"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Lai atbloķētu, ievadiet paroli."</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-mk/strings.xml b/pdf/pdf-viewer/src/main/res/values-mk/strings.xml
index 86424ba..f7b0a43 100644
--- a/pdf/pdf-viewer/src/main/res/values-mk/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-mk/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"Следно"</string>
<string name="close_button_description" msgid="7379823906921067675">"Затвори"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"Нема резултати што се совпаѓаат"</string>
<string name="action_edit" msgid="5882082700509010966">"Изменете ја датотеката"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Внесете лозинка за да отклучите"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-ml/strings.xml b/pdf/pdf-viewer/src/main/res/values-ml/strings.xml
index e1c7e401..18dda28 100644
--- a/pdf/pdf-viewer/src/main/res/values-ml/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ml/strings.xml
@@ -47,6 +47,7 @@
<string name="next_button_description" msgid="4702699322249103693">"അടുത്തത്"</string>
<string name="close_button_description" msgid="7379823906921067675">"അടയ്ക്കുക"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="match_status_description" msgid="4996847358326345288">"<xliff:g id="TOTAL">%2$d</xliff:g>-ൽ <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="message_no_match_status" msgid="5929387004361286433">"പൊരുത്തപ്പെടുന്ന ഫലങ്ങളൊന്നുമില്ല"</string>
<string name="action_edit" msgid="5882082700509010966">"ഫയൽ എഡിറ്റ് ചെയ്യുക"</string>
<string name="password_not_entered" msgid="8875370870743585303">"അൺലോക്ക് ചെയ്യാൻ പാസ്വേഡ് നൽകുക"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-mn/strings.xml b/pdf/pdf-viewer/src/main/res/values-mn/strings.xml
index eee4b83..5a406cd 100644
--- a/pdf/pdf-viewer/src/main/res/values-mn/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-mn/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"Дараах"</string>
<string name="close_button_description" msgid="7379823906921067675">"Хаах"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"Ямар ч тохирох илэрц байхгүй"</string>
<string name="action_edit" msgid="5882082700509010966">"Файлыг засах"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Түгжээг тайлахын тулд нууц үг оруулна уу"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-mr/strings.xml b/pdf/pdf-viewer/src/main/res/values-mr/strings.xml
index 73892fc..663888f 100644
--- a/pdf/pdf-viewer/src/main/res/values-mr/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-mr/strings.xml
@@ -47,6 +47,7 @@
<string name="next_button_description" msgid="4702699322249103693">"पुढील"</string>
<string name="close_button_description" msgid="7379823906921067675">"बंद करा"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="match_status_description" msgid="4996847358326345288">"<xliff:g id="TOTAL">%2$d</xliff:g> पैकी <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="message_no_match_status" msgid="5929387004361286433">"कोणतेही जुळणारे परिणाम नाहीत"</string>
<string name="action_edit" msgid="5882082700509010966">"फाइल संपादित करा"</string>
<string name="password_not_entered" msgid="8875370870743585303">"अनलॉक करण्यासाठी पासवर्ड एंटर करा"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-ms/strings.xml b/pdf/pdf-viewer/src/main/res/values-ms/strings.xml
index 7d642d7..c4565f0 100644
--- a/pdf/pdf-viewer/src/main/res/values-ms/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ms/strings.xml
@@ -47,6 +47,7 @@
<string name="next_button_description" msgid="4702699322249103693">"Seterusnya"</string>
<string name="close_button_description" msgid="7379823906921067675">"Tutup"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="match_status_description" msgid="4996847358326345288">"<xliff:g id="POSITION">%1$d</xliff:g> daripada <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="message_no_match_status" msgid="5929387004361286433">"Tiada hasil carian yang sepadan"</string>
<string name="action_edit" msgid="5882082700509010966">"Edit fail"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Masukkan kata laluan untuk membuka kunci"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-my/strings.xml b/pdf/pdf-viewer/src/main/res/values-my/strings.xml
index e642765..09ff37e 100644
--- a/pdf/pdf-viewer/src/main/res/values-my/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-my/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"ရှေ့သို့"</string>
<string name="close_button_description" msgid="7379823906921067675">"ပိတ်ရန်"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"ကိုက်ညီသောရလဒ် မရှိပါ"</string>
<string name="action_edit" msgid="5882082700509010966">"ဖိုင် တည်းဖြတ်ရန်"</string>
<string name="password_not_entered" msgid="8875370870743585303">"ဖွင့်ရန် စကားဝှက်ထည့်ပါ"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-nb/strings.xml b/pdf/pdf-viewer/src/main/res/values-nb/strings.xml
index 163e356..cb39104 100644
--- a/pdf/pdf-viewer/src/main/res/values-nb/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-nb/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"Neste"</string>
<string name="close_button_description" msgid="7379823906921067675">"Lukk"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"Ingen treff"</string>
<string name="action_edit" msgid="5882082700509010966">"Endre filen"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Skriv inn passordet for å låse opp"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-ne/strings.xml b/pdf/pdf-viewer/src/main/res/values-ne/strings.xml
index 4530550..5ef47e9 100644
--- a/pdf/pdf-viewer/src/main/res/values-ne/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ne/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"अर्को"</string>
<string name="close_button_description" msgid="7379823906921067675">"बन्द गर्नुहोस्"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"कुनै पनि मिल्दोजुल्दो परिणाम भेटिएन"</string>
<string name="action_edit" msgid="5882082700509010966">"फाइल सम्पादन गर्नुहोस्"</string>
<string name="password_not_entered" msgid="8875370870743585303">"अनलक गर्न पासवर्ड हाल्नुहोस्"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-nl/strings.xml b/pdf/pdf-viewer/src/main/res/values-nl/strings.xml
index 1ddbf68..25fe496 100644
--- a/pdf/pdf-viewer/src/main/res/values-nl/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-nl/strings.xml
@@ -47,6 +47,7 @@
<string name="next_button_description" msgid="4702699322249103693">"Volgende"</string>
<string name="close_button_description" msgid="7379823906921067675">"Sluiten"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="match_status_description" msgid="4996847358326345288">"<xliff:g id="POSITION">%1$d</xliff:g> van <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="message_no_match_status" msgid="5929387004361286433">"Geen overeenkomende resultaten"</string>
<string name="action_edit" msgid="5882082700509010966">"Bestand bewerken"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Voer het wachtwoord in om te ontgrendelen"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-or/strings.xml b/pdf/pdf-viewer/src/main/res/values-or/strings.xml
index 4b08b7b..e67a2f5 100644
--- a/pdf/pdf-viewer/src/main/res/values-or/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-or/strings.xml
@@ -47,6 +47,7 @@
<string name="next_button_description" msgid="4702699322249103693">"ପରବର୍ତ୍ତୀ"</string>
<string name="close_button_description" msgid="7379823906921067675">"ବନ୍ଦ କରନ୍ତୁ"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="match_status_description" msgid="4996847358326345288">"<xliff:g id="TOTAL">%2$d</xliff:g>ର <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="message_no_match_status" msgid="5929387004361286433">"ମେଳ ହେଉଥିବା କୌଣସି ଫଳାଫଳ ନାହିଁ"</string>
<string name="action_edit" msgid="5882082700509010966">"ଫାଇଲକୁ ଏଡିଟ କରନ୍ତୁ"</string>
<string name="password_not_entered" msgid="8875370870743585303">"ଅନଲକ କରିବା ପାଇଁ ପାସୱାର୍ଡ ଲେଖନ୍ତୁ"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-pa/strings.xml b/pdf/pdf-viewer/src/main/res/values-pa/strings.xml
index e68fbfa..b4845ba 100644
--- a/pdf/pdf-viewer/src/main/res/values-pa/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-pa/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"ਅੱਗੇ"</string>
<string name="close_button_description" msgid="7379823906921067675">"ਬੰਦ ਕਰੋ"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"ਕੋਈ ਮੇਲ ਖਾਂਦਾ ਨਤੀਜਾ ਨਹੀਂ"</string>
<string name="action_edit" msgid="5882082700509010966">"ਫ਼ਾਈਲ ਦਾ ਸੰਪਾਦਨ ਕਰੋ"</string>
<string name="password_not_entered" msgid="8875370870743585303">"ਅਣਲਾਕ ਕਰਨ ਲਈ ਪਾਸਵਰਡ ਦਾਖਲ ਕਰੋ"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-pl/strings.xml b/pdf/pdf-viewer/src/main/res/values-pl/strings.xml
index 3d52bbc..848f550 100644
--- a/pdf/pdf-viewer/src/main/res/values-pl/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-pl/strings.xml
@@ -47,6 +47,7 @@
<string name="next_button_description" msgid="4702699322249103693">"Dalej"</string>
<string name="close_button_description" msgid="7379823906921067675">"Zamknij"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="match_status_description" msgid="4996847358326345288">"<xliff:g id="POSITION">%1$d</xliff:g> z <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="message_no_match_status" msgid="5929387004361286433">"Brak pasujących wyników"</string>
<string name="action_edit" msgid="5882082700509010966">"Edytuj plik"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Podaj hasło, aby odblokować"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-pt-rBR/strings.xml b/pdf/pdf-viewer/src/main/res/values-pt-rBR/strings.xml
index 57583b90..642730e 100644
--- a/pdf/pdf-viewer/src/main/res/values-pt-rBR/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-pt-rBR/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"Próxima"</string>
<string name="close_button_description" msgid="7379823906921067675">"Fechar"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"Nenhum resultado encontrado"</string>
<string name="action_edit" msgid="5882082700509010966">"Editar arquivo"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Digite a senha para desbloquear"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-pt-rPT/strings.xml b/pdf/pdf-viewer/src/main/res/values-pt-rPT/strings.xml
index 17901b0..667143f 100644
--- a/pdf/pdf-viewer/src/main/res/values-pt-rPT/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-pt-rPT/strings.xml
@@ -47,6 +47,7 @@
<string name="next_button_description" msgid="4702699322249103693">"Seguinte"</string>
<string name="close_button_description" msgid="7379823906921067675">"Fechar"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="match_status_description" msgid="4996847358326345288">"<xliff:g id="POSITION">%1$d</xliff:g> de <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="message_no_match_status" msgid="5929387004361286433">"Sem resultados correspondentes"</string>
<string name="action_edit" msgid="5882082700509010966">"Editar ficheiro"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Introduza a palavra-passe para desbloquear"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-pt/strings.xml b/pdf/pdf-viewer/src/main/res/values-pt/strings.xml
index 57583b90..642730e 100644
--- a/pdf/pdf-viewer/src/main/res/values-pt/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-pt/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"Próxima"</string>
<string name="close_button_description" msgid="7379823906921067675">"Fechar"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"Nenhum resultado encontrado"</string>
<string name="action_edit" msgid="5882082700509010966">"Editar arquivo"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Digite a senha para desbloquear"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-ro/strings.xml b/pdf/pdf-viewer/src/main/res/values-ro/strings.xml
index f33ce79..ea88bec 100644
--- a/pdf/pdf-viewer/src/main/res/values-ro/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ro/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"Înainte"</string>
<string name="close_button_description" msgid="7379823906921067675">"Închide"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"Niciun rezultat"</string>
<string name="action_edit" msgid="5882082700509010966">"Editează fișierul"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Introdu parola pentru a debloca"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-ru/strings.xml b/pdf/pdf-viewer/src/main/res/values-ru/strings.xml
index b3bed54..d062234 100644
--- a/pdf/pdf-viewer/src/main/res/values-ru/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ru/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"Далее"</string>
<string name="close_button_description" msgid="7379823906921067675">"Закрыть"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> из <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"Ничего не найдено."</string>
<string name="action_edit" msgid="5882082700509010966">"Редактировать файл"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Введите пароль для разблокировки."</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-si/strings.xml b/pdf/pdf-viewer/src/main/res/values-si/strings.xml
index 7aeb8c8..3c22ba7 100644
--- a/pdf/pdf-viewer/src/main/res/values-si/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-si/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"මීළඟ"</string>
<string name="close_button_description" msgid="7379823906921067675">"වසන්න"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"ගැළපෙන ප්රතිඵල නැත"</string>
<string name="action_edit" msgid="5882082700509010966">"ගොනුව සංස්කරණ කරන්න"</string>
<string name="password_not_entered" msgid="8875370870743585303">"අගුලු හැරීමට මුරපදය ඇතුළත් කරන්න"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-sk/strings.xml b/pdf/pdf-viewer/src/main/res/values-sk/strings.xml
index 2c4e7b4..8fdcc4c 100644
--- a/pdf/pdf-viewer/src/main/res/values-sk/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-sk/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"Ďalej"</string>
<string name="close_button_description" msgid="7379823906921067675">"Zavrieť"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"Žiadne zodpovedajúce výsledky"</string>
<string name="action_edit" msgid="5882082700509010966">"Upraviť súbor"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Zadajte heslo na odomknutie"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-sl/strings.xml b/pdf/pdf-viewer/src/main/res/values-sl/strings.xml
index 5ff5caa..e43931d 100644
--- a/pdf/pdf-viewer/src/main/res/values-sl/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-sl/strings.xml
@@ -47,6 +47,7 @@
<string name="next_button_description" msgid="4702699322249103693">"Naprej"</string>
<string name="close_button_description" msgid="7379823906921067675">"Zapri"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="match_status_description" msgid="4996847358326345288">"<xliff:g id="POSITION">%1$d</xliff:g> od <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="message_no_match_status" msgid="5929387004361286433">"Ni ustreznih rezultatov"</string>
<string name="action_edit" msgid="5882082700509010966">"Urejanje datoteke"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Vnesite geslo za odklepanje"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-sq/strings.xml b/pdf/pdf-viewer/src/main/res/values-sq/strings.xml
index a78b08a..482261d 100644
--- a/pdf/pdf-viewer/src/main/res/values-sq/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-sq/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"Para"</string>
<string name="close_button_description" msgid="7379823906921067675">"Mbyll"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"Nuk përputhet asnjë rezultat"</string>
<string name="action_edit" msgid="5882082700509010966">"Modifiko skedarin"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Fut fjalëkalimin për ta shkyçur"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-sr/strings.xml b/pdf/pdf-viewer/src/main/res/values-sr/strings.xml
index 61074fd..ccd521a 100644
--- a/pdf/pdf-viewer/src/main/res/values-sr/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-sr/strings.xml
@@ -47,6 +47,7 @@
<string name="next_button_description" msgid="4702699322249103693">"Даље"</string>
<string name="close_button_description" msgid="7379823906921067675">"Затвори"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="match_status_description" msgid="4996847358326345288">"<xliff:g id="POSITION">%1$d</xliff:g> од <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="message_no_match_status" msgid="5929387004361286433">"Нема подударних резултата"</string>
<string name="action_edit" msgid="5882082700509010966">"Измени фајл"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Унесите лозинку за откључавање"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-sv/strings.xml b/pdf/pdf-viewer/src/main/res/values-sv/strings.xml
index e756be8..edbbf58 100644
--- a/pdf/pdf-viewer/src/main/res/values-sv/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-sv/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"Nästa"</string>
<string name="close_button_description" msgid="7379823906921067675">"Stäng"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"Det finns inga matchande resultat"</string>
<string name="action_edit" msgid="5882082700509010966">"Redigera fil"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Ange lösenord för att låsa upp"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-sw/strings.xml b/pdf/pdf-viewer/src/main/res/values-sw/strings.xml
index 9f50a92a..f26bdf5 100644
--- a/pdf/pdf-viewer/src/main/res/values-sw/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-sw/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"Endelea"</string>
<string name="close_button_description" msgid="7379823906921067675">"Funga"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> kati ya <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"Hakuna matokeo yanayolingana"</string>
<string name="action_edit" msgid="5882082700509010966">"Badilisha faili"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Weka nenosiri ili ufungue"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-ta/strings.xml b/pdf/pdf-viewer/src/main/res/values-ta/strings.xml
index 3c329ca..56321e0 100644
--- a/pdf/pdf-viewer/src/main/res/values-ta/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ta/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"அடுத்ததற்குச் செல்லும்"</string>
<string name="close_button_description" msgid="7379823906921067675">"மூடும்"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"பொருந்தும் முடிவுகள் எதுவுமில்லை"</string>
<string name="action_edit" msgid="5882082700509010966">"ஃபைலைத் திருத்து"</string>
<string name="password_not_entered" msgid="8875370870743585303">"அன்லாக் செய்ய கடவுச்சொல்லை டைப் செய்யவும்"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-te/strings.xml b/pdf/pdf-viewer/src/main/res/values-te/strings.xml
index 1b0c0d8..3cc15c5 100644
--- a/pdf/pdf-viewer/src/main/res/values-te/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-te/strings.xml
@@ -47,6 +47,7 @@
<string name="next_button_description" msgid="4702699322249103693">"తర్వాత"</string>
<string name="close_button_description" msgid="7379823906921067675">"మూసివేయండి"</string>
<string name="message_match_status" msgid="6288242289981639727">"మొత్తం <xliff:g id="TOTAL">%2$d</xliff:g>లో <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="match_status_description" msgid="4996847358326345288">"<xliff:g id="TOTAL">%2$d</xliff:g>లో <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="message_no_match_status" msgid="5929387004361286433">"మ్యాచ్ అయ్యే ఫలితాలు ఏవీ లేవు"</string>
<string name="action_edit" msgid="5882082700509010966">"ఫైల్ను ఎడిట్ చేయండి"</string>
<string name="password_not_entered" msgid="8875370870743585303">"అన్లాక్ చేయడానికి పాస్వర్డ్ను నమోదు చేయండి"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-th/strings.xml b/pdf/pdf-viewer/src/main/res/values-th/strings.xml
index 17236b8..3f4cacb 100644
--- a/pdf/pdf-viewer/src/main/res/values-th/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-th/strings.xml
@@ -47,6 +47,7 @@
<string name="next_button_description" msgid="4702699322249103693">"ถัดไป"</string>
<string name="close_button_description" msgid="7379823906921067675">"ปิด"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="match_status_description" msgid="4996847358326345288">"<xliff:g id="POSITION">%1$d</xliff:g> จาก <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="message_no_match_status" msgid="5929387004361286433">"ไม่มีผลลัพธ์ที่ตรงกัน"</string>
<string name="action_edit" msgid="5882082700509010966">"แก้ไขไฟล์"</string>
<string name="password_not_entered" msgid="8875370870743585303">"ป้อนรหัสผ่านเพื่อปลดล็อก"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-tl/strings.xml b/pdf/pdf-viewer/src/main/res/values-tl/strings.xml
index 1daaa55..8dded5f 100644
--- a/pdf/pdf-viewer/src/main/res/values-tl/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-tl/strings.xml
@@ -47,6 +47,7 @@
<string name="next_button_description" msgid="4702699322249103693">"Susunod"</string>
<string name="close_button_description" msgid="7379823906921067675">"Isara"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="match_status_description" msgid="4996847358326345288">"<xliff:g id="POSITION">%1$d</xliff:g> sa <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="message_no_match_status" msgid="5929387004361286433">"Walang tumutugmang resulta"</string>
<string name="action_edit" msgid="5882082700509010966">"I-edit ang file"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Ilagay ang password para i-unlock"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-tr/strings.xml b/pdf/pdf-viewer/src/main/res/values-tr/strings.xml
index c378486..d247153 100644
--- a/pdf/pdf-viewer/src/main/res/values-tr/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-tr/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"Sonraki"</string>
<string name="close_button_description" msgid="7379823906921067675">"Kapat"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"Eşleşen sonuç yok"</string>
<string name="action_edit" msgid="5882082700509010966">"Dosyayı düzenle"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Kilidi açmak için şifreyi girin"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-uk/strings.xml b/pdf/pdf-viewer/src/main/res/values-uk/strings.xml
index 358244d..11c4ecf 100644
--- a/pdf/pdf-viewer/src/main/res/values-uk/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-uk/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"Далі"</string>
<string name="close_button_description" msgid="7379823906921067675">"Закрити"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"Немає результатів"</string>
<string name="action_edit" msgid="5882082700509010966">"Редагувати файл"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Введіть пароль, щоб розблокувати"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-ur/strings.xml b/pdf/pdf-viewer/src/main/res/values-ur/strings.xml
index 97c7973..12ccee6 100644
--- a/pdf/pdf-viewer/src/main/res/values-ur/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-ur/strings.xml
@@ -47,6 +47,7 @@
<string name="next_button_description" msgid="4702699322249103693">"اگلا"</string>
<string name="close_button_description" msgid="7379823906921067675">"بند کریں"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="TOTAL">%2$d</xliff:g> / <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="match_status_description" msgid="4996847358326345288">"<xliff:g id="POSITION">%1$d</xliff:g> از <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="message_no_match_status" msgid="5929387004361286433">"کوئی مماثل نتائج نہیں ہیں"</string>
<string name="action_edit" msgid="5882082700509010966">"فائل میں ترمیم کریں"</string>
<string name="password_not_entered" msgid="8875370870743585303">"غیر مقفل کرنے کیلئے پاس ورڈ درج کریں"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-uz/strings.xml b/pdf/pdf-viewer/src/main/res/values-uz/strings.xml
index eaea88b..82dd413 100644
--- a/pdf/pdf-viewer/src/main/res/values-uz/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-uz/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"Keyingisi"</string>
<string name="close_button_description" msgid="7379823906921067675">"Yopish"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"Mos keladigani topilmadi"</string>
<string name="action_edit" msgid="5882082700509010966">"Faylni tahrirlash"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Ochish uchun parolni kiriting"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-vi/strings.xml b/pdf/pdf-viewer/src/main/res/values-vi/strings.xml
index 1960c80..42e04e9 100644
--- a/pdf/pdf-viewer/src/main/res/values-vi/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-vi/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"Tiếp theo"</string>
<string name="close_button_description" msgid="7379823906921067675">"Đóng"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"Không có kết quả phù hợp"</string>
<string name="action_edit" msgid="5882082700509010966">"Chỉnh sửa tệp"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Nhập mật khẩu để mở khoá"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-zh-rCN/strings.xml b/pdf/pdf-viewer/src/main/res/values-zh-rCN/strings.xml
index cf29a52..343768c 100644
--- a/pdf/pdf-viewer/src/main/res/values-zh-rCN/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-zh-rCN/strings.xml
@@ -47,6 +47,7 @@
<string name="next_button_description" msgid="4702699322249103693">"下一页"</string>
<string name="close_button_description" msgid="7379823906921067675">"关闭"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <string name="match_status_description" msgid="4996847358326345288">"第 <xliff:g id="POSITION">%1$d</xliff:g> 个(共 <xliff:g id="TOTAL">%2$d</xliff:g> 个)"</string>
<string name="message_no_match_status" msgid="5929387004361286433">"没有符合条件的结果"</string>
<string name="action_edit" msgid="5882082700509010966">"编辑文件"</string>
<string name="password_not_entered" msgid="8875370870743585303">"请输入密码进行解锁"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-zh-rHK/strings.xml b/pdf/pdf-viewer/src/main/res/values-zh-rHK/strings.xml
index 06c8e92..984fbe1 100644
--- a/pdf/pdf-viewer/src/main/res/values-zh-rHK/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-zh-rHK/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"下一個"</string>
<string name="close_button_description" msgid="7379823906921067675">"閂"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"找不到相符的結果"</string>
<string name="action_edit" msgid="5882082700509010966">"編輯檔案"</string>
<string name="password_not_entered" msgid="8875370870743585303">"輸入密碼即可解鎖"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-zh-rTW/strings.xml b/pdf/pdf-viewer/src/main/res/values-zh-rTW/strings.xml
index 12b583f..915cd4a 100644
--- a/pdf/pdf-viewer/src/main/res/values-zh-rTW/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-zh-rTW/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"下一個"</string>
<string name="close_button_description" msgid="7379823906921067675">"關閉"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"找不到相符的結果"</string>
<string name="action_edit" msgid="5882082700509010966">"編輯檔案"</string>
<string name="password_not_entered" msgid="8875370870743585303">"輸入密碼即可解鎖"</string>
diff --git a/pdf/pdf-viewer/src/main/res/values-zu/strings.xml b/pdf/pdf-viewer/src/main/res/values-zu/strings.xml
index a3ad158..23f95f7 100644
--- a/pdf/pdf-viewer/src/main/res/values-zu/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values-zu/strings.xml
@@ -47,6 +47,8 @@
<string name="next_button_description" msgid="4702699322249103693">"Okulandelayo"</string>
<string name="close_button_description" msgid="7379823906921067675">"Vala"</string>
<string name="message_match_status" msgid="6288242289981639727">"<xliff:g id="POSITION">%1$d</xliff:g> / <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+ <!-- no translation found for match_status_description (4996847358326345288) -->
+ <skip />
<string name="message_no_match_status" msgid="5929387004361286433">"Ayikho imiphumela efanayo"</string>
<string name="action_edit" msgid="5882082700509010966">"Hlela ifayela"</string>
<string name="password_not_entered" msgid="8875370870743585303">"Faka iphasiwedi ukuvula"</string>
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/ext/xelement_ext.kt b/room/room-compiler/src/main/kotlin/androidx/room/ext/xelement_ext.kt
index bf4537a..28f9f0c 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/ext/xelement_ext.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/ext/xelement_ext.kt
@@ -16,10 +16,8 @@
package androidx.room.ext
-import androidx.room.compiler.processing.XConstructorElement
import androidx.room.compiler.processing.XElement
import androidx.room.compiler.processing.XExecutableParameterElement
-import androidx.room.compiler.processing.XFieldElement
import androidx.room.compiler.processing.XTypeElement
import kotlin.contracts.contract
@@ -28,7 +26,7 @@
return this.hasAnnotation(androidx.room.Entity::class)
}
-fun XTypeElement.getValueClassUnderlyingInfo(): ValueClassInfo {
+fun XTypeElement.getValueClassUnderlyingElement(): XExecutableParameterElement {
check(this.isValueClass()) {
"Can't get value class property, type element '$this' is not a value class"
}
@@ -36,21 +34,12 @@
// * Primary constructor is required for value class
// * Value class must have exactly one primary constructor parameter
// * Value class primary constructor must only have final read-only (val) property parameter
- val constructor =
- checkNotNull(this.findPrimaryConstructor()) {
+ return checkNotNull(this.findPrimaryConstructor()) {
"Couldn't find primary constructor for value class."
}
- val param = constructor.parameters.first()
- val field = getDeclaredFields().first { it.name == param.name }
- return ValueClassInfo(constructor, param, field)
+ .parameters
+ .single()
}
-/** Store information about the underlying value property of a Kotlin value class */
-class ValueClassInfo(
- val constructor: XConstructorElement,
- val parameter: XExecutableParameterElement,
- val field: XFieldElement,
-)
-
/** Suffix of the Kotlin synthetic class created interface method implementations. */
const val DEFAULT_IMPLS_CLASS_NAME = "DefaultImpls"
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
index 86c5e95..798bfeb 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
@@ -28,7 +28,7 @@
import androidx.room.ext.CollectionTypeNames.LONG_SPARSE_ARRAY
import androidx.room.ext.CommonTypeNames
import androidx.room.ext.GuavaTypeNames
-import androidx.room.ext.getValueClassUnderlyingInfo
+import androidx.room.ext.getValueClassUnderlyingElement
import androidx.room.ext.isByteBuffer
import androidx.room.ext.isEntityElement
import androidx.room.ext.isNotByte
@@ -377,15 +377,12 @@
val typeElement = type.typeElement
if (typeElement?.isValueClass() == true) {
// Extract the type value of the Value class element
- val underlyingInfo = typeElement.getValueClassUnderlyingInfo()
- if (underlyingInfo.constructor.isPrivate() || underlyingInfo.field.getter == null) {
- return null
- }
+ val underlyingProperty = typeElement.getValueClassUnderlyingElement()
val underlyingTypeColumnAdapter =
findColumnTypeAdapter(
// Find an adapter for the non-null underlying type, nullability will be handled
// by the value class adapter.
- out = underlyingInfo.parameter.asMemberOf(type).makeNonNullable(),
+ out = underlyingProperty.asMemberOf(type).makeNonNullable(),
affinity = affinity,
skipDefaultConverter = false
) ?: return null
@@ -394,7 +391,7 @@
valueTypeColumnAdapter = underlyingTypeColumnAdapter,
affinity = underlyingTypeColumnAdapter.typeAffinity,
out = type,
- valuePropertyName = underlyingInfo.parameter.name
+ valuePropertyName = underlyingProperty.name
)
}
return when {
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/ext/ElementExtTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/ext/ElementExtTest.kt
index db835d4..1e314bf 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/ext/ElementExtTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/ext/ElementExtTest.kt
@@ -24,7 +24,7 @@
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.XTestInvocation
import androidx.room.compiler.processing.util.compileFiles
-import androidx.room.compiler.processing.util.runKspTest
+import androidx.room.runKspTestWithK1
import androidx.room.runProcessorTestWithK1
import org.junit.Test
import org.junit.runner.RunWith
@@ -225,44 +225,25 @@
"""
package foo
class Subject {
- fun uLongFunction(): ULong = TODO()
- fun durationFunction(): kotlin.time.Duration = TODO()
+ fun makeULong(): ULong {
+ TODO()
+ }
}
"""
.trimIndent()
)
- runKspTest(
+ runKspTestWithK1(
sources = listOf(src),
config =
XProcessingEnvConfig.DEFAULT.copy(excludeMethodsWithInvalidJvmSourceNames = false)
) { invocation ->
val subject = invocation.processingEnv.requireTypeElement("foo.Subject")
- subject
- .getDeclaredMethods()
- .first { it.name == "uLongFunction" }
- .let { uLongFunction ->
- val returnType = uLongFunction.returnType
- val info = checkNotNull(returnType.typeElement).getValueClassUnderlyingInfo()
- assertThat(info.parameter.name).isEqualTo("data")
- assertThat(info.field.name).isEqualTo("data")
- assertThat(info.parameter.type)
- .isEqualTo(invocation.processingEnv.requireType(XTypeName.PRIMITIVE_LONG))
- assertThat(info.field.type)
- .isEqualTo(invocation.processingEnv.requireType(XTypeName.PRIMITIVE_LONG))
- }
- subject
- .getDeclaredMethods()
- .first { it.name == "durationFunction" }
- .let { durationFunction ->
- val returnType = durationFunction.returnType
- val info = checkNotNull(returnType.typeElement).getValueClassUnderlyingInfo()
- assertThat(info.parameter.name).isEqualTo("rawValue")
- assertThat(info.field.name).isEqualTo("rawValue")
- assertThat(info.parameter.type)
- .isEqualTo(invocation.processingEnv.requireType(XTypeName.PRIMITIVE_LONG))
- assertThat(info.field.type)
- .isEqualTo(invocation.processingEnv.requireType(XTypeName.PRIMITIVE_LONG))
- }
+ val returnType =
+ subject.getDeclaredMethods().single { it.name == "makeULong" }.returnType
+ val prop = checkNotNull(returnType.typeElement).getValueClassUnderlyingElement()
+ assertThat(prop.name).isEqualTo("data")
+ assertThat(prop.type)
+ .isEqualTo(invocation.processingEnv.requireType(XTypeName.PRIMITIVE_LONG))
}
}
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeAdapterStoreTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeAdapterStoreTest.kt
index d43a813..29cbd79 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeAdapterStoreTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeAdapterStoreTest.kt
@@ -30,7 +30,6 @@
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.XTestInvocation
import androidx.room.compiler.processing.util.compileFiles
-import androidx.room.compiler.processing.util.runKspTest
import androidx.room.compiler.processing.util.runProcessorTest
import androidx.room.ext.CommonTypeNames
import androidx.room.ext.GuavaUtilConcurrentTypeNames
@@ -196,6 +195,7 @@
Source.java(
"foo.bar.Fruit",
""" package foo.bar;
+ import androidx.room.*;
enum Fruit {
APPLE,
BANANA,
@@ -222,6 +222,7 @@
Source.kotlin(
"Foo.kt",
"""
+ import androidx.room.*
@JvmInline
value class IntValueClass(val data: Int)
@JvmInline
@@ -281,6 +282,7 @@
Source.kotlin(
"Foo.kt",
"""
+ import androidx.room.*
@JvmInline
value class Foo(val value : Int) {
val double
@@ -290,78 +292,15 @@
.trimIndent()
)
- runKspTest(sources = listOf(source)) { invocation ->
- val store =
- TypeAdapterStore.create(
- context = invocation.context,
- builtInConverterFlags = BuiltInConverterFlags.DEFAULT
- )
- val typeElement = invocation.processingEnv.requireTypeElement("Foo")
- val result =
- store.findColumnTypeAdapter(
- out = typeElement.type,
- affinity = null,
- skipDefaultConverter = false
- )
- assertThat(result).isInstanceOf<ValueClassConverterWrapper>()
- }
- }
-
- @Test
- fun testValueClassWithPrivateVal() {
- val source =
- Source.kotlin(
- "Foo.kt",
- """
- @JvmInline
- value class Foo(private val value : Int)
- """
- .trimIndent()
+ runProcessorTestWithK1(sources = listOf(source)) { invocation ->
+ TypeAdapterStore.create(
+ context = invocation.context,
+ builtInConverterFlags = BuiltInConverterFlags.DEFAULT
)
-
- runKspTest(sources = listOf(source)) { invocation ->
- val store =
- TypeAdapterStore.create(
- context = invocation.context,
- builtInConverterFlags = BuiltInConverterFlags.DEFAULT
- )
val typeElement = invocation.processingEnv.requireTypeElement("Foo")
- val result =
- store.findColumnTypeAdapter(
- out = typeElement.type,
- affinity = null,
- skipDefaultConverter = false
- )
- assertThat(result).isNull()
- }
- }
-
- @Test
- fun testValueClassWithPrivateConstructor() {
- val source =
- Source.kotlin(
- "Foo.kt",
- """
- @JvmInline
- value class Foo private constructor(val value : Int)
- """
- .trimIndent()
- )
-
- runKspTest(sources = listOf(source)) { invocation ->
- val store =
- TypeAdapterStore.create(
- context = invocation.context,
- builtInConverterFlags = BuiltInConverterFlags.DEFAULT
- )
- val typeElement = invocation.processingEnv.requireTypeElement("Foo")
- val result =
- store.findColumnTypeAdapter(
- out = typeElement.type,
- affinity = null,
- skipDefaultConverter = false
- )
- assertThat(result).isNull()
+ assertThat(typeElement.getDeclaredFields()).hasSize(1)
+ assertThat(typeElement.getDeclaredFields().single().type.asTypeName())
+ .isEqualTo(PRIMITIVE_INT)
}
}
diff --git a/room/room-testing/build.gradle b/room/room-testing/build.gradle
index 14a4823..4678633 100644
--- a/room/room-testing/build.gradle
+++ b/room/room-testing/build.gradle
@@ -22,7 +22,6 @@
* modifying its settings.
*/
-
import androidx.build.KotlinTarget
import androidx.build.PlatformIdentifier
import androidx.build.LibraryType
@@ -30,11 +29,20 @@
plugins {
id("AndroidXPlugin")
- id("com.android.library")
}
androidXMultiplatform {
- android()
+ androidLibrary {
+ namespace = "androidx.room.testing"
+ withAndroidTestOnDeviceBuilder {
+ it.compilationName = "instrumentedTest"
+ it.defaultSourceSetName = "androidInstrumentedTest"
+ it.sourceSetTreeName = "test"
+ }
+ withAndroidTestOnJvmBuilder {
+ it.defaultSourceSetName = "androidUnitTest"
+ }
+ }
ios()
jvm()
linux()
@@ -88,9 +96,6 @@
}
}
-android {
- namespace "androidx.room.testing"
-}
androidx {
name = "Room Testing"
diff --git a/savedstate/savedstate/api/current.txt b/savedstate/savedstate/api/current.txt
index fe1c8f0..89289e4 100644
--- a/savedstate/savedstate/api/current.txt
+++ b/savedstate/savedstate/api/current.txt
@@ -13,6 +13,7 @@
@kotlin.jvm.JvmInline public final value class SavedStateReader {
ctor public SavedStateReader(android.os.Bundle source);
method public inline operator boolean contains(String key);
+ method public boolean contentDeepEquals(android.os.Bundle other);
method public inline boolean getBoolean(String key);
method public inline boolean getBooleanOrElse(String key, kotlin.jvm.functions.Function0<java.lang.Boolean> defaultValue);
method public inline double getDouble(String key);
@@ -99,7 +100,7 @@
}
public final class SavedState_androidKt {
- method public static inline android.os.Bundle savedState(optional kotlin.jvm.functions.Function1<? super androidx.savedstate.SavedStateWriter,kotlin.Unit> block);
+ method public static inline android.os.Bundle savedState(optional java.util.Map<java.lang.String,?> initialState, optional kotlin.jvm.functions.Function1<? super androidx.savedstate.SavedStateWriter,kotlin.Unit> builderAction);
}
public final class ViewKt {
diff --git a/savedstate/savedstate/api/restricted_current.txt b/savedstate/savedstate/api/restricted_current.txt
index 47dcf78..a7d6580 100644
--- a/savedstate/savedstate/api/restricted_current.txt
+++ b/savedstate/savedstate/api/restricted_current.txt
@@ -13,6 +13,7 @@
@kotlin.jvm.JvmInline public final value class SavedStateReader {
ctor public SavedStateReader(android.os.Bundle source);
method public inline operator boolean contains(String key);
+ method public boolean contentDeepEquals(android.os.Bundle other);
method public inline boolean getBoolean(String key);
method public inline boolean getBooleanOrElse(String key, kotlin.jvm.functions.Function0<java.lang.Boolean> defaultValue);
method public inline double getDouble(String key);
@@ -45,6 +46,10 @@
property public final android.os.Bundle source;
}
+ public final class SavedStateReader_androidKt {
+ method @kotlin.PublishedApi internal static boolean contentDeepEquals(android.os.Bundle, android.os.Bundle other);
+ }
+
public final class SavedStateRegistry {
method @MainThread public android.os.Bundle? consumeRestoredStateForKey(String key);
method public androidx.savedstate.SavedStateRegistry.SavedStateProvider? getSavedStateProvider(String key);
@@ -104,7 +109,7 @@
}
public final class SavedState_androidKt {
- method public static inline android.os.Bundle savedState(optional kotlin.jvm.functions.Function1<? super androidx.savedstate.SavedStateWriter,kotlin.Unit> block);
+ method public static inline android.os.Bundle savedState(optional java.util.Map<java.lang.String,?> initialState, optional kotlin.jvm.functions.Function1<? super androidx.savedstate.SavedStateWriter,kotlin.Unit> builderAction);
}
public final class ViewKt {
diff --git a/savedstate/savedstate/bcv/native/current.txt b/savedstate/savedstate/bcv/native/current.txt
index 1a70510..8cad989 100644
--- a/savedstate/savedstate/bcv/native/current.txt
+++ b/savedstate/savedstate/bcv/native/current.txt
@@ -51,6 +51,7 @@
final val source // androidx.savedstate/SavedStateReader.source|{}source[0]
final fun <get-source>(): androidx.savedstate/SavedState // androidx.savedstate/SavedStateReader.source.<get-source>|<get-source>(){}[0]
+ final fun contentDeepEquals(androidx.savedstate/SavedState): kotlin/Boolean // androidx.savedstate/SavedStateReader.contentDeepEquals|contentDeepEquals(androidx.savedstate.SavedState){}[0]
final fun equals(kotlin/Any?): kotlin/Boolean // androidx.savedstate/SavedStateReader.equals|equals(kotlin.Any?){}[0]
final fun hashCode(): kotlin/Int // androidx.savedstate/SavedStateReader.hashCode|hashCode(){}[0]
final fun toString(): kotlin/String // androidx.savedstate/SavedStateReader.toString|toString(){}[0]
@@ -126,4 +127,4 @@
final inline fun <#A: kotlin/Any?> (androidx.savedstate/SavedState).androidx.savedstate/write(kotlin/Function1<androidx.savedstate/SavedStateWriter, #A>): #A // androidx.savedstate/write|write@androidx.savedstate.SavedState(kotlin.Function1<androidx.savedstate.SavedStateWriter,0:0>){0§<kotlin.Any?>}[0]
final inline fun <#A: kotlin/Any?> (androidx.savedstate/SavedStateReader).androidx.savedstate/write(kotlin/Function1<androidx.savedstate/SavedStateWriter, #A>): #A // androidx.savedstate/write|write@androidx.savedstate.SavedStateReader(kotlin.Function1<androidx.savedstate.SavedStateWriter,0:0>){0§<kotlin.Any?>}[0]
final inline fun <#A: kotlin/Any?> (androidx.savedstate/SavedStateWriter).androidx.savedstate/read(kotlin/Function1<androidx.savedstate/SavedStateReader, #A>): #A // androidx.savedstate/read|read@androidx.savedstate.SavedStateWriter(kotlin.Function1<androidx.savedstate.SavedStateReader,0:0>){0§<kotlin.Any?>}[0]
-final inline fun androidx.savedstate/savedState(kotlin/Function1<androidx.savedstate/SavedStateWriter, kotlin/Unit> = ...): androidx.savedstate/SavedState // androidx.savedstate/savedState|savedState(kotlin.Function1<androidx.savedstate.SavedStateWriter,kotlin.Unit>){}[0]
+final inline fun androidx.savedstate/savedState(kotlin.collections/Map<kotlin/String, kotlin/Any> = ..., kotlin/Function1<androidx.savedstate/SavedStateWriter, kotlin/Unit> = ...): androidx.savedstate/SavedState // androidx.savedstate/savedState|savedState(kotlin.collections.Map<kotlin.String,kotlin.Any>;kotlin.Function1<androidx.savedstate.SavedStateWriter,kotlin.Unit>){}[0]
diff --git a/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/SavedState.android.kt b/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/SavedState.android.kt
index 6d51126..6d339bc 100644
--- a/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/SavedState.android.kt
+++ b/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/SavedState.android.kt
@@ -14,9 +14,23 @@
* limitations under the License.
*/
+@file:Suppress("NOTHING_TO_INLINE")
+
package androidx.savedstate
+import androidx.core.os.bundleOf
+
public actual typealias SavedState = android.os.Bundle
-public actual inline fun savedState(block: SavedStateWriter.() -> Unit): SavedState =
- SavedState().apply { write(block) }
+public actual inline fun savedState(
+ initialState: Map<String, Any>,
+ builderAction: SavedStateWriter.() -> Unit,
+): SavedState {
+ val pairs =
+ if (initialState.isEmpty()) {
+ emptyArray()
+ } else {
+ initialState.map { (key, value) -> key to value }.toTypedArray()
+ }
+ return bundleOf(*pairs).apply { write(builderAction) }
+}
diff --git a/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/SavedStateReader.android.kt b/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/SavedStateReader.android.kt
index c17f0fa..f14a7d4 100644
--- a/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/SavedStateReader.android.kt
+++ b/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/SavedStateReader.android.kt
@@ -33,7 +33,7 @@
}
actual inline fun getBooleanOrElse(key: String, defaultValue: () -> Boolean): Boolean {
- return getSingleResultOrElse(key, defaultValue) { source.getBoolean(key) }
+ return getSingleResultOrElse(key, defaultValue) { source.getBoolean(key, defaultValue()) }
}
actual inline fun getDouble(key: String): Double {
@@ -41,7 +41,7 @@
}
actual inline fun getDoubleOrElse(key: String, defaultValue: () -> Double): Double {
- return getSingleResultOrElse(key, defaultValue) { source.getDouble(key) }
+ return getSingleResultOrElse(key, defaultValue) { source.getDouble(key, defaultValue()) }
}
actual inline fun getFloat(key: String): Float {
@@ -49,7 +49,7 @@
}
actual inline fun getFloatOrElse(key: String, defaultValue: () -> Float): Float {
- return getSingleResultOrElse(key, defaultValue) { source.getFloat(key) }
+ return getSingleResultOrElse(key, defaultValue) { source.getFloat(key, defaultValue()) }
}
actual inline fun getInt(key: String): Int {
@@ -57,15 +57,15 @@
}
actual inline fun getIntOrElse(key: String, defaultValue: () -> Int): Int {
- return getSingleResultOrElse(key, defaultValue) { source.getInt(key) }
+ return getSingleResultOrElse(key, defaultValue) { source.getInt(key, defaultValue()) }
}
actual inline fun getLong(key: String): Long {
- return getSingleResultOrThrow(key) { source.getLong(key) }
+ return getSingleResultOrThrow(key) { source.getLong(key, SavedStateUtils.DEFAULT_LONG) }
}
actual inline fun getLongOrElse(key: String, defaultValue: () -> Long): Long {
- return getSingleResultOrElse(key, defaultValue) { source.getLong(key) }
+ return getSingleResultOrElse(key, defaultValue) { source.getLong(key, defaultValue()) }
}
/**
@@ -102,7 +102,7 @@
}
actual inline fun getStringOrElse(key: String, defaultValue: () -> String): String {
- return getSingleResultOrElse(key, defaultValue) { source.getString(key) }
+ return getSingleResultOrElse(key, defaultValue) { source.getString(key, defaultValue()) }
}
actual inline fun getIntList(key: String): List<Int> {
@@ -171,6 +171,8 @@
actual inline operator fun contains(key: String): Boolean = source.containsKey(key)
+ actual fun contentDeepEquals(other: SavedState): Boolean = source.contentDeepEquals(other)
+
@PublishedApi
internal inline fun <reified T> getSingleResultOrThrow(
key: String,
@@ -221,3 +223,23 @@
defaultValue = { defaultValue() },
)
}
+
+@PublishedApi
+internal fun SavedState.contentDeepEquals(other: SavedState): Boolean {
+ if (this === other) return true
+ if (this.size() != other.size()) return false
+
+ for (k in this.keySet()) {
+ @Suppress("DEPRECATION") val v1 = this[k]
+ @Suppress("DEPRECATION") val v2 = other[k]
+
+ when {
+ v1 === v2 -> continue
+ v1 == null || v2 == null -> return false
+ v1 is SavedState && v2 is SavedState -> if (!v1.contentDeepEquals(v2)) return false
+ v1 is Array<*> && v2 is Array<*> -> if (!v1.contentDeepEquals(v2)) return false
+ else -> if (v1 != v2) return false
+ }
+ }
+ return true
+}
diff --git a/savedstate/savedstate/src/androidUnitTest/kotlin/androidx/savedstate/ParcelableSavedStateTest.android.kt b/savedstate/savedstate/src/androidUnitTest/kotlin/androidx/savedstate/ParcelableSavedStateTest.android.kt
index 48f76bf..73d9c8d 100644
--- a/savedstate/savedstate/src/androidUnitTest/kotlin/androidx/savedstate/ParcelableSavedStateTest.android.kt
+++ b/savedstate/savedstate/src/androidUnitTest/kotlin/androidx/savedstate/ParcelableSavedStateTest.android.kt
@@ -64,6 +64,14 @@
}
@Test
+ fun getParcelableOrElse_whenSet_differentType_returnsDefault() {
+ val underTest = savedState { putInt(KEY_1, Int.MAX_VALUE) }
+ val actual = underTest.read { getParcelableOrElse(KEY_1) { PARCELABLE_VALUE_1 } }
+
+ assertThat(actual).isEqualTo(PARCELABLE_VALUE_1)
+ }
+
+ @Test
fun getParcelableList_whenSet_returns() {
val expected = List(size = 5) { idx -> TestParcelable(idx) }
@@ -81,7 +89,7 @@
}
@Test
- fun getListofParcelable_whenSet_differentType_throws() {
+ fun getList_whenSet_differentType_throws() {
val underTest = savedState { putInt(KEY_1, Int.MAX_VALUE) }
assertThrows<IllegalStateException> {
@@ -108,6 +116,14 @@
assertThat(actual).isEqualTo(emptyList<TestParcelable>())
}
+ @Test
+ fun getListOrElse_whenSet_differentType_throws() {
+ val underTest = savedState { putInt(KEY_1, Int.MAX_VALUE) }
+ val actual = underTest.read { getParcelableListOrElse(KEY_1) { emptyList() } }
+
+ assertThat(actual).isEqualTo(emptyList<Parcelable>())
+ }
+
private companion object {
const val KEY_1 = "KEY_1"
val PARCELABLE_VALUE_1 = TestParcelable(value = Int.MIN_VALUE)
diff --git a/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/SavedState.kt b/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/SavedState.kt
index 710bdd0..dbfb175 100644
--- a/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/SavedState.kt
+++ b/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/SavedState.kt
@@ -31,8 +31,24 @@
*/
public expect class SavedState
-/** Constructs an empty [SavedState] instance. */
-public expect inline fun savedState(block: SavedStateWriter.() -> Unit = {}): SavedState
+/**
+ * Builds a new [SavedState] with the specified [initialState], given as a [Map] of [String] keys
+ * and [Any] value.
+ *
+ * Allows further modification of the state using the [builderAction].
+ *
+ * **IMPORTANT:** The [SavedStateWriter] passed as a receiver to the [builderAction] is valid only
+ * inside that function. Using it outside of the function may produce an unspecified behavior.
+ *
+ * @param initialState An initial map of key-value pairs to populate the state. Defaults to an empty
+ * map.
+ * @param builderAction A lambda function with a [SavedStateWriter] receiver to modify the state.
+ * @return A [SavedState] instance containing the initialized key-value pairs.
+ */
+public expect inline fun savedState(
+ initialState: Map<String, Any> = emptyMap(),
+ builderAction: SavedStateWriter.() -> Unit = {},
+): SavedState
/** Creates a new [SavedStateReader] for the [SavedState]. */
public fun SavedState.reader(): SavedStateReader = SavedStateReader(source = this)
diff --git a/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/SavedStateReader.kt b/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/SavedStateReader.kt
index e3f37ac..611d5d8 100644
--- a/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/SavedStateReader.kt
+++ b/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/SavedStateReader.kt
@@ -245,4 +245,16 @@
* @return `true` if the [SavedState] contains the [key], `false` otherwise.
*/
public inline operator fun contains(key: String): Boolean
+
+ /**
+ * Checks if the two specified [SavedState] are *deeply* equal to one another.
+ *
+ * Two [SavedState] are considered deeply equal if they have the same size, and elements at
+ * corresponding keys are deeply equal. That is, if two corresponding elements are nested
+ * [SavedState], they are also compared deeply.
+ *
+ * @param other the object to compare deeply with this.
+ * @return `true` if the two are deeply equal, `false` otherwise.
+ */
+ public fun contentDeepEquals(other: SavedState): Boolean
}
diff --git a/savedstate/savedstate/src/commonTest/kotlin/androidx/savedstate/SavedStateTest.kt b/savedstate/savedstate/src/commonTest/kotlin/androidx/savedstate/SavedStateTest.kt
index b40f557..f7d2380 100644
--- a/savedstate/savedstate/src/commonTest/kotlin/androidx/savedstate/SavedStateTest.kt
+++ b/savedstate/savedstate/src/commonTest/kotlin/androidx/savedstate/SavedStateTest.kt
@@ -95,6 +95,89 @@
assertThat(underTest.read { isEmpty() }).isTrue()
}
+ @Test
+ fun contentDeepEquals_withEqualContent_returnsTrue() {
+ val sharedState = savedState {
+ putInt(KEY_1, Int.MAX_VALUE)
+ putInt(KEY_2, Int.MAX_VALUE)
+ }
+ val state1 = savedState {
+ putInt(KEY_1, Int.MAX_VALUE)
+ putInt(KEY_2, Int.MAX_VALUE)
+ putSavedState(KEY_3, sharedState)
+ }
+ val state2 = savedState {
+ putInt(KEY_1, Int.MAX_VALUE)
+ putInt(KEY_2, Int.MAX_VALUE)
+ putSavedState(KEY_3, sharedState)
+ }
+
+ val contentDeepEquals = state1.read { contentDeepEquals(state2) }
+
+ assertThat(contentDeepEquals).isTrue()
+ }
+
+ @Test
+ fun contentDeepEquals_withMissingKey_returnsFalse() {
+ val sharedState = savedState {
+ putInt(KEY_1, Int.MAX_VALUE)
+ putInt(KEY_2, Int.MAX_VALUE)
+ }
+ val state1 = savedState {
+ putInt(KEY_1, Int.MAX_VALUE)
+ putInt(KEY_2, Int.MAX_VALUE)
+ putSavedState(KEY_3, sharedState)
+ }
+ val state2 = savedState {
+ putInt(KEY_1, Int.MAX_VALUE)
+ putSavedState(KEY_3, sharedState)
+ }
+
+ val contentDeepEquals = state1.read { contentDeepEquals(state2) }
+
+ assertThat(contentDeepEquals).isFalse()
+ }
+
+ @Test
+ fun contentDeepEquals_withDifferentContent_returnsFalse() {
+ val sharedState = savedState {
+ putInt(KEY_1, Int.MAX_VALUE)
+ putInt(KEY_2, Int.MAX_VALUE)
+ }
+ val state1 = savedState {
+ putInt(KEY_1, Int.MAX_VALUE)
+ putInt(KEY_2, Int.MAX_VALUE)
+ putSavedState(KEY_3, sharedState)
+ }
+ val state2 = savedState {
+ putFloat(KEY_1, Float.MAX_VALUE)
+ putFloat(KEY_2, Float.MAX_VALUE)
+ putSavedState(KEY_3, sharedState)
+ }
+
+ val contentDeepEquals = state1.read { contentDeepEquals(state2) }
+
+ assertThat(contentDeepEquals).isFalse()
+ }
+
+ @Test
+ fun contentDeepEquals_withEmptyContent_returnsFalse() {
+ val sharedState = savedState {
+ putInt(KEY_1, Int.MAX_VALUE)
+ putInt(KEY_2, Int.MAX_VALUE)
+ }
+ val state1 = savedState {
+ putInt(KEY_1, Int.MAX_VALUE)
+ putInt(KEY_2, Int.MAX_VALUE)
+ putSavedState(KEY_3, sharedState)
+ }
+ val state2 = savedState()
+
+ val contentDeepEquals = state1.read { contentDeepEquals(state2) }
+
+ assertThat(contentDeepEquals).isFalse()
+ }
+
// region getters and setters
@Test
fun getBoolean_whenSet_returns() {
@@ -131,9 +214,17 @@
@Test
fun getBooleanOrElse_whenNotSet_returnsElse() {
- val actual = savedState().read { getBooleanOrElse(KEY_1) { false } }
+ val actual = savedState().read { getBooleanOrElse(KEY_1) { true } }
- assertThat(actual).isFalse()
+ assertThat(actual).isTrue()
+ }
+
+ @Test
+ fun getBooleanOrElse_whenSet_differentType_returnsElse() {
+ val underTest = savedState { putInt(KEY_1, Int.MAX_VALUE) }
+ val actual = underTest.read { getBooleanOrElse(KEY_1) { true } }
+
+ assertThat(actual).isTrue()
}
@Test
@@ -173,6 +264,14 @@
}
@Test
+ fun getDoubleOrElse_whenSet_differentType_returnsElse() {
+ val underTest = savedState { putInt(KEY_1, Int.MAX_VALUE) }
+ val actual = underTest.read { getDoubleOrElse(KEY_1) { Double.MIN_VALUE } }
+
+ assertThat(actual).isEqualTo(Double.MIN_VALUE)
+ }
+
+ @Test
fun getFloat_whenSet_returns() {
val underTest = savedState { putFloat(KEY_1, Float.MAX_VALUE) }
val actual = underTest.read { getFloat(KEY_1) }
@@ -209,6 +308,14 @@
}
@Test
+ fun getFloatOrElse_whenSet_differentType_returnsElse() {
+ val underTest = savedState { putInt(KEY_1, Int.MAX_VALUE) }
+ val actual = underTest.read { getFloatOrElse(KEY_1) { Float.MIN_VALUE } }
+
+ assertThat(actual).isEqualTo(Float.MIN_VALUE)
+ }
+
+ @Test
fun getInt_whenSet_returns() {
val underTest = savedState { putInt(KEY_1, Int.MAX_VALUE) }
val actual = underTest.read { getInt(KEY_1) }
@@ -245,6 +352,14 @@
}
@Test
+ fun getIntOrElse_whenSet_differentType_returnsElse() {
+ val underTest = savedState { putBoolean(KEY_1, false) }
+ val actual = underTest.read { getIntOrElse(KEY_1) { Int.MIN_VALUE } }
+
+ assertThat(actual).isEqualTo(Int.MIN_VALUE)
+ }
+
+ @Test
fun getLong_whenSet_returns() {
val underTest = savedState { putLong(KEY_1, Long.MAX_VALUE) }
val actual = underTest.read { getLong(KEY_1) }
@@ -281,6 +396,14 @@
}
@Test
+ fun getLongOrElse_whenSet_differentType_returnsElse() {
+ val underTest = savedState { putBoolean(KEY_1, false) }
+ val actual = underTest.read { getLongOrElse(KEY_1) { Long.MIN_VALUE } }
+
+ assertThat(actual).isEqualTo(Long.MIN_VALUE)
+ }
+
+ @Test
fun getString_whenSet_returns() {
val underTest = savedState { putString(KEY_1, STRING_VALUE) }
val actual = underTest.read { getString(KEY_1) }
@@ -316,6 +439,15 @@
}
@Test
+ fun getStringOrElse_whenSet_differentType_returnsElse() {
+ val underTest = savedState { putInt(KEY_1, Int.MAX_VALUE) }
+
+ val actual = underTest.read { getStringOrElse(KEY_1) { STRING_VALUE } }
+
+ assertThat(actual).isEqualTo(STRING_VALUE)
+ }
+
+ @Test
fun getIntList_whenSet_returns() {
val expected = List(size = 5) { idx -> idx }
@@ -355,6 +487,16 @@
}
@Test
+ fun getIntOrElseList_whenSet_differentType_returnsElse() {
+ val expected = Int.MAX_VALUE
+
+ val underTest = savedState { putInt(KEY_1, expected) }
+ val actual = underTest.read { getIntListOrElse(KEY_1) { emptyList() } }
+
+ assertThat(actual).isEqualTo(emptyList<Int>())
+ }
+
+ @Test
fun getStringList_whenSet_returns() {
val underTest = savedState { putStringList(KEY_1, LIST_STRING_VALUE) }
val actual = underTest.read { getStringList(KEY_1) }
@@ -392,6 +534,16 @@
}
@Test
+ fun getStringListOrElse_whenSet_differentType_returnsElse() {
+ val expected = Int.MAX_VALUE
+
+ val underTest = savedState { putInt(KEY_1, expected) }
+ val actual = underTest.read { getStringListOrElse(KEY_1) { emptyList() } }
+
+ assertThat(actual).isEqualTo(emptyList<String>())
+ }
+
+ @Test
fun getSavedState_whenSet_returns() {
val underTest = savedState { putSavedState(KEY_1, SAVED_STATE_VALUE) }
val actual = underTest.read { getSavedState(KEY_1) }
@@ -405,6 +557,13 @@
}
@Test
+ fun getSavedState_whenSet_differentType_throws() {
+ val underTest = savedState { putInt(KEY_1, Int.MAX_VALUE) }
+
+ assertThrows<IllegalStateException> { underTest.read { getSavedState(KEY_1) } }
+ }
+
+ @Test
fun getSavedStateOrElse_whenSet_returns() {
val underTest = savedState { putSavedState(KEY_1, SAVED_STATE_VALUE) }
val actual = underTest.read { getSavedStateOrElse(KEY_1) { savedState() } }
@@ -422,6 +581,16 @@
}
@Test
+ fun getSavedStateOrElse_whenSet_differentType_returnsElse() {
+ val expected = savedState()
+
+ val underTest = savedState { putInt(KEY_1, Int.MAX_VALUE) }
+ val actual = underTest.read { getSavedStateOrElse(KEY_1) { expected } }
+
+ assertThat(actual).isEqualTo(expected)
+ }
+
+ @Test
fun putAll() {
val previousState = savedState { putInt(KEY_1, Int.MAX_VALUE) }
@@ -436,6 +605,7 @@
private companion object {
const val KEY_1 = "KEY_1"
const val KEY_2 = "KEY_2"
+ const val KEY_3 = "KEY_3"
const val STRING_VALUE = "string-value"
val LIST_INT_VALUE = List(size = 5) { idx -> idx }
val LIST_STRING_VALUE = List(size = 5) { idx -> "index=$idx" }
diff --git a/savedstate/savedstate/src/nonAndroidMain/kotlin/androidx/savedstate/SavedState.nonAndroid.kt b/savedstate/savedstate/src/nonAndroidMain/kotlin/androidx/savedstate/SavedState.nonAndroid.kt
index 127e516..78417b8 100644
--- a/savedstate/savedstate/src/nonAndroidMain/kotlin/androidx/savedstate/SavedState.nonAndroid.kt
+++ b/savedstate/savedstate/src/nonAndroidMain/kotlin/androidx/savedstate/SavedState.nonAndroid.kt
@@ -14,11 +14,15 @@
* limitations under the License.
*/
+@file:Suppress("NOTHING_TO_INLINE")
+
package androidx.savedstate
public actual class SavedState
@PublishedApi
internal constructor(@PublishedApi internal val map: MutableMap<String, Any> = mutableMapOf())
-actual inline fun savedState(block: SavedStateWriter.() -> Unit): SavedState =
- SavedState().apply { write(block) }
+actual inline fun savedState(
+ initialState: Map<String, Any>,
+ builderAction: SavedStateWriter.() -> Unit,
+): SavedState = SavedState(initialState.toMutableMap()).apply { write(builderAction) }
diff --git a/savedstate/savedstate/src/nonAndroidMain/kotlin/androidx/savedstate/SavedStateReader.nonAndroid.kt b/savedstate/savedstate/src/nonAndroidMain/kotlin/androidx/savedstate/SavedStateReader.nonAndroid.kt
index 01e3cd9..cec009a 100644
--- a/savedstate/savedstate/src/nonAndroidMain/kotlin/androidx/savedstate/SavedStateReader.nonAndroid.kt
+++ b/savedstate/savedstate/src/nonAndroidMain/kotlin/androidx/savedstate/SavedStateReader.nonAndroid.kt
@@ -31,41 +31,31 @@
}
actual inline fun getBooleanOrElse(key: String, defaultValue: () -> Boolean): Boolean =
- getSingleResultOrElse(key, defaultValue) {
- source.map[key] as? Boolean ?: SavedStateUtils.DEFAULT_BOOLEAN
- }
+ getSingleResultOrElse(key, defaultValue) { source.map[key] as? Boolean }
actual inline fun getDouble(key: String): Double =
getSingleResultOrThrow(key) { source.map[key] as? Double ?: SavedStateUtils.DEFAULT_DOUBLE }
actual inline fun getDoubleOrElse(key: String, defaultValue: () -> Double): Double =
- getSingleResultOrElse(key, defaultValue) {
- source.map[key] as? Double ?: SavedStateUtils.DEFAULT_DOUBLE
- }
+ getSingleResultOrElse(key, defaultValue) { source.map[key] as? Double }
actual inline fun getFloat(key: String): Float =
getSingleResultOrThrow(key) { source.map[key] as? Float ?: SavedStateUtils.DEFAULT_FLOAT }
actual inline fun getFloatOrElse(key: String, defaultValue: () -> Float): Float =
- getSingleResultOrElse(key, defaultValue) {
- source.map[key] as? Float ?: SavedStateUtils.DEFAULT_FLOAT
- }
+ getSingleResultOrElse(key, defaultValue) { source.map[key] as? Float }
actual inline fun getInt(key: String): Int =
getSingleResultOrThrow(key) { source.map[key] as? Int ?: SavedStateUtils.DEFAULT_INT }
actual inline fun getIntOrElse(key: String, defaultValue: () -> Int): Int =
- getSingleResultOrElse(key, defaultValue) {
- source.map[key] as? Int ?: SavedStateUtils.DEFAULT_INT
- }
+ getSingleResultOrElse(key, defaultValue) { source.map[key] as? Int }
actual inline fun getLong(key: String): Long =
getSingleResultOrThrow(key) { source.map[key] as? Long ?: SavedStateUtils.DEFAULT_LONG }
actual inline fun getLongOrElse(key: String, defaultValue: () -> Long): Long =
- getSingleResultOrElse(key, defaultValue) {
- source.map[key] as? Long ?: SavedStateUtils.DEFAULT_LONG
- }
+ getSingleResultOrElse(key, defaultValue) { source.map[key] as? Long }
actual inline fun getString(key: String): String =
getSingleResultOrThrow(key) { source.map[key] as? String }
@@ -163,4 +153,9 @@
currentValue = { currentValue() },
defaultValue = { defaultValue() },
)
+
+ actual fun contentDeepEquals(other: SavedState): Boolean {
+ // Map implements `equals` as a content deep, there is no need to do anything else.
+ return source.map == other.map
+ }
}
diff --git a/security/security-state/api/current.txt b/security/security-state/api/current.txt
index 4424adf..7f6c944 100644
--- a/security/security-state/api/current.txt
+++ b/security/security-state/api/current.txt
@@ -19,7 +19,6 @@
field public static final String COMPONENT_KERNEL = "KERNEL";
field public static final String COMPONENT_SYSTEM = "SYSTEM";
field public static final String COMPONENT_SYSTEM_MODULES = "SYSTEM_MODULES";
- field public static final String COMPONENT_VENDOR = "VENDOR";
field public static final androidx.security.state.SecurityPatchState.Companion Companion;
field public static final java.util.List<java.lang.String> DEFAULT_SYSTEM_MODULES;
field public static final String DEFAULT_VULNERABILITY_REPORTS_URL = "https://storage.googleapis.com/osv-android-api";
diff --git a/security/security-state/api/restricted_current.txt b/security/security-state/api/restricted_current.txt
index 4424adf..7f6c944 100644
--- a/security/security-state/api/restricted_current.txt
+++ b/security/security-state/api/restricted_current.txt
@@ -19,7 +19,6 @@
field public static final String COMPONENT_KERNEL = "KERNEL";
field public static final String COMPONENT_SYSTEM = "SYSTEM";
field public static final String COMPONENT_SYSTEM_MODULES = "SYSTEM_MODULES";
- field public static final String COMPONENT_VENDOR = "VENDOR";
field public static final androidx.security.state.SecurityPatchState.Companion Companion;
field public static final java.util.List<java.lang.String> DEFAULT_SYSTEM_MODULES;
field public static final String DEFAULT_VULNERABILITY_REPORTS_URL = "https://storage.googleapis.com/osv-android-api";
diff --git a/security/security-state/src/androidTest/java/androidx/security/state/SecurityStateManagerTest.kt b/security/security-state/src/androidTest/java/androidx/security/state/SecurityStateManagerTest.kt
index d0852df..73796ac 100644
--- a/security/security-state/src/androidTest/java/androidx/security/state/SecurityStateManagerTest.kt
+++ b/security/security-state/src/androidTest/java/androidx/security/state/SecurityStateManagerTest.kt
@@ -86,7 +86,6 @@
fun testGetGlobalSecurityState_sdkAbove29() {
val bundle = securityStateManager.getGlobalSecurityState()
assertTrue(matchesDateFormat(bundle.getString("system_spl")!!))
- assertTrue(matchesDateFormat(bundle.getString("vendor_spl")!!))
assertTrue(matchesKernelFormat(bundle.getString("kernel_version")!!))
assertTrue(containsModuleMetadataPackage(bundle))
assertTrue(containsWebViewPackage(bundle))
@@ -97,7 +96,6 @@
fun testGetGlobalSecurityState_sdkAbove25Below29_doesNotContainModuleMetadata() {
val bundle = securityStateManager.getGlobalSecurityState()
assertTrue(matchesDateFormat(bundle.getString("system_spl")!!))
- assertTrue(matchesDateFormat(bundle.getString("vendor_spl")!!))
assertTrue(matchesKernelFormat(bundle.getString("kernel_version")!!))
assertTrue(containsWebViewPackage(bundle))
assertFalse(containsModuleMetadataPackage(bundle))
@@ -108,7 +106,6 @@
fun testGetGlobalSecurityState_sdkAbove22Below26_doesNotContainModuleMetadataOrWebView() {
val bundle = securityStateManager.getGlobalSecurityState()
assertTrue(matchesDateFormat(bundle.getString("system_spl")!!))
- assertTrue(matchesDateFormat(bundle.getString("vendor_spl")!!))
assertTrue(matchesKernelFormat(bundle.getString("kernel_version")!!))
assertFalse(containsModuleMetadataPackage(bundle))
assertFalse(containsWebViewPackage(bundle))
@@ -127,4 +124,20 @@
assertFalse(containsModuleMetadataPackage(bundle))
assertFalse(containsWebViewPackage(bundle))
}
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.M)
+ @Test
+ fun testGetGlobalSecurityState_whenVendorIsEnabled_containsVendorSpl() {
+ SecurityPatchState.Companion.USE_VENDOR_SPL = true
+ val bundle = securityStateManager.getGlobalSecurityState()
+ assertTrue(bundle.containsKey("vendor_spl"))
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.M)
+ @Test
+ fun testGetGlobalSecurityState_whenVendorIsDisabled_doesNotContainVendorSpl() {
+ SecurityPatchState.Companion.USE_VENDOR_SPL = false
+ val bundle = securityStateManager.getGlobalSecurityState()
+ assertFalse(bundle.containsKey("vendor_spl"))
+ }
}
diff --git a/security/security-state/src/main/java/androidx/security/state/SecurityPatchState.kt b/security/security-state/src/main/java/androidx/security/state/SecurityPatchState.kt
index 07bb133..bc317cf 100644
--- a/security/security-state/src/main/java/androidx/security/state/SecurityPatchState.kt
+++ b/security/security-state/src/main/java/androidx/security/state/SecurityPatchState.kt
@@ -91,16 +91,19 @@
/** System modules component providing DateBasedSpl of system modules patch level. */
public const val COMPONENT_SYSTEM_MODULES: String = "SYSTEM_MODULES"
- /**
- * Vendor component providing ro.vendor.build.security_patch property value as DateBasedSpl.
- */
- public const val COMPONENT_VENDOR: String = "VENDOR"
-
/** Kernel component providing kernel version as VersionedSpl. */
public const val COMPONENT_KERNEL: String = "KERNEL"
/** WebView component providing default WebView provider version as VersionedSpl. */
internal const val COMPONENT_WEBVIEW: String = "WEBVIEW"
+
+ /**
+ * Vendor component providing ro.vendor.build.security_patch property value as DateBasedSpl.
+ */
+ internal const val COMPONENT_VENDOR: String = "VENDOR"
+
+ /** Disabled until Android provides sufficient guidelines for the usage of Vendor SPL. */
+ internal var USE_VENDOR_SPL = false
}
/** Annotation for defining the component to use. */
@@ -598,9 +601,13 @@
COMPONENT_SYSTEM_MODULES -> listOf(getSystemModulesPublishedSecurityPatchLevel())
COMPONENT_SYSTEM,
COMPONENT_VENDOR -> {
+ val exception = IllegalStateException("SPL data not available: $component")
+ if (component == COMPONENT_VENDOR && !USE_VENDOR_SPL) {
+ throw exception
+ }
listOf(
getMaxComponentSecurityPatchLevel(componentToString(component))
- ?: throw IllegalStateException("SPL data not available.")
+ ?: throw exception
)
}
COMPONENT_KERNEL -> getPublishedKernelVersions()
@@ -691,8 +698,16 @@
spl: SecurityPatchLevel
): Map<Severity, Set<String>> {
// Check if the component is valid for this operation
- if (component !in listOf(COMPONENT_SYSTEM, COMPONENT_VENDOR, COMPONENT_SYSTEM_MODULES)) {
- throw IllegalArgumentException("Component must be SYSTEM, VENDOR, or SYSTEM_MODULES")
+ val validComponents =
+ listOfNotNull(
+ COMPONENT_SYSTEM,
+ if (USE_VENDOR_SPL) COMPONENT_VENDOR else null,
+ COMPONENT_SYSTEM_MODULES
+ )
+ if (component !in validComponents) {
+ throw IllegalArgumentException(
+ "Component must be one of $validComponents but was $component"
+ )
}
checkVulnerabilityReport()
@@ -736,10 +751,14 @@
@Component component: String,
securityPatchLevel: String
): SecurityPatchLevel {
+ val exception = IllegalArgumentException("Unknown component: $component")
return when (component) {
COMPONENT_SYSTEM,
COMPONENT_SYSTEM_MODULES,
COMPONENT_VENDOR -> {
+ if (component == COMPONENT_VENDOR && !USE_VENDOR_SPL) {
+ throw exception
+ }
// These components are expected to use DateBasedSpl
DateBasedSecurityPatchLevel.fromString(securityPatchLevel)
}
@@ -748,7 +767,7 @@
// These components are expected to use VersionedSpl
VersionedSecurityPatchLevel.fromString(securityPatchLevel)
}
- else -> throw IllegalArgumentException("Unknown component: $component")
+ else -> throw exception
}
}
@@ -826,6 +845,7 @@
)
components.forEach { component ->
+ if (component == COMPONENT_VENDOR && !USE_VENDOR_SPL) return@forEach
// TODO(musashi): Unblock once support for WebView is present.
if (component == COMPONENT_WEBVIEW) return@forEach
val deviceSpl =
@@ -877,7 +897,12 @@
* @return true if all provided CVEs are patched, false otherwise.
*/
public fun areCvesPatched(cveList: List<String>): Boolean {
- val componentsToCheck = listOf(COMPONENT_SYSTEM, COMPONENT_VENDOR, COMPONENT_SYSTEM_MODULES)
+ val componentsToCheck =
+ listOfNotNull(
+ COMPONENT_SYSTEM,
+ if (USE_VENDOR_SPL) COMPONENT_VENDOR else null,
+ COMPONENT_SYSTEM_MODULES
+ )
val allPatchedCves = mutableSetOf<String>()
// Aggregate all CVEs from security fixes across necessary components
diff --git a/security/security-state/src/main/java/androidx/security/state/SecurityStateManager.kt b/security/security-state/src/main/java/androidx/security/state/SecurityStateManager.kt
index 9329c9b..2676d6c 100644
--- a/security/security-state/src/main/java/androidx/security/state/SecurityStateManager.kt
+++ b/security/security-state/src/main/java/androidx/security/state/SecurityStateManager.kt
@@ -23,6 +23,7 @@
import android.os.Bundle
import android.system.Os
import androidx.annotation.RequiresApi
+import androidx.security.state.SecurityPatchState.Companion.USE_VENDOR_SPL
import androidx.webkit.WebViewCompat
import java.util.regex.Pattern
@@ -87,13 +88,8 @@
return Bundle().apply {
if (getAndroidSdkInt() >= Build.VERSION_CODES.M) {
putString(KEY_SYSTEM_SPL, Build.VERSION.SECURITY_PATCH)
-
- val vendorSpl = getVendorSpl()
- if (vendorSpl.isNotEmpty()) {
- putString(KEY_VENDOR_SPL, vendorSpl)
- } else {
- // Assume vendor SPL == system SPL
- putString(KEY_VENDOR_SPL, Build.VERSION.SECURITY_PATCH)
+ if (USE_VENDOR_SPL) {
+ putString(KEY_VENDOR_SPL, getVendorSpl())
}
}
if (getAndroidSdkInt() >= Build.VERSION_CODES.Q) {
@@ -128,13 +124,8 @@
context.getSystemService(Context.SECURITY_STATE_SERVICE)
as android.os.SecurityStateManager
val globalSecurityState = securityStateManagerService.globalSecurityState
- val vendorSpl = globalSecurityState.getString(KEY_VENDOR_SPL, "")
- if (vendorSpl.isEmpty()) {
- // Assume vendor SPL == system SPL
- globalSecurityState.putString(
- KEY_VENDOR_SPL,
- globalSecurityState.getString(KEY_SYSTEM_SPL)
- )
+ if (!USE_VENDOR_SPL) {
+ globalSecurityState.remove(KEY_VENDOR_SPL)
}
return globalSecurityState
}
diff --git a/security/security-state/src/test/java/androidx/security/state/SecurityPatchStateTest.kt b/security/security-state/src/test/java/androidx/security/state/SecurityPatchStateTest.kt
index 29177c4..6e22fe3 100644
--- a/security/security-state/src/test/java/androidx/security/state/SecurityPatchStateTest.kt
+++ b/security/security-state/src/test/java/androidx/security/state/SecurityPatchStateTest.kt
@@ -120,7 +120,7 @@
}
@Test
- fun testGetSecurityPatchLevelWithDateBasedComponent() {
+ fun testGetComponentSecurityPatchLevel_withSystemComponent_returnsDateBasedSpl() {
val spl =
securityState.getComponentSecurityPatchLevel(
SecurityPatchState.COMPONENT_SYSTEM,
@@ -131,7 +131,29 @@
}
@Test
- fun testGetSecurityPatchLevelWithVersionedComponent() {
+ fun testGetComponentSecurityPatchLevel_withVendorComponent_whenVendorIsEnabled_returnsDateBasedSpl() {
+ SecurityPatchState.Companion.USE_VENDOR_SPL = true
+ val spl =
+ securityState.getComponentSecurityPatchLevel(
+ SecurityPatchState.COMPONENT_VENDOR,
+ "2022-01-01"
+ )
+ assertTrue(spl is SecurityPatchState.DateBasedSecurityPatchLevel)
+ assertEquals("2022-01-01", spl.toString())
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun testGetComponentSecurityPatchLevel_withVendorComponent_whenVendorIsDisabled_throwsException() {
+ SecurityPatchState.Companion.USE_VENDOR_SPL = false
+
+ securityState.getComponentSecurityPatchLevel(
+ SecurityPatchState.COMPONENT_VENDOR,
+ "2022-01-01"
+ )
+ }
+
+ @Test
+ fun testGetComponentSecurityPatchLevel_withKernelComponent_returnsVersionedSpl() {
val spl =
securityState.getComponentSecurityPatchLevel(
SecurityPatchState.COMPONENT_KERNEL,
@@ -142,7 +164,7 @@
}
@Test(expected = IllegalArgumentException::class)
- fun testGetSecurityPatchLevelWithInvalidDateBasedInput() {
+ fun testGetComponentSecurityPatchLevel_withInvalidDateBasedInput_throwsException() {
securityState.getComponentSecurityPatchLevel(
SecurityPatchState.COMPONENT_SYSTEM,
"invalid-date"
@@ -150,7 +172,7 @@
}
@Test(expected = IllegalArgumentException::class)
- fun testGetSecurityPatchLevelWithInvalidVersionedInput() {
+ fun testGetComponentSecurityPatchLevel_withInvalidVersionedInput_throwsException() {
securityState.getComponentSecurityPatchLevel(
SecurityPatchState.COMPONENT_KERNEL,
"invalid-version"
@@ -191,16 +213,16 @@
"""
securityState.loadVulnerabilityReport(jsonString)
- val fixes =
+ val cves =
securityState.getPatchedCves(
SecurityPatchState.COMPONENT_SYSTEM,
SecurityPatchState.DateBasedSecurityPatchLevel(2022, 1, 1)
)
- assertEquals(1, fixes[SecurityPatchState.Severity.HIGH]?.size)
- assertEquals(1, fixes[SecurityPatchState.Severity.MODERATE]?.size)
- assertEquals(setOf("CVE-2020-1234"), fixes[SecurityPatchState.Severity.HIGH])
- assertEquals(setOf("CVE-2020-5678"), fixes[SecurityPatchState.Severity.MODERATE])
+ assertEquals(1, cves[SecurityPatchState.Severity.HIGH]?.size)
+ assertEquals(1, cves[SecurityPatchState.Severity.MODERATE]?.size)
+ assertEquals(setOf("CVE-2020-1234"), cves[SecurityPatchState.Severity.HIGH])
+ assertEquals(setOf("CVE-2020-5678"), cves[SecurityPatchState.Severity.MODERATE])
}
@Test(expected = IllegalArgumentException::class)
@@ -358,7 +380,8 @@
}
@Test
- fun testGetPublishedSpl_ReturnsCorrectSplForVendor() {
+ fun testGetPublishedSpl_withVendorComponent_whenVendorIsEnabled_returnsCorrectSpl() {
+ SecurityPatchState.Companion.USE_VENDOR_SPL = true
val jsonInput =
"""
{
@@ -386,6 +409,29 @@
assertEquals(15, spl.getDay())
}
+ @Test(expected = IllegalStateException::class)
+ fun testGetPublishedSpl_withVendorComponent_whenVendorIsDisabled_throwsException() {
+ SecurityPatchState.Companion.USE_VENDOR_SPL = false
+ val jsonInput =
+ """
+ {
+ "vulnerabilities": {
+ "2023-05-15": [{
+ "cve_identifiers": ["CVE-5678-1234"],
+ "asb_identifiers": ["ASB-A-2024222"],
+ "severity": "critical",
+ "components": ["vendor"]
+ }]
+ },
+ "kernel_lts_versions": {}
+ }
+ """
+ .trimIndent()
+
+ securityState.loadVulnerabilityReport(jsonInput)
+ securityState.getPublishedSecurityPatchLevel(SecurityPatchState.COMPONENT_VENDOR)
+ }
+
@Test
fun testGetPublishedKernelVersions_ReturnsCorrectVersions_DifferentVersionsSameSpl() {
val jsonInput =
@@ -558,28 +604,28 @@
}
@Test
- fun testGetSecurityFixes_ReturnsNoFixes() {
- securityState.loadVulnerabilityReport(generateMockReport("vendor", "2023-01-01"))
+ fun testGetPatchedCves_ReturnsNoCves() {
+ securityState.loadVulnerabilityReport(generateMockReport("system", "2023-01-01"))
val spl = SecurityPatchState.DateBasedSecurityPatchLevel.fromString("2023-01-01")
- val fixes = securityState.getPatchedCves(SecurityPatchState.COMPONENT_SYSTEM, spl)
+ val cves = securityState.getPatchedCves(SecurityPatchState.COMPONENT_SYSTEM_MODULES, spl)
- assertEquals(null, fixes[SecurityPatchState.Severity.CRITICAL])
- assertEquals(null, fixes[SecurityPatchState.Severity.HIGH])
- assertEquals(null, fixes[SecurityPatchState.Severity.MODERATE])
- assertEquals(null, fixes[SecurityPatchState.Severity.LOW])
+ assertEquals(null, cves[SecurityPatchState.Severity.CRITICAL])
+ assertEquals(null, cves[SecurityPatchState.Severity.HIGH])
+ assertEquals(null, cves[SecurityPatchState.Severity.MODERATE])
+ assertEquals(null, cves[SecurityPatchState.Severity.LOW])
val spl2 = SecurityPatchState.DateBasedSecurityPatchLevel.fromString("2022-01-01")
- val fixes2 = securityState.getPatchedCves(SecurityPatchState.COMPONENT_VENDOR, spl2)
+ val cves2 = securityState.getPatchedCves(SecurityPatchState.COMPONENT_SYSTEM, spl2)
- assertEquals(null, fixes2[SecurityPatchState.Severity.CRITICAL])
- assertEquals(null, fixes2[SecurityPatchState.Severity.HIGH])
- assertEquals(null, fixes2[SecurityPatchState.Severity.MODERATE])
- assertEquals(null, fixes2[SecurityPatchState.Severity.LOW])
+ assertEquals(null, cves2[SecurityPatchState.Severity.CRITICAL])
+ assertEquals(null, cves2[SecurityPatchState.Severity.HIGH])
+ assertEquals(null, cves2[SecurityPatchState.Severity.MODERATE])
+ assertEquals(null, cves2[SecurityPatchState.Severity.LOW])
}
@Test
- fun testGetSecurityFixes_ReturnsCorrectFixesCategorizedBySeverity() {
+ fun testGetPatchedCves_withSystemComponent_returnsCorrectCvesCategorizedBySeverity() {
val spl = SecurityPatchState.DateBasedSecurityPatchLevel.fromString("2023-01-01")
val jsonInput =
"""
@@ -604,19 +650,62 @@
.trimIndent()
securityState.loadVulnerabilityReport(jsonInput)
- val fixes = securityState.getPatchedCves(SecurityPatchState.COMPONENT_SYSTEM, spl)
+ val cves = securityState.getPatchedCves(SecurityPatchState.COMPONENT_SYSTEM, spl)
- assertEquals(2, fixes[SecurityPatchState.Severity.HIGH]?.size)
+ assertEquals(2, cves[SecurityPatchState.Severity.HIGH]?.size)
assertEquals(
setOf("CVE-2023-0001", "CVE-2023-0002"),
- fixes[SecurityPatchState.Severity.HIGH]
+ cves[SecurityPatchState.Severity.HIGH]
)
- assertEquals(null, fixes[SecurityPatchState.Severity.MODERATE])
+ assertEquals(null, cves[SecurityPatchState.Severity.MODERATE])
+ }
+
+ @Test
+ fun testGetPatchedCves_withVendorComponent_whenVendorIsEnabled_returnsCorrectCvesCategorizedBySeverity() {
+ SecurityPatchState.Companion.USE_VENDOR_SPL = true
+ val spl = SecurityPatchState.DateBasedSecurityPatchLevel.fromString("2023-01-15")
+ val jsonInput =
+ """
+ {
+ "vulnerabilities": {
+ "2023-01-01": [{
+ "cve_identifiers": ["CVE-2023-0001", "CVE-2023-0002"],
+ "asb_identifiers": ["ASB-A-2023011"],
+ "severity": "high",
+ "components": ["system"]
+ }],
+ "2023-01-15": [{
+ "cve_identifiers": ["CVE-2023-0010"],
+ "asb_identifiers": ["ASB-A-2023022"],
+ "severity": "moderate",
+ "components": ["vendor"]
+ }]
+ },
+ "kernel_lts_versions": {}
+ }
+ """
+ .trimIndent()
+ securityState.loadVulnerabilityReport(jsonInput)
+
+ val cves = securityState.getPatchedCves(SecurityPatchState.COMPONENT_VENDOR, spl)
+
+ assertEquals(1, cves[SecurityPatchState.Severity.MODERATE]?.size)
+ assertEquals(setOf("CVE-2023-0010"), cves[SecurityPatchState.Severity.MODERATE])
+
+ assertEquals(null, cves[SecurityPatchState.Severity.HIGH])
}
@Test(expected = IllegalArgumentException::class)
- fun testGetSecurityFixes_ThrowsExceptionForInvalidComponent() {
+ fun testGetPatchedCves_withVendorComponent_whenVendorIsDisabled_throwsException() {
+ SecurityPatchState.Companion.USE_VENDOR_SPL = false
+ val spl = SecurityPatchState.DateBasedSecurityPatchLevel.fromString("2022-01-01")
+
+ securityState.getPatchedCves(SecurityPatchState.COMPONENT_VENDOR, spl)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun testGetPatchedCves_ThrowsExceptionForInvalidComponent() {
val spl = SecurityPatchState.DateBasedSecurityPatchLevel.fromString("2023-01-01")
securityState.getPatchedCves(SecurityPatchState.COMPONENT_WEBVIEW, spl)
@@ -769,7 +858,7 @@
}
@Test
- fun testIsDeviceFullyUpdated_returnsTrue() {
+ fun testIsDeviceFullyUpdated_withUpdatedSpl_returnsTrue() {
val bundle = Bundle()
bundle.putString("system_spl", "2023-01-01")
bundle.putString("vendor_spl", "2023-02-01")
@@ -813,7 +902,97 @@
}
@Test
- fun testIsDeviceFullyUpdated_returnsFalse() {
+ fun testIsDeviceFullyUpdated_withOutdatedVendorSpl_whenVendorIsEnabled_returnsFalse() {
+ SecurityPatchState.Companion.USE_VENDOR_SPL = true
+ val bundle = Bundle()
+ bundle.putString("system_spl", "2023-01-01")
+ bundle.putString("vendor_spl", "2020-01-01")
+ bundle.putString("kernel_version", "5.4.123")
+ bundle.putString("com.google.android.modulemetadata", "2023-10-05")
+
+ `when`(mockSecurityStateManager.getGlobalSecurityState(anyString())).thenReturn(bundle)
+ doReturn("2023-10-05").`when`(mockSecurityStateManager).getPackageVersion(anyString())
+
+ val jsonInput =
+ """
+ {
+ "vulnerabilities": {
+ "2023-05-01": [{
+ "cve_identifiers": ["CVE-1234-4321"],
+ "asb_identifiers": ["ASB-A-2023111"],
+ "severity": "high",
+ "components": ["com.google.android.modulemetadata"]
+ }],
+ "2023-01-01": [{
+ "cve_identifiers": ["CVE-1234-1321"],
+ "asb_identifiers": ["ASB-A-2023121"],
+ "severity": "critical",
+ "components": ["system"]
+ }],
+ "2023-02-01": [{
+ "cve_identifiers": ["CVE-1234-3321"],
+ "asb_identifiers": ["ASB-A-2023151"],
+ "severity": "moderate",
+ "components": ["vendor"]
+ }]
+ },
+ "kernel_lts_versions": { "2023-05-01": [ "5.4.123", "6.1.234.25" ] }
+ }
+ """
+ .trimIndent()
+
+ securityState.loadVulnerabilityReport(jsonInput)
+
+ assertFalse(securityState.isDeviceFullyUpdated())
+ }
+
+ @Test
+ fun testIsDeviceFullyUpdated_withOutdatedVendorSpl_whenVendorIsDisabled_returnsTrue() {
+ SecurityPatchState.Companion.USE_VENDOR_SPL = false
+ val bundle = Bundle()
+ bundle.putString("system_spl", "2023-01-01")
+ bundle.putString("vendor_spl", "2020-01-01")
+ bundle.putString("kernel_version", "5.4.123")
+ bundle.putString("com.google.android.modulemetadata", "2023-10-05")
+
+ `when`(mockSecurityStateManager.getGlobalSecurityState(anyString())).thenReturn(bundle)
+ doReturn("2023-10-05").`when`(mockSecurityStateManager).getPackageVersion(anyString())
+
+ val jsonInput =
+ """
+ {
+ "vulnerabilities": {
+ "2023-05-01": [{
+ "cve_identifiers": ["CVE-1234-4321"],
+ "asb_identifiers": ["ASB-A-2023111"],
+ "severity": "high",
+ "components": ["com.google.android.modulemetadata"]
+ }],
+ "2023-01-01": [{
+ "cve_identifiers": ["CVE-1234-1321"],
+ "asb_identifiers": ["ASB-A-2023121"],
+ "severity": "critical",
+ "components": ["system"]
+ }],
+ "2023-02-01": [{
+ "cve_identifiers": ["CVE-1234-3321"],
+ "asb_identifiers": ["ASB-A-2023151"],
+ "severity": "moderate",
+ "components": ["vendor"]
+ }]
+ },
+ "kernel_lts_versions": { "2023-05-01": [ "5.4.123", "6.1.234.25" ] }
+ }
+ """
+ .trimIndent()
+
+ securityState.loadVulnerabilityReport(jsonInput)
+
+ assertTrue(securityState.isDeviceFullyUpdated())
+ }
+
+ @Test
+ fun testIsDeviceFullyUpdated_withOutdatedSpl_returnsFalse() {
val bundle = Bundle()
bundle.putString("system_spl", "2022-01-01")
bundle.putString("com.google.android.modulemetadata", "2023-10-05")
@@ -894,9 +1073,7 @@
`when`(mockSecurityStateManager.getGlobalSecurityState(anyString())).thenReturn(bundle)
doReturn(systemSpl).`when`(mockSecurityStateManager).getPackageVersion(Mockito.anyString())
- assertTrue(
- securityState.areCvesPatched(listOf("CVE-2023-0010", "CVE-2023-0001", "CVE-2023-0002"))
- )
+ assertTrue(securityState.areCvesPatched(listOf("CVE-2023-0001", "CVE-2023-0002")))
}
@Test
@@ -988,4 +1165,92 @@
securityState.areCvesPatched(listOf("CVE-2024-1010", "CVE-2023-0001", "CVE-2023-0002"))
)
}
+
+ @Test
+ fun testAreCvesPatched_withVendorCve_whenVendorIsEnabled_returnsTrue() {
+ SecurityPatchState.Companion.USE_VENDOR_SPL = true
+ val jsonInput =
+ """
+ {
+ "vulnerabilities": {
+ "2021-05-01": [{
+ "cve_identifiers": ["CVE-1234-4321"],
+ "asb_identifiers": ["ASB-A-2023111"],
+ "severity": "high",
+ "components": ["com.google.android.modulemetadata"]
+ }],
+ "2022-01-01": [{
+ "cve_identifiers": ["CVE-2023-0001", "CVE-2023-0002"],
+ "asb_identifiers": ["ASB-A-2023011"],
+ "severity": "high",
+ "components": ["system"]
+ }],
+ "2021-01-15": [{
+ "cve_identifiers": ["CVE-2023-0010"],
+ "asb_identifiers": ["ASB-A-2023022"],
+ "severity": "moderate",
+ "components": ["vendor"]
+ }]
+ },
+ "kernel_lts_versions": {}
+ }
+ """
+ .trimIndent()
+ securityState.loadVulnerabilityReport(jsonInput)
+
+ val systemSpl = "2023-01-01"
+ val bundle = Bundle()
+ bundle.putString("system_spl", systemSpl)
+ bundle.putString("vendor_spl", systemSpl)
+ bundle.putString("com.google.android.modulemetadata", systemSpl)
+
+ `when`(mockSecurityStateManager.getGlobalSecurityState(anyString())).thenReturn(bundle)
+ doReturn(systemSpl).`when`(mockSecurityStateManager).getPackageVersion(Mockito.anyString())
+
+ assertTrue(securityState.areCvesPatched(listOf("CVE-2023-0010")))
+ }
+
+ @Test
+ fun testAreCvesPatched_withVendorCve_whenVendorIsDisabled_returnsFalse() {
+ SecurityPatchState.Companion.USE_VENDOR_SPL = false
+ val jsonInput =
+ """
+ {
+ "vulnerabilities": {
+ "2021-05-01": [{
+ "cve_identifiers": ["CVE-1234-4321"],
+ "asb_identifiers": ["ASB-A-2023111"],
+ "severity": "high",
+ "components": ["com.google.android.modulemetadata"]
+ }],
+ "2022-01-01": [{
+ "cve_identifiers": ["CVE-2023-0001", "CVE-2023-0002"],
+ "asb_identifiers": ["ASB-A-2023011"],
+ "severity": "high",
+ "components": ["system"]
+ }],
+ "2021-01-15": [{
+ "cve_identifiers": ["CVE-2023-0010"],
+ "asb_identifiers": ["ASB-A-2023022"],
+ "severity": "moderate",
+ "components": ["vendor"]
+ }]
+ },
+ "kernel_lts_versions": {}
+ }
+ """
+ .trimIndent()
+ securityState.loadVulnerabilityReport(jsonInput)
+
+ val systemSpl = "2023-01-01"
+ val bundle = Bundle()
+ bundle.putString("system_spl", systemSpl)
+ bundle.putString("vendor_spl", systemSpl)
+ bundle.putString("com.google.android.modulemetadata", systemSpl)
+
+ `when`(mockSecurityStateManager.getGlobalSecurityState(anyString())).thenReturn(bundle)
+ doReturn(systemSpl).`when`(mockSecurityStateManager).getPackageVersion(Mockito.anyString())
+
+ assertFalse(securityState.areCvesPatched(listOf("CVE-2023-0010")))
+ }
}
diff --git a/sqlite/integration-tests/driver-conformance-test/build.gradle b/sqlite/integration-tests/driver-conformance-test/build.gradle
index fd7b912..a2dc3f9 100644
--- a/sqlite/integration-tests/driver-conformance-test/build.gradle
+++ b/sqlite/integration-tests/driver-conformance-test/build.gradle
@@ -26,11 +26,20 @@
plugins {
id("AndroidXPlugin")
- id("com.android.library")
}
androidXMultiplatform {
- android()
+ androidLibrary {
+ namespace = "androidx.sqlite.driver.test"
+ withAndroidTestOnDeviceBuilder {
+ it.compilationName = "instrumentedTest"
+ it.defaultSourceSetName = "androidInstrumentedTest"
+ it.sourceSetTreeName = "test"
+ }
+ withAndroidTestOnJvmBuilder {
+ it.defaultSourceSetName = "androidUnitTest"
+ }
+ }
ios()
jvm()
linux()
@@ -78,9 +87,6 @@
}
}
-android {
- namespace "androidx.sqlite.driver.test"
-}
androidx {
name = "SQLite Driver Coformance Base Tests"
diff --git a/sqlite/sqlite-framework/build.gradle b/sqlite/sqlite-framework/build.gradle
index 411b332f..c9fff30 100644
--- a/sqlite/sqlite-framework/build.gradle
+++ b/sqlite/sqlite-framework/build.gradle
@@ -29,10 +29,8 @@
plugins {
id("AndroidXPlugin")
- id("com.android.library")
}
-
configurations {
// Configuration for resolving shared archive file of androidx's SQLite compilation
sqliteSharedArchive {
@@ -53,7 +51,17 @@
}
androidXMultiplatform {
- android()
+ androidLibrary {
+ namespace = "androidx.sqlite.db.framework"
+ withAndroidTestOnDeviceBuilder {
+ it.compilationName = "instrumentedTest"
+ it.defaultSourceSetName = "androidInstrumentedTest"
+ it.sourceSetTreeName = "test"
+ }
+ withAndroidTestOnJvmBuilder {
+ it.defaultSourceSetName = "androidUnitTest"
+ }
+ }
ios() {
// Link to sqlite3 available in iOS
binaries.configureEach {
@@ -148,6 +156,3 @@
description = "The implementation of SQLite library using the framework code."
}
-android {
- namespace "androidx.sqlite.db.framework"
-}
diff --git a/sqlite/sqlite/build.gradle b/sqlite/sqlite/build.gradle
index 3c4a438..43cf644 100644
--- a/sqlite/sqlite/build.gradle
+++ b/sqlite/sqlite/build.gradle
@@ -28,12 +28,20 @@
plugins {
id("AndroidXPlugin")
- id("com.android.library")
}
-
androidXMultiplatform {
- android()
+ androidLibrary {
+ namespace = "androidx.sqlite.db"
+ withAndroidTestOnDeviceBuilder {
+ it.compilationName = "instrumentedTest"
+ it.defaultSourceSetName = "androidInstrumentedTest"
+ it.sourceSetTreeName = "test"
+ }
+ withAndroidTestOnJvmBuilder {
+ it.defaultSourceSetName = "androidUnitTest"
+ }
+ }
ios()
jvm()
linux()
@@ -81,9 +89,6 @@
}
}
-android {
- namespace "androidx.sqlite.db"
-}
androidx {
name = "SQLite"
diff --git a/testutils/testutils-lifecycle/build.gradle b/testutils/testutils-lifecycle/build.gradle
index 9d088a1..4408879 100644
--- a/testutils/testutils-lifecycle/build.gradle
+++ b/testutils/testutils-lifecycle/build.gradle
@@ -27,11 +27,20 @@
plugins {
id("AndroidXPlugin")
- id("com.android.library")
}
androidXMultiplatform {
- android()
+ androidLibrary {
+ namespace = "androidx.testutils.lifecycle"
+ withAndroidTestOnDeviceBuilder {
+ it.compilationName = "instrumentedTest"
+ it.defaultSourceSetName = "androidInstrumentedTest"
+ it.sourceSetTreeName = "test"
+ }
+ withAndroidTestOnJvmBuilder {
+ it.defaultSourceSetName = "androidUnitTest"
+ }
+ }
desktop()
mac()
linux()
@@ -66,9 +75,6 @@
}
}
-android {
- namespace "androidx.testutils.lifecycle"
-}
androidx {
type = LibraryType.INTERNAL_TEST_LIBRARY
diff --git a/wear/compose/compose-foundation/src/main/baseline-prof.txt b/wear/compose/compose-foundation/src/main/baseline-prof.txt
index 55e69e6..4fbef96 100644
--- a/wear/compose/compose-foundation/src/main/baseline-prof.txt
+++ b/wear/compose/compose-foundation/src/main/baseline-prof.txt
@@ -1,15 +1,19 @@
HSPLandroidx/wear/compose/foundation/AnchorType;->**(**)**
+SPLandroidx/wear/compose/foundation/AngularWidthSizeWrapper;->**(**)**
Landroidx/wear/compose/foundation/ArcPaddingValues;
HSPLandroidx/wear/compose/foundation/ArcPaddingValuesImpl;->**(**)**
HSPLandroidx/wear/compose/foundation/BaseCurvedChildWrapper;->**(**)**
HSPLandroidx/wear/compose/foundation/BasicCurvedTextKt**->**(**)**
+HSPLandroidx/wear/compose/foundation/BaseSizeWrapper;->**(**)**
HSPLandroidx/wear/compose/foundation/BasicSwipeToDismissBoxKt**->**(**)**
SPLandroidx/wear/compose/foundation/ComposableSingletons;->**(**)**
HSPLandroidx/wear/compose/foundation/CompositionLocalsKt**->**(**)**
HSPLandroidx/wear/compose/foundation/ContainerChild;->**(**)**
Landroidx/wear/compose/foundation/CurvedAlignment;
+HSPLandroidx/wear/compose/foundation/CurvedBoxChild;->**(**)**
HSPLandroidx/wear/compose/foundation/CurvedChild;->**(**)**
HSPLandroidx/wear/compose/foundation/CurvedComposableKt**->**(**)**
+HSPLandroidx/wear/compose/foundation/CurvedDrawKt**->**(**)**
HSPLandroidx/wear/compose/foundation/CurvedLayoutDirection;->**(**)**
HSPLandroidx/wear/compose/foundation/CurvedLayoutInfo;->**(**)**
HSPLandroidx/wear/compose/foundation/CurvedLayoutKt**->**(**)**
@@ -19,10 +23,14 @@
SPLandroidx/wear/compose/foundation/CurvedPaddingKt**->**(**)**
HSPLandroidx/wear/compose/foundation/CurvedRowChild;->**(**)**
HSPLandroidx/wear/compose/foundation/CurvedScope;->**(**)**
+Landroidx/wear/compose/foundation/CurvedScopeParentData;
+HSPLandroidx/wear/compose/foundation/CurvedSizeKt**->**(**)**
HSPLandroidx/wear/compose/foundation/CurvedTextChild;->**(**)**
HSPLandroidx/wear/compose/foundation/CurvedTextDelegate;->**(**)**
HSPLandroidx/wear/compose/foundation/CurvedTextStyle;->**(**)**
SPLandroidx/wear/compose/foundation/CurvedTextStyleKt**->**(**)**
+HSPLandroidx/wear/compose/foundation/DrawWrapper;->**(**)**
+Landroidx/wear/compose/foundation/Element;
SPLandroidx/wear/compose/foundation/ExpandableItemsDefaults;->**(**)**
HSPLandroidx/wear/compose/foundation/ExpandableKt**->**(**)**
HSPLandroidx/wear/compose/foundation/ExpandableState;->**(**)**
@@ -30,10 +38,14 @@
HSPLandroidx/wear/compose/foundation/HierarchicalFocusCoordinatorKt**->**(**)**
SPLandroidx/wear/compose/foundation/InternalMutatorMutex;->**(**)**
HSPLandroidx/wear/compose/foundation/PaddingWrapper;->**(**)**
+SPLandroidx/wear/compose/foundation/ParentDataWrapper;->**(**)**
+HSPLandroidx/wear/compose/foundation/PartialLayoutInfo;->**(**)**
PLandroidx/wear/compose/foundation/RevealActionType;->**(**)**
PLandroidx/wear/compose/foundation/RevealScopeImpl;->**(**)**
PLandroidx/wear/compose/foundation/RevealState;->**(**)**
PLandroidx/wear/compose/foundation/RevealValue;->**(**)**
+Landroidx/wear/compose/foundation/ScrollInfoProvider;
+HSPLandroidx/wear/compose/foundation/SweepSizeWrapper;->**(**)**
PLandroidx/wear/compose/foundation/SwipeAnchorsModifier;->**(**)**
SPLandroidx/wear/compose/foundation/SwipeToDismissBoxState;->**(**)**
SPLandroidx/wear/compose/foundation/SwipeToDismissKeys;->**(**)**
@@ -43,12 +55,15 @@
SPLandroidx/wear/compose/foundation/SwipeableV2Defaults;->**(**)**
HSPLandroidx/wear/compose/foundation/SwipeableV2Kt**->**(**)**
HSPLandroidx/wear/compose/foundation/SwipeableV2State;->**(**)**
+HSPLandroidx/wear/compose/foundation/TransformingLazyColumnStateScrollInfoProvider;->**(**)**
SPLandroidx/wear/compose/foundation/lazy/AutoCenteringParams;->**(**)**
SPLandroidx/wear/compose/foundation/lazy/CombinedPaddingValues;->**(**)**
HSPLandroidx/wear/compose/foundation/lazy/DefaultScalingLazyListItemInfo;->**(**)**
HSPLandroidx/wear/compose/foundation/lazy/DefaultScalingLazyListLayoutInfo;->**(**)**
SPLandroidx/wear/compose/foundation/lazy/DefaultScalingParams;->**(**)**
SPLandroidx/wear/compose/foundation/lazy/EmptyScalingLazyListLayoutInfo;->**(**)**
+SPLandroidx/wear/compose/foundation/lazy/HeightProviderParentData;->**(**)**
+HSPLandroidx/wear/compose/foundation/lazy/NearestRangeKeyIndexMap;->**(**)**
HSPLandroidx/wear/compose/foundation/lazy/ScalingLazyColumnKt**->**(**)**
PLandroidx/wear/compose/foundation/lazy/ScalingLazyColumnSnapFlingBehavior;->**(**)**
SPLandroidx/wear/compose/foundation/lazy/ScalingLazyListItemScopeImpl;->**(**)**
@@ -57,6 +72,26 @@
HSPLandroidx/wear/compose/foundation/lazy/ScalingLazyListState;->**(**)**
SPLandroidx/wear/compose/foundation/lazy/ScalingLazyListStateKt**->**(**)**
Landroidx/wear/compose/foundation/lazy/ScalingParams;
+HSPLandroidx/wear/compose/foundation/lazy/TransformingLazyColumnContentPaddingMeasurementStrategy;->**(**)**
+HSPLandroidx/wear/compose/foundation/lazy/TransformingLazyColumnInterval;->**(**)**
+HSPLandroidx/wear/compose/foundation/lazy/TransformingLazyColumnItemProvider;->**(**)**
+HSPLandroidx/wear/compose/foundation/lazy/TransformingLazyColumnItemScopeImpl;->**(**)**
+HSPLandroidx/wear/compose/foundation/lazy/TransformingLazyColumnItemScrollProgress;->**(**)**
+HSPLandroidx/wear/compose/foundation/lazy/TransformingLazyColumnKt**->**(**)**
+HSPLandroidx/wear/compose/foundation/lazy/TransformingLazyColumnMeasureResult;->**(**)**
+HSPLandroidx/wear/compose/foundation/lazy/TransformingLazyColumnMeasuredItem;->**(**)**
+HSPLandroidx/wear/compose/foundation/lazy/TransformingLazyColumnMeasurementKt**->**(**)**
+Landroidx/wear/compose/foundation/lazy/TransformingLazyColumnMeasurementStrategy;
+HSPLandroidx/wear/compose/foundation/lazy/TransformingLazyColumnMeasurementStrategyKt**->**(**)**
+Landroidx/wear/compose/foundation/lazy/TransformingLazyColumnScope;
+HSPLandroidx/wear/compose/foundation/lazy/TransformingLazyColumnScopeImpl;->**(**)**
+HSPLandroidx/wear/compose/foundation/lazy/TransformingLazyColumnState;->**(**)**
+SPLandroidx/wear/compose/foundation/lazy/TransformingLazyColumnStateKt**->**(**)**
+Landroidx/wear/compose/foundation/lazy/TransformingLazyColumnVisibleItemInfo;
+SPLandroidx/wear/compose/foundation/pager/CustomTouchSlop;->**(**)**
+HSPLandroidx/wear/compose/foundation/pager/DefaultPagerState;->**(**)**
+HSPLandroidx/wear/compose/foundation/pager/PagerKt**->**(**)**
+Landroidx/wear/compose/foundation/pager/PagerState;
SPLandroidx/wear/compose/foundation/rotary/BaseRotaryScrollableBehavior;->**(**)**
SPLandroidx/wear/compose/foundation/rotary/CustomRotaryHapticHandler;->**(**)**
SPLandroidx/wear/compose/foundation/rotary/FlingRotaryScrollableBehavior;->**(**)**
diff --git a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/BasicSwipeToDismissBox.kt b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/BasicSwipeToDismissBox.kt
index 78cdb6f..4ef9228 100644
--- a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/BasicSwipeToDismissBox.kt
+++ b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/BasicSwipeToDismissBox.kt
@@ -351,14 +351,13 @@
edgeSwipeState: State<EdgeSwipeState>
): NestedScrollConnection =
object : NestedScrollConnection {
- @Suppress("DEPRECATION") // b/327155912
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
val delta = available.x
// If swipeState = SwipeState.SWIPING_TO_DISMISS - perform swipeToDismiss
// drag and consume everything
return if (
edgeSwipeState.value == EdgeSwipeState.SwipingToDismiss &&
- source == NestedScrollSource.Drag
+ source == NestedScrollSource.UserInput
) {
dispatchRawDelta(delta)
available
diff --git a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/SwipeableV2.kt b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/SwipeableV2.kt
index c4ffe24..2f5c10d 100644
--- a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/SwipeableV2.kt
+++ b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/SwipeableV2.kt
@@ -477,7 +477,6 @@
*
* @return The delta the consumed by the [SwipeableV2State]
*/
- @Suppress("DEPRECATION") // b/327155912
fun dispatchRawDelta(delta: Float): Float {
var remainingDelta = delta
@@ -486,7 +485,7 @@
val consumedByParent =
nestedScrollDispatcher.dispatchPreScroll(
available = offsetWithOrientation(remainingDelta),
- source = NestedScrollSource.Drag
+ source = NestedScrollSource.UserInput
)
remainingDelta -= (consumedByParent.x + consumedByParent.y)
}
@@ -503,7 +502,7 @@
nestedScrollDispatcher.dispatchPostScroll(
consumed = offsetWithOrientation(deltaToConsume),
available = offsetWithOrientation(delta - deltaToConsume),
- source = NestedScrollSource.Drag
+ source = NestedScrollSource.UserInput
)
remainingDelta -= (deltaToConsume + consumedDelta.x + consumedDelta.y)
}
diff --git a/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/SelectionControls.kt b/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/SelectionControls.kt
index aa1b486..988ad1c 100644
--- a/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/SelectionControls.kt
+++ b/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/SelectionControls.kt
@@ -20,6 +20,7 @@
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.CubicBezierEasing
+import androidx.compose.animation.core.FiniteAnimationSpec
import androidx.compose.animation.core.Transition
import androidx.compose.animation.core.TweenSpec
import androidx.compose.animation.core.animateFloat
@@ -87,7 +88,7 @@
enabled: Boolean,
onCheckedChange: ((Boolean) -> Unit)?,
interactionSource: MutableInteractionSource?,
- progressAnimationSpec: TweenSpec<Float>,
+ progressAnimationSpec: FiniteAnimationSpec<Float>,
drawBox: FunctionDrawBox,
width: Dp,
height: Dp,
@@ -274,6 +275,59 @@
width: Dp,
height: Dp,
ripple: Indication
+) =
+ RadioButton(
+ modifier = modifier,
+ selected = selected,
+ enabled = enabled,
+ ringColor = ringColor,
+ dotColor = dotColor,
+ onClick = onClick,
+ interactionSource = interactionSource,
+ dotRadiusAnimationSpec = tween(dotRadiusProgressDuration(selected), 0, easing),
+ dotAlphaAnimationSpec = tween(dotAlphaProgressDuration, dotAlphaProgressDelay, easing),
+ width = width,
+ height = height,
+ ripple = ripple
+ )
+
+/**
+ * [RadioButton] provides an animated radio button for use in material APIs.
+ *
+ * @param modifier Modifier to be applied to the radio button. This can be used to provide a content
+ * description for accessibility.
+ * @param selected Boolean flag indicating whether this radio button is currently toggled on.
+ * @param enabled Boolean flag indicating the enabled state of the [RadioButton] (affects the
+ * color).
+ * @param ringColor Composable lambda from which the ring color of the radio button will be
+ * obtained.
+ * @param dotColor Composable lambda from which the dot color of the radio button will be obtained.
+ * @param onClick Callback to be invoked when RadioButton is clicked. If null, then this is passive
+ * and relies entirely on a higher-level component to control the state.
+ * @param interactionSource When also providing [onClick], the [MutableInteractionSource]
+ * representing the stream of [Interaction]s for the "toggleable" tap area - can be used to
+ * customise the appearance / behavior of the RadioButton.
+ * @param dotRadiusAnimationSpec Animation spec of the dot radius progress animation.
+ * @param dotAlphaAnimationSpec Animation spec of the dot alpha progress animation.
+ * @param width Width of the radio button.
+ * @param height Height of the radio button.
+ * @param ripple Ripple used for the radio button.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@Composable
+fun RadioButton(
+ modifier: Modifier,
+ selected: Boolean,
+ enabled: Boolean,
+ ringColor: @Composable (enabled: Boolean, checked: Boolean) -> State<Color>,
+ dotColor: @Composable (enabled: Boolean, checked: Boolean) -> State<Color>,
+ onClick: (() -> Unit)?,
+ interactionSource: MutableInteractionSource?,
+ dotRadiusAnimationSpec: FiniteAnimationSpec<Float>,
+ dotAlphaAnimationSpec: FiniteAnimationSpec<Float>,
+ width: Dp,
+ height: Dp,
+ ripple: Indication
) {
val targetState = if (selected) SelectionStage.Checked else SelectionStage.Unchecked
val transition = updateTransition(targetState)
@@ -286,7 +340,7 @@
animateProgress(
transition = transition,
label = "dot-radius",
- animationSpec = tween(dotRadiusProgressDuration(selected), 0, easing)
+ animationSpec = dotRadiusAnimationSpec
)
// Animation of the dot alpha only happens when toggling On to Off.
val dotAlphaProgress =
@@ -294,7 +348,7 @@
animateProgress(
transition = transition,
label = "dot-alpha",
- animationSpec = tween(dotAlphaProgressDuration, dotAlphaProgressDelay, easing)
+ animationSpec = dotAlphaAnimationSpec
)
else null
@@ -426,7 +480,7 @@
private fun animateProgress(
transition: Transition<SelectionStage>,
label: String,
- animationSpec: TweenSpec<Float>,
+ animationSpec: FiniteAnimationSpec<Float>,
) =
transition.animateFloat(transitionSpec = { animationSpec }, label = label) {
when (it) {
diff --git a/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/dialog/DialogTest.kt b/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/dialog/DialogTest.kt
index f28182d..a2e882f 100644
--- a/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/dialog/DialogTest.kt
+++ b/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/dialog/DialogTest.kt
@@ -424,10 +424,11 @@
dismissCounter++
show.value = false
},
- durationMillis = 100
+ durationMillis = 300
)
}
}
+ rule.waitForIdle()
rule.waitUntilDoesNotExist(hasTestTag(TEST_TAG))
assertEquals(1, dismissCounter)
}
diff --git a/wear/compose/compose-material3/api/current.txt b/wear/compose/compose-material3/api/current.txt
index 551c115..178be84 100644
--- a/wear/compose/compose-material3/api/current.txt
+++ b/wear/compose/compose-material3/api/current.txt
@@ -27,6 +27,17 @@
method @androidx.compose.runtime.Composable public static void AlertDialogContent(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> confirmButton, kotlin.jvm.functions.Function0<kotlin.Unit> title, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> dismissButton, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? text, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.lazy.ScalingLazyListScope,kotlin.Unit>? content);
}
+ @kotlin.jvm.JvmInline public final value class AngularDirection {
+ field public static final androidx.wear.compose.material3.AngularDirection.Companion Companion;
+ }
+
+ public static final class AngularDirection.Companion {
+ method public int getClockwise();
+ method public int getCounterClockwise();
+ property public final int Clockwise;
+ property public final int CounterClockwise;
+ }
+
@RequiresApi(31) public final class AnimatedTextDefaults {
field public static final int CacheSize = 5; // 0x5
field public static final androidx.wear.compose.material3.AnimatedTextDefaults INSTANCE;
@@ -45,6 +56,21 @@
method @androidx.compose.runtime.Composable public static void AppScaffold(optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> timeText, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
}
+ public final class ArcProgressIndicatorDefaults {
+ method public float calculateRecommendedGapSize(float strokeWidth);
+ method public float getIndeterminateStrokeWidth();
+ method @androidx.compose.runtime.Composable public float getRecommendedIndeterminateDiameter();
+ property public final float IndeterminateStrokeWidth;
+ property @androidx.compose.runtime.Composable public final float recommendedIndeterminateDiameter;
+ field public static final androidx.wear.compose.material3.ArcProgressIndicatorDefaults INSTANCE;
+ field public static final float IndeterminateEndAngle = 118.0f;
+ field public static final float IndeterminateStartAngle = 62.0f;
+ }
+
+ public final class ArcProgressIndicatorKt {
+ method @androidx.compose.runtime.Composable public static void ArcProgressIndicator(optional float startAngle, optional float endAngle, optional androidx.compose.ui.Modifier modifier, optional int angularDirection, optional androidx.wear.compose.material3.ProgressIndicatorColors colors, optional float strokeWidth, optional float gapSize);
+ }
+
@androidx.compose.runtime.Immutable public final class ButtonColors {
ctor public ButtonColors(androidx.compose.ui.graphics.painter.Painter containerPainter, long contentColor, long secondaryContentColor, long iconColor, androidx.compose.ui.graphics.painter.Painter disabledContainerPainter, long disabledContentColor, long disabledSecondaryContentColor, long disabledIconColor);
ctor public ButtonColors(long containerColor, long contentColor, long secondaryContentColor, long iconColor, long disabledContainerColor, long disabledContentColor, long disabledSecondaryContentColor, long disabledIconColor);
@@ -825,11 +851,11 @@
public final class PlaceholderDefaults {
method public androidx.compose.ui.graphics.Shape getShape();
- method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.painter.Painter painterWithPlaceholderOverlayBackgroundBrush(androidx.wear.compose.material3.PlaceholderState placeholderState, androidx.compose.ui.graphics.painter.Painter painter, optional long color);
+ method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.painter.Painter painterWithPlaceholderOverlayBackgroundBrush(androidx.wear.compose.material3.PlaceholderState placeholderState, androidx.compose.ui.graphics.painter.Painter originalPainter, optional long color);
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.painter.Painter placeholderBackgroundBrush(androidx.wear.compose.material3.PlaceholderState placeholderState, optional long color);
method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.ButtonColors placeholderButtonColors(androidx.wear.compose.material3.ButtonColors originalButtonColors, androidx.wear.compose.material3.PlaceholderState placeholderState, optional long color);
method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.ButtonColors placeholderButtonColors(androidx.wear.compose.material3.PlaceholderState placeholderState, optional long color);
- property public final androidx.compose.ui.graphics.Shape shape;
+ property public final androidx.compose.ui.graphics.Shape Shape;
field public static final androidx.wear.compose.material3.PlaceholderDefaults INSTANCE;
}
@@ -840,13 +866,11 @@
}
@androidx.compose.runtime.Stable public final class PlaceholderState {
- method public float getPlaceholderProgression();
- method public boolean isShowContent();
- method public boolean isWipeOff();
- method public suspend Object? startPlaceholderAnimation(kotlin.coroutines.Continuation<? super kotlin.Unit>);
- property public final boolean isShowContent;
- property public final boolean isWipeOff;
- property public final float placeholderProgression;
+ method public suspend Object? animatePlaceholder(kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ method public boolean isHidden();
+ method public boolean isWipingOff();
+ property public final boolean isHidden;
+ property public final boolean isWipingOff;
}
public final class ProgressIndicatorColors {
diff --git a/wear/compose/compose-material3/api/restricted_current.txt b/wear/compose/compose-material3/api/restricted_current.txt
index 551c115..178be84 100644
--- a/wear/compose/compose-material3/api/restricted_current.txt
+++ b/wear/compose/compose-material3/api/restricted_current.txt
@@ -27,6 +27,17 @@
method @androidx.compose.runtime.Composable public static void AlertDialogContent(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> confirmButton, kotlin.jvm.functions.Function0<kotlin.Unit> title, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> dismissButton, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? text, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.lazy.ScalingLazyListScope,kotlin.Unit>? content);
}
+ @kotlin.jvm.JvmInline public final value class AngularDirection {
+ field public static final androidx.wear.compose.material3.AngularDirection.Companion Companion;
+ }
+
+ public static final class AngularDirection.Companion {
+ method public int getClockwise();
+ method public int getCounterClockwise();
+ property public final int Clockwise;
+ property public final int CounterClockwise;
+ }
+
@RequiresApi(31) public final class AnimatedTextDefaults {
field public static final int CacheSize = 5; // 0x5
field public static final androidx.wear.compose.material3.AnimatedTextDefaults INSTANCE;
@@ -45,6 +56,21 @@
method @androidx.compose.runtime.Composable public static void AppScaffold(optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> timeText, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
}
+ public final class ArcProgressIndicatorDefaults {
+ method public float calculateRecommendedGapSize(float strokeWidth);
+ method public float getIndeterminateStrokeWidth();
+ method @androidx.compose.runtime.Composable public float getRecommendedIndeterminateDiameter();
+ property public final float IndeterminateStrokeWidth;
+ property @androidx.compose.runtime.Composable public final float recommendedIndeterminateDiameter;
+ field public static final androidx.wear.compose.material3.ArcProgressIndicatorDefaults INSTANCE;
+ field public static final float IndeterminateEndAngle = 118.0f;
+ field public static final float IndeterminateStartAngle = 62.0f;
+ }
+
+ public final class ArcProgressIndicatorKt {
+ method @androidx.compose.runtime.Composable public static void ArcProgressIndicator(optional float startAngle, optional float endAngle, optional androidx.compose.ui.Modifier modifier, optional int angularDirection, optional androidx.wear.compose.material3.ProgressIndicatorColors colors, optional float strokeWidth, optional float gapSize);
+ }
+
@androidx.compose.runtime.Immutable public final class ButtonColors {
ctor public ButtonColors(androidx.compose.ui.graphics.painter.Painter containerPainter, long contentColor, long secondaryContentColor, long iconColor, androidx.compose.ui.graphics.painter.Painter disabledContainerPainter, long disabledContentColor, long disabledSecondaryContentColor, long disabledIconColor);
ctor public ButtonColors(long containerColor, long contentColor, long secondaryContentColor, long iconColor, long disabledContainerColor, long disabledContentColor, long disabledSecondaryContentColor, long disabledIconColor);
@@ -825,11 +851,11 @@
public final class PlaceholderDefaults {
method public androidx.compose.ui.graphics.Shape getShape();
- method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.painter.Painter painterWithPlaceholderOverlayBackgroundBrush(androidx.wear.compose.material3.PlaceholderState placeholderState, androidx.compose.ui.graphics.painter.Painter painter, optional long color);
+ method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.painter.Painter painterWithPlaceholderOverlayBackgroundBrush(androidx.wear.compose.material3.PlaceholderState placeholderState, androidx.compose.ui.graphics.painter.Painter originalPainter, optional long color);
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.painter.Painter placeholderBackgroundBrush(androidx.wear.compose.material3.PlaceholderState placeholderState, optional long color);
method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.ButtonColors placeholderButtonColors(androidx.wear.compose.material3.ButtonColors originalButtonColors, androidx.wear.compose.material3.PlaceholderState placeholderState, optional long color);
method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.ButtonColors placeholderButtonColors(androidx.wear.compose.material3.PlaceholderState placeholderState, optional long color);
- property public final androidx.compose.ui.graphics.Shape shape;
+ property public final androidx.compose.ui.graphics.Shape Shape;
field public static final androidx.wear.compose.material3.PlaceholderDefaults INSTANCE;
}
@@ -840,13 +866,11 @@
}
@androidx.compose.runtime.Stable public final class PlaceholderState {
- method public float getPlaceholderProgression();
- method public boolean isShowContent();
- method public boolean isWipeOff();
- method public suspend Object? startPlaceholderAnimation(kotlin.coroutines.Continuation<? super kotlin.Unit>);
- property public final boolean isShowContent;
- property public final boolean isWipeOff;
- property public final float placeholderProgression;
+ method public suspend Object? animatePlaceholder(kotlin.coroutines.Continuation<? super kotlin.Unit>);
+ method public boolean isHidden();
+ method public boolean isWipingOff();
+ property public final boolean isHidden;
+ property public final boolean isWipingOff;
}
public final class ProgressIndicatorColors {
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/PlaceholderDemo.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/PlaceholderDemo.kt
index bf7184c..79493f9 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/PlaceholderDemo.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/PlaceholderDemo.kt
@@ -405,7 +405,7 @@
placeholderState = buttonPlaceholderState
)
)
- if (!buttonPlaceholderState.isShowContent) {
+ if (!buttonPlaceholderState.isHidden) {
Button(
modifier =
modifier
@@ -457,7 +457,7 @@
)
}
}
- LaunchedEffect(buttonPlaceholderState) { buttonPlaceholderState.startPlaceholderAnimation() }
+ LaunchedEffect(buttonPlaceholderState) { buttonPlaceholderState.animatePlaceholder() }
}
@Composable
@@ -532,7 +532,7 @@
placeholderState = buttonPlaceholderState
)
)
- LaunchedEffect(buttonPlaceholderState) { buttonPlaceholderState.startPlaceholderAnimation() }
+ LaunchedEffect(buttonPlaceholderState) { buttonPlaceholderState.animatePlaceholder() }
}
@Composable
@@ -553,7 +553,7 @@
) {
if (content != null) content()
}
- if (!cardPlaceholderState.isShowContent) {
+ if (!cardPlaceholderState.isHidden) {
AppCard(
onClick = {},
appName = {
@@ -596,5 +596,5 @@
}
}
}
- LaunchedEffect(cardPlaceholderState) { cardPlaceholderState.startPlaceholderAnimation() }
+ LaunchedEffect(cardPlaceholderState) { cardPlaceholderState.animatePlaceholder() }
}
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/ProgressIndicatorDemo.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/ProgressIndicatorDemo.kt
index 854b089..99e8400c 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/ProgressIndicatorDemo.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/ProgressIndicatorDemo.kt
@@ -38,6 +38,9 @@
import androidx.wear.compose.integration.demos.common.Centralize
import androidx.wear.compose.integration.demos.common.ComposableDemo
import androidx.wear.compose.integration.demos.common.Material3DemoCategory
+import androidx.wear.compose.material3.AngularDirection
+import androidx.wear.compose.material3.ArcProgressIndicator
+import androidx.wear.compose.material3.ArcProgressIndicatorDefaults
import androidx.wear.compose.material3.Button
import androidx.wear.compose.material3.CircularProgressIndicator
import androidx.wear.compose.material3.CircularProgressIndicatorDefaults
@@ -51,6 +54,7 @@
import androidx.wear.compose.material3.SwitchButton
import androidx.wear.compose.material3.Text
import androidx.wear.compose.material3.samples.FullScreenProgressIndicatorSample
+import androidx.wear.compose.material3.samples.IndeterminateProgressArcSample
import androidx.wear.compose.material3.samples.IndeterminateProgressIndicatorSample
import androidx.wear.compose.material3.samples.LinearProgressIndicatorSample
import androidx.wear.compose.material3.samples.MediaButtonProgressIndicatorSample
@@ -126,11 +130,15 @@
},
)
),
+ ComposableDemo("Linear progress") { Centralize { LinearProgressIndicatorSamples() } },
Material3DemoCategory(
- title = "Linear progress",
+ title = "Arc Progress Indicator",
listOf(
- ComposableDemo("Linear progress") {
- Centralize { LinearProgressIndicatorSamples() }
+ ComposableDemo("Indeterminate arc") {
+ Centralize { IndeterminateProgressArcSample() }
+ },
+ ComposableDemo("Custom indeterminate arc") {
+ Centralize { ArcProgressCustomisableFullScreenDemo() }
},
)
)
@@ -268,6 +276,60 @@
}
@Composable
+fun ArcProgressCustomisableFullScreenDemo() {
+ val startAngle = remember {
+ mutableFloatStateOf(ArcProgressIndicatorDefaults.IndeterminateStartAngle)
+ }
+ val endAngle = remember {
+ mutableFloatStateOf(ArcProgressIndicatorDefaults.IndeterminateEndAngle)
+ }
+ val defaultDiameter = ArcProgressIndicatorDefaults.recommendedIndeterminateDiameter
+ val diameter = remember { mutableFloatStateOf(defaultDiameter.value) }
+ val strokeWidth = remember {
+ mutableFloatStateOf(ArcProgressIndicatorDefaults.IndeterminateStrokeWidth.value)
+ }
+ val angularDirection = remember { mutableStateOf(AngularDirection.CounterClockwise) }
+ val hasCustomColors = remember { mutableStateOf(false) }
+ val colors =
+ if (hasCustomColors.value) {
+ ProgressIndicatorDefaults.colors(
+ indicatorColor = Color.Green,
+ trackColor = Color.Green.copy(alpha = 0.5f),
+ overflowTrackColor = Color.Green.copy(alpha = 0.7f),
+ )
+ } else {
+ ProgressIndicatorDefaults.colors()
+ }
+
+ Box(
+ modifier =
+ Modifier.background(MaterialTheme.colorScheme.background)
+ .padding(CircularProgressIndicatorDefaults.FullScreenPadding)
+ .fillMaxSize()
+ ) {
+ ArcIndicatorCustomizer(
+ startAngle = startAngle,
+ endAngle = endAngle,
+ diameter = diameter,
+ strokeWidth = strokeWidth,
+ angularDirection = angularDirection,
+ hasCustomColors = hasCustomColors,
+ )
+
+ Centralize {
+ ArcProgressIndicator(
+ startAngle = startAngle.floatValue,
+ endAngle = endAngle.floatValue,
+ strokeWidth = strokeWidth.floatValue.dp,
+ angularDirection = angularDirection.value,
+ colors = colors,
+ modifier = Modifier.size(diameter.floatValue.dp)
+ )
+ }
+ }
+}
+
+@Composable
fun ProgressIndicatorCustomizer(
progress: MutableState<Float>,
startAngle: MutableState<Float>,
@@ -396,3 +458,95 @@
)
}
}
+
+@Composable
+fun ArcIndicatorCustomizer(
+ startAngle: MutableState<Float>,
+ endAngle: MutableState<Float>,
+ diameter: MutableState<Float>,
+ strokeWidth: MutableState<Float>,
+ angularDirection: MutableState<AngularDirection>,
+ hasCustomColors: MutableState<Boolean>,
+) {
+ ScalingLazyColumn(
+ modifier = Modifier.fillMaxSize().padding(12.dp),
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ item { Text("Start Angle: ${startAngle.value.toInt()}") }
+ item {
+ Slider(
+ value = startAngle.value,
+ onValueChange = { startAngle.value = it },
+ valueRange = 0f..360f,
+ steps = 35,
+ segmented = false,
+ colors =
+ SliderDefaults.sliderColors(
+ containerColor = MaterialTheme.colorScheme.background,
+ ),
+ )
+ }
+ item { Text("End angle: ${endAngle.value.toInt()}") }
+ item {
+ Slider(
+ value = endAngle.value,
+ onValueChange = { endAngle.value = it },
+ valueRange = 0f..360f,
+ steps = 35,
+ segmented = false,
+ colors =
+ SliderDefaults.sliderColors(
+ containerColor = MaterialTheme.colorScheme.background,
+ ),
+ )
+ }
+ item { Text("Diameter: ${diameter.value.toInt()}") }
+ item {
+ Slider(
+ value = diameter.value,
+ onValueChange = { diameter.value = it },
+ valueRange = 10f..400f,
+ steps = 38,
+ segmented = false,
+ colors =
+ SliderDefaults.sliderColors(
+ containerColor = MaterialTheme.colorScheme.background,
+ ),
+ )
+ }
+ item { Text("StrokeWidth: ${strokeWidth.value}") }
+ item {
+ Slider(
+ value = strokeWidth.value,
+ onValueChange = { strokeWidth.value = it },
+ valueRange = 1f..20f,
+ steps = 18,
+ segmented = false,
+ colors =
+ SliderDefaults.sliderColors(
+ containerColor = MaterialTheme.colorScheme.background,
+ ),
+ )
+ }
+ item {
+ SwitchButton(
+ modifier = Modifier.fillMaxWidth().padding(8.dp),
+ checked = angularDirection.value == AngularDirection.Clockwise,
+ onCheckedChange = {
+ angularDirection.value =
+ if (it) AngularDirection.Clockwise else AngularDirection.CounterClockwise
+ },
+ label = { Text("Clockwise") },
+ )
+ }
+ item {
+ SwitchButton(
+ modifier = Modifier.fillMaxWidth().padding(8.dp),
+ checked = hasCustomColors.value,
+ onCheckedChange = { hasCustomColors.value = it },
+ label = { Text("Custom colors") },
+ )
+ }
+ }
+}
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt
index b1a80cf..108dbdb 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt
@@ -84,6 +84,7 @@
)
),
Material3DemoCategory("ScrollAway", ScrollAwayDemos),
+ Material3DemoCategory(title = "Typography", TypographyDemos),
ComposableDemo("Compact Button") { CompactButtonDemo() },
ComposableDemo("Icon Button") { IconButtonDemo() },
ComposableDemo("Image Button") { ImageButtonDemo() },
@@ -171,7 +172,6 @@
}
)
),
- Material3DemoCategory(title = "Typography", TypographyDemos),
Material3DemoCategory(
"Animated Text",
if (Build.VERSION.SDK_INT > 31) {
diff --git a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/MacrobenchmarkScreen.kt b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/MacrobenchmarkScreen.kt
new file mode 100644
index 0000000..67330d5
--- /dev/null
+++ b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/MacrobenchmarkScreen.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material3.macrobenchmark.common
+
+import androidx.benchmark.macro.MacrobenchmarkScope
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.runtime.Composable
+
+/** Represents a screen that can be used in Macrobenchmark tests. */
+interface MacrobenchmarkScreen {
+ val content: @Composable BoxScope.() -> Unit
+ val exercise: MacrobenchmarkScope.() -> Unit
+ get() = { device.waitForIdle() }
+}
diff --git a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/TransformingLazyColumnBenchmark.kt b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/TransformingLazyColumnBenchmark.kt
new file mode 100644
index 0000000..ccf678d
--- /dev/null
+++ b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/TransformingLazyColumnBenchmark.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material3.macrobenchmark.common
+
+import android.graphics.Point
+import androidx.benchmark.macro.MacrobenchmarkScope
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.unit.dp
+import androidx.test.uiautomator.By
+import androidx.wear.compose.foundation.lazy.TransformingLazyColumn
+import androidx.wear.compose.foundation.lazy.rememberTransformingLazyColumnState
+import androidx.wear.compose.material3.AppScaffold
+import androidx.wear.compose.material3.EdgeButton
+import androidx.wear.compose.material3.EdgeButtonSize
+import androidx.wear.compose.material3.MaterialTheme
+import androidx.wear.compose.material3.ScreenScaffold
+import androidx.wear.compose.material3.ScreenScaffoldDefaults
+import androidx.wear.compose.material3.Text
+import androidx.wear.compose.material3.lazy.scrollTransform
+import kotlinx.coroutines.launch
+
+val TransformingLazyColumnBenchmark =
+ object : MacrobenchmarkScreen {
+ override val content: @Composable (BoxScope.() -> Unit)
+ get() = {
+ val state = rememberTransformingLazyColumnState()
+ val coroutineScope = rememberCoroutineScope()
+ AppScaffold {
+ ScreenScaffold(
+ state,
+ edgeButton = {
+ EdgeButton(
+ onClick = { coroutineScope.launch { state.scrollToItem(1) } }
+ ) {
+ Text("To top")
+ }
+ }
+ ) {
+ TransformingLazyColumn(
+ state = state,
+ contentPadding =
+ ScreenScaffoldDefaults.contentPaddingWithEdgeButton(
+ EdgeButtonSize.Small,
+ start = 10.dp,
+ end = 10.dp,
+ top = 20.dp,
+ extraBottom = 20.dp
+ ),
+ modifier =
+ Modifier.background(MaterialTheme.colorScheme.background)
+ .semantics { contentDescription = CONTENT_DESCRIPTION }
+ ) {
+ items(5000) {
+ Text(
+ "Item $it",
+ color = MaterialTheme.colorScheme.onSurface,
+ style = MaterialTheme.typography.bodyLarge,
+ modifier =
+ Modifier.fillMaxWidth()
+ // Apply Material 3 Motion transformations.
+ .scrollTransform(
+ this,
+ backgroundColor =
+ MaterialTheme.colorScheme.surfaceContainer,
+ shape = MaterialTheme.shapes.small
+ )
+ .padding(10.dp)
+ )
+ }
+ }
+ }
+ }
+ }
+
+ override val exercise: MacrobenchmarkScope.() -> Unit
+ get() = {
+ val list = device.findObject(By.desc(CONTENT_DESCRIPTION))
+ // Setting a gesture margin is important otherwise gesture nav is triggered.
+ list.setGestureMargin(device.displayWidth / 5)
+ repeat(5) {
+ list.drag(Point(list.visibleCenter.x, list.visibleCenter.y / 3))
+ device.waitForIdle()
+ }
+ }
+ }
diff --git a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/Utils.kt b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/Utils.kt
index 1f54bb2..3cf03ed 100644
--- a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/Utils.kt
+++ b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/Utils.kt
@@ -18,7 +18,9 @@
import androidx.test.uiautomator.UiDevice
-internal fun numberedContentDescription(n: Int) = "find-me-$n"
+internal val CONTENT_DESCRIPTION = "find-me"
+
+internal fun numberedContentDescription(n: Int) = "$CONTENT_DESCRIPTION-$n"
internal fun UiDevice.scrollDown() {
swipe(
diff --git a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/AlertDialogScreen.kt b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/AlertDialogScreen.kt
index 2b5adb3..762b883 100644
--- a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/AlertDialogScreen.kt
+++ b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/AlertDialogScreen.kt
@@ -46,10 +46,11 @@
import androidx.wear.compose.material3.MaterialTheme
import androidx.wear.compose.material3.Text
import androidx.wear.compose.material3.macrobenchmark.common.FIND_OBJECT_TIMEOUT_MS
+import androidx.wear.compose.material3.macrobenchmark.common.MacrobenchmarkScreen
import androidx.wear.compose.material3.macrobenchmark.common.R
val AlertDialogScreen =
- object : BaselineProfileScreen {
+ object : MacrobenchmarkScreen {
override val content: @Composable BoxScope.() -> Unit
get() = {
diff --git a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/AnimatedTextScreen.kt b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/AnimatedTextScreen.kt
index 6be0bfe..615f51c 100644
--- a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/AnimatedTextScreen.kt
+++ b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/AnimatedTextScreen.kt
@@ -49,12 +49,13 @@
import androidx.wear.compose.material3.AnimatedText
import androidx.wear.compose.material3.Text
import androidx.wear.compose.material3.macrobenchmark.common.FIND_OBJECT_TIMEOUT_MS
+import androidx.wear.compose.material3.macrobenchmark.common.MacrobenchmarkScreen
import androidx.wear.compose.material3.macrobenchmark.common.R
import androidx.wear.compose.material3.rememberAnimatedTextFontRegistry
import kotlinx.coroutines.launch
val AnimatedTextScreen =
- object : BaselineProfileScreen {
+ object : MacrobenchmarkScreen {
override val content: @Composable BoxScope.() -> Unit
get() = {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
diff --git a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/BaselineProfileScreens.kt b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/BaselineProfileScreens.kt
index c50d169..e5b993a 100644
--- a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/BaselineProfileScreens.kt
+++ b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/BaselineProfileScreens.kt
@@ -16,10 +16,6 @@
package androidx.wear.compose.material3.macrobenchmark.common.baselineprofile
-import androidx.benchmark.macro.MacrobenchmarkScope
-import androidx.compose.foundation.layout.BoxScope
-import androidx.compose.runtime.Composable
-
val BaselineProfileScreens =
listOf(
AlertDialogScreen,
@@ -56,10 +52,3 @@
TimePickerScreen,
TransformingLazyColumnScreen,
)
-
-/** Represents a screen used for generating a baseline profile. */
-interface BaselineProfileScreen {
- val content: @Composable BoxScope.() -> Unit
- val exercise: MacrobenchmarkScope.() -> Unit
- get() = { device.waitForIdle() }
-}
diff --git a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/ButtonGroupScreen.kt b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/ButtonGroupScreen.kt
index 2125418..d7fc697 100644
--- a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/ButtonGroupScreen.kt
+++ b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/ButtonGroupScreen.kt
@@ -18,10 +18,11 @@
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.runtime.Composable
+import androidx.wear.compose.material3.macrobenchmark.common.MacrobenchmarkScreen
import androidx.wear.compose.material3.samples.ButtonGroupSample
val ButtonGroupScreen =
- object : BaselineProfileScreen {
+ object : MacrobenchmarkScreen {
override val content: @Composable BoxScope.() -> Unit
get() = { ButtonGroupSample() }
}
diff --git a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/ButtonScreen.kt b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/ButtonScreen.kt
index 6274bc5..8766fc3 100644
--- a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/ButtonScreen.kt
+++ b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/ButtonScreen.kt
@@ -25,6 +25,7 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.wear.compose.material3.IconButtonDefaults
+import androidx.wear.compose.material3.macrobenchmark.common.MacrobenchmarkScreen
import androidx.wear.compose.material3.macrobenchmark.common.R
import androidx.wear.compose.material3.macrobenchmark.common.scrollDown
import androidx.wear.compose.material3.samples.ButtonSample
@@ -38,7 +39,7 @@
import androidx.wear.compose.material3.samples.OutlinedCompactButtonSample
val ButtonScreen =
- object : BaselineProfileScreen {
+ object : MacrobenchmarkScreen {
override val content: @Composable BoxScope.() -> Unit
get() = {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
diff --git a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/CardScreen.kt b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/CardScreen.kt
index 4e149a6..7fbf379 100644
--- a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/CardScreen.kt
+++ b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/CardScreen.kt
@@ -37,11 +37,12 @@
import androidx.wear.compose.material3.OutlinedCard
import androidx.wear.compose.material3.Text
import androidx.wear.compose.material3.TitleCard
+import androidx.wear.compose.material3.macrobenchmark.common.MacrobenchmarkScreen
import androidx.wear.compose.material3.macrobenchmark.common.R
import androidx.wear.compose.material3.macrobenchmark.common.scrollDown
val CardScreen =
- object : BaselineProfileScreen {
+ object : MacrobenchmarkScreen {
override val content: @Composable BoxScope.() -> Unit
get() = {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
diff --git a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/CheckboxButtonScreen.kt b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/CheckboxButtonScreen.kt
index 8d8000a..69bf7b3 100644
--- a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/CheckboxButtonScreen.kt
+++ b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/CheckboxButtonScreen.kt
@@ -22,11 +22,12 @@
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
+import androidx.wear.compose.material3.macrobenchmark.common.MacrobenchmarkScreen
import androidx.wear.compose.material3.samples.CheckboxButtonSample
import androidx.wear.compose.material3.samples.SplitCheckboxButtonSample
val CheckboxButtonScreen =
- object : BaselineProfileScreen {
+ object : MacrobenchmarkScreen {
override val content: @Composable BoxScope.() -> Unit
get() = {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
diff --git a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/ColorSchemeScreen.kt b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/ColorSchemeScreen.kt
index f978ad6..a85413c 100644
--- a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/ColorSchemeScreen.kt
+++ b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/ColorSchemeScreen.kt
@@ -30,10 +30,11 @@
import androidx.wear.compose.material3.ButtonDefaults
import androidx.wear.compose.material3.MaterialTheme
import androidx.wear.compose.material3.Text
+import androidx.wear.compose.material3.macrobenchmark.common.MacrobenchmarkScreen
import androidx.wear.compose.material3.macrobenchmark.common.scrollDown
val ColorSchemeScreen =
- object : BaselineProfileScreen {
+ object : MacrobenchmarkScreen {
override val content: @Composable BoxScope.() -> Unit
get() = {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
diff --git a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/ConfirmationScreen.kt b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/ConfirmationScreen.kt
index 8796147..406042b 100644
--- a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/ConfirmationScreen.kt
+++ b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/ConfirmationScreen.kt
@@ -26,10 +26,8 @@
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
-import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
@@ -45,11 +43,12 @@
import androidx.wear.compose.material3.SuccessConfirmation
import androidx.wear.compose.material3.Text
import androidx.wear.compose.material3.macrobenchmark.common.FIND_OBJECT_TIMEOUT_MS
+import androidx.wear.compose.material3.macrobenchmark.common.MacrobenchmarkScreen
import androidx.wear.compose.material3.macrobenchmark.common.R
import androidx.wear.compose.material3.macrobenchmark.common.numberedContentDescription
val ConfirmationScreen =
- object : BaselineProfileScreen {
+ object : MacrobenchmarkScreen {
override val content: @Composable BoxScope.() -> Unit
get() = {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
diff --git a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/CurvedTextScreen.kt b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/CurvedTextScreen.kt
index e5f6508..f149ecb 100644
--- a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/CurvedTextScreen.kt
+++ b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/CurvedTextScreen.kt
@@ -18,11 +18,12 @@
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.runtime.Composable
+import androidx.wear.compose.material3.macrobenchmark.common.MacrobenchmarkScreen
import androidx.wear.compose.material3.samples.CurvedTextBottom
import androidx.wear.compose.material3.samples.CurvedTextTop
val CurvedTextScreen =
- object : BaselineProfileScreen {
+ object : MacrobenchmarkScreen {
override val content: @Composable BoxScope.() -> Unit
get() = {
CurvedTextTop()
diff --git a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/DatePickerScreen.kt b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/DatePickerScreen.kt
index 97b1296..a42a4a9 100644
--- a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/DatePickerScreen.kt
+++ b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/DatePickerScreen.kt
@@ -21,10 +21,11 @@
import androidx.compose.runtime.Composable
import androidx.wear.compose.material3.DatePicker
import androidx.wear.compose.material3.DatePickerType
+import androidx.wear.compose.material3.macrobenchmark.common.MacrobenchmarkScreen
import java.time.LocalDate
val DatePickerScreen =
- object : BaselineProfileScreen {
+ object : MacrobenchmarkScreen {
override val content: @Composable BoxScope.() -> Unit
get() = {
if (Build.VERSION.SDK_INT >= 26) {
diff --git a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/EdgeButtonScreen.kt b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/EdgeButtonScreen.kt
index 910bac1..c2dc7ad 100644
--- a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/EdgeButtonScreen.kt
+++ b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/EdgeButtonScreen.kt
@@ -19,11 +19,12 @@
import androidx.benchmark.macro.MacrobenchmarkScope
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.runtime.Composable
+import androidx.wear.compose.material3.macrobenchmark.common.MacrobenchmarkScreen
import androidx.wear.compose.material3.macrobenchmark.common.scrollDown
import androidx.wear.compose.material3.samples.EdgeButtonSample
val EdgeButtonScreen =
- object : BaselineProfileScreen {
+ object : MacrobenchmarkScreen {
override val content: @Composable BoxScope.() -> Unit
get() = { EdgeButtonSample() }
diff --git a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/IconButtonScreen.kt b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/IconButtonScreen.kt
index 31d35a2..799e267 100644
--- a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/IconButtonScreen.kt
+++ b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/IconButtonScreen.kt
@@ -24,6 +24,7 @@
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
+import androidx.wear.compose.material3.macrobenchmark.common.MacrobenchmarkScreen
import androidx.wear.compose.material3.samples.FilledIconButtonSample
import androidx.wear.compose.material3.samples.FilledTonalIconButtonSample
import androidx.wear.compose.material3.samples.FilledVariantIconButtonSample
@@ -34,7 +35,7 @@
@OptIn(ExperimentalLayoutApi::class)
val IconButtonScreen =
- object : BaselineProfileScreen {
+ object : MacrobenchmarkScreen {
override val content: @Composable() (BoxScope.() -> Unit)
get() = {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
diff --git a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/IconToggleButtonScreen.kt b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/IconToggleButtonScreen.kt
index 2a170f3..d1b2bb1 100644
--- a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/IconToggleButtonScreen.kt
+++ b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/IconToggleButtonScreen.kt
@@ -36,13 +36,14 @@
import androidx.wear.compose.material3.IconToggleButton
import androidx.wear.compose.material3.IconToggleButtonDefaults
import androidx.wear.compose.material3.macrobenchmark.common.FIND_OBJECT_TIMEOUT_MS
+import androidx.wear.compose.material3.macrobenchmark.common.MacrobenchmarkScreen
import androidx.wear.compose.material3.macrobenchmark.common.R
import androidx.wear.compose.material3.samples.IconToggleButtonSample
import androidx.wear.compose.material3.samples.IconToggleButtonVariantSample
@OptIn(ExperimentalLayoutApi::class)
val IconToggleButtonScreen =
- object : BaselineProfileScreen {
+ object : MacrobenchmarkScreen {
override val content: @Composable (BoxScope.() -> Unit)
get() = {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
diff --git a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/ListHeaderScreen.kt b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/ListHeaderScreen.kt
index 0cede8a..21a41e5 100644
--- a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/ListHeaderScreen.kt
+++ b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/ListHeaderScreen.kt
@@ -22,12 +22,13 @@
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
+import androidx.wear.compose.material3.macrobenchmark.common.MacrobenchmarkScreen
import androidx.wear.compose.material3.samples.ListHeaderSample
import androidx.wear.compose.material3.samples.ListSubHeaderSample
import androidx.wear.compose.material3.samples.ListSubHeaderWithIconSample
val ListHeaderScreen =
- object : BaselineProfileScreen {
+ object : MacrobenchmarkScreen {
override val content: @Composable BoxScope.() -> Unit
get() = {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
diff --git a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/OpenOnPhoneDialogScreen.kt b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/OpenOnPhoneDialogScreen.kt
index 1515b55..de4901b 100644
--- a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/OpenOnPhoneDialogScreen.kt
+++ b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/OpenOnPhoneDialogScreen.kt
@@ -35,9 +35,10 @@
import androidx.wear.compose.material3.FilledTonalButton
import androidx.wear.compose.material3.OpenOnPhoneDialog
import androidx.wear.compose.material3.Text
+import androidx.wear.compose.material3.macrobenchmark.common.MacrobenchmarkScreen
val OpenOnPhoneDialogScreen =
- object : BaselineProfileScreen {
+ object : MacrobenchmarkScreen {
override val content: @Composable BoxScope.() -> Unit
get() = {
Column {
diff --git a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/PageIndicatorScreen.kt b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/PageIndicatorScreen.kt
index 1c069dc..c7c67f4 100644
--- a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/PageIndicatorScreen.kt
+++ b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/PageIndicatorScreen.kt
@@ -18,10 +18,11 @@
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.runtime.Composable
+import androidx.wear.compose.material3.macrobenchmark.common.MacrobenchmarkScreen
import androidx.wear.compose.material3.samples.VerticalPageIndicatorWithPagerSample
val PageIndicatorScreen =
- object : BaselineProfileScreen {
+ object : MacrobenchmarkScreen {
override val content: @Composable BoxScope.() -> Unit
get() = { VerticalPageIndicatorWithPagerSample() }
}
diff --git a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/PickerGroupScreen.kt b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/PickerGroupScreen.kt
index 037263f..4a31a18 100644
--- a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/PickerGroupScreen.kt
+++ b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/PickerGroupScreen.kt
@@ -18,10 +18,11 @@
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.runtime.Composable
+import androidx.wear.compose.material3.macrobenchmark.common.MacrobenchmarkScreen
import androidx.wear.compose.material3.samples.AutoCenteringPickerGroup
val PickerGroupScreen =
- object : BaselineProfileScreen {
+ object : MacrobenchmarkScreen {
override val content: @Composable (BoxScope.() -> Unit)
get() = { AutoCenteringPickerGroup() }
}
diff --git a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/PickerScreen.kt b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/PickerScreen.kt
index ba68003..3b5c299 100644
--- a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/PickerScreen.kt
+++ b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/PickerScreen.kt
@@ -18,10 +18,11 @@
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.runtime.Composable
+import androidx.wear.compose.material3.macrobenchmark.common.MacrobenchmarkScreen
import androidx.wear.compose.material3.samples.PickerScrollToOption
val PickerScreen =
- object : BaselineProfileScreen {
+ object : MacrobenchmarkScreen {
override val content: @Composable BoxScope.() -> Unit
get() = { PickerScrollToOption() }
}
diff --git a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/PlaceHolderScreen.kt b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/PlaceHolderScreen.kt
index 1dd1def..5af8a5a 100644
--- a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/PlaceHolderScreen.kt
+++ b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/PlaceHolderScreen.kt
@@ -22,12 +22,13 @@
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
+import androidx.wear.compose.material3.macrobenchmark.common.MacrobenchmarkScreen
import androidx.wear.compose.material3.samples.ButtonWithIconAndLabelAndPlaceholders
import androidx.wear.compose.material3.samples.ButtonWithIconAndLabelsAndOverlaidPlaceholder
import androidx.wear.compose.material3.samples.TextPlaceholder
val PlaceHolderScreen =
- object : BaselineProfileScreen {
+ object : MacrobenchmarkScreen {
override val content: @Composable BoxScope.() -> Unit
get() = {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
diff --git a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/ProgressIndicatorScreen.kt b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/ProgressIndicatorScreen.kt
index 8c3a78b..c9c5b8e 100644
--- a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/ProgressIndicatorScreen.kt
+++ b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/ProgressIndicatorScreen.kt
@@ -18,6 +18,7 @@
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.runtime.Composable
+import androidx.wear.compose.material3.macrobenchmark.common.MacrobenchmarkScreen
import androidx.wear.compose.material3.samples.FullScreenProgressIndicatorSample
import androidx.wear.compose.material3.samples.IndeterminateProgressIndicatorSample
import androidx.wear.compose.material3.samples.MediaButtonProgressIndicatorSample
@@ -28,7 +29,7 @@
import androidx.wear.compose.material3.samples.SmallValuesProgressIndicatorSample
val ProgressIndicatorScreen =
- object : BaselineProfileScreen {
+ object : MacrobenchmarkScreen {
override val content: @Composable BoxScope.() -> Unit
get() = {
FullScreenProgressIndicatorSample()
diff --git a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/RadioButtonScreen.kt b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/RadioButtonScreen.kt
index db6b300..bff77db 100644
--- a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/RadioButtonScreen.kt
+++ b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/RadioButtonScreen.kt
@@ -22,11 +22,12 @@
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
+import androidx.wear.compose.material3.macrobenchmark.common.MacrobenchmarkScreen
import androidx.wear.compose.material3.samples.RadioButtonSample
import androidx.wear.compose.material3.samples.SplitRadioButtonSample
val RadioButtonScreen =
- object : BaselineProfileScreen {
+ object : MacrobenchmarkScreen {
override val content: @Composable BoxScope.() -> Unit
get() = {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
diff --git a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/ScaffoldScreen.kt b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/ScaffoldScreen.kt
index 8a63b05..462a87f 100644
--- a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/ScaffoldScreen.kt
+++ b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/ScaffoldScreen.kt
@@ -18,10 +18,11 @@
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.runtime.Composable
+import androidx.wear.compose.material3.macrobenchmark.common.MacrobenchmarkScreen
import androidx.wear.compose.material3.samples.ScaffoldSample
val ScaffoldScreen =
- object : BaselineProfileScreen {
+ object : MacrobenchmarkScreen {
override val content: @Composable BoxScope.() -> Unit
get() = { ScaffoldSample() }
diff --git a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/ScrollIndicatorScreen.kt b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/ScrollIndicatorScreen.kt
index 3929d0e..088a27d 100644
--- a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/ScrollIndicatorScreen.kt
+++ b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/ScrollIndicatorScreen.kt
@@ -18,10 +18,11 @@
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.runtime.Composable
+import androidx.wear.compose.material3.macrobenchmark.common.MacrobenchmarkScreen
import androidx.wear.compose.material3.samples.ScrollIndicatorWithColumnSample
val ScrollIndicatorScreen =
- object : BaselineProfileScreen {
+ object : MacrobenchmarkScreen {
override val content: @Composable BoxScope.() -> Unit
get() = { ScrollIndicatorWithColumnSample() }
}
diff --git a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/SliderScreen.kt b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/SliderScreen.kt
index 5d133de..c622a31 100644
--- a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/SliderScreen.kt
+++ b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/SliderScreen.kt
@@ -23,6 +23,7 @@
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
+import androidx.wear.compose.material3.macrobenchmark.common.MacrobenchmarkScreen
import androidx.wear.compose.material3.macrobenchmark.common.scrollDown
import androidx.wear.compose.material3.samples.ChangedSliderSample
import androidx.wear.compose.material3.samples.SliderSample
@@ -30,7 +31,7 @@
import androidx.wear.compose.material3.samples.SliderWithIntegerSample
val SliderScreen =
- object : BaselineProfileScreen {
+ object : MacrobenchmarkScreen {
override val content: @Composable BoxScope.() -> Unit
get() = {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
diff --git a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/StepperScreen.kt b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/StepperScreen.kt
index b262e91..6c89908 100644
--- a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/StepperScreen.kt
+++ b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/StepperScreen.kt
@@ -18,10 +18,11 @@
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.runtime.Composable
+import androidx.wear.compose.material3.macrobenchmark.common.MacrobenchmarkScreen
import androidx.wear.compose.material3.samples.StepperWithButtonSample
val StepperScreen =
- object : BaselineProfileScreen {
+ object : MacrobenchmarkScreen {
override val content: @Composable BoxScope.() -> Unit
get() = { StepperWithButtonSample() }
}
diff --git a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/SwipeToDismissScreen.kt b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/SwipeToDismissScreen.kt
index 80fe730..482604e 100644
--- a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/SwipeToDismissScreen.kt
+++ b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/SwipeToDismissScreen.kt
@@ -18,10 +18,11 @@
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.runtime.Composable
+import androidx.wear.compose.material3.macrobenchmark.common.MacrobenchmarkScreen
import androidx.wear.compose.material3.samples.StatefulSwipeToDismissBox
val SwipeToDismissScreen =
- object : BaselineProfileScreen {
+ object : MacrobenchmarkScreen {
override val content: @Composable BoxScope.() -> Unit
get() = { StatefulSwipeToDismissBox() }
}
diff --git a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/SwipeToRevealScreen.kt b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/SwipeToRevealScreen.kt
index 7356bc9..a940608 100644
--- a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/SwipeToRevealScreen.kt
+++ b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/SwipeToRevealScreen.kt
@@ -18,10 +18,11 @@
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.runtime.Composable
+import androidx.wear.compose.material3.macrobenchmark.common.MacrobenchmarkScreen
import androidx.wear.compose.material3.samples.SwipeToRevealSample
val SwipeToRevealScreen =
- object : BaselineProfileScreen {
+ object : MacrobenchmarkScreen {
override val content: @Composable BoxScope.() -> Unit
get() = { SwipeToRevealSample() }
}
diff --git a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/SwitchButtonScreen.kt b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/SwitchButtonScreen.kt
index e9e98188..2e70194 100644
--- a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/SwitchButtonScreen.kt
+++ b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/SwitchButtonScreen.kt
@@ -22,11 +22,12 @@
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
+import androidx.wear.compose.material3.macrobenchmark.common.MacrobenchmarkScreen
import androidx.wear.compose.material3.samples.SplitSwitchButtonSample
import androidx.wear.compose.material3.samples.SwitchButtonSample
val SwitchButtonScreen =
- object : BaselineProfileScreen {
+ object : MacrobenchmarkScreen {
override val content: @Composable BoxScope.() -> Unit
get() = {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
diff --git a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/TextButtonScreen.kt b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/TextButtonScreen.kt
index b9b82e7..e1d202e 100644
--- a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/TextButtonScreen.kt
+++ b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/TextButtonScreen.kt
@@ -24,6 +24,7 @@
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
+import androidx.wear.compose.material3.macrobenchmark.common.MacrobenchmarkScreen
import androidx.wear.compose.material3.samples.FilledTextButtonSample
import androidx.wear.compose.material3.samples.FilledTonalTextButtonSample
import androidx.wear.compose.material3.samples.FilledVariantTextButtonSample
@@ -34,7 +35,7 @@
@OptIn(ExperimentalLayoutApi::class)
val TextButtonScreen =
- object : BaselineProfileScreen {
+ object : MacrobenchmarkScreen {
override val content: @Composable (BoxScope.() -> Unit)
get() = {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
diff --git a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/TextToggleButtonScreen.kt b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/TextToggleButtonScreen.kt
index 72a2aa4..833143d 100644
--- a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/TextToggleButtonScreen.kt
+++ b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/TextToggleButtonScreen.kt
@@ -35,12 +35,13 @@
import androidx.wear.compose.material3.TextToggleButton
import androidx.wear.compose.material3.TextToggleButtonDefaults
import androidx.wear.compose.material3.macrobenchmark.common.FIND_OBJECT_TIMEOUT_MS
+import androidx.wear.compose.material3.macrobenchmark.common.MacrobenchmarkScreen
import androidx.wear.compose.material3.samples.LargeTextToggleButtonSample
import androidx.wear.compose.material3.samples.TextToggleButtonSample
@OptIn(ExperimentalLayoutApi::class)
val TextToggleButtonScreen =
- object : BaselineProfileScreen {
+ object : MacrobenchmarkScreen {
override val content: @Composable (BoxScope.() -> Unit)
get() = {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
diff --git a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/TimePickerScreen.kt b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/TimePickerScreen.kt
index 8022686..f506e50 100644
--- a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/TimePickerScreen.kt
+++ b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/TimePickerScreen.kt
@@ -18,10 +18,11 @@
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.runtime.Composable
+import androidx.wear.compose.material3.macrobenchmark.common.MacrobenchmarkScreen
import androidx.wear.compose.material3.samples.TimePickerSample
val TimePickerScreen =
- object : BaselineProfileScreen {
+ object : MacrobenchmarkScreen {
override val content: @Composable BoxScope.() -> Unit
get() = { TimePickerSample() }
}
diff --git a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/TimeTextScreen.kt b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/TimeTextScreen.kt
index e2b5bb9..5565366 100644
--- a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/TimeTextScreen.kt
+++ b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/TimeTextScreen.kt
@@ -18,10 +18,11 @@
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.runtime.Composable
+import androidx.wear.compose.material3.macrobenchmark.common.MacrobenchmarkScreen
import androidx.wear.compose.material3.samples.TimeTextWithStatus
val TimeTextScreen =
- object : BaselineProfileScreen {
+ object : MacrobenchmarkScreen {
override val content: @Composable BoxScope.() -> Unit
get() = { TimeTextWithStatus() }
}
diff --git a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/TransformingLazyColumnScreen.kt b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/TransformingLazyColumnScreen.kt
index ffb08e1..71ba2d3 100644
--- a/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/TransformingLazyColumnScreen.kt
+++ b/wear/compose/compose-material3/macrobenchmark-common/src/main/java/androidx/wear/compose/material3/macrobenchmark/common/baselineprofile/TransformingLazyColumnScreen.kt
@@ -18,10 +18,11 @@
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.runtime.Composable
+import androidx.wear.compose.material3.macrobenchmark.common.MacrobenchmarkScreen
import androidx.wear.compose.material3.samples.TransformingLazyColumnScalingMorphingEffectSample
val TransformingLazyColumnScreen =
- object : BaselineProfileScreen {
+ object : MacrobenchmarkScreen {
override val content: @Composable BoxScope.() -> Unit
get() = { TransformingLazyColumnScalingMorphingEffectSample() }
}
diff --git a/wear/compose/compose-material3/macrobenchmark-target/src/main/AndroidManifest.xml b/wear/compose/compose-material3/macrobenchmark-target/src/main/AndroidManifest.xml
index 40010e2..9e2ee38 100644
--- a/wear/compose/compose-material3/macrobenchmark-target/src/main/AndroidManifest.xml
+++ b/wear/compose/compose-material3/macrobenchmark-target/src/main/AndroidManifest.xml
@@ -43,6 +43,17 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
+
+ <activity
+ android:name=".TransformingLazyColumnActivity"
+ android:theme="@style/AppTheme"
+ android:exported="true">
+ <intent-filter>
+ <action android:name=
+ "androidx.wear.compose.material3.macrobenchmark.target.TRANSFORMING_LAZY_COLUMN_ACTIVITY" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
</application>
<uses-permission android:name="android.permission.WAKE_LOCK" />
diff --git a/wear/compose/compose-material3/macrobenchmark-target/src/main/java/androidx/wear/compose/material3/macrobenchmark/target/TransformingLazyColumnActivity.kt b/wear/compose/compose-material3/macrobenchmark-target/src/main/java/androidx/wear/compose/material3/macrobenchmark/target/TransformingLazyColumnActivity.kt
new file mode 100644
index 0000000..7bdd7eb
--- /dev/null
+++ b/wear/compose/compose-material3/macrobenchmark-target/src/main/java/androidx/wear/compose/material3/macrobenchmark/target/TransformingLazyColumnActivity.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material3.macrobenchmark.target
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.wear.compose.material3.MaterialTheme
+import androidx.wear.compose.material3.macrobenchmark.common.TransformingLazyColumnBenchmark
+
+class TransformingLazyColumnActivity : ComponentActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContent {
+ MaterialTheme {
+ Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+ TransformingLazyColumnBenchmark.content.invoke(this)
+ }
+ }
+ }
+ }
+}
diff --git a/wear/compose/compose-material3/macrobenchmark/src/main/java/androidx/wear/compose/material3/macrobenchmark/BaselineProfile.kt b/wear/compose/compose-material3/macrobenchmark/src/main/java/androidx/wear/compose/material3/macrobenchmark/BaselineProfile.kt
index 168f053..254e765 100644
--- a/wear/compose/compose-material3/macrobenchmark/src/main/java/androidx/wear/compose/material3/macrobenchmark/BaselineProfile.kt
+++ b/wear/compose/compose-material3/macrobenchmark/src/main/java/androidx/wear/compose/material3/macrobenchmark/BaselineProfile.kt
@@ -78,7 +78,6 @@
}
companion object {
- private const val PACKAGE_NAME = "androidx.wear.compose.material3.macrobenchmark.target"
private const val BASELINE_ACTIVITY = "$PACKAGE_NAME.BASELINE_ACTIVITY"
}
}
diff --git a/wear/compose/compose-material3/macrobenchmark/src/main/java/androidx/wear/compose/material3/macrobenchmark/Common.kt b/wear/compose/compose-material3/macrobenchmark/src/main/java/androidx/wear/compose/material3/macrobenchmark/Common.kt
new file mode 100644
index 0000000..0a9b71d
--- /dev/null
+++ b/wear/compose/compose-material3/macrobenchmark/src/main/java/androidx/wear/compose/material3/macrobenchmark/Common.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material3.macrobenchmark
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+
+internal fun disableChargingExperience() {
+ val instrumentation = InstrumentationRegistry.getInstrumentation()
+ val device = UiDevice.getInstance(instrumentation)
+ device.executeShellCommand(
+ "am broadcast -a " +
+ "com.google.android.clockwork.sysui.charging.ENABLE_CHARGING_EXPERIENCE " +
+ "--ez value \"false\" com.google.android.wearable.sysui"
+ )
+}
+
+internal fun enableChargingExperience() {
+ val instrumentation = InstrumentationRegistry.getInstrumentation()
+ val device = UiDevice.getInstance(instrumentation)
+ device.executeShellCommand(
+ "am broadcast -a " +
+ "com.google.android.clockwork.sysui.charging.ENABLE_CHARGING_EXPERIENCE " +
+ "--ez value \"true\" com.google.android.wearable.sysui"
+ )
+}
+
+internal const val PACKAGE_NAME = "androidx.wear.compose.material3.macrobenchmark.target"
diff --git a/wear/compose/compose-material3/macrobenchmark/src/main/java/androidx/wear/compose/material3/macrobenchmark/TransformingLazyColumnBenchmark.kt b/wear/compose/compose-material3/macrobenchmark/src/main/java/androidx/wear/compose/material3/macrobenchmark/TransformingLazyColumnBenchmark.kt
new file mode 100644
index 0000000..1f82ab3
--- /dev/null
+++ b/wear/compose/compose-material3/macrobenchmark/src/main/java/androidx/wear/compose/material3/macrobenchmark/TransformingLazyColumnBenchmark.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material3.macrobenchmark
+
+import android.content.Intent
+import androidx.benchmark.macro.CompilationMode
+import androidx.benchmark.macro.FrameTimingMetric
+import androidx.benchmark.macro.junit4.MacrobenchmarkRule
+import androidx.test.filters.LargeTest
+import androidx.testutils.createCompilationParams
+import androidx.wear.compose.material3.macrobenchmark.common.TransformingLazyColumnBenchmark
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@LargeTest
+@RunWith(Parameterized::class)
+class TransformingLazyColumnBenchmark(private val compilationMode: CompilationMode) {
+ @get:Rule val benchmarkRule = MacrobenchmarkRule()
+
+ @Before
+ fun setUp() {
+ disableChargingExperience()
+ }
+
+ @After
+ fun destroy() {
+ enableChargingExperience()
+ }
+
+ @Test
+ fun start() {
+ benchmarkRule.measureRepeated(
+ packageName = PACKAGE_NAME,
+ metrics = listOf(FrameTimingMetric()),
+ compilationMode = compilationMode,
+ iterations = 10,
+ setupBlock = {
+ val intent = Intent()
+ intent.action = TRANSFORMING_LAZY_COLUMN_ACTIVITY
+ startActivityAndWait(intent)
+ }
+ ) {
+ TransformingLazyColumnBenchmark.exercise.invoke(this)
+ }
+ }
+
+ companion object {
+
+ private const val TRANSFORMING_LAZY_COLUMN_ACTIVITY =
+ "$PACKAGE_NAME.TRANSFORMING_LAZY_COLUMN_ACTIVITY"
+
+ @Parameterized.Parameters(name = "compilation={0}")
+ @JvmStatic
+ fun parameters() = createCompilationParams()
+ }
+}
diff --git a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/PlaceholderSample.kt b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/PlaceholderSample.kt
index fedc367..50aa05f 100644
--- a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/PlaceholderSample.kt
+++ b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/PlaceholderSample.kt
@@ -106,10 +106,8 @@
delay(1000)
labelText = "A label"
}
- if (!buttonPlaceholderState.isShowContent) {
- LaunchedEffect(buttonPlaceholderState) {
- buttonPlaceholderState.startPlaceholderAnimation()
- }
+ if (!buttonPlaceholderState.isHidden) {
+ LaunchedEffect(buttonPlaceholderState) { buttonPlaceholderState.animatePlaceholder() }
}
}
@@ -135,7 +133,7 @@
labelText.isNotEmpty() && secondaryLabelText.isNotEmpty() && imageVector != null
}
Box {
- if (buttonPlaceholderState.isShowContent || buttonPlaceholderState.isWipeOff) {
+ if (buttonPlaceholderState.isHidden || buttonPlaceholderState.isWipingOff) {
Button(
onClick = { /* Do something */ },
enabled = true,
@@ -171,7 +169,7 @@
modifier = Modifier.fillMaxWidth()
)
}
- if (!buttonPlaceholderState.isShowContent) {
+ if (!buttonPlaceholderState.isHidden) {
Button(
onClick = { /* Do something */ },
enabled = true,
@@ -220,10 +218,8 @@
delay(500)
labelText = "A label"
}
- if (!buttonPlaceholderState.isShowContent) {
- LaunchedEffect(buttonPlaceholderState) {
- buttonPlaceholderState.startPlaceholderAnimation()
- }
+ if (!buttonPlaceholderState.isHidden) {
+ LaunchedEffect(buttonPlaceholderState) { buttonPlaceholderState.animatePlaceholder() }
}
}
@@ -255,9 +251,7 @@
delay(3000)
labelText = "A label"
}
- if (!buttonPlaceholderState.isShowContent) {
- LaunchedEffect(buttonPlaceholderState) {
- buttonPlaceholderState.startPlaceholderAnimation()
- }
+ if (!buttonPlaceholderState.isHidden) {
+ LaunchedEffect(buttonPlaceholderState) { buttonPlaceholderState.animatePlaceholder() }
}
}
diff --git a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/ProgressIndicatorSample.kt b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/ProgressIndicatorSample.kt
index 8f82fcd2..83573a4 100644
--- a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/ProgressIndicatorSample.kt
+++ b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/ProgressIndicatorSample.kt
@@ -38,6 +38,8 @@
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
+import androidx.wear.compose.material3.ArcProgressIndicator
+import androidx.wear.compose.material3.ArcProgressIndicatorDefaults
import androidx.wear.compose.material3.CircularProgressIndicator
import androidx.wear.compose.material3.CircularProgressIndicatorDefaults
import androidx.wear.compose.material3.Icon
@@ -161,6 +163,18 @@
@Sampled
@Composable
+fun IndeterminateProgressArcSample() {
+ Box(modifier = Modifier.fillMaxSize()) {
+ ArcProgressIndicator(
+ modifier =
+ Modifier.align(Alignment.Center)
+ .size(ArcProgressIndicatorDefaults.recommendedIndeterminateDiameter),
+ )
+ }
+}
+
+@Sampled
+@Composable
fun SegmentedProgressIndicatorSample() {
Box(
modifier =
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ArcProgressIndicatorScreenshotTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ArcProgressIndicatorScreenshotTest.kt
new file mode 100644
index 0000000..e31504f
--- /dev/null
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ArcProgressIndicatorScreenshotTest.kt
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material3
+
+import android.os.Build
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.testutils.assertAgainstGolden
+import androidx.compose.ui.Alignment
+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.onNodeWithTag
+import androidx.compose.ui.unit.dp
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.screenshot.AndroidXScreenshotTestRule
+import com.google.testing.junit.testparameterinjector.TestParameter
+import com.google.testing.junit.testparameterinjector.TestParameterInjector
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TestName
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(TestParameterInjector::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+class ArcProgressIndicatorScreenshotTest {
+
+ @get:Rule val rule = createComposeRule()
+
+ @get:Rule val screenshotRule = AndroidXScreenshotTestRule(SCREENSHOT_GOLDEN_PATH)
+
+ @get:Rule val testName = TestName()
+
+ @Test
+ fun arc_progress_indicator_starts_empty(@TestParameter screenSize: ScreenSize) {
+ verifyScreenshot(screenSize, 0L) {
+ ArcProgressIndicator(
+ modifier =
+ Modifier.testTag(TEST_TAG)
+ .align(Alignment.Center)
+ .size(ArcProgressIndicatorDefaults.recommendedIndeterminateDiameter),
+ )
+ }
+ }
+
+ @Test
+ fun arc_progress_indicator_500ms(@TestParameter screenSize: ScreenSize) {
+ verifyScreenshot(screenSize, 500L) {
+ ArcProgressIndicator(
+ modifier =
+ Modifier.testTag(TEST_TAG)
+ .align(Alignment.Center)
+ .size(ArcProgressIndicatorDefaults.recommendedIndeterminateDiameter),
+ )
+ }
+ }
+
+ @Test
+ fun arc_progress_indicator_500ms_clockwise(@TestParameter screenSize: ScreenSize) {
+ verifyScreenshot(screenSize, 500L) {
+ ArcProgressIndicator(
+ modifier =
+ Modifier.testTag(TEST_TAG)
+ .align(Alignment.Center)
+ .size(ArcProgressIndicatorDefaults.recommendedIndeterminateDiameter),
+ angularDirection = AngularDirection.Clockwise
+ )
+ }
+ }
+
+ @Test
+ fun arc_progress_indicator_angles(@TestParameter screenSize: ScreenSize) {
+ verifyScreenshot(screenSize, 500L) {
+ ArcProgressIndicator(
+ modifier =
+ Modifier.testTag(TEST_TAG)
+ .align(Alignment.Center)
+ .size(ArcProgressIndicatorDefaults.recommendedIndeterminateDiameter),
+ startAngle = 0f,
+ endAngle = 180f,
+ )
+ }
+ }
+
+ @Test
+ fun arc_progress_indicator_diameter(@TestParameter screenSize: ScreenSize) {
+ verifyScreenshot(screenSize, 500L) {
+ ArcProgressIndicator(
+ modifier =
+ Modifier.testTag(TEST_TAG).align(Alignment.Center).size(screenSize.size.dp),
+ )
+ }
+ }
+
+ @Test
+ fun arc_progress_indicator_strokewidth() {
+ verifyScreenshot(ScreenSize.LARGE, 500L) {
+ ArcProgressIndicator(
+ modifier =
+ Modifier.testTag(TEST_TAG)
+ .align(Alignment.Center)
+ .size(ArcProgressIndicatorDefaults.recommendedIndeterminateDiameter),
+ strokeWidth = ArcProgressIndicatorDefaults.IndeterminateStrokeWidth * 2
+ )
+ }
+ }
+
+ private fun verifyScreenshot(
+ screenSize: ScreenSize,
+ milliseconds: Long,
+ content: @Composable (BoxScope.() -> Unit)
+ ) {
+ rule.mainClock.autoAdvance = false
+
+ rule.setContentWithTheme(modifier = Modifier.background(Color.Black)) {
+ ScreenConfiguration(screenSize.size) {
+ Box(modifier = Modifier.fillMaxSize()) { content() }
+ }
+ }
+
+ rule.mainClock.advanceTimeBy(milliseconds)
+
+ rule
+ .onNodeWithTag(TEST_TAG)
+ .captureToImage()
+ .assertAgainstGolden(screenshotRule, testName.goldenIdentifier())
+ }
+}
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ArcProgressIndicatorTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ArcProgressIndicatorTest.kt
new file mode 100644
index 0000000..ca41d31
--- /dev/null
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ArcProgressIndicatorTest.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material3
+
+import android.os.Build
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.testutils.assertContainsColor
+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.onNodeWithTag
+import androidx.compose.ui.unit.dp
+import androidx.test.filters.SdkSuppress
+import org.junit.Rule
+import org.junit.Test
+
+class ArcProgressIndicatorTest {
+
+ @get:Rule val rule = createComposeRule()
+
+ @Test
+ fun indeterminate_arc_supports_testtag() {
+ setContentWithTheme { ArcProgressIndicator(modifier = Modifier.testTag(TEST_TAG)) }
+
+ rule.onNodeWithTag(TEST_TAG).assertExists()
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ @Test
+ fun indeterminate_arc_contains_default_indicator_color() {
+ rule.mainClock.autoAdvance = false
+ var expectedColor = Color.Unspecified
+
+ setContentWithTheme {
+ ArcProgressIndicator(
+ modifier = Modifier.size(COMPONENT_SIZE).testTag(TEST_TAG),
+ )
+ expectedColor = MaterialTheme.colorScheme.primary
+ }
+
+ rule.mainClock.advanceTimeBy(250)
+
+ rule.onNodeWithTag(TEST_TAG).captureToImage().assertContainsColor(expectedColor)
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ @Test
+ fun indeterminate_arc_contains_custom_indicator_color() {
+ rule.mainClock.autoAdvance = false
+ val customColor = Color.Yellow
+
+ setContentWithTheme {
+ ArcProgressIndicator(
+ modifier = Modifier.size(COMPONENT_SIZE).testTag(TEST_TAG),
+ colors = ProgressIndicatorDefaults.colors(indicatorColor = customColor)
+ )
+ }
+
+ rule.mainClock.advanceTimeBy(250)
+
+ rule.onNodeWithTag(TEST_TAG).captureToImage().assertContainsColor(customColor)
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ @Test
+ fun indeterminate_arc_contains_default_track_color() {
+ rule.mainClock.autoAdvance = false
+ var expectedColor = Color.Unspecified
+
+ setContentWithTheme {
+ ArcProgressIndicator(
+ modifier = Modifier.size(COMPONENT_SIZE).testTag(TEST_TAG),
+ )
+ expectedColor = MaterialTheme.colorScheme.surfaceContainer
+ }
+
+ rule.mainClock.advanceTimeBy(250)
+
+ rule.onNodeWithTag(TEST_TAG).captureToImage().assertContainsColor(expectedColor)
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ @Test
+ fun indeterminate_arc_contains_custom_track_color() {
+ rule.mainClock.autoAdvance = false
+ val customColor = Color.Yellow
+
+ setContentWithTheme {
+ ArcProgressIndicator(
+ modifier = Modifier.size(COMPONENT_SIZE).testTag(TEST_TAG),
+ colors = ProgressIndicatorDefaults.colors(trackColor = customColor)
+ )
+ }
+
+ rule.mainClock.advanceTimeBy(250)
+
+ rule.onNodeWithTag(TEST_TAG).captureToImage().assertContainsColor(customColor)
+ }
+
+ private fun setContentWithTheme(composable: @Composable BoxScope.() -> Unit) {
+ // Use constant size modifier to limit relative color percentage ranges.
+ rule.setContentWithTheme(modifier = Modifier.size(COMPONENT_SIZE)) {
+ ScreenConfiguration(SCREEN_SIZE_LARGE) { composable() }
+ }
+ }
+}
+
+private val COMPONENT_SIZE = 204.dp
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconButtonTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconButtonTest.kt
index 5e3ffa6..e13753d 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconButtonTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconButtonTest.kt
@@ -317,6 +317,7 @@
baseShape,
pressedShape,
0.75f,
+ 8,
color = { IconButtonDefaults.filledIconButtonColors().containerColor }
) { modifier ->
FilledIconButton(
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 6c5c90e..cee8cc6 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
@@ -23,10 +23,13 @@
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.testutils.assertContainsColor
import androidx.compose.testutils.assertShape
import androidx.compose.ui.Modifier
@@ -621,6 +624,90 @@
}
@RequiresApi(Build.VERSION_CODES.O)
+ @Test
+ fun animates_corners_to_75_percent_on_click() {
+ val uncheckedShape = RoundedCornerShape(20.dp)
+ val checkedShape = RoundedCornerShape(10.dp)
+ val pressedShape = RoundedCornerShape(0.dp)
+ // Ignore the color transition from unchecked to checked color
+ val colors =
+ IconToggleButtonColors(
+ Color.Black,
+ Color.Black,
+ Color.Black,
+ Color.Black,
+ Color.Black,
+ Color.Black,
+ Color.Black,
+ Color.Black
+ )
+
+ rule.verifyRoundedButtonTapAnimationEnd(
+ uncheckedShape,
+ pressedShape,
+ 0.75f,
+ 8,
+ color = { colors.checkedContainerColor }
+ ) { modifier ->
+ IconToggleButton(
+ checked = false,
+ onCheckedChange = {},
+ modifier = modifier,
+ shapes = IconToggleButtonShapes(uncheckedShape, checkedShape, pressedShape),
+ colors = colors
+ ) {}
+ }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ @Test
+ fun changes_unchecked_to_checked_shape_on_click() {
+ val uncheckedShape = RoundedCornerShape(20.dp)
+ val checkedShape = RoundedCornerShape(10.dp)
+ val pressedShape = RoundedCornerShape(0.dp)
+ rule.verifyRoundedButtonTapAnimationEnd(
+ uncheckedShape,
+ checkedShape,
+ 1f,
+ 100,
+ color = { IconToggleButtonDefaults.iconToggleButtonColors().checkedContainerColor },
+ antiAliasingGap = 4f,
+ ) { modifier ->
+ var checked by remember { mutableStateOf(false) }
+ IconToggleButton(
+ checked = checked,
+ onCheckedChange = { checked = !checked },
+ modifier = modifier,
+ shapes = IconToggleButtonShapes(uncheckedShape, checkedShape, pressedShape)
+ ) {}
+ }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ @Test
+ fun changes_checked_to_unchecked_shape_on_click() {
+ val uncheckedShape = RoundedCornerShape(10.dp)
+ val checkedShape = RoundedCornerShape(20.dp)
+ val pressedShape = RoundedCornerShape(0.dp)
+ rule.verifyRoundedButtonTapAnimationEnd(
+ checkedShape,
+ uncheckedShape,
+ 1f,
+ 100,
+ color = { IconToggleButtonDefaults.iconToggleButtonColors().uncheckedContainerColor },
+ antiAliasingGap = 4f,
+ ) { modifier ->
+ var checked by remember { mutableStateOf(true) }
+ IconToggleButton(
+ checked = checked,
+ onCheckedChange = { checked = !checked },
+ modifier = modifier,
+ shapes = IconToggleButtonShapes(uncheckedShape, checkedShape, pressedShape)
+ ) {}
+ }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.O)
private fun ComposeContentTestRule.verifyIconToggleButtonColors(
status: Status,
checked: Boolean,
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/Material3Test.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/Material3Test.kt
index f383b29..adb74f9 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/Material3Test.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/Material3Test.kt
@@ -403,7 +403,9 @@
baseShape: RoundedCornerShape,
pressedShape: RoundedCornerShape,
targetProgress: Float,
+ expectedFramesUntilTarget: Int,
color: @Composable () -> Color,
+ antiAliasingGap: Float = 2f,
content: @Composable (Modifier) -> Unit
) {
val expectedAnimationEnd =
@@ -426,7 +428,7 @@
* 2) rule.mainClock.waitUntil expects a condition. However, the shape validations for
* ImageBitMap only includes of assets
*/
- repeat(8) { mainClock.advanceTimeByFrame() }
+ repeat(expectedFramesUntilTarget) { mainClock.advanceTimeByFrame() }
onNodeWithTag(TEST_TAG)
.captureToImage()
@@ -436,7 +438,7 @@
verticalPadding = 0.dp,
shapeColor = fillColor,
backgroundColor = Color.Transparent,
- antiAliasingGap = 2.0f,
+ antiAliasingGap = antiAliasingGap,
shape = expectedAnimationEnd,
)
}
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/PlaceholderTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/PlaceholderTest.kt
index 9383e90..3234616 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/PlaceholderTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/PlaceholderTest.kt
@@ -57,12 +57,12 @@
}
// For testing we need to manually manage the frame clock for the placeholder animation
- placeholderState.initializeTestFrameMillis(PlaceholderStage.ShowContent)
+ placeholderState.initializeTestFrameMillis(PlaceholderStage.HidePlaceholder)
// Advance placeholder clock without changing the content ready and confirm still in
// ShowPlaceholder
placeholderState.advanceToNextPlaceholderAnimationLoopAndCheckStage(
- PlaceholderStage.ShowContent
+ PlaceholderStage.HidePlaceholder
)
}
@@ -92,7 +92,7 @@
// Advance the clock by one cycle and check we have moved to ShowContent
placeholderState.advanceFrameMillisAndCheckState(
PLACEHOLDER_WIPE_OFF_PROGRESSION_DURATION_MS,
- PlaceholderStage.ShowContent
+ PlaceholderStage.HidePlaceholder
)
}
@@ -112,12 +112,12 @@
}
// For testing we need to manually manage the frame clock for the placeholder animation
- placeholderState.initializeTestFrameMillis(PlaceholderStage.ShowContent)
+ placeholderState.initializeTestFrameMillis(PlaceholderStage.HidePlaceholder)
// Advance placeholder clock without changing the content ready and confirm still in
// ShowPlaceholder
placeholderState.advanceToNextPlaceholderAnimationLoopAndCheckStage(
- PlaceholderStage.ShowContent
+ PlaceholderStage.HidePlaceholder
)
contentReady.value = false
@@ -182,7 +182,7 @@
// Advance the clock by one cycle and check we have moved to ShowContent
placeholderState.advanceFrameMillisAndCheckState(
PLACEHOLDER_WIPE_OFF_PROGRESSION_DURATION_MS,
- PlaceholderStage.ShowContent
+ PlaceholderStage.HidePlaceholder
)
rule.onNodeWithTag(TEST_TAG).captureToImage().assertContainsColor(expectedBackgroundColor)
@@ -240,7 +240,7 @@
// Advance the clock by one cycle and check we have moved to ShowContent
placeholderState.advanceFrameMillisAndCheckState(
PLACEHOLDER_WIPE_OFF_PROGRESSION_DURATION_MS,
- PlaceholderStage.ShowContent
+ PlaceholderStage.HidePlaceholder
)
// Check that the shimmer is no longer visible
@@ -317,7 +317,7 @@
// Now move the end of the wipe-off and confirm that the proper button background is visible
placeholderState.advanceFrameMillisAndCheckState(
PLACEHOLDER_WIPE_OFF_PROGRESSION_DURATION_MS,
- PlaceholderStage.ShowContent
+ PlaceholderStage.HidePlaceholder
)
// Check that normal button background is now visible
@@ -338,7 +338,7 @@
placeholderState = placeholderState,
),
)
- LaunchedEffect(placeholderState) { placeholderState.startPlaceholderAnimation() }
+ LaunchedEffect(placeholderState) { placeholderState.animatePlaceholder() }
}
@Test
@@ -366,7 +366,7 @@
placeholderState.value?.advanceFrameMillisAndCheckState(
PLACEHOLDER_WIPE_OFF_PROGRESSION_DURATION_MS,
- PlaceholderStage.ShowContent
+ PlaceholderStage.HidePlaceholder
)
}
@@ -392,7 +392,7 @@
placeholderState = placeholderState,
),
)
- LaunchedEffect(placeholderState) { placeholderState.startPlaceholderAnimation() }
+ LaunchedEffect(placeholderState) { placeholderState.animatePlaceholder() }
}
placeholderState.initializeTestFrameMillis()
@@ -408,7 +408,7 @@
placeholderState.advanceFrameMillisAndCheckState(
PLACEHOLDER_WIPE_OFF_PROGRESSION_DURATION_MS,
- PlaceholderStage.ShowContent
+ PlaceholderStage.HidePlaceholder
)
// Check the placeholder background has gone and that we can see the buttons background
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextButtonTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextButtonTest.kt
index 202c8b9..3a64620 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextButtonTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextButtonTest.kt
@@ -536,6 +536,7 @@
baseShape,
pressedShape,
0.75f,
+ 8,
color = { TextButtonDefaults.filledTextButtonColors().containerColor }
) { modifier ->
TextButton(
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextToggleButtonTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextToggleButtonTest.kt
index 1455f333..2b3d35e 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextToggleButtonTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextToggleButtonTest.kt
@@ -23,10 +23,13 @@
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.testutils.assertContainsColor
import androidx.compose.testutils.assertShape
import androidx.compose.ui.Modifier
@@ -604,6 +607,90 @@
.assert(SemanticsMatcher.expectValue(SemanticsProperties.Role, overrideRole))
}
+ @RequiresApi(Build.VERSION_CODES.O)
+ @Test
+ fun animates_corners_to_75_percent_on_click() {
+ val uncheckedShape = RoundedCornerShape(20.dp)
+ val checkedShape = RoundedCornerShape(10.dp)
+ val pressedShape = RoundedCornerShape(0.dp)
+ // Ignore the color transition from unchecked to checked color
+ val colors =
+ TextToggleButtonColors(
+ Color.Black,
+ Color.Black,
+ Color.Black,
+ Color.Black,
+ Color.Black,
+ Color.Black,
+ Color.Black,
+ Color.Black
+ )
+
+ rule.verifyRoundedButtonTapAnimationEnd(
+ uncheckedShape,
+ pressedShape,
+ 0.75f,
+ 8,
+ color = { colors.checkedContainerColor }
+ ) { modifier ->
+ TextToggleButton(
+ checked = false,
+ onCheckedChange = {},
+ modifier = modifier,
+ shapes = TextToggleButtonShapes(uncheckedShape, checkedShape, pressedShape),
+ colors = colors
+ ) {}
+ }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ @Test
+ fun changes_unchecked_to_checked_shape_on_click() {
+ val uncheckedShape = RoundedCornerShape(20.dp)
+ val checkedShape = RoundedCornerShape(10.dp)
+ val pressedShape = RoundedCornerShape(0.dp)
+ rule.verifyRoundedButtonTapAnimationEnd(
+ uncheckedShape,
+ checkedShape,
+ 1f,
+ 100,
+ color = { TextToggleButtonDefaults.textToggleButtonColors().checkedContainerColor },
+ antiAliasingGap = 4f,
+ ) { modifier ->
+ var checked by remember { mutableStateOf(false) }
+ TextToggleButton(
+ checked = checked,
+ onCheckedChange = { checked = !checked },
+ modifier = modifier,
+ shapes = TextToggleButtonShapes(uncheckedShape, checkedShape, pressedShape)
+ ) {}
+ }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ @Test
+ fun changes_checked_to_unchecked_shape_on_click() {
+ val uncheckedShape = RoundedCornerShape(10.dp)
+ val checkedShape = RoundedCornerShape(20.dp)
+ val pressedShape = RoundedCornerShape(0.dp)
+ rule.verifyRoundedButtonTapAnimationEnd(
+ checkedShape,
+ uncheckedShape,
+ 1f,
+ 100,
+ color = { TextToggleButtonDefaults.textToggleButtonColors().uncheckedContainerColor },
+ antiAliasingGap = 4f,
+ ) { modifier ->
+ var checked by remember { mutableStateOf(true) }
+ TextToggleButton(
+ checked = checked,
+ onCheckedChange = { checked = !checked },
+ modifier = modifier,
+ shapes = TextToggleButtonShapes(uncheckedShape, checkedShape, pressedShape)
+ ) {}
+ }
+ }
+
@Composable
private fun shapeColor(): Color {
return TextToggleButtonDefaults.textToggleButtonColors()
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/AnimatedToggleRoundedCornerShape.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/AnimatedToggleRoundedCornerShape.kt
index f660f8f..a4a8ebc 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/AnimatedToggleRoundedCornerShape.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/AnimatedToggleRoundedCornerShape.kt
@@ -16,49 +16,53 @@
package androidx.wear.compose.material3
+import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.FiniteAnimationSpec
-import androidx.compose.animation.core.animateFloat
-import androidx.compose.animation.core.updateTransition
import androidx.compose.foundation.shape.CornerSize
-import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.geometry.RoundRect
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.geometry.toRect
import androidx.compose.ui.graphics.Outline
import androidx.compose.ui.graphics.Shape
-import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.LayoutDirection
+import kotlinx.coroutines.launch
/**
- * A animated [RoundedCornerShape]. Animation is driven by changes to the [cornerSize] lambda.
- * [currentShapeSize] is provided as Size is received here, but must affect the animation.
+ * An implementation similar to RoundedCornerShape, but based on linear interpolation between a
+ * start and stop CornerSize, and an observable progress between 0.0 and 1.0.
*
- * @param currentShapeSize MutableState coordinating the current size.
- * @param cornerSize a lambda resolving to the current Corner size.
+ * @param startCornerSize the corner size when progress is 0.0
+ * @param endCornerSize the corner size when progress is 1.0
+ * @param progress returns the current progress from start to stop.
*/
@Stable
-internal class AnimatedToggleRoundedCornerShape(
- private val currentShapeSize: MutableState<Size?>,
- private val cornerSize: () -> CornerSize,
+private class AnimatedToggleRoundedCornerShape(
+ var startCornerSize: CornerSize,
+ var endCornerSize: CornerSize,
+ var progress: () -> Float,
) : Shape {
override fun createOutline(
size: Size,
layoutDirection: LayoutDirection,
density: Density,
): Outline {
- val cornerRadius = cornerSize().toPx(size, density)
-
- currentShapeSize.value = size
+ val animatedCornerSize = AnimatedCornerSize(startCornerSize, endCornerSize, progress)
+ val animatedCornerSizePx = animatedCornerSize.toPx(size, density)
return Outline.Rounded(
roundRect =
- RoundRect(rect = size.toRect(), radiusX = cornerRadius, radiusY = cornerRadius)
+ RoundRect(
+ rect = size.toRect(),
+ radiusX = animatedCornerSizePx,
+ radiusY = animatedCornerSizePx
+ )
)
}
}
@@ -84,49 +88,41 @@
else -> ToggleState.Unchecked
}
- val transition = updateTransition(toggleState, label = "Toggle State")
- val density = LocalDensity.current
+ val previous = remember { mutableStateOf(toggleState) }
+ val scope = rememberCoroutineScope()
+ val progress = remember { Animatable(1f) }
- val currentShapeSize = remember { mutableStateOf<Size?>(null) }
+ val toggledCornerSize =
+ toggleState.cornerSize(uncheckedCornerSize, checkedCornerSize, pressedCornerSize)
+ val animationSpec = if (pressed) onPressAnimationSpec else onReleaseAnimationSpec
- val observedSize = currentShapeSize.value
+ val animatedShape = remember {
+ AnimatedToggleRoundedCornerShape(
+ startCornerSize = toggledCornerSize,
+ endCornerSize = toggledCornerSize,
+ progress = { progress.value },
+ )
+ }
- if (observedSize != null) {
- val sizePx =
- transition.animateFloat(
- label = "Corner Size",
- transitionSpec = {
- when {
- targetState isTransitioningTo ToggleState.Pressed -> onPressAnimationSpec
- else -> onReleaseAnimationSpec
- }
- },
- ) { newState ->
- newState
- .cornerSize(uncheckedCornerSize, checkedCornerSize, pressedCornerSize)
- .toPx(observedSize, density)
- }
-
- return remember(sizePx) {
- AnimatedToggleRoundedCornerShape(
- currentShapeSize = currentShapeSize,
- ) {
- CornerSize(sizePx.value)
- }
+ LaunchedEffect(toggleState) {
+ // Allow the press up animation to finish its minimum duration before starting the next
+ if (!pressed) {
+ waitUntil { !progress.isRunning || progress.value > MIN_REQUIRED_ANIMATION_PROGRESS }
}
- } else {
- return remember(toggleState, uncheckedCornerSize, checkedCornerSize, pressedCornerSize) {
- AnimatedToggleRoundedCornerShape(
- currentShapeSize = currentShapeSize,
- ) {
- toggleState.cornerSize(
- uncheckedCornerSize,
- checkedCornerSize,
- pressedCornerSize,
- )
+
+ if (toggleState != previous.value) {
+ animatedShape.startCornerSize = animatedShape.endCornerSize
+ animatedShape.endCornerSize = toggledCornerSize
+ previous.value = toggleState
+
+ scope.launch {
+ progress.snapTo(1f - progress.value)
+ progress.animateTo(1f, animationSpec = animationSpec)
}
}
}
+
+ return animatedShape
}
private fun ToggleState.cornerSize(
@@ -145,3 +141,5 @@
Checked,
Pressed,
}
+
+private const val MIN_REQUIRED_ANIMATION_PROGRESS = 0.75f
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/AnimationSpecUtils.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/AnimationSpecUtils.kt
index 11dbdef..f28dd95 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/AnimationSpecUtils.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/AnimationSpecUtils.kt
@@ -26,6 +26,7 @@
import androidx.compose.animation.core.SpringSpec
import androidx.compose.animation.core.TwoWayConverter
import androidx.compose.animation.core.VectorizedAnimationSpec
+import androidx.compose.runtime.withFrameMillis
/**
* Returns a new [AnimationSpec] that is a faster version of this one.
@@ -129,3 +130,14 @@
}
as T
}
+
+internal suspend fun waitUntil(condition: () -> Boolean) {
+ val initialTimeMillis = withFrameMillis { it }
+ while (!condition()) {
+ val timeMillis = withFrameMillis { it }
+ if (timeMillis - initialTimeMillis > MAX_WAIT_TIME_MILLIS) return
+ }
+ return
+}
+
+private const val MAX_WAIT_TIME_MILLIS = 1_000L
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ArcProgressIndicator.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ArcProgressIndicator.kt
new file mode 100644
index 0000000..d38aad2
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ArcProgressIndicator.kt
@@ -0,0 +1,222 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material3
+
+import androidx.compose.animation.core.CubicBezierEasing
+import androidx.compose.animation.core.animateFloat
+import androidx.compose.animation.core.infiniteRepeatable
+import androidx.compose.animation.core.keyframes
+import androidx.compose.animation.core.rememberInfiniteTransition
+import androidx.compose.foundation.Canvas
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.StrokeCap
+import androidx.compose.ui.graphics.drawscope.Stroke
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.wear.compose.materialcore.screenHeightDp
+import kotlin.math.PI
+import kotlin.math.asin
+
+/**
+ * Indeterminate Material Design arc progress indicator.
+ *
+ * Indeterminate progress indicator expresses an unspecified wait time and animates indefinitely.
+ * This overload provides a variation over the usual circular spinner by allowing the start and end
+ * angles to be specified.
+ *
+ * Example of indeterminate arc progress indicator:
+ *
+ * @sample androidx.wear.compose.material3.samples.IndeterminateProgressArcSample
+ * @param startAngle the start angle of this progress indicator arc (specified in degrees). It is
+ * recommended to use [ArcProgressIndicatorDefaults.IndeterminateStartAngle]. Measured clockwise
+ * from the three o'clock position.
+ * @param endAngle the end angle of this progress indicator arc (specified in degrees). It is
+ * recommended to use [ArcProgressIndicatorDefaults.IndeterminateEndAngle]. Measured clockwise
+ * from the three o'clock position.
+ * @param modifier Modifier to be applied to the ArcProgressIndicator.
+ * @param angularDirection Determines whether the animation is in the clockwise or counter-clockwise
+ * direction.
+ * @param colors [ProgressIndicatorColors] that will be used to resolve the indicator and track
+ * color for this progress indicator.
+ * @param strokeWidth The stroke width for the progress indicator. The recommended value is
+ * [ArcProgressIndicatorDefaults.IndeterminateStrokeWidth].
+ * @param gapSize The size (in Dp) of the gap between the ends of the progress indicator and the
+ * track. The stroke end caps are not included in this distance.
+ */
+@Composable
+fun ArcProgressIndicator(
+ startAngle: Float = ArcProgressIndicatorDefaults.IndeterminateStartAngle,
+ endAngle: Float = ArcProgressIndicatorDefaults.IndeterminateEndAngle,
+ modifier: Modifier = Modifier,
+ angularDirection: AngularDirection = AngularDirection.CounterClockwise,
+ colors: ProgressIndicatorColors = ProgressIndicatorDefaults.colors(),
+ strokeWidth: Dp = ArcProgressIndicatorDefaults.IndeterminateStrokeWidth,
+ gapSize: Dp = ArcProgressIndicatorDefaults.calculateRecommendedGapSize(strokeWidth),
+) {
+ val infiniteTransition = rememberInfiniteTransition()
+ val head =
+ infiniteTransition.animateFloat(
+ initialValue = 0f,
+ targetValue = 1f,
+ animationSpec = arcIndeterminateHeadAnimationSpec
+ )
+ val tail =
+ infiniteTransition.animateFloat(
+ initialValue = 0f,
+ targetValue = 1f,
+ animationSpec = arcIndeterminateTailAnimationSpec
+ )
+
+ val strokeWidthPx = with(LocalDensity.current) { strokeWidth.toPx() }
+ val stroke = Stroke(width = strokeWidthPx, cap = StrokeCap.Round)
+ val adjustedGapSize = gapSize + strokeWidth
+ val fullSweep = ((endAngle - startAngle) % 360 + 360) % 360
+
+ Canvas(modifier) {
+ val gapSizeSweep = asin(adjustedGapSize.toPx() / size.width) * 360f / PI.toFloat()
+
+ // Track before arc
+ val beforeTrackSweep =
+ if (tail.value >= 0.999f) (1 - head.value) * fullSweep
+ else (1 - head.value) * fullSweep - gapSizeSweep
+ if (beforeTrackSweep > 0) {
+ drawCircularIndicator(
+ startAngle =
+ if (angularDirection == AngularDirection.CounterClockwise) startAngle
+ else endAngle,
+ sweep =
+ if (angularDirection == AngularDirection.CounterClockwise) beforeTrackSweep
+ else -beforeTrackSweep,
+ colors.trackBrush,
+ stroke = stroke
+ )
+ }
+
+ // Arc
+ val arcStart =
+ if (angularDirection == AngularDirection.CounterClockwise)
+ endAngle - tail.value * fullSweep
+ else startAngle + tail.value * fullSweep
+ val arcSweep =
+ if (angularDirection == AngularDirection.CounterClockwise)
+ (tail.value - head.value) * fullSweep
+ else (head.value - tail.value) * fullSweep
+ drawCircularIndicator(
+ startAngle = arcStart,
+ sweep = arcSweep,
+ colors.indicatorBrush,
+ stroke = stroke
+ )
+
+ // Track after arc
+ val afterTrackSweep =
+ if (tail.value >= 0.999f) tail.value * fullSweep
+ else tail.value * fullSweep - gapSizeSweep
+ if (afterTrackSweep > 0) {
+ drawCircularIndicator(
+ startAngle =
+ if (angularDirection == AngularDirection.CounterClockwise) endAngle
+ else startAngle,
+ sweep =
+ if (angularDirection == AngularDirection.CounterClockwise) -afterTrackSweep
+ else afterTrackSweep,
+ colors.trackBrush,
+ stroke = stroke
+ )
+ }
+ }
+}
+
+/** Contains default values for [ArcProgressIndicator]. */
+object ArcProgressIndicatorDefaults {
+ /** The default start angle in degrees for an indeterminate arc progress indicator */
+ const val IndeterminateStartAngle = 62f
+
+ /** The default end angle in degrees for an indeterminate arc progress indicator */
+ const val IndeterminateEndAngle = 118f
+
+ /** Stroke width of the indeterminate arc progress indicator. */
+ val IndeterminateStrokeWidth = 8.dp
+
+ /**
+ * The recommended diameter of the indeterminate arc progress indicator, which leaves room for
+ * additional content such as a message above the indicator.
+ */
+ val recommendedIndeterminateDiameter: Dp
+ @Composable
+ get() {
+ // Calculate the recommended diameter as 76% of screen height.
+ val screenHeight = screenHeightDp()
+ return 0.76.dp * screenHeight.toFloat()
+ }
+
+ /**
+ * Returns recommended size of the gap based on `strokeWidth`.
+ *
+ * The absolute value can be customized with `gapSize` parameter on [CircularProgressIndicator].
+ */
+ fun calculateRecommendedGapSize(strokeWidth: Dp): Dp = strokeWidth / 3f
+}
+
+/** Class to define angular direction - Clockwise and Counter Clockwise. */
+@JvmInline
+value class AngularDirection internal constructor(internal val type: Int) {
+ companion object {
+ /** Clockwise is the standard direction for an analog clock. */
+ val Clockwise = AngularDirection(0)
+
+ /** CounterClockwise is the opposite direction to Clockwise */
+ val CounterClockwise = AngularDirection(1)
+ }
+}
+
+/** An animation spec for indeterminate arc progress indicator head position. */
+internal val arcIndeterminateHeadAnimationSpec =
+ infiniteRepeatable(
+ animation =
+ keyframes {
+ durationMillis = TotalArcAnimationDuration
+ 0f at HeadDelay using ArcIndeterminateProgressEasing
+ 1f at HeadDuration + HeadDelay
+ }
+ )
+
+/** An animation spec for indeterminate arc progress indicator tail position. */
+internal val arcIndeterminateTailAnimationSpec =
+ infiniteRepeatable(
+ animation =
+ keyframes {
+ durationMillis = TotalArcAnimationDuration
+ 0f at TailDelay using ArcIndeterminateProgressEasing
+ 1f at TailDuration + TailDelay
+ }
+ )
+
+// Total duration for one arc cycle
+internal const val TotalArcAnimationDuration = 2000 // extra-long4 * 2
+
+// Duration of the head and tail animations for the indeterminate arc indicator
+internal const val HeadDuration = 600 // long4
+internal const val TailDuration = 600 // long4
+
+// Delay before the start of the head and tail animations for the indeterminate arc indicator
+internal const val HeadDelay = 0
+internal const val TailDelay = 300 // medium2
+
+internal val ArcIndeterminateProgressEasing = CubicBezierEasing(0.3f, 0f, 0.7f, 1f)
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/CheckboxButton.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/CheckboxButton.kt
index 69996bc..86d0df6 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/CheckboxButton.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/CheckboxButton.kt
@@ -17,8 +17,7 @@
package androidx.wear.compose.material3
import androidx.compose.animation.core.AnimationSpec
-import androidx.compose.animation.core.TweenSpec
-import androidx.compose.animation.core.tween
+import androidx.compose.animation.core.FiniteAnimationSpec
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.Interaction
@@ -63,7 +62,6 @@
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.wear.compose.material3.tokens.CheckboxButtonTokens
-import androidx.wear.compose.material3.tokens.MotionTokens
import androidx.wear.compose.material3.tokens.ShapeTokens
import androidx.wear.compose.material3.tokens.SplitCheckboxButtonTokens
import androidx.wear.compose.materialcore.animateSelectionColor
@@ -1526,7 +1524,7 @@
private val SPLIT_MIN_WIDTH = 48.dp
private val SPLIT_SECTIONS_SHAPE = ShapeTokens.CornerExtraSmall
-private val COLOR_ANIMATION_SPEC: AnimationSpec<Color> =
- tween(MotionTokens.DurationMedium1, 0, MotionTokens.EasingStandardDecelerate)
-private val PROGRESS_ANIMATION_SPEC: TweenSpec<Float> =
- tween(MotionTokens.DurationMedium1, 0, MotionTokens.EasingStandardDecelerate)
+private val COLOR_ANIMATION_SPEC: AnimationSpec<Color>
+ @Composable get() = MaterialTheme.motionScheme.slowEffectsSpec()
+private val PROGRESS_ANIMATION_SPEC: FiniteAnimationSpec<Float>
+ @Composable get() = MaterialTheme.motionScheme.fastEffectsSpec()
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/CircularProgressIndicator.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/CircularProgressIndicator.kt
index ee73aea..4b3f9c4 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/CircularProgressIndicator.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/CircularProgressIndicator.kt
@@ -37,11 +37,7 @@
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithCache
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Size
-import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.StrokeCap
-import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.drawscope.rotate
import androidx.compose.ui.platform.LocalDensity
@@ -150,7 +146,7 @@
*
* Indeterminate progress indicator expresses an unspecified wait time and spins indefinitely.
*
- * Example of indeterminate progress indicator:
+ * Example of indeterminate circular progress indicator:
*
* @sample androidx.wear.compose.material3.samples.IndeterminateProgressIndicatorSample
* @param modifier Modifier to be applied to the CircularProgressIndicator.
@@ -462,27 +458,6 @@
val IndeterminateStrokeWidth = 3.dp
}
-private fun DrawScope.drawCircularIndicator(
- startAngle: Float,
- sweep: Float,
- brush: Brush,
- stroke: Stroke
-) {
- // To draw this circle we need a rect with edges that line up with the midpoint of the stroke.
- // To do this we need to remove half the stroke width from the total diameter for both sides.
- val diameterOffset = stroke.width / 2
- val arcDimen = size.width - 2 * diameterOffset
- drawArc(
- brush = brush,
- startAngle = startAngle,
- sweepAngle = sweep,
- useCenter = false,
- topLeft = Offset(diameterOffset, diameterOffset),
- size = Size(arcDimen, arcDimen),
- style = stroke
- )
-}
-
private fun coercedProgressWithGap(progress: Float, isFullCircle: Boolean): Float {
val coercedProgress = progress.coerceIn(0f, 1f)
return if (isFullCircle && coercedProgress == 1f) {
@@ -493,57 +468,6 @@
}
}
-/** A global animation spec for indeterminate circular progress indicator. */
-internal val circularIndeterminateGlobalRotationAnimationSpec
- get() =
- infiniteRepeatable<Float>(
- animation = tween(CircularAnimationProgressDuration, easing = LinearEasing)
- )
-
-/**
- * An animation spec for indeterminate circular progress indicators that infinitely rotates a 360
- * degrees.
- */
-internal val circularIndeterminateRotationAnimationSpec
- get() =
- infiniteRepeatable(
- animation =
- keyframes {
- durationMillis = CircularAnimationProgressDuration // 6000ms
- 90f at
- CircularAnimationAdditionalRotationDuration using
- MotionTokens
- .EasingEmphasizedDecelerate // MotionTokens.EasingEmphasizedDecelerateCubicBezier // 300ms
- 90f at CircularAnimationAdditionalRotationDelay // hold till 1500ms
- 180f at
- CircularAnimationAdditionalRotationDuration +
- CircularAnimationAdditionalRotationDelay // 1800ms
- 180f at CircularAnimationAdditionalRotationDelay * 2 // hold till 3000ms
- 270f at
- CircularAnimationAdditionalRotationDuration +
- CircularAnimationAdditionalRotationDelay * 2 // 3300ms
- 270f at CircularAnimationAdditionalRotationDelay * 3 // hold till 4500ms
- 360f at
- CircularAnimationAdditionalRotationDuration +
- CircularAnimationAdditionalRotationDelay * 3 // 4800ms
- 360f at CircularAnimationProgressDuration // hold till 6000ms
- }
- )
-
-/** An animation spec for indeterminate circular progress indicators progress motion. */
-internal val circularIndeterminateProgressAnimationSpec
- get() =
- infiniteRepeatable(
- animation =
- keyframes {
- durationMillis = CircularAnimationProgressDuration // 6000ms
- CircularIndeterminateMaxProgress at
- CircularAnimationProgressDuration / 2 using
- CircularProgressEasing // 3000ms
- CircularIndeterminateMinProgress at CircularAnimationProgressDuration
- }
- )
-
// The indeterminate circular indicator easing constants for its motion
internal val CircularProgressEasing = MotionTokens.EasingStandard
internal const val CircularIndeterminateMinProgress = 0.1f
@@ -555,5 +479,53 @@
internal const val CircularAdditionalRotationDegreesTarget = 360f
internal const val CircularGlobalRotationDegreesTarget = 1080f
+/** A global animation spec for indeterminate circular progress indicator. */
+internal val circularIndeterminateGlobalRotationAnimationSpec =
+ infiniteRepeatable<Float>(
+ animation = tween(CircularAnimationProgressDuration, easing = LinearEasing)
+ )
+
+/**
+ * An animation spec for indeterminate circular progress indicators that infinitely rotates a 360
+ * degrees.
+ */
+internal val circularIndeterminateRotationAnimationSpec =
+ infiniteRepeatable(
+ animation =
+ keyframes {
+ durationMillis = CircularAnimationProgressDuration // 6000ms
+ 90f at
+ CircularAnimationAdditionalRotationDuration using
+ MotionTokens
+ .EasingEmphasizedDecelerate // MotionTokens.EasingEmphasizedDecelerateCubicBezier // 300ms
+ 90f at CircularAnimationAdditionalRotationDelay // hold till 1500ms
+ 180f at
+ CircularAnimationAdditionalRotationDuration +
+ CircularAnimationAdditionalRotationDelay // 1800ms
+ 180f at CircularAnimationAdditionalRotationDelay * 2 // hold till 3000ms
+ 270f at
+ CircularAnimationAdditionalRotationDuration +
+ CircularAnimationAdditionalRotationDelay * 2 // 3300ms
+ 270f at CircularAnimationAdditionalRotationDelay * 3 // hold till 4500ms
+ 360f at
+ CircularAnimationAdditionalRotationDuration +
+ CircularAnimationAdditionalRotationDelay * 3 // 4800ms
+ 360f at CircularAnimationProgressDuration // hold till 6000ms
+ }
+ )
+
+/** An animation spec for indeterminate circular progress indicators progress motion. */
+internal val circularIndeterminateProgressAnimationSpec =
+ infiniteRepeatable(
+ animation =
+ keyframes {
+ durationMillis = CircularAnimationProgressDuration // 6000ms
+ CircularIndeterminateMaxProgress at
+ CircularAnimationProgressDuration / 2 using
+ CircularProgressEasing // 3000ms
+ CircularIndeterminateMinProgress at CircularAnimationProgressDuration
+ }
+ )
+
// extra progress added for the gap merge animation
internal const val GapExtraProgress = 0.05f
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Placeholder.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Placeholder.kt
index 8eef2be..cca2673 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Placeholder.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Placeholder.kt
@@ -100,7 +100,7 @@
@Composable
fun Modifier.placeholder(
placeholderState: PlaceholderState,
- shape: Shape = PlaceholderDefaults.shape,
+ shape: Shape = PlaceholderDefaults.Shape,
color: Color =
MaterialTheme.colorScheme.onSurface
.copy(alpha = 0.1f)
@@ -152,7 +152,7 @@
@Composable
fun Modifier.placeholderShimmer(
placeholderState: PlaceholderState,
- shape: Shape = PlaceholderDefaults.shape,
+ shape: Shape = PlaceholderDefaults.Shape,
color: Color = MaterialTheme.colorScheme.onSurface,
): Modifier =
this.then(
@@ -175,8 +175,8 @@
)
/**
- * Creates a [PlaceholderState] that is remembered across compositions. To start placeholder
- * animations run [PlaceholderState.startPlaceholderAnimation].
+ * Creates a [PlaceholderState] that is remembered across compositions. To animate the placeholder
+ * depending on the state, run [PlaceholderState.animatePlaceholder].
*
* A [PlaceholderState] should be created for each component that has placeholder data. The state is
* used to coordinate all of the different placeholder effects and animations.
@@ -222,15 +222,15 @@
object PlaceholderDefaults {
/** Default [Shape] for Placeholder. */
- val shape: Shape = ShapeTokens.CornerFull
+ val Shape: Shape = ShapeTokens.CornerFull
/**
* Create a [ButtonColors] that can be used in placeholder mode. This will provide the
* placeholder background effect that covers the normal button background with a solid
* background of [color] when the [placeholderState] is set to show the placeholder and a wipe
* off gradient brush when the state is in wipe-off mode. If the state is
- * [PlaceholderState.isShowContent] then the normal background will be used. All other colors
- * will be delegated to [originalButtonColors].
+ * [PlaceholderState.isHidden] then the normal background will be used. All other colors will be
+ * delegated to [originalButtonColors].
*
* Example of a [Button] with icon and a label that put placeholders over individual content
* slots and then draws a placeholder shimmer over the result and draws over the [Button]s
@@ -248,7 +248,7 @@
placeholderState: PlaceholderState,
color: Color = MaterialTheme.colorScheme.surfaceContainer
): ButtonColors {
- return if (!placeholderState.isShowContent) {
+ return if (!placeholderState.isHidden) {
ButtonColors(
containerPainter =
PlaceholderBackgroundPainter(
@@ -318,37 +318,37 @@
/**
* Create a [Painter] that wraps another painter and overlays a placeholder background brush on
- * top. If the [placeholderState] is [PlaceholderState.isShowContent] the original painter will
- * be used. Otherwise the [painter] will be drawn and then a placeholder background will be
+ * top. If the [placeholderState] is [PlaceholderState.isHidden] the original painter will be
+ * used. Otherwise the [originalPainter] will be drawn and then a placeholder background will be
* drawn over it or a wipe-off brush will be used to reveal the background when the state is
- * [PlaceholderState.isWipeOff].
+ * [PlaceholderState.isWipingOff].
*
* @param placeholderState the state of the placeholder
- * @param painter the original painter that will be drawn over when in placeholder mode.
+ * @param originalPainter the original painter that will be drawn over when in placeholder mode.
* @param color the color to use for the placeholder background brush
*/
@Composable
fun painterWithPlaceholderOverlayBackgroundBrush(
placeholderState: PlaceholderState,
- painter: Painter,
+ originalPainter: Painter,
color: Color = MaterialTheme.colorScheme.surfaceContainer,
): Painter {
- return if (!placeholderState.isShowContent) {
+ return if (!placeholderState.isHidden) {
PlaceholderBackgroundPainter(
- painter = painter,
+ painter = originalPainter,
placeholderState = placeholderState,
color = color
)
} else {
- painter
+ originalPainter
}
}
/**
* Create a [Painter] that paints with a placeholder background brush. If the [placeholderState]
- * is [PlaceholderState.isShowContent] then a transparent background will be shown. Otherwise a
+ * is [PlaceholderState.isHidden] then a transparent background will be shown. Otherwise a
* placeholder background will be drawn or a wipe-off brush will be used to reveal the content
- * underneath when [PlaceholderState.isWipeOff] is true.
+ * underneath when [PlaceholderState.isWipingOff] is true.
*
* @param placeholderState the state of the placeholder
* @param color the color to use for the placeholder background brush
@@ -418,8 +418,11 @@
*/
internal var backgroundOffset: Offset = Offset.Zero
- /** Start the animation of the placeholder state. */
- suspend fun startPlaceholderAnimation() {
+ /**
+ * Animate the placeholder according to the placeholder state. Cancels when the placeholder
+ * becomes inactive.
+ */
+ suspend fun animatePlaceholder() {
if (!isReduceMotionEnabled) {
coroutineScope {
while (isActive) {
@@ -430,7 +433,7 @@
}
/**
- * The current value of the placeholder wipe-off visual effect gradient progression. The
+ * The current value of the placeholder wipe-off visual effect gradient progression in Px. The
* progression is a 45 degree angle sweep across the whole screen running from outside of the
* Top|Left of the screen to Bottom|Right used as the anchor for wipe-off gradient effects.
*
@@ -439,8 +442,7 @@
* of height/width to create a 45 degree angle) * 1.75f and progress to the
* maximumScreenDimension * 0.75f.
*
- * The time taken for this progression to reach the edge of visible screen is
- * [PLACEHOLDER_WIPE_OFF_PROGRESSION_DURATION_MS]
+ * The time taken for this progression to reach the edge of visible screen is 300ms.
*/
internal val placeholderWipeOffProgression: Float by derivedStateOf {
val absoluteProgression =
@@ -461,7 +463,7 @@
* gradient that flows across the screen. The progression will start at -maxScreenDimension (max
* of height/width to create a 45 degree angle) and progress to the maximumScreenDimension.
*
- * The time taken for this progression is [PLACEHOLDER_WIPE_OFF_PROGRESSION_ALPHA_DURATION_MS]
+ * The time taken for this progression is 80ms.
*/
internal val placeholderWipeOffAlpha: Float by derivedStateOf {
val absoluteProgression =
@@ -475,12 +477,12 @@
}
/**
- * The current value of the placeholder visual effect gradient progression. The progression
- * gives the x coordinate to be applied to the placeholder gradient as it moves across the
- * screen. Starting off screen to the left and progressing across the screen and finishing off
- * the screen to the right after [PLACEHOLDER_SHIMMER_DURATION_MS].
+ * The current value of the placeholder visual effect gradient progression in Px. The
+ * progression gives the x coordinate to be applied to the placeholder gradient as it moves
+ * across the screen. Starting off screen to the left and progressing across the screen and
+ * finishing off the screen to the after 800ms.
*/
- val placeholderProgression: Float by derivedStateOf {
+ internal val placeholderShimmerProgression: Float by derivedStateOf {
val absoluteProgression =
(frameMillis.longValue
.mod(PLACEHOLDER_SHIMMER_GAP_BETWEEN_ANIMATION_LOOPS_MS)
@@ -493,7 +495,7 @@
/**
* The current value of the placeholder visual effect gradient progression alpha/opacity. The
* progression gives the alpha to apply during the period of the placeholder effect. This allows
- * the effect to be faded in and then out during the [PLACEHOLDER_SHIMMER_DURATION_MS].
+ * the effect to be faded in and then out during 800ms.
*/
internal val placeholderShimmerAlpha: Float by derivedStateOf {
val absoluteProgression =
@@ -514,7 +516,7 @@
/**
* The current value of the placeholder visual effect gradient progression alpha/opacity during
* the fade-in part of reset placeholder animation. This allows the effect to be faded in during
- * the [PLACEHOLDER_RESET_ANIMATION_DURATION_MS].
+ * 450ms.
*/
internal val resetPlaceholderFadeInAlpha: Float by derivedStateOf {
val absoluteProgression =
@@ -534,7 +536,7 @@
/**
* The current value of the placeholder visual effect gradient progression alpha/opacity during
* the fade-out part of reset placeholder animation. This allows the effect to be faded out
- * during the [PLACEHOLDER_RESET_ANIMATION_DURATION_MS].
+ * during 450ms.
*/
internal val resetPlaceholderFadeOutAlpha: Float by derivedStateOf {
val absoluteProgression =
@@ -549,15 +551,13 @@
* Returns true if the placeholder content should be shown with no placeholders effects and
* false if either the placeholder or the wipe-off effect are being shown.
*/
- val isShowContent: Boolean by derivedStateOf {
- placeholderStage == PlaceholderStage.ShowContent
- }
+ val isHidden: Boolean by derivedStateOf { placeholderStage == PlaceholderStage.HidePlaceholder }
/**
- * Should only be called when [isShowContent] is false. Returns true if the wipe-off effect that
- * reveals content should be shown and false if the placeholder effect should be shown.
+ * Returns true if the wipe-off effect that reveals content is being shown and false if the
+ * placeholder effect should be shown.
*/
- val isWipeOff: Boolean by derivedStateOf { placeholderStage == PlaceholderStage.WipeOff }
+ val isWipingOff: Boolean by derivedStateOf { placeholderStage == PlaceholderStage.WipeOff }
/**
* The width of the gradient to use for the placeholder shimmer and wipe-off effects. This is
@@ -567,7 +567,7 @@
internal val gradientXYWidth: Float by derivedStateOf { maxScreenDimension * 2f.pow(1.5f) }
internal var placeholderStage: PlaceholderStage =
- if (isContentReady.value.invoke()) PlaceholderStage.ShowContent
+ if (isContentReady.value.invoke()) PlaceholderStage.HidePlaceholder
else PlaceholderStage.ShowPlaceholder
get() =
derivedStateOf {
@@ -581,12 +581,12 @@
(frameMillis.longValue - startOfWipeOffAnimation) >=
PLACEHOLDER_WIPE_OFF_PROGRESSION_DURATION_MS
) {
- field = PlaceholderStage.ShowContent
+ field = PlaceholderStage.HidePlaceholder
}
// Placeholder
} else if (isContentReady.value()) {
if (isReduceMotionEnabled) {
- field = PlaceholderStage.ShowContent
+ field = PlaceholderStage.HidePlaceholder
} else {
startOfWipeOffAnimation = frameMillis.longValue
field = PlaceholderStage.WipeOff
@@ -652,13 +652,14 @@
/**
* Indicates that placeholders no longer to be shown. Enter this stage from [WipeOff] in the
- * loop after the wire-off animation.
+ * loop after the wipe-off animation.
*/
- val ShowContent = PlaceholderStage(2)
+ val HidePlaceholder = PlaceholderStage(2)
/**
* Resets the component to remove the content and reinstate the placeholders so that new
- * content can be loaded. Enter this stage from [ShowContent] and exit to [ShowPlaceholder].
+ * content can be loaded. Enter this stage from [HidePlaceholder] and exit to
+ * [ShowPlaceholder].
*/
val ResetContent = PlaceholderStage(3)
}
@@ -668,7 +669,7 @@
ShowPlaceholder -> "PlaceholderStage.ShowPlaceholder"
WipeOff -> "PlaceholderStage.WipeOff"
ResetContent -> "PlaceholderStage.ResetContent"
- else -> "PlaceholderStage.ShowContent"
+ else -> "PlaceholderStage.HidePlaceholder"
}
}
}
@@ -992,13 +993,23 @@
Brush.linearGradient(
start =
Offset(
- x = placeholderState.placeholderProgression - halfGradientWidth - offset.x,
- y = placeholderState.placeholderProgression - halfGradientWidth - offset.y
+ x =
+ placeholderState.placeholderShimmerProgression -
+ halfGradientWidth -
+ offset.x,
+ y =
+ placeholderState.placeholderShimmerProgression -
+ halfGradientWidth -
+ offset.y
),
end =
Offset(
- x = placeholderState.placeholderProgression + halfGradientWidth - offset.x,
- y = placeholderState.placeholderProgression + halfGradientWidth - offset.y
+ x =
+ placeholderState.placeholderShimmerProgression + halfGradientWidth -
+ offset.x,
+ y =
+ placeholderState.placeholderShimmerProgression + halfGradientWidth -
+ offset.y
),
colorStops =
listOf(
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ProgressIndicator.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ProgressIndicator.kt
index 9886984..8390137 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ProgressIndicator.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ProgressIndicator.kt
@@ -358,6 +358,27 @@
return if (roundedFraction == 0.0f) 1.0f else roundedFraction
}
+internal fun DrawScope.drawCircularIndicator(
+ startAngle: Float,
+ sweep: Float,
+ brush: Brush,
+ stroke: Stroke
+) {
+ // To draw this circle we need a rect with edges that line up with the midpoint of the stroke.
+ // To do this we need to remove half the stroke width from the total diameter for both sides.
+ val diameterOffset = stroke.width / 2
+ val arcDimen = size.width - 2 * diameterOffset
+ drawArc(
+ brush = brush,
+ startAngle = startAngle,
+ sweepAngle = sweep,
+ useCenter = false,
+ topLeft = Offset(diameterOffset, diameterOffset),
+ size = Size(arcDimen, arcDimen),
+ style = stroke
+ )
+}
+
internal fun Float.isFullInt(): Boolean = (round(this) == this)
internal fun Float.equalsWithTolerance(number: Float, tolerance: Float = 0.1f) =
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/RadioButton.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/RadioButton.kt
index b5f891f..e846a05 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/RadioButton.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/RadioButton.kt
@@ -17,7 +17,7 @@
package androidx.wear.compose.material3
import androidx.compose.animation.core.AnimationSpec
-import androidx.compose.animation.core.tween
+import androidx.compose.animation.core.FiniteAnimationSpec
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.Interaction
@@ -57,7 +57,6 @@
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
-import androidx.wear.compose.material3.tokens.MotionTokens
import androidx.wear.compose.material3.tokens.RadioButtonTokens
import androidx.wear.compose.material3.tokens.ShapeTokens
import androidx.wear.compose.material3.tokens.SplitRadioButtonTokens
@@ -1418,12 +1417,8 @@
dotColor = color,
onClick = null,
interactionSource = null,
- dotRadiusProgressDuration = { isSelected ->
- if (isSelected) MotionTokens.DurationMedium1 else MotionTokens.DurationShort3
- },
- dotAlphaProgressDuration = MotionTokens.DurationShort3,
- dotAlphaProgressDelay = MotionTokens.DurationShort2,
- easing = MotionTokens.EasingStandardDecelerate,
+ dotRadiusAnimationSpec = PROGRESS_ANIMATION_SPEC,
+ dotAlphaAnimationSpec = PROGRESS_ANIMATION_SPEC,
width = CONTROL_WIDTH,
height = CONTROL_HEIGHT,
ripple = ripple()
@@ -1443,8 +1438,10 @@
}
}
-private val COLOR_ANIMATION_SPEC: AnimationSpec<Color> =
- tween(MotionTokens.DurationMedium1, 0, MotionTokens.EasingStandardDecelerate)
+private val COLOR_ANIMATION_SPEC: AnimationSpec<Color>
+ @Composable get() = MaterialTheme.motionScheme.slowEffectsSpec()
+private val PROGRESS_ANIMATION_SPEC: FiniteAnimationSpec<Float>
+ @Composable get() = MaterialTheme.motionScheme.fastEffectsSpec()
private val SELECTION_CONTROL_WIDTH = 32.dp
private val SELECTION_CONTROL_HEIGHT = 24.dp
private val SELECTION_CONTROL_SPACING = 6.dp
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/RoundButton.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/RoundButton.kt
index b2be46e..7a4cd99 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/RoundButton.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/RoundButton.kt
@@ -35,7 +35,6 @@
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.runtime.withFrameMillis
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@@ -211,15 +210,4 @@
}
}
-private suspend fun waitUntil(condition: () -> Boolean) {
- val initialTimeMillis = withFrameMillis { it }
- while (!condition()) {
- val timeMillis = withFrameMillis { it }
- if (timeMillis - initialTimeMillis > MAX_WAIT_TIME_MILLIS) return
- }
- return
-}
-
-private const val MAX_WAIT_TIME_MILLIS = 1_000L
-
private const val MIN_REQUIRED_ANIMATION_PROGRESS = 0.75f
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/SwitchButton.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/SwitchButton.kt
index cd792cf..dc91159 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/SwitchButton.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/SwitchButton.kt
@@ -17,9 +17,8 @@
package androidx.wear.compose.material3
import androidx.compose.animation.core.AnimationSpec
-import androidx.compose.animation.core.TweenSpec
+import androidx.compose.animation.core.FiniteAnimationSpec
import androidx.compose.animation.core.animateFloat
-import androidx.compose.animation.core.tween
import androidx.compose.animation.core.updateTransition
import androidx.compose.foundation.background
import androidx.compose.foundation.border
@@ -66,7 +65,6 @@
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.lerp
-import androidx.wear.compose.material3.tokens.MotionTokens
import androidx.wear.compose.material3.tokens.ShapeTokens
import androidx.wear.compose.material3.tokens.SplitSwitchButtonTokens
import androidx.wear.compose.material3.tokens.SwitchButtonTokens
@@ -1940,7 +1938,7 @@
private val SPLIT_MIN_WIDTH = 48.dp
private val SPLIT_SECTIONS_SHAPE = ShapeTokens.CornerExtraSmall
-private val COLOR_ANIMATION_SPEC: AnimationSpec<Color> =
- tween(MotionTokens.DurationMedium1, 0, MotionTokens.EasingStandardDecelerate)
-private val SWITCH_PROGRESS_ANIMATION_SPEC: TweenSpec<Float> =
- tween(MotionTokens.DurationMedium2, 0, MotionTokens.EasingStandardDecelerate)
+private val COLOR_ANIMATION_SPEC: AnimationSpec<Color>
+ @Composable get() = MaterialTheme.motionScheme.slowEffectsSpec()
+private val SWITCH_PROGRESS_ANIMATION_SPEC: FiniteAnimationSpec<Float>
+ @Composable get() = MaterialTheme.motionScheme.fastEffectsSpec()
diff --git a/wear/compose/compose-material3/src/main/res/values-af/strings.xml b/wear/compose/compose-material3/src/main/res/values-af/strings.xml
index 3ab44cf..4d7a998 100644
--- a/wear/compose/compose-material3/src/main/res/values-af/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-af/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Vermeerder"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Verminder"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Vermeerder"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Het misluk"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Sukses"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Maak op foon oop"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-am/strings.xml b/wear/compose/compose-material3/src/main/res/values-am/strings.xml
index 8897e0b..d3c755b 100644
--- a/wear/compose/compose-material3/src/main/res/values-am/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-am/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"ጨምር"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"ቀንስ"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"ጨምር"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"አልተሳካም"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"ተሳክቷል"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"ስልክ ላይ ክፈት"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-ar/strings.xml b/wear/compose/compose-material3/src/main/res/values-ar/strings.xml
index da562e4..02c4b8d 100644
--- a/wear/compose/compose-material3/src/main/res/values-ar/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-ar/strings.xml
@@ -54,6 +54,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"زيادة"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"تقليل"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"زيادة"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"تعذر الإجراء"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"نجحَ الإجراء"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"فتح على الهاتف"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-as/strings.xml b/wear/compose/compose-material3/src/main/res/values-as/strings.xml
index 1a3d0b6..0b775bb 100644
--- a/wear/compose/compose-material3/src/main/res/values-as/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-as/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"বঢ়াওক"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"কমাওক"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"বঢ়াওক"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"বিফল হৈছে"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"সফল"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"ফ’নত খোলক"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-az/strings.xml b/wear/compose/compose-material3/src/main/res/values-az/strings.xml
index 2116d76..a649f33 100644
--- a/wear/compose/compose-material3/src/main/res/values-az/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-az/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Artırın"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Azaldın"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Artırın"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Alınmadı"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Alındı"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Telefonda aç"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-b+sr+Latn/strings.xml b/wear/compose/compose-material3/src/main/res/values-b+sr+Latn/strings.xml
index 83aaef1..cc50b58 100644
--- a/wear/compose/compose-material3/src/main/res/values-b+sr+Latn/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-b+sr+Latn/strings.xml
@@ -45,6 +45,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Povećaj"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Smanji"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Povećaj"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Nije uspelo"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Uspelo"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Na telefonu"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-be/strings.xml b/wear/compose/compose-material3/src/main/res/values-be/strings.xml
index 3504095cc..44a1d7a 100644
--- a/wear/compose/compose-material3/src/main/res/values-be/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-be/strings.xml
@@ -48,6 +48,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Павялічыць"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Паменшыць"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Павялічыць"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Памылка"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Выканана"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"На тэлефоне"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-bg/strings.xml b/wear/compose/compose-material3/src/main/res/values-bg/strings.xml
index a0d9411..5741a7c 100644
--- a/wear/compose/compose-material3/src/main/res/values-bg/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-bg/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Увеличаване"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Намаляване"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Увеличаване"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Неуспешно"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Успешно"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Отв. на тел."</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-bn/strings.xml b/wear/compose/compose-material3/src/main/res/values-bn/strings.xml
index af0fb65..9ee49c4 100644
--- a/wear/compose/compose-material3/src/main/res/values-bn/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-bn/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"বাড়ান"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"কমানো"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"বাড়ানো"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"সফল হয়নি"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"সফল হয়েছে"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"ফোনে খুলুন"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-bs/strings.xml b/wear/compose/compose-material3/src/main/res/values-bs/strings.xml
index 1c2416d..3f986f8 100644
--- a/wear/compose/compose-material3/src/main/res/values-bs/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-bs/strings.xml
@@ -45,6 +45,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Povećavanje"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Smanjenje"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Povećanje"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Neuspješno"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Uspješno"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Otvor. na tel."</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-ca/strings.xml b/wear/compose/compose-material3/src/main/res/values-ca/strings.xml
index 1376675..4f4240a 100644
--- a/wear/compose/compose-material3/src/main/res/values-ca/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-ca/strings.xml
@@ -45,6 +45,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Augmenta"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Disminueix"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Augmenta"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Ha fallat"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Correcte"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Obre al telèfon"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-cs/strings.xml b/wear/compose/compose-material3/src/main/res/values-cs/strings.xml
index a914697..33664db 100644
--- a/wear/compose/compose-material3/src/main/res/values-cs/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-cs/strings.xml
@@ -48,6 +48,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Zvýšit"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Snížit"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Zvýšit"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Nezdařilo se"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Hotovo"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Otevřít v telefonu"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-da/strings.xml b/wear/compose/compose-material3/src/main/res/values-da/strings.xml
index 5737e13..842214a 100644
--- a/wear/compose/compose-material3/src/main/res/values-da/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-da/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Øg"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Sænk"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Øg"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Mislykket"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Gennemført"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Åbn på telefon"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-de/strings.xml b/wear/compose/compose-material3/src/main/res/values-de/strings.xml
index b763f40..875685b 100644
--- a/wear/compose/compose-material3/src/main/res/values-de/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-de/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Erhöhen"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Verringern"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Erhöhen"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Fehlgeschlagen"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Abgeschlossen"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Auf Smartphone öffnen"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-el/strings.xml b/wear/compose/compose-material3/src/main/res/values-el/strings.xml
index e0be921..2fb649b 100644
--- a/wear/compose/compose-material3/src/main/res/values-el/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-el/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Αύξηση"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Μείωση"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Αύξηση"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Αποτυχία"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Επιτυχία"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Στο τηλέφωνο"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-en-rAU/strings.xml b/wear/compose/compose-material3/src/main/res/values-en-rAU/strings.xml
index 272e741..b83a6af3 100644
--- a/wear/compose/compose-material3/src/main/res/values-en-rAU/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-en-rAU/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Increase"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Decrease"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Increase"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Failed"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Success"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Open on phone"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-en-rCA/strings.xml b/wear/compose/compose-material3/src/main/res/values-en-rCA/strings.xml
index a1f8f19..33c33d5 100644
--- a/wear/compose/compose-material3/src/main/res/values-en-rCA/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-en-rCA/strings.xml
@@ -42,6 +42,8 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Increase"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Decrease"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Increase"</string>
+ <string name="wear_m3c_alert_dialog_content_description_confirm_button" msgid="7776845597891182382">"Confirm"</string>
+ <string name="wear_m3c_alert_dialog_content_description_dismiss_button" msgid="3572467833850785688">"Dismiss"</string>
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Failed"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Success"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Open on phone"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-en-rGB/strings.xml b/wear/compose/compose-material3/src/main/res/values-en-rGB/strings.xml
index 272e741..b83a6af3 100644
--- a/wear/compose/compose-material3/src/main/res/values-en-rGB/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-en-rGB/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Increase"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Decrease"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Increase"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Failed"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Success"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Open on phone"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-en-rIN/strings.xml b/wear/compose/compose-material3/src/main/res/values-en-rIN/strings.xml
index 272e741..b83a6af3 100644
--- a/wear/compose/compose-material3/src/main/res/values-en-rIN/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-en-rIN/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Increase"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Decrease"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Increase"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Failed"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Success"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Open on phone"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-es-rUS/strings.xml b/wear/compose/compose-material3/src/main/res/values-es-rUS/strings.xml
index 74e4311..56c5f4f 100644
--- a/wear/compose/compose-material3/src/main/res/values-es-rUS/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-es-rUS/strings.xml
@@ -45,6 +45,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Aumentar"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Disminuir"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Aumentar"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Error"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Listo"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Abrir en el teléfono"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-es/strings.xml b/wear/compose/compose-material3/src/main/res/values-es/strings.xml
index a3f0fb8..1a99712 100644
--- a/wear/compose/compose-material3/src/main/res/values-es/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-es/strings.xml
@@ -45,6 +45,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Aumentar"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Reducir"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Aumentar"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Error"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Todo correcto"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Ábrelo en el teléfono"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-et/strings.xml b/wear/compose/compose-material3/src/main/res/values-et/strings.xml
index abb3ac0..8d80314 100644
--- a/wear/compose/compose-material3/src/main/res/values-et/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-et/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Suurenda"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Vähenda"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Suurenda"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Ebaõnnestus"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Õnnestus"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Ava telefonis"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-eu/strings.xml b/wear/compose/compose-material3/src/main/res/values-eu/strings.xml
index a44067d..76fc614 100644
--- a/wear/compose/compose-material3/src/main/res/values-eu/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-eu/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Igo"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Jaitsi"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Igo"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Huts egin du"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Eginda"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Ireki telefonoan"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-fa/strings.xml b/wear/compose/compose-material3/src/main/res/values-fa/strings.xml
index 4ae1008..ecf3999 100644
--- a/wear/compose/compose-material3/src/main/res/values-fa/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-fa/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"افزایش دادن"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"کاهش دادن"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"افزایش دادن"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"انجام نشد"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"انجام شد"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"باز کردن در تلفن"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-fi/strings.xml b/wear/compose/compose-material3/src/main/res/values-fi/strings.xml
index 824c7b52..c704c03 100644
--- a/wear/compose/compose-material3/src/main/res/values-fi/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-fi/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Lisää"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Vähennä"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Lisää"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Epäonnistui"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Onnistui"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Puhelimella"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-fr-rCA/strings.xml b/wear/compose/compose-material3/src/main/res/values-fr-rCA/strings.xml
index 6aae002..f6fa0fe 100644
--- a/wear/compose/compose-material3/src/main/res/values-fr-rCA/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-fr-rCA/strings.xml
@@ -45,6 +45,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Augmenter"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Diminuer"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Augmenter"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Échec"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Réussite"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Ouv. ds tél."</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-fr/strings.xml b/wear/compose/compose-material3/src/main/res/values-fr/strings.xml
index f8cfafe..6cd5c75 100644
--- a/wear/compose/compose-material3/src/main/res/values-fr/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-fr/strings.xml
@@ -45,6 +45,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Augmenter"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Diminuer"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Augmenter"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Échec"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Opération réussie"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Ouvrir sur le téléphone"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-gl/strings.xml b/wear/compose/compose-material3/src/main/res/values-gl/strings.xml
index c22ed45..985b2f3 100644
--- a/wear/compose/compose-material3/src/main/res/values-gl/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-gl/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Aumentar"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Reducir"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Aumentar"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Erro"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Todo correcto"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Abrir no tel."</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-gu/strings.xml b/wear/compose/compose-material3/src/main/res/values-gu/strings.xml
index 92e9105..5d9c5073 100644
--- a/wear/compose/compose-material3/src/main/res/values-gu/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-gu/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"વધારો"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"ઘટાડો"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"વધારો"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"નિષ્ફળ થઈ"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"સફળ થઈ"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"ફોન પર ખોલો"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-hi/strings.xml b/wear/compose/compose-material3/src/main/res/values-hi/strings.xml
index cafe258..b50a062 100644
--- a/wear/compose/compose-material3/src/main/res/values-hi/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-hi/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"बढ़ाएं"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"घटाएं"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"बढ़ाएं"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"काम नहीं हुआ"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"काम हो गया"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"फ़ोन पर खोलें"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-hr/strings.xml b/wear/compose/compose-material3/src/main/res/values-hr/strings.xml
index 754b6b6..1d6cdd0 100644
--- a/wear/compose/compose-material3/src/main/res/values-hr/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-hr/strings.xml
@@ -45,6 +45,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Povećaj"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Smanji"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Povećaj"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Nije uspjelo"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Uspjeh"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Na telefonu"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-hu/strings.xml b/wear/compose/compose-material3/src/main/res/values-hu/strings.xml
index 167ca98..c9cf8e1 100644
--- a/wear/compose/compose-material3/src/main/res/values-hu/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-hu/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Növelés"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Csökkentés"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Növelés"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Sikertelen"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Sikerült"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Nyissa meg mobilon"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-hy/strings.xml b/wear/compose/compose-material3/src/main/res/values-hy/strings.xml
index c2b9640..1edfa943 100644
--- a/wear/compose/compose-material3/src/main/res/values-hy/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-hy/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Ավելացնել"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Իջեցնել"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Բարձրացնել"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Ձախողվել է"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Պատրաստ է"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Բացեք հեռախոսում"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-in/strings.xml b/wear/compose/compose-material3/src/main/res/values-in/strings.xml
index 2edf41c..82f3c6e 100644
--- a/wear/compose/compose-material3/src/main/res/values-in/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-in/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Tingkatkan"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Turunkan"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Tingkatkan"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Gagal"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Berhasil"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Buka di ponsel"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-is/strings.xml b/wear/compose/compose-material3/src/main/res/values-is/strings.xml
index a8654ef..494885e 100644
--- a/wear/compose/compose-material3/src/main/res/values-is/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-is/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Auka"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Lækka"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Hækka"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Mistókst"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Tókst"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Opna í símanum"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-it/strings.xml b/wear/compose/compose-material3/src/main/res/values-it/strings.xml
index 1604a5f..dbb4bdf 100644
--- a/wear/compose/compose-material3/src/main/res/values-it/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-it/strings.xml
@@ -45,6 +45,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Aumenta"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Riduci"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Aumenta"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Non riuscita"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Riuscita"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Su smartph."</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-iw/strings.xml b/wear/compose/compose-material3/src/main/res/values-iw/strings.xml
index 0a0b1cd..27bb9c0 100644
--- a/wear/compose/compose-material3/src/main/res/values-iw/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-iw/strings.xml
@@ -45,6 +45,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"הגברה"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"הפחתה"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"העלאה"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"הפעולה נכשלה"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"הפעולה הצליחה"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"פתיחה בטלפון"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-ja/strings.xml b/wear/compose/compose-material3/src/main/res/values-ja/strings.xml
index d7ac4f2c..e0d13b8 100644
--- a/wear/compose/compose-material3/src/main/res/values-ja/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-ja/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"上げる"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"減らす"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"増やす"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"失敗"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"成功"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"スマホで開く"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-ka/strings.xml b/wear/compose/compose-material3/src/main/res/values-ka/strings.xml
index deda66ae..87a5202 100644
--- a/wear/compose/compose-material3/src/main/res/values-ka/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-ka/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"გაზრდა"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"შემცირება"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"გაზრდა"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"ვერ შესრულდა"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"შესრულდა"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"ტელეფონში გახსნა"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-kk/strings.xml b/wear/compose/compose-material3/src/main/res/values-kk/strings.xml
index e3e7615..8af1496 100644
--- a/wear/compose/compose-material3/src/main/res/values-kk/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-kk/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Көбейту"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Азайту"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Көбейту"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Расталмады."</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Расталды."</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Телефоннан ашыңыз."</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-km/strings.xml b/wear/compose/compose-material3/src/main/res/values-km/strings.xml
index 83ca8d5..4ce6dc5 100644
--- a/wear/compose/compose-material3/src/main/res/values-km/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-km/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"បង្កើន"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"បន្ថយ"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"បង្កើន"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"មិនបានសម្រេច"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"ជោគជ័យ"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"បើកលើទូរសព្ទ"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-kn/strings.xml b/wear/compose/compose-material3/src/main/res/values-kn/strings.xml
index 447dc4d..c0bcb1c 100644
--- a/wear/compose/compose-material3/src/main/res/values-kn/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-kn/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"ಹೆಚ್ಚಿಸಿ"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"ಕಡಿಮೆ ಮಾಡಿ"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"ಹೆಚ್ಚಿಸಿ"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"ವಿಫಲವಾಗಿದೆ"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"ಯಶಸ್ವಿಯಾಗಿದೆ"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"ಫೋನ್ನಲ್ಲಿ ತೆರೆಯಿರಿ"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-ko/strings.xml b/wear/compose/compose-material3/src/main/res/values-ko/strings.xml
index f30113a..3870de0 100644
--- a/wear/compose/compose-material3/src/main/res/values-ko/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-ko/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"증가"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"줄이기"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"늘리기"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"실패"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"성공"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"휴대전화에서 열기"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-ky/strings.xml b/wear/compose/compose-material3/src/main/res/values-ky/strings.xml
index 4ffd0d3..9e0d4c0 100644
--- a/wear/compose/compose-material3/src/main/res/values-ky/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-ky/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Жогорулатуу"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Төмөндөтүү"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Жогорулатуу"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Ишке ашпады"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Ийгилик"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Телефондо ачуу"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-lo/strings.xml b/wear/compose/compose-material3/src/main/res/values-lo/strings.xml
index 52e8a9b..774ced7 100644
--- a/wear/compose/compose-material3/src/main/res/values-lo/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-lo/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"ເພີ່ມຂຶ້ນ"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"ຫຼຸດລົງ"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"ເພີ່ມຂຶ້ນ"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"ບໍ່ສຳເລັດ"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"ສຳເລັດ"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"ເປີດໃນໂທລະສັບ"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-lt/strings.xml b/wear/compose/compose-material3/src/main/res/values-lt/strings.xml
index 28e19fe..5bd1303 100644
--- a/wear/compose/compose-material3/src/main/res/values-lt/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-lt/strings.xml
@@ -48,6 +48,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Padidinti"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Sumažinti"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Padidinti"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Nepavyko"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Pavyko"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Atidaryti telefone"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-lv/strings.xml b/wear/compose/compose-material3/src/main/res/values-lv/strings.xml
index 94d0107..f355546 100644
--- a/wear/compose/compose-material3/src/main/res/values-lv/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-lv/strings.xml
@@ -45,6 +45,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Palielināt"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Samazināt"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Palielināt"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Neizdevās"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Izdevās"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Atvērt tālrunī"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-mk/strings.xml b/wear/compose/compose-material3/src/main/res/values-mk/strings.xml
index f2cd97d..3f556e4 100644
--- a/wear/compose/compose-material3/src/main/res/values-mk/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-mk/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Зголеми"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Намалување"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Зголемување"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Неуспешно"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Успешно"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Отвори на телефонот"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-ml/strings.xml b/wear/compose/compose-material3/src/main/res/values-ml/strings.xml
index 4d26863..632c921 100644
--- a/wear/compose/compose-material3/src/main/res/values-ml/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-ml/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"കൂട്ടുക"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"കുറയ്ക്കുക"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"കൂട്ടുക"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"പരാജയപ്പെട്ടു"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"വിജയിച്ചു"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"ഫോണിൽ തുറക്കൂ"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-mn/strings.xml b/wear/compose/compose-material3/src/main/res/values-mn/strings.xml
index 3b24ce1..582aa3a 100644
--- a/wear/compose/compose-material3/src/main/res/values-mn/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-mn/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Нэмэгдүүлэх"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Багасгах"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Нэмэгдүүлэх"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Амжилтгүй"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Амжилттай"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Утсанд нээх"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-mr/strings.xml b/wear/compose/compose-material3/src/main/res/values-mr/strings.xml
index e6efee5..acbfd1c 100644
--- a/wear/compose/compose-material3/src/main/res/values-mr/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-mr/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"वाढवा"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"कमी करा"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"वाढवा"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"अयशस्वी"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"यशस्वी झाले"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"फोनवर उघडा"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-ms/strings.xml b/wear/compose/compose-material3/src/main/res/values-ms/strings.xml
index b41938c..9a77a5d 100644
--- a/wear/compose/compose-material3/src/main/res/values-ms/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-ms/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Tambahkan"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Kurangkan"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Tambahkan"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Gagal"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Berjaya"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Buka pada telefon"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-my/strings.xml b/wear/compose/compose-material3/src/main/res/values-my/strings.xml
index 678ad76..da6e684 100644
--- a/wear/compose/compose-material3/src/main/res/values-my/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-my/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"တိုးရန်"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"လျှော့ရန်"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"ချဲ့ရန်"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"မအောင်မြင်ပါ"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"အောင်မြင်သည်"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"ဖုန်း၌ဖွင့်ရန်"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-nb/strings.xml b/wear/compose/compose-material3/src/main/res/values-nb/strings.xml
index 664750c..ef7e2f0 100644
--- a/wear/compose/compose-material3/src/main/res/values-nb/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-nb/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Øk"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Reduser"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Øk"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Mislyktes"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Vellykket"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Åpne på tlf."</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-ne/strings.xml b/wear/compose/compose-material3/src/main/res/values-ne/strings.xml
index fe60e64..fd2cf9b 100644
--- a/wear/compose/compose-material3/src/main/res/values-ne/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-ne/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"बढाउनुहोस्"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"घटाउनुहोस्"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"बढाउनुहोस्"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"पुष्टि गर्न सकिएन"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"पुष्टि गरियो"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"फोनमा खोल्नुहोस्"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-nl/strings.xml b/wear/compose/compose-material3/src/main/res/values-nl/strings.xml
index a267de1..34beeaa 100644
--- a/wear/compose/compose-material3/src/main/res/values-nl/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-nl/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Verhogen"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Verlagen"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Verhogen"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Mislukt"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Geslaagd"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Openen op telefoon"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-or/strings.xml b/wear/compose/compose-material3/src/main/res/values-or/strings.xml
index d356304..1b9346e 100644
--- a/wear/compose/compose-material3/src/main/res/values-or/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-or/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"ବଢ଼ାନ୍ତୁ"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"କମାନ୍ତୁ"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"ବଢ଼ାନ୍ତୁ"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"ବିଫଳ ହୋଇଛି"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"ସଫଳ"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"ଫୋନରେ ଖୋଲନ୍ତୁ"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-pa/strings.xml b/wear/compose/compose-material3/src/main/res/values-pa/strings.xml
index 3c68908..96306b7 100644
--- a/wear/compose/compose-material3/src/main/res/values-pa/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-pa/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"ਵਧਾਓ"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"ਘਟਾਓ"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"ਵਧਾਓ"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"ਅਸਫਲ ਰਿਹਾ"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"ਸਫਲ ਰਿਹਾ"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"ਫ਼ੋਨ \'ਤੇ ਖੋਲ੍ਹੋ"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-pl/strings.xml b/wear/compose/compose-material3/src/main/res/values-pl/strings.xml
index a9249328..fecfbf7 100644
--- a/wear/compose/compose-material3/src/main/res/values-pl/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-pl/strings.xml
@@ -48,6 +48,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Zwiększ"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Zmniejsz"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Zwiększ"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Niepowodzenie"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Udało się"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Otwórz na telefonie"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-pt-rBR/strings.xml b/wear/compose/compose-material3/src/main/res/values-pt-rBR/strings.xml
index 775ebcb..e5ab9320 100644
--- a/wear/compose/compose-material3/src/main/res/values-pt-rBR/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-pt-rBR/strings.xml
@@ -45,6 +45,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Aumentar"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Diminuir"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Aumentar"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Falha"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Pronto"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Abra no smartphone"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-pt-rPT/strings.xml b/wear/compose/compose-material3/src/main/res/values-pt-rPT/strings.xml
index a99c8c1..15c07d1 100644
--- a/wear/compose/compose-material3/src/main/res/values-pt-rPT/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-pt-rPT/strings.xml
@@ -45,6 +45,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Aumentar"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Diminuir"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Aumentar"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Falhou"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Concluído"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Abrir no tel."</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-pt/strings.xml b/wear/compose/compose-material3/src/main/res/values-pt/strings.xml
index 775ebcb..e5ab9320 100644
--- a/wear/compose/compose-material3/src/main/res/values-pt/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-pt/strings.xml
@@ -45,6 +45,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Aumentar"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Diminuir"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Aumentar"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Falha"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Pronto"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Abra no smartphone"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-ro/strings.xml b/wear/compose/compose-material3/src/main/res/values-ro/strings.xml
index b2920e2..8e21c1b 100644
--- a/wear/compose/compose-material3/src/main/res/values-ro/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-ro/strings.xml
@@ -45,6 +45,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Crește"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Scade"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Crește"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Eroare"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Succes"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Pe telefon"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-ru/strings.xml b/wear/compose/compose-material3/src/main/res/values-ru/strings.xml
index 38b6613..bfa21739 100644
--- a/wear/compose/compose-material3/src/main/res/values-ru/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-ru/strings.xml
@@ -48,6 +48,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Увеличить"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Уменьшить"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Увеличить"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Ошибка"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Готово"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Открыть на телефоне"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-si/strings.xml b/wear/compose/compose-material3/src/main/res/values-si/strings.xml
index db2458b..37aa465 100644
--- a/wear/compose/compose-material3/src/main/res/values-si/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-si/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"වැඩි කරන්න"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"අඩු කරන්න"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"වැඩි කරන්න"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"අසමත් විය"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"සාර්ථකයි"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"දුරකථනයෙන් විවෘත කරන්න"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-sk/strings.xml b/wear/compose/compose-material3/src/main/res/values-sk/strings.xml
index 1569d57..30e57bb 100644
--- a/wear/compose/compose-material3/src/main/res/values-sk/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-sk/strings.xml
@@ -48,6 +48,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Zvýšiť"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Znížiť"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Zvýšiť"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Neúspešné"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Podarilo sa"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Otvorte v telefóne"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-sl/strings.xml b/wear/compose/compose-material3/src/main/res/values-sl/strings.xml
index cb50296..54c0542 100644
--- a/wear/compose/compose-material3/src/main/res/values-sl/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-sl/strings.xml
@@ -48,6 +48,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Povečanje"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Zmanjšaj"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Povečaj"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Neuspešno"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Uspešno"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Odpri v telefonu"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-sq/strings.xml b/wear/compose/compose-material3/src/main/res/values-sq/strings.xml
index 0ecac90..ba1133a 100644
--- a/wear/compose/compose-material3/src/main/res/values-sq/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-sq/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Rrit"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Ul"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Rrit"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Dështoi"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Me sukses"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Hape në telefon"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-sr/strings.xml b/wear/compose/compose-material3/src/main/res/values-sr/strings.xml
index 78c8d8c..44ef98d 100644
--- a/wear/compose/compose-material3/src/main/res/values-sr/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-sr/strings.xml
@@ -45,6 +45,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Повећај"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Смањи"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Повећај"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Није успело"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Успело"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"На телефону"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-sv/strings.xml b/wear/compose/compose-material3/src/main/res/values-sv/strings.xml
index c8d50d9..e388d545 100644
--- a/wear/compose/compose-material3/src/main/res/values-sv/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-sv/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Öka"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Minska"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Öka"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Misslyckades"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Klart"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"På telefonen"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-sw/strings.xml b/wear/compose/compose-material3/src/main/res/values-sw/strings.xml
index 78cb7e0..bbdd5f8 100644
--- a/wear/compose/compose-material3/src/main/res/values-sw/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-sw/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Ongeza"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Punguza"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Ongeza"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Imeshindwa"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Imemaliza"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Fungua kwenye simu"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-ta/strings.xml b/wear/compose/compose-material3/src/main/res/values-ta/strings.xml
index b8952f2..18b4454 100644
--- a/wear/compose/compose-material3/src/main/res/values-ta/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-ta/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"அதிகரிக்கும்"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"குறைக்கும்"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"அதிகரிக்கும்"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"தோல்வி"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"முடிந்தது"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"மொபைலில் திற"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-te/strings.xml b/wear/compose/compose-material3/src/main/res/values-te/strings.xml
index 3664364..b0ad3e6 100644
--- a/wear/compose/compose-material3/src/main/res/values-te/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-te/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"పెంచండి"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"తగ్గించండి"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"పెంచండి"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"విఫలమైంది"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"విజయవంతమైంది"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"ఫోన్లో తెరు"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-th/strings.xml b/wear/compose/compose-material3/src/main/res/values-th/strings.xml
index f82c0a5..fc10a30 100644
--- a/wear/compose/compose-material3/src/main/res/values-th/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-th/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"เพิ่ม"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"ลด"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"เพิ่ม"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"ไม่สำเร็จ"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"สำเร็จ"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"เปิดในโทรศัพท์"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-tl/strings.xml b/wear/compose/compose-material3/src/main/res/values-tl/strings.xml
index c9781cd..c51056a 100644
--- a/wear/compose/compose-material3/src/main/res/values-tl/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-tl/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Dagdagan"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Bawasan"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Dagdagan"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Nabigo"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Matagumpay"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Buksan"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-tr/strings.xml b/wear/compose/compose-material3/src/main/res/values-tr/strings.xml
index e950de0..425a8af 100644
--- a/wear/compose/compose-material3/src/main/res/values-tr/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-tr/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Artır"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Azalt"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Artır"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Başarısız"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Başarılı"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Telefonda aç"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-uk/strings.xml b/wear/compose/compose-material3/src/main/res/values-uk/strings.xml
index 9c8f77f..b09239b 100644
--- a/wear/compose/compose-material3/src/main/res/values-uk/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-uk/strings.xml
@@ -48,6 +48,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Збільшити"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Зменшити"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Збільшити"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Помилка"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Готово"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"На телефоні"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-ur/strings.xml b/wear/compose/compose-material3/src/main/res/values-ur/strings.xml
index 4c82b62..a6c5a45 100644
--- a/wear/compose/compose-material3/src/main/res/values-ur/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-ur/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"بڑھائیں"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"کم کریں"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"بڑھائیں"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"ناکام ہوا"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"کامیاب"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"فون پر کھولیں"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-uz/strings.xml b/wear/compose/compose-material3/src/main/res/values-uz/strings.xml
index 3a0749d..038a7ef 100644
--- a/wear/compose/compose-material3/src/main/res/values-uz/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-uz/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Oshirish"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Pasaytirish"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Oshirish"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Bajarilmadi"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Bajarildi"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Telefonda"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-vi/strings.xml b/wear/compose/compose-material3/src/main/res/values-vi/strings.xml
index e1a9df8..5110168 100644
--- a/wear/compose/compose-material3/src/main/res/values-vi/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-vi/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Tăng"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Giảm"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Tăng"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Lỗi"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Thành công"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Mở trên điện thoại"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-zh-rCN/strings.xml b/wear/compose/compose-material3/src/main/res/values-zh-rCN/strings.xml
index 7f82188..bcbe3a2 100644
--- a/wear/compose/compose-material3/src/main/res/values-zh-rCN/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-zh-rCN/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"增加"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"降低"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"提高"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"失败"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"成功"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"在手机上打开"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-zh-rHK/strings.xml b/wear/compose/compose-material3/src/main/res/values-zh-rHK/strings.xml
index e87a261..7cedc50 100644
--- a/wear/compose/compose-material3/src/main/res/values-zh-rHK/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-zh-rHK/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"調高"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"調低"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"調高"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"失敗"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"成功"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"在手機開啟"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-zh-rTW/strings.xml b/wear/compose/compose-material3/src/main/res/values-zh-rTW/strings.xml
index 311e77d9..c330c20 100644
--- a/wear/compose/compose-material3/src/main/res/values-zh-rTW/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-zh-rTW/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"調高"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"調低"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"調高"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"失敗"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"成功"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"在手機上開啟"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-zu/strings.xml b/wear/compose/compose-material3/src/main/res/values-zu/strings.xml
index b71059a..d9b5dbc 100644
--- a/wear/compose/compose-material3/src/main/res/values-zu/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-zu/strings.xml
@@ -42,6 +42,10 @@
<string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Khulisa"</string>
<string name="wear_m3c_stepper_decrease_content_description" msgid="6939134411425530620">"Yehlisa"</string>
<string name="wear_m3c_stepper_increase_content_description" msgid="6513575827514139918">"Khulisa"</string>
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_confirm_button (7776845597891182382) -->
+ <skip />
+ <!-- no translation found for wear_m3c_alert_dialog_content_description_dismiss_button (3572467833850785688) -->
+ <skip />
<string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Yehlulekile"</string>
<string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Impumelelo"</string>
<string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Vula efonini"</string>
diff --git a/wear/compose/integration-tests/macrobenchmark/src/main/java/androidx/wear/compose/integration/macrobenchmark/ButtonBenchmarkBase.kt b/wear/compose/integration-tests/macrobenchmark/src/main/java/androidx/wear/compose/integration/macrobenchmark/ButtonBenchmarkBase.kt
index 9ba55cc..3fdfc12 100644
--- a/wear/compose/integration-tests/macrobenchmark/src/main/java/androidx/wear/compose/integration/macrobenchmark/ButtonBenchmarkBase.kt
+++ b/wear/compose/integration-tests/macrobenchmark/src/main/java/androidx/wear/compose/integration/macrobenchmark/ButtonBenchmarkBase.kt
@@ -18,7 +18,9 @@
import android.content.Intent
import androidx.benchmark.macro.CompilationMode
-import androidx.benchmark.macro.FrameTimingMetric
+import androidx.benchmark.macro.ExperimentalMetricApi
+import androidx.benchmark.macro.FrameTimingGfxInfoMetric
+import androidx.benchmark.macro.MemoryUsageMetric
import androidx.benchmark.macro.junit4.MacrobenchmarkRule
import androidx.test.uiautomator.By
import org.junit.After
@@ -26,6 +28,7 @@
import org.junit.Rule
import org.junit.Test
+@OptIn(ExperimentalMetricApi::class)
abstract class ButtonBenchmarkBase(
private val compilationMode: CompilationMode,
private val activityAction: String
@@ -46,7 +49,8 @@
fun start() {
benchmarkRule.measureRepeated(
packageName = PACKAGE_NAME,
- metrics = listOf(FrameTimingMetric()),
+ metrics =
+ listOf(FrameTimingGfxInfoMetric(), MemoryUsageMetric(MemoryUsageMetric.Mode.Last)),
compilationMode = compilationMode,
iterations = 10,
setupBlock = {
diff --git a/wear/compose/integration-tests/macrobenchmark/src/main/java/androidx/wear/compose/integration/macrobenchmark/PositionIndicatorBenchmark.kt b/wear/compose/integration-tests/macrobenchmark/src/main/java/androidx/wear/compose/integration/macrobenchmark/PositionIndicatorBenchmark.kt
index 22b55a3..9662bfc 100644
--- a/wear/compose/integration-tests/macrobenchmark/src/main/java/androidx/wear/compose/integration/macrobenchmark/PositionIndicatorBenchmark.kt
+++ b/wear/compose/integration-tests/macrobenchmark/src/main/java/androidx/wear/compose/integration/macrobenchmark/PositionIndicatorBenchmark.kt
@@ -18,7 +18,9 @@
import android.content.Intent
import androidx.benchmark.macro.CompilationMode
-import androidx.benchmark.macro.FrameTimingMetric
+import androidx.benchmark.macro.ExperimentalMetricApi
+import androidx.benchmark.macro.FrameTimingGfxInfoMetric
+import androidx.benchmark.macro.MemoryUsageMetric
import androidx.benchmark.macro.junit4.MacrobenchmarkRule
import androidx.test.filters.LargeTest
import androidx.test.uiautomator.By
@@ -32,6 +34,7 @@
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
+@OptIn(ExperimentalMetricApi::class)
@LargeTest
@RunWith(Parameterized::class)
class PositionIndicatorBenchmark(private val compilationMode: CompilationMode) {
@@ -51,7 +54,8 @@
fun start() {
benchmarkRule.measureRepeated(
packageName = PACKAGE_NAME,
- metrics = listOf(FrameTimingMetric()),
+ metrics =
+ listOf(FrameTimingGfxInfoMetric(), MemoryUsageMetric(MemoryUsageMetric.Mode.Last)),
compilationMode = compilationMode,
iterations = 5,
setupBlock = {
diff --git a/wear/compose/integration-tests/macrobenchmark/src/main/java/androidx/wear/compose/integration/macrobenchmark/ScrollBenchmark.kt b/wear/compose/integration-tests/macrobenchmark/src/main/java/androidx/wear/compose/integration/macrobenchmark/ScrollBenchmark.kt
index b3704cc..f357f35 100644
--- a/wear/compose/integration-tests/macrobenchmark/src/main/java/androidx/wear/compose/integration/macrobenchmark/ScrollBenchmark.kt
+++ b/wear/compose/integration-tests/macrobenchmark/src/main/java/androidx/wear/compose/integration/macrobenchmark/ScrollBenchmark.kt
@@ -19,7 +19,9 @@
import android.content.Intent
import android.graphics.Point
import androidx.benchmark.macro.CompilationMode
-import androidx.benchmark.macro.FrameTimingMetric
+import androidx.benchmark.macro.ExperimentalMetricApi
+import androidx.benchmark.macro.FrameTimingGfxInfoMetric
+import androidx.benchmark.macro.MemoryUsageMetric
import androidx.benchmark.macro.junit4.MacrobenchmarkRule
import androidx.test.filters.LargeTest
import androidx.test.uiautomator.By
@@ -31,6 +33,7 @@
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
+@OptIn(ExperimentalMetricApi::class)
@LargeTest
@RunWith(Parameterized::class)
class ScrollBenchmark(private val compilationMode: CompilationMode) {
@@ -50,7 +53,11 @@
fun start() {
benchmarkRule.measureRepeated(
packageName = PACKAGE_NAME,
- metrics = listOf(FrameTimingMetric()),
+ metrics =
+ listOf(
+ FrameTimingGfxInfoMetric(),
+ MemoryUsageMetric(MemoryUsageMetric.Mode.Last),
+ ),
compilationMode = compilationMode,
iterations = 10,
setupBlock = {
diff --git a/wear/compose/integration-tests/macrobenchmark/src/main/java/androidx/wear/compose/integration/macrobenchmark/SwipeToDismissBenchmark.kt b/wear/compose/integration-tests/macrobenchmark/src/main/java/androidx/wear/compose/integration/macrobenchmark/SwipeToDismissBenchmark.kt
index fc09578..4e4a9072 100644
--- a/wear/compose/integration-tests/macrobenchmark/src/main/java/androidx/wear/compose/integration/macrobenchmark/SwipeToDismissBenchmark.kt
+++ b/wear/compose/integration-tests/macrobenchmark/src/main/java/androidx/wear/compose/integration/macrobenchmark/SwipeToDismissBenchmark.kt
@@ -18,7 +18,9 @@
import android.content.Intent
import androidx.benchmark.macro.CompilationMode
-import androidx.benchmark.macro.FrameTimingMetric
+import androidx.benchmark.macro.ExperimentalMetricApi
+import androidx.benchmark.macro.FrameTimingGfxInfoMetric
+import androidx.benchmark.macro.MemoryUsageMetric
import androidx.benchmark.macro.junit4.MacrobenchmarkRule
import androidx.test.filters.LargeTest
import androidx.test.uiautomator.By
@@ -31,6 +33,7 @@
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
+@OptIn(ExperimentalMetricApi::class)
@LargeTest
@RunWith(Parameterized::class)
class SwipeToDismissBenchmark(private val compilationMode: CompilationMode) {
@@ -50,7 +53,11 @@
fun start() {
benchmarkRule.measureRepeated(
packageName = PACKAGE_NAME,
- metrics = listOf(FrameTimingMetric()),
+ metrics =
+ listOf(
+ FrameTimingGfxInfoMetric(),
+ MemoryUsageMetric(MemoryUsageMetric.Mode.Last),
+ ),
compilationMode = compilationMode,
iterations = 10,
setupBlock = {
diff --git a/wear/compose/integration-tests/macrobenchmark/src/main/java/androidx/wear/compose/integration/macrobenchmark/SwipeToRevealBenchmark.kt b/wear/compose/integration-tests/macrobenchmark/src/main/java/androidx/wear/compose/integration/macrobenchmark/SwipeToRevealBenchmark.kt
index 9845b66..9786346 100644
--- a/wear/compose/integration-tests/macrobenchmark/src/main/java/androidx/wear/compose/integration/macrobenchmark/SwipeToRevealBenchmark.kt
+++ b/wear/compose/integration-tests/macrobenchmark/src/main/java/androidx/wear/compose/integration/macrobenchmark/SwipeToRevealBenchmark.kt
@@ -18,7 +18,9 @@
import android.content.Intent
import androidx.benchmark.macro.CompilationMode
-import androidx.benchmark.macro.FrameTimingMetric
+import androidx.benchmark.macro.ExperimentalMetricApi
+import androidx.benchmark.macro.FrameTimingGfxInfoMetric
+import androidx.benchmark.macro.MemoryUsageMetric
import androidx.benchmark.macro.junit4.MacrobenchmarkRule
import androidx.test.filters.LargeTest
import androidx.test.uiautomator.By
@@ -31,6 +33,7 @@
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
+@OptIn(ExperimentalMetricApi::class)
@LargeTest
@RunWith(Parameterized::class)
class SwipeToRevealBenchmark(private val compilationMode: CompilationMode) {
@@ -50,7 +53,11 @@
fun start() {
benchmarkRule.measureRepeated(
packageName = PACKAGE_NAME,
- metrics = listOf(FrameTimingMetric()),
+ metrics =
+ listOf(
+ FrameTimingGfxInfoMetric(),
+ MemoryUsageMetric(MemoryUsageMetric.Mode.Last),
+ ),
compilationMode = compilationMode,
iterations = 10,
setupBlock = {
diff --git a/wear/compose/integration-tests/macrobenchmark/src/main/java/androidx/wear/compose/integration/macrobenchmark/material3/DialogBenchmark.kt b/wear/compose/integration-tests/macrobenchmark/src/main/java/androidx/wear/compose/integration/macrobenchmark/material3/DialogBenchmark.kt
index 337bb62..fa6aa78 100644
--- a/wear/compose/integration-tests/macrobenchmark/src/main/java/androidx/wear/compose/integration/macrobenchmark/material3/DialogBenchmark.kt
+++ b/wear/compose/integration-tests/macrobenchmark/src/main/java/androidx/wear/compose/integration/macrobenchmark/material3/DialogBenchmark.kt
@@ -18,7 +18,9 @@
import android.content.Intent
import androidx.benchmark.macro.CompilationMode
-import androidx.benchmark.macro.FrameTimingMetric
+import androidx.benchmark.macro.ExperimentalMetricApi
+import androidx.benchmark.macro.FrameTimingGfxInfoMetric
+import androidx.benchmark.macro.MemoryUsageMetric
import androidx.benchmark.macro.junit4.MacrobenchmarkRule
import androidx.test.filters.LargeTest
import androidx.test.uiautomator.By
@@ -33,6 +35,7 @@
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
+@OptIn(ExperimentalMetricApi::class)
@LargeTest
@RunWith(Parameterized::class)
class DialogBenchmark(private val compilationMode: CompilationMode) {
@@ -52,7 +55,8 @@
fun start() {
benchmarkRule.measureRepeated(
packageName = PACKAGE_NAME,
- metrics = listOf(FrameTimingMetric()),
+ metrics =
+ listOf(FrameTimingGfxInfoMetric(), MemoryUsageMetric(MemoryUsageMetric.Mode.Last)),
compilationMode = compilationMode,
iterations = 5,
setupBlock = {
diff --git a/wear/protolayout/protolayout-proto/src/main/proto/resources.proto b/wear/protolayout/protolayout-proto/src/main/proto/resources.proto
index 0409795..7e04e8a 100644
--- a/wear/protolayout/protolayout-proto/src/main/proto/resources.proto
+++ b/wear/protolayout/protolayout-proto/src/main/proto/resources.proto
@@ -3,6 +3,7 @@
package androidx.wear.protolayout.proto;
+import "animation_parameters.proto";
import "dynamic.proto";
import "trigger.proto";
@@ -137,6 +138,25 @@
androidx.wear.protolayout.expression.proto.DynamicFloat progress = 3;
}
+// A Lottie resource that is read from a raw Android resource ID.
+message AndroidLottieResourceByResId {
+ // The Android resource ID, e.g. R.raw.foo
+ int32 raw_resource_id = 1;
+
+ // A DynamicFloat, normally transformed from certain states with the data
+ // binding pipeline to control the progress of the animation.
+ //
+ // <setter>Its value is required to fall in the range of [0.0, 1.0]. Any
+ // values outside this range would be clamped.
+ //
+ // When the first value of the DynamicFloat arrives, the animation starts from
+ // progress 0 to that value. After that it plays from current progress to the
+ // new value on subsequent updates.
+ //
+ // If not set, the animation will play on load.</setter>
+ androidx.wear.protolayout.expression.proto.DynamicFloat progress = 2;
+}
+
// An image resource, which can be used by layouts. This holds multiple
// underlying resource types, which the underlying runtime will pick according
// to what it thinks is appropriate.
@@ -164,6 +184,9 @@
// resource ID. The animation progress is bound to the provided dynamic float.
AndroidSeekableAnimatedImageResourceByResId
android_seekable_animated_resource_by_res_id = 7;
+
+ // A Lottie resource that is read from a raw Android resource ID.
+ AndroidLottieResourceByResId android_lottie_resource_by_res_id = 8;
}
// The resources for a layout.
diff --git a/wear/protolayout/protolayout-testing/api/current.txt b/wear/protolayout/protolayout-testing/api/current.txt
index e6f50d0..dd57766 100644
--- a/wear/protolayout/protolayout-testing/api/current.txt
+++ b/wear/protolayout/protolayout-testing/api/current.txt
@@ -1 +1,25 @@
// Signature format: 4.0
+package androidx.wear.protolayout.testing {
+
+ public final class LayoutElementAssertion {
+ method public androidx.wear.protolayout.testing.LayoutElementAssertion assert(androidx.wear.protolayout.testing.LayoutElementMatcher matcher);
+ method public void assertDoesNotExist();
+ method public void assertExists();
+ }
+
+ public final class LayoutElementAssertionsProvider {
+ ctor public LayoutElementAssertionsProvider(androidx.wear.protolayout.LayoutElementBuilders.Layout layout);
+ ctor public LayoutElementAssertionsProvider(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement layoutRoot);
+ method public androidx.wear.protolayout.testing.LayoutElementAssertion onElement(androidx.wear.protolayout.testing.LayoutElementMatcher matcher);
+ method public androidx.wear.protolayout.testing.LayoutElementAssertion onRoot();
+ }
+
+ public final class LayoutElementMatcher {
+ ctor public LayoutElementMatcher(String description, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.LayoutElementBuilders.LayoutElement,java.lang.Boolean> matcher);
+ method public infix androidx.wear.protolayout.testing.LayoutElementMatcher and(androidx.wear.protolayout.testing.LayoutElementMatcher other);
+ method public operator androidx.wear.protolayout.testing.LayoutElementMatcher not();
+ method public infix androidx.wear.protolayout.testing.LayoutElementMatcher or(androidx.wear.protolayout.testing.LayoutElementMatcher other);
+ }
+
+}
+
diff --git a/wear/protolayout/protolayout-testing/api/restricted_current.txt b/wear/protolayout/protolayout-testing/api/restricted_current.txt
index e6f50d0..dd57766 100644
--- a/wear/protolayout/protolayout-testing/api/restricted_current.txt
+++ b/wear/protolayout/protolayout-testing/api/restricted_current.txt
@@ -1 +1,25 @@
// Signature format: 4.0
+package androidx.wear.protolayout.testing {
+
+ public final class LayoutElementAssertion {
+ method public androidx.wear.protolayout.testing.LayoutElementAssertion assert(androidx.wear.protolayout.testing.LayoutElementMatcher matcher);
+ method public void assertDoesNotExist();
+ method public void assertExists();
+ }
+
+ public final class LayoutElementAssertionsProvider {
+ ctor public LayoutElementAssertionsProvider(androidx.wear.protolayout.LayoutElementBuilders.Layout layout);
+ ctor public LayoutElementAssertionsProvider(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement layoutRoot);
+ method public androidx.wear.protolayout.testing.LayoutElementAssertion onElement(androidx.wear.protolayout.testing.LayoutElementMatcher matcher);
+ method public androidx.wear.protolayout.testing.LayoutElementAssertion onRoot();
+ }
+
+ public final class LayoutElementMatcher {
+ ctor public LayoutElementMatcher(String description, kotlin.jvm.functions.Function1<? super androidx.wear.protolayout.LayoutElementBuilders.LayoutElement,java.lang.Boolean> matcher);
+ method public infix androidx.wear.protolayout.testing.LayoutElementMatcher and(androidx.wear.protolayout.testing.LayoutElementMatcher other);
+ method public operator androidx.wear.protolayout.testing.LayoutElementMatcher not();
+ method public infix androidx.wear.protolayout.testing.LayoutElementMatcher or(androidx.wear.protolayout.testing.LayoutElementMatcher other);
+ }
+
+}
+
diff --git a/wear/protolayout/protolayout-testing/build.gradle b/wear/protolayout/protolayout-testing/build.gradle
index 565226d..443c60b 100644
--- a/wear/protolayout/protolayout-testing/build.gradle
+++ b/wear/protolayout/protolayout-testing/build.gradle
@@ -31,16 +31,32 @@
dependencies {
api(libs.kotlinStdlib)
+ api(project(":wear:protolayout:protolayout"))
+ api(project(":wear:protolayout:protolayout-expression"))
implementation("androidx.annotation:annotation:1.8.1")
+ implementation(project(":wear:protolayout:protolayout-proto"))
+ implementation(project(":wear:protolayout:protolayout-expression-pipeline"))
+
+ testImplementation(libs.junit)
+ testImplementation(libs.robolectric)
+ testImplementation(libs.testExtJunit)
+ testImplementation(libs.truth)
}
android {
+ defaultConfig {
+ minSdkVersion 26
+ }
namespace "androidx.wear.protolayout.testing"
+
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
}
androidx {
name = "androidx.wear.protolayout:protolayout-testing"
- type = LibraryType.PUBLISHED_LIBRARY
+ type = LibraryType.PUBLISHED_TEST_LIBRARY
inceptionYear = "2024"
description = "Testing framework for protolayout element tree."
}
diff --git a/wear/protolayout/protolayout-testing/src/main/java/androidx/wear/protolayout/testing/LayoutElementAssertion.kt b/wear/protolayout/protolayout-testing/src/main/java/androidx/wear/protolayout/testing/LayoutElementAssertion.kt
new file mode 100644
index 0000000..f34070d
--- /dev/null
+++ b/wear/protolayout/protolayout-testing/src/main/java/androidx/wear/protolayout/testing/LayoutElementAssertion.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.protolayout.testing
+
+import androidx.wear.protolayout.LayoutElementBuilders
+import java.lang.AssertionError
+
+/**
+ * Represents a layout element that can be asserted on.
+ *
+ * <p>An instance of [LayoutElementAssertion] can be obtained from 'onElement' method on a
+ * [LayoutElementAssertionsProvider].
+ */
+public class LayoutElementAssertion
+internal constructor(
+ private val elementDescription: String,
+ internal val element: LayoutElementBuilders.LayoutElement?,
+) {
+ /** Asserts that the element was found in the element tree. */
+ public fun assertExists() {
+ if (element == null) {
+ throw AssertionError("Expected $elementDescription to exist, but it does not.")
+ }
+ }
+
+ /** Asserts that no element was found in the element tree. */
+ public fun assertDoesNotExist() {
+ if (element != null) {
+ throw AssertionError("Expected $elementDescription to not exist, but it does.")
+ }
+ }
+
+ /** Asserts that the provided [LayoutElementMatcher] is satisfied for this element. */
+ public fun assert(matcher: LayoutElementMatcher): LayoutElementAssertion {
+ assertExists()
+ if (!matcher.matches(element!!)) {
+ throw AssertionError(
+ "Expected $elementDescription to match '${matcher.description}'," +
+ " but it does not."
+ )
+ }
+ return this
+ }
+}
diff --git a/wear/protolayout/protolayout-testing/src/main/java/androidx/wear/protolayout/testing/LayoutElementAssertionsProvider.kt b/wear/protolayout/protolayout-testing/src/main/java/androidx/wear/protolayout/testing/LayoutElementAssertionsProvider.kt
new file mode 100644
index 0000000..4971419
--- /dev/null
+++ b/wear/protolayout/protolayout-testing/src/main/java/androidx/wear/protolayout/testing/LayoutElementAssertionsProvider.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.protolayout.testing
+
+import androidx.wear.protolayout.LayoutElementBuilders.Layout
+import androidx.wear.protolayout.LayoutElementBuilders.LayoutElement
+import androidx.wear.protolayout.LayoutElementBuilders.layoutElementFromProto
+
+/** Provides the main entry point into testing by exposing methods to find a layout element. */
+public class LayoutElementAssertionsProvider(layoutRoot: LayoutElement) {
+ private val root: LayoutElement =
+ layoutElementFromProto(layoutRoot.toLayoutElementProto(), null)
+
+ public constructor(layout: Layout) : this(layout.root!!)
+
+ /** Finds an element that matches the given condition. */
+ public fun onElement(matcher: LayoutElementMatcher): LayoutElementAssertion {
+ val elementDescription = "element matching '${matcher.description}'"
+ return LayoutElementAssertion(elementDescription, searchElement(root, matcher))
+ }
+
+ /**
+ * Finds the top level element of the element tree added to this
+ * [LayoutElementAssertionsProvider].
+ */
+ public fun onRoot(): LayoutElementAssertion = LayoutElementAssertion("root", root)
+
+ // TODO - b/374944199: add onAllElement which returns a LayoutElementAssertionCollection
+}
diff --git a/wear/protolayout/protolayout-testing/src/main/java/androidx/wear/protolayout/testing/LayoutElementMatcher.kt b/wear/protolayout/protolayout-testing/src/main/java/androidx/wear/protolayout/testing/LayoutElementMatcher.kt
new file mode 100644
index 0000000..77aa0c9
--- /dev/null
+++ b/wear/protolayout/protolayout-testing/src/main/java/androidx/wear/protolayout/testing/LayoutElementMatcher.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.protolayout.testing
+
+import androidx.wear.protolayout.LayoutElementBuilders.LayoutElement
+
+/**
+ * Wrapper for element matcher lambdas that allows to build string explaining to the developer what
+ * conditions are being tested.
+ *
+ * @param description a string explaining to the developer what conditions were being tested.
+ * @param matcher a lambda performing the actual logic of matching on the layout element.
+ */
+public class LayoutElementMatcher(
+ internal val description: String,
+ private val matcher: (LayoutElement) -> Boolean
+) {
+ /** Returns whether the given element is matched by this matcher. */
+ internal fun matches(element: LayoutElement): Boolean = matcher(element)
+
+ /**
+ * Returns whether the given element is matched by both this and the other mather.
+ *
+ * @param other mather that should also match in addition to current matcher.
+ */
+ public infix fun and(other: LayoutElementMatcher): LayoutElementMatcher =
+ LayoutElementMatcher("($description) && (${other.description})") {
+ matcher(it) && other.matches(it)
+ }
+
+ /**
+ * Returns whether the given element is matched by this or the other mather.
+ *
+ * @param other mather that can be tested to match if the current matcher does not.
+ */
+ public infix fun or(other: LayoutElementMatcher): LayoutElementMatcher =
+ LayoutElementMatcher("($description) || (${other.description})") {
+ matcher(it) || other.matches(it)
+ }
+
+ /** Returns whether the given element does not match the matcher. */
+ public operator fun not(): LayoutElementMatcher =
+ LayoutElementMatcher("NOT ($description)") { !matcher(it) }
+}
diff --git a/wear/protolayout/protolayout-testing/src/main/java/androidx/wear/protolayout/testing/helpers.kt b/wear/protolayout/protolayout-testing/src/main/java/androidx/wear/protolayout/testing/helpers.kt
new file mode 100644
index 0000000..de300bc
--- /dev/null
+++ b/wear/protolayout/protolayout-testing/src/main/java/androidx/wear/protolayout/testing/helpers.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.protolayout.testing
+
+import androidx.wear.protolayout.LayoutElementBuilders.Box
+import androidx.wear.protolayout.LayoutElementBuilders.Column
+import androidx.wear.protolayout.LayoutElementBuilders.LayoutElement
+import androidx.wear.protolayout.LayoutElementBuilders.Row
+
+internal val LayoutElement.children: List<LayoutElement>
+ get() =
+ when (this) {
+ is Box -> this.contents
+ is Row -> this.contents
+ is Column -> this.contents
+ // TODO b/372916396 - Dealing with Arc container and ArcLayoutElements
+ else -> emptyList<LayoutElement>()
+ }
+
+internal fun searchElement(root: LayoutElement?, matcher: LayoutElementMatcher): LayoutElement? {
+ if (root == null) return null
+ if (matcher.matches(root)) return root
+ return root.children.firstNotNullOfOrNull { searchElement(it, matcher) }
+}
diff --git a/wear/protolayout/protolayout-testing/src/main/java/androidx/wear/protolayout/testing/package-info.java b/wear/protolayout/protolayout-testing/src/main/java/androidx/wear/protolayout/testing/package-info.java
index cef88a5..46afd06 100644
--- a/wear/protolayout/protolayout-testing/src/main/java/androidx/wear/protolayout/testing/package-info.java
+++ b/wear/protolayout/protolayout-testing/src/main/java/androidx/wear/protolayout/testing/package-info.java
@@ -14,8 +14,4 @@
* limitations under the License.
*/
-// TODO(b/371013214): Make protolayout-testing library public.
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
package androidx.wear.protolayout.testing;
-
-import androidx.annotation.RestrictTo;
diff --git a/wear/protolayout/protolayout-testing/src/test/java/androidx/wear/protolayout/testing/LayoutElementAssertionTest.kt b/wear/protolayout/protolayout-testing/src/test/java/androidx/wear/protolayout/testing/LayoutElementAssertionTest.kt
new file mode 100644
index 0000000..924796a
--- /dev/null
+++ b/wear/protolayout/protolayout-testing/src/test/java/androidx/wear/protolayout/testing/LayoutElementAssertionTest.kt
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.protolayout.testing
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.wear.protolayout.LayoutElementBuilders.Box
+import androidx.wear.protolayout.LayoutElementBuilders.Text
+import com.google.common.truth.ExpectFailure.assertThat
+import org.junit.Assert.assertThrows
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.annotation.internal.DoNotInstrument
+
+@RunWith(AndroidJUnit4::class)
+@DoNotInstrument
+class LayoutElementAssertionTest {
+
+ @Test
+ fun assertExists_success() {
+ val assertion = LayoutElementAssertion(ELEMENT_DESCRIPTION, Box.Builder().build())
+
+ assertion.assertExists() // no error
+ }
+
+ @Test
+ fun assertExists_error() {
+ val assertion = LayoutElementAssertion(ELEMENT_DESCRIPTION, null)
+
+ val assertionError = assertThrows(AssertionError::class.java) { assertion.assertExists() }
+
+ assertThat(assertionError)
+ .hasMessageThat()
+ .isEqualTo("Expected $ELEMENT_DESCRIPTION to exist, but it does not.")
+ }
+
+ @Test
+ fun assertDoesNotExist_success() {
+ val assertion = LayoutElementAssertion(ELEMENT_DESCRIPTION, null)
+
+ assertion.assertDoesNotExist() // no error
+ }
+
+ @Test
+ fun assertDoesNotExist_error() {
+ val assertion = LayoutElementAssertion(ELEMENT_DESCRIPTION, Box.Builder().build())
+
+ val assertionError =
+ assertThrows(AssertionError::class.java) { assertion.assertDoesNotExist() }
+
+ assertThat(assertionError)
+ .hasMessageThat()
+ .isEqualTo("Expected $ELEMENT_DESCRIPTION to not exist, but it does.")
+ }
+
+ @Test
+ fun assert_withMatcher_success() {
+ val assertion = LayoutElementAssertion(ELEMENT_DESCRIPTION, Box.Builder().build())
+ assertion.assert(LayoutElementMatcher("Element type is Box") { it is Box })
+ }
+
+ @Test
+ fun assert_withMatcher_error() {
+ val assertion = LayoutElementAssertion(ELEMENT_DESCRIPTION, Box.Builder().build())
+ val matcher = LayoutElementMatcher("Element type is Text") { it is Text }
+
+ val assertionError = assertThrows(AssertionError::class.java) { assertion.assert(matcher) }
+
+ assertThat(assertionError)
+ .hasMessageThat()
+ .isEqualTo(
+ "Expected $ELEMENT_DESCRIPTION to match '${matcher.description}'," +
+ " but it does not."
+ )
+ }
+
+ @Test
+ fun chainAssertions() {
+ val textContent = "testing text"
+ val assertion =
+ LayoutElementAssertion(ELEMENT_DESCRIPTION, Text.Builder().setText(textContent).build())
+ val typeMatcher = LayoutElementMatcher("Element type is Text") { it is Text }
+ val contentMatcher =
+ LayoutElementMatcher("Element text = '$textContent'") {
+ it is Text && it.text?.value == textContent
+ }
+
+ assertion.assert(typeMatcher).assert(contentMatcher)
+ }
+
+ @Test
+ fun chainAssertions_failureInFirst() {
+ val textContent = "testing text"
+ val assertion =
+ LayoutElementAssertion(ELEMENT_DESCRIPTION, Text.Builder().setText(textContent).build())
+ val firstMatcher = LayoutElementMatcher("Element type is Box") { it is Box }
+ val secondMatcher = LayoutElementMatcher("Element type is Text") { it is Text }
+
+ val assertionError =
+ assertThrows(AssertionError::class.java) {
+ assertion.assert(firstMatcher).assert(secondMatcher)
+ }
+
+ assertThat(assertionError)
+ .hasMessageThat()
+ .isEqualTo(
+ "Expected $ELEMENT_DESCRIPTION to match " +
+ "'${firstMatcher.description}', " +
+ "but it does not."
+ )
+ }
+
+ @Test
+ fun chainAssertions_failureInSecond() {
+ val textContent = "testing text"
+ val assertion =
+ LayoutElementAssertion(ELEMENT_DESCRIPTION, Text.Builder().setText(textContent).build())
+ val firstMatcher = LayoutElementMatcher("Element type is Text") { it is Text }
+ val secondMatcher = LayoutElementMatcher("Element type is Box") { it is Box }
+
+ val assertionError =
+ assertThrows(AssertionError::class.java) {
+ assertion.assert(firstMatcher).assert(secondMatcher)
+ }
+
+ assertThat(assertionError)
+ .hasMessageThat()
+ .isEqualTo(
+ "Expected $ELEMENT_DESCRIPTION to match " +
+ "'${secondMatcher.description}', " +
+ "but it does not."
+ )
+ }
+
+ companion object {
+ const val ELEMENT_DESCRIPTION = "testing element"
+ }
+}
diff --git a/wear/protolayout/protolayout-testing/src/test/java/androidx/wear/protolayout/testing/LayoutElementAssertionsProviderTest.kt b/wear/protolayout/protolayout-testing/src/test/java/androidx/wear/protolayout/testing/LayoutElementAssertionsProviderTest.kt
new file mode 100644
index 0000000..036ef7d
--- /dev/null
+++ b/wear/protolayout/protolayout-testing/src/test/java/androidx/wear/protolayout/testing/LayoutElementAssertionsProviderTest.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.protolayout.testing
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.wear.protolayout.LayoutElementBuilders.Box
+import androidx.wear.protolayout.LayoutElementBuilders.Column
+import androidx.wear.protolayout.LayoutElementBuilders.Image
+import androidx.wear.protolayout.LayoutElementBuilders.Layout
+import androidx.wear.protolayout.LayoutElementBuilders.Row
+import androidx.wear.protolayout.LayoutElementBuilders.Text
+import com.google.common.truth.ExpectFailure
+import com.google.common.truth.Truth.assertThat
+import junit.framework.TestCase.assertTrue
+import org.junit.Assert.assertThrows
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.annotation.internal.DoNotInstrument
+
+@RunWith(AndroidJUnit4::class)
+@DoNotInstrument
+class LayoutElementAssertionsProviderTest {
+
+ @Test
+ fun primaryConstructor_onRoot() {
+ assertTrue(LayoutElementAssertionsProvider(TEST_LAYOUT.root!!).onRoot().element is Box)
+ }
+
+ @Test
+ fun secondaryConstructor_onRoot() {
+ assertTrue(LayoutElementAssertionsProvider(TEST_LAYOUT).onRoot().element is Box)
+ }
+
+ @Test
+ fun onRoot_description() {
+ val assertionError =
+ assertThrows(AssertionError::class.java) {
+ LayoutElementAssertionsProvider(TEST_LAYOUT).onRoot().assertDoesNotExist()
+ }
+
+ val rootDescription = "root"
+
+ ExpectFailure.assertThat(assertionError)
+ .hasMessageThat()
+ .isEqualTo("Expected $rootDescription to not exist, but it does.")
+ }
+
+ @Test
+ fun onElement_isImage() {
+ val firstImageElement =
+ LayoutElementAssertionsProvider(TEST_LAYOUT).onElement(isImage).element as Image
+ assertThat(firstImageElement.resourceId!!.value).isEqualTo("image1")
+ }
+
+ @Test
+ fun onElement_isText() {
+ val firstTextElement =
+ LayoutElementAssertionsProvider(TEST_LAYOUT).onElement(isText).element as Text
+ assertThat(firstTextElement.text!!.value).isEqualTo("text1")
+ }
+
+ @Test
+ fun onElement_description() {
+ val assertionError =
+ assertThrows(AssertionError::class.java) {
+ LayoutElementAssertionsProvider(TEST_LAYOUT).onElement(isText).assertDoesNotExist()
+ }
+
+ val elementDescription = "element matching '${isText.description}'"
+
+ ExpectFailure.assertThat(assertionError)
+ .hasMessageThat()
+ .isEqualTo("Expected $elementDescription to not exist, but it does.")
+ }
+
+ companion object {
+ val isBox = LayoutElementMatcher("Element type is Box") { it is Box }
+ val isImage = LayoutElementMatcher("Element type is Image") { it is Image }
+ val isText = LayoutElementMatcher("Element type is Text") { it is Text }
+ val TEST_LAYOUT =
+ Layout.Builder()
+ .setRoot(
+ Box.Builder()
+ .addContent(
+ Row.Builder()
+ .addContent(Image.Builder().setResourceId("image1").build())
+ .addContent(Image.Builder().setResourceId("image2").build())
+ .build()
+ )
+ .addContent(
+ Column.Builder()
+ .addContent(Text.Builder().setText("text1").build())
+ .addContent(Text.Builder().setText("text2").build())
+ .build()
+ )
+ .build()
+ )
+ .build()
+ }
+}
diff --git a/wear/protolayout/protolayout/api/current.txt b/wear/protolayout/protolayout/api/current.txt
index 721be43..7ef6729 100644
--- a/wear/protolayout/protolayout/api/current.txt
+++ b/wear/protolayout/protolayout/api/current.txt
@@ -1242,6 +1242,17 @@
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.ResourceBuilders.AndroidImageResourceByResId.Builder setResourceId(@DrawableRes int);
}
+ @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=500) public static final class ResourceBuilders.AndroidLottieResourceByResId {
+ method public androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat? getProgress();
+ method @RawRes public int getRawResourceId();
+ }
+
+ public static final class ResourceBuilders.AndroidLottieResourceByResId.Builder {
+ ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=500) public ResourceBuilders.AndroidLottieResourceByResId.Builder(@RawRes int);
+ method public androidx.wear.protolayout.ResourceBuilders.AndroidLottieResourceByResId build();
+ method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=500) public androidx.wear.protolayout.ResourceBuilders.AndroidLottieResourceByResId.Builder setProgress(androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat);
+ }
+
@SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public static final class ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId {
method public int getAnimatedImageFormat();
method public androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat? getProgress();
@@ -1258,6 +1269,7 @@
@androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final class ResourceBuilders.ImageResource {
method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental public androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId? getAndroidAnimatedResourceByResId();
+ method public androidx.wear.protolayout.ResourceBuilders.AndroidLottieResourceByResId? getAndroidLottieResourceByResId();
method public androidx.wear.protolayout.ResourceBuilders.AndroidImageResourceByResId? getAndroidResourceByResId();
method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental public androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId? getAndroidSeekableAnimatedResourceByResId();
method public androidx.wear.protolayout.ResourceBuilders.InlineImageResource? getInlineResource();
@@ -1267,6 +1279,7 @@
ctor public ResourceBuilders.ImageResource.Builder();
method public androidx.wear.protolayout.ResourceBuilders.ImageResource build();
method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public androidx.wear.protolayout.ResourceBuilders.ImageResource.Builder setAndroidAnimatedResourceByResId(androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId);
+ method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=500) public androidx.wear.protolayout.ResourceBuilders.ImageResource.Builder setAndroidLottieResourceByResId(androidx.wear.protolayout.ResourceBuilders.AndroidLottieResourceByResId);
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.ResourceBuilders.ImageResource.Builder setAndroidResourceByResId(androidx.wear.protolayout.ResourceBuilders.AndroidImageResourceByResId);
method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public androidx.wear.protolayout.ResourceBuilders.ImageResource.Builder setAndroidSeekableAnimatedResourceByResId(androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId);
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.ResourceBuilders.ImageResource.Builder setInlineResource(androidx.wear.protolayout.ResourceBuilders.InlineImageResource);
diff --git a/wear/protolayout/protolayout/api/restricted_current.txt b/wear/protolayout/protolayout/api/restricted_current.txt
index 721be43..7ef6729 100644
--- a/wear/protolayout/protolayout/api/restricted_current.txt
+++ b/wear/protolayout/protolayout/api/restricted_current.txt
@@ -1242,6 +1242,17 @@
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.ResourceBuilders.AndroidImageResourceByResId.Builder setResourceId(@DrawableRes int);
}
+ @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=500) public static final class ResourceBuilders.AndroidLottieResourceByResId {
+ method public androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat? getProgress();
+ method @RawRes public int getRawResourceId();
+ }
+
+ public static final class ResourceBuilders.AndroidLottieResourceByResId.Builder {
+ ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=500) public ResourceBuilders.AndroidLottieResourceByResId.Builder(@RawRes int);
+ method public androidx.wear.protolayout.ResourceBuilders.AndroidLottieResourceByResId build();
+ method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=500) public androidx.wear.protolayout.ResourceBuilders.AndroidLottieResourceByResId.Builder setProgress(androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat);
+ }
+
@SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public static final class ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId {
method public int getAnimatedImageFormat();
method public androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat? getProgress();
@@ -1258,6 +1269,7 @@
@androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final class ResourceBuilders.ImageResource {
method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental public androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId? getAndroidAnimatedResourceByResId();
+ method public androidx.wear.protolayout.ResourceBuilders.AndroidLottieResourceByResId? getAndroidLottieResourceByResId();
method public androidx.wear.protolayout.ResourceBuilders.AndroidImageResourceByResId? getAndroidResourceByResId();
method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental public androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId? getAndroidSeekableAnimatedResourceByResId();
method public androidx.wear.protolayout.ResourceBuilders.InlineImageResource? getInlineResource();
@@ -1267,6 +1279,7 @@
ctor public ResourceBuilders.ImageResource.Builder();
method public androidx.wear.protolayout.ResourceBuilders.ImageResource build();
method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public androidx.wear.protolayout.ResourceBuilders.ImageResource.Builder setAndroidAnimatedResourceByResId(androidx.wear.protolayout.ResourceBuilders.AndroidAnimatedImageResourceByResId);
+ method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=500) public androidx.wear.protolayout.ResourceBuilders.ImageResource.Builder setAndroidLottieResourceByResId(androidx.wear.protolayout.ResourceBuilders.AndroidLottieResourceByResId);
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.ResourceBuilders.ImageResource.Builder setAndroidResourceByResId(androidx.wear.protolayout.ResourceBuilders.AndroidImageResourceByResId);
method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public androidx.wear.protolayout.ResourceBuilders.ImageResource.Builder setAndroidSeekableAnimatedResourceByResId(androidx.wear.protolayout.ResourceBuilders.AndroidSeekableAnimatedImageResourceByResId);
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.ResourceBuilders.ImageResource.Builder setInlineResource(androidx.wear.protolayout.ResourceBuilders.InlineImageResource);
diff --git a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/ResourceBuilders.java b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/ResourceBuilders.java
index 8fdc539..72d7ef0 100644
--- a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/ResourceBuilders.java
+++ b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/ResourceBuilders.java
@@ -26,6 +26,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
+import androidx.annotation.RawRes;
import androidx.annotation.RestrictTo;
import androidx.annotation.RestrictTo.Scope;
import androidx.wear.protolayout.TriggerBuilders.Trigger;
@@ -518,6 +519,118 @@
}
}
+ /** A Lottie resource that is read from a raw Android resource ID. */
+ @RequiresSchemaVersion(major = 1, minor = 500)
+ public static final class AndroidLottieResourceByResId {
+ private final ResourceProto.AndroidLottieResourceByResId mImpl;
+
+ AndroidLottieResourceByResId(ResourceProto.AndroidLottieResourceByResId impl) {
+ this.mImpl = impl;
+ }
+
+ /** Gets the Android resource ID, e.g. R.raw.foo. */
+ @RawRes
+ public int getRawResourceId() {
+ return mImpl.getRawResourceId();
+ }
+
+ /**
+ * Gets a {@link androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat},
+ * normally transformed from certain states with the data binding pipeline to control the
+ * progress of the animation.
+ */
+ @Nullable
+ public DynamicFloat getProgress() {
+ if (mImpl.hasProgress()) {
+ return DynamicBuilders.dynamicFloatFromProto(mImpl.getProgress());
+ } else {
+ return null;
+ }
+ }
+
+ /** Creates a new wrapper instance from the proto. */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @NonNull
+ public static AndroidLottieResourceByResId fromProto(
+ @NonNull ResourceProto.AndroidLottieResourceByResId proto) {
+ return new AndroidLottieResourceByResId(proto);
+ }
+
+ /** Returns the internal proto instance. */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @NonNull
+ public ResourceProto.AndroidLottieResourceByResId toProto() {
+ return mImpl;
+ }
+
+ @Override
+ @NonNull
+ public String toString() {
+ return "AndroidLottieResourceByResId{"
+ + "rawResourceId="
+ + getRawResourceId()
+ + ", progress="
+ + getProgress()
+ + "}";
+ }
+
+ /** Builder for {@link AndroidLottieResourceByResId} */
+ public static final class Builder {
+ private final ResourceProto.AndroidLottieResourceByResId.Builder mImpl =
+ ResourceProto.AndroidLottieResourceByResId.newBuilder();
+
+ /**
+ * Creates an instance of {@link Builder}.
+ *
+ * @param resourceId the Android resource ID, e.g. R.raw.foo.
+ */
+ @RequiresSchemaVersion(major = 1, minor = 500)
+ @SuppressLint("CheckResult") // (b/247804720)
+ public Builder(@RawRes int resourceId) {
+ setRawResourceId(resourceId);
+ }
+
+ @RequiresSchemaVersion(major = 1, minor = 500)
+ Builder() {}
+
+ /** Sets the Android resource ID, e.g. R.raw.foo. */
+ @RequiresSchemaVersion(major = 1, minor = 500)
+ @NonNull
+ Builder setRawResourceId(@RawRes int rawResourceId) {
+ mImpl.setRawResourceId(rawResourceId);
+ return this;
+ }
+
+ /**
+ * Sets a {@link androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat},
+ * normally transformed from certain states with the data binding pipeline to control
+ * the progress of the animation.
+ *
+ * <p>Its value is required to fall in the range of [0.0, 1.0]. Any values outside this
+ * range would be clamped.
+ *
+ * <p>When the first value of the {@link
+ * androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat} arrives, the
+ * animation starts from progress 0 to that value. After that it plays from current
+ * progress to the new value on subsequent updates.
+ *
+ * <p>If not set, the animation will play on load.
+ */
+ @RequiresSchemaVersion(major = 1, minor = 500)
+ @NonNull
+ public Builder setProgress(@NonNull DynamicFloat progress) {
+ mImpl.setProgress(progress.toDynamicFloatProto());
+ return this;
+ }
+
+ /** Builds an instance from accumulated values. */
+ @NonNull
+ public AndroidLottieResourceByResId build() {
+ return AndroidLottieResourceByResId.fromProto(mImpl.build());
+ }
+ }
+ }
+
/**
* An image resource, which can be used by layouts. This holds multiple underlying resource
* types, which the underlying runtime will pick according to what it thinks is appropriate.
@@ -581,6 +694,17 @@
}
}
+ /** Gets a Lottie resource that is read from a raw Android resource ID. */
+ @Nullable
+ public AndroidLottieResourceByResId getAndroidLottieResourceByResId() {
+ if (mImpl.hasAndroidLottieResourceByResId()) {
+ return AndroidLottieResourceByResId.fromProto(
+ mImpl.getAndroidLottieResourceByResId());
+ } else {
+ return null;
+ }
+ }
+
/** Creates a new wrapper instance from the proto. */
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
@@ -608,6 +732,8 @@
+ getAndroidAnimatedResourceByResId()
+ ", androidSeekableAnimatedResourceByResId="
+ getAndroidSeekableAnimatedResourceByResId()
+ + ", androidLottieResourceByResId="
+ + getAndroidLottieResourceByResId()
+ "}";
}
@@ -665,6 +791,15 @@
return this;
}
+ /** sets a Lottie resource that is read from a raw Android resource ID. */
+ @RequiresSchemaVersion(major = 1, minor = 500)
+ @NonNull
+ public Builder setAndroidLottieResourceByResId(
+ @NonNull AndroidLottieResourceByResId androidLottieResourceByResId) {
+ mImpl.setAndroidLottieResourceByResId(androidLottieResourceByResId.toProto());
+ return this;
+ }
+
/** Builds an instance from accumulated values. */
@NonNull
public ImageResource build() {
diff --git a/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/ResourceBuildersTest.java b/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/ResourceBuildersTest.java
index 7f63205..3591875 100644
--- a/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/ResourceBuildersTest.java
+++ b/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/ResourceBuildersTest.java
@@ -65,4 +65,18 @@
assertThat(avdProto.getAnimatedImageFormat().getNumber()).isEqualTo(FORMAT);
assertThat(avdProto.getProgress().getStateSource().getSourceKey()).isEqualTo(stateKey);
}
+
+ @Test
+ public void lottieAnimation() {
+ String stateKey = "state-key";
+ ResourceBuilders.AndroidLottieResourceByResId lottieResource =
+ new ResourceBuilders.AndroidLottieResourceByResId.Builder(RESOURCE_ID)
+ .setProgress(DynamicBuilders.DynamicFloat.from(new AppDataKey<>(stateKey)))
+ .build();
+
+ ResourceProto.AndroidLottieResourceByResId avdProto = lottieResource.toProto();
+
+ assertThat(avdProto.getRawResourceId()).isEqualTo(RESOURCE_ID);
+ assertThat(avdProto.getProgress().getStateSource().getSourceKey()).isEqualTo(stateKey);
+ }
}